作者: Grey

原文地址:Spring的轻量级实现

本文是参考公众号:码农翻身 的从零开始造Spring 教程的学习笔记

源码

github

开发方法

使用TDD的开发方法,TDD的开发流程是:

  1. 写一个测试用例

  2. 运行:失败

  3. 写Just enough的代码,让测试通过

  4. 重构代码保持测试通过,

然后循环往复。

说明

  • 仅实现核心功能

  • 基于spring-framework-3.2.18.RELEASE版本

通过XML实例化一个对象

解析XML文件,拿到Bean的id和完整路径,通过反射方式实例化一个对象。

XML格式如下,文件名为:bean-v1.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="userService" class="org.spring.service.v1.UserService"></bean>
  7. </beans>

需要解析上述XML并生成userService对象,调用者只需要做如下调用即可:

  1. public class BeanFactoryV1Test {
  2. @Test
  3. public void testGetBean() {
  4. BeanFactory factory = new DefaultBeanFactory("bean-v1.xml");
  5. UserService userService = (UserService) factory.getBean("userService");
  6. assertNotNull(userService);
  7. }
  8. }

思路为:

解析XML,并把XML中的类通过反射方式生成对象,最后,把这个生成的对象放到一个Map中,其中Map的key为beanId,如上例就是:userService, Map的Value是UserService的全路径org.spring.service.v1.UserService

实现细节参考代码见:step1

基础工作和基本封装

  • 增加日志支持:log4j2 + SLF4j
  • 增加异常处理,所有异常的父类设计为BeansException
  • 封装BeanDefinition

由于DefaultBeanFactory中的BEAN_MAP目前只包括了beanClassName信息,后续如果要扩展其他的信息,肯定需要增加字段,所以我们需要抽象出一个接口BeanDefinition,方便后续扩展其他的字段。

  • 封装Resource

在BeanFactory初始化的时候,传入的是XML格式的配置信息,比如bean-v1.xml, Spring会把这个抽象成一个Resource,常见Resource有

FileSystemResource: 从文件地址读配置

ClassPathResource: 从classpath下读配置

BeanFactory在创建Bean的时候,只关注Resource即可。

实现细节参考代码见:vstep4-2-resource

封装XML的解析逻辑和Bean的注册逻辑

设计XmlBeanDefinitionReader,用于解析XML,传入Resource,即可获取所有BeanDefinition

  1. public void loadBeanDefinitions(Resource resource) {
  2. // 从Resource中获取所有的BeanDefinition
  3. // 注册到BEAN_MAP中
  4. }

由于要把BeanDefinition放入BEAN_MAP中,所以XmlBeanDefinitionReader需要持有一个DefaultBeanFactory,且DefaultBeanFactory需要有注册BeanDefinition和获取BeanDefintion的能力,这样DefaultBeanFactory的职责就不单一了,所以需要抽象出一个BeanDefinitionRegistry,这个BeanDefinitionRegistry专门负责注册BeanDefinition和获取BeanDefintion

  1. public interface BeanDefinitionRegistry {
  2. /**
  3. * 注册Bean
  4. * @param beanId
  5. * @param beanDefinition
  6. */
  7. void registerBeanDefinition(String beanId, BeanDefinition beanDefinition);
  8. }

XmlBeanDefinitionReader只需要持有BeanDefinitionRegistry,即可将解析生成的BeanDefinition注入BEAN_MAP中。

实现细节参考代码见:vstep5-final

单例多例模式的配置实现

XML文件中会增加一个属性,如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="userService" class="org.spring.service.v1.UserService"></bean>
  7. <bean id="orgService" class="org.spring.service.v1.OrgService" scope="prototype"></bean>
  8. </beans>

其中orgService这个bean配置成了prototype的属性,所以在BeanDefinition这个数据结构要增加是否单例,是否多例的逻辑

  1. public interface BeanDefinition {
  2. ...
  3. boolean isSingleton();
  4. boolean isPrototype();
  5. ...
  6. }

DefaultBeanFactory调用getBean的时候,判断是否单例,如果是单例,则复用对象,如果是多例,则new新的对象。

  1. @Override
  2. public Object getBean(String beanId) {
  3. // TODO bean存在与否判断
  4. // TODO 异常处理
  5. // TODO 构造函数带参数
  6. BeanDefinition definition = BEAN_MAP.get(beanId);
  7. if (definition.isSingleton()) {
  8. Object bean = this.getSingleton(beanId);
  9. if(bean == null){
  10. bean = createBean(definition);
  11. this.registerSingleton(beanId, bean);
  12. }
  13. return bean;
  14. }
  15. return createBean(definition);
  16. }

抽象SingletonBeanRegistry这个接口,专门用于注册和获取单例对象,

  1. public interface SingletonBeanRegistry {
  2. void registerSingleton(String beanName, Object singletonObject);
  3. Object getSingleton(String beanName);
  4. }

DefaultSingletonBeanRegistry实现这个接口,实现对单例对象的注册

  1. public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
  2. // TODO 考虑线程安全的容器
  3. private final Map<String, Object> singletonObjects = new HashMap<>();
  4. @Override
  5. public void registerSingleton(String beanName, Object singletonObject) {
  6. // 注册单例Bean
  7. ...
  8. }
  9. @Override
  10. public Object getSingleton(String beanName) {
  11. // 获取单例Bean
  12. return this.singletonObjects.get(beanName);
  13. }
  14. }

DefaultBeanFactory继承DefaultSingletonBeanRegistry这个类,就有了获取单例Bean和注册单例Bean的能力。

实现细节参考代码见:vstep6-scope

整合并抽象出ApplicationContext

我们使用Spring的时候,一般是这样做的:

  1. ApplicationContext ctx = new ClassPathXmlApplicationContext("mycontainer.xml");
  2. UserService userService = (UserService) ctx.getBean("userService");

  1. ApplicationContext ctx = new FileSystemApplicationContext("src\\test\\resources\\bean-v1.xml");
  2. UserService userService = (UserService) ctx.getBean("userService");

现在,我们需要抽象出ApplicationContext这个接口来实现如上的功能,其中有如下两个类去实现这个接口。

ClassPathXmlApplicationContext

从classpath中读取配置文件

FileSystemApplicationContext

从文件中读取配置文件

这两个子类都需要持有DefaultBeanFactory才能有getBean的能力,

ClassPathXmlApplicationContext代码如下:

  1. public class ClassPathXmlApplicationContext implements ApplicationContext {
  2. private final DefaultBeanFactory factory;
  3. public ClassPathXmlApplicationContext(String configPath) {
  4. factory = new DefaultBeanFactory();
  5. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
  6. reader.loadBeanDefinitions(new ClassPathResource(configPath));
  7. }
  8. @Override
  9. public Object getBean(String beanId) {
  10. return factory.getBean(beanId);
  11. }
  12. }

FileSystemApplicationContext代码如下:

  1. public class FileSystemApplicationContext implements ApplicationContext {
  2. private final DefaultBeanFactory factory;
  3. public FileSystemApplicationContext(String configPath) {
  4. factory = new DefaultBeanFactory();
  5. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
  6. reader.loadBeanDefinitions(new FileSystemResource(configPath));
  7. }
  8. @Override
  9. public Object getBean(String beanId) {
  10. return factory.getBean(beanId);
  11. }
  12. }

实现细节参考代码见:vstep7-applicationcontext-v1

通过观察发现,ClassPathXmlApplicationContextFileSystemApplicationContext大部分代码都是相同的,只有在获取Resource的时候,方法不一样,所以,我们通过模板方法这个设计模式,设计一个抽象类AbstractApplicationContext,代码如下:

  1. public abstract class AbstractApplicationContext implements ApplicationContext {
  2. private DefaultBeanFactory factory;
  3. public AbstractApplicationContext(String configPath) {
  4. factory = new DefaultBeanFactory();
  5. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
  6. reader.loadBeanDefinitions(getResourceByPath(configPath));
  7. }
  8. @Override
  9. public Object getBean(String beanId) {
  10. return factory.getBean(beanId);
  11. }
  12. protected abstract Resource getResourceByPath(String path);
  13. }

这个抽象类实现除了获取Resource以外的所有逻辑,ClassPathXmlApplicationContextFileSystemApplicationContext都继承这个抽象类,完成Resource的获取逻辑的编写即可。以FileSystemApplicationContext为例,示例代码如下:

  1. public class FileSystemApplicationContext extends AbstractApplicationContext {
  2. public FileSystemApplicationContext(String configPath) {
  3. super(configPath);
  4. }
  5. @Override
  6. protected Resource getResourceByPath(String path) {
  7. return new FileSystemResource(path);
  8. }
  9. }

实现细节参考代码见:vstep7-applicationcontext-v2

注入Bean和字符串常量

我们需要对于如下类型的XML配置文件进行解析:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="userService" class="org.spring.service.v2.UserService">
  7. <property name="accountDao" ref="accountDao"/>
  8. <property name="itemDao" ref="itemDao"/>
  9. <property name="owner" value="test"/>
  10. <property name="version" value="2"/>
  11. <property name="checked" value="on"/>
  12. </bean>
  13. <bean id="accountDao" class="org.spring.dao.v2.AccountDao">
  14. </bean>
  15. <bean id="itemDao" class="org.spring.dao.v2.ItemDao">
  16. </bean>
  17. </beans>

需要达到的目的就是:可以把整型,字符串类型,简单对象类型注入到一个Bean中,我们需要解决如下两个问题:

第一个问题是:把字符串转成各种各样的Value,比如把String转换成Integer或者转换成Boolean。jdk中java.bean包中的PropertyEditorSupport这个类来完成的,我们新建了CustomBooleanEditorCustomNumberEditor两个类,这两个类都继承于PropertyEditorSupport,分别实现了String类型转换成Boolean类型和String类型转换成Integer类型的功能。其他的类型转换也可以通过类似的方法来实现。然后抽象出了TypeConvert这个接口,并把这些转换器加入一个特定的Map中,Mapkey就是要转换的目标的类型,Value就是对应的转换器的实现类,即可实现类型转换。

  1. public interface TypeConverter {
  2. // TODO 抽象出:TypeMismatchException
  3. <T> T convertIfNecessary(Object value, Class<T> requiredType);
  4. }

第二个问题是:我们调用Bean的setXXX方法把这些Value值set到目标Bean中,做法是抽象出PropertyValue

  1. public class PropertyValue {
  2. private final String name;
  3. private final Object value;
  4. // 省略构造方法和get/set方法
  5. }

BeanDefiniton需要增加方法获取PropertyValue的逻辑,BeanDefiniton的所有子类,例如:GenericBeanDefinition中需要增加

  1. private List<PropertyValue> propertyValues = new ArrayList<>();

在解析XML文件的时候,就需要把List<PropertyValue>识别出来并加入BeanDefinition中(RuntimeBeanReference,TypedStringValue),使用BeanDefinitionValueResolver把对应的PropertyValue给初始化好,如下代码:

  1. public class BeanDefinitionValueResolver {
  2. ...
  3. public Object resolveValueIfNecessary(Object value) {
  4. if (value instanceof RuntimeBeanReference) {
  5. ...
  6. } else if (value instanceof TypedStringValue) {
  7. return ((TypedStringValue) value).getValue();
  8. } else {
  9. //TODO
  10. throw new RuntimeException("the value " + value + " has not implemented");
  11. }
  12. }
  13. ...
  14. }

而setXXX的背后实现利用的是jdk原生java.beans.Introspector来实现,见DefaultBeanFactorypopulateBean方法

  1. private void populateBean(BeanDefinition bd, Object bean) {
  2. ....
  3. try {
  4. for (PropertyValue pv : pvs) {
  5. String propertyName = pv.getName();
  6. Object originalValue = pv.getValue();
  7. Object resolvedValue = valueResolver.resolveValueIfNecessary(originalValue);
  8. BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
  9. PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
  10. for (PropertyDescriptor pd : pds) {
  11. if (pd.getName().equals(propertyName)) {
  12. Object convertedValue = converter.convertIfNecessary(resolvedValue, pd.getPropertyType());
  13. pd.getWriteMethod().invoke(bean, convertedValue);
  14. break;
  15. }
  16. }
  17. }
  18. } catch (Exception ex) {
  19. // TODO 封装Exception
  20. throw new RuntimeException("Failed to obtain BeanInfo for class [" + bd.getBeanClassName() + "]", ex);
  21. }
  22. }

其中

  1. pd.getWriteMethod().invoke(bean, convertedValue);

就是对bean的属性进行赋值操作(即:setXXX方法)

实现细节参考代码见:vstep8-inject

实现构造器注入

处理形如以下的配置:

  1. <bean id="userService" class="org.spring.service.v3.UserService">
  2. <constructor-arg ref="accountDao"/>
  3. <constructor-arg ref="itemDao"/>
  4. <constructor-arg value="1"/>
  5. </bean>
  6. <bean id="accountDao" class="org.spring.dao.v3.AccountDao"></bean>
  7. <bean id="itemDao" class="org.spring.dao.v3.ItemDao"></bean>

和上例中注入Bean和字符串常量一样,我们抽象出ConstructorArgument用于表示一个构造函数信息,每个BeanDefinition中持有这个对象,

  1. public class ConstructorArgument {
  2. private final List<ValueHolder> argumentValues = new LinkedList<>();
  3. public ConstructorArgument() {}
  4. public void addArgumentValue(ValueHolder valueHolder) {
  5. this.argumentValues.add(valueHolder);
  6. }
  7. public List<ValueHolder> getArgumentValues() {
  8. return Collections.unmodifiableList(this.argumentValues);
  9. }
  10. public int getArgumentCount() {
  11. return this.argumentValues.size();
  12. }
  13. public boolean isEmpty() {
  14. return this.argumentValues.isEmpty();
  15. }
  16. /**
  17. * Clear this holder, removing all argument values.
  18. */
  19. public void clear() {
  20. this.argumentValues.clear();
  21. }
  22. public static class ValueHolder {
  23. private Object value;
  24. private String type;
  25. private String name;
  26. // 省略get/set和构造方法
  27. }
  28. }

在解析XML的时候,XmlBeanDefinitionReader需要负责解析出ConstuctorArgumentDefaultBeanFactory通过指定构造函数来生成Bean对象并通过ConstructorResolver注入Bean实例到构造方法中。

  1. public class ConstructorResolver {
  2. ....
  3. public Object autowireConstructor(final BeanDefinition bd) {
  4. // ...通过bd找到一个合适的构造函数
  5. try {
  6. // 找到了一个合适的构造函数,则用这个构造函数初始化Bean对象初始化Bean对象
  7. return constructorToUse.newInstance(argsToUse);
  8. } catch (Exception e) {
  9. // TODO throw new BeanCreationException(bd.getID(), "can't find a create instance using " + constructorToUse); }
  10. throw new RuntimeException(bd.getID() + "can't find a create instance using " + constructorToUse);
  11. }
  12. }
  13. ....
  14. }

注:这里指定的构造函数的查找逻辑为:解析出XML的构造函数的参数列表,和通过反射拿到对应的构造函数的参数列表进行对比(每个参数的类型和个数必须一样)

  1. Constructor<?>[] candidates = beanClass.getConstructors();
  2. BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory);
  3. ConstructorArgument cargs = bd.getConstructorArgument();
  4. TypeConverter typeConverter = new SimpleTypeConverter();
  5. for (int i = 0; i < candidates.length; i++) {
  6. // 匹配参数类型和个数,要完全对应上才可以
  7. Class<?>[] parameterTypes = candidates[i].getParameterTypes();
  8. if (parameterTypes.length != cargs.getArgumentCount()) {
  9. continue;
  10. }
  11. argsToUse = new Object[parameterTypes.length];
  12. boolean result = this.valuesMatchTypes(parameterTypes,
  13. cargs.getArgumentValues(),
  14. argsToUse,
  15. valueResolver,
  16. typeConverter);
  17. if (result) {
  18. constructorToUse = candidates[i];
  19. break;
  20. }
  21. }

实现细节参考代码见:vstep9-constructor

实现注解

实现两个注解:@Component @Autowired(只针对属性注入,暂时不考虑方法注入)

且需要实现如下的XML的解析,即实现某个包下的Bean扫描。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd">
  9. <context:component-scan base-package="org.spring.service.v4,org.spring.dao.v4"></context:component-scan>
  10. </beans>

我们首先需要定义注解Component ,Autowired,代码如下:

  1. @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Autowired {
  5. boolean required() default true;
  6. }
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Component {
  5. String value() default "";
  6. }

其次,我们需要实现一个功能,即:给一个包名,扫描获取到这个包以及子包下面的所有Class,示例代码如下:

  1. public Resource[] getResources(String basePackage) throws IOException {
  2. Assert.notNull(basePackage, "basePackage must not be null");
  3. // 把包名中的.转成/, 即可获取包的路径
  4. String location = ClassUtils.convertClassNameToResourcePath(basePackage);
  5. // TODO ClassLoader cl = getClassLoader();
  6. URL url = Thread.currentThread().getContextClassLoader().getResource(location);
  7. File rootDir = new File(url.getFile());
  8. Set<File> matchingFiles = retrieveMatchingFiles(rootDir);
  9. Resource[] result = new Resource[matchingFiles.size()];
  10. int i = 0;
  11. for (File file : matchingFiles) {
  12. result[i++] = new FileSystemResource(file);
  13. }
  14. return result;
  15. }

主要思路是将包名转换成文件路径,然后递归获取路径下的Class文件。

  1. protected Set<File> retrieveMatchingFiles(File rootDir) throws IOException {
  2. if (!rootDir.exists()) {
  3. // Silently skip non-existing directories.
  4. /*if (logger.isDebugEnabled()) {
  5. logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
  6. }*/
  7. return Collections.emptySet();
  8. }
  9. if (!rootDir.isDirectory()) {
  10. // Complain louder if it exists but is no directory.
  11. /* if (logger.isWarnEnabled()) {
  12. logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
  13. }*/
  14. return Collections.emptySet();
  15. }
  16. if (!rootDir.canRead()) {
  17. /*if (logger.isWarnEnabled()) {
  18. logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
  19. "] because the application is not allowed to read the directory");
  20. }*/
  21. return Collections.emptySet();
  22. }
  23. /*String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
  24. if (!pattern.startsWith("/")) {
  25. fullPattern += "/";
  26. }
  27. fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
  28. */
  29. Set<File> result = new LinkedHashSet<>(8);
  30. doRetrieveMatchingFiles(rootDir, result);
  31. return result;
  32. }
  33. protected void doRetrieveMatchingFiles(File dir, Set<File> result) throws IOException {
  34. File[] dirContents = dir.listFiles();
  35. if (dirContents == null) {
  36. /* if (logger.isWarnEnabled()) {
  37. logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
  38. }*/
  39. return;
  40. }
  41. for (File content : dirContents) {
  42. if (content.isDirectory()) {
  43. if (!content.canRead()) {
  44. /* if (logger.isDebugEnabled()) {
  45. logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
  46. "] because the application is not allowed to read the directory");
  47. }*/
  48. } else {
  49. doRetrieveMatchingFiles(content, result);
  50. }
  51. } else {
  52. result.add(content);
  53. }
  54. }
  55. }

由于注解的Bean不像之前的xml定义的Bean那样,会对Bean配置一个id,所以,这里解析出来的Bean定义需要自动生成一个BeanId(默认先取注解中的value的配置,否则就就是类名第一个字母小写,抽象BeanNameGenerator来专门对Bean定义ID),同时,Spring中单独新建了一个AnnotatedBeanDefinition接口来定义包含注解的BeanDefinition

我们得到了对应的Class文件,我们需要通过某种方式去解析这个Class文件,拿到这个Class中的所有信息,特别是注解信息。可以使用ASM这个来解析Class的信息,用ASM的原生方式解析不太方便,解析ClassMetaDataAnnotation都需要定义一个Visitor,所以Spring抽象了一个接口MetadataReader来封装ASM的实现

  1. public interface MetadataReader {
  2. /**
  3. * Read basic class metadata for the underlying class.
  4. */
  5. ClassMetadata getClassMetadata();
  6. /**
  7. * Read full annotation metadata for the underlying class,
  8. * including metadata for annotated methods.
  9. */
  10. AnnotationMetadata getAnnotationMetadata();
  11. }

然后,我们需要拿到Bean中的所有Field(带注解的),并把他实例化成一个对象,并将这个对象注入目标Bean中,示例代码如下:

  1. public class AutowiredFieldElement extends InjectionElement {
  2. ...
  3. @Override
  4. public void inject(Object target) {
  5. Field field = getField();
  6. try {
  7. DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
  8. Object value = factory.resolveDependency(desc);
  9. if (value != null) {
  10. ReflectionUtils.makeAccessible(field);
  11. field.set(target, value);
  12. }
  13. } catch (Throwable ex) {
  14. // TODO 异常处理 throw new BeanCreationException("Could not autowire field: " + field, ex);
  15. throw new RuntimeException("Could not autowire field: " + field);
  16. }
  17. }
  18. }

针对于XML的解析,新建了一个ScannedGenericBeanDefinition来处理扫描包下的所有Bean定义。

使用AutowiredAnnotationProcessor来将上述流程整合起来,同时涉及Bean生命周期的钩子函数设计, 相关示例代码如下:

  1. public interface BeanPostProcessor {
  2. Object beforeInitialization(Object bean, String beanName) throws BeansException;
  3. Object afterInitialization(Object bean, String beanName) throws BeansException;
  4. }
  1. public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
  2. Object beforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
  3. boolean afterInstantiation(Object bean, String beanName) throws BeansException;
  4. void postProcessPropertyValues(Object bean, String beanName) throws BeansException;
  5. }
  1. public class AutowiredAnnotationProcessor implements InstantiationAwareBeanPostProcessor {
  2. // 实现Bean初始化,并且预留Bean的生命周期的钩子函数
  3. }

关于Bean的生命周期和Bean生命周期中各个钩子函数,参考如下图

实现细节参考代码见:vstep10-annotation-final

实现AOP

即要实现如下XML格式的解析

  1. <context:component-scan
  2. base-package="org.litespring.service.v5,org.litespring.dao.v5">
  3. </context:component-scan>
  4. <bean id="tx" class="org.litespring.tx.TransactionManager" />
  5. <aop:config>
  6. <aop:aspect ref="tx">
  7. <aop:pointcut id="placeOrder" expression="execution(* org.litespring.service.v5.*.placeOrder(..))" />
  8. <aop:before pointcut-ref="placeOrder" method="start" />
  9. <aop:after-returning pointcut-ref="placeOrder" method="commit" />
  10. <aop:after-throwing pointcut-ref="placeOrder" method = "rollback"/>
  11. </aop:aspect>
  12. </aop:config>

首先,我们需要实现如下功能,即,给定一个表达式,然后判断某个类的某个方法是否匹配这个表达式,这需要依赖AspectJ这个组件来实现,具体使用参考AspectJExpressionPointcutPointcutTest这两个类。

其次,我们需要通过Bean的名称("tx")和方法名("start")定位到这个Method,然后反射调用这个Method,具体可参考MethodLocatingFactoryTest

  1. public class MethodLocatingFactoryTest {
  2. @Test
  3. public void testGetMethod() throws Exception{
  4. DefaultBeanFactory beanFactory = new DefaultBeanFactory();
  5. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
  6. Resource resource = new ClassPathResource("bean-v5.xml");
  7. reader.loadBeanDefinitions(resource);
  8. MethodLocatingFactory methodLocatingFactory = new MethodLocatingFactory();
  9. methodLocatingFactory.setTargetBeanName("tx");
  10. methodLocatingFactory.setMethodName("start");
  11. methodLocatingFactory.setBeanFactory(beanFactory);
  12. // 获取到目标方法
  13. Method m = methodLocatingFactory.getObject();
  14. Assert.assertEquals(TransactionManager.class, m.getDeclaringClass());
  15. Assert.assertEquals(m, TransactionManager.class.getMethod("start"));
  16. }
  17. }

然后,我们需要使用AOP Alliance实现指定顺序的链式调用,即根据配置的不同advice顺序调用。

具体可查看ReflectiveMethodInvocationReflectiveMethodInvocationTest这两个类。

  1. @Test
  2. public void testMethodInvocation() throws Throwable{
  3. Method targetMethod = UserService.class.getMethod("placeOrder");
  4. List<MethodInterceptor> interceptors = new ArrayList<>();
  5. interceptors.add(beforeAdvice);
  6. interceptors.add(afterAdvice);
  7. ReflectiveMethodInvocation mi = new ReflectiveMethodInvocation(userService,targetMethod,new Object[0],interceptors);
  8. mi.proceed();
  9. List<String> msgs = MessageTracker.getMsgs();
  10. Assert.assertEquals(3, msgs.size());
  11. Assert.assertEquals("start tx", msgs.get(0));
  12. Assert.assertEquals("place order", msgs.get(1));
  13. Assert.assertEquals("commit tx", msgs.get(2));
  14. }

其中

  1. Assert.assertEquals(3, msgs.size());
  2. Assert.assertEquals("start tx", msgs.get(0));
  3. Assert.assertEquals("place order", msgs.get(1));
  4. Assert.assertEquals("commit tx", msgs.get(2));

就是验证我们配置的advice是否按指定顺序运行。

最后,我们需要实现动态代理,在一个方法前后增加一些逻辑,而不用改动原始代码。如果是普通类就使用CGLib实现,如果有接口的类可以使用JDK自带的动态代理,具体可参考CGlibTestCglibAopProxyTest

实现细节参考代码见:vaop-v3

完整代码

lite-spring

参考资料

从零开始造Spring

Spring的轻量级实现的更多相关文章

  1. Spring 5| 轻量级的开源JavaEE框架

    一.Spring框架的概述 1.Spring是轻量级的开源的JavaEE框架 2.Spring可以解决企业应用开发的复杂性 3.Spring有两个核心的部分:IOC(控制反转)和AOP(面向切面编程) ...

  2. 基于Spring的轻量级工作流框架

    项目地址 码云:https://git.oschina.net/null_584_3382/business-flow-parent github:https://github.com/Athlizo ...

  3. SSH:Struts + Spring + Hibernate 轻量级Java EE企业框架

    Java EE(Java Platform,Enterprise Edition)是sun公司(2009年4月20日甲骨文将其收购)推出的企业级应用程序版本.这个版本以前称为 J2EE.能够帮助我们开 ...

  4. 史上最轻松入门之Spring Batch - 轻量级批处理框架实践

    从 MariaDB 一张表内读 10 万条记录,经处理后写到 MongoDB . Batch 任务模型 具体实现 1.新建 Spring Boot 应用,依赖如下: <!-- Web 应用 -- ...

  5. spring框架-----轻量级的应用开发框架

    一.bean 1.容器实例化 ApplicationContext ac=             new ClassPathXmlApplicationContext("applicati ...

  6. java web后台开发SSM框架(Spring+SpringMVC+MyBaitis)搭建与优化

    一.ssm框架搭建 1.1创建项目 新建项目后规划好各层的包. 1.2导入包 搭建SSM框架所需包百度云链接:http://pan.baidu.com/s/1cvKjL0 1.3整合spring与my ...

  7. Spring知识点提炼

    原文出处: 朱小厮 1. Spring框架的作用 轻量:Spring是轻量级的,基本的版本大小为2MB 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对 ...

  8. Spring之Ioc

    Spring的特性 >> 轻量级(Lightweight)相较于EJB而言Spring是轻量级的容器,不依赖任何web容器 >> 容器(Container) Spring本身不 ...

  9. shh(struts+spring+Hibernate)的搭建

    一.Struts 一.struts的流程 (1)首先,用户在地址栏中输入你的项目访问路径,然后这个请求会发送到服务器,服务器先找到要web.xml的,给web.xml中配置了一个filter过滤器,过 ...

随机推荐

  1. P3507-[POI2010]GRA-The Minima Game【dp,博弈论】

    正题 题目链接:https://www.luogu.com.cn/problem/P3507 题目大意 \(n\)个数,没人轮流取若干个并获得取走的数中最小数的权值,两人的目标都是自己的权值\(-\) ...

  2. IdentityServer4[3]:使用客户端认证控制API访问(客户端授权模式)

    使用客户端认证控制API访问(客户端授权模式) 场景描述 使用IdentityServer保护API的最基本场景. 我们定义一个API和要访问API的客户端.客户端从IdentityServer请求A ...

  3. Docker安装ElasticSearch5.6.8

    前言 因实验室项目需要,准备docker安装个ES , 使用TransportClient练练手,然后死活连接不上 环境准备 系统:centos7 软件:docker ElasticSearch版本: ...

  4. mysql 建表后 重新构建 自增字段 (保留 原有字段结构)

    添加字段 1.去除原id的自增功能:ALTER TABLE A_A MODIFY COLUMN id int(10) NOT NULL FIRST ; 2.添加名称为cstId,类型为bigint的字 ...

  5. 基于Tesseract组件的OCR识别

    基于Tesseract组件的OCR识别 背景以及介绍 欲研究C#端如何进行图像的基本OCR识别,找到一款开源的OCR识别组件.该组件当前已经已经升级到了4.0版本.和传统的版本(3.x)比,4.0时代 ...

  6. 市区择房分析(ArcPy实现)

    1, 背景 如何找到环境好.购物方便.小孩上学方便的居住区地段是购房者最关心的问题.因此购房者就需要从总体上对商品房的信息进行研究分析,选择最适宜的购房地段. 2,目的 学会利用缓冲区分析和叠置分析解 ...

  7. 来说说JPA、Hibernate、Spring Data JPA之间的什么关系?

    目录 JPA Hibernate Spring Data JPA 实践 来说说JPA.Hibernate.Spring Data JPA之间的什么关系 Java 持久层框架访问数据库的方式大致分为两种 ...

  8. 【c++ Prime 学习笔记】目录索引

    第1章 开始 第Ⅰ部分 C++基础 第2章 变量和基本类型 第3章 字符串.向量和数组 第4章 表达式 第5章 语句 第6章 函数 第7章 类 第 Ⅱ 部分 C++标准库 第8章 IO库 第9章 顺序 ...

  9. 395.至少有 K 个重复字符的最长子串

    题目 给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于k .返回这一子串的长度. 示例 1: 输入:s = "aaabb" ...

  10. 计算机网络:HTTP

    计算机网络基础:HTTP 先验知识 HTTP和其他协议的关系 通过下图,了解IP协议,TCP协议,DNS服务在使用HTTP协议通信过程中各自发挥的作用: 服务器处理流程 接受客户端连接 ------& ...