三、自动配置原理入门

三、自动配置原理入门

3. 自动配置原理

3.1. @SpringBootApplication

主程序上的 @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 {...}

其中前四个是元注解,重点是在 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 这三个注解身上。

3.1.1. @SpringBootConfiguration

@SpringBootConfiguration 的源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {...}
  • 前四个:元注解
  • @Configuration:代表 SpringBootConfiguration 是一个配置类
  • @Indexed:Spring Framework 5.0 引入的一个注解,旨在为 Spring 的模式注解添加索引,以提高启动性能
3.1.2. @EnableAutoConfiguration

@EnableAutoConfiguration 的源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	...
}
  • 前四个:元注解

@AutoConfigurationPackage

@AutoConfigurationPackage 的源码:

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

Registrar 的源码

/**
 * Class for storing auto-configuration packages for reference later (e.g. by JPA entity
 * scanner).
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Oliver Gierke
 * @since 1.0.0
 */
public abstract class AutoConfigurationPackages {
	...
    /**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.
	 */
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}
		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}
	}
	...
}

metadata 包含以下的内容:

在这里插入图片描述

表明这个注解是标记在 MainApp 这个类上边的。然后通过这个信息获取 MainApp 所在的包名,如下:

在这里插入图片描述

综上所述:

  • 利用 Registrar 向容器中导入一批组件
  • 导入的组件就是主程序所在的包及其子包 (上述示例的 MainApp 所在的包及其子包)

@Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector 的源码:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
	
    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(AutoConfigurationImportSelector.class) 的作用是向容器中导入一批组件。导入的组件就是 autoConfigurationEntry.getConfigurations() 转成为 String 数组后,该数组中的内容。autoConfigurationEntry 是 getAutoConfigurationEntry(annotationMetadata) 的返回值。所以往下走,

getAutoConfigurationEntry(annotationMetadata) 的源码:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        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.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

源码中,getCandidateConfigurations(annotationMetadata, attributes) 的返回值经过

Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);

this.checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations = this.getConfigurationClassFilter().filter(configurations);

this.fireAutoConfigurationImportEvents(configurations, exclusions);

去掉一些排除项,并且经过

AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);

封装之后的返回结果。所以继续往下走,

getCandidateConfigurations(annotationMetadata, attributes) 的源码:

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(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()) 加载器加载一些类,从而得到 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(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()) 的结果,所以继续往下走:

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 {
            Enumeration urls = classLoader.getResources("META-INF/spring.factories");

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

Enumeration urls = classLoader.getResources(“META-INF/spring.factories”) 会读取 META-INF/spring.factories 文件,从而根据配置文件的内容加载所有的组件。spring.factories 文件的位置:

spring-boot-autoconfigure-2.5.5.jar\META-INF/spring.factories

内容如下:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
...\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

在这个版本(2.5.5)下应该是 132 个,而且格式都是 XXXAutoConfiguration 。这 132 个在启动时默认全部都有,但是最终会按 @Confitional 的派生类所指定的条件生效(参照 3.2 自动装配流程)。

3.1.3. @ComponentScan

指定要扫描哪些内容。使用 @SpringBootConfiguration 注解了主程序则不需要再使用 @ComponentScan 去指定扫描的范围,因为会自动扫描主程序所在的包及其子包。

3.2. 自动配置流程

3.2.1. 生效的配置

拿 DispatcherServlet 举例子。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {...}
  • @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE):指定该配置类的优先级
  • @Configuration(proxyBeanMethods = false):标明该类是个配置类
  • @ConditionalOnWebApplication(type = Type.SERVLET):标明该类是一个 Servlet 类型的 Web 工程(原生类型:Spring MVC)
    • 与之对应的就是 spring-boot 的响应式编程:导入的是 Spring WebFlux
  • @ConditionalOnClass(DispatcherServlet.class):存在 DispatcherServlet 这个类,导入 spring MVC 必定会存在
  • @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class):标明在 ServletWebServerFactoryAutoConfiguration 配置完成后才进行配置

只有在上述的条件都通过了,下面的才会被执行:

@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

    @Bean
    @ConditionalOnBean(MultipartResolver.class)
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
        // Detect if the user has created a MultipartResolver but named it incorrectly
        return resolver;
    }

}
  • Configuration(proxyBeanMethods = false):这是个配置类

  • Conditional(DefaultDispatcherServletCondition.class):通过 DefaultDispatcherServletCondition 判断是否要实例化

  • ConditionalOnClass(ServletRegistration.class):存在 ServletRegistration 类的情况,Tomcat 组件被导入

  • @EnableConfigurationProperties(WebMvcProperties.class):绑定配置文件

    • @ConfigurationProperties(
          prefix = "spring.mvc"
      )
      public class WebMvcProperties {...}
      
  • dispatcherServlet 方法:实例化一个 DispatcherServlet 并且经过初始设置并且返回,默认名(dispatcherServlet)

  • multipartResolver 方法(文件上传解析器):

    • @Bean:声明为组件
    • @ConditionalOnBean(MultipartResolver.class):如果容器中存在这种类型的组件,则返回
    • @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME):不存在名字为 multipartResolver 的组件
    • 给 @Bean 标记的方法传递参数的话,那么这个参数的值是会从容器中相应的组件返回

    这三个注解是为了担心用户自定义的文件上传解析器不符合规范。

3.2.2. 不生效的配置

拿 Aop 举例子。

在这里插入图片描述

有些包没有导进来,就会导致这些配置不会生效。

这个是需要导入以下的内容,也就是需要导入 aspectj 相关的依赖:

import org.aspectj.weaver.Advice;
3.2.3. 自定义配置

方式一:

使用 @Bean 替换底层组件。

方式二:

去修改组件的配置文件的默认值,以达到预期的效果。

方式二的示例:将编码格式指定为 UTF-8

  1. 查看编码格式的底层组件绑定配置文件的前缀

    • @Configuration(
          proxyBeanMethods = false
      )
      @EnableConfigurationProperties({ServerProperties.class})
      @ConditionalOnWebApplication(
          type = Type.SERVLET
      )
      @ConditionalOnClass({CharacterEncodingFilter.class})
      @ConditionalOnProperty(
          prefix = "server.servlet.encoding",
          value = {"enabled"},
          matchIfMissing = true
      )
      public class HttpEncodingAutoConfiguration {
      
    • 查官方文档,所有的都列出来了

  2. 修改配置文件

    resources\application.properties

    # encoding format
    server.servlet.encoding.charset=UTF-8
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值