Spring定义Bean类(一) @Component 派生性

本文主要讨论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 注解如何被加载的,搜索源码,发现@ComponentClassPathScanningCandidateComponentProvider.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 派生性。

总结

  1. @Component 注解具有派生性,需要配合@ComponentScan 使用,或者编程方式调用 AnnotationConfigApplicationContext.scan 方法。
  2. @Component 注解的类,通过 ClassPathBeanDefinitionScanner.includeFilter 中寻找到 FilterType 确定可以加载到Spring 容器中。
  3. @Component 派生性是通过Spring实现的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值