使用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()指定只包含哪些组件时,需要禁用默认的过滤规则