03 componentScan注解

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;
    }
}

测试

  1. 配置类中指定自定义的生成器
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 {
}

  1. 测试
@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中的includeFiltersexcludeFilters属性可以指定过滤规则

/**
	 * 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注解

includeFiltersexcludeFilters属性的值又是一个注解@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'
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值