콩의 SpringBoot 응용 프로그램의 조각은 0-1에서 사용자 정의 등록을 구현

191213 - SpringBoot 응용 프로그램은 콩의 사용자 지정 조각이 0에서 1로 등록 구현


우리는에 의해 봄 것을 알고 @Component, @Service, @Repository자동으로 빈으로 등록 스캔하여, 클래스를 장식, 또한 이용하여 클래스를 구성 할 수 있습니다 @Bean등록 콩 한 후 여러 가지 방법이뿐만 아니라, 다른 어떤 방법으로 선언 빈 왜위한 클래스?

하든 우리는 의견을 사용자 정의하고 유사한 달성하기 위해, 스프링 빈 컨테이너 클래스 등록으로 선언이 주석 이니셔티브로 장식 할 수 @Component효과를?

를 통해하면 다음 기사, 소개합니다 ImportBeanDefinitionRegistrar다음과 같이 사용자 정의 주석의 등록 빈 조합을 달성하기 위해, 지식을 주로 사용 :

  • ImportBeanDefinitionRegistrar 로그인 콩 핵심 클래스
  • @Import 구성 가져 오기
  • ClassPathBeanDefinitionScanner

I. 정의 콩 등록

어디서부터 시작 우리의 목표는 상대적으로 손해를보고 정말 약간, 취소, 갑자기 우리가 그런 일을 달성 할 수 있도록, 그것은해야하고 있지만?

"공물"개체를 찾고 0

내가 SpringBoot에 한 번도 본 적이 있다면 삼총사가 (필터, 서블릿, 리스너) 관련 보웬의 학생들이 지식을 기억해야 자바 웹은 중요한 포인트입니다 결합 :

  • @WebListener, @WebServlet, @WebFilter세 주석 사양 Servlet3 +에 속하는
  • 이러한 시행한다 위의 요구 사항 등 SpringBoot 프로젝트, 당신은 시작 클래스에 메모를 추가 할 필요가 @ServletComponentScan

이 (특히 주석 뒤에 논리 보았다 보웬 위에서 쓴시) 흥분됩니다 영감의 흔적, 헤이, 나는 성공적인 여행 경로를 찾는 느낌이 아니라, 위의 내용을 참조하십시오

때문에 @WebXxx음이 아닌 네이티브 봄 주석 지원, 그래서 효과가 그를 댓글 수 있도록 @ServletComponentScan매우 중요, 분명히 그것은, 우리는 (복사) 오브젝트 경례 (일에 종사)의 다리가 역할

@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 {};
}

주석을 정의는 상대적으로 간단하고, 힘없이으로 최종 엔트리는 확실히 말을 ServletComponentScanRegistrar하고 일을 살펴 다음

(SpringBoot 다른 버전 스프링 부팅 버전 2.1.2.RELEASE 패키지 찍은 일부 차이 상기 코드 일 수있다 상기 등급을 달성하기 위해)

논문을 준비하십시오

공물 객체는 다음 우선 우리가 특정 인스턴스를 대상으로, 공식 구현하기 전에 몇 가지 준비 작업을 시작, 발견

  • 모든 클래스에 사용자 지정 주석이 @Meta클래스를 일반 객체 Bean으로, 스프링 컨테이너에 등록됩니다

이어서 테스트는 키 케이스의 발효를 확인하는 시험이다

  • 의 외부 의존성없는 @Meta클래스가 제대로 식별 할 수 있는지 여부를 봄
  • @Meta그것은 또 다른 클래스가 될 수 있습니다 bean또는 @Meta하여 카테고리 @Autowired소개
  • @Meta여부 클래스는 일반적으로 일반에 의존 할 수 bean, @Meta클래스

2. 시작 달성

가. @Meta 주석을 정의

유사 @Component주석 기능, 우리는 조금 단순한를 얻을 수 있습니다

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

나. @MetaComponentScan 코멘트

노트와 @ServletComponentScan거의 주로로드하는 데 사용되는 역할 ImportBeanDefinitionRegistrar구현 클래스는, 후자는 빈 코어 클래스를 정의

다음을 달성

@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 {};
}

가져 오기 값이 일시적으로 무시하고 노트 봐 basePackagesbasePackageClasses

우리는 알고 @ComponentScan, 역할이 주로 패키지 열고 주석 검색 경로를 지정하는 클래스에 사용되는 MetaComponentScan몇 가지 주요 역할의 회원들과 상기와 같은;

  • 경우 상기 지정된 값이 패키지의 메인 하중 경로 @Meta클래스 주석;
  • 모든 기본 값 (즉 비어 있음), 다음 검색 경로는 패킷이 클래스에 대응하는 경우 모든 주석을 포함하는 경우 @Meta클래스를

기음. 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

추천

출처www.cnblogs.com/yihuihui/p/12038632.html