前言

上个博客【Spring框架的ImportSelector到底可以干嘛】总结了ImportSelector的用法,但是其实这个接口能做的事情非常的多。除了上个博客说的AOP的例子以外,我们也可以用ImportSelect和BeanPostProcessor一起模拟一个Spring的动态加载的过程。更多Spring内容进入【Spring解读系列目录】。

Spring底层动态加载原理

在模拟之前我们先讲解下Spring动态加载是怎么做的。我们都知道Bean在Spring中其实是一一个map存在的,需要哪个Bean就去里面找。那么动态加载的原理就是把一个类实现了BeanPostProcessor接口,在new这个类的对象的时候,用这个接口的方法去干涉这个Bean的返回。然后使用一个InvocationHandler去对目标类的方法进行干预以达到代理的目的。而且这个干涉可以通过一个EnableXXX的注解进行开启。

构建简单例子

既然知道了原理,那么就可以构建这样一个简单的例子了。首先需要有一个实现了ImportSelector的类MyImportSelect,再构造一个代理类IndexDaoIS,然后自定义注解EnableMySelector,在配置类上用@引入自己注解,最后测试类Test。但是这次我们的IndexDaoIS实现BeanPostProcessor接口。关于这个接口可以参考【Spring后置处理器BeanPostProcessor的应用】。为了实现这个代理我们肯定还需要一个目标类IndexDao和一个接口Dao,以及一个InvocationHandler,备齐如下。

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{IndexDaoIS.class.getName()};
    }
}
@Retention(RetentionPolicy.RUNTIME)//开启运行时加载
@Import(MyImportSelector.class)
public @interface EnableMySelector {
}
@ComponentScan("com.demo")
@EnableMySelector
public class AppConfig {
}
public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
         Dao dao= (Dao) anno.getBean("indexDao");
         dao.query();
         }
}
public interface Dao { //要完成代理必须要有一个接口
    void query();
}
@Repository
public class IndexDao implements Dao{
    public void query(){
        System.out.println("query");
    }
}
public class MyInvocation implements InvocationHandler {

   public MyInvocation() {
   }

   Object obj;

   public MyInvocation(Object obj) {
      this.obj=obj;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//在这里对方法的执行进行干预
      System.out.println("This is a proxy in InvocationHandler");
      return method.invoke(obj,args);
   }
}
public class IndexDaoIS implements BeanPostProcessor {
    public void query(){
        System.out.println("IndexDaoIS");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("indexDao")){ //当查询到bean的名字为indexDao的时候开始代理。这里我们就要干涉IndexDao这个bean的生成了
            bean= Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[]{Dao.class},new MyInvocation(bean));
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return null;
    }
}

运行结果:
This is a proxy in InvocationHandler
query

通过运行的结果完美的插手了Bean的方法执行,如果修改一下Test类。

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
         IndexDao dao= (IndexDao) anno.getBean("indexDao");
         dao.query();
         }
}
就会报错,可见我们拿回来的确实是一个代理对象。
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy11 cannot be cast to com.demo.dao.IndexDao
	at com.demo.main.Test.main(Test.java:27)

遗留问题

这个问题就是上面的例子中IndexDaoIS是什么时候实例化的?通过源码可以知道一个类要想被Spring变成一个对象首先要这对象变成一个BeanDefinition,而这个过程就是通过BeanDefinitionRegistry这接口的实现类构造方法实现的。而这个过程必须用到的就是类名。而这个类名就是我们的MyImportSelector里面的方法返回出去的,有了类名以后在进行一个反射newInstance进行实现。那么直接看Spring源码是怎么处理的,具体的分析都在注解里,说到底虽然仿制的比较拙劣,但是原理都是相同的。

org.springframework.context.annotation.ConfigurationClassParser#processImports:
for (SourceClass candidate : importCandidates) {
   //判断并处理ImportSelector
   if (candidate.isAssignable(ImportSelector.class)) {
      // Candidate class is an ImportSelector -> delegate to it to determine imports
      Class<?> candidateClass = candidate.loadClass();
      //反射得到一个对象,拿到对象以后Spring就知道到底要实例化的是哪些类。返回的是一个数组。
      //接着的事情就是要循环,并且实例化,最终放到一个map<name, BeanDefinition>中
      ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
            this.environment, this.resourceLoader, this.registry);
      Predicate<String> selectorFilter = selector.getExclusionFilter();
      if (selectorFilter != null) {
         exclusionFilter = exclusionFilter.or(selectorFilter);
      }
      if (selector instanceof DeferredImportSelector) {
         this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
      }
      else { //判断并处理普通类,@Component的类
         String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
         Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
         //这里会递归处理,因为有可能普通的类上也会有新的@Import注解。直到处理到没有@Import为止
         processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
      }
   }//判断并处理ImportBeanDefinitionRegistrar,能够动态添加bean,之所以能够做到这里,
   // 是因为这个类把注册的map<BeanName,BeanDefinition>暴露出来了
   // 这样做有什么好处呢?梳理下一个Class转化为BeanDefinition的方法
   // register()方法    传入一个类  无法参与BeanDefinition的过程
   // scan()方法       传入一个类  无法参与BeanDefinition的过程
   // ImportBeanDefinitionRegistrar 参与BeanDefinition的过程
   // Mybatis有一个@MapperScan的注解,其作用就是把一个接口变成一个对象,这个功能实现就是使用了这个类
   else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
      // Candidate class is an ImportBeanDefinitionRegistrar ->
      // delegate to it to register additional bean definitions
      Class<?> candidateClass = candidate.loadClass();
      //实例化instantiateClass()
      ImportBeanDefinitionRegistrar registrar =
            ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                  this.environment, this.resourceLoader, this.registry);
      //添加到一个map:importBeanDefinitionRegistrars中
      configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
   }
   else {//判断并处理普通类,@Component的类
      // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
      // process it as an @Configuration class
      // 如果是一个普通类。加入到importStack后调用processConfigurationClass进行处理,
      // configurationClasses是一个集合,会在后面的流程中拿出来解析成BeanDefinition继而注册
      // 也就意味着只要是普通的类,被扫描出来就会被注册进去。
      // 如果是ImportSelector,会先放到configurationClasses后面再注册
      this.importStack.registerImport(
            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
      //在这个方法里面放到map:configurationClasses里
      processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
   }
}