到目前为止,介绍了2种注册bean的方式:
- xml中bean元素的方式
- @Bean注解标注方法的方式
如果有很多类都需要注册bean呢? 手动分别为每个类添加@confiuration和@Bean?
这也太麻烦了。
@ComponentScan
@ComponentScan用于批量注册bean。
这个注解的源码:
@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 "**/*.class";
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
}
ElementType.TYPE可用在任何类型上面,不过我们通常用在类上面
参数:
value、basePackages:指的是要扫描的包 如 com.zzb.pojo
nameGenerator:自定义bean名称生成器
resourcePattern:需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的class文件
useDefaultFilters:对扫描的类是否启用默认过滤器,默认为true
includeFilters:过滤器:过滤被扫描的类,判断哪些放行进行注册
excludeFilters:过滤器,和includeFilters作用相反,对扫描的类进行排除,被排除的类不被注册到容器中
lazyInit:是否延迟初始化被注册的bean
其他参数会在后面提及。
@ComponentScan工作的过程:
- Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
- 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
总结:
- value、backPackages、basePackageClasses 这3个参数来控制需要扫描哪些包,来获取类数组
- useDefaultFilters、includeFilters、excludeFilters 这3个参数来控制过滤器
默认情况下,任何参数都不设置的情况下,此时,会将@ComponentScan修饰的类所在的包作为扫描包;默认情况下useDefaultFilters为true,这个为true的时候,spring容器内部会使用默认过滤器。
规则是:凡是类上有 @Repository、@Service、@Controller、@Component 这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中,所以默认情况下,只需在类上加上这几个注解中的任何一个,这些类就会自动交给spring容器来管理了。
ComponentScan就像一个扫描器,扫描其所在的包中的以下注解。
另外@Repeatable(ComponentScans.class)修饰,可以看出此**@ComponentScan可以重复使用**,ComponentScans是其容器。关于@Repeatable在上一篇中已讲,如何重复使用也在上一篇中讲了。
@Component、@Repository、@Service、@Controller
@Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
通常情这个注解用在类上面,标注这个类为一个组件,默认情况下,被扫描的时候会被作
为bean注册到容器中。
value参数:被注册为bean的时候,用来指定bean的名称,如果不指定,默认为类名首字母小
写。如:类UserService对应的beanname为userService
@Repository
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}
Repository上面有@Component注解。
value参数上面有 @AliasFor(annotation = Component.class) ,设置value参数的时候,也相当于给 @Component 注解中的value设置值。
@Service、@Controller 源码和 @Repository 源码类似。都是继承了Component.class
所以说这几个注解本质上是一样的,本质完全相同,只是名字不同。
spring提供这4个注解,是为了让系统进行分层,controller层、service层、dao层。
@controller通常用来标注controller层组件,@service注解标注service层的组件,@Repository标注dao层的组件,这样可以让整个系统的结构更清晰,当看到这些注解的时候,会和清晰的知道属于哪个层,对于spring来说,将这3个注解替换成@Component注解,对系统没有任何影响,产生的效果是一样的。
举个例子:
当com.zzb.test包中有个类被@ComponentScan修饰时,这个类作为了扫描类。扫描类所在包及其以下的包,如com.zzb.test.dao包、com.zzb.test.service包、com.zzb.test.controller包,其中被@Component、@Repository、@Service、@Controller修饰的类都会被扫描到,并注册Bean.
这里就光贴个测试类了,其他类就不贴了
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ComponentScanTest {
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean1.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName + "->" + context.getBean(beanName));
}
}
}
@ComponentScan的使用
@ComponentScan除了可以无参使用,直接使用默认外,还有其他使用方法。
无参使用就是方法一。
方法2:(直接指定包)
@ComponentScan({
"com.zzb.test.controller",
"com.zzb.test.service"
})
@ComponentScan的basePackageClasses参数
指定扫描范围
案例:
在com.zzb.test.beans包中定义一个接口ScanClass
package com.zzb.test.beans;
public interface ScanClass {
}
同样的包下,再来两个Bean类
package com.zzb.test.beans;
@Component
public class Bean1{
}
package com.zzb.test.beans;
@Component
public class Bean2{
}
在com.zzb.test包下定义一个扫描类
并指定ScanClass 接口为basePackageClasses 的值
@ComponentScan(basePackageClasses = ScanClass.class)
package com.zzb.test;
@ComponentScan(basePackageClasses = ScanClass.class)
public class ScanBean {
}
测试类:
package com.zzb.test;
import com.zzb.test.annotation.ScanBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name + "->" + context.getBean(name));
}
}
}
输出:(部分)
scanBean->com.zzb.test.beans.ScanBean@53f65459
bean1->com.zzb.test.beans.Bean1@3b088d51
bean2->com.zzb.test.beans.Bean2@1786dec2
现在的目录结构是这样的:
在此结构上做修改:
如果将ScanBean放入abc包中,依然可以检测到并注册Bean1和Bean2
如果将ScanBean放入abc包中,并且@ComponentScan设置无参,不可检测到Bean
如果将ScanClass放入abc包中,不可检测到Bean
如果将ScanClass放入test包中,依然可以检测到并注册Bean1和Bean2
总结:
- @ComponentScan无参数时,其所在同级目录和下级目录的包可扫描到。其他目录无法扫描到。
- @ComponentScan的basePackageClasses参数指定的接口或者类,其所在同级目录或下级目录中的包可以扫描到。
- 配置basePackageClasses后,默认扫描失效,@ComponentScan其所在同级目录或下级目录中的包不再进行扫描,除非basePackageClasses指定了相关类。
@ComponentScan的includeFilters参数
Filter[] includeFilters() default {};
是一个 Filter 类型的数组,多个Filter之间为或者关系,即满足任意一个就可以了。
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 {};
}
参数:
type:枚举类型,5种
public enum FilterType {
ANNOTATION,//通过注解的方式来筛选候选者
ASSIGNABLE_TYPE,//通过指定的类型来筛选候选者
ASPECTJ,//ASPECTJ表达式方式
REGEX,//正则表达式方式
CUSTOM;//用户自定义过滤器来筛选候选者
private FilterType() {
}
}
value、classes:
- 当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类上是否有classes参数指定的注解。
- 当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型。
- 当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口
pattern:2种情况如下
4. 当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值
5. 当type=FilterType.REGEX时,通过pattern来自正则表达式的值
扫描包含指定注解的类
案例:自定义一个注解,让标注有这些注解的类自动注册到容器中。
自定义注解 type=FilterType.ANNOTATION
package com.zzb.test;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
@AliasFor(annotation = Component.class)
String value() default "";//这个参数使我们可以配置Bean的注册名称
//@AliasFor注解在上一篇文章中已讲。
}
Bean类 一个自定义注解修饰,一个@Component修饰
package com.zzb.test;
@MyBean
public class Service1 {
}
扫描类
package com.zzb.test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
MyBean.class)
})
public class ScanBean {
}
package com.zzb.test;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
}
测试类
package com.zzb.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name + "->" + context.getBean(name));
}
}
}
输出:
scanBean->com.zzb.test.ScanBean@17579e0f
service1->com.zzb.test.Service1@4d41cee
service2->com.zzb.test.Service2@3712b94
两个Bean都注册到容器中了,原因:
@CompontentScan注解中的 useDefaultFilters默认是 true ,表示会启用默认的过滤器。
同时includeFilters 参数又添加了一个过滤器。
这两个过滤器以 或 的方式起作用,满足一个就会被注册。
扫描包含指定类型的类
指定类型,接口、类都行。用接口的话Bean类就实现,用类就继承。这里不进行演示了,有兴趣的可以自己写写看。
在这只贴个扫描类上的注解了。以接口为例。
@ComponentScan(
useDefaultFilters = false, //不启用默认过滤器
includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = 指定接口实现类.class) //@1
}
)
自定义Filter
自定义的过滤器,使用自定义过滤器的步骤:
- 设置@Filter中type的类型为:FilterType.CUSTOM
- 自定义过滤器类,需要实现接口:org.springframework.core.type.filter.TypeFilter
- 设置@Filter中的classses为自定义的过滤器类型
TypeFilter
@FunctionalInterface
public interface TypeFilter {
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException;
}
@FunctionalInterface
规定规定此接口为函数式接口
函数式接口:一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口
ps:jdk8之后接口中可以实现默认方法和静态方法了。
match方法,2个参数都是接口类型,返回值boolean类型。
MetadataReader 接口
类元数据读取器,可以读取一个类上的任意信息,如类上面的注解信息、类的磁盘路径信息、类的class对象的各种信息,spring进行了封装,提供了各种方便使用的方法
MetadataReaderFactory 接口
类元数据读取器工厂,可以通过这个类获取任意一个类的MetadataReader对象。
自定义Filter案例:
自定义的Filter,判断被扫描的类如果是 User 接口类型的,就让其注册到容器中
接口
package com.zzb.test;
public interface User {
}
Bean
package com.zzb.test;
public class User1 implements User{
}
package com.zzb.test;
public class User2 implements User{
}
自定义TypeFilter实现类MyFilter 即:自定义过滤器
package com.zzb.test;
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;
public class MyFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
Class getClass = null;
try {
//当前被扫描的类
getClass = Class.forName(metadataReader.getClassMetadata().getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//判断getClass是否是规定类型
boolean result = User.class.isAssignableFrom(getClass);
return result;
}
}
扫描类
package com.zzb.test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(
basePackages = {"com.zzb.test"},
useDefaultFilters = false, //不启用默认过滤器
includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes =
MyFilter.class)
})
public class ScanBean {
}
测试类还是用前面写的,没变化。
输出:
user1->com.zzb.test.User1@1786dec2
user2->com.zzb.test.User2@74650e52
@ComponentScan的excludeFilters参数
配置排除的过滤器,满足这些过滤器的类不会被注册到容器中,用法上面和includeFilters用一样。这里不进行演示了,有兴趣的可以自己写写看。