前言
想干嘛
深入了解spring原理,特别是IOC容器是如何实现的?AOP是如何实现的?
手写一个spring迷你版框架,实现容器和AOP机制。
我为什么想这么做
spring是整个java体系中最重要的框架,它整合第三方技术,将所有的技术可以融合在一起协同工作。
想要真正了解spring最核心的两个概念:IOC & AOP
想要通过手写一个简单的spring框架学习设计模式的思想。
完成度
总结一下本框架完成的功能:
IOC容器
beanFactory
作为最重要的组件,充当着spring运行过程中核心中间人的身份。
主要为它完成了:
通过各种姿势获取bean。
存储各式各样的bean实体对象,bean的描述信息等等。
本框架中,主要完成了DefaultListableBeanFactory
ApplicationContext
此容器是与beanFactory同级别重要的组件。它最重要的方法是refresh()方法。此方法会真正启动spring,它会注册相当多的处理器来对bean做处理。
在此容器上,应用了模板方法的设计模式。通过分离出bean的生命周期各个阶段,通过注册bean处理器的方式来为它们做相关操作。
本框架中,完成了AbstractApplicationContext 和 AnnotationConfigApplicationContext 前者是一个父类,主要将bean的生命周期各阶段抽象出来,交由具体的实现类去做。例如像AnnotationConfigApplicationContext就是专门处理通过注解完成的bean注册,依赖注入等操作。类似的还有通过xml配置文件来处理配置文件内的bean,不过本框架中仅完成了AnnotationConfigApplicationContext。
BeanPostProcessor/ BeanFactoryPostProcessor
后处理器分为了bean和beanFactory,前者是对单个bean做操作;后者是对整个工厂内的Bean做操作。
后处理器是spring中真正"干活"的组件,它完成的主要工作有: 扫描包下的bean(工厂后处理器), 对bean的生命周期各阶段进行操作(bean后处理器)
完成的处理器主要有:
工厂后处理器:
ConfigurationClassPostProcessor
主要完成对类文件的扫描,对相关注解的扫描,例如@Configuration,@Component等。
bean后处理器:
AutowiredAnnotationBeanPostProcessor
根据名字也猜得出来,此处理器主要处理@Autowired注解的依赖注入,以及对于@Value的解析以及注入。
CommonAnnotationBeanPostProcessor
此处理器主要处理@Resource注解的依赖注入,@PostConstruct 注解的初始化方法,@PreDestroy注解的销毁前方法。
LazyInjectBeanPostProcessor
此处理器主要解决单例bean对多例bean的依赖注入时懒加载的问题。
AspectBeansInitBeanPostProcessor
此处理器主要解决当依赖注入的对象是代理对象时,对其从sourceBean替换为代理bean的问题。
AnnotationAwareAspectJAutoProxyCreator
此处理器主要处理AOP机制的相关操作。例如生成代理对象。
AOP机制
本框架的AOP中,主要完成了通过注解方式完成对于切面类的织入。
目前已完成的注解有: @Aspect ,@Pointcut,@Aroud,@Before,@After
切点表达式的解析目前完成了三种: execution,@annotation,within
通过AnnotationAwareAspectJAutoProxyCreator这个处理器来完成所有代理对象的生成。注意,此时虽然生成了代理对象,但是我并没有在这个处理器中完成对其的替换,因为仅仅是解析切点表达式,解析各个注解就已经让这个类特别的"胖"了。并且也是考虑程序设计时的单一职责原则。
生成代理对象的方式采用了cglib。原先也是计划使用jdk动态代理的,但是后面遇到了一个无法解决的问题: 那就是对于依赖注入的对象是代理对象时,无法通过filed.set()进行反射注入,因为jdk动态代理生成的对象类型为Proxy$... 我去测试spring是怎么处理的适合,发现它也没有办法。。当我强制使用jdk动态代理,并且对被依赖注入的对象做增强时,它报了这样的错:
Description:
The bean 'testBean1' could not be injected because it is a JDK dynamic proxy
最后我干脆就不用jdk的动态代理了,而是全部采用了cglib。
通过上一步生成了代理对象之后,就可以通过AspectBeansInitBeanPostProcessor这个bean处理器完成对代理对象的替换啦。
遇到的问题
纠结对于应该先实例化再进入生命周期的处理, 还是不实例化直接进入生命周期的处理
如果先实例化,那么对bean生命周期的控制性就比较差。
如果不预先实例化,那么依赖注入的顺序问题难以解决。
最终还是选择了预先实例化,工厂后处理器负责注册@Component等注解的Bean,实例化的动作交给了context容器。
通过autowired 后处理器完成对${}的解析,并且将值注入进对应的注解上面的value里面。但是,通过ImportAwareBeanPostProcessor 完成注入的时候,发现取得的method不是同一个(虽然class对象是同一个)。导致前面做的注解注入value,到ImportAwareBeanPostProcessor 的时候仍然是 ${xxx} ,后面通过将待处理的method加入一个集合结局这个问题。
单例bean注入多例bean的时候,出现注入为null的情况。
解决:
参考spring的处理手段,增加一个@Lazy注解,专门处理注入多例bean.
增加一个bean后处理器里专门处理这一情况。
解析切点表达式,获取目标方法。由于目标方法可能存在多个,并且还有可能存在于不同的类中。所以需要考虑一个合适的数据结构来进行解析后的存储。这里我使用map,以class为Key methods为value.封装于poinCutDefinition中。
如何处理pointCut注解,before..注解。如果它们的value不是目标方法,那么该去pointcut里面找。
需要对这些注解信息做排序,将包含真正全类名的排在前面。
得到了目标方法,就可以考虑增强的操作了。需要将增强方法织入到目标方法去。
织入之前需要判断目标bean是否实现了接口,如果实现了接口统一使用jdk代理。否则使用cglib代理。
还需要考虑before,after,around 它们对原方法的invoke顺序是有差别的。其中around的最不一样。
这里需要考虑beanfactory里的bean有可能被替换的问题。(再次定义一个map用于存储aspect的bean)
在这里获取到代理对象之后,并没有马上返回,而是留给了最后的bean后处理器。让它将aspectBean做最终的返回。
需要考虑依赖注入之后的bean代理失效问题。主要是由于依赖注入拿到的bean和代理生成的bean不统一。
先生成代理对象 还是先依赖注入?
先生成代理对象,再依赖注入:
代理对象生成之后,无法对该对象的原来的field成员变量赋值,(cglib代理对象没有拿到它的成员变量。)
先注入,再生成代理对象:
无法从已有对象生成,而是直接create一个新的。它的原来的成员变量需要通过get来获取。
最终敲定: 先依赖注入,再生成代理对象。到最后的bean处理器的时候,专门处理将原先被依赖注入的bean替换成生成的代理对象。
使用jdk动态代理生成对象,无法设置为依赖注入的bean,因为其类型为proxy类型。而被注入的Bean有可能是xxService类型。
无法解决该问题。
最终我自己测试了一下spring,发现spring用的也是cglib动态代理。
报错如下:
APPLICATION FAILED TO START
Description:
The bean 'testBean1' could not be injected because it is a JDK dynamic proxy
The bean is of type 'com.sun.proxy.$Proxy51' and implements: com.markyao.springbootsource.bean.Tb1 org.springframework.aop.SpringProxy org.springframework.aop.framework.Advised org.springframework.core.DecoratingProxy
Expected a bean of type 'com.markyao.springbootsource.bean.TestBean1' which implements: com.markyao.springbootsource.bean.Tb1
Action:
Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.
也就是说,依赖注入代理对象的情况下,只能使用cglib进行动态代理。
最后
不出意外的话,项目还存在很多bug,如果可以的话欢迎各位朋友玩一下,顺便帮我测试测试,如果有Bug可以及时通知我。
gitee
目前已经将其上传至码云,感兴趣的朋友可以玩玩。
markyao-gitee