Spring boot的autoConfiguration的实战运用和源码分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

使用场景:

SpringBoot框架里写的SDK代码中有一个XXXFilter拦截器,接入方依赖了这个sdk,想要使用这个拦截器必须要使用@ServletComponentScan(basePackageClasses = XXXFilter.class)才能生效。现在想去掉这句话,使得对接入方使用更加友好。

代码使用:

1、在client层的resources/META-INF/下新建一个spring.factoies的配置文件,告诉SpringBoot:CargoFilterAutoConfiguration要启用自动配置功能。

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.ctrip.ibu.cargo.filter.CargoFilterAutoConfiguration

2、在CargoFilterAutoConfiguration类中执行FilterRegistrationBean的相关配置。

    package com.ctrip.ibu.cargo.filter;

    import org.springframework.beans.BeansException;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;

    import javax.servlet.DispatcherType;
    import javax.servlet.Filter;

    @Configuration
    @Conditional(CargoFilterCondition.class)
    @ConditionalOnClass(name = "com.ctrip.ibu.cargo.filter.CargoFilter")
    public class CargoFilterAutoConfiguration implements ApplicationContextAware {

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            if (applicationContext.containsBeanDefinition("EnableCargoFilterBeanNew") || applicationContext.containsBeanDefinition("EnableCargoFilterBeanOld")){
                //TODO 3.0.0-beta.3 要动态获取
                ProductVersionManager.getInstance().register("cargo-client.Version","3.0.0-beta.3");
            }
        }

        @Configuration
        @ConditionalOnMissingClass("org.springframework.boot.context.embedded.FilterRegistrationBean")
        static class CargoFilterConfigurationNew {
            @Bean(name = "EnableCargoFilterBeanNew")
            public org.springframework.boot.web.servlet.FilterRegistrationBean factory() {
                org.springframework.boot.web.servlet.FilterRegistrationBean filter = new org.springframework.boot.web.servlet.FilterRegistrationBean();
                filter.setFilter(new CargoFilter());
                filter.setName("cargo-filter");
                filter.addUrlPatterns("/*");
                filter.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
                filter.setAsyncSupported(true);
                filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
                return filter;
            }
        }

        @Configuration
        @ConditionalOnClass(name = "org.springframework.boot.context.embedded.FilterRegistrationBean")
        static class CargoFilterConfigurationOld {
            @Bean(name = "EnableCargoFilterBeanOld")
            public org.springframework.boot.context.embedded.FilterRegistrationBean factory() {
                org.springframework.boot.context.embedded.FilterRegistrationBean filter = new org.springframework.boot.context.embedded.FilterRegistrationBean();
                filter.setFilter(new CargoFilter());
                filter.setName("cargo-filter");
                filter.addUrlPatterns("/*");
                filter.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
                filter.setAsyncSupported(true);
                filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
                return filter;
            }
        }

    }

3、CargoFilterCondition类的match方法,在return为true时候才会执行2中的配置逻辑。即@Conditional(CargoFilterCondition.class)语句的作用。

    package com.ctrip.ibu.cargo.filter;

    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;

    public class CargoFilterCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            //TODO
    //        String parameter = System.getProperty("cargo.filter.enabled", "true");
    //        return !parameter.equals("false");
            return true;
        }

    }

4、做具体的Filter的业务逻辑即可。

    package com.ctrip.ibu.cargo.filter;

    import com.ctrip.ibu.cargo.context.CargoContext;
    import com.ctrip.ibu.cargo.context.ContextInfo;
    import com.ctrip.ibu.cargo.context.ContextParser;
    import com.ctrip.ibu.cargo.entity.Currency;
    import com.ctrip.ibu.cargo.entity.Locale;
    import com.ctrip.ibu.cargo.entity.Site;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;

    /**
     * Cargo filter for Online, which will put the thread context info.
     */
    @WebFilter(urlPatterns = "/*", filterName = "cargo-filter", description = "Cargo Filter to put the thread context info.")
    public class CargoFilter implements Filter {

        private static final Logger log = LoggerFactory.getLogger(CargoFilter.class);

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {

        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
            try {
                ContextInfo contextInfo = ContextParser.parse((HttpServletRequest) servletRequest);
                if (contextInfo != null) {
                    CargoContext.set(CargoContext.HOST, contextInfo.getHost());
                    if (contextInfo.getSite() != null){
                        CargoContext.set(CargoContext.DOMAIN, contextInfo.getDomain());
                        CargoContext.set(CargoContext.LANGUAGE, contextInfo.getLanguage());
                        CargoContext.set(Currency.class, contextInfo.getCurrency());
                        CargoContext.set(Locale.class, contextInfo.getLocale());
                        CargoContext.set(Site.class, contextInfo.getSite());
                    }
                }
                chain.doFilter(servletRequest, servletResponse);
            } catch (Exception ex) {
                log.error("An exception occurred when set the current thread context.", ex);
            } finally {
                CargoContext.clear();
            }
        }

        @Override
        public void destroy() {

        }
    }

下面是华丽丽的分割线。。

原理方面的源代码分析:

SpringBoot的自动化配置原理,首先是从@SpringBootApplication开始的,这个注解是一个组合注解,核心功能时由@EnableAutoConfiguration提供的。观察@EnableAutoConfiguration可以发现,这里Import了@EnableAutoConfigurationImportSelector,这就是Spring Boot自动化配置的“始作俑者”。

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

        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

        /**
         * Exclude specific auto-configuration classes such that they will never be applied.
         * @return the classes to exclude
         */
        Class<?>[] exclude() default {};

        /**
         * Exclude specific auto-configuration class names such that they will never be
         * applied.
         * @return the class names to exclude
         * @since 1.3.0
         */
        String[] excludeName() default {};

    }

一路debug过来(最上面的堆栈就是getCandidateConfigurations的代码了)。

20191102100707\_1.png

20191102100707\_2.png

20191102100707\_3.png

EnableAutoConfigurationImportSelector的关键代码在这里:(上面我实际运用的例子中我配置了:org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ctrip.ibu.cargo.filter.CargoFilterAutoConfiguration,所以CargoFilterAutoConfiguration类就会被springboot认识了。)

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                AnnotationAttributes attributes) {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                    getSpringFactoriesLoaderFactoryClass(), 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;
        }

接下来再细致的讲解一下springboot的执行过程:(不仅限于autoConfiguration本身,还包括接下来如何生效)

1、首先Springboot框架会扫描业务代码中所有继承SpringBootServletInitializer的类,然后执行SpringApplication.run(XXXServiceInitializer.class);方法。用过Springboot的人都知道这个,我就不多展开说明了。

2、然后会执行SpringApplication的refreshContext方法,里面有个initialize(sources);方法。initialize中会解析上面写的这个spring.factories配置文件(参见2.2),然后生成若干个XXXInitialzers和XXXListeners的类的实例。

    @SuppressWarnings({ "unchecked", "rawtypes" })
        private void initialize(Object[] sources) {
            if (sources != null && sources.length > 0) {
                this.sources.addAll(Arrays.asList(sources));
            }
            this.webEnvironment = deduceWebEnvironment();
            setInitializers((Collection) getSpringFactoriesInstances(
                    ApplicationContextInitializer.class));
            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = deduceMainApplicationClass();
        }

2.1、getSpringFactoriesInstances方法执行loadFactoryNames方法然后通过反射生成配置文件中若干个value所对应的类的实例。

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
                Class<?>[] parameterTypes, Object... args) {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            // Use names and ensure unique to protect against duplicates
            Set<String> names = new LinkedHashSet<String>(
                    SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                    classLoader, args, names);
            AnnotationAwareOrderComparator.sort(instances);
            return instances;
        }

2.2、其中loadFactoryNames方法如下:SpringFactoriesLoader类会去项目的META-INF/spring.factories路径下去寻找这个spring.factories配置文件,然后加以解析。

        /**
         * The location to look for factories.
         * <p>Can be present in multiple JAR files.
         */
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            try {
                Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                List<String> result = new ArrayList<String>();
                while (urls.hasMoreElements()) {
                    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 ex) {
                throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                        "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }

3、以上事情都做完之后会根据之前生成的Listener来发一系列的事件(这是很经典的一种设计模式,值得学习。有助于写出很棒的低耦合高内聚的很棒的代码,脱离草根程序员的level),会有一个后置处理器来利用之前实例化好的类来真正做事情。

    public void environmentPrepared(ConfigurableEnvironment environment) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.environmentPrepared(environment);
            }
        }

4、经历了一堆的代码之后,CargoFilterAutoConfiguration类processConfigBeanDefinitions方法会将之前注册的拿来执行

    /**
         * Derive further bean definitions from the configuration classes in the registry.
         */
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
            RootBeanDefinition iabpp = new RootBeanDefinition(ImportAwareBeanPostProcessor.class);
            iabpp.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(IMPORT_AWARE_PROCESSOR_BEAN_NAME, iabpp);

            RootBeanDefinition ecbpp = new RootBeanDefinition(EnhancedConfigurationBeanPostProcessor.class);
            ecbpp.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(ENHANCED_CONFIGURATION_PROCESSOR_BEAN_NAME, ecbpp);

            int registryId = System.identityHashCode(registry);
            if (this.registriesPostProcessed.contains(registryId)) {
                throw new IllegalStateException(
                        "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
            }
            if (this.factoriesPostProcessed.contains(registryId)) {
                throw new IllegalStateException(
                        "postProcessBeanFactory already called on this post-processor against " + registry);
            }
            this.registriesPostProcessed.add(registryId);

            processConfigBeanDefinitions(registry);
        }
    /**
         * Build and validate a configuration model based on the registry of
         * {@link Configuration} classes.
         */
        public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
            List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
            String[] candidateNames = registry.getBeanDefinitionNames();

            for (String beanName : candidateNames) {
                BeanDefinition beanDef = registry.getBeanDefinition(beanName);
                if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                        ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                    }
                }
                else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                    configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
                }
            }

            // Return immediately if no @Configuration classes were found
            if (configCandidates.isEmpty()) {
                return;
            }

            // Sort by previously determined @Order value, if applicable
            Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
                @Override
                public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
                    int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                    int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                    return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
                }
            });

            // Detect any custom bean name generation strategy supplied through the enclosing application context
            SingletonBeanRegistry singletonRegistry = null;
            if (registry instanceof SingletonBeanRegistry) {
                singletonRegistry = (SingletonBeanRegistry) registry;
                if (!this.localBeanNameGeneratorSet && singletonRegistry.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
                    BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }

            // Parse each @Configuration class
            ConfigurationClassParser parser = new ConfigurationClassParser(
                    this.metadataReaderFactory, this.problemReporter, this.environment,
                    this.resourceLoader, this.componentScanBeanNameGenerator, registry);

            Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
            Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
            do {
                parser.parse(candidates);
                parser.validate();

                Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
                configClasses.removeAll(alreadyParsed);

                // Read the model and create bean definitions based on its content
                if (this.reader == null) {
                    this.reader = new ConfigurationClassBeanDefinitionReader(
                            registry, this.sourceExtractor, this.resourceLoader, this.environment,
                            this.importBeanNameGenerator, parser.getImportRegistry());
                }
                this.reader.loadBeanDefinitions(configClasses);
                alreadyParsed.addAll(configClasses);

                candidates.clear();
                if (registry.getBeanDefinitionCount() > candidateNames.length) {
                    String[] newCandidateNames = registry.getBeanDefinitionNames();
                    Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
                    Set<String> alreadyParsedClasses = new HashSet<String>();
                    for (ConfigurationClass configurationClass : alreadyParsed) {
                        alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                    }
                    for (String candidateName : newCandidateNames) {
                        if (!oldCandidateNames.contains(candidateName)) {
                            BeanDefinition beanDef = registry.getBeanDefinition(candidateName);
                            if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory) &&
                                    !alreadyParsedClasses.contains(beanDef.getBeanClassName())) {
                                candidates.add(new BeanDefinitionHolder(beanDef, candidateName));
                            }
                        }
                    }
                    candidateNames = newCandidateNames;
                }
            }
            while (!candidates.isEmpty());

            // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
            if (singletonRegistry != null) {
                if (!singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
                    singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
                }
            }

            if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
                ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
            }
        }

ConfigurationClassParser类执行doProcessConfigurationClass方法,该方法会循环递归的调用自己。完成springboot配置发挥作用的业务逻辑。限于篇幅且比较复杂就不再赘述。

以上的源代码逻辑就讲解完毕


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring boot的autoConfiguration的实战运用和源码分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏