spring源码 - 理解@Import原理及运用

先看@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类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值