从上篇我们已经大概了解了依赖注入的作用,一句话概括就是自动为我们的类的属性进行设值。如果让你设计你会想到要解决哪些问题呢?下面我简单列出几个需要解决的问题。
1.需要spring为那些类进行依赖注入
也就是需要告诉spring哪些类需要它帮我们进行依赖注入,毕竟并不是所有类都需要依赖注入,比如一些工具类,常量类等是不需要依赖注入的
2.如何确定要被注入的依赖?
通过依赖类型还是通过名称来找到对应的需要被注入依赖呢
3.通过什么方式进行依赖注入或者什么时候进行依赖注入
比如通过属性的setter方法进行注入,还是属性filed设值,或者构造器里进行注入
4.如何保证被注入的依赖的唯一性
比如一个类依赖B接口,那么B接口如果有很多实现该注入哪个实现呢?
5.如果循环依赖了怎么办
比如A依赖了B,B却又依赖了A该如何是好?
针对上面的问题1,首先要确定需要依赖注入的范围可以通过xml配置或者注解指定,比如可以约定所有加了@AutoInject注解的类都需要依赖注入。但是这么做有个问题就是需要扫描项目下所有的类一个个检查有没有@AutoInject注解,为了缩小范围可以再定义一个注解来指定扫描范围,比如定义注解@autoInjectPackage("com.xxx.service")表示只需要扫描com.xxx.service包下的类是否有@AutoInject注解即可,这样就大大缩小了扫描范围提升了性能。
再确定了哪些类需要依赖注入后,第2步肯定就是需要确定到底哪些属性需要依赖注入,比如可以定义注解@autowired只要在属性上加了这个注解就是希望给它自动注入。
做好了这些标记之后接下来就需要把这些信息获取到并维护起来,最起码得维护好哪些类需要依赖注入,且每个类需要注入的属性有哪些,这些属性的类型是什么等等。
接下来就可以创建这些类的对象,并将这些创建好的对象维护起来,然后检查它们需要依赖注入的属性到维护好的对象容器中找到对应的对象设值进去。
看看,依赖注入是不是并不复杂,但是作为一个成熟的开源框架怎么能不考虑扩展性呢?比如如果需要注入的属性对象希望它是个代理对象呢?举个例子,在获取到数据源对象时如果希望它不仅仅是简单的操作数据库,还希望进行数据源相关信息收集呢?或者希望根据执行是sql类型不同在程序端做读写分离呢。这就需要spring在注入对象的时候提供扩展,可以让程序员们任意的注入他们希望注入的对象类型,只要是属性定义的类型的子类型即可。
这也就要求spring需要提供一些扩展能力给开发者去实现,去干预对象的生成和依赖的注入。
接下来我们来看个简单的springboot启动类
java
@SpringBootApplication
public class TestMain {
public static void main(String[] args) {
SpringApplication.run(TestMain.class, args);
}
}
这里的@SpringBootApplication注解的作用大家可以理解为就是我们上面说的@autoInjectPackage("com.xxx.service")注解的作用。细心的朋友可能发现这个注解并没有指定需要扫描的路径,不指定的话讲使用被注解的TestMain所在的包路径作为扫描路径。
这时可能有小伙伴会问了,你怎么知道没指定包路径的话就会使用TestMain类所在的包路径的呢?
要解释这个问题接下来我们要做两件事,第一:找到在哪里使用了@SpringBootApplication注解,第二就是拿到@SpringBootApplication注解到底干了啥。
从注解的使用特性上我们知道,注解是依附于承载它的类上的不论是放在属性上的 注解或者放在方法上的又或者放在类上的注解都需要一个类来承载它,而我们需要获取注解时就必须先获取类。所以我们想知道哪里使用了@SpringBootApplication注解就必须找到spring在哪里处理了TestMain.class。
从SpringApplication.run(TestMain.class, args)一层层点进去不难发现参数TestMain.class最终赋值给了SpringApplication类里的primarySources属性,那我们只需要在获取primarySources的方法上打个断点就可以知道哪里会使用primarySources属性里的TestMain.class类啦。我们发现在SpringApplication类里有个getAllSources方法会返回primarySources属性,那我们就在这个方法上打个断点继续debug。
很快我们发现在SpringApplication#prepareContext里使用了getAllSources方法,在并将获取到的primarySources属性传给了SpringApplication类的load方法,跟随着load方法一步一步debug下去就会发现spring把TestMain.class封装成了BeanDefinition,并放到了DefaultListableBeanFactory的Map类型的beanDefinitionMap属性中了,key为testMain。
到这我们并没有发现到底哪里使用了TestMain.class只知道spring把TestMain.class解析成了 BeanDefinition并且给存起来了,存起来肯定为了后面的使用。既然要使用肯定会获取,那么我们继续从beanDefinitionMap找哪里调用用了它存的key为testMain,value为BeanDefinition,我们发现DefaultListableBeanFactory#getBeanDefinition方法会根据name返回BeanDefinition,那我们就在idea工具里在该方法上打一个带条件的断点 beanName.equles("testMain")继续往下执行,终于被我们发现在ConfigurationClassPostProcessor#processConfigBeanDefinitions调用了DefaultListableBeanFactory#getBeanDefinition获取了testMain的BeanDefinition并用于后续业务逻辑,嗯看着似乎是这里拿了TestMain.class要开始使用了,接下来我们就看这个方法拿estMain的BeanDefinition到底做了什么了。
这篇我并不是想讲spring的源码,如果大家看的有点晕可能是过于关注代码了,因为我并没有贴图去具体解释相关代码。这篇我主要是想说一些思考思路希望能给大家一些参考。
上面我们提到了一个类就是ConfigurationClassPostProcessor,这个类为什么可以处理我们的启动类TestMian.class呢?它的作用又是什么呢?