自研框架之自定义注解标注的Bean交由Spring管理

开篇前,我给此文起个小标题(受老钱的《Redis深度历险》有感起的)----自研框架中那颗最核心的螺丝
在注解驱动编程模型下,自定义注解永远避不开;在Java领域中,同样离不开Spring。相信大家都对Dubbo的@Service(高版本已变为@DubboService)、@Reference(高版本已变为@DubboReference)以及Mybatis的@Mapper注解等有所了解。但是有没有深入想过他们区区一个普通注解是怎么让spring接管这些注解所标记的Bean呢?

在注解模式下,纳入Spring管理方法

  • 运用Spring提供的元注解@Component。这种方式是最暴力最傻瓜的,同时也是最简单的。但缺点也是很明显的,扩展性弱,因为它将遵循spring默认的处理方式。你能运用就只剩spring提供的那些后置处理器或者钩子方法来扩展。比如,你想有自己专门的BeanDefinition装配模式,不想按照spring默认的,这时你就要用BeanDefinitionRegistryPostProcessor这个扩展点去修改BeanDefinition元配置信息。

通俗点说,就是有一把五颜六色的豆子和一个麻袋,但在麻袋里,你要的只想要一种颜色。按照上面的来说,不管三七二十一,一把豆子放下去,再把不需要的豆子挑出来,达到麻袋里只剩你要的颜色的豆子。方法可行,但显得有点那个。为何不如一开始就只放你想要的那种豆子放到麻袋呢?这就是将要介绍的第二方式。
其实,Dubbo的PMC 小马哥也说过Dubbo最开始的做法也是这样,后面不好扩展才改换的。

  • 扩展spring。自定义扫描器,自定义注册Bean逻辑等。这也是自研框架中最推荐的做法。

接下来笔者将以第二种方法以例子示范重点展开讲解。下文需要读者有一定的spring功底才能比较容易理解,比如spring的bean生命周期,spring的扩展点以及钩子方法都有一定基础的了解。

自定义注解

自定义注解@Honey,作用就是类似spring的@Controller、@Service等注解标注这是一个spring组件(Bean)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Honey {
}

第一个注解是打标记,那么就得有个在spring启动时触发扫描我们自定义注解标记的Bean的注解。我们起名@HoneyComponentScan。最关键的就是告诉从哪里扫起,此时扫描包的指定就很关键了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HoneyComponentScanRegistrar.class)
public @interface HoneyComponentScan {

    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

此时,这个注解就可以打在项目启动类上了。细心的读者会发现这个注解里有@Import一个类,熟悉springboot的读者应该不难理解,这是springboot的自动装配中的一个机制特点。你可以@Import直接引入,也可以实现ImportSelector 接口。这里不再展开赘述。

自定义扫描注册器

怎么扫,怎么注册。这是接下来的核心流程。自定义HoneyComponentScanRegistrar。实现spring的
ImportBeanDefinitionRegistrar 扩展点。重写registerBeanDefinitions逻辑。

public class HoneyComponentScanRegistrar implements ImportBeanDefinitionRegistrar {


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		// 从HoneyComponentScan注解解析得到包路径集合
        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
		// 构建并注册一个BeanFactoryPostProcessor后置处理器
        registerHoneyAnnotationBeanPostProcessor(packagesToScan, registry);
    }


    private void registerHoneyAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        BeanDefinitionBuilder builder = rootBeanDefinition(HoneyAnnotationBeanPostProcessor.class);
        builder.addConstructorArgValue(packagesToScan);
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);

    }

    private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(HoneyComponentScan.class.getName()));
        String[] basePackages = attributes.getStringArray("basePackages");
        Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
        String[] value = attributes.getStringArray("value");
        // Appends value array attributes
        Set<String> packagesToScan = new LinkedHashSet<String>(Arrays.asList(value));
        packagesToScan.addAll(Arrays.asList(basePackages));
        for (Class<?> basePackageClass : basePackageClasses) {
            packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
        }
        if (packagesToScan.isEmpty()) {
            return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
        }
        return packagesToScan;
    }
}

自定义BeanFactoryPostProcessor后置处理器–HoneyAnnotationBeanPostProcessor。

实现BeanDefinitionRegistryPostProcessor,重写postProcessBeanDefinitionRegistry。它是BeanFactoryPostProcessor的子类。各种织面是各种资源的织入。在这里我们可以对即将注册的Bean的beanDefinition进行专门处理,可以加入我们各种特定的自定义业务逻辑。

像Dubbo就是在这里构建了ServiceBean,加入了一些需要暴露的属性信息。
笔者猜测Mybatis的动态代理将mapper接口与xml的sql建立映射关系也是在这里,后面研究mybatis源码再来论证一下。

public class HoneyAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware, BeanClassLoaderAware {
       
      .......
      
@Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
            registerServiceBeans(resolvedPackagesToScan, registry);
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
            }
        }
    }

    private Set<String> resolvePackagesToScan(Set<String> packagesToScan) {
        Set<String> resolvedPackagesToScan = new LinkedHashSet<String>(packagesToScan.size());
        for (String packageToScan : packagesToScan) {
            if (StringUtils.hasText(packageToScan)) {
                String resolvedPackageToScan = environment.resolvePlaceholders(packageToScan.trim());
                resolvedPackagesToScan.add(resolvedPackageToScan);
            }
        }
        return resolvedPackagesToScan;
    }

    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        HoneyClassPathBeanDefinitionScanner scanner =
                new HoneyClassPathBeanDefinitionScanner(registry, environment, resourceLoader);

        BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);

        scanner.setBeanNameGenerator(beanNameGenerator);

        scanner.addIncludeFilter(new AnnotationTypeFilter(Honey.class));

        for (String packageToScan : packagesToScan) {
            // Registers @Service Bean first
            scanner.scan(packageToScan);
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

                for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                    registerServiceBean(beanDefinitionHolder, registry, scanner);
                }

              .............
        }

    }
}

测试验证

建立两个测试类,含依赖注入。

@Honey
public class MyLove {

    private String loveName;

    private int loveAge;
}

@Honey
public class MyHoney {

    @Autowired
    private MyLove myLove;

    private String honeyName;

    private int honeyAge;
}

在这里插入图片描述
成功纳入spring容器管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊杰eboy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值