Spring系列(16-19)
十六、@Configration和@Bean注解
1、@Configuration注解
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);
有没有@Configuration注解,@Bean都会起效,都会将@Bean修饰的方法作为bean注册到容器中。被@Configuration修饰的bean最后输出的时候带有EnhancerBySpringCGLIB 的字样,而没有@Configuration注解的bean没有Cglib的字样;有 EnhancerBySpringCGLIB 字样的说明这个bean被cglib处理过的,变成了一个代理对象。
被@Configuration修饰的类,Spring容器中会通过cglib给这个类创建一个代理,代理会拦截所有被@Bean 修饰的方法,默认情况(bean为单例)下确保这些方法只被调用一次,从而确保这些bean是同一个bean,即单例的。
2、@Bean注解
@Bean
public User user1() {
return new User();
}十七、@ComponentScan、@ComponentScans注解
1、@ComponentScan
@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;
}value:指定需要扫描的包。
basePackages:作用同value;value和basePackages不能同时存在设置,可二选一。
basePackageClasses:指定一些类,spring容器会扫描这些类所在的包及其子包中的类。
nameGenerator:自定义bean名称生成器。
resourcePattern:需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的class文件。
useDefaultFilters:对扫描的类是否启用默认过滤器,默认为true。
includeFilters:过滤器:用来配置被扫描出来的那些类会被作为组件注册到容器中。
excludeFilters:过滤器,和includeFilters作用刚好相反,用来对扫描的类进行排除的,被排除的类不会被注册到容器中。
lazyInit:是否延迟初始化被注册的bean。
@Repeatable(ComponentScans.class),这个注解可以同时使用多个。
@ComponentScan工作的过程:
2、@Component、@Repository、@Service、@Controller
3、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
ANNOTATION:通过注解的方式来筛选候选者,即判断候选者是否有指定的注解。
ASSIGNABLE_TYPE:通过指定的类型来筛选候选者,即判断候选者是否是指定的类型。
ASPECTJ:ASPECTJ表达式方式,即判断候选者是否匹配ASPECTJ表达式。
REGEX:正则表达式方式,即判断候选者的完整名称是否和正则表达式匹配。
CUSTOM:用户自定义过滤器来筛选候选者,对候选者的筛选交给用户自己来判断。
value
classes
当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类上是否有classes参数指定的注解。
当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型。
当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口。
pattern
当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值。
当type=FilterType.REGEX时,通过pattern来自正则表达式的值。
案例:扫描包含某个注解的类
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component //@1
public @interface MyBean {
@AliasFor(annotation = Component.class) //@2
String value() default ""; //@3
}@MyBean
public class Service1 {
}使用@CompontentScan标注
@ComponentScan(
useDefaultFilters = false, //不启用默认过滤器
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes =MyBean.class)})
public class ScanBean3 {
}案例:扫描包含指定类型的类
public interface IService {
}@CompontentScan标注的类
@ComponentScan(
useDefaultFilters = false, //不启用默认过滤器
includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes= IService.class)}
)
public class ScanBean4 {
}4、自定义Filter
设置@Filter中type的类型为:FilterType.CUSTOM。
自定义过滤器类,需要实现接口:org.springframework.core.type.filter.TypeFilter。
设置@Filter中的classses为自定义的过滤器类型。
@FunctionalInterface
public interface TypeFilter {
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException;
}MetadataReader接口
public interface MetadataReader {
/**
* 返回类文件的资源引用
*/
Resource getResource();
/**
* 返回一个ClassMetadata对象,可以通过这个读想获取类的一些元数据信息,如类的class对象、
是否是接口、是否有注解、是否是抽象类、父类名称、接口名称、内部包含的之类列表等等,可以去看一下源
码
*/
ClassMetadata getClassMetadata();
/**
* 获取类上所有的注解信息
*/
AnnotationMetadata getAnnotationMetadata();
}MetadataReaderFactory接口
public interface MetadataReaderFactory {
/**
* 返回给定类名的MetadataReader对象
*/
MetadataReader getMetadataReader(String className) throws IOException;
/**
* 返回指定资源的MetadataReader对象
*/
MetadataReader getMetadataReader(Resource resource) throws IOException;
}我们来个自定义的Filter,判断被扫描的类如果是 IService 接口类型的,就让其注册到容器中。
public class MyFilter implements TypeFilter {
/**
* @param metadataReader
* @param metadataReaderFactory
* @return
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
Class curClass = null;
try {
//当前被扫描的类
curClass = Class.forName(metadataReader.getClassMetadata().getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//判断curClass是否是IService类型
boolean result = IService.class.isAssignableFrom(curClass);
return result;
}
}@CompontentScan标注的类
@ComponentScan(
useDefaultFilters = false, //不启用默认过滤器
includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes =MyFilter.class)}
)
public class ScanBean5 {
}5、excludeFilters
十八、@Import批量注册bean
1、@Import出现的背景
@Configuration结合@Bean注解的方式
@CompontentScan扫描包的方式
问题1
通过@Bean标注方法的方式,一个个来注册。
@CompontentScan的方式:默认的@CompontentScan是无能为力的,默认情况下只会注册@Compontent标注的类,此时只能自定义@CompontentScan中的过滤器来实现了。
问题2
2、@Import使用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to
import.
*/
Class<?>[] value();
}使用步骤
将@Import标注在类上,设置value参数。
将@Import标注的类作为AnnotationConfigApplicationContext构造参数创建AnnotationConfigApplicationContext对象。
使用AnnotationConfigApplicationContext对象。
@Import的value常见的有5种用法
value为普通的类。
value为@Configuration标注的类。
value为@CompontentScan标注的类。
value为ImportBeanDefinitionRegistrar接口类型。
value为ImportSelector接口类型。
value为DeferredImportSelector接口类型。
ImportBeanDefinitionRegistrar接口
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}importingClassMetadata:AnnotationMetadata类型的,通过这个可以获取被@Import注解标注的类所有注解的信息。
registry:BeanDefinitionRegistry类型,是一个接口,内部提供了注册bean的各种方法。
importBeanNameGenerator:BeanNameGenerator类型,是一个接口,内部有一个方法,用来生成bean的名称。
BeanDefinitionRegistry接口:bean定义注册器
public interface BeanDefinitionRegistry extends AliasRegistry {
/**
* 注册一个新的bean定义
* beanName:bean的名称
* beanDefinition:bean定义信息
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException;
/**
* 通过bean名称移除已注册的bean
* beanName:bean名称
*/
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
/**
* 通过名称获取bean的定义信息
* beanName:bean名称
*/
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
/**
* 查看beanName是否注册过
*/
boolean containsBeanDefinition(String beanName);
/**
* 获取已经定义(注册)的bean名称列表
*/
String[] getBeanDefinitionNames();
/**
* 返回注册器中已注册的bean数量
*/
int getBeanDefinitionCount();
/**
* 确定给定的bean名称或者别名是否已在此注册表中使用
* beanName:可以是bean名称或者bean的别名
*/
boolean isBeanNameInUse(String beanName);
}BeanNameGenerator接口:bean名称生成器
public interface BeanNameGenerator {
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}DefaultBeanNameGenerator:默认bean名称生成器,xml中bean未指定名称的时候,默认就会使用这个生成器,默认为:完整的类名#bean编号。
AnnotationBeanNameGenerator:注解方式的bean名称生成器,比如通过@Component(bean名称)的方式指定bean名称,如果没有通过注解方式指定名称,默认会将完整的类名作为bean名称。
FullyQualifiedAnnotationBeanNameGenerator:将完整的类名作为bean的名称。
BeanDefinition接口:bean定义信息
value为ImportBeanDefinitionRegistrar接口类型用法(4个步骤)
定义ImportBeanDefinitionRegistrar接口实现类,在registerBeanDefinitions方法中使用registry来注册bean。
使用@Import来导入步骤1中定义的类。
使用步骤2中@Import标注的类作为AnnotationConfigApplicationContext构造参数创建Spring容器。
使用AnnotationConfigApplicationContext操作bean。
案例
public class Service1 {
}
public class Service2 {
private Service1 service1;
public Service1 getService1() {
return service1;
}
public void setService1(Service1 service1) {
this.service1 = service1;
}
}来个类实现ImportBeanDefinitionRegistrar接口,然后在里面实现上面2个类的注册,如下:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//定义一个bean:Service1
BeanDefinition service1BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service1.class).getBeanDefinition();
//注册bean
registry.registerBeanDefinition("service1", service1BeanDinition);
//定义一个bean:Service2,通过addPropertyReference注入service1
BeanDefinition service2BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service2.class).addPropertyReference("service1", "service1").getBeanDefinition();
//注册bean
registry.registerBeanDefinition("service2", service2BeanDinition);
}
}value为ImportSelector接口类型
public interface ImportSelector {
/**
* 返回需要导入的类名的数组,可以是任何普通类,配置类(@Configuration、@Bean、@CompontentScan等标注的类)
* @importingClassMetadata:用来获取被@Import标注的类上面所有的注解信息
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}定义ImportSelector接口实现类,在selectImports返回需要导入的类的名称数组。
使用@Import来导入步骤1中定义的类。
使用步骤2中@Import标注的类作为AnnotationConfigApplicationContext构造参数创建spring容器。
使用AnnotationConfigApplicationContext操作bean。
案例
public class Service1 {
}@Configuration标注的配置类:Module1Config
@Configuration
public class Module1Config {
@Bean
public String name() {
return "公众号:路人甲java";
}
@Bean
public String address() {
return "上海市";
}
}下面自定义一个ImportSelector,然后返回上面2个类的名称
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
Service1.class.getName(),
Module1Config.class.getName()
};
}
}来个@Import标注的类,导入MyImportSelector
/**
* 通过@Import导入MyImportSelector接口实现类
*/
@Import({MyImportSelector.class})
public class MainConfig5 {
}测试
@Test
public void test5() {
//1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
//2.输出容器中定义的所有bean信息
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}部分输出如下:
com.javacode2018.lesson001.demo24.test5.Service1- >com.javacode2018.lesson001.demo24.test5.Service1@45b4c3a9 name->公众号:路人甲java address->上海市
DeferredImportSelector接口
延迟导入。
比如@Import的value包含了多个普通类、多个@Configuration标注的配置类、多个ImportSelector接口的实现类,多个ImportBeanDefinitionRegistrar接口的实现类,还有DeferredImportSelector接口实现类,此时Spring处理这些被导入的类的时候,会将DeferredImportSelector类型的放在最后处理,会先处理其他被导入的类,其他类会按照value所在的前后顺序进行处理。
那么我们是可以做很多事情的,比如我们可以在DeferredImportSelector导入的类中判断一下容器中是否已经注册了某个bean,如果没有注册过,那么再来注册。
以后我们会讲到另外一个注解@Conditional,这个注解可以按条件来注册bean,比如可以判断某个bean不存在的时候才进行注册,某个类存在的时候才进行注册等等各种条件判断,通过@Conditional来结合DeferredImportSelector可以做很多事情。
指定导入的类的处理。
指定导入的类的处理顺序
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
int value() default Ordered.LOWEST_PRECEDENCE;
}3、总结
@Import可以用来批量导入任何普通的组件、配置类,将这些类中定义的所有bean注册到容器中。
@Import常见的5种用法需要掌握。
掌握ImportSelector、ImportBeanDefinitionRegistrar、DeferredImportSelector的用法。
DeferredImportSelector接口可以实现延迟导入、按序导入的功能。
Spring中很多以@Enable开头的都是使用@Import集合ImportSelector方式实现的。
BeanDefinitionRegistry接口:bean定义注册器,这个需要掌握常见的。
十九,@Conditional通过条件来控制bean的注册
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}1、Condition接口
@FunctionalInterface
public interface Condition {
/**
* 判断条件是否匹配
* context:条件判断上下文
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}context:条件上下文,ConditionContext接口类型的,可以用来获取容器中的各种信息。
metadata:用来获取被@Conditional标注的对象上的所有注解信息。
2、ConditionContext接口
public interface ConditionContext {
/**
* 返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息
*/
BeanDefinitionRegistry getRegistry();
/**
* 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* 返回当前spring容器的环境配置信息对象
*/
Environment getEnvironment();
/**
* 返回资源加载器
*/
ResourceLoader getResourceLoader();
/**
* 返回类加载器
*/
@Nullable
ClassLoader getClassLoader();
}3、比较关键性的问题:Condition条件判断在什么时候执行?
配置类解析阶段:会得到一批配置类的信息,和一些需要注册的bean。
bean注册阶段:将配置类解析阶段得到的配置类和需要注册的bean注册到Spring容器中。
什么是配置类
类上有@Compontent注解
类上有@Configuration注解
类上有@CompontentScan注解
类上有@Import注解
类上有@ImportResource注解
类中有@Bean标注的方法
Spring对配置类处理过程
通常我们会通过new AnnotationConfigApplicationContext()传入多个配置类来启动Spring容器。
Spring对传入的多个配置类进行解析。
配置类解析阶段:这个过程就是处理配置类上面6中注解的过程,此过程中又会发现很多新的配置类,比如@Import导入的一批新的类刚好也符合配置类,而被@CompontentScan扫描到的一些类刚好也是配置类;此时会对这些新产生的配置类进行同样的过程解析。
bean注册阶段:配置类解析完成后,会得到一批配置类和一批需要注册的bean,此时Spring容器会将这批配置类作为bean注册到spring容器,同样也会将这批需要注册的bean注册到Spring容器中。
经过上面第3个阶段之后,Spring容器中会注册很多新的bean,这些新的bean中可能又有很多新的配置类。
Spring从容器中将所有bean拿出来,遍历一下,会过滤得到一批未处理的新的配置类,继续交给第3步进行处理。直到完成所有配置类的解析和bean的注册。
可以在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解析,那么这个配置上面6种注解的解析都会被跳过。
可以在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到Spring容器中。
如果配置类不会被注册到容器,那么这个配置类解析所产生的所有新的配置类及所产生的所有新的bean都不会被注册到容器。
4、ConfigurationCondition接口
public interface ConfigurationCondition extends Condition {
/**
* 条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤
*/
ConfigurationPhase getConfigurationPhase();
/**
* 表示阶段的枚举:2个值
*/
enum ConfigurationPhase {
/**
* 配置类解析阶段,如果条件为false,配置类将不会被解析
*/
PARSE_CONFIGURATION,
/**
* bean注册阶段,如果为false,bean将不会被注册
*/
REGISTER_BEAN
}
}5、@Conditional使用的3步骤
自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法。
在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型。
启动Spring容器加载资源,此时@Conditional就会起作用了。
6、案例
bean不存在的时候才注册
public class OnMissingBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取bean工厂
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//从容器中获取IService类型bean
Map<String, IService> serviceMap = beanFactory.getBeansOfType(IService.class);
//判断serviceMap是否为空
return serviceMap.isEmpty();
}
}根据环境选择配置类
@Conditional(EnvCondition.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnvConditional {
//环境(测试环境、开发环境、生产环境)
enum Env {
TEST, DEV, PROD
}
//环境
Env value() default Env.DEV;
}@Configuration
@EnvConditional(EnvConditional.Env.TEST)
public class TestBeanConfig {
@Bean
public String name() {
return "我是测试环境!";
}
}开发环境配置类
@Configuration
@EnvConditional(EnvConditional.Env.DEV)
public class DevBeanConfig {
@Bean
public String name() {
return "我是开发环境!";
}
}生产环境配置类
@Configuration
@EnvConditional(EnvConditional.Env.PROD)
public class ProdBeanConfig {
@Bean
public String name() {
return "我是生产环境!";
}
}public class EnvCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//当前需要使用的环境
EnvConditional.Env curEnv = EnvConditional.Env.DEV; // 可以修改这个改变环境
//获取使用条件的类上的EnvCondition注解中对应的环境
EnvConditional.Env env = (EnvConditional.Env)
metadata.getAllAnnotationAttributes(EnvConditional.class.getName()).get("value")
.get(0);
return env.equals(curEnv);
}
}Condition指定优先级
总结
@Conditional注解可以标注在Spring需要处理的对象上(配置类、@Bean方法),相当于加了个条件判断,通过判断的结果,让Spring觉得是否要继续处理被这个注解标注的对象。
Spring处理配置类大致有2个过程:解析配置类、注册bean,这两个过程中都可以使用@Conditional来进行控制Spring是否需要处理这个过程。
Condition默认会对2个过程都有效。
ConfigurationCondition控制得更细一些,可以控制到具体那个阶段使用条件判断。
参考:路人甲-Spring系列
版权声明
非特殊说明,本文由Zender原创或收集发布,欢迎转载。
ZENDER
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。