写在前面
在实际项目中, 我们更多的是使用 Spring 的包扫描功能对项目中的包进行扫描, 凡是在指定的包或子包中的类上标注了 @Repository,@Service,@Controller,@Component 注解的类都会被扫描到, 并将这个类注入到 Spring 容器中. Spring 包扫描功能可以使用 xml 文件进行配置, 也可以直接使用 @ComponentScan 注解进行设置, 使用 @ComponentScan 注解进行设置比使用 xml 文件配置要简单的多.
项目工程源码已经提交到 GitHub: https://github.com/sunshinelyz/spring-annotation
使用 xml 文件配置包扫描
我们可以在 Spring 的 xml 配置文件中配置包的扫描, 在配置包扫描时, 需要在 Spring 的 xml 文件中的 beans 节点中引入 context 标签, 如下所示.
- <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
- http://www.springframework.org/context/spring-context.xsd ">
接下来, 我们就可以在 xml 文件中定义要扫描的包了, 如下所示.
<context:component-scan base-package="io.mykit.spring"/>
整个 beans.xml 文件如下所示.
- <?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
- http://www.springframework.org/schema/context.xsd">
- <context:component-scan base-package="io.mykit.spring"/>
- <bean id = "person" class="io.mykit.spring.bean.Person">
- <property name="name" value="binghe"></property>
- <property name="age" value="18"></property>
- </bean>
- </beans>
此时, 只要在 io.mykit.spring 包下, 或者 io.mykit.spring 的子包下标注了 @Repository,@Service,@Controller,@Component 注解的类都会被扫描到, 并自动注入到 Spring 容器中.
此时, 我们分别创建 PersonDao,PersonService, 和 PersonController 类, 并在这三个类中分别添加 @Repository,@Service,@Controller 注解, 如下所示.
- PersonDao
- package io.mykit.spring.plugins.register.dao;
- import org.springframework.stereotype.Repository;
- /**
- * @author binghe
- * @version 1.0.0
- * @description 测试的 dao
- */
- @Repository
- public class PersonDao {
- }
- PersonService
- package io.mykit.spring.plugins.register.service;
- import org.springframework.stereotype.Service;
- /**
- * @author binghe
- * @version 1.0.0
- * @description 测试的 Service
- */
- @Service
- public class PersonService {
- }
- PersonController
- package io.mykit.spring.plugins.register.controller;
- import org.springframework.stereotype.Controller;
- /**
- * @author binghe
- * @version 1.0.0
- * @description 测试的 controller
- */
- @Controller
- public class PersonController {
- }
接下来, 我们在 SpringBeanTest 类中新建一个测试方法 testComponentScanByXml() 进行测试, 如下所示.
- @Test
- public void testComponentScanByXml(){
- ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
- String[] names = context.getBeanDefinitionNames();
- Arrays.stream(names).forEach(System.out::println);
- }
运行测试用例, 输出的结果信息如下所示.
- personConfig
- personController
- personDao
- personService
- org.springframework.context.annotation.internalConfigurationAnnotationProcessor
- org.springframework.context.annotation.internalAutowiredAnnotationProcessor
- org.springframework.context.annotation.internalCommonAnnotationProcessor
- org.springframework.context.event.internalEventListenerProcessor
- org.springframework.context.event.internalEventListenerFactory
- person
可以看到, 除了输出我们自己创建的 bean 名称之外, 也输出了 Spring 内部使用的一些重要的 bean 名称.
接下来, 我们使用注解来完成这些功能.
使用注解配置包扫描
使用 @ComponentScan 注解之前我们先将 beans.xml 文件中的下述配置注释.
<context:component-scan base-package="io.mykit.spring"></context:component-scan>
注释后如下所示.
<!--<context:component-scan base-package="io.mykit.spring"></context:component-scan>-->
使用 @ComponentScan 注解配置包扫描就非常 Easy 了! 在我们的 PersonConfig 类上添加 @ComponentScan 注解, 并将扫描的包指定为 io.mykit.spring 即可, 整个的 PersonConfig 类如下所示.
- package io.mykit.spring.plugins.register.config;
- import io.mykit.spring.bean.Person;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
- /**
- * @author binghe
- * @version 1.0.0
- * @description 以注解的形式来配置 Person
- */
- @Configuration
- @ComponentScan(value = "io.mykit.spring")
- public class PersonConfig {
- @Bean("person")
- public Person person01(){
- return new Person("binghe001", 18);
- }
- }
没错, 就是这么简单, 只需要在类上添加 @ComponentScan(value = "io.mykit.spring") 注解即可.
接下来, 我们在 SpringBeanTest 类中新增 testComponentScanByAnnotation() 方法, 如下所示.
- @Test
- public void testComponentScanByAnnotation(){
- ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
- String[] names = context.getBeanDefinitionNames();
- Arrays.stream(names).forEach(System.out::println);
- }
运行 testComponentScanByAnnotation() 方法输出的结果信息如下所示.
- org.springframework.context.annotation.internalConfigurationAnnotationProcessor
- org.springframework.context.annotation.internalAutowiredAnnotationProcessor
- org.springframework.context.annotation.internalCommonAnnotationProcessor
- org.springframework.context.event.internalEventListenerProcessor
- org.springframework.context.event.internalEventListenerFactory
- personConfig
- personController
- personDao
- personService
- person
可以看到使用 @ComponentScan 注解同样输出了 bean 的名称.
既然使用 xml 文件和注解的方式都能够将相应的类注入到 Spring 容器当中, 那我们是使用 xml 文件还是使用注解呢? 我更倾向于使用注解, 如果你确实喜欢使用 xml 文件进行配置, 也可以, 哈哈, 个人喜好嘛! 好了, 我们继续.
关于 @ComponentScan 注解
我们点开 ComponentScan 注解类, 如下所示.
- package org.springframework.context.annotation;
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Repeatable;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- import org.springframework.beans.factory.support.BeanNameGenerator;
- import org.springframework.core.annotation.AliasFor;
- import org.springframework.core.type.filter.TypeFilter;
- @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 {};
- }
- }
这里, 我们着重来看 ComponentScan 类的两个方法, 如下所示.
- Filter[] includeFilters() default {
- };
- Filter[] excludeFilters() default {
- };
includeFilters() 方法表示 Spring 扫描的时候, 只包含哪些注解, 而 excludeFilters() 方法表示不包含哪些注解. 两个方法的返回值都是 Filter[] 数组, 在 ComponentScan 注解类的内部存在 Filter 注解类, 大家可以看下上面的代码.
1. 扫描时排除注解标注的类
例如, 我们现在排除 @Controller,@Service 和 @Repository 注解, 我们可以在 PersonConfig 类上通过 @ComponentScan 注解的 excludeFilters() 实现. 例如, 我们在 PersonConfig 类上添加了如下的注解.
- @ComponentScan(value = "io.mykit.spring", excludeFilters = {
- @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class, Repository.class})
- })
这样, 我们就使得 Spring 在扫描包的时候排除了使用 @Controller,@Service 和 @Repository 注解标注的类. 运行 SpringBeanTest 类中的 testComponentScanByAnnotation() 方法, 输出的结果信息如下所示.
- org.springframework.context.annotation.internalConfigurationAnnotationProcessor
- org.springframework.context.annotation.internalAutowiredAnnotationProcessor
- org.springframework.context.annotation.internalCommonAnnotationProcessor
- org.springframework.context.event.internalEventListenerProcessor
- org.springframework.context.event.internalEventListenerFactory
- personConfig
- person
可以看到, 输出的结果信息中不再输出 personController,personService 和 personDao 说明 Spring 在进行包扫描时, 忽略了 @Controller,@Service 和 @Repository 注解标注的类.
2. 扫描时只包含注解标注的类
我们也可以使用 ComponentScan 注解类的 includeFilters() 来指定 Spring 在进行包扫描时, 只包含哪些注解标注的类.
这里需要注意的是, 当我们使用 includeFilters() 来指定只包含哪些注解标注的类时, 需要禁用默认的过滤规则.
例如, 我们需要 Spring 在扫描时, 只包含 @Controller 注解标注的类, 可以在 PersonConfig 类上添加 @ComponentScan 注解, 设置只包含 @Controller 注解标注的类, 并禁用默认的过滤规则, 如下所示.
- @ComponentScan(value = "io.mykit.spring", includeFilters = {
- @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
- }, useDefaultFilters = false)
此时, 我们再次运行 SpringBeanTest 类的 testComponentScanByAnnotation() 方法, 输出的结果信息如下所示.
- org.springframework.context.annotation.internalConfigurationAnnotationProcessor
- org.springframework.context.annotation.internalAutowiredAnnotationProcessor
- org.springframework.context.annotation.internalCommonAnnotationProcessor
- org.springframework.context.event.internalEventListenerProcessor
- org.springframework.context.event.internalEventListenerFactory
- personConfig
- personController
- person
可以看到, 在输出的结果中, 只包含了 @Controller 注解标注的组件名称, 并没有输出 @Service 和 @Repository 注解标注的组件名称.
注意: 在使用 includeFilters() 来指定只包含哪些注解标注的类时, 结果信息中会一同输出 Spring 内部的组件名称.
3. 重复注解
不知道小伙伴们有没有注意到 ComponentScan 注解类上有一个如下所示的注解.
@Repeatable(ComponentScans.class)
我们先来看看 @ComponentScans 注解是个啥, 如下所示.
- package org.springframework.context.annotation;
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- @Documented
- public @interface ComponentScans {
- ComponentScan[] value();
- }
可以看到, 在 ComponentScans 注解类中只声明了一个返回 ComponentScan[] 数组的 value(), 说到这里, 大家是不是就明白了, 没错, 这在 Java8 中是一个重复注解.
对于 Java8 不熟悉的小伙伴, 可以到 [Java8 新特性] 专栏查看关于 Java8 新特性的文章. 专栏地址小伙伴们可以猛戳下面的链接地址进行查看:
在 Java8 中表示 @ComponentScan 注解是一个重复注解, 可以在一个类上重复使用这个注解, 如下所示.
- @Configuration
- @ComponentScan(value = "io.mykit.spring", includeFilters = {
- @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
- }, useDefaultFilters = false)
- @ComponentScan(value = "io.mykit.spring", includeFilters = {
- @Filter(type = FilterType.ANNOTATION, classes = {Service.class})
- }, useDefaultFilters = false)
- public class PersonConfig {
- @Bean("person")
- public Person person01(){
- return new Person("binghe001", 18);
- }
- }
运行 SpringBeanTest 类的 testComponentScanByAnnotation() 方法, 输出的结果信息如下所示.
- org.springframework.context.annotation.internalConfigurationAnnotationProcessor
- org.springframework.context.annotation.internalAutowiredAnnotationProcessor
- org.springframework.context.annotation.internalCommonAnnotationProcessor
- org.springframework.context.event.internalEventListenerProcessor
- org.springframework.context.event.internalEventListenerFactory
- personConfig
- personController
- personService
- person
可以看到, 同时输出了 @Controller 注解和 @Service 注解标注的组件名称.
如果使用的是 Java8 之前的版本, 我们就不能直接在类上写多个 @ComponentScan 注解了. 此时, 我们可以在 PersonConfig 类上使用 @ComponentScans 注解, 如下所示.
- @ComponentScans(value = {
- @ComponentScan(value = "io.mykit.spring", includeFilters = {
- @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
- }, useDefaultFilters = false),
- @ComponentScan(value = "io.mykit.spring", includeFilters = {
- @Filter(type = FilterType.ANNOTATION, classes = {Service.class})
- }, useDefaultFilters = false)
- })
再次运行 SpringBeanTest 类的 testComponentScanByAnnotation() 方法, 输出的结果信息如下所示.
- org.springframework.context.annotation.internalConfigurationAnnotationProcessor
- org.springframework.context.annotation.internalAutowiredAnnotationProcessor
- org.springframework.context.annotation.internalCommonAnnotationProcessor
- org.springframework.context.event.internalEventListenerProcessor
- org.springframework.context.event.internalEventListenerFactory
- personConfig
- personController
- personService
- person
与使用多个 @ComponentScan 注解输出的结果信息相同.
总结: 我们可以使用 @ComponentScan 注解来指定 Spring 扫描哪些包, 可以使用 excludeFilters() 指定扫描时排除哪些组件, 也可以使用 includeFilters() 指定扫描时只包含哪些组件. 当使用 includeFilters() 指定只包含哪些组件时, 需要禁用默认的过滤规则
好了, 咱们今天就聊到这儿吧! 别忘了给个在看和转发, 让更多的人看到, 一起学习一起进步!!
项目工程源码已经提交到 GitHub: https://github.com/sunshinelyz/spring-annotation
写在最后
如果觉得文章对你有点帮助, 请微信搜索并关注「 冰河技术 」微信公众号, 跟冰河学习 Spring 注解驱动开发. 公众号回复 "spring 注解" 关键字, 领取 Spring 注解驱动开发核心知识图, 让 Spring 注解驱动开发不再迷茫.
来源: https://www.cnblogs.com/binghe001/p/13054945.html