Spring中常用的注解
4.3.1 @ComponentScan和ComponentScans(bean批量注册)
- @ComponentScan用于批量注册bean,spring会按照这个注解的配置,递归扫描指定包中的所有类,将满足条件的类批量注册到spring容器中
- 可以通过value、basePackages、basePackageClasses 这几个参数来配置包的扫描范围
- 可以通过useDefaultFilters、includeFilters、excludeFilters这几个参数来配置类的过滤器,被过滤器处理之后剩下的类会被注册到容器中
- 指定包名的方式配置扫描范围存在隐患,包名被重命名之后,会导致扫描实现,所以一般我们在需要扫描的包中可以创建一个标记的接口或者类,作为basePackageClasses的值,通过这个来控制包的扫描范围
- @CompontScan注解会被ConfigurationClassPostProcessor类递归处理,最终得到所有需要注册的类
4.3.1.1 源码定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class) //@1
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;
}
常用参数:
- value:指定需要扫描的包,如:com.javacode2018
- basePackages:作用同value;value和basePackages不能同时存在设置,可二选一
- basePackageClasses:指定一些类,spring容器会扫描这些类所在的包及其子包中的类
- nameGenerator:自定义bean名称生成器
- resourcePattern:需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的class文件
- useDefaultFilters:对扫描的类是否启用默认过滤器,默认为true
- includeFilters:过滤器:用来配置被扫描出来的那些类会被作为组件注册到容器中
- excludeFilters:过滤器,和includeFilters作用刚好相反,用来对扫描的类进行排除的,被排除的类不会被注册到容器中
- lazyInit:是否延迟初始化被注册的bean
- @Repeatable(ComponentScans.class),这个注解可以同时使用多个
4.3.1.2 工作过程:
- Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
- 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
4.3.1.3 关键问题:
- 需要扫描哪些包?
通过value、backPackages、basePackageClasses这3个参数来控制 - 过滤器有哪些?
通过useDefaultFilters、includeFilters、excludeFilters这3个参数来控制过滤器
4.3.1.4 扫描规则:
默认情况下,任何参数都不设置的情况下会将@ComponentScan修饰的类所在的包作为扫描包。
默认情况下,useDefaultFilters=true,spring容器内部会使用默认过滤器,规则是:凡是类上有@Repository、@Service、@Controller、@Component这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中,所以默认情况下,只需在类上加上这几个注解中的任何一个,这些类就会自动交给spring容器来管理了。
4.3.1.5 案例1:任何参数未设置
分别在dao,controller,service包下创建类,用@Service,@Controller,@Repository注解标注
UserService
package com.zjhc.componentSacn.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
}
UserController
package com.zjhc.componentSacn.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
}
UserDao
package com.zjhc.componentSacn.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
}
UserModel
package com.zjhc.componentSacn;
import org.springframework.stereotype.Component;
@Component
public class UserModel {
}
ScanBean
package com.zjhc.componentSacn;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class ScanBean {
}
测试
@Test
public void test2(){
ApplicationContext context = new AnnotationConfigApplicationContext(ScanBean.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName+"---->"+context.getBean(beanName));
}
}
使用AnnotationConfigApplicationContext作为ioc容器,将ScanBean.class作为参数传入,默认会扫描ScanBean类所在的包中的所有类,类上有@Component、@Repository、@Service、@Controller任何一个注解的都会被注册到容器中
4.3.1.6 案例2:指定需要扫描的包
指定需要扫毛哪些包,可以通过value或者basePackage来配置,二者选其一,都配置运行会报错,下面我们通过value来配置
package com.zjhc.componentSacn;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan({
"com.zjhc.componentSacn.controller,
com.zjhc.componentSacn.dao"})
public class ScanBean {
}
测试结果
4.3.1.7 案例:basePackageClasses指定扫描范围
指定包名的方式扫描存在的一个隐患,若包被重名了,会导致扫描会失效,我们可以在需要扫描的包中定义一个标记的接口或者类,他们的唯一的作用是作为basePackageClasses的值,其他没有任何用途。
定义一个类或者接口
package com.zjhc.componentSacn.controller;
public class a {
}
import com.zjhc.componentSacn.controller.a;
import org.springframework.context.annotation.ComponentScan;
//@ComponentScan({"com.zjhc.componentSacn.controller,com.zjhc.componentSacn.dao"})
@ComponentScan(basePackageClasses = a.class)
public class ScanBean {
}
scanBean---->com.zjhc.componentSacn.ScanBean@67e2d983
userController---->com.zjhc.componentSacn.controller.UserController@5d47c63f
4.3.1.8 includeFilters和excludeFilters的使用
是一个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种类型
ANNOTATION:通过注解的方式来筛选候选者,即判断候选者是否有指定的注解
ASSIGNABLE_TYPE:通过指定的类型来筛选候选者,即判断候选者是否是指定的类型
ASPECTJ:ASPECTJ表达式方式,即判断候选者是否匹配ASPECTJ表达式
REGEX:正则表达式方式,即判断候选者的完整名称是否和正则表达式匹配
CUSTOM:用户自定义过滤器来筛选候选者,对候选者的筛选交给用户自己来判断
value:和参数classes效果一样,二选一
classes:3种情况如下
当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类上是否有classes参数指定的注解
当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型
当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口
pattern:2种情况如下
当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值
当type=FilterType.REGEX时,通过pattern来自正则表达式的值
4.3.1.8.1 扫描包含注解的类
我们自定义一个注解,让标注有这些注解的类自动注册到容器中
package com.zjhc.componentSacn.annotation;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
}
创建一个类,使用这个注解标注
package com.zjhc.componentSacn.annotation;
@MyAnno
public class Service1 {
}
再来一个类,使用spring中的@Compontent
标注
package com.zjhc.componentSacn.annotation;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
}
再来一个类,使用@CompontentScan标注
package com.zjhc.componentSacn;
@ComponentScan(includeFilters = {
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes = MyAnno.class)})
public class ScanBean2 {
}
测试用例
@Test
public void test3(){
ApplicationContext context = new AnnotationConfigApplicationContext(ScanBean2.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName+"---->"+context.getBean(beanName));
}
}
结果
问题:Service1上标注了@MyBean注解,被注册到容器了,但是没有标注@MyBean啊,怎么也被注册到容器了?
回答:@CompontentScan注解中的useDefaultFilters默认是true,表示会启用默认的过滤器,默认的过滤器会将标注有@Component、@Repository、@Service、@Controller这几个注解的类也注册到容器中。
修改扫描代码:
如果我们只想将标注有@MyBean注解的bean注册到容器,需要将默认过滤器关闭,即:useDefaultFilters=false
package com.zjhc.componentSacn;
@ComponentScan(useDefaultFilters = false,includeFilters = {
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes = MyAnno.class)})
public class ScanBean2 {
}
再输出:
4.3.1.8.2 包含指定类型的类
被扫描的类满足IService.class.isAssignableFrom(被扫描的类)条件的都会被注册到spring容器中
@ComponentScan(
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,classes = IService.class)}
)
接口
package com.zjhc.componentSacn.annotation.componentBytype;
public