先导知识:
1. Spring IoC容器
IoC:控制反转
DI:依赖注入
Spring框架负责控制对象的生命周期和对象间的关系。
IoC使对象只需要关心业务逻辑本身,这些互相依赖的对象的创建和协调等工作交给Spring容器来做。对象如何得到它的协作对象的责任就被反转给Spring了。告诉Spring容器这里需要某个bean,它就会丢个实例过来。
容器中的每个bean都有一个对应的BeanDefinition实例,负责保存bean对象的所有必要信息。如bean 对象的 class 类型、是否是抽象类、构造方法和参数、其它属性等等。
BeanFactory:getBean、containBean、getType、getAliases
BeanDefinitionRegistry:registerBeanDefinition、removeBeanDefinition、getBeanDefinition
DefaultListableBeanFactory实现了BeanFactory和BeanDefinitionRegistry接口,负责bean的注册管理工作。
Spring IoC容器的工作流程主要分为两阶段:启动阶段和实例化阶段。
容器启动时会通过一些工具类等加载Configuration MetaData,将其解析后组装成相应的BeanDefinition,然后将保存了bean信息的BeanDefinition注册到BeanDefinitionRegistry。
经过第一阶段,所有 bean 定义都通过 BeanDefinition 的方式注册到 BeanDefinitionRegistry 中,当某个请求通过容器的 getBean 方法请求某个对象,或者因为依赖关系容器需要隐式的调用 getBean 时,就会触发第二阶段的活动:容器会首先检查所请求的对象之前是否已经实例化完成。如果没有,则会根据注册的 BeanDefinition 所提供的信息实例化被请求对象,并为其注入依赖。当该对象装配完毕后,容器会立即将其返回给请求方法使用。
BeanFactory 只是 Spring IoC 容器的一种实现,如果没有特殊指定,它采用采用延迟初始化策略:只有当访问容器中的某个对象时,才对该对象进行初始化和依赖注入操作。而实际上更多的使用另外一种类型的容器: ApplicationContext
,它是Bean-Factory 的一个实现类,除了具有 BeanFactory 的所有能力之外,还提供对事件监听机制以及国际化的支持等。它管理的 bean,在容器启动时全部完成初始化和依赖注入操作。
IoC容器负责管理容器中所有bean的生命周期,而在bean生命周期的不同阶段,Spring提供了不同的扩展点来改变bean的命运。
在容器的启动阶段, BeanFactoryPostProcessor
允许我们在容器实例化相应对象之前,对注册到容器的 BeanDefinition 所保存的信息做一些额外的操作,比如修改 bean 定义的某些属性或者增加其他信息等。
比如BeanFactoryPostProcessor的一个实现:PropertyPlaceholderConfigurer。当BeanFactory在第一阶段加载完所有配置信息时,BeanFactory中保存的对象的属性还是以占位符方式存在的,比如 ${jdbc.mysql.url}
。当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的值来替换相应的BeanDefinition中占位符所表示的属性值。当需要实例化bean时,bean定义中的属性值就已经被替换成我们配置的值。
另外还有BeanPostProcessor,工作在bean的实例化过程中。
bean的生命周期:。。。
https://www.jianshu.com/p/3944792a5fff
2. 注解篇
@ConfigurationProperties: 这个注解就是为什么龙凯让我在.properties文件中用authclient.appkey和authclient.appsecret的原因。
@EnableConfigurationProperties:表示对@ConfigurationProperties的内嵌支持,默认会将对应Properties Class作为bean注入的IoC容器中,即在相应的Properties类上不用加@Component注解。
...
3.ClassLoader&SpringFactoriesLoader
BootstrapClassLoader
、 ExtClassLoader
、 AppClassLoader
分别加载Java核心类库、扩展类库以及应用的类路径( CLASSPATH
)下的类库。JVM通过双亲委派模型进行类的加载,我们也可以通过继承 java.lang.classloader
实现自己的类加载器。
双亲委派模型:当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务时,才会尝试自己来加载。
双亲委派模型的优点:保证使用不同类加载器最终得到的都是同一个对象,这样就可以保证Java 核心库的类型安全,比如,加载位于rt.jar包中的 java.lang.Object
类,不管是哪个加载器加载这个类,最终都是委托给顶层的BootstrapClassLoader来加载的,这样就可以保证任何的类加载器最终得到的都是同样一个Object对象
双亲委派模型不能解决的事情:比如,Java 提供了很多服务提供者接口( ServiceProviderInterface
,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP 等,这些SPI的接口由核心类库提供,却由第三方实现,这样就存在一个问题:SPI 的接口是 Java 核心库的一部分,是由BootstrapClassLoader加载的;SPI实现的Java类一般是由AppClassLoader来加载的。BootstrapClassLoader是无法找到 SPI 的实现类的,因为它只加载Java的核心库。它也不能代理给AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。
怎么办呢:线程上下文类加载器( ContextClassLoader
)正好解决了这个问题。从名称上看,可能会误解为它是一种新的类加载器,实际上,它仅仅是Thread类的一个变量而已,可以通过 setContextClassLoader(ClassLoadercl)
和 getContextClassLoader()
来设置和获取该对象。如果不做任何的设置,Java应用的线程的上下文类加载器默认就是AppClassLoader。在核心类库使用SPI接口时,传递的类加载器使用线程上下文类加载器,就可以成功的加载到SPI实现的类。
除了加载类之外还可以加载资源:
首先判断父类加载器是否为空,不为空则委托父类加载器执行资源查找任务,直到BootstrapClassLoader,最后才轮到自己查找。而不同的类加载器负责扫描不同路径下的jar包,就如同加载class一样,最后会扫描所有的jar包,找到符合条件的资源文件。类加载器的 findResources(name)
方法会遍历其负责加载的所有jar包,找到jar包中名称为name的资源文件,这里的资源可以是任何文件,甚至是.class文件
SpringFactoriesLoader:
从 CLASSPATH
下的每个Jar包中搜寻所有 META-INF/spring.factories
配置文件,然后将解析properties文件,找到指定名称的配置后返回。执行 loadFactoryNames(EnableAutoConfiguration.class,classLoader)
后,得到对应的一组 @Configuration
类, 我们就可以通过反射实例化这些类然后注入到IOC容器中,最后容器里就有了一系列标注了 @Configuration
的JavaConfig形式的配置类。
4. Spring 的监听机制
To be continued...