11.spring系列- bean的注册@Import

  1. @Import是做什么的?
  2. @Import使用有几种方式?有何区别?
  3. DeferredImportSelector是做什么的,他和ImportSelector有什么区别?
  4. spring中哪些功能是通过@Import来实现的?
  5. spring中是如何解析@Import注解的?

@Import的value常见的几种用法

  1. value为普通类
  2. value为@Configuration标注的类
  3. value为@CompontentScan标注的类
  4. value为ImportBeanDefinitionRegistrar接口类型
  5. value为ImportSelector接口类型
  6. 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的用法:

  1. 定义ImportBeanDefinitionRegistrat接口的实现类
  2. 使用@Import来导入这个实现类
  3. 把@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不同地方有两点:

  1. 延迟导入
  2. 指定导入的类的处理顺序

延迟导入

比如@Import的value包含了多个普通类、多个@Configuration标注的配置类、多个ImportSelector接口的实现类,多个ImportBeanDefinitionRegistrar接口的实现类,还有DeferredImportSelector接口实现类,此时spring处理这些被导入的类的时候,会将DeferredImportSelector类型的放在最后处理,会先处理其他被导入的类,其他类会按照value所在的前后顺序进行处理。

指定导入的类的处理顺序

指定顺序常见2种方式:

  1. 实现Ordered接口的方式
  2. 实现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

总结

  1. @Import可以用来批量导入任何普通的组件、配置类,将这些类中定义的所有bean注册到容器中
  2. @Import常见的用法需要掌握
  3. 掌握ImportSelector、ImportBeanDefinitionRegistrar、DeferredImportSelector的用法
  4. DeferredImportSelector接口可以实现延迟导入、按序导入的功能
  5. spring中很多以@Enable开头的都是使用@Import集合ImportSelector方式实现的
  6. BeanDefinitionRegistry接口:bean定义注册器,这个需要掌握常见的方法

@Import注解是被下面这个类处理的 : ConfigurationClassPostProcessor
前面介绍的@Configuration、@Bean、@CompontentScan、@CompontentScans都是被这个类处理的,这个类是高手必经之路,建议花点时间研究研究。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值