- @Import是做什么的?
- @Import使用有几种方式?有何区别?
- DeferredImportSelector是做什么的,他和ImportSelector有什么区别?
- spring中哪些功能是通过@Import来实现的?
- spring中是如何解析@Import注解的?
@Import的value常见的几种用法
- value为普通类
- value为@Configuration标注的类
- value为@CompontentScan标注的类
- value为ImportBeanDefinitionRegistrar接口类型
- value为ImportSelector接口类型
- value为DeferredImportSelector接口类型
前三种,我们不说了,自己可以做下测试,没什么难度,我们主要说后面三种。
ImportBeanDefinitionRegistrar接口
先看下他的源码:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
2个方法中主要有3个参数:
AnnotationMetadata importingClassMetadata 类型的,通过这个可以获取被@Import注解标注的类所有注解的信息。
BeanDefinitionRegistry registry 是一个接口,内部提供了注册bean的各种方法。
BeanNameGenerator importBeanNameGenerator 是一个接口,内部有一个方法,用来生成bean的名称。
BeanDefinitionRegistry bean定义注册器
public interface BeanDefinitionRegistry extends AliasRegistry {
/**
* 注册一个新的bean定义
* beanName:bean的名称
* beanDefinition:bean定义信息
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
/**
* 通过bean名称移除已注册的bean
* beanName:bean名称
*/
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
/**
* 通过名称获取bean的定义信息
* beanName:bean名称
*/
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
/**
* 查看beanName是否注册过
*/
boolean containsBeanDefinition(String beanName);
/**
* 获取已经定义(注册)的bean名称列表
*/
String[] getBeanDefinitionNames();
/**
* 返回注册器中已注册的bean数量
*/
int getBeanDefinitionCount();
/**
* 确定给定的bean名称或者别名是否已在此注册表中使用
* beanName:可以是bean名称或者bean的别名
*/
boolean isBeanNameInUse(String beanName);
}
BeanNameGenerator接口:bean名称生成器
public interface BeanNameGenerator {
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}
它有3个实现:
DefaultBeanNameGenerator :默认bean名称生成器,默认为:完整的类名#bean编号
AnnotationBeanNameGenerator:注解方式的bean名称生成器,比如通过@Component(bean名称)的方式指定bean名称,如果没有通过注解方式指定名称,默认会将完整的类名作为bean名称。
FullyQualifiedAnnotationBeanNameGenerator:将完整的类名作为bean的名称。
说完参数之后,我们来说下ImportBeanDefinitionRegistrar的用法:
- 定义ImportBeanDefinitionRegistrat接口的实现类
- 使用@Import来导入这个实现类
- 把@Import标注的类作为AnnotationConfigApplicationContext构造参数,创建spring容器
案例
//定义两个类,我们要把这两个类注册到容器中
public class Service1 {
}
public class Service2 {
private Service1 service1;
Service2(Service1 service1){
this.service1 = service1;
}
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
public Service1 getService1() {
return service1;
}
public void setService1(Service1 service1) {
this.service1 = service1;
}
}
//ImportBeanDefinitionRegistrat 实现类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//使用了BeanDefinitionBuilder这个类,这个是BeanDefinition的构造器,内部提供了很多静态方法方便构建BeanDefinition对象。
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Service1.class)
.getBeanDefinition();
registry.registerBeanDefinition("service1", beanDefinition);
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition(Service2.class)
.addPropertyReference("service1", "service1")
.getBeanDefinition();
registry.registerBeanDefinition("service2", beanDefinition1);
}
}
@Import(MyImportBeanDefinitionRegistrar.class)
public class ImportBean {
}
测试:
@Test
public void testImport() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportBean.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
String[] aliases = context.getAliases(beanName);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",
beanName,
Arrays.asList(aliases),
context.getBean(beanName)));
}
}
运行结果:
bean名称:importBean,别名:[],bean对象:com.spring.importBean.ImportBean@7d900ecf
bean名称:service1,别名:[],bean对象:com.spring.importBean.Service1@6f01b95f
bean名称:service2,别名:[],bean对象:Service2{service1=com.spring.importBean.Service1@6f01b95f}
ImportSelector的用法
用法和上面的用法差不多,我们来个升级的案例。
需求:凡是类名中包含service的,调用他们内部任何方法,我们希望调用之后能够输出这些方法的耗时。
分析:
1.类名中包含service的,怎么判断?在创建bean的时候,我们可以使用BeanPostProcessor来进行拦截。
2.调用方法之后输出这些方法的耗时?是不是需要代理?给bean实例生成代理
案例
1.来两个Service类:
@Component
public class Service1 {
public void m1(){
System.out.println("service1 --> m1");
}
}
@Component
public class Service2 {
public void m1(){
System.out.println("service2 --> m1");
}
}
2.来个代理,来实现方法的执行时间
public class ServiceProxy implements MethodInterceptor {
private Object target;
ServiceProxy(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long starTime = System.nanoTime();
Object invoke = method.invoke(target, objects);
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
return invoke;
}
//生成代理对象
public static <T> T createProxy(T target) {
ServiceProxy costTimeProxy = new ServiceProxy(target);
Enhancer enhancer = new Enhancer();
enhancer.setCallback(costTimeProxy);
enhancer.setSuperclass(target.getClass());
return (T) enhancer.create();
}
}
3.拦截他们的创建,如何类名中含有service,则我们生成代理对象,这样就可以执行方法的时候,进行耗时统计
public class MyBeanPostProcessor implements BeanPostProcessor {
//注意,我们是在bean实例生成之前来进行拦截
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
String name = bean.getClass().getName();
boolean isContain = name.toLowerCase().contains("service");
if(isContain){
return ServiceProxy.createProxy(bean);
}
return bean;
}
}
4.我们把MyBeanPostProcessor 注册到容器中,使用@ImportSelector
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
MyBeanPostProcessor.class.getName()
};
}
}
5.来个注解,作用是:表示耗时的统计注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class)
public @interface EnableMethodCostTime {
}
6.来个配置类,给spring使用
@ComponentScan
@EnableMethodCostTime
public class BeanConfig {
}
7.使用这个配置类
@Test
public void testImport1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
Service1 service1 = context.getBean(Service1.class);
service1.m1();
Service2 service2 = context.getBean(Service2.class);
service2.m1();
}
执行结果:
service1 --> m1
public void com.spring.importBean1.Service1.m1(),耗时(纳秒):87600
service2 --> m1
public void com.spring.importBean1.Service2.m1(),耗时(纳秒):52700
如果我们不想开启方法耗时统计,只需要将BeanConfig 上的@EnableMethodCostTime去掉就可以了,用起来是不是特别爽?
spring中有很多类似的注解,以@EnableXXX开头的注解,基本上都是通过上面这种方式实现的,比如:
@EnableAspectJAutoProxy , @EnableCaching , @EnableAsync
DeferredImportSelector接口
DeferredImportSelector是ImportSelector的子接口,既然是ImportSelector的子接口,所以也可以通过@Import进行导入,这个接口和ImportSelector不同地方有两点:
- 延迟导入
- 指定导入的类的处理顺序
延迟导入
比如@Import的value包含了多个普通类、多个@Configuration标注的配置类、多个ImportSelector接口的实现类,多个ImportBeanDefinitionRegistrar接口的实现类,还有DeferredImportSelector接口实现类,此时spring处理这些被导入的类的时候,会将DeferredImportSelector类型的放在最后处理,会先处理其他被导入的类,其他类会按照value所在的前后顺序进行处理。
指定导入的类的处理顺序
指定顺序常见2种方式:
- 实现Ordered接口的方式
- 实现Order注解的方式
这两种方式,都是通过value来进行控制顺序的:value的值越小,优先级越高。
案例
两个配置类:
@Configuration
public class Service1Config {
@Bean
public String s1() {
return "s1";
}
}
@Configuration
public class Service2Config {
@Bean
public String s2() {
return "s2";
}
}
两个DeferredImportSelector的实现类:
public class MyDeferred1 implements DeferredImportSelector, Ordered {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
Service1Config.class.getName()
};
}
@Override
public int getOrder() {
return 2;
}
}
public class MyDeferred2 implements DeferredImportSelector, Ordered{
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
Service2Config.class.getName()
};
}
@Override
public int getOrder() {
return 0;
}
}
@Import({MyDeferred1.class, MyDeferred2.class})
public class BeanImport {
}
获取bean:
@Test
public void testImport2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanImport.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
String[] aliases = context.getAliases(beanName);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",
beanName,
Arrays.asList(aliases),
context.getBean(beanName)));
}
}
运行结果:
bean名称:beanImport,别名:[],bean对象:com.spring.importBean2.BeanImport@332796d3
bean名称:com.spring.importBean2.Service2Config,别名:[],bean对象:com.spring.importBean2.Service2Config$$EnhancerBySpringCGLIB$$7b566b2c@4f0100a7
bean名称:s2,别名:[],bean对象:s2
bean名称:com.spring.importBean2.Service1Config,别名:[],bean对象:com.spring.importBean2.Service1Config$$EnhancerBySpringCGLIB$$2b0c64cb@3cdf2c61
bean名称:s1,别名:[],bean对象:s1
总结
- @Import可以用来批量导入任何普通的组件、配置类,将这些类中定义的所有bean注册到容器中
- @Import常见的用法需要掌握
- 掌握ImportSelector、ImportBeanDefinitionRegistrar、DeferredImportSelector的用法
- DeferredImportSelector接口可以实现延迟导入、按序导入的功能
- spring中很多以@Enable开头的都是使用@Import集合ImportSelector方式实现的
- BeanDefinitionRegistry接口:bean定义注册器,这个需要掌握常见的方法
@Import注解是被下面这个类处理的 : ConfigurationClassPostProcessor
前面介绍的@Configuration、@Bean、@CompontentScan、@CompontentScans都是被这个类处理的,这个类是高手必经之路,建议花点时间研究研究。