本文主要讨论Spring通过注解@Component及派生注解配置类的方式,主要有以下内容
- 使用方式
- 原理
- 扩展
- 总结
使用方式
注解@Component
是标注在类上,使得类成为Spring容器管理的对象,@Repository
@Service
@Controller
类都是@Component
的派生注解,拥有和@Component
相同的功能,目前只是作为某一类型的说明。例如,@Component
和@Service
都可以作为服务层类的注解,但是@Service
可以更直观的表示服务层类。其他说明如下:
@Reporitory
持久层注解@Service
服务层注解@Controller
视图层注解
@Componet
注解的使用方式如下:
@Component
public class HelloWorld {
public void test() {
System.out.println("hello world");
}
}
@Componet
注解并非一定会被加载,一般需要使用@ComponentScan
注解指定扫描的包,类HelloWord
若在@ComponentScan
扫描包内,则可以被扫描
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
同样,使用@Repository
@Service
@Controller
这三种派生注解,也需要@ComponentScan
配置扫描的包
原理
加载过程
首先看@Component
注解如何被加载的,搜索源码,发现@Component
在ClassPathScanningCandidateComponentProvider.registerDefaultFilters
中被使用, 以下通过一个实例调试代码
public class AnnotationBootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.scan("com.ethan.diveinspringboot.bean");
applicationContext.refresh();
HelloWorld helloWorld = applicationContext.getBean(HelloWorld.class);
helloWorld.test();
}
}
Debug启动后,断点追踪至ClassPathScanningCandidateComponentProvider.registerDefaultFilters
从调试堆栈中可以看出,@Component
注解是在AnnotationConfigApplicationContext
实例化时被调用,然后被添加至org.springframework.context.annotation.ClassPathBeanDefinitionScanner.includeFilters
中
/**
* Register the default filter for {@link Component @Component}.
* <p>This will implicitly register all annotations that have the
* {@link Component @Component} meta-annotation including the
* {@link Repository @Repository}, {@link Service @Service}, and
* {@link Controller @Controller} stereotype annotations.
* <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
* JSR-330's {@link javax.inject.Named} annotations, if available.
*
*/
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
我们再看includeFilters
在哪里被使用,通过查找发现在org.springframework.context.annotation.ClassPathBeanDefinitionScanner.isCandidateComponent
中使用,代码详情如下
/**
* Determine whether the given class does not match any exclude filter
* and does match at least one include filter.
* @param metadataReader the ASM ClassReader for the class
* @return whether the class qualifies as a candidate component
*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
放开调试代码,继续走代码逻辑,进入isCandidateComponent
方法中,通过注释可以看出本分本方法是判断MetadataReader
是否匹配规则,先判断是否在excludeFilters
中包含,在判断是否有任意一个includeFilters
匹配,以此确定是否将MetadataReader
对应的类加载至Spring 容器中。
在继续观察类此时的堆栈情况:
从堆栈中方法名,可以大致猜测出,此时正扫描@Component
对应组件,以此判断是否加载到Spring容器中。
派生性原理
在使用方式中提到@Component
具有派生性,派生出@Repository
、@Service
、@Controller
。对于Java本身并不具有派生性,所以@Component
的派生性是Spring赋予的能力。
在回头看上面提到的org.springframework.context.annotation.ClassPathBeanDefinitionScanner.isCandidateComponent
方法
/**
* Determine whether the given class does not match any exclude filter
* and does match at least one include filter.
* @param metadataReader the ASM ClassReader for the class
* @return whether the class qualifies as a candidate component
*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
14行是通过TypeFilter.match
判断是否匹配的Filter的关键,调试代码进入match
方法,跟踪代码,定位到AnnotationTypeFilter.matchSelf
方法中,方法如下:
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
本例中,使用了注解@Service
,当metadataReader
对应的类为使用了@Service
注解的类时,可进入metadata.hasMetaAnnotation(this.annotationType.getName())
查看,此时返回true
,具体代码比较简单,可自行查看。这样就实现了使用 @Service
时,同样具有 @Component
的作用,实现了 @Component
的派生性。
扩展
针对@Component
的派生性,可以实现自定义 @Component
派生注解,定义以下注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyComponent {
String value() default "";
}
定义类,使用自定义注解
@MyComponent(value = "myFirstLevelRepository") // Bean 名称
public class MyFirstComponent {
public void test() {
System.out.println("hello world");
}
}
初始化AnnotationConfigApplicationContext
获取MyFirstComponet
public class AnnotationBootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.scan("com.ethan.diveinspringboot.bean");
applicationContext.refresh();
MyFirstComponet com = applicationContext.getBean(MyFirstComponet.class);
com.test();
}
}
发现可以通过ApplicationContext
获取到MyFirstComponent
对象。至此实现了自定义@Component
派生性。
总结
@Component
注解具有派生性,需要配合@ComponentScan
使用,或者编程方式调用AnnotationConfigApplicationContext.scan
方法。@Component
注解的类,通过ClassPathBeanDefinitionScanner.includeFilter
中寻找到FilterType
确定可以加载到Spring 容器中。@Component
派生性是通过Spring实现的