前言
上个博客【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);
}
}