首页 > 其他分享 >【Spring面试全家桶】@ComponentScan你真的会用吗

【Spring面试全家桶】@ComponentScan你真的会用吗

时间:2023-06-23 11:02:21浏览次数:36  
标签:会用 Spring 扫描 ComponentScan Bean 注解 com

(文章目录)

1.@ComponentScan介绍

@ComponentScan 是 Spring 框架提供的一种自动化扫描和加载 Bean 的机制。它通过指定一个或多个包路径,自动扫描这些路径下的所有类,并将被注解标记的 Bean(如 @Component、@Service、@Repository、@Controller 等等)实例化并加入到 Spring 容器中。这样的话,我们就可以在其他地方直接使用这些 Bean,而不需要手动创建它们。

需要注意的是,@ComponentScan 并不是默认开启的,我们需要在配置文件中进行配置才能启用它。一般来说,我们会在 Spring 配置文件(如 applicationContext.xml)中配置 @ComponentScan 注解,指定需要扫描的包路径。例如:

<context:component-scan base-package="com.example"/>

这里,我们指定了要扫描的包路径为 com.example,并通过使用 context:component-scan 标签来开启扫描机制。当 Spring 启动时,它会自动扫描该路径下的所有组件,并将它们自动装载到 Spring 容器中。

需要注意的是,在使用 @ComponentScan 注解时,我们可以通过指定 includeFilters 和 excludeFilters 参数来过滤需要扫描的类。例如,如果我们只想扫描某些特定的类,可以通过如下方式进行配置:

@ComponentScan(basePackages = "com.example", includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {Bean1.class, Bean2.class}))

这里,我们只扫描了 Bean1 和 Bean2 两个类,其他类不会被扫描到。

总的来说,@ComponentScan 是 Spring 框架中非常重要的一部分,它简化了我们的开发流程,提高了应用程序的可维护性和可扩展性。同时,对于 Java 开发人员来说,深入理解 @ComponentScan 的原理和运行机制,有助于提高我们的 Java 技能水平。

下面是一个简单的 @ComponentScan 的使用示例:

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
   // 配置其他 Bean
}

这个示例中,@ComponentScan 注解会自动扫描 com.example 包下面的所有子包,查找所有被注解标记的 Bean,然后将它们装载到 Spring 容器中,供其他组件使用。

需要注意的是,@ComponentScan 要放在一个 @Configuration 标记的类中,这样 Spring 才会在配置类中查找和扫描 Bean。同时,@ComponentScan 也可以根据需要进行更精细的配置,比如指定扫描路径、排除指定的类、添加过滤器等等。

2.@ComponentScan底层实现

@ComponentScan是Spring框架中用于扫描指定包及其子包中的bean定义的注解。它的底层工作原理可以简单概括如下:

  1. 获取指定包及其子包的所有文件路径,创建Resource对象,将其保存到资源列表中。

  2. 遍历资源列表,通过过滤器筛选出符合条件的类,获取类的名称和注解信息,将其保存到BeanDefinition对象中。

  3. 根据BeanDefinition对象中的信息,使用BeanDefinitionReader将其转换为BeanDefinition对象。

  4. 将所有的BeanDefinition对象保存到BeanDefinitionRegistry中,完成bean的注册。

在源码级别,@ComponentScan注解的具体实现可以参考以下代码:

public class ClassPathBeanDefinitionScanner extends ResourceLoaderAwareBeanFactoryPostProcessor {

    // 注册中心
    private BeanDefinitionRegistry registry;

    // 过滤器
    private TypeFilter[] includeFilters;

    // 排除过滤器
    private TypeFilter[] excludeFilters;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 扫描指定的包
        scan("com.example.demo");
    }

    public void scan(String... basePackages) {
        // 文件路径列表
        Set<BeanDefinitionHolder> beanDefinitions = doScan(basePackages);
        // 注册bean
        registerBeanDefinitions(beanDefinitions, registry);
    }

    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 逐个扫描指定的包,获取文件路径列表
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + "/" + DEFAULT_RESOURCE_PATTERN;
            Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            // 对文件列表进行过滤,筛选出符合条件的bean
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    if (matches(metadataReader)) {
                        // 将符合条件的bean保存到BeanDefinitionHolder中
                        BeanDefinitionHolder holder = new BeanDefinitionHolder(
                                getBeanDefinitionReader().createBeanDefinition(metadataReader),
                                metadataReader.getClassMetadata().getClassName());
                        beanDefinitions.add(holder);
                    }
                }
            }
        }
        return beanDefinitions;
    }

    private boolean matches(MetadataReader metadataReader) throws IOException {
        // 对bean进行过滤
        for (TypeFilter filter : this.excludeFilters) {
            if (filter.match(metadataReader, this.getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter filter : this.includeFilters) {
            if (filter.match(metadataReader, this.getMetadataReaderFactory())) {
                return true;
            }
        }
        return false;
    }
}

以上是对@ComponentScan底层工作原理的一个简单解释,当然其中涉及到的一些类和方法还需要深入了解。

3.@ComponentScan高级用法

@ComponentScan注解是Spring框架中用于扫描指定包路径下的所有使用@Component注解的类,并将其注册为Bean的注解。除了指定包路径外,@ComponentScan还支持一些高级用法。

1. 指定排除和包含的类

使用excludeFilters和includeFilters属性可以指定要排除和包含的类,支持正则表达式。例如,

@ComponentScan(basePackages = "com.example",
    excludeFilters = {
        @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Exclude.*"),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ExcludeBean.class})
    },
    includeFilters = {
        @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Include.*"),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {IncludeBean.class})
    })

上述例子中,excludeFilters属性指定了要排除的类,包括类名包含"Exclude"的所有类和ExcludeBean类;includeFilters属性指定了要包含的类,包括类名包含"Include"的所有类和IncludeBean类。

2. 指定扫描的类

使用basePackageClasses属性可以指定扫描的类,Spring框架会自动扫描这些类所在的包。例如,

@ComponentScan(basePackageClasses = {Service1.class, Service2.class})

上述例子中,指定了Service1和Service2两个类,Spring框架会自动扫描它们所在的包。

3. 指定扫描的特定注解

使用annotation属性可以指定扫描特定注解的类。例如,

@ComponentScan(basePackages = "com.example", annotation = MyAnnotation.class)

上述例子中,指定了扫描所有使用@MyAnnotation注解的类。

总之,@ComponentScan注解支持非常灵活的配置方式,可以根据应用的实际需求来灵活配置。

@ComponentScan之扫描索引

@ComponentScan注解用于定义Spring应用程序的组件扫描范围,以查找并加载所有的@Component、@Service、@Repository和@Controller注解的类。

在@ComponentScan注解中,可以使用多个value属性来指定要扫描的基础包。如果没有指定value属性,则默认扫描当前类所在的包及其子包。例如:

@Configuration
@ComponentScan(basePackages = { "com.example.demo.service", "com.example.demo.controller" })
public class AppConfig {
    // 配置类的其他内容
}

在上面的例子中,我们指定了两个要扫描的基础包:com.example.demo.service和com.example.demo.controller。这意味着Spring会扫描这两个包及其子包中所有带有@Component、@Service、@Repository和@Controller注解的类,并将它们加载到应用程序上下文中。

此外,@ComponentScan还有其他可选属性,可以用来指定要扫描的类路径匹配模式、要排除的类和包、要包含的类和包等。例如:

@Configuration
@ComponentScan(basePackages = "com.example.demo",
               excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX,
                                                      pattern = "com.example.demo.exclude.*"))
public class AppConfig {
    // 配置类的其他内容
}

在上面的例子中,我们指定了一个要排除的类路径匹配模式,即以com.example.demo.exclude为前缀的所有类都将被排除在外。这意味着Spring不会扫描这些类,也不会将它们加载到应用程序上下文中。

4.实战中@ComponentScan的问题与解决方案

问题:

@ComponentScan是Spring框架用来扫描指定包及其子包下被@Component、@Service、@Repository、@Controller等注解修饰的类,并将其注册为Spring容器中的Bean。但在实战中,可能会出现以下问题:

  1. 扫描包路径不正确:如果包路径设置不正确,Spring无法扫描到所需的类。

  2. 扫描到非目标包下的类:如果扫描到了不应该被注册为Bean的类,会造成不必要的内存浪费。

  3. 扫描结果重复:如果不同的包中存在相同的类名,同样的类可能会被扫描到多次,也会造成不必要的内存浪费。

解决方案:

  1. 确认包路径:在使用@ComponentScan时,需要确保扫描到正确的包路径,或者使用通配符设置多个包路径。

  2. 排除不需要注册的类:可以通过excludeFilters设置排除不需要注册的类,例如排除所有被@Deprecated注解修饰的类。

  3. 改变Bean名称:如果扫描到了相同名称的类,可以通过设置@Bean注解来改变Bean的名称,例如:@Bean(name="myBean")。这样可以避免重复加载,同时可以更明确地标识Bean的作用。

总之,使用@ComponentScan时,需要注意包路径,避免扫描到不必要的类,并通过设置合适的Bean名称来避免重复加载。

Java代码示例:

  1. 确认包路径

使用@ComponentScan注解时,可以设置其value属性来指定需要扫描的包路径,例如:

@ComponentScan(value = {"com.example.package1", "com.example.package2"})

其中,value属性可以接受多个包路径,使用数组形式表示。也可以使用通配符来指定多个包路径,例如:

@ComponentScan(value = {"com.example.package.*"})

这样就会扫描所有以com.example.package开头的包路径。

  1. 排除不需要注册的类

使用@ComponentScan时,可以通过excludeFilters属性来排除不需要注册的类,例如:

@ComponentScan(value = {"com.example.package"}, excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = Deprecated.class)})

上述代码会排除所有被@Deprecated注解修饰的类。

  1. 改变Bean名称

如果扫描到了相同名称的类,可以通过设置@Bean注解来改变Bean的名称,例如:

@Bean(name="myBean")
public MyClass myClass(){
    return new MyClass();
}

这样就可以避免重复加载,并更明确地标识Bean的作用。

标签:会用,Spring,扫描,ComponentScan,Bean,注解,com
From: https://blog.51cto.com/liaozhiweiblog/6537527

相关文章

  • Springboot更改banner
    首先创建一个banner.txt。 将图像放到txt然后你启动就会发现: ......
  • 1. Spring相关概念
    1.初始Spring‍1.1Spring家族‍官网:​https://spring.io,从官网我们可以大概了解到:Spring能做什么:用以开发web、微服务以及分布式系统等,光这三块就已经占了JavaEE开发的九成多。Spring并不是单一的一个技术,而是一个大家族,可以从官网的​Projects​中查看其包......
  • SpringCloud Alibaba入门1-创建多模块工程
    一、创建父项目创建一个Maven的父项目,命名为mymall用于管理子项目。项目创建完成后,删除src目录和在pom.xml文件里面设置packing的方式为pom,管理其他子模块的依赖。删除之后的项目结构为:二、创建子module在父项目上右键,新建module,命令为mymall-common,然后创建子模块创建子模块成......
  • SpringBoot内容协商机制
    1、是什么?SpringBoot内容协商机制是一种实现了内容协商(ContentNegotiation)的Web服务器,它可以根据客户端请求的不同,将响应返回给客户端。在传统的Web服务器中,如果客户端请求的URL与服务器上的URL不一致,服务器就会返回一个错误响应,告诉客户端所请求的URL不存在或者不合法。而Spri......
  • spring boot 实现热部署
    一、热部署/热加载热部署(HotDeploy):热部署针对的是容器或者是整个应用,部署了新的资源或者修改了一些代码,需要在不停机的情况下的重新加载整个应用。热加载(HotSwap):热加载针对的是单个字节码文件,指的是重新编译后,不需要停机,应用程序就可以加载使用新的class文件。二、springb......
  • Springboot敏感字段脱敏的实现思路
    生产环境用户的隐私数据,比如手机号、身份证或者一些账号配置等信息,入库时都要进行不落地脱敏,也就是在进入我们系统时就要实时的脱敏处理。用户数据进入系统,脱敏处理后持久化到数据库,用户查询数据时还要进行反向解密。这种场景一般需要全局处理,那么用AOP切面来实现在适合不过了。......
  • SpringCloud依赖问题:spring-cloud-starter-eureka-server 和 spring-cloud-starter-ne
    学习SpringCloud微服务时,很多资料上都写的是spring-cloud-starter-eureka-server,结果问题无法正常启动,这是因为与当前的SpringBoot版本不匹配。其实较新的版本应该使用spring-cloud-starter-netflix-eureka-server依赖。PS:SpringCloud的版本不兼容好坑。......
  • org.springframework.boot.builder.SpringApplicationBuilder.init([LjavalangObject;
    一SpringBoot2.0.4集成SpringCloud异常:org.springframework.boot.builder.SpringApplicationBuilder.([Ljava/lang/Object;)V二、异常处理参考:缘起初学springcloud的朋友可能不知道,其实SpringBoot与SpringCloud需要版本对应,否则可能会造成很多意料之外的错误,比如eureka注册了......
  • Spring boot 手动开启事务 手动提交 手动回滚
    直接上代码@ServicepublicclassXXXService{//这两个必须要注入@ResourceDataSourceTransactionManagerdataSourceTransactionManager;@ResourceTransactionDefinitiontransactionDefinition;publicvoidmethod1(){//开启事务......
  • spring aop
    切面表达式execution表达式基本语法格式为:execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)除了返回类型模式,方法名模式和参数模式外,其它项都是可选的。例如://com下所有方法@Pointcut("execution(*com..*.*(..))") @Pointcut("execution(public*cn.......