• Spring学习之ImportBeanDefinitionRegistrar接口


    一、本文内容分类

    1、接口功能
    2、接口运用场景
    3、使用案例
    4、注意事项

    二、接口功能介绍

    描述:ImportBeanDefinitionRegistrar接口是也是spring的扩展点之一,它可以支持我们自己写的代码封装成BeanDefinition对象,注册到Spring容器中,功能类似于注解@Service @Component。
    很多三方框架集成Spring的时候,都会通过该接口,实现扫描指定的类,然后注册到spring容器中,比如Mybatis中的Mapper接口,springCloud中的FeignClient接口,都是通过该接口实现的自定义注册逻辑。

    1、ImportBeanDefinitionRegistrar接口实现类,只能通过@Import注解的方式来注入,通常把@Import修饰在启动类或配置类。
    2、使用@Import,如果括号中的类是ImportBeanDefinitionRegistrar的实现类,启动时会触发ImportBeanDefinitionRegistrar接口的方法,将其中要注册的类注册成bean。
    3、实现该接口的类拥有注册bean的能力。

    //接口所有抽象方法,合并看就一个注册BeanDefinition的方法
    public interface ImportBeanDefinitionRegistrar {
    	//把自定义的类封装成BeanDefinition对象,注册到Spring里面去
    	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
    		registerBeanDefinitions(importingClassMetadata, registry);
    	}
    	//我们平时重写这个就可以了
    	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    	
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    三、接口运用场景

    四、使用案例

    1、案例1

    自定义业务类UserServiceTest,通过自定义ImportBeanDefinitionRegistrar实现类,将UserServiceTest注册到Spring容器中。在通过spring容器获取Bean=UserServiceTest

    //业务类
    public class UserServiceTest {
    	/**
    	 * 获取用户名称
    	 * @return 用户名称
    	 */
    	public String getUserName(){
    		return "测试";
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ImportBeanDefinitionRegistrar实现类

    //注意这里不能加注解,要通过Import导入进去。
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    	//业务类转成bd,注册到spring容器中注入
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    		//1、通过Bd工具类生成bd对象,只是这个Db对象比较纯洁没有绑定任何类
    		BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
    		GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
    		//2、设置bd绑定的类型
    		beanDefinition.setBeanClass(UserServiceTest.class);
    		//3、注册到spring容器中
    		registry.registerBeanDefinition("userServiceTest",beanDefinition);
    	}
    }
    
    @Configuration
    @Import(MyImportBeanDefinitionRegistrar.class)
    public class AppConfigClassTest {
    	//在配置类导入ImportBeanDefinitionRegistrar实现类
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    //测试
    public static void main(String[] args) {
    	AnnotationConfigApplicationContext AnnotationConfigApplicationContext =
    			new AnnotationConfigApplicationContext(AppConfigClassTest.class);
    	UserServiceTest userServiceTest = AnnotationConfigApplicationContext.getBean("userServiceTest", UserServiceTest.class);
    	String userName = userServiceTest.getUserName();
    	System.out.println(userName);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果只是把业务类注册到Spring容器中,我们通过其他注解就可以做到了,比如@Service、@Component,那么ImportBeanDefinitionRegistrar有没有更高级的玩法。

    2、案例2

    定义一个业务接口

    public interface UserServiceTestInterface {
    	public void list();
    }
    
    • 1
    • 2
    • 3

    将UserServiceTestInterface接口注册到Spring容器中,这里和上面案例不一样,上面的案例是注册一个具体的类,到Spring容器中,那么对于接口怎么注入。

    //spring的容器里面是不允许注入接口的,只能是接口的实现类。
    //这次我们通过代理类来完成对接口的实现,再把这个代理类注册到Spring容器里面去
    public class MyInvocationHandler implements InvocationHandler {
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		System.out.println("代理类逻辑代码");
    		return null;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    //注意这里不能加注解,要通过Import导入进去。
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    	 @Override
    	 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            //通过工具类生成一个bd,只是这个Db对象比较纯洁没有绑定任何类
    		BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
    		GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
    		//设置Bean的类型MyInvocationHandler,类型是实现类的类型,不是接口类型。因为在实例化的时候,调用的是设置类型所以对应的构造方法。
    		beanDefinition.setBeanClass(MyInvocationHandler.class);
    		//注册到Spring容器中进去
    		registry.registerBeanDefinition("userServiceTest",beanDefinition);
        }
    }
    //注意此时MyInvocationHandler还没有和UserServiceTestInterface接口关联上
    
    //通过配置类,导入ImportBeanDefinitionRegistrar的实现类
    @Configuration
    @Import(MyImportBeanDefinitionRegistrar.class)
    public class AppConfigClassTest {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    测试

    public static void main(String[] args) {
    	AnnotationConfigApplicationContext AnnotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfigClassTest.class);
    	//通过name获取Bean,注意此时的bean类型不是UserServiceTestInterface类型,而是MyInvocationHandler
    	Object userServiceTestInterface = AnnotationConfigApplicationContext.getBean("userServiceTest");
    	if(userServiceTestInterface instanceof MyInvocationHandler){
    		//代码实际会走到这里,把object转成带代理类。
    		MyInvocationHandler u = (MyInvocationHandler) userServiceTestInterface;
    		//生成接口UserServiceTestInterface的代理对象
    		UserServiceTestInterface o = (UserServiceTestInterface) Proxy.newProxyInstance(MainTest.class.getClassLoader(), new Class[]{UserServiceTestInterface.class}, u);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3、案例3

    定义一个业务接口,通过FactoryBean+InvocationHandler来生成该接口的代理类,无需手动写业务接口的实现类,很多底层框架就是这样实现的。

    InvocationHandler:主要是通过Invoke方法来拦截业务接口的方法
    FactoryBean:主要是用来生成的代理类。
    ImportBeanDefinitionRegistrar:在这里的作用就是帮忙我们把自定义的FactoryBean注册到Spring中

    //业务接口
    public interface UserServiceTestInterface {
    	public void list();
    }
    
    • 1
    • 2
    • 3
    • 4

    自定义FactoryBean这样我们控制Bean的创建的过程,实现InvocationHandler用来拦截业务接口的方法。
    关于FactoryBean的介绍可以看我这篇文章《FactoryBean是什么》

    //创建代理类,目标是UserServiceTestInterface接口,UserServiceTestInterface接口方法在执行前后都会被invoke方法拦截
    //FactoryBean可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程
    public class MyFactoryBean implements FactoryBean, InvocationHandler {
    
    	//为了使这个类更好地扩展。创建更多的接口,我们定义一个参数,让他们通过参数传递进来。
    	private Class classs;
    
    	//添加一个有参的构造方法。
    	public MyFactoryBean(Class classs){
    		this.classs = classs;
    	}
    
    	//拦截Class的所有方法
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		System.out.println("diaoyonlejiekou123");
    		return null;
    	}
    
    	//返回bean的对象。spring会自动把它add到容器里面去。
    	@Override
    	public Object getObject() throws Exception {
    		Class[] clazzs = new Class[]{classs};//目标类集合。
    		//通过proxy来得到代理对象。本来最有一个参数需要穿代理类对象,但因为本类实现了InvocationHandler,所以只需传this
    		Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), clazzs, this);
    		return proxy;//返回的这个对象,会把加到spring的容器中。
    	}
    
    	//返回要添加到容器里bean的类型
    	@Override
    	public Class<?> getObjectType() {
    		return this.classs;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    自定义ImportBeanDefinitionRegistrar实现类,把我们自定义的FactoryBean注册到Spring中。

    //注意这里不能加注解,要通过Import导入进去。
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    		//通过工具类生成一个bd,只是这个Db对象比较纯洁没有绑定任何类
    		BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
    		//为什么要转成GenericBeanDefinition这种类型。因为GenericBeanDefinition有更多修改bd属性的方法。后面我会介绍为什么要修改属性。
    		GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
    		
    		//这里很重要。getConstructorArgumentValues是为了获取该bd的所有构造方法,因为我们重写了有参构造方法,所有我们需要带参数过去 
    		//不然spring没法帮我们实例化,addGenericArgumentValue是添加参数,该代码会执行两步
    		//第一步是匹配对应的构造方法,第二步是实例化。
    		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserServiceTestInterface.class.getName());
    		//因为代理对象类型的,实例化的时候走的是代理类的构造方法
    		beanDefinition.setBeanClass(MyFactoryBean.class);
    		//注册bd
    		registry.registerBeanDefinition("userServiceTest",beanDefinition);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    测试

    public static void main(String[] args) {
    	AnnotationConfigApplicationContext AnnotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfigClassTest.class);
    	//通过name获取Bean
    	Object userServiceTestInterface = AnnotationConfigApplicationContext.getBean("userServiceTest");
    	//针对这种场景Bean的类型是,通过FactoryBean的getObjectType方法返回的。
    	UserServiceTestInterface u = (UserServiceTestInterface) userServiceTestInterface;
    	u.list();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8


    ImportBeanDefinitionRegistrar是被谁处理了?

    因为@Import是修饰在配置类里面的,所以在解析配置类的时候,也就是ConfigurationClassPostProcessor处理的时候,会解析@Import注解,然后会判断@Import导入类是否是ImportBeanDefinitionRegistrar的子类,执行重写的registerBeanDefinitions方法。这样我们的类就被加到BD集合里面去了。

  • 相关阅读:
    【今天是Ajax同学的到来,我们了解一下她吧】
    【一起学Rust 基础篇】Rust基础——变量和数据类型
    【ceph】AI时代-数据为王-ceph存储将成为未来比较看好的赛道之一,为什么不all in一把学习一个不那么卷的赛道呢?
    Keras深度学习实战(36)——基于编码器-解码器的机器翻译模型
    结点的查找
    服务器硬件有哪些组成
    新茶饮进入“大逃杀”赛程
    pytest+allure生成测试报告
    排序3——C语言
    商业合作保密协议 (1)
  • 原文地址:https://blog.csdn.net/weixin_37862824/article/details/133062243