02-ComponentScan注解

@ComponentScan包扫描

配置文件方式

  1. 在配置文件中配置包扫描
  2. 测试
    1. 通过包扫描自动注入@Configuration配置类组件
    2. 在@Configuration组件中创建bean对象
    3. 通过配置文件启动spring程序, 从容器中获取@Configuration组件中配置的bean对象

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置包扫描-->
    <context:component-scan base-package="com.xiong"/>

</beans>

配置类

package com.xiong.config;

import com.xiong.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {
    @Bean("human")
    Person person() {
        Person person = new Person();
        person.setName("person");
        person.setAge(20);
        return person;
    }
}

测试类

package com.xiong;


import com.xiong.bean.Person;
import com.xiong.config.MainConfig;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {

    @Test
    public void getBeanByXmlFileTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Person person = context.getBean("person", Person.class);
        System.out.println(person);
    }
}

实验效果

不添加包扫描的效果

image-20221118030030259

添加包扫描的效果

image-20221118030146090

image-20221118030400871

注解方式

  1. 添加一个配置类, 在配置类中通过@ComponentScan注解配置包扫描
  2. 测试
    1. spring应用程序通过读取该配置类来启动
    2. 获取通过包扫描获得的其他组件

配置类(主启动类)

package com.xiong;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = "com.xiong")
public class SpringApplicationMain {
}

包扫描获得的其他组件

package com.xiong.config;

import com.xiong.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {
    @Bean
    Person person() {
        Person person = new Person();
        person.setName("person");
        person.setAge(20);
        return person;
    }
}

测试类

public class MainTest {
    @Test
    public void getBeanByConfigClassTest() {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringApplicationMain.class);
        Person person = context.getBean("person", Person.class);
        System.out.println(person);
    }
}

实验结果

image-20221118031847267

image-20221118032054933

@ComponentScan注解解析

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

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

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

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

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

    boolean useDefaultFilters() default true;

    Filter[] includeFilters() default {};

    Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    @interface Filter {

        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}

value属性

包扫描的位置

includeFilters属性

指定只包含哪些组件

只包含哪些注解需要禁用默认的包扫描规则才能够生效

package com.xiong;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

@Configuration
@ComponentScan(value = "com.xiong",
        includeFilters = {
                @ComponentScan.Filter(
                        //指定注解方式过滤
                        type = FilterType.ANNOTATION,
                        //指定需要扫描的具体类型
                        classes = {Controller.class, Service.class}
                )
        },
        useDefaultFilters = false)
public class SpringApplicationMain {
}

禁用默认过滤规则前

image-20221118034945469

禁用默认规则后

image-20221118035243012

excludeFilters属性

指定需要排除的组件

默认包含所有组件

image-20221118034033777

选择过滤类型

选择过滤方式

  • ANNOTATION: 按照注解
  • ASPECTJ: 使用aspectj表达式
  • ASSIGNABLE_TYPE: 按照给定的class的类型
  • CUSTOM: 自定义规则
  • REGEX: 使用正则表达式
package com.xiong;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

@Configuration
@ComponentScan(value = "com.xiong",
        excludeFilters = {
                @ComponentScan.Filter(
                        //指定注解方式过滤
                        type = FilterType.ANNOTATION,
                        //指定过滤的具体类型
                        classes = {Controller.class, Service.class}
                )
        })
public class SpringApplicationMain {
}

image-20221118034141007

image-20221118034441579

自定义过滤规则TypeFilter

使用FilterType.CUSTOM时自定义过滤器

public enum FilterType {

    /**
     * Filter candidates marked with a given annotation.
     * @see org.springframework.core.type.filter.AnnotationTypeFilter
     */
    ANNOTATION,

    /**
     * Filter candidates assignable to a given type.
     * @see org.springframework.core.type.filter.AssignableTypeFilter
     */
    ASSIGNABLE_TYPE,

    /**
     * Filter candidates matching a given AspectJ type pattern expression.
     * @see org.springframework.core.type.filter.AspectJTypeFilter
     */
    ASPECTJ,

    /**
     * Filter candidates matching a given regex pattern.
     * @see org.springframework.core.type.filter.RegexPatternTypeFilter
     */
    REGEX,

    /** 
     * Filter candidates using a given custom
     * {@link org.springframework.core.type.filter.TypeFilter} implementation.
     */
    CUSTOM

}

自定义TypeFilter的实现类

package com.xiong.filter;

import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.util.Locale;

public class XTypeFilter implements TypeFilter {
    /**
     * @param metadataReader        读取到的当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取到其他任何类的信息
     * @return 如果成功匹配, 返回true; 否则返回false
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取正在扫描的类的类名
        String className = classMetadata.getClassName().toLowerCase(Locale.ROOT);
        System.out.println(className);
        //获取当前类资源(类路径classpath)
        Resource resource = metadataReader.getResource();

        // 如果扫描的类的类名中包含service(不考虑大小写的情况下), 返回true; 否则返回false
        return className.contains("service");
    }
}

使用FilterType.CUSTOM方式的包扫描

package com.xiong;

import com.xiong.filter.XTypeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(value = "com.xiong",
        excludeFilters = {
                @ComponentScan.Filter(
                        //指定注解方式过滤
                        type = FilterType.CUSTOM,
                        //使用自定义的包扫描规则
                        classes = {XTypeFilter.class}
                )
        })
public class SpringApplicationMain {
}

image-20221118043103395

实验结果

image-20221118043152098

拓展学习

MetadataReader类 和 MetadataReaderFactory类

MetadataReader 用于记录当前正在处理的类的信息,而 MetadataReaderFactory 可以调用 getMetadataReader() 方法传入类名来获得指定类的 MetadataReader。

MetadataReaderFactory 的作用就是为了获取 MetadataReader,同时框架已经提供了现成的 MetadataReader,因此是否需要使用 MetadataReaderFactory 取决于当前类的匹配条件是否需要使用其它类的信息。

MetadataReader 接口中有三个方法,分别是:

  • getClassMetadata():获取类本身的元数据信息
  • getAnnotationMetadata():获取类级别注解的元数据信息
  • getResource():获取文件相关的信息(文件对象File、文件路径等)
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.junit.Test;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * 自定义类型过滤器
 * <p>
 * 用途:
 * <li>{@link org.springframework.context.annotation.ComponentScan}
 */
public class MyTypeFilterUsedByComponentScanAnnotation implements TypeFilter {
    /**
     * @param metadataReader        读取到的当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取到其他任何类的信息
     * @return 如果成功匹配, 返回true; 否则返回false
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // SimpleMetadataReader 和 CachingMetadataReaderFactory

        //1. 获取当前类注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

        //2. 获取当前正在扫描的类的类信息 SimpleAnnotationMetadata
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //2.1 获取正在扫描的类的类名
        String className = classMetadata.getClassName();
        System.out.println("className = " + className);
        //2.2
        String enclosingClassName = classMetadata.getEnclosingClassName();
        System.out.println("enclosingClassName = " + enclosingClassName);
        //2.3
        String[] memberClassNames = classMetadata.getMemberClassNames();
        for (String memberClassName : memberClassNames) {
            System.out.println(memberClassName);
        }

        //3. 获取当前类资源(类路径classpath) FileSystemResource
        Resource resource = metadataReader.getResource();

        // 如果扫描的类的类名中包含Service, 返回true; 否则返回false
        // TODO: 根据前面获取的信息, 定义匹配逻辑, 这里简单定义为类名中是否包含“service”字符串
        return className.contains("service");
    }


    /**
     * ClassMetadata: 涉及与类相关的元数据信息
     *
     * @throws IOException
     */
    @Test
    public void getClassMetadataTest() throws IOException {
        SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
        //通过 MetadataReaderFactory 可以获得其他类的信息
        MetadataReader studentMetadataReader = metadataReaderFactory.getMetadataReader(Student.class.getName());
        ClassMetadata classMetadata = studentMetadataReader.getClassMetadata();

        // className: 类名
        String className = classMetadata.getClassName();
        System.out.println("className = " + className);

        // enclosingClass(封闭类), 表示嵌套类(静态内部类)所在的最外层类
        boolean hasEnclosingClass = classMetadata.hasEnclosingClass();
        System.out.println("hasEnclosingClass = " + hasEnclosingClass);
        String enclosingClassName = classMetadata.getEnclosingClassName();
        System.out.println("enclosingClassName = " + enclosingClassName);

        // memberClassNames(内部类)
        String[] memberClassNames = classMetadata.getMemberClassNames();
        Arrays.asList(memberClassNames).forEach(System.out::println);

        // superClassName(父类)
        String superClassName = classMetadata.getSuperClassName();
        System.out.println("superClassName = " + superClassName);

        // interface(接口)
        String[] interfaceNames = classMetadata.getInterfaceNames();
        Arrays.asList(interfaceNames).forEach(System.out::println);
    }

    /**
     * AnnotationMetadata: 注解相关的元数据信息
     *
     * @throws IOException
     */
    @Test
    public void getAnnotationMetadataTest() throws IOException {
        SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
        //通过 MetadataReaderFactory 可以获得其他类的信息
        MetadataReader studentMetadataReader = metadataReaderFactory.getMetadataReader(Student.class.getName());

        AnnotationMetadata annotationMetadata = studentMetadataReader.getAnnotationMetadata();

        // 是否含有某个注解
        boolean b1 = annotationMetadata.hasAnnotation(Component.class.getName());
        System.out.println(b1);
        System.out.println("#######################################################");

        // 获取当前类上的RUNTIME类型的注解, 父类上的注解无法获取到
        Set<String> annotationTypes = annotationMetadata.getAnnotationTypes();
        annotationTypes.forEach(System.out::println);
        System.out.println("#######################################################");

        MergedAnnotations annotations = annotationMetadata.getAnnotations();
        for (MergedAnnotation<Annotation> annotation : annotations) {
            Class<Annotation> type = annotation.getType();
            String typeName = type.getName();
            System.out.println("typeName = " + typeName);
        }
        System.out.println("#######################################################");

        // 这种方式只能获取类级别的注解, 获取方法级别的注解需要借助RequestMappingHandlerMapping
        MergedAnnotations mergedAnnotations = MergedAnnotations.from(Student.class);
        for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
            List<Class<? extends Annotation>> metaTypes = mergedAnnotation.getMetaTypes();
            metaTypes.forEach(System.out::println);

            Class<Annotation> type = mergedAnnotation.getType();
            System.out.println("type = " + type);
        }

        //TODO: 能否确定类中有哪些方法注解吗? 这里是写死了MyAnnotation.class, 能否动态获取呢?
        Set<MethodMetadata> annotatedMethods = annotationMetadata.getAnnotatedMethods(MyAnnotation.class.getName());
        for (MethodMetadata methodMetadata : annotatedMethods) {
            String methodName = methodMetadata.getMethodName();
            System.out.println("methodName = " + methodName);

            String declaringClassName = methodMetadata.getDeclaringClassName();
            System.out.println("declaringClassName = " + declaringClassName);

            String returnTypeName = methodMetadata.getReturnTypeName();
            System.out.println("returnTypeName = " + returnTypeName);
        }
        System.out.println("#######################################################");
    }

    /**
     * Resource: 和字节码文件(.class)相关
     *
     * @throws IOException
     */
    @Test
    public void getResourceTest() throws IOException {
        SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
        //通过 MetadataReaderFactory 可以获得其他类的信息
        MetadataReader studentMetadataReader = metadataReaderFactory.getMetadataReader(Student.class.getName());

        Resource resource = studentMetadataReader.getResource();

        String filename = resource.getFilename();
        System.out.println("filename = " + filename);

        File file = resource.getFile();
        String fileAbsolutePath = file.getAbsolutePath();
        System.out.println("fileAbsolutePath = " + fileAbsolutePath);
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {

    }


    @Component
    @Getter
    @Setter
    private static class Person {
        private String name;
        private Integer age;

        public void hello() {
            System.out.println("name = " + name);
            System.out.println("age = " + age);
        }
    }


    @Service
    @Getter
    @Setter
    private static class Student extends Person implements Serializable, Comparable<Student> {
        private static final long serialVersionUID = 1L;
        private Integer level;

        @Override
        @MyAnnotation
        public void hello() {
            super.hello();
            System.out.println("level = " + level);
        }

        @Override
        public int compareTo(Student other) {
            return Integer.compare(this.level, other.level);
        }

        // 为了测试MemberClass
        @Data
        static class Grade {
            private String name;
        }
    }
}

   转载规则


《02-ComponentScan注解》 熊水斌 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
01-Bean注解 01-Bean注解
配置Bean对象xml配置文件方式流程 写配置文件 bean标签注入 测试获取bean对象 配置文件<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www
2023-05-13
下一篇 
03-Scope注解 03-Scope注解
@Scope设置组件作用域总结 singleton: ioc容器创建的时候调用bean方法创建bean对象 prototype: 每次bean对象被调用的时候创建 使用 @Lazy 可以使得bean对象在被调用的时候才加载 验证proto
2023-05-13
  目录