우리는에 의해 봄 것을 알고 @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 {};
}
가져 오기 값이 일시적으로 무시하고 노트 봐 basePackages
와basePackageClasses
우리는 알고 @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. 项目
- 181013-SpringBoot 基础篇 Bean 之动态注册
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 项目:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-case/006-importbean
1. 一灰灰 Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰 Blog 个人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top