Spring源码阅读之ImportSelector使用案例

2,432 阅读2分钟

spring在初始化容器的时候除了扫描出我们定义的普通类之外,还会扫描出特殊类完成容器的初始化动,这种特殊的类有一种就是我们使用了注解@Import导入的类。

@Import有下面三种使用形式:
  • @Import(xxx.class),其中xxx为普通形式的类;
  • @Import(A.class),其中,A为ImportSelector的实现类;
  • @Import(B.class),其中,B为ImportBeanDefinitionRegistrar的实现类。

在本节文章中只讨论ImportSelector,(ImportBeanDefinitionRegistrar比ImportSelector更强大,可以人为的为spring容器中添加bean,例如mybatis就是使用了这种方式将mapper添加到容器中的。具体的等到了分析mybatis文章的时候说吧)。关于ImportBeanDefinitionRegistrar的使用demon可以看这篇吧。 ImportBeanDefinitionRegistrar——spring对外提供的注册bean功能

使用起来也很简单,首先ImportSelector
public class MyImportSelector implements ImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		return new String[]{IndexDao1.class.getName()};
	}
}

通过代码可以看到,这里只是将IndexDao1这个类的名字返回。这里返回某个类的名字后,spring就可以根据类名得到对应的类,然后得到相应的BeanDefinition,最会中放入spring容器当中。

IndexDao1类如下:
public class IndexDao1 implements BeanPostProcessor, Dao {

	public void query() {
		System.out.println("IndexDao1");
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (beanName.equals("indexDao")) {
			return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{Dao.class}, new MyInvocationHandler(bean));
		}

		return bean;
	}

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

通过以上代码,我们看到这么几点:

  • 1、IndexDao1 通过实现BeanPostProcessor接口来插手bean的实例化;
  • 2、将名字为indexDao的bean返回了一个代理对象。 关于BeanPostProcessor,其实就是spring提供给我们插手bean实例化过程的一个扩展点。
IndexDao和MyInvocationHandler如下:
@Repository
public class IndexDao implements Dao {
	public void query() {
		System.out.println("query");
	}
}
public class MyInvocationHandler implements InvocationHandler {

	Object target;

	public MyInvocationHandler(Object o) {
		target = o;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("我是代理");
		return method.invoke(target, args);
	}
}

然后直接使用@Import(MyImportSelector.class)即可,不过我们为了优雅点,可以定义一个注解,当做使用我们自定义这个导入类的“开关”

@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelector.class)
public @interface EnableMySelector {
}

最后,我们在配置类上引入EnableMySelector注解,表示开启MyImportSelector的使用

@Configuration
@ComponentScan("com.study")
@EnableMySelector
public class AppConfig {
}
测试类如下:
public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		Dao dao = (Dao)context.getBean("indexDao");
		dao.query();
	}

这里我们从容器中获取了indexDao这个bean,输出如下:

我是代理
query
总结:

可以看到,当我们获取indexDao时,实际上spring返回的是一个代理对象完成的功能,回顾上面的代码,我们主要是通过MyImportSelector和IndexDao1联合使用来完成了这个功能。这个呢,也就是ImportSelector这个接口的一个简单使用场景,当我们需要改变一个类的行为,或者说增强的时候我们就可以使用这种处理方式,直接在配置类上增加EnableMySelector注解,如果不需要增强类行为,在配置类上移除EnableMySelector注解即可,代码灵活性比较好。