文章目录
1 入门演示
新建一个模块:03-spring-annnotion-componentScan,引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>study.wyy</groupId>
<artifactId>00-spring-annnotion-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
新建一个组件UserService : 注意包的路径
package study.wyy.spring.anno.componentscan.service;
import org.springframework.stereotype.Component;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:27 下午
*/
@Component
public class UserService {
public void sayHello(){
System.out.println("Hello World");
}
}
新建一个Config类
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.Configuration;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration
public class SpringConfig {
}
测试:调用sayHello方法
package study.wyy.spring.anno.componentscan;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import study.wyy.spring.anno.componentscan.config.SpringConfig;
import study.wyy.spring.anno.componentscan.service.UserService;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:34 下午
*/
public class Test {
@org.junit.Test
public void test1(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean(UserService.class);
userService.sayHello();
}
}
抛出一下异常信息: 没有可用的bean
.NoSuchBeanDefinitionException: No qualifying bean of type 'study.wyy.spring.anno.componentscan.service.UserService' available
如何将我们的UserService注入到spring容器?
使用@ComponentScan
注解将UserService的路径扫描进去
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration
@ComponentScan("study.wyy.spring.anno.componentscan.service")
public class SpringConfig {
}
2 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 {};
}
}
作用
用于指定创建容器时要扫描的包,该注解在指定扫描的位置时,可以指定包名,也可以指定扫描的类。同时还支持定义扫描规则。例如:包含哪些或者排除哪些,也可以自己定义bean的命名规则(默认是首字母小写)
属性介绍
- value:用于要指定扫描的包。指定了包名之后,spring会扫描指定的包和其下的子包,默认是扫描使用该注解类所在的包和其下子包。
- basePackages: 和value互为别名,作用一样
- basePackageClasses: 指定具体要扫描的类的字节码。默认扫描这个类所在的包和其下子包
- nameGenerator: 指定扫描到的bean对象存入spring容器时的命名规则。默认是类名首字母小写。
- scopeResolver: 用于处理并转换检测到的Bean的作用范围
- scopedProxy: 用于指定bean生成时的代理方式。默认时不使用代理。
- INTERFACES:jdk自带的代理方式
- TARGET_CLASS: 使用CGLIB
- resourcePattern: 用于指定符合组件检测条件的类文件,默认是包扫描下的**/*.class
**
表示包及其子包*.class
: 表示class文件,也就是之前默认说扫描包及其子包下
- useDefaultFilters: 是否对带有
@Repository
,@Service
,@Controller
,@Component
注解的类开启检测,默认时开启的 - includeFilters:自定义组件扫描的过滤规则
FilterType有5种类型:- ANNOTATION:注解类型(默认)
- ASSIGNABLE_TYPE: 指定固定类
- ASPECTJ: ASPECTJ类型
- REGEX:正则表达式
- CUSTOM:自定义类型
3 基本演示
3.1 basePackageClasses
指定具体要扫描的类的字节码。
增加一个 AddressService和UserService同一个包
package study.wyy.spring.anno.componentscan.service;
import org.springframework.stereotype.Service;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 10:22 下午
*/
@Service
public class AddressService {
public void address(){
System.out.println("address");
}
}
study.wyy.spring.anno.componentscan.service 增加一个子包,提供一个UserDao
package study.wyy.spring.anno.componentscan.service.dao;
import org.springframework.stereotype.Repository;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 10:26 下午
*/
@Repository
public class UserDao {
public void saveUser(){
System.out.println("save user");
}
}
提供一个配置类
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import study.wyy.spring.anno.componentscan.service.UserService;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration
@ComponentScan(basePackageClasses = {UserService.class})
public class SpringConfig1 {
}
测试
@org.junit.Test
public void test2(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig1.class);
UserService userService = context.getBean(UserService.class);
userService.sayHello();
AddressService addressService = context.getBean(AddressService.class);
addressService.address();
UserDao userDao = context.getBean(UserDao.class);
userDao.saveUser();
}
Hello World
address
save user
三个组件都被扫描到了,这就是默认扫描指定的包和及其子包
3.2 resourcePattern
- resourcePattern: 用于指定符合组件检测条件的类文件,默认是包扫描下的**/*.class
**
表示包及其子包*.class
: 表示class文件,也就是之前默认说扫描包及其子包下
改为不扫描其子包,只扫描指定包下的class,期望就是UserDao不会被扫描到
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import study.wyy.spring.anno.componentscan.service.UserService;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration
@ComponentScan(
basePackages = {"study.wyy.spring.anno.componentscan.service"},
resourcePattern = "*.class"
)
public class SpringConfig2 {
}
测试
@org.junit.Test
public void test3(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig2.class);
UserService userService = context.getBean(UserService.class);
userService.sayHello();
AddressService addressService = context.getBean(AddressService.class);
addressService.address();
UserDao userDao = context.getBean(UserDao.class);
userDao.saveUser();
}
结果
Hello World
address
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'study.wyy.spring.anno.componentscan.service.dao.UserDao' available
UserDao没有被扫描到了,和我们的期望结果是一样的
3.3 bean的name生成规则
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
这个属性就是配置bean的name生成规则
public interface BeanNameGenerator {
/**
* Generate a bean name for the given bean definition.
* @param definition the bean definition to generate a name for
* @param registry the bean definition registry that the given definition
* is supposed to be registered with
* @return the generated bean name
*/
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}
实现类:
- DefaultBeanNameGenerator
- AnnotationBeanNameGenerator:
下面就简单介绍AnnotationBeanNameGenerator实现的生成规则
3.3.1 beanName生成规则源码解读
跟一下源代码
定义一个配置类
- SpringConfig3 的Configuration注解value属性指定为:
helloSpringConfig3
- 此时SpringConfig3这个类在容器中的bean的name就是
helloSpringConfig3
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration(value = "helloSpringConfig3")
@ComponentScan(
basePackages = {"study.wyy.spring.anno.componentscan.service"},
resourcePattern = "*.class"
)
public class SpringConfig3 {
}
测试
@org.junit.Test
public void test1(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean(UserService.class);
userService.sayHello();
}
AnnotationBeanNameGenerator的generateBeanName打个断点
跟进determineBeanNameFromAnnotation
到这我们通过Value属性指定beanName的方式源码跟踪就差不多了
UserService这种没有指定value属性的,则默认的beanName是怎么生成?
3.3.2 自定义beanName生成
既然了解了生成的源码,那就在自己自定义一个
package study.wyy.spring.anno.componentscan.spi;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.beans.Introspector;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/15 2:10 下午
* 自定义beanName生成
* 这里不直接重写BeanNameGenerator接口,直接继承AnnotationBeanNameGenerator
* 可以直接复用一些
*/
public class ConsumerBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
// 为了明显这里重写generateBeanName,直接调用父类的
// 当时我们要重写buildDefaultBeanName这个方法
return super.generateBeanName(definition,registry);
}
@Override
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils.getShortName(beanClassName);
String decapitalize = Introspector.decapitalize(shortClassName);
// 到这还是之前的逻辑,我们就在返回结果上拼接一个consumer_
return "consumer_" +decapitalize;
}
}
测试
- 配置类中指定自定义的生成器
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import study.wyy.spring.anno.componentscan.spi.ConsumerBeanNameGenerator;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration(value = "helloSpringConfig3")
@ComponentScan(
basePackages = {"study.wyy.spring.anno.componentscan.service"},
resourcePattern = "*.class",
nameGenerator= ConsumerBeanNameGenerator.class
)
public class SpringConfig4 {
}
- 测试
@org.junit.Test
public void test5(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig4.class);
// 这里我们还是指定了value属性,依然以指定的name为准
SpringConfig4 helloSpringConfig4 = context.getBean("helloSpringConfig4",SpringConfig4.class);
UserService userService = context.getBean("consumer_userService", UserService.class);
userService.sayHello();
// 默认的首字母小写已经会找不到了NoSuchBeanDefinitionException
UserService userService2 = context.getBean("userService", UserService.class);
userService.sayHello();
}
Hello World
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'userService' available
4 过滤规则
4.1 includeFilters和excludeFilters
@ComponentScan
中的includeFilters
和excludeFilters
属性可以指定过滤规则
/**
* Indicates whether automatic detection of classes annotated with {@code @Component}
* {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
*/
boolean useDefaultFilters() default true;
默认是true,默认会扫描指定包下的@Repository
,@Service
,@Controller
,@Component
注解
includeFilters
和excludeFilters
属性的值又是一个注解@Filter
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
// 默认类型是注解类型
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
FilterType(枚举)
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的表达式,基本上不会用到
*/
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
}
4.2 基本演示
4.2.1 excludeFilters
如何排除掉Repository注解标注的类
- 指定FilterType为ANNOTATION
- 指定排除的注解的字节码
如下
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Repository;
import study.wyy.spring.anno.componentscan.spi.ConsumerBeanNameGenerator;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration
@ComponentScan(
basePackages = {"study.wyy.spring.anno.componentscan.service"},
// 指定FilterType为ANNOTATION
// 指定排除的注解(Repository)的字节码
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Repository.class})}
)
public class SpringConfig5 {
}
测试
@org.junit.Test
public void test6(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig5.class);
UserService userService = context.getBean(UserService.class);
userService.sayHello();
UserDao userDao = context.getBean(UserDao.class);
userDao.saveUser();
}
结果
Hello World
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'study.wyy.spring.anno.componentscan.service.dao.UserDao' a
容器中没有注入UserDao(Repository注解标注的类)
4.2.2 includeFilters
将刚刚的excludeFilters换成includeFilters
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Repository;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration
@ComponentScan(
basePackages = {"study.wyy.spring.anno.componentscan.service"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Repository.class})}
)
public class SpringConfig6 {
}
测试
@org.junit.Test
public void test7(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig6.class);
UserDao userDao = context.getBean(UserDao.class);
userDao.saveUser();
UserService userService = context.getBean(UserService.class);
userService.sayHello();
}
save user
Hello World
发现这个时候@Component
注解标注的UserService也被扫描进来,说明includeFilters
不会只扫描其包含的类型,默认的@Component
,@Service
注解依然会被扫描
可见我们可以自定义一个注解,使用includeFilters属性,让@ComponentScan
把标注我们自己类的注解也注入到ioc容器中
自定义一个注解
package study.wyy.spring.anno.componentscan.anno;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/17 10:08 下午
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Node {
}
提供一个组件,使用
@Node
标注,在@ComponentScan
扫描的包下
package study.wyy.spring.anno.componentscan.service;
import study.wyy.spring.anno.componentscan.anno.Node;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/17 10:08 下午
*/
@Node
public class MyNode {
public void node(){
System.out.println("MyNode...");
}
}
配置
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Repository;
import study.wyy.spring.anno.componentscan.anno.Node;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration
@ComponentScan(
basePackages = {"study.wyy.spring.anno.componentscan.service"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Node.class})}
)
public class SpringConfig7 {
}
测试
@org.junit.Test
public void test8(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig7.class);
UserDao userDao = context.getBean(UserDao.class);
userDao.saveUser();
UserService userService = context.getBean(UserService.class);
userService.sayHello();
MyNode node = context.getBean(MyNode.class);
node.node();
}
save user
Hello World
MyNode...
4.2.3 自定义过滤规则
需要实现TypeFilter
@FunctionalInterface
public interface TypeFilter {
/**
* 返回值为True表示匹配成功
* @param metadataReader: 获取当前扫描到的类的信息
* @param metadataReaderFactory: 可以获取到其他类的信息
* @return
* @throws IOException
*/
boolean match(MetadataReader var1, MetadataReaderFactory var2) throws IOException;
}
实现一个
package study.wyy.spring.anno.componentscan.spi;
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;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/17 10:19 下午
* 只扫描类名包含User的
*/
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 获取当前类的注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取当前类资源(类路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
// 类名包含User,匹配成功
if(className.contains("User")){
return true;
}
return false;
}
}
配置
package study.wyy.spring.anno.componentscan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import study.wyy.spring.anno.componentscan.anno.Node;
import study.wyy.spring.anno.componentscan.spi.MyTypeFilter;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/14 9:33 下午
*/
@Configuration
@ComponentScan(
basePackages = {"study.wyy.spring.anno.componentscan.service"},
excludeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,value = {MyTypeFilter.class})}
)
public class SpringConfig8 {
}
测试
UserDao,UserService会被匹配到,不会注入到容器中
@org.junit.Test
public void test9(){
// 1 加载配置类
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig8.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
System.out.println("ioc 容器中定义的bean: "+beanName);
}
AddressService addressService = context.getBean(AddressService.class);
addressService.address();
UserDao userDao = context.getBean(UserDao.class);
userDao.saveUser();
UserService userService = context.getBean(UserService.class);
userService.sayHello();
}
ioc 容器中定义的bean: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
ioc 容器中定义的bean: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
ioc 容器中定义的bean: org.springframework.context.annotation.internalCommonAnnotationProcessor
ioc 容器中定义的bean: org.springframework.context.event.internalEventListenerProcessor
ioc 容器中定义的bean: org.springframework.context.event.internalEventListenerFactory
ioc 容器中定义的bean: springConfig8
ioc 容器中定义的bean: addressService
address
.NoSuchBeanDefinitionException: No qualifying bean of type 'study.wyy.spring.anno.componentscan.service.dao.UserDao'