Spring Boot自动配置原理

Spring Boot自动化配置原理

Spring Boot提供了很多”开箱即用“的依赖模块,这些模块已经帮我们做了自动配置,这些依赖模块都是以spring-boot-starter-xx作为命名的,比如spring-boot-starter-redis、spring-boot-starter-data-mongodb ,Spring Boot关于自动配置的源码在spring-boot-autoconfigure.jar内 在说spring的自动配置,我们先看看Spring Boot的运作原理,我们都知道,在Spring Boot的启动类上都会添加@SpringBootApplication注解,我们点击进去看看里面的源码,自动配置的核心注解是@EnableAutoConfiguration

 */
@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 

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

这里的关键功能时@Import注解到的配置功能,AutoConfigurationImportSelector使用SpringFactoriesLoader.loadFacotryNames方法来扫描spring-boot-autoconfigure.jar包里面spring.factories文件,此文件声明了哪些类可以自动注入

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

# 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.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\

平时我们知道在创建springWeb的时候,我们都需要在web.xml里面配置一个filter,设置Encode编码为UTF-8,但在spring boot中我们却没有,另外如果本地启动了Redis,我们也可以不需要配置Redis,我们点进去HttpEncodingAutoConfiguration、RedisAutoConfiguration里面看看里面的配置

@Configuration
@EnableConfigurationProperties(HttpEncodingProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration

可以看到注解上面都包含了ConditionalOnClass和EnableConfigurationProperties两个注解,我们深入ConditionalOnClass看它是如何实现的

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        ClassLoader classLoader = context.getClassLoader();
        ConditionMessage matchMessage = ConditionMessage.empty();
        List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
        if (onClasses != null) {
            List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
            if (!missing.isEmpty()) {
                return ConditionOutcome
                        .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                                .didNotFind("required class", "required classes")
                                .items(Style.QUOTE, missing));
            }
            matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
                    .found("required class", "required classes").items(Style.QUOTE,
                            getMatches(onClasses, MatchType.PRESENT, classLoader));
        }
        List<String> onMissingClasses = getCandidates(metadata,
                ConditionalOnMissingClass.class);
        if (onMissingClasses != null) {
            List<String> present = getMatches(onMissingClasses, MatchType.PRESENT,
                    classLoader);
            if (!present.isEmpty()) {
                return ConditionOutcome.noMatch(
                        ConditionMessage.forCondition(ConditionalOnMissingClass.class)
                                .found("unwanted class", "unwanted classes")
                                .items(Style.QUOTE, present));
            }
            matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
                    .didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE,
                            getMatches(onMissingClasses, MatchType.MISSING, classLoader));
        }
        return ConditionOutcome.match(matchMessage);
    }

源码中会通过ConditionalOnClass中设置的参数,在classPath下查找是否存在,现在如果我们引入了web依赖,那么CharacterEncodingFilter这个类肯定是存在的,此时它就会通过EnableConfigurationProperties注解配置的参数使用里面的默认配置,我们点进去HttpEncodingProperties看看

@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {

    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    /**
     * Charset of HTTP requests and responses. Added to the "Content-Type" header if not
     * set explicitly.
     */
    private Charset charset = DEFAULT_CHARSET;

果然这里面有一个参数DEFAULT_CHARSET已经默认设置为UTF-8了,这里我们也可以通过spring.http.encoding来修改它的默认编码
同样,我们看看Redis的配置源码。

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

这里redis为我们自动注入了一个Bean,名字是redisTemplate,所以我们在项目中使用redisTemplate直接就可以用了,通过spring.redis可以修改默认的配置

所以可以总结spring boot自动配置的原理是:先在claspath下查找是否存在的依赖类,如果存在则触发自动配置,我们只要通过Maven添加依赖,这些依赖就会下载很多jar包到classpath中。

实现一个自动配置模块

通过上面的分析,我们发现spring的自动配置还是比较简单的,那我们也可以自己实现一个,创建一个Maven项目,添加autoconfigure依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
</dependencies>

定义一个配置文件,我们以chuanz开头,后面我们在application.properties中就可以直接以chuanz开头来配置了

@ConfigurationProperties(prefix = "chuanz")
public class ChuanzProperties {
    public static final String DEFAULT_NAME = "baichuan";
    public String name = DEFAULT_NAME;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

实现一个简单的服务类

public class ChuanzService {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

下面我们来实现最主要的自动配置类,注意配置完成后,需要将这个类添加到spring.factories,我们在src/main/resources/ META-INF/创建一个spring.factories文件,然后配置它

@Configuration
@ConditionalOnClass({ ChuanzService.class })
@EnableConfigurationProperties(ChuanzProperties.class)
public class ChuanzAutoConfiguration {

    @Autowired
    private ChuanzProperties chuanzProperties;

    @Bean
    @ConditionalOnMissingBean(ChuanzService.class)
    public ChuanzService chuanzService() {
        ChuanzService chuanzService = new ChuanzService();
        chuanzService.setName(chuanzProperties.getName());
        return chuanzService;
    }
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.chuanz.springboot.autoconfig.ChuanzAutoConfiguration

然后我们将项目打包成一个jar文件,这里我们可以把文件上传到本地私服上,然后再通过maven来引用

<dependency>
    <groupId>cn.chuanz</groupId>
    <artifactId>springboot-chuanz-autoconfig</artifactId>
    <version>0.1</version>
</dependency>

然后我们就可以在项目中通过@Autowired来注入ChuanzService使用了,通过chuanz前缀在application.properties中也可以配置name属性的值

 

网络释义
Autowired: 自动装配

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>