经常会看见一些例如spring-cloud-starter-feign这种maven依赖,或者@EnableMongo注解,直接开始使用相关bean对象实现功能。那么这些东西是怎么做的,有哪几种方式去做。今天有空就梳理一下。
因为这里还是一个方法的总结归纳,原理解释还是得看参考资料:
【SpringBoot2.x】-自定义Spring boot Starter(原理、demo代码实现以及解决面试问题)
我总结了三种方式,三种方法有一些共同点,我提前说一下,后文中可以对照理解。
方法一:
使用SpringBootApplication,或者EnableAutoConfiguration注解。这个注解会去扫描所有JAR包中的spring.factories文件,加载配置在EnableAutoConfiguration属性后的类。
所以我们只需要在使用注解后,在spring.factories中定义要加载的类全路径名。例如 spring-boot-autoconfigure这个maven依赖中的spring.factories文件,这里的 \ 是为了换行后还能读取到属性,多个类使用 , 分隔
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,\
这种方式适合于你有一个比较完整的,复杂的,依赖较多的功能模块,通过二方包的形式提供给其他工程使用
那么这种方式的原理简单解释如下:
使用@Import加载EnableAutoConfigurationImportSelector类,在其父类AutoConfigurationImportSelector中,实现了ImportSelector接口
@SuppressWarnings("deprecation")
@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 {};
}
ImportSelector#selectImports 返回了哪些类是需要被加载的,但是这些类是需要被@Configuration注解修饰的
public interface DeferredImportSelector extends ImportSelector {
}
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
在AutoConfigurationImportSelector#selectImports 方法中调用了如下方法,就是在这去加载spring.factories文件的,读取指定的属性EnableAutoConfiguration
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;
}
方法二
方法二其实是从方法一演变而来的,方法一的本质是通过实现ImportSelector接口,返回需要自动夹杂的类全路径名,所以我们可以自定义一个实现ImportSelector接口的类。
例如spring-context中的@EnableCaching注解,先用Import注解加载CachingConfigurationSelector类。
CachingConfigurationSelector类实现了ImportSelector接口,在selectImports方法中返回了类全路径名。
方法二和方法一同样,适合于你有一个比较完整的,复杂的,依赖较多的功能模块,通过二方包的形式提供给其他工程使用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
方法三:
该方法只加载单个被@Configuration注解修饰的类
例如@EnableMongo注解
通过Import注解,加载MongoAutoConfiguration类,同时MongoAutoConfiguration直接被@Configuration注解,类中定义了很多@Bean对象。
同时这些Bean对象依赖一个配置文件,配置文件通过@EnableConfigurationProperties定义。在配置文件中通过@ConfigurationProperties注解来定义配置属性的来源。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import({MongoAutoConfiguration.class})
public @interface EnableMongo {
}
@Configuration
@EnableConfigurationProperties({MongoProperties.class})
public class MongoAutoConfiguration {
@Autowired
private MongoProperties mongoProperties;
public MongoAutoConfiguration() {
}
@Bean
public MongoClient mongo() {
……
}
@Bean
public DB mongoDB(MongoClient mongo) {
……
}
@Bean
public MongoManager mongoManager(DB mongoDB) {
return new MongoManager(mongoDB);
}
}
@ConfigurationProperties(
prefix = "mongo"
)
public class MongoProperties
除此之外,我们还可以通过org.springframwork.boot.autoconfigure.condition的条件注解自定义在何种条件下加载不同的类,例如:当类路径下有HystrixCommand.class, HystrixFeign.class,feign.hystrix.enabled属性为true的时候加载HystrixFeign.builder
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
条件注解如下
注解 | 说明 |
---|---|
@ConditionalOnBean | 当容器里有指定的Bean的条件下。 |
@ConditionalOnClass | 当类路径下有指定的类的条件下。 |
@ConditionalOnExpression | 基于SpEL表达式作为判断条件。 |
@ConditionalOnJava | 基于JVM版本作为判断条件。 |
@ConditionalOnJndi | 在JNDI存在的条件下查找指定的位置。 |
@ConditionalOnMissingBean | 当容器里没有指定Bean的情况下。 |
@ConditionalOnMissingClass | 当类路径下没有指定的类的条件下。 |
@ConditionalOnNotWebApplication | 当前项目不是Web项目的条件下。 |
@ConditionalOnProperty | 指定的属性是否有指定的值。 |
@ConditionalOnResource | 类路径是否有指定的值。 |
@ConditionalOnSingleCandidate | 当指定Bean在容器中只有一个, 或者虽然有多个但是指定首选的Bean。 |
@ConditionalOnWebApplicatio | 当前项目是Web项目的条件下。 |