Spring Boot怎么用这种类型的文章已经有很多了。其实把官方的Specifications看一遍基本都会用了。不过在阅读其源码的过程中,发现了一些很有意思的代码。所以在此记录和分析下。这篇主要讲讲ImportSelector这个接口的用法。
ImportSelector介绍
ImportSelector这个接口不是有了springboot之后才有的,它是在org.springframework.context.annotation这个包下,随着spring-context包3.1版本发布的时候出现的。这个可以看看它的类注解。
Interface to be implemented by types that determine which @Configuration class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.
An ImportSelector may implement any of the following Aware interfaces, and their respective methods will be called prior to selectImports(org.springframework.core.type.AnnotationMetadata):
- EnvironmentAware
- BeanFactoryAware
- BeanClassLoaderAware
- ResourceLoaderAware
ImportSelectors are usually processed in the same way as regular @Import annotations, however, it is also possible to defer selection of imports until all @Configuration classes have been processed (see DeferredImportSelector for details).
用法和用途
其用途比较简单,可以根据启动的相关环境配置来决定让哪些类能够被Spring容器初始化。
举两个例子:
一个是事务配置相关的TransactionManagementConfigurationSelector,其实现如下:
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
另一个是Spring Security中的权限验证配置相关的GlobalMethodSecuritySelector,其实现如下:
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
Map<String, Object> annotationAttributes = importingClassMetadata
.getAnnotationAttributes(annoType.getName(), false);
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(annotationAttributes);
Assert.notNull(attributes, String.format(
"@%s is not present on importing class '%s' as expected",
annoType.getSimpleName(), importingClassMetadata.getClassName()));
// TODO would be nice if could use BeanClassLoaderAware (does not work)
Class<?> importingClass = ClassUtils
.resolveClassName(importingClassMetadata.getClassName(),
ClassUtils.getDefaultClassLoader());
boolean skipMethodSecurityConfiguration = GlobalMethodSecurityConfiguration.class
.isAssignableFrom(importingClass);
AdviceMode mode = attributes.getEnum("mode");
boolean isProxy = AdviceMode.PROXY == mode;
String autoProxyClassName = isProxy ? AutoProxyRegistrar.class
.getName() : GlobalMethodSecurityAspectJAutoProxyRegistrar.class
.getName();
boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled");
List<String> classNames = new ArrayList<String>(4);
if(isProxy) {
classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName());
}
classNames.add(autoProxyClassName);
if (!skipMethodSecurityConfiguration) {
classNames.add(GlobalMethodSecurityConfiguration.class.getName());
}
if (jsr250Enabled) {
classNames.add(Jsr250MetadataSourceConfiguration.class.getName());
}
return classNames.toArray(new String[0]);
}
TransactionManagementConfigurationSelector在上层还有一个封装,GlobalMethodSecuritySelector的实现相对原始。
总结下来的用法应该是这样:
-
定义一个Annotation, Annotation中定义一些属性,到时候会根据这些属性的不同返回不同的class数组。
-
在selectImports方法中,获取对应的Annotation的配置,根据不同的配置来初始化不同的class。
-
实现ImportSelector接口的对象应该是在Annotation中由@Import Annotation来引入。这也就意味着,一旦启动了注解,那么就会实例化这个对象。
那么,我们自己来实践下。
比如我有一个需求,要根据当前环境是测试还是生产来决定我们的日志是输出到本地还是阿里云。
那假设我定义两个logger:LoggerA 和 LoggerB ,两个logger实现 Logger接口。我们再定义一个Annotation。
我们先看看Annotation的定义:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = {java.lang.annotation.ElementType.TYPE})
@Documented
@Import({LoggerSelector.class})
public @interface EnableMyLogger {
}
我们再看看Selector的定义
public class LoggerSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (testenv()) {
classNames.add(LoggerA.class);
} else {
classNames.add(LoggerB.class);
}
return classNames.toArray(new String[0]);
}
}
这样就能实现动态的实例化logger。
网友评论