美文网首页
1.3springboot自动装配解读

1.3springboot自动装配解读

作者: 谜00016 | 来源:发表于2018-09-26 18:38 被阅读49次

    在前面两篇文章1.1Spring @Enable 模块装配1.2spring条件装配我们大致了解了一下在springframwork中对Bean是如何装配的有所了解了。我们这篇文章来看下对于颠覆Java开发方式的springboot是如何做到如此令人发指的简洁的。

    重新认识@SpringBootApplication注解

    springboot之所以能做到如此简洁开发,他遵循了一个原则,规约大于配置。这个原则下,springboot工程会自动帮我们装载很多Bean。
    springboot工程我们一般会打上这么一个注解@SpringBootApplication。我们来看下这个注解到底做了什么能让我们开发简化。我们看下源码:

    package org.springframework.boot.autoconfigure;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.boot.SpringBootConfiguration;
    import org.springframework.boot.context.TypeExcludeFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.ComponentScan.Filter;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.core.annotation.AliasFor;
    
    /**
     ...
     * @since 1.2.0
     */
    @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 {
    
        /**
         * Exclude specific auto-configuration classes such that they will never be applied.
         * @return the classes to exclude
         */
        @AliasFor(annotation = EnableAutoConfiguration.class)
        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
         */
        @AliasFor(annotation = EnableAutoConfiguration.class)
        String[] excludeName() default {};
    
        /**
         * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
         * for a type-safe alternative to String-based package names.
         * @return base packages to scan
         * @since 1.3.0
         */
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
        String[] scanBasePackages() default {};
    
        /**
         * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
         * scan for annotated components. The package of each class specified will be scanned.
         * <p>
         * Consider creating a special no-op marker class or interface in each package that
         * serves no purpose other than being referenced by this attribute.
         * @return base packages to scan
         * @since 1.3.0
         */
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
        Class<?>[] scanBasePackageClasses() default {};
    
    }
    
    

    从源码中我们可以知晓他是一个组合注解,我们重点关注一下@EnableAutoConfiguration注解,让我们继续往下看,@EnableAutoConfiguration源码:

    
    package org.springframework.boot.autoconfigure;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.core.io.support.SpringFactoriesLoader;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.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 {};
    
    }
    

    是不是很熟悉,原来他也是通过选择器来加载需要的Bean。我们稍后来看下这个AutoConfigurationImportSelector类的源码。

    源码看了一部分,我们能不能自己来实现自动装配一个自定义的类呢?此处先给出代码实现,稍后我们通过debug的方式来看下具体内部逻辑。

    前面我们模拟实现了MyEnableEurekaServer,我们在此基础上做些补充。
    先建立一个引导类EnableAutoConfigurationBootstrap:注意打上@EnableAutoConfiguration注解

    package com.wangming.bootstrap;
    
    import org.springframework.boot.WebApplicationType;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.context.ConfigurableApplicationContext;
    
    
    @EnableAutoConfiguration
    public class EnableAutoConfigurationBootstrap {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class)
                    .web(WebApplicationType.NONE)
                    .run(args);
    
            // helloWorld Bean 是否存在
            String hello =
                    context.getBean("hello", String.class);
    
            System.out.println("hello Bean : " + hello);
    
            // 关闭上下文
            context.close();
    
        }
    }
    
    

    建立一个MyEurekaServerMarkerAutoConfiguration类:

    package com.wangming.config;
    
    import com.wangming.annotation.MyEnableEurekaServer;
    import com.wangming.condition.MyConditionOnProperty;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * 模拟EurekaServerMarkerConfiguration
     */
    @Configuration
    @MyEnableEurekaServer
    @MyConditionOnProperty(prefix = "pass")
    public class MyEurekaServerMarkerAutoConfiguration {
    
    }
    
    

    此处为了演示,加上了三个注解分别是模式注解,@Enable模块装配和条件装配。
    接下来我们需要在resource目录下建立META-INF/spring.factories目录和文件。

    结构如下: image.png spring.factories内容如下:
    # 自动装配
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.wangming.config.MyEurekaServerMarkerAutoConfiguration
    

    我们运行一下引导类,发现hello Bean已经被装配进来了。
    我们把这个思路理一下,在EnableAutoConfigurationBootstrap引导类中打上了@EnableAutoConfiguration注解,在启动时springboot会去resource/META-INF目录下寻找spring.factories文件,该文件内容是以key-value的形式保存的要加载的类。此处key为org.springframework.boot.autoconfigure.EnableAutoConfiguration即@EnableAutoConfiguration注解,value为com.wangming.config.MyEurekaServerMarkerAutoConfiguration。
    很明显这个我们自定义的类中通过springframwork中的三种装配方式(此处是为了演示)将Bean装配进容器。

    大致思路就是这样,我们来debug一下验证一下我们的猜想。我们回到之前遗留的一个疑问上面,EnableAutoConfiguration注解类中AutoConfigurationImportSelector这个类,我们看下这个类的源码:

    package org.springframework.boot.autoconfigure;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.LinkedHashMap;
    import java.util.LinkedHashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.Collectors;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.Aware;
    import org.springframework.beans.factory.BeanClassLoaderAware;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.BeanFactoryAware;
    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.boot.context.properties.bind.Binder;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.ResourceLoaderAware;
    import org.springframework.context.annotation.DeferredImportSelector;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.AnnotationAttributes;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.core.env.Environment;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.core.io.support.SpringFactoriesLoader;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.util.Assert;
    import org.springframework.util.ClassUtils;
    import org.springframework.util.StringUtils;
    
    /**
     * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
     * auto-configuration}. This class can also be subclassed if a custom variant of
     * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
     *
     * @author Phillip Webb
     * @author Andy Wilkinson
     * @author Stephane Nicoll
     * @author Madhura Bhave
     * @since 1.3.0
     * @see EnableAutoConfiguration
     */
    public class AutoConfigurationImportSelector
            implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
            BeanFactoryAware, EnvironmentAware, Ordered {
    
        ...
    
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }
    
    ...
    
    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;
        }
    
    ...
    
    直接看debug截图: image.png

    我们发现我们自定义的类MyEurekaServerMarkerAutoConfiguration被装载了进来。至于为甚么这个集合有这么多bean,其实我们可以在spring-boot-autoconfigure

    jar包下的METE-INF文件夹下的spring.factories文件中找到答案。 image.png image.png

    相关文章

      网友评论

          本文标题:1.3springboot自动装配解读

          本文链接:https://www.haomeiwen.com/subject/wtqjoftx.html