前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何实现类似@Component的Spring动态注入功能

如何实现类似@Component的Spring动态注入功能

作者头像
码农小胖哥
发布2020-09-27 20:43:04
1.2K0
发布2020-09-27 20:43:04
举报

1. 前言

我们在上一篇Mybatis如何将Mapper接口注入Spring IoC进行了分析,有同学问胖哥这个有什么用,这个作用其实挺大的,比如让你实现一个类似@Controller的注解(或者继承某个统一接口)来完成比如定时任务的统一注入或者Websocket处理器的统一注入等这种将某种共性的Bean动态注入。

代码语言:javascript
复制
// 模仿 Controller
@XBean(description = "ETL JOB")
public class JobShedule {

    @Caller(cron = "* * 0/5 * * ?")
    public void exec(){
        // job
    }
}

以上伪代码就是一个模仿 Controller 的定时任务 Bean。

2. 设计思路

详细的开发设计思路我已经总结好了,各位同学只要按部就班就可以实现这个功能了。

2.1 定义扫描注解

定义一个类似@MappScan的进行导入自定义ImportBeanDefinitionRegistrar,并指定扫描包范围。

代码语言:javascript
复制
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import(XBeanDefinitionRegistrar.class)
public @interface XBeanScan {

    String[] basePackages();
}

我们自定义了一个扫描注解@XBeanScan。它有两个作用:

  • 通过basePackages指定扫描包的范围。
  • 导入我们自定义ImportBeanDefinitionRegistrar 的实现XBeanDefinitionRegistrar

2.2 定义目标 Bean 的通用标记

通常我们可以选择一个标识接口,所有其实现类都会注入Spring IoC;或者用更加方便的注解,所有被该注解标记的类都将注入Spring IoC。这里我们使用更加灵活方便的注解,实现了一个@XBean标记注解:

代码语言:javascript
复制
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface XBean {
    String description() default "";
}

2.3 实现扫描器

Spring框架为我们提供了扫描器来注册被标记的Bean,它就是上节提到的ClassPathBeanDefinitionScanner,我们继承它进行稍加改造:

代码语言:javascript
复制
public class XBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    public XBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
        super.addIncludeFilter(new AnnotationTypeFilter(XBean.class));
    }
}

这里我们不使用默认的过滤器,我们指定了扫描器扫描的目标为被@XBean标记的那些Bean

2.4 实现 Bean 注册机

重头戏来了,我们需要将2.12.3定义的这些组件在ImportBeanDefinitionRegistrar的实现中组装起来。

代码语言:javascript
复制
/**
 * The type X bean definition registrar.
 *
 * @author felord.cn
 * @since 2020 /9/18 22:59
 */
public class XBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 不使用默认过滤器
        XBeanDefinitionScanner xBeanDefinitionScanner = new XBeanDefinitionScanner(registry, false);
        xBeanDefinitionScanner.setResourceLoader(resourceLoader);
        // 扫描XBeanScan注解指定的包
        xBeanDefinitionScanner.scan(getBasePackagesToScan(importingClassMetadata));
    }

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

    /**
     * 获取{@link XBeanScan}中声明的扫描包路径
     * @param metadata the meta
     * @return  包路径数组
     */
    private String[] getBasePackagesToScan(AnnotationMetadata metadata) {
        String name = XBeanScan.class.getName();
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
        Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
                + " annotated with " + ClassUtils.getShortName(name) + "?");
        return attributes.getStringArray("basePackages");
    }
}

从注解元数据importingClassMetadata解析我们需要的扫描路径basePackages等元数据,然后让扫描器在该路径扫描即可。

2.5 使用

在具有@Configuration标记的类或者Spring BootMain类上使用@XBeanScan即可,是不是非常简单!

其实@ComponentScan提供类似的功能。

3. 总结

本篇是对上一篇理论的具体应用,如果你需要细粒度控制就加上那些BeanDefinitionRegistryPostProcessorFactoryBeanSpring提供的功能性接口。从这两篇中更多需要你学习的是如何从阅读源码中触类旁通,来利用已有的组件来实现自己的逻辑。这对你的提高是极大的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农小胖哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. 设计思路
    • 2.1 定义扫描注解
      • 2.2 定义目标 Bean 的通用标记
        • 2.3 实现扫描器
          • 2.4 实现 Bean 注册机
            • 2.5 使用
            • 3. 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档