• 利用ImportBeanDefinitionRegistrar和BeanPostProcessor实现Spring对自定义注解bean的管理


    最近有需求是将某些类用代理来调用,屏蔽一些通信上的细节。直接写的话需要手动创建代理对象来用,比较麻烦,转成Spring来进行管理。利用自定义注解来规定需要bean容器管理的类,之后在BeanPostProcessor中进行增强处理即可。把这部分抽离出来写了个demo记录下。

    自定义注解

    ServiceRegisterClass

    作用在类上的注解,Spring扫描到该注解类会进行bean创建并管理。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface ServiceRegisterClass {
    }
    
    • 1
    • 2
    • 3
    • 4

    ServiceRegisterField

    作用在成员上的注解,在Bean初始化过程中进行该对象的代理对象的注入。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface ServiceRegisterField {
    }
    
    • 1
    • 2
    • 3
    • 4

    ServiceScan

    作用在调用主方法的类上的注解,scanPackage用来标记扫描范围。这个注解最重要的是import了ServiceScannerRegistrar.class。这个类实现了ImportBeanDefinitionRegistrar来注册我们的自定义注解给bean容器。

    // 自定义扫描注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(ServiceScannerRegistrar.class)
    public @interface ServiceScan {
        // 用于设置扫描的包
        String[] scanPackage();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Spring bean 扫描与创建规则改写

    ServiceScannerRegistrar 扫描规则注册类

    这个类是最关键的之一,通过实现ImportBeanDefinitionRegistrar中的registerBeanDefinitions方法,就可以进行增加我们的自定义注解到bean容器扫描的范围中。

    public class ServiceScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
        private ResourceLoader resourceLoader;
    
        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }
    
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    首先对获取注解的元信息,找到注解中属性scanPackage的值,未找到就默认扫描当前启动类所在包。

            // 获取自定义扫描注解的信息
            AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(ServiceScan.class.getName()));
            String[] scanPackages = new String[0];
            if (annotationAttributes != null) {
                scanPackages = annotationAttributes.getStringArray("scanPackage");
            }
            if (scanPackages.length == 0) {
                // 未设置就默认为自定义注解作用类所在包
                scanPackages = new String[]{((StandardAnnotationMetadata) importingClassMetadata).getIntrospectedClass().getPackage().getName()};
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    除了Spring自带的bean注解(@Component等),增加自定义注解@ServiceRegisterClass作为bean注解让bean容器进行解析。ClassPathBeanDefinitionScanner构造方法的第二个参数为是否保留自带的bean注解。

            // 增加自定义注解的扫描,同时保留spring预设bean注解的扫描(@Component等)
            ClassPathBeanDefinitionScanner serviceScanner = new ClassPathBeanDefinitionScanner(registry,true);
            serviceScanner.addIncludeFilter(new AnnotationTypeFilter(ServiceRegisterClass.class));
            if (resourceLoader != null) {
                serviceScanner.setResourceLoader(resourceLoader);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后扫描即可。

            // 开始扫描
            int beanCount = serviceScanner.scan(scanPackages);
            System.out.println("bean count:" + beanCount);
        }
    
    • 1
    • 2
    • 3
    • 4

    ServiceBeanPostProcessor bean初始化增强类

    实现BeanPostProcessor,在处理bean的时候寻找@ServiceRegisterField注解的对象并注入代理对象。

    // BeanPostProcessor对bean创建过程处理
    @Component
    public class ServiceBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            // 获取bean的field中的注解信息,注入有ServiceRegisterField注解的成员
            System.out.println("bean:" + beanName + " created");
            Field[] fields = bean.getClass().getDeclaredFields();
            for (Field field:fields) {
                 ServiceRegisterField serviceRegisterField = field.getAnnotation(ServiceRegisterField.class);
                 // 存在则注入
                if (serviceRegisterField != null) {
                    System.out.println("find service field");
                    Enhancer enhancer = new Enhancer();
                    enhancer.setClassLoader(field.getType().getClassLoader());
                    enhancer.setSuperclass(field.getType());
                    enhancer.setCallback(new CGLibInterceptor());
                    Object proxy = enhancer.create();
                    field.setAccessible(true);
                    try {
                        field.set(bean, proxy);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            return bean;
        }
    }
    
    • 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

    要注意的是BeanPostProcessor需要被Spring bean管理才能生效,所以加了个@Component

    CGLibInterceptor CGLib代理增强类

    因为没有接口所以用了CGLib来演示代理。

    public class CGLibInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("CGLib proxying, do something in proxy");
            return methodProxy.invokeSuper(o, objects);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    演示

    Hello.class

    @ServiceRegisterClass
    public class Hello {
        public String hello(){
            return "hello world test class";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Hello2.class

    public class Hello2 {
        public String hello(){
            return "hello world test field";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    MainController

    @Component
    public class MainController {
    
        @ServiceRegisterField
        Hello2 hello2;
    
        @Autowired
        Hello hello;
    
        public void printHello(){
            System.out.println("output1:" + hello.hello());
            System.out.println("output2:" + hello2.hello());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    启动类 StartApplication.class

    // 调用类
    // 自定义扫描注解
    @ServiceScan(scanPackage = {"com.huiluczp"})
    public class StartApplication {
        public static void main(String[] args) {
            // 基于注解的容器
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(StartApplication.class);
            MainController mainController = (MainController)applicationContext.getBean(MainController.class);
            mainController.printHello();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出结果:
    在这里插入图片描述
    总结:
    把注入过程交给spring管理确实方便很多,通过代理屏蔽细节在调用对象的时候只需要打上对应注解就行,拓展很方便挺好的。简单demo记录,觉得有用就看看吧。

  • 相关阅读:
    【JUC】JMM
    深度学习pytorch实战二:AlexNet图像分类篇且官网提供花数据集分五类
    Vega Prime入门教程12.01:运行测试
    网络安全-典型的恶意代码
    分析Jetpack Compose动画内部是如何实现的
    vue ant 隐藏列
    L63.linux命令每日一练 -- 第九章 Linux进程管理命令 -- runlevel、init和service
    「北大社送书」学习Flutter编程 — 《从零基础到精通Flutter开发》
    Rust 如何优雅关闭 channel
    leetcode299--猜数字游戏
  • 原文地址:https://blog.csdn.net/qq_41733192/article/details/125908548