SpringBoot @EnableAutoConfiguration非详细解读

提示

本文是我的学习笔记,请自己思考内容并且实践,有错误的地方欢迎指出。

前置知识

SpringBoot要求4.3.2.BUILD-SNAPSHOT或以上版本,有一个很重要的原因是因为spring4提供了条件注解@Conditional,而SpringBoot大量地使用了这种注解来为我们自动配置一些信息,可以根据容器是否存在某些bean,jvm版本,指定路径是否存在指定类等等,功能十分强大。

简单的实验@Conditional

public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").contains("Windows");
    }
}
public class LinuxCondition implements Condition{
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").contains("Linux");
    }
}
    @Bean
    @Conditional(WindowsCondition.class)
    public WhatIs windows(){
        return new Windows();
    }

    @Bean
    @Conditional(LinuxCondition.class)
    public WhatIs linux(){
        return new Linux();
    }

这里的代码其实是JavaEE颠覆者:SpringBoot里面解释条件注解的示例代码。效果是根据系统的名称,这里简单地判断系统名称,然后实例化不同的类。我大概地推测是:@Conditional注解会调用XXXCondition.matches()方法,然后通过返回true or false判定是否接下来生成bean。可以看到LinuxCondition和WindowsCondition都实现了Condition接口,并重写了matches方法。

总结:条件注解的实现步骤:实现org.springframework.context.annotation.Condition接口,并重写matches()方法,生成bean的时候通过@Conditional注解来调用具体的matches()方法判断是否实例化接下来的bean。

SpringBoot的@EnableAutoConfiguration

基于SpringBoot版本v1.5.8.RELEASE

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class[] exclude() default {};

    String[] excludeName() default {};
}

@EnableAutoConfiguration注解如上,重点在@Import({EnableAutoConfigrationImportSelector.class})

Ctrl+左键可以进入该方法,可以看到该方法extends AutoConfigurationImportSelector,在这个超类中有一个重要的方法如下:

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List 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;
    }

这个方法会扫描具有META-INF/spring.factories文件的jar包,而我们的Spring-boot-autoconfigure-x.x.x.jar就有这个文件。(书上说的)idea快捷键Shift+Ctrl+n可以快速找到这个文件。

spring.factories文件如下:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# 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,\
...

这里#Auto Configure注释下面有大量的XXXAutoConfiguration的类,容器在扫描到这些配置类之后会一个一个判断是否符合条件,如何条件则实例化该配置。

我们这里以org.springframework.boot.autoconfigure.aop.AopAutoConfiguration为例。

@Configuration
@ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class})
@ConditionalOnProperty(
    prefix = "spring.aop",
    name = {"auto"},
    havingValue = "true",
    matchIfMissing = true
)
public class AopAutoConfiguration {
    public AopAutoConfiguration() {
    }

    @Configuration
    @EnableAspectJAutoProxy(
        proxyTargetClass = true
    )
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"proxy-target-class"},
        havingValue = "true",
        matchIfMissing = false
    )
    public static class CglibAutoProxyConfiguration {
        public CglibAutoProxyConfiguration() {
        }
    }

    @Configuration
    @EnableAspectJAutoProxy(
        proxyTargetClass = false
    )
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"proxy-target-class"},
        havingValue = "false",
        matchIfMissing = true
    )
    public static class JdkDynamicAutoProxyConfiguration {
        public JdkDynamicAutoProxyConfiguration() {
        }
    }
}

这里@Configuration注解首先声明了这是个配置类。

接下来是两个条件,符合这两个条件则实例化该bean。

条件一:@ConditionalOnClass注解是一个组合注解,如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
    Class[] value() default {};

    String[] name() default {};
}

这里的条件是:当类路径下有指定类的时候。

条件二:@ConditionalOnProperty也是个组合注解,如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
    String[] value() default {};

    String prefix() default "";

    String[] name() default {};

    String havingValue() default "";

    boolean matchIfMissing() default false;

    boolean relaxedNames() default true;
}

我们注意到,这个条件可以注解在类,接口,方法等,而它的条件是:指定属性存在指定值的时候。

在类内部还有对方法的条件注解也是如此,接下来我们以@ConditionalOnClass为例,看看它如何与我们前置知识的条件注解关联上。

我们直接看类的继承层次:

class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware
public abstract class SpringBootCondition implements Condition 

可以看到OnClassCondition继承自SpringBootCondition,SpringBootCondition实现了Condition方法。

SpringBootCondition方法重写了matches()方法如下:

public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String classOrMethodName = getClassOrMethodName(metadata);

        try {
            ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
            this.logOutcome(classOrMethodName, outcome);
            this.recordEvaluation(context, classOrMethodName, outcome);
            return outcome.isMatch();
        } catch (NoClassDefFoundError var5) {
            throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);
        } catch (RuntimeException var6) {
            throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
        }
    }

其中getMatchOutcome是一个模板方法,如下:

public abstract ConditionOutcome getMatchOutcome(ConditionContext var1, AnnotatedTypeMetadata var2);

对模板方法设计模式不陌生的话应该想到了,它的实现是在子类OnClassCondition里,如下:

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ClassLoader classLoader = context.getClassLoader();
        ConditionMessage matchMessage = ConditionMessage.empty();
        List onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
        List onMissingClasses;
        if (onClasses != null) {
            onMissingClasses = this.getMatches(onClasses, OnClassCondition.MatchType.MISSING, classLoader);
            if (!onMissingClasses.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
            }

            matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.getMatches(onClasses, OnClassCondition.MatchType.PRESENT, classLoader));
        }

        onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
        if (onMissingClasses != null) {
            List present = this.getMatches(onMissingClasses, OnClassCondition.MatchType.PRESENT, classLoader);
            if (!present.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
            }

            matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.getMatches(onMissingClasses, OnClassCondition.MatchType.MISSING, classLoader));
        }

        return ConditionOutcome.match(matchMessage);
    }

至此,到这里整个实现过程就理清了。

总结: SprigBoot的@EnableAutoConfiguration通过条件注解@Conditional实现,而为了产生更复杂的行为,SpringBoot用SpringBootCondition实现了Condition,我认为实际上是对Condition的进一步拓展,让它具有更多的作用,之后通过模板方法把判断的权利发放非子类,也就是具体的XXXConfiguration类。

很高兴你能点进来看这篇文章,一篇不是很有深度的文章,如果有任何错误的地方,欢迎指正,并与我交流。


2017.11.22补充

上面关于如何读取spring.factories说得比较含糊,这里有了新的发现,故补充。
提示:内容包含大量本人无验证且目前无能力验证的猜测,请自己理解分析。

@EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class[] exclude() default {};

    String[] excludeName() default {};
}

上面我们提到,@EnableAutoConfiguration中最重要的一个注解是@Import({EnableAutoConfigurationImportSelector.class}),引入spring.factories文件中的xxxAutoConfiguration类的也是这个注解的功劳,因此这里我们主要解析这个注解。

Import的类EnableAutoConfigurationImportSelector.class的继承层次如下:

public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered
public interface DeferredImportSelector extends ImportSelector
public interface ImportSelector

其中ImportSelector内部声明了如下方法:

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

这里我认为容器在读取到@Import注解时便会调用import的类的selectImports方法,这是一种学习注解的一种思考方式,当然我不会打包票说就是这样,因为我并没有看过容器是怎么调用它的,这仅仅出于一种猜测。

显然,实现应该是其实现类,那么我们重新往上检索会发现AutoConfigurationImportSelector实现了该方法:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            try {
                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                configurations = this.removeDuplicates(configurations);
                configurations = this.sort(configurations, autoConfigurationMetadata);
                Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                this.checkExcludedClasses(configurations, exclusions);
                configurations.removeAll(exclusions);
                configurations = this.filter(configurations, autoConfigurationMetadata);
                this.fireAutoConfigurationImportEvents(configurations, exclusions);
                return (String[])configurations.toArray(new String[configurations.size()]);
            } catch (IOException var6) {
                throw new IllegalStateException(var6);
            }
        }
    }

这个方法里面有两个比较重要的方法,分别是isEnabled(annotationMetadata)和getCandidateConfigurations(annotationMetadata, attributes)。

isEnabled是一个模板方法,默认返回true

protected boolean isEnabled(AnnotationMetadata metadata) {
        return true;
    }

实现依然是由其子类EnableAutoConfigurationImportSelector实现:

 protected boolean isEnabled(AnnotationMetadata metadata) {
        return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? ((Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true)).booleanValue() : true;
    }

而getCandidateConfigurations则通过SpringFactoriesLoader.loadFactoryNames来载入spring.factories这个文件。

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List 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;
    }

你可能感兴趣的:(JavaWeb)