SpringBoot application piece of Bean implement a custom registrar from 0-1

191213-SpringBoot applications implement a custom piece of Bean register from 0 to 1


We know that in the spring by @Component, @Service, @Repositorydecorate a class, by automatically scanning registered as a bean; can also be configured in a class by means of @Beanregistering bean; then in addition to this in several ways, what other way to declare a class for the bean Why?

Whether we can customize a comment, and then decorated with this annotation initiative be declared as class registration to spring bean container, in order to achieve a similar @Componenteffect?

The next article will introduce, if through ImportBeanDefinitionRegistrarto achieve registration bean combination of custom annotation, knowledge mainly used as follows:

  • ImportBeanDefinitionRegistrar Sign bean core classes
  • @Import Import Configuration
  • ClassPathBeanDefinitionScanner

I. custom bean registrar

Although our goal is relatively clear, but suddenly let us achieve such a thing, really somewhat at a loss, it should be where to start?

0. looking for "tribute" Object

If I've seen before on SpringBoot combine java web Three Musketeers (Filter, Servlet, Listener) related Bowen students, should remember that knowledge is an important point:

  • @WebListener, @WebServlet, @WebFilterThree belonging to the annotation specification Servlet3 +
  • In SpringBoot projects, such as the need notes above take effect, you need to add notes on startup class @ServletComponentScan

See above, this is not a trace of inspiration will be excited (at the time when Bowen wrote above, specifically looked at the logic behind annotated), hey, I feel to find a path to a successful trip

Since the @WebXxxnotes are not native Spring annotation support, so let him comment in effect @ServletComponentScanbecomes very important, obviously it acts as a bridge (engaged in things), then we salute (copied) objects there

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

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

Annotation defines relatively simple, the final entry into force Needless to say, certainly ServletComponentScanRegistrar, and then followed by a look at one

(SpringBoot different versions, to achieve the above classes may be some differences, the above code, taken from the spring-boot version 2.1.2.RELEASE package)

1. Prepare papers

Tribute object is found, the next start some preparatory work before the official implementation, first of all we target specific instances of

  • Have a custom annotations on all classes @Metaof classes, will be registered to Spring container, as an ordinary objects Bean

Then the test is to test to verify that the entry into force of the key case

  • No external dependencies of @Metawhether the class can be properly identified spring
  • @MetaIt can be another class beanor @Metacategory by @Autowiredintroducing
  • @MetaWhether classes can normally rely on a common bean, @Metaclass

2. Start achieve

a. @Meta annotation defines

Similar @Componentannotation functions, we can get a little simple

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

b. @MetaComponentScan comment

The notes and @ServletComponentScanthe role almost, is mainly used to load the ImportBeanDefinitionRegistrarimplementation class, the latter is defined bean core classes

Achieve the following

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MetaAutoConfigureRegistrar.class})
public @interface MetaComponentScan {
    @AliasFor("basePackages") String[] value() default {};

    @AliasFor("value") String[] basePackages() default {};

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

Import values temporarily ignore, and look at the notes basePackagesandbasePackageClasses

We know that @ComponentScanthe role is mainly used in the class which specifies the path to the package open annotation scanning; MetaComponentScanseveral members of the main roles and the same as above;

  • When the specified value, the main load paths in these packages, comprising @Metaclass annotation;
  • If all default values (i.e. empty), then the scanning path that the packet corresponding to the class where all the annotation contains @Metaclasses

c. MetaAutoConfigureRegistrar

接下来进入我们的核心类,它主要继承自ImportBeanDefinitionRegistrar,bean 定义注册器,其核心方法为

void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}

两个参数,第一个顾名思义,注解元数据,多半是用来获取注解的属性;第二个 bean 定义注册器,我们在学习 bean 的动态注册时(详情参考: - 181013-SpringBoot 基础篇 Bean 之动态注册) 知道可以用 BeanDefinitionRegistry 注册 bean,因为我们这里的目标是注册所有带 @Meta 注解的类

自然而然的想法

  • 扫描所有的类,判断是否有@Meta注解,有则通过 registry 手动注册

然而在实际动手之前,再稍微停一停;扫描所有类判断是否有某个注解,这个操作在 spring 中应该属于比较常见的 case(why?),应该是有一些可供我们使用的辅助类

继续撸"致敬"的对象,ServletComponentScanRegistrar类主要是注册servletComponentRegisteringPostProcessor,所以我们再转移目标到后者的详情(下图来自org.springframework.boot.web.servlet.ServletComponentRegisteringPostProcessor#createComponentProvider)

到这里我们的思路又打开了,可以借助ClassPathScanningCandidateComponentProvider来实现 bean 注册


上面的一段内容属于前戏,放在脑海里迅速的过一过就好了,接下来进入正文;

首先是创建一个ClassPathScanningCandidateComponentProvider的子类,注册一个AnnotationTypeFilter,确保过滤获取所有@Meta注解的类

private static class MetaBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    public MetaBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
            Environment environment, ResourceLoader resourceLoader) {
        super(registry, useDefaultFilters, environment, resourceLoader);
        registerFilters();
    }

    protected void registerFilters() {
        addIncludeFilter(new AnnotationTypeFilter(Meta.class));
    }
}

然后就是获取扫描的包路径了,通过解析前面定义的MetaComponentScan的属性来获取

private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
    AnnotationAttributes attributes =
            AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(MetaComponentScan.class.getName()));
    String[] basePackages = attributes.getStringArray("basePackages");
    Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");

    Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
    for (Class clz : basePackageClasses) {
        packagesToScan.add(ClassUtils.getPackageName(clz));
    }

    if (packagesToScan.isEmpty()) {
        packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
    }

    return packagesToScan;
}

所以完整的 MetaAutoConfigureRegistrar 的实现就有了

public class MetaAutoConfigureRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    private ResourceLoader resourceLoader;

    private Environment environment;

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

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

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        MetaBeanDefinitionScanner scanner =
                new MetaBeanDefinitionScanner(registry, this.environment, this.resourceLoader);
        Set<String> packagesToScan = this.getPackagesToScan(importingClassMetadata);
        scanner.scan(packagesToScan.toArray(new String[]{}));
    }

    private static class MetaBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
      // ... 参考前面,这里省略
    }

    private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
      // ... 参考前面,这省略
    }
}

II. 测试与小结

上面实现现在看来非常简单了(两个注解定义,一个核心类,也复杂不到哪里去了);接下来就需要验证这个是否生效了

1. case0 Meta 注解类

如果被 spring 识别为 bean,则构造方法会被调用

@Meta
public class DemoBean1 {
    public  DemoBean1() {
        System.out.println("DemoBean1 register!");
    }
}

2. case1 Meat 注解类,依赖 Bean

定义一个普通的 bean 对象

@Component
public class NormalBean {
    public NormalBean() {
        System.out.println("normal bean");
    }
}

然后定义一个 Meta 装饰的类,依赖 NormalBean

@Meta
public class DependBean {
    public DependBean(NormalBean normalBean) {
        System.out.println("depend bean! " + normalBean);
    }
}

3. case2 bean 依赖 Meta 注解类

@Component
public class ABean {
    public ABean(DemoBean1 demoBean1) {
        System.out.println("a bean : " + demoBean1);
    }
}

4. 测试

启动类,注意需要添加上我们自定义的@MetaComponentScan注解

@SpringBootApplication
@MetaComponentScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

执行输出结果

5. 小结

本文主要介绍了如何通过ImportBeanDefinitionRegistrar来实现自定义的 bean 注册器的全过程,包括面向新手可以怎样通过"致敬"既有的代码逻辑,来"巧妙"的实现我们的目标

II. 其他

0. 项目

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog

Guess you like

Origin www.cnblogs.com/yihuihui/p/12038632.html