@ComponentScan包扫描
配置文件方式
- 在配置文件中配置包扫描
- 测试
- 通过包扫描自动注入@Configuration配置类组件
- 在@Configuration组件中创建bean对象
- 通过配置文件启动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);
}
}
实验效果
不添加包扫描的效果

添加包扫描的效果


注解方式
- 添加一个配置类, 在配置类中通过@ComponentScan注解配置包扫描
- 测试
- spring应用程序通过读取该配置类来启动
- 获取通过包扫描获得的其他组件
配置类(主启动类)
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);
}
}
实验结果


@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 {
}
禁用默认过滤规则前

禁用默认规则后

excludeFilters属性
指定需要排除的组件
默认包含所有组件

选择过滤类型
选择过滤方式
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 {
}


自定义过滤规则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 {
}

实验结果

拓展学习
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;
}
}
}