一步一步揭开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

猜你喜欢

转载自blog.csdn.net/weixin_41300437/article/details/104443887