先看@Import解析代码
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
} else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
看代码得到的可引入哪些class
从以上代码我们可以看出在@Import注解中可以看出import可以引入四种类型的class
1.普通类
主要是通过调用processConfigurationClass方法处理
2.实现ImportSelector接口的类
主要实现selectImports方法,返回字符串数组,数组内容须名class全名,包括package部分
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
3.实现DeferredImportSelector接口类
从下面的代码可以看出DeferredImportSelector接口是继承ImportSelector ,所以实现类需要实现getImportGroup方法,面且还需要实现其中内部接口类Group
另外如果getImportGroup返回null的值,需要实现其父接口ImportSelector 的selectImports方法,要不然会出现异常
public interface DeferredImportSelector extends ImportSelector {
@Nullable
default Class<? extends Group> getImportGroup() {
return null;
}
interface Group {
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
Iterable<Entry> selectImports();
class Entry {
private final AnnotationMetadata metadata;
private final String importClassName;
public Entry(AnnotationMetadata metadata, String importClassName) {
this.metadata = metadata;
this.importClassName = importClassName;
}
public AnnotationMetadata getMetadata() {
return this.metadata;
}
public String getImportClassName() {
return this.importClassName;
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
Entry entry = (Entry) other;
return (this.metadata.equals(entry.metadata) && this.importClassName.equals(entry.importClassName));
}
@Override
public int hashCode() {
return (this.metadata.hashCode() * 31 + this.importClassName.hashCode());
}
@Override
public String toString() {
return this.importClassName;
}
}
}
}
4.实现ImportBeanDefinitionRegistrar接口类
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
引入实现ImportSelector接口类样例
在这里我们实现一个EnableMyImportClass注解,根据其值自动引入class,其实和@Iimport指定普通类一个意思,但是我们这里也可以将其作为条件来决定引入哪一个类,比如根据DB名字引名具体DB连接类,这里只是一个简单实现
1.准备EnableMyImportClass类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyDeferredImportSelector.class)
public @interface EnableMyImportClass {
String[] value();
}
2.准备实现ImportSelectoor类
这里最为重要的逻辑是selectImports的内容,主要逻辑取到EnableMyImportClass里指定的具体值,其中这里内容其实是让EnableMyImportClass的值指定为具体的类名
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Set<String> set= importingClassMetadata.getAnnotationTypes();
String[] a=null;
if (set.contains(EnableMyImportClass.class.getName())){
MultiValueMap<String, Object> valueMap= importingClassMetadata.getAllAnnotationAttributes(EnableMyImportClass.class.getName());
Object o=valueMap.get("value");
if (o instanceof ArrayList){
ArrayList<String> arrayList=(ArrayList<String>)o;
if (arrayList.size()==1){
Object o1=arrayList.get(0);
a=new String[Array.getLength(o1)];
for(int i=0;i<Array.getLength(o1);i++){
a[i]=(String)Array.get(o1,i);
}
}
}
}
return a;
}
@Override
public Predicate<String> getExclusionFilter() {
return ImportSelector.super.getExclusionFilter();
}
}
3.在@Configuration或@Component类中以下方式引入
@EnableMyImportClass({"com.extions.Person"})
如指定的类名不存在框将会报错,找不到引用类,比如如下
如果@EnableMyImportClass({"testme"}),其中testmes是不存在的,运行时会报如下错误
十一月 22, 2022 3:02:02 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.zhouyu.AppConfig]; nested exception is java.io.FileNotFoundException: class path resource [testme.class] cannot be opened because it does not exist
Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.zhouyu.AppConfig]; nested exception is java.io.FileNotFoundException: class path resource [testme.class] cannot be opened because it does not exist
at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:646)
引入实现DeferredImportSelector接口类样例
以下代码最为关键的是内部类MyGroup及getImportGroup方法
public class MyDeferredImportSelector implements DeferredImportSelector {
static class MyGroup implements Group{
private AnnotationMetadata metadata;
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
this.metadata=metadata;
}
@Override
public Iterable<Entry> selectImports() {
List<Entry> list=new ArrayList<Entry>();
Entry entry=new Entry(this.metadata, Person.class.getName());
list.add(entry);
return list;
}
}
@Override
public Class<? extends Group> getImportGroup() {
return MyGroup.class;
}
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return null;
}
@Override
public Predicate<String> getExclusionFilter() {
return DeferredImportSelector.super.getExclusionFilter();
}
}
然后在Configuration类或@Compoent、@Service、@Controller中加入@Import(MyDeferredImportSelector.class),可能上面的方法来使用,只不过getGoup的逻辑需要修改
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyDeferredImportSelector.class)
public @interface EnableMyImportClass {
String[] value();
}
总结
@Import注解是一个非常实用的方式,如果我们在系统开发时有非常定制化需要要根据不同条件引入不同的class作为bean,比如我们的系统需要支持不同的mq,但是不同的客户选择的MQ产品不一样,这样的话我们就可以采用这种方式为客户启用不同的mq类。