SpringBoot自动配置详解

刚创建好的SpringBoot项目,只有一个Springboot01Application类,就可以直接运行。SpringBoot帮我们完成了很多工作。我们来看看这个类:

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

Springboot01Application类上有一个SpringBootApplication注解,点进@SpringBootApplication,@Target、@Retention、@Documented、@Inherited均为java元注解,详见 java元注解

//此SpringBootApplication注解的注解目标是接口、类、枚举、注解
@Target({ElementType.TYPE})
//注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Retention(RetentionPolicy.RUNTIME)
//会包含在javadoc中
@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,
        attribute = "exclude"
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class,
        attribute = "excludeName"
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

@SpringBootConfiguration实际是一个加上@Configuration的注解,@Configuration注解是一个加上@Component的注解。@SpringBootConfiguration作用就是标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。

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


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

接着来看@EnableAutoConfiguration注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
//@Import,给容器中导入一个组件
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

这里的@Import往容器中导入了一个EnableAutoConfigurationImportSelector类,点开EnableAutoConfigurationImportSelector的父类AutoConfigurationImportSelector,这个类中有个selectImports方法,这个方法调用getCandidateConfigurations方法得到一个String类型的List,在getCandidateConfigurations方法中,调用loadFactoryNames方法,并将EnableAutoConfiguration.class传入:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!this.isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	} else {
		try {
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
			//关键代码:获取候选的配置
			List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
			configurations = this.removeDuplicates(configurations);
			configurations = this.sort(configurations, autoConfigurationMetadata);
			Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
			this.checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
			configurations = this.filter(configurations, autoConfigurationMetadata);
			this.fireAutoConfigurationImportEvents(configurations, exclusions);
			return (String[])configurations.toArray(new String[configurations.size()]);
		} catch (IOException var6) {
			throw new IllegalStateException(var6);
		}
	}
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //getSpringFactoriesLoaderFactoryClass返回EnableAutoConfiguration.class
    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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

loadFactoryNames方法的执行步骤:

  • 在jar包类路径下找META-INF/spring.factories这个文件
  • 将文件中的内容封装成Properties对象
  • 从properties中获取到EnableAutoConfiguration类中对应的值,添加到容器中
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    //此时factoryClassName值为:org.springframework.boot.autoconfigure.EnableAutoConfiguration
    String factoryClassName = factoryClass.getName();

    try {
        Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
        ArrayList result = new ArrayList();

        while(urls.hasMoreElements()) {
            URL url = (URL)urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }

        return result;
    } catch (IOException var8) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
    }
}

就是将类路径下  META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中

下面是IDEA中找这个文件:

文件中定义了很多XXAutoConfiguration命名自动配置类,他们都被添加到容器中。

接着,每一个自动配置类进行自动配置功能。

以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理:

//表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration   
/*启动指定类的ConfigurationProperties功能;将配置文件中对应的值和
HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中*/
@EnableConfigurationProperties(HttpEncodingProperties.class)  
//Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;
//判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication 
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)  
//判断配置文件中是否存在某个配置  spring.http.encoding.enabled;如果不存在,判断也是成立的
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)  
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {
  
  	//他已经和SpringBoot的配置文件映射了
  	private final HttpEncodingProperties properties;
  
  	//只有一个有参构造器的情况下,参数的值就会从容器中拿
  	public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
		this.properties = properties;
	}
	//给容器中添加一个组件,这个组件的某些值需要从properties中获取
    	@Bean   
	//判断容器没有这个组件,如果自己实现了一个并放入容器,这时这个方法则不调用
	@ConditionalOnMissingBean(CharacterEncodingFilter.class) 
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}

根据当前不同的条件判断,决定这个配置类是否生效。

一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的。

所有在配置文件中能配置的属性都是在xxProperties类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类

@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Charset charset;
    private Boolean force;
    private Boolean forceRequest;
    private Boolean forceResponse;
    private Map<Locale, Charset> mapping;

 在配置文件中,可以配置这些:

 

总结:

  1. SpringBoot启动会加载大量的自动配置类
  2. 我们看我们需要的功能有没有SpringBoot默认写好的自动配置类
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值

另外附上@Conditional注解的派生注解:

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

 

猜你喜欢

转载自blog.csdn.net/xcy1193068639/article/details/81390204