SpringBoot自动装配原理
1. 早期的spring解决的问题
早期的spring,解决了Bean的自动注入问题
package com.lchtest.spirngbootautoconfigprinciple.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { // 加入spring-boot-starter-data-redis依赖,能够自动注入的前提是,RedisTemplate一定存在与IOC容器中(DI) // 解决了Bean的注入问题 @Autowired private RedisTemplate<String,String> redisTemplate; @RequestMapping("/") public Object index(){ return "hello springboot."; } }
2. Enable* 注解
是springframework中提供的开启自动装配的注解,装配的是容器中的Bean
springboot-starter 提供一种标注,核心自动装配(JavaConfig) springboot通过JavaConfig 将springframework变的无配置化。
3. springboot如何自动注入bean
随便定义一个bean HelloService
package com.lchtest.spirngbootautoconfigprinciple.demo1; public class HelloService { public String say(){ return "HelloService"; } }
编写一个javaConfig配置类:
@Configuration声明这是一个配置类
@Bean注解会以方法名为bean的名字(helloService),把return出来的对象自动注入到IOC容器中,
package com.lchtest.spirngbootautoconfigprinciple.demo1; import com.lchtest.spirngbootautoconfigprinciple.demo02.OtherConfig; import org.springframework.context.annotation.*; import org.springframework.stereotype.Component; /** * 声明一个配置类 * 通过JavaConfig形式完全取代Spring xml形式来提供bean的注入 * 这个配置类等价于Spring xml配置中的 * <bean id="helloService" class="com.lchtest.spirngbootautoconfigprinciple.demo1.HelloService"></bean> * 这里不能够使用@Component注解,@Component是通过@ComponentScan注解来生效的 */ @Configuration public class SpringConfig { @Bean public HelloService helloService(){ return new HelloService(); } }
测试类:
package com.lchtest.spirngbootautoconfigprinciple.demo1; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class ConfigMain { public static void main(String[] args) { // AnnotationConfigApplicationContext这个类会解析JavaConfig类中的所有注解,将其注册到IOC容器中 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 从IOC容器中获取对象 HelloService helloService = context.getBean(HelloService.class); System.out.println(helloService.say()); } }
运行代码,控制台会输出HelloService
4. @Import注解
现在定义另外一个配置类,用来注入一个其他的bean到IOC容器中,在ConfigMain 中看一下能否获取到bean
package com.lchtest.spirngbootautoconfigprinciple.demo02; public class OtherClass { }
package com.lchtest.spirngbootautoconfigprinciple.demo02; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OtherConfig { @Bean public OtherClass otherClass(){ return new OtherClass(); } }
package com.lchtest.spirngbootautoconfigprinciple.demo1; import com.lchtest.spirngbootautoconfigprinciple.demo02.OtherClass; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class ConfigMain { public static void main(String[] args) { // AnnotationConfigApplicationContext这个类会解析JavaConfig类中的所有注解,将其注册到IOC容器中 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 从IOC容器中获取对象 HelloService helloService = context.getBean(HelloService.class); System.out.println(helloService.say()); // 直接这样获取是取不到的,context默认扫描的是SpringConfig这个配置类 OtherClass otherClass = context.getBean(OtherClass.class); System.out.println(otherClass); } }
运行,结果如下,这是是因为只扫描了SpringConfig这个配置类,如何才能扫描到OtherClass 对应的配置类呢
这里需要使用@Import注解
@Import() 注解导入另外一个配置类,相当于两个配置类合并 ,跟spring相比,等价于xml配置中的标签!
package com.lchtest.spirngbootautoconfigprinciple.demo1; import com.lchtest.spirngbootautoconfigprinciple.demo02.OtherConfig; import org.springframework.context.annotation.*; import org.springframework.stereotype.Component; @Configuration @Import(value= OtherConfig.class) public class SpringConfig { @Bean public HelloService helloService(){ return new HelloService(); } }
再次运行ConfigMain 代码:
5. @Conditional注解
首先自定义一个类,这个类实现org.springframework.context.annotation.Condition接口,返回true 或者false
package com.lchtest.spirngbootautoconfigprinciple.demo1; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; public class DefineConditional implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return false; } }
然后修改SpringConfig 类:
package com.lchtest.spirngbootautoconfigprinciple.demo1; import com.lchtest.spirngbootautoconfigprinciple.demo02.OtherConfig; import org.springframework.context.annotation.*; import org.springframework.stereotype.Component; @Configuration // @Import() 注解导入另外一个配置类,相当于两个配置类合并 ,跟spring相比,等价于xml配置中的<import></import>标签! @Import(value= OtherConfig.class) public class SpringConfig { @Conditional(DefineConditional.class) @Bean public HelloService helloService(){ return new HelloService(); } }
@Conditional 注解的作用是条件判断,是注入HelloService的前提,如果 注解的值 DefineConditional类中返回的是true,才注入helloService 这个bean,否则不注入,DefineConditional 类中已经返回了false,再来运行一下ConfigMain:
@Conditional作用: 如果要加载HelloService,那么必须要提前加载某个类; 或者只能在某个环境下才能被加载
6. springboot 自动装配原理-@EnableAutoConfiguration
- @Conditional
- @Configuration
- @Import
- AutoConfigurationImportSelector
在pom中加入redis starter依赖spring-boot-starter-data-redis 就可以使用 RedisTemplate来操作redis了,
那么RedisTemplate是怎么被加载到spring容器中的,找到RedisAutoConfiguration 这个类
package org.springframework.boot.autoconfigure.data.redis; import java.net.UnknownHostException; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; @Configuration(proxyBeanMethods = false) @ConditionalOnClass({RedisOperations.class}) // 必须存在RedisOperations这个类时,RedisAutoConfiguration 才能被加载 @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
可以看到注入redisTemplate的代码,RedisAutoConfiguration 这个配置类又是什么时候被装载的呢?有两种情况,一种是 @ConditionalOnClass
@ConditionalOnClass 是spring官方提供的starter的装配方式 ,以spring-boot-starter-XXX 开头的是官方提供的starter自动装配,XXX-spring-boot-starter 是第三方提供的starter
对于redis , 很明显,RedisOperations这个类存在时,RedisAutoConfiguration类才会被加载,RedisOperations如何才能存在呢,我们仅仅是在pom中引入了spring-boot-starter-data-redis依赖。因此可以推断出,引入了spring-boot-starter-data-redis,一定产生了一个RedisOperations类,触发@ConditionalOnClass的条件满足,才会接着自动装配redisTemplate。也就是说,通过引入一个jar包的方式来触发自动装配
如果是第三方的starter,那么autoconfiguration类是否需要自己手写?
在pom中加入mybatis依赖,会在org.mybatis.spring.boot.autoconfigure这个包下有一个MybatisAutoConfiguration类
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency>
可以看到这个类注入的bean
如上图,应该存在某种机制使得spring会去扫描这些 starter的 XXXAutoConfiguration类,实现bean的自动装配
1.那么如何去扫描不同的starter中的配置类
2.如果官方提供了配置类,自己需要自定义一些配置,自己的配置类和官方的配置类有冲突,如何实现选择性的装配?
@SpringBootApplication(exclude = “”)
选择性装配 ImportSelector
ImportSelector 选择性的将一些bean装载到spring容器中
SPI (java)
现在,创建一个类TestService,再定义一个注解EnableDefineService ,我们期望在spring容器启动的时候,加上EnableDefineService 这个注解,能够把TestService的实例给自动注册到spring IOC容器中去,如何实现:
package com.lchtest.spirngbootautoconfigprinciple.demo3; public class TestService { }
package com.lchtest.spirngbootautoconfigprinciple.demo3; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage public @interface EnableDefineService { }
package com.lchtest.spirngbootautoconfigprinciple.demo3; import com.lchtest.spirngbootautoconfigprinciple.demo02.OtherClass; import com.lchtest.spirngbootautoconfigprinciple.demo1.HelloService; import com.lchtest.spirngbootautoconfigprinciple.demo1.SpringConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @EnableDefineService // 启用TestService的注入 @SpringBootApplication public class BootStrap { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(BootStrap.class); TestService bean = context.getBean(TestService.class); System.out.println(bean); } }
启动运行,报错
此时,定义一个MyDefineImportSelector 类,实现ImportSelector,返回的数组中,把需要被装配的的对象TestService放进去:
package com.lchtest.spirngbootautoconfigprinciple.demo3; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; public class MyDefineImportSelector implements ImportSelector { // 选择性导入 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 判断逻辑,对需要被装配的对象,写到数组里面去 // 数组元素是需要被装配的的对象 return new String[]{TestService.class.getName()}; } }
注解需要修改一下 ,加上@Import(MyDefineImportSelector.class)
package com.lchtest.spirngbootautoconfigprinciple.demo3; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(MyDefineImportSelector.class) public @interface EnableDefineService { }
此时再去运行,发现TestService的bean被注入了! 仅仅是重写了selectImports方法,然后在EnableDefineService注解里面加上了@Import(MyDefineImportSelector.class) 这个注解,spring就可以帮我们完成自动注入了!
反过来,它的执行流程应该是,启动时,发现有一个EnableDefineService注解,EnableDefineService注解中又有一个@Import(MyDefineImportSelector.class) ,于是spring又去导入MyDefineImportSelector,拿到数组中的类,然后进行装配,注入到IOC容器
通过上面的实验,可以发现关键的代码就是这一段:
public class MyDefineImportSelector implements ImportSelector { // 选择性导入 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 判断逻辑,对需要被装配的对象,写到数组里面去 // 数组元素是需要被装配的的对象 return new String[]{TestService.class.getName()}; } }
对上面这段代码,可以引申一下,如果这里导入的是starter包中的类呢? 最大的问题是,这些starter包中的自动配置又从哪里来呢? 它应该也是来自于starter包。starter包又有很多路径,应该从哪里获取呢? 有个东西叫spring.factories, 它是Spring 的SPI机制,里面有个SpringFactoriesLoader,会扫描classpath路径下 /META-INF/spring.factories文件指定key的value。这个key就是指定的要被装配的类。那么这个类被扫描之后应该会返回一个values[] 数组,里面存放了所有的配置类,然后spring容器去扫描并加载配置类中的bean。接下来,我们从springboot的源码中去验证:
找到一个项目的主类:
@SpringBootApplication public class SpirngbootAutoconfigPrincipleApplication { public static void main(String[] args) { SpringApplication.run(SpirngbootAutoconfigPrincipleApplication.class, args); } }
可以看到上面只有一个@SpringBootApplication 注解,该注解是一个复合注解:
package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})}) public @interface SpringBootApplication { @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class,attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; @AliasFor( annotation = Configuration.class) boolean proxyBeanMethods() default true; }
上面又有一个注解@EnableAutoConfiguration,点进去,它里面是这样的:
package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
我们重点关注下@Import({AutoConfigurationImportSelector.class}) 这个注解中的类,由前面的实验,可以知道@Import注解中的类,里面会返回一个values数组,这个数组元素就是要装配的类,那么AutoConfigurationImportSelector也应该返回要被装配的类,看看AutoConfigurationImportSelector这个类做了什么,点进去,很明显有一个selectImports方法(返回自动配置类的)
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }
找到真正干活的方法:
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); 点进去:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { AnnotationAttributes attributes = this.getAttributes(annotationMetadata); // 获取候选配置类的名字 Candidate中文意为候选人,候选项 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); // 去除重复的配置类 configurations = this.removeDuplicates(configurations); // 获取手动配置的不需要装配的类 Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); // 移除手动配置的不需要装配的类 configurations.removeAll(exclusions); // 过滤,得到最终需要自动装配的类 configurations = this.filter(configurations, autoConfigurationMetadata); // this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }
进入getCandidateConfigurations方法,发现只有一行代码:
SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }
再点进去,SpringFactoriesLoader类中loadFactoryNames方法里面有这么关键的一行:
Enumeration urls = classLoader != null ? classLoader.getResources(“META-INF/spring.factories”) : ClassLoader.getSystemResources(“META-INF/spring.factories”);
它的作用是找到类路径下的所有spring.factories,返回一个集合,这就验证了springboot自动装配的配置类来源,是spring.factories中预先定义好的配置类的全路径名
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
验证spring.factories :
创建一个配置类TestAutoConfiguration,加上@Configuration注解
package com.lchtest.spirngbootautoconfigprinciple.demo3; import org.springframework.context.annotation.Configuration; @Configuration public class TestAutoConfiguration { }
在resources路径下创建一个META-INF目录,再创建一个spring.factories, 加上下图这个配置:
在org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry 方法里的 this.fireAutoConfigurationImportEvents(configurations, exclusions);这一行加上断点,然后debug启动,可以看到,自定义的TestAutoConfiguration类被读取到了!
7.springboot自动装配图解
代码地址:
https://github.com/liuch0228/springboot/tree/master/springbootdemo/spirngboot-autoconfig-principle
- 点赞
- 收藏
- 分享
- 文章举报
- SpringBoot启动流程分析(五):SpringBoot自动装配原理实现
- SpringBoot自动装配原理初探
- springboot自动装配原理-以redis为例
- Springboot自动装配原理
- SpringBoot 2.2.2 源码详解(二):自动装配原理
- SpringBoot自动装配原理解析
- springboot自动装配原理详解
- Java的注解机制——Spring自动装配的实现原理
- springboot自动配置原理
- 浅谈springboot自动配置原理
- Spring Boot 自动配置原理
- Java的注解机制——Spring自动装配的实现原理
- Spring Boot自动配置原理与实践(一)
- 我是如何做到springboot自动配置原理解析
- Spring boot 自动配置原理
- Spring Boot自动配置原理
- [Spring Boot] 4. Spring Boot实现自动配置的原理
- springBoot 自动配置原理
- 第5章 Spring Boot自动配置原理
- 《Spring Boot 实战:从0到1》第3章 Spring Boot自动配置原理