SpringBoot开发实战之自动配置

网友投稿 252 2022-12-19

SpringBoot开发实战之自动配置

在介绍SpringBoot的自动配置之前,先了解下注解@Import的使用,SpringBoot的@Enable*开头的注解底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中,而@Import提供了以下4中用法:

直接导入Bean

通过配置类导入Bean

导入ImportSelector实现类,一般用于加载配置文件的类

导入ImportBeanDefinitionRegistrar实现类

下面来分别介绍这几种用法。

直接导入Bean就比较简单了,新建一个User类

public class User{

private String name;

private String address;

}

然后在启动类上使用@Import注解导入即可

@SpringBootApplication

@Import(User.class)

public class Application {

public static void main(String[] args) {

ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);

System.out.println(context.getBean(User.class));

}

}

这里需要注意的是,通过上下文获取Bean时,需要使用Bean的class,因为通过Bean的方式导入,Spring存入IOC容器,是用类的全类名存储的。可以使用上下文的getBeansOfType方法查看,返回的是Map对象。

{com.tenghu.sbc.entity.User=User(name=null, age=0)}

从返回的结果可以看出,key就是存的User的全类名。

通过配置类导入Bean,创建一个配置类;

public class UserConfig {

@Bean(name = "user")

public User user(){

return new User();

}

}

然后通过@Import导入这个配置类

@SpringBootApplication

@Import(UserConfig.class)

public class Application {

public static void main(String[] args) {

ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);

System.out.println(context.getBean(User.class));

}

}

通过配置类的方式可以在配置类里面定义多个Bean,当导入配置类时,配置类下定义的Bean都会被导入。

导入ImportSelector实现类

public class MyImportSelector implements ImportSelector {

@Override

public String[] selectImports(AnnotationMetadata annotationMetadata) {

return new String[]{User.class.getName()};

}

}

实现ImportSelector类,必须实现selectImports,然后返回需要导入的Bean。与上面一样使用@Import导入这个实现类。

@Import(MyImportSelector.class)

导入ImportBeanDefinitionRegistrar实现类

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

@Override

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(User.class).getBeanDefinition();

registry.registerBeanDefinition("user",beanDefinition);

}

}

使用方式一样,通过@Import导入

@Import(MyImportBeanDefinitionRegistrar.class)

了解完@Import的使用,接下来可以来看下SpringBoot的自动配置是怎么处理的。从上面的启动类,使用SpringBoot就用了一个注解@SpringBootApplication,可以打开这个注解的源码看下:

@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

用到这样一个注解@EnableAutoConfiguration注解。底层使用@Import导入上面第三种方式AutoConfigurationImportSelector。

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import({AutoConfigurationImportSelector.class})

public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class>[] exclude() default {};

String[] excludeName() default {};

}

进入源码找到实现了selectImports方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {

if (!this.isEnabled(annotationMetadata)) {

return NO_IMPORTS;

} else {

AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);

return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

}

}

通过调用方法getAutoConfigurationEntry

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationhttp://Metadata) {

if (!this.isEnabled(annotationMetadata)) {

return EMPTY_ENTRY;

} else {

AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

configurationshttp:// = this.removeDuplicates(configurations);

Set exclusions = this.getExclusions(annotationMetadata, attributes);

this.checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations = this.getConfigurationClassFilter().filter(configurations);

this.fireAutoConfigurationImportEvents(configurations, exclusions);

return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);

}

}

这里主要的看调用这个方法getCandidateConfigurations,返回的就是要自动加载的Bean

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配置文件里的EnableAutoConfiguration获取配置的Bean

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

.....

太多了,有兴趣的可以查看Spring的xxx-autoconfigure包。将读取到的配置最终返回给selectImports,然后通过工具类StringUtils.toStringArray转换为字符串数组返回给@Import,从而实现自动配置。第三方包只要是xxx-autoconfigure结尾的包,META-INF都有spring.factories,这个名字是固定写法。都可以被SpringBoot识别并且进行自动配置,前提是需要配置到org.springframework.boot.autoconfigure.EnableAutoConfiguration下。

从以上总结来看,SpringBoot的自动配置原理如下:

@EnableAutoConfiguration注解内部使用Import(AutoConfigurationImportSelector.class)来加载配置类

通过配置文件:META-INF/spring.factories,配置大量的配置类,SpringBoot启动时就会自动加载这些类并初始化的Bean。

这里需要说明一点,并不是所有配置到配置文件的Bean都会被初始化,需要符合配置类中使用Condition来加载满足条件的Bean。比如我们打开RedisAutoConfiguration的源码查看:

@Configuration(

proxyBeanMethods = false

)

@ConditionalOnClass({RedisOperations.class})

@EnableConfigurationProperties({RedisProperties.class})

@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})

public class RedisAutoConfiguration {

public RedisAutoConfiguration() {

}

@Bean

@ConditionalOnMissingBean(

name = {"redisTemplate"}

)

@ConditionalOnSingleCandidate(RedisConnectioLVKmmQnFactory.class)

public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {

RedisTemplate template = new RedisTemplate();

template.setConnectionFactory(redisConnectionFactory);

return template;

}

@Bean

@ConditionalOnMissingBean

@ConditionalOnSingleCandidate(RedisConnectionFactory.class)

public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

StringRedisTemplate template = new StringRedisTemplate();

template.setConnectionFactory(redisConnectionFactory);

return template;

}

}

类上面有这么个注解@ConditionalOnClass({RedisOperations.class}),意思就是需要RedisOperations类存在的情况下,才自动加载;这还不算完,继续查看下面的方法上有个@ConditionalOnMissingBean(name = {"redisTemplate"}),这里的意思是,当其他地方没有redisTemplate实例化这个Bean时,才自动加载。符合这两个条件,SpringBoot才会进行自动加载并初始化。

总结

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:构建Maven多模块项目的方法
下一篇:Springboot如何利用拦截器拦截请求信息收集到日志详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~