美文网首页
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