您的位置:首页 > 编程语言 > Java开发

SpringBoot自动装配原理

2020-03-05 10:48 543 查看

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

  • 点赞
  • 收藏
  • 分享
  • 文章举报
小白杨0520 发布了37 篇原创文章 · 获赞 0 · 访问量 579 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: