SpringBoot自动装配原理

1、首先从启动类入手

package com.wust.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }



}

可以看到main方法运行了一个run()方法,在run方法中必须要传入一个被@SpringBootApplication注解的类。 

  • @SpringBootApplication

@SpringBootApplication注解标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就会运行这个类的main方法来启动SpringBoot项目。

接下来看看@SpringBootApplication注解源码:

@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 {
    ..................
}

不难发现@SpringBootApplication是@SpringBootConfiguration和@EnableAutoConfiguration的组合注解

  • @SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
.................................
}

然来@SpringBootConfiguration注解等同于spring的@Configuration注解,作用是表明被标注的类是spring的配置类。这个注解加上@Bean的注解就等同于spring的配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <bean id="user" class="com.wust.bean.User">

    </bean>
</beans>
  • @EnableAutoConfiguration

首先要清楚这个注解是使注解@ConfigurationProperties生效,当然@ConfigurationProperties也可以和@Component组合使用,使yml配置文件里对象的数据映射到实体类中转化为bean注册到IOC容器中。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
................................
}

通过源码可以看到,它也是一个组合注解。

  • @AutoConfigurationPackage
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
 ..............................
}

这个注解的主要功能是自动配置包,其关键在于使用@Import注解将Register.class的实例注入到IOC容器中.Registrar.class 作用就是将主启动类的所在包及包下面所有子包里面的所有组件注册到Spring容器 ;

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

 

可以看到new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()的作用就是获取标注有@AutoConfigurationPackage注解的类的所在包。也就是说当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package。所以主启动类的位置最好不要乱放。

再来看看AutoConfigurationImportSelector.class这个类,里面有个selectImports的方法,最终将返回的全类名的代表的类注册到IOC容器中。

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

这里就不得不说以下@Import注解。

  • @Import

@Import只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中。其用法有以下三种:

1、直接填class数组方式

@Import({ 类名.class , 类名.class... })
public class Test {

}

2、ImportSelector方式【重点】

2.1、创建User实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @Value("张三")
    private String userName;
    @Value("1111")
    private String password;
}

2.2、创建ImportSelector的实现类

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.function.Predicate;

public class ImportSelectorTest implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.wust.springboot.bean.User"};
    }

    @Override
    public Predicate<String> getExclusionFilter() {
        return null;
    }
}
  • 返回值: 实际上要导入到容器中的组件全类名
  • 参数: AnnotationMetadata表示当前被@Import注解给标注的所有注解信息

2.3、创建测试类,在测试类上使用@Import注解导入ImportSelector的实现类

@SpringBootTest
@Import(ImportSelectorTest.class)
class SpringbootApplicationTests {
    @Resource
    private User user;

    @Test
    void contextLoads() {
        System.out.println(user);
    }
}

 

3、ImportBeanDefinitionRegistrar方式

同样是一个接口,类似于第二种ImportSelector用法具体如下:

3.1、创建ImportBeanDefinitionRegistrar实现类

public class  Test implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //指定bean定义信息(包括bean的类型、作用域...)
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class);
        //注册一个bean指定bean名字(id)
        beanDefinitionRegistry.registerBeanDefinition("user",rootBeanDefinition);
    }
}

后面就只要编写测试类,并在测试类使用@Import注解导入上面的实现类即可。

再来看看AutoConfigurationImportSelector.class里的selectImports方法,可以追溯到getCandidateConfigurations这个方法。

   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;
    }

loadFactoryNames方法

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

再通过loadFactoryNames方法,可以追溯到loadSpringFactories这个静态内部类

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                //去获取一个资源 "META-INF/spring.factories"
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");
                //将读取到的资源遍历,封装成为一个Properties
                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[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

最终会发现这个静态内部类加载了META-INF/spring.factories这个文件,我们再来看看org.springframework.boot.autoconfigure.EnableAutoConfiguration这个jar包下的META-INF/spring.factories

2、HttpEncodingAutoConfiguration

以HttpEncodingAutoConfiguration为例解释自动配置的原理


//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration 

//启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({ServerProperties.class}) 

//Spring底层@Conditional注解
  //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
  //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //。。。。。。。
}
  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件,这些组建就是上面代码中被@Bean标注的方法所生成的实例;

  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;

  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

再进入ServerProperties.class看看。


@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();
    private ServerProperties.ForwardHeadersStrategy forwardHeadersStrategy;
    private String serverHeader;
    private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L);
    private Shutdown shutdown;
    @NestedConfigurationProperty
    private Ssl ssl;
    @NestedConfigurationProperty
    private final Compression compression;
    @NestedConfigurationProperty
    private final Http2 http2;
    private final ServerProperties.Servlet servlet;
    private final ServerProperties.Tomcat tomcat;
    private final ServerProperties.Jetty jetty;
    private final ServerProperties.Netty netty;
    private final ServerProperties.Undertow undertow;
..........................
}

里面包含了很多字段,通过@ConfigurationProperties注解将yml配置文件中以server开头的数据和这个配置类中的对应数据进行绑定,比如:server.port=8080

以上就是自动装配的大致原理,总的来说就是:

  • xxxxAutoConfigurartion:自动配置类;给容器中添加组件
  • xxxxProperties:封装配置文件中相关属性;

 3、@Conditional相关注解

自动配置类必须在一定的条件下才能生效,之所以这样是离不开@Conditional相关注解相关注解的

@ConditionalOnProPerty定义某个指定参数是否满足设定值,满足就加载,不满足就不加载
@ConditionalOnBean仅仅在当前上下文中存在某个对象时,才会实例化一个Bean
@ConditionalOnClass某个class位于类路径上,才会实例化一个Bean
@ConditionalOnExpression当表达式为true的时候,才会实例化一个Bean
@ConditionalOnMissingBean仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean
@ConditionalOnMissingClass某个class类路径上不存在的时候,才会实例化一个Bean
@ConditionalOnNotWebApplication不是web应用

最后我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类debug=true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有满足条件的类)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值