丛林探险之Spring自定义注解加载Bean

丛林背景

自定义注解加载Bean是Spring框架提供的一个扩展点。基于这个扩展点可以实现灵活加载Bean的功能。

例如 Dubbo框架通过这个扩展点将添加了自定义注解@org.apache.dubbo.config.annotation.Service和@org.apache.dubbo.config.annotation.Reference的类加载到Spring的Ioc容器中。

今天我们像Dubbo框架一样自定义注解和必要的处理类来演绎自定义注解加载到Ioc容器的过程。

角色

(一)自定义注解 StrategyBean

/**
 * 自定义注解
 * 添加该自定义注解的类会被动态添加到Ioc容器
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface StrategyBean {
    String value();
}

@StrategyBean("beijing")
public class BeijingStrategy {
    public BeijingStrategy(){
        System.out.println("BeijingStrategy Created");
    }
}

@StrategyBean("hangzhou")
public class HangzhouStrategy {
    public HangzhouStrategy(){
        System.out.println("HangzhouStrategy Created");
    }
}

(二)自定义注解 StrategyBeanScan

这个类有两个功能
1) 组合了了一个注解 @Import(StrategyBeanScanRegistrar.class),这个Import注解会将StrategyBeanScanRegistrar加载到Ioc容器中,Ioc容器会在后续调用加载进来的StrategyBeanScanRegistrar的registerBeanDefinitions方法
2) 配置要扫描的包路径

这个注解可以添加在启动类上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(StrategyBeanScanRegistrar.class)
public @interface StrategyBeanScan {
    String[] basePackages() default {};

    Class[] basePackageClasses() default {};

    String[] value() default {};
}

@StrategyBeanScan(basePackages = {"com.liuapi.incubator.repository"})
public class RepositoryApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(RepositoryApplication.class);
    }
}

(三)自定义注册机 StrategyBeanScanRegistrar

StrategyBeanScanRegistrar实现了ImportBeanDefinitionRegistrar 接口,他的职责就是将工厂后置处理器StrategyBeanAnnotationBeanPostProcessor加载到Ioc容器中

/**
 * 该注册机的职责为 将工厂后置处理器StrategyBeanAnnotationBeanPostProcessor加载到Ioc容器中
 */
public class StrategyBeanScanRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        // 获取注解中配置的要扫描的包名集合
        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
        // 将Bean工厂后置处理器架加载到Ioc容器
        BeanDefinitionBuilder beanDefinitionBuilder =  BeanDefinitionBuilder.rootBeanDefinition(StrategyBeanAnnotationBeanPostProcessor.class);
        beanDefinitionBuilder.addConstructorArgValue(packagesToScan);
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    }
    private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(StrategyBeanScan.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;
    }
}

(四)自定义Bean工厂后置处理器StrategyBeanAnnotationBeanPostProcessor

这个Bean工厂后置处理器的职责就是扫描指定包下添加了自定义注解StrategyBean的类,并将这些类动态加载到Ioc容器中

/**
 * Note: 需要将该Bean工厂后置处理器导入到 Ioc容器中,需要借助@Import导入的注册机
 * Description: 该工厂后置处理器会扫描指定包下添加了自定义注解StrategyBean的类,并将这些类动态加载到Ioc容器中
 */
@Slf4j
public class StrategyBeanAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware {
    private final Set<String> packagesToScan;
    private Environment environment;
    private ResourceLoader resourceLoader;

    public StrategyBeanAnnotationBeanPostProcessor(Collection<String> packagesToScan) {
        this.packagesToScan = new LinkedHashSet<>(packagesToScan);
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        ClassPathBeanDefinitionScanner scanner =
                new ClassPathBeanDefinitionScanner(registry, false, environment, resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(StrategyBean.class));
        if (CollectionUtils.isEmpty(packagesToScan)) {
            return;
        }
        packagesToScan.stream()
                .forEach(
                        packageToScan -> {
                            int scan = scanner.scan(packageToScan);
                            if (0 == scan) {
                                log.warn("packagesToScan is empty , {} registry will be ignored!", StrategyBean.class.getSimpleName());
                            } else {
                                log.info("Load {} bean with Annotation {} in package {}", scan, StrategyBean.class.getSimpleName(), packageToScan);
                            }
                        }
                );

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

时序图

在这里插入图片描述

源码地址

github传送门

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值