zoukankan      html  css  js  c++  java
  • Springboot自动配置原理

    自动配置的执行流程

    • 需要先创建SpringApplication,再依据Environment创建出ApplicaitonContext,即创建好容器后,在依据一定的注解优先级顺序实例化bean时,才会调用AutoConfigurationImportSelector的selectImports方法,读取spring.factories中的AutoConfiguration标签的自动配置类进行实例化加入容器中

    • @SpringBootApplication

    1. @SpringBootApplication // 由启动类的@SpringBootApplication开启自动配置
        
        
    2. @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration //标至该类是一个配置类,与@Configuration作用一致
    @EnableAutoConfiguration //启动
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    }
         
    

    从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

    注意:@Configuration注解的配置类有如下要求:

    1. @Configuration不可以是final类型;
    2. @Configuration不可以是匿名类;
    3. 嵌套的configuration必须是静态类。

    一、用@Configuration加载spring
    1.1、@Configuration配置spring并启动spring容器
    1.2、@Configuration启动容器+@Bean注册Bean
    1.3、@Configuration启动容器+@Component注册Bean
    1.4、使用 AnnotationConfigApplicationContext 注册 AppContext 类的两种方法

    1.5、配置Web应用程序(web.xml中配置AnnotationConfigApplicationContext)

    二、组合多个配置类
    2.1、在@configuration中引入spring的xml配置文件
    2.2、在@configuration中引入其它注解配置
    2.3、@configuration嵌套(嵌套的Configuration必须是静态类)
    三、@EnableXXX注解
    四、@Profile逻辑组配置
    五、使用外部变量

    • EnableAutoConfiguration
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage //当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package。
    @Import(AutoConfigurationImportSelector.class) //@EnableAutoConfiguration注解是Spring Boot中配置自动装载的总开关。
    public @interface EnableAutoConfiguration {
    }
    
    

    boot.autoconfigure.EnableAutoConfiguration注解

    -> @Import了一个AutoConfigurationImportSelector实例

    -> AutoConfigurationImportSelector类(implement ImportSelector),实现了selectImports() 方法,用来筛选被@Import的Configuration类(减去exclude等)

    • AutoConfigurationImportSelector.class
    public class AutoConfigurationImportSelector implementsDeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
      //......
      @Override
      publicString[] selectImports(AnnotationMetadata annotationMetadata) {
        // 如果AutoConfiguration没开,返回{}
        if(!isEnabled(annotationMetadata)) {
          returnNO_IMPORTS;
        }
        // 将spring-autoconfigure-metadata.properties的键值对配置载入到PropertiesAutoConfigurationMetadata对象中并返回
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
        // 基于各种配置计算需要import的configuration和exclusion
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
            annotationMetadata);
        returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
      }
          
      // 判断AudoConfiguration是否开启
      protectedbooleanisEnabled(AnnotationMetadata metadata) {
        if(getClass() == AutoConfigurationImportSelector.class) {
          // 如果配置文件中有"spring.boot.enableautoconfiguration",返回该字段的值;否则返回true
          returngetEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
        }
        returntrue;
      }
          
      protectedAutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
          AnnotationMetadata annotationMetadata) {
        if(!isEnabled(annotationMetadata)) {
          returnEMPTY_ENTRY;
        }
        // 获取注解的属性值
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 从META-INF/spring.factories文件中获取EnableAutoConfiguration所对应的configurations,但并不实例化,还要筛选
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 去重,List转Set再转List
        configurations = removeDuplicates(configurations);
        // 从注解的exclude/excludeName属性中获取排除项
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        // 对于不属于AutoConfiguration的exclude报错
        checkExcludedClasses(configurations, exclusions);
        // 从configurations去除exclusions
        configurations.removeAll(exclusions);
        // 所有AutoConfigurationImportFilter类实例化,并再进行一次筛选
        configurations = filter(configurations, autoConfigurationMetadata);
        // 实例化剩下configuration中的类,并把AutoConfigurationImportEvent绑定在所有AutoConfigurationImportListener子类实例上,当fireAutoConfigurationImportEvents事件被触发时,打印出已经注册到spring上下文中的@Configuration注解的类,打印出被阻止注册到spring
        fireAutoConfigurationImportEvents(configurations, exclusions);
        // 返回(configurations, exclusions)组
        return newAutoConfigurationEntry(configurations, exclusions);
      }
      // ......
    }
    
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        
       		// getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class;
    
    		// getBeanClassLoader()这里使用的是AppClassLoader。
    
    		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()  		//getSpringFactoriesLoaderFactoryClass()获取需要配置的类即EnableAutoConfiguration.class
    				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;
    	}
    
    
    • getCandidateConfigurations方法中,SpringFactoriesLoader.loadFactoryNames(),扫描所有jar包类路径下 META-INF/spring.factories,并对相应的key值进行筛选,这里使用的key值为org.springframework.boot.autoconfigure.EnableAutoConfiguration。
    • 把扫描到的这些文件的内容包装成properties对象,以 Properties 类型(即 key-value 形式)配置,就可以将相应的实现类注入 Spirng 容器中(key为factory类型)。从properties中获取到EnableAutoConfiguration.class(类名)对应的值,然后把它们添加在容器中
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    		String factoryTypeName = factoryType.getName();
      		// loadSpringFactories方法是获取所有的springFactories
    		// getOrDefault是通过key,获取到对应的类的集合。因为value是通过逗号相隔的,可以有多个,所以是list
    		// getOrDefault如果存在就返回,如果不存在那么就返回给的默认值
    		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    	}
    
    
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
       
        MultiValueMap<String, String> result = cache.get(classLoader);
       if (result != null) {
          return result;
       }
    
       try {
           // 三目表达式,判断参数classLoader是否为空,如果不为空,那么直接使用传入的classLoader获取META-INF/spring.factories
    			// 如果为空,那么就使用系统的classLoader来获取META-INF/spring.factories
    			// 总之健壮性比较强,
          Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
          result = new LinkedMultiValueMap<>();
          while (urls.hasMoreElements()) {
              // 通过循环遍历所有的META-INF/spring.factories
             URL url = urls.nextElement();
             UrlResource resource = new UrlResource(url);
        // 找到的每个 META-INF/spring.factories 文件都是一个 Properties 文件,将其内容加载到一个 Properties 对象然后处理其中的每个属性
             Properties properties = PropertiesLoaderUtils.loadProperties(resource);
             for (Map.Entry<?, ?> entry : properties.entrySet()) {        
                 // 获取工厂类名称(接口或者抽象类的全限定名)
                 String factoryTypeName = ((String) entry.getKey()).trim();
                   // 将逗号分割的属性值逐个取出,然后放到 结果result 中去
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                   result.add(factoryTypeName, factoryImplementationName.trim());
                }
             }
          }
           
         // 筛选出的结果集Map放入内存中,
          cache.put(classLoader, result);
          return result;
       }
       catch (IOException ex) {
          throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
       }
    
    • 在getConfigurationClassFilter与fireAutoConfigurationImportEvents方法中将其通过SpringFactoriesLoader 中的loadFactories反射对所有的配置进行筛选,实例化,并绑定到AutoConfigurationImportListener子类实例上
    protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
    		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
    	}
    
    protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
    	}
    
    /**
    	 * 通过classLoader从各个jar包的classpath下面的META-INF/spring.factories加载并解析其key-value值,然后创建其给定类型的工厂实现
    	 *
    	 * 返回的工厂通过AnnotationAwareOrderComparator进行排序过的。
    	 * AnnotationAwareOrderComparator就是通过@Order注解上面的值进行排序的,值越高,则排的越靠后
    	 *
    	 * 如果需要自定义实例化策略,请使用loadFactoryNames方法获取所有注册工厂名称。
    	 *
    	 * @param  factoryType 接口或者抽象类的Class对象
    	 * @param  classLoader 用于加载的类加载器(可以是null,如果是null,则使用默认值)
    	 * @throws IllegalArgumentException 如果无法加载任何工厂实现类,或者在实例化任何工厂时发生错误,则会抛出IllegalArgumentException异常
    	 */
    	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
    		// 首先断言,传入的接口或者抽象类的Class对象不能为空
    		Assert.notNull(factoryType, "'factoryType' must not be null");
    		// 将传入的classLoader赋值给classLoaderToUse
    		// 判断classLoaderToUse是否为空,如果为空,则使用默认的SpringFactoriesLoader的classLoader
    		ClassLoader classLoaderToUse = classLoader;
    		if (classLoaderToUse == null) {
    			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    		}
    		// 加载所有的META-INF/spring.factories并解析,获取其配置的factoryNames
    		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
    		if (logger.isTraceEnabled()) {
    			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
    		}
    
    		List<T> result = new ArrayList<>(factoryImplementationNames.size());
    		// 通过反射对所有的配置进行实例化。
    		for (String factoryImplementationName : factoryImplementationNames) {
    			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
    		}
    		AnnotationAwareOrderComparator.sort(result);
    		return result;
    	}
    
    // 实例化工厂,根据
    	@SuppressWarnings("unchecked")
    	private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
    		try {
    			// 根据全限定类名通过反射获取Class对象
    			Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
    			// 判断获取的Class对象是否从factoryType里面来,
    			// 说具体点就是判断我们配置的spring.factories中的权限定类名所对应的类是否是对应的子类或者实现类
    			// 比如
    			// org.springframework.context.ApplicationContextInitializer=
    			// org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,
    			// org.springframework.boot.context.ContextIdApplicationContextInitializer,
    			// 那么他就会验证ConfigurationWarningsApplicationContextInitializer和ContextIdApplicationContextInitializer是否是ApplicationContextInitializer的子类
    //			isAssignableFrom()方法与instanceof关键字的区别总结为以下两个点:
    //			isAssignableFrom()方法是从类继承的角度去判断,instanceof关键字是从实例继承的角度去判断。
    //			isAssignableFrom()方法是判断是否为某个类的父类,instanceof关键字是判断是否某个类的子类。
    			// 如果不是,则会保存
    			if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
    				throw new IllegalArgumentException(
    						"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
    			}
    			// 通过反射的有参构造函数进行实例化:如果直接newInstance的话,那么只能通过空参构造函数进行实例化。
    			// 通过这种方式可以通过不同参数的构造函数进行创建实例,但是这里并没有传入参数,所以调用的是默认空惨构造函数
    			return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
    		}
    		catch (Throwable ex) {
    			throw new IllegalArgumentException(
    				"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
    				ex);
    		}
    	}
    
    • 可见selectImports()是AutoConfigurationImportSelector的核心函数,其核心功能就是获取spring.factories中EnableAutoConfiguration所对应的Configuration类列表,由@EnableAutoConfiguration注解中的exclude/excludeName参数筛选一遍,再由AutoConfigurationImportFilter类所有实例筛选一遍,得到最终的用于Import的configuration和exclusion。

    • 该函数是被谁调用的呢?在org.springframework.context.annotation.ConfigurationClassParser类中被processImports()调用,而processImports()函数被doProcessConfigurationClass()调用。下面从doProcessConfigurationClass() 看起。

    • 上面为@Import()的Spring的注释的底层实现

    • 接着看我们EnableAutoConfiguration.class(类名)对应的值:

    • 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;

    • 每一个自动配置类进行自动配置功能;

    • eg :HttpEncodingAutoConfiguration

    package org.springframework.boot.autoconfigure.web.servlet;
    
    ......
    
    //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
    @Configuration(
        proxyBeanMethods = false
    )
    
    /**
     * 启动指定类的ConfigurationProperties功能;
     * 将配置文件中对应的值和HttpProperties绑定起来;
     * 并把HttpProperties加入到ioc容器中
     */
    @EnableConfigurationProperties({HttpProperties.class})
    
    /**
     * Spring底层@Conditional注解
     * 根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;
     * 判断当前应用是否是web应用,如果是,当前配置类生效
     */
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )
    
    //判断当前项目有没有这个类
    @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();
        }
    
        @Bean     //给容器中添加一个组件,这个组件的某些值需要从properties中获取
        @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;
        }
    
        ......
    
    1. 根据当前不同的条件判断,决定这个配置类是否生效
    2. 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
    3. 相应的配置类在@EnableConfigurationProperties中指定,其会在读取我们写的properties并将值注入类中,由xxxAutoConfiguration 读取 实例化的xxxxProperties类,根据属性生成相应的bean给ApplicationContext调用。

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

    @ConfigurationProperties(
        prefix = "spring.http"
    )
    public class HttpProperties {
        private boolean logRequestDetails;
        private final HttpProperties.Encoding encoding = new HttpProperties.Encoding();
    

    我们配置时的流程:

    • SpringBoot启动会加载大量的自动配置类

    • 我们看我们需要的功能有没有SpringBoot默认写好的自动配置类

    • 再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)

    • 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值

    • xxxxAutoConfigurartion:自动配置类;

      xxxxProperties:封装配置文件中相关属性;

  • 相关阅读:
    进程与线程的区别与联系
    任务、进程、线程
    类、对象、方法、实例方法、类方法
    java 泛型详解
    Java总结篇系列:Java泛型
    html+css+js 实现自动滑动轮播图
    第三篇web前端面试自我介绍(刚毕业的菜鸟)
    怎么写网站的需求文档
    在phpStudy怎么配置虚拟地址
    第二篇web前端面试自我介绍(刚毕业的菜鸟)
  • 原文地址:https://www.cnblogs.com/eternal-heathens/p/13591738.html
Copyright © 2011-2022 走看看