在使用Spring框架开发应用的过程中,大家都知道使用Spring开发应用程序,我们应用程序中大多数的Bean都是通过Spring的IOC容器来管理。将Bean注入到Spring IOC容器中的方式多种多样,如通过传统的XML方式注入,通过注解的方式注入等。本文我们就来详细的探索一下在使用XML配置方式注入Bean时,component-scan组件扫描的应用。
1、<context:component-scan />
介绍
首先我们来简单了解一下<context:component-scan />
标签,在使用该标签时,我们必须指定一个base-package,在Spring IOC容器启动时,会根据base-package递归向下扫描出base-package包及其所有子包中被@Repository、@Service、@Controller、@RestController、@Component、@Configuration注解所注解的Bean,并将它们注入到Spring IOC容器中,由Spring IOC来统一管理。
相对于直接使用<bean id="***" class="****">
标签来说,使用<context:component-scan base-package="***" />
组件扫描大大减少了我们XML的配置量、为我们带来了非常大的便利。
2、使用<context:component-scan base-package="***" />
注入组件
首先我们新创建一个Maven项目,添加spring-context和junit的依赖,pom.xml如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
在resources目录下,创建一个beans.xml文件,加入<context:component-scan />
标签,指定 base-package
为com.training.componentscan
。
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="com.training.componentscan"></context:component-scan>
</beans>
在com.training.componentscan
包下我们分别创建component、controller、repository、service
包。
在component包中创建SoftWareComponent类,加@Component注解:
@Component
public class SoftWareComponent {
//... ...
}
在controller包中创建SoftWareController类,加@Controller注解:
@Controller
public class SoftWareController {
//... ...
}
在repository包中创建SoftWareRepository类,加@Repository注解:
@Repository
public class SoftWareRepository {
//... ...
}
在service包中创建SoftWareService类,加@Service注解:
@Service
public class SoftWareService {
//... ...
}
我们创建一个单元测试类IOCTest,用来测试看有哪些组件被注入到了Spring IOC容器中。
public class IOCTest {
@Test
public void componentTest01(){
ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");
String[] beanNames = application.getBeanDefinitionNames();
for (String name : beanNames) {
System.out.println(name);
}
}
}
整个项目结构看起来如下图:
执行单元测试,我们可以看大如下的结果:
softWareComponent
softWareController
softWareRepository
softWareService
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
从执行结果我们可以看到,容器启动后,我们所有加了@Repository、@Service、@Controller、@Component注解的Bean都被注入到了Spring IOC容器中。
3、如何排除不需要的注入Spring IOC中的组件?
在上面的例子中,在使用组件扫描时,如果我们想排除某些Bean,不希望所有加了@Repository、@Service、@Controller、@RestController、@Component、@Configuration这些注解的Bean都被注入到Spring IOC中,我们该怎么处理呢?
<context:component-scan >
提供两个子标签:<context:include-filter>
和<context:exclude-filter>
;
<context:include-filter>
表示只包含过滤器,即指定具备某种条件的Bean可以注入到Spring IOC中;<context:exclude-filter>
表示排除过滤器,即指定具备某种条件的Bean不能注入到Spring IOC中;
对于以上提到的注解所注解的Bean,Spring 给我们提供了5种方式排除不期望通过自动扫描注入到Spring IOC中的Bean。
- annotation:表示被某个注解标记的Bean不能注入到Spring IOC容器;
- assignable:表示某个具体的Bean不能注入到Spring IOC容器;
- aspectj:通过面向切面的方式,将与切点匹配的Bean排除,使其不能注入到Spring IOC容器;
- regex:通过正则表达式判断所扫描范围,和指定正则表达式匹配的Bean都不能注入到Spring IOC容器;
- custom:自定义过滤器,满足自定义过滤器条件的Bean都不能注入到Spring IOC容器;
下面我们分别对每一种方式
3.1 annotation方式排除
根据上面的实例,我们演示在容器启动时,如何通过annotation方式排除被@Repository注解所注解的Bean。
首先我们调整bean.xml如下:
<context:component-scan base-package="com.training.componentscan">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository" />
</context:component-scan>
注意,在排除某个注解注解的Bean时,expression的值为指定注解的完全限定名(Qualified Name)。
执行单元测试我们就可以得到如下结果:
softWareComponent
softWareController
softWareService
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
从执行结果我们可以看到,被@Repository注解所注解的SoftWareRepository被排除了,没有注入到Spring IOC中。
3.2 assignable方式排除
根据上面的实例,我们演示在容器启动时,如何通过assignable方式排除SoftWareController。
修改bean.xml如下:
<context:component-scan base-package="com.training.componentscan">
<context:exclude-filter type="assignable" expression="com.training.componentscan.controller.SoftWareController" />
</context:component-scan>
注意,在排除某个具体的Bean时,expression的值为要排除的类的完全限定名(Qualified Name)。
执行单元测试我们就可以得到如下结果:
softWareComponent
softWareRepository
softWareService
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
从执行结果我们可以看到,虽然SoftWareController我们加了@Controller注解,但是我们通过assignable方式指定排除SoftWareController之后,Spring IOC容器启动之后,该Bean就被排除了,没有注入到Spring IOC中。
3.3 aspectj方式排除
根据上面的实例,我们演示在容器启动时,如何通过切面(aspectj)的方式排除满足切点为com.training.componentscan..*
的所有Bean。
在使用切面时,首先我们需要加入切面aspectj的依赖。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
修改bean.xml如下:
<context:component-scan base-package="com.training.componentscan">
<context:exclude-filter type="aspectj" expression="com.training.componentscan..*"/>
</context:component-scan>
执行单元测试我们就可以得到如下结果:
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
从执行结果我们可以看到,我们已经将com.training.componentscan包下的所有加了@Repository、@Service、@Controller、@Component的Bean都排除了。
3.4 regex方式排除
根据上面的实例,我们演示在容器启动时,如何通过正则表达式(regex)的方式排除以Service结尾的Bean。
首先修改bean.xml如下:
<context:component-scan base-package="com.training.componentscan">
<context:exclude-filter type="regex" expression=".*Service"/>
</context:component-scan>
执行单元测试我们就可以得到如下结果:
softWareComponent
softWareController
softWareRepository
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
从执行结果我们可以看到虽然SoftWareService我们加了@Service注解,但是加了排除过滤器之后,SoftWareService并没有被注入到Spring IOC中。
3.5 custom方式排除
根据上面的实例,我们来看看如何通过个性化(custom)的方式来排除类名中没有包含“Co”的Bean。
使用个性化的方式,首先我们需要自定义一个自己的过滤器并且实现org.springframework.core.type.filter.TypeFilter
接口。我们定义我们个性化过滤器为ComponentFilter,并实现我们的过滤规则如下:
public class ComponentFilter implements TypeFilter {
/**
* metadataReader:读取到的当前正在扫描的类的信息
* metadataReaderFactory:可以获取到其他任何类信息
*/
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();
System.out.println("---->>>>>"+className);
if(!className.contains("Co")){
return true;
}
return false;
}
}
修改bean.xml如下
<context:component-scan base-package="com.training.componentscan">
<context:exclude-filter type="custom" expression="com.training.componentscan.filter.ComponentFilter"/>
</context:component-scan>
执行我们的单元测试,我们就会得到如下的结果:
---->>>>>com.training.componentscan.IOCTest
---->>>>>com.training.componentscan.component.SoftWareComponent
---->>>>>com.training.componentscan.controller.SoftWareController
---->>>>>com.training.componentscan.filter.ComponentFilter
---->>>>>com.training.componentscan.repository.SoftWareRepository
---->>>>>com.training.componentscan.service.SoftWareService
softWareComponent
softWareController
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
从结果我么就可知,所有类名中没有包含“Co”的Bean都没有注入到Spring IOC容器中。
4、如何自动扫描指定的Bean注入Spring IOC中的组件?
在上文中,我们有提到<context:component-scan >
提供两个子标签:<context:include-filter>
和<context:exclude-filter>
;
<context:include-filter>
表示只包含过滤器,即指定具备某种条件的Bean可以注入到Spring IOC中;<context:exclude-filter>
表示排除过滤器,即指定具备某种条件的Bean不能注入到Spring IOC中;
所以如果我们要指定自动扫描时只包含满足具体条件的Bean注入到Spring IOC中,我们只需要使用<context:include-filter>
子标签即可,同样,对于只包含标签,Spring也提供了5中方式,这5中方式与<context:exclude-filter>
雷同,这里不再赘述。
但是需要说明的是,在使用<context:include-filter>
只包含子标签时,我们需要将<context:component-scan >
的use-default-filters
属性设置成false
,因为默认情况下,use-default-filters
为true
,默认会根据注解的方式自动扫描被@Repository、@Service、@Controller、@RestController、@Component、@Configuration注解所注解的Bean注入到Spring IOC容器中。将use-default-filters
设置为false
,就可以很好的按照自己的规则将需要自动扫描的Bean注入到Spring IOC容器。
我们这里只举一个简单的例子,按照自定义的方式将类名中包含“Co”的Bean通过自动扫描注入到Spring IOC容器。
我们修改bean.xml如下:
<context:component-scan base-package="com.training.componentscan" use-default-filters="false">
<context:include-filter type="custom" expression="com.training.componentscan.filter.ComponentFilter" />
</context:component-scan>
修改ComponentFilter如下:
public class ComponentFilter implements TypeFilter {
/**
* metadataReader:读取到的当前正在扫描的类的信息 metadataReaderFactory:可以获取到其他任何类信息
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
//... ...
if(className.contains("Co")){
return true;
}
//... ...
}
}
执行单元测试,我们就可以看到如下的结果:
---->>>>>com.training.componentscan.IOCTest
---->>>>>com.training.componentscan.component.SoftWareComponent
---->>>>>com.training.componentscan.controller.SoftWareController
---->>>>>com.training.componentscan.filter.ComponentFilter
---->>>>>com.training.componentscan.repository.SoftWareRepository
---->>>>>com.training.componentscan.service.SoftWareService
softWareComponent
softWareController
componentFilter
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
在com.training.componentscan包下所有包含“Co”的Bean已经全部注入到了Spring IOC容器中。
小结:
本文我们通过XML注解的方式讲述了component-scan的应用,以及通过实例演示的方式向大家展示了自动扫描注入时如何通过不同方式排除、包含满足指定条件的Bean,希望大家共勉。