写在前面

当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,不再创建新的bean。

如果bean是单实例,并且使用@Lazy注解设置了懒加载,则Spring容器启动时,不会实例化bean,也不会将bean注册到IOC容器中,只有第一次获取bean的时候,才会实例化bean,并且将bean注册到IOC容器中。

如果bean是多实例,则Spring容器启动时,不会实例化bean,也不会将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,都会创建一个新的bean返回。

Spring支持按照条件向IOC容器中注册bean,满足条件的bean就会被注册到IOC容器中,不满足条件的bean就不会被注册到IOC容器中。接下来,我们就一起来探讨Spring中如何实现按照条件向IOC容器中注册bean。

项目工程源码已经提交到GitHub:https://github.com/sunshinelyz/spring-annotation

@Conditional注解概述

@Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean。

@Conditional注解是由 SpringFramework 提供的一个注解,位于 org.springframework.context.annotation 包内,定义如下。

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; @Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}

从@Conditional注解的源码来看,@Conditional注解可以添加到类上,也可以添加到方法上。在@Conditional注解中,存在一个Condition类型或者其子类型的Class对象数组,Condition是个啥?我们点进去看一下。

package org.springframework.context.annotation;

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

可以看到,Condition是一个函数式接口,对于函数式接口不了解的同学可以参见【Java8新特性】中的《【Java8新特性】还没搞懂函数式接口?赶快过来看看吧!》一文。也可以直接查看《Java8新特性专栏》来系统学习Java8的新特性。

所以,我们使用@Conditional注解时,需要一个类实现Spring提供的Condition接口,它会匹配@Conditional所符合的方法,然后我们可以使用我们在@Conditional注解中定义的类来检查。

@Conditional注解的使用场景如下所示。

  • 可以作为类级别的注解直接或者间接的与@Component相关联,包括@Configuration类;
  • 可以作为元注解,用于自动编写构造性注解;
  • 作为方法级别的注解,作用在任何@Bean方法上。

向Spring容器注册bean

不带条件注册bean

我们在PersonConfig2类中新增person01()方法和person02()方法,并为两个方法添加@Bean注解,如下所示。

@Bean("binghe001")
public Person person01(){
return new Person("binghe001", 18);
} @Bean("binghe002")
public Person person02(){
return new Person("binghe002", 20);
}

那么,这两个bean默认是否会被注册到Spring容器中呢,我们新建一个测试用例来测试一下。在SpringBeanTest类中新建testAnnotationConfig6()方法,如下所示。

@Test
public void testAnnotationConfig6(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);
}

我们运行testAnnotationConfig6()方法,输出的结果信息如下所示。

person
binghe001
binghe002

从输出结果可以看出,同时输出了binghe001和binghe002。说明默认情况下,Spring容器会将单实例并且非懒加载的bean注册到IOC容器中。

接下来,我们再输出bean的名称和bean实例对象信息,此时我们在testAnnotationConfig6()方法中添加相应的代码片段,如下所示。

@Test
public void testAnnotationConfig6(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println); Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
}

再次运行SpringBeanTest类中的testAnnotationConfig6()方法,输出结果如下所示。

person
binghe001
binghe002
给容器中添加Person....
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}

可以看到,输出了注册到容器的bean。

带条件注册bean

现在,我们就要提出新的需求了,比如,如果当前操作系统是Windows操作系统,则向Spring容器中注册binghe001;如果当前操作系统是Linux操作系统,则向Spring容器中注册binghe002。此时,我们就需要使用@Conditional注解了。

这里,有小伙伴可能会问:如何获取操作系统的类型呢,别急,这个问题很简单,我们继续向下看。

使用Spring的ApplicationContext接口就能够获取到当前操作系统的类型,如下所示。

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);

我们将上述代码整合到SpringBeanTest类中的testAnnotationConfig6()方法中,如下所示。

@Test
public void testAnnotationConfig6(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName); String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println); Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
}

接下来,我们运行SpringBeanTest类中的testAnnotationConfig6()方法,输出的结果信息如下所示。

Windows 10
person
binghe001
binghe002
给容器中添加Person....
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}

由于我使用的操作系统是Windows 10操作系统,所以在结果信息中输出了Windows 10。

到这里,我们成功获取到了操作系统的类型,接下来,就可以实现:如果当前操作系统是Windows操作系统,则向Spring容器中注册binghe001;如果当前操作系统是Linux操作系统,则向Spring容器中注册binghe002的需求了。此时,我们就需要借助Spring的@Conditional注解来实现了。

要想使用@Conditional注解,我们需要实现Condition接口来为@Conditional注解设置条件,所以,这里,我们创建了两个实现Condition接口的类,分别为WindowsCondition和LinuxCondition,如下所示。

  • WindowsCondition
package io.mykit.spring.plugins.register.condition;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; /**
* @author binghe
* @version 1.0.0
* @description Windows条件,判断操作系统是否是Windows
*/
public class WindowsCondition implements Condition {
/**
* ConditionContext:判断条件使用的上下文环境
* AnnotatedTypeMetadata:注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//判断是否是Linux系统
//1.获取到IOC容器使用的BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2.获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3.获取当前的环境信息
Environment environment = context.getEnvironment();
//4.获取bean定义的注册类,我们可以通过BeanDefinitionRegistry对象查看
//Spring容器中注册了哪些bean,也可以通过BeanDefinitionRegistry对象向
//Spring容器中注册bean,移除bean,查看bean的定义,查看是否包含某个bean的定义
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
return property.contains("Windows");
}
}
  • LinuxCondition
package io.mykit.spring.plugins.register.condition;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; /**
* @author binghe
* @version 1.0.0
* @description Linux条件,判断操作系统是否是Linux
*/
public class LinuxCondition implements Condition {
/**
* ConditionContext:判断条件使用的上下文环境
* AnnotatedTypeMetadata:注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//判断是否是Linux系统
//1.获取到IOC容器使用的BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2.获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3.获取当前的环境信息
Environment environment = context.getEnvironment();
//4.获取bean定义的注册类,我们可以通过BeanDefinitionRegistry对象查看
//Spring容器中注册了哪些bean,也可以通过BeanDefinitionRegistry对象向
//Spring容器中注册bean,移除bean,查看bean的定义,查看是否包含某个bean的定义
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
return property.contains("linux");
}
}

接下来,我们就需要在PersonConfig2类中使用@Conditional注解添加条件了。添加注解后的方法如下所示。

@Conditional({WindowsCondition.class})
@Bean("binghe001")
public Person person01(){
return new Person("binghe001", 18);
} @Conditional({LinuxCondition.class})
@Bean("binghe002")
public Person person02(){
return new Person("binghe002", 20);
}

此时,我们再次运行SpringBeanTest类中的testAnnotationConfig6()方法,输出的结果信息如下所示。

Windows 10
person
binghe001
给容器中添加Person....
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18)}

可以看到,输出结果中不再含有名称为binghe002的bean了,说明程序中检测到当前操作系统为Windows10,没有向Spring容器中注册名称为binghe002的bean。

@Conditional注解也可以标注在类上,标注在类上含义为:满足当前条件,这个类中配置的所有bean注册才能生效,大家可以自行验证@Conditional注解标注在类上的情况

@Conditional的扩展注解

@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。

@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。

@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。

@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。

@ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。

@ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。

@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。

@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。

@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。

@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。

@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。

@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。

@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。

@ConditionalOnExpression:基于SpEL表达式的条件判断。

@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。

@ConditionalOnResource:当类路径下有指定的资源时触发实例化。

@ConditionalOnJndi:在JNDI存在的条件下触发实例化。

@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

@Conditional 与@Profile 的对比

Spring3.0 也有一些和@Conditional 相似的注解,它们是Spring SPEL 表达式和Spring Profiles 注解 Spring4.0的@Conditional 注解要比@Profile 注解更加高级。@Profile 注解用来加载应用程序的环境。@Profile注解仅限于根据预定义属性编写条件检查。 @Conditional注释则没有此限制。

Spring中的@Profile 和 @Conditional 注解用来检查"If…then…else"的语义。然而,Spring4 @Conditional是@Profile 注解的更通用法。

  • Spring 3中的 @Profile仅用于编写基于Environment变量的条件检查。 配置文件可用于基于环境加载应用程序配置。
  • Spring 4 @Conditional注解允许开发人员为条件检查定义用户定义的策略。 @Conditional可用于条件bean注册。

好了,咱们今天就聊到这儿吧!别忘了给个在看和转发,让更多的人看到,一起学习一起进步!!

项目工程源码已经提交到GitHub:https://github.com/sunshinelyz/spring-annotation

写在最后

如果觉得文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习Spring注解驱动开发。公众号回复“spring注解”关键字,领取Spring注解驱动开发核心知识图,让Spring注解驱动开发不再迷茫。

参考:

https://www.cnblogs.com/cxuanBlog/p/10960575.html

【String注解驱动开发】如何按照条件向Spring容器中注册bean?这次我懂了!!的更多相关文章

  1. 【String注解驱动开发】面试官让我说说:如何使用FactoryBean向Spring容器中注册bean?

    写在前面 在前面的文章中,我们知道可以通过多种方式向Spring容器中注册bean.可以使用@Configuration结合@Bean向Spring容器中注册bean:可以按照条件向Spring容器中 ...

  2. 【Spring注解驱动开发】在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean

    写在前面 在前面的文章中,我们学习了如何使用@Import注解向Spring容器中导入bean,可以使用@Import注解快速向容器中导入bean,小伙伴们可以参见<[Spring注解驱动开发] ...

  3. 【String注解驱动开发】困扰了我很久的AOP嵌套调用终于解决了!

    写在前面 最近在分析Spring源码时,在同一个类中写了嵌套的AOP方法,测试时出现:Spring AOP在同一个类里自身方法相互调用时无法拦截.哎,怎么办?还能怎么办呢?继续分析Spring源码,解 ...

  4. 【String注解驱动开发】你了解@PostConstruct注解和@PreDestroy注解吗?

    写在前面 在之前的文章中,我们介绍了如何使用@Bean注解指定初始化和销毁的方法,小伙伴们可以参见<[Spring注解驱动开发]如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!& ...

  5. Spring注解驱动开发03(按照条件注册bean)

    按照条件注册bean 使用@Conditional注解来控制bean的注册 使用步骤 先实现Condition接口,条件写在matches方法里 注意事项:Condition接口是org.spring ...

  6. 【Spring注解驱动开发】使用@Import注解给容器中快速导入一个组件

    写在前面 我们可以将一些bean组件交由Spring管理,并且Spring支持单实例bean和多实例bean.我们自己写的类,可以通过包扫描+标注注解(@Controller.@Servcie.@Re ...

  7. 【Spring注解驱动开发】如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!

    写在前面 在[String注解驱动开发专题]中,前面的文章我们主要讲了有关于如何向Spring容器中注册bean的知识,大家可以到[String注解驱动开发专题]中系统学习.接下来,我们继续肝Spri ...

  8. java框架之Spring(5)-注解驱动开发

    准备 1.使用 maven 创建一个 java 项目,依赖如下: <dependency> <groupId>org.springframework</groupId&g ...

  9. 【Spring注解驱动开发】关于BeanPostProcessor后置处理器,你了解多少?

    写在前面 有些小伙伴问我,学习Spring是不是不用学习到这么细节的程度啊?感觉这些细节的部分在实际工作中使用不到啊,我到底需不需要学习到这么细节的程度呢?我的答案是:有必要学习到这么细节的程度,而且 ...

随机推荐

  1. 爬虫之requests的请求与响应

    requests是基于urllib3的一个用于发起http请求的库(中文文档)数据采集流程: 指定url>> 基于 requests模块发起请求>> 获取响应中的数据>& ...

  2. etcd分布式锁及事务

    前言 分布式锁是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互 ...

  3. Kubernetes学习笔记(四):服务

    服务介绍 服务是一种为一组相同功能的pod提供单一不变接入点的资源.当服务存在时,他的IP和端口不会改变.客户端通过IP和端口建立连接,这些连接会被路由到任何一个pod上.如此,客户端不需要知道每个单 ...

  4. Kubernetes学习笔记(八):Deployment--声明式的升级应用

    概述 本文核心问题是:如何升级应用. 对于Pod的更新有两种策略: 一是删除全部旧Pod之后再创建新Pod.好处是,同一时间只会有一个版本的应用存在:缺点是,应用有一段时间不可用. 二是先创建新Pod ...

  5. 使用cxfreeze打包成exe文件

    旧版本下载链接地址python3.4以下的:https://www.lfd.uci.edu/~gohlke/pythonlibs/#cx_freeze 最新版本python3.5以上直接使用  pip ...

  6. node进程间通信

    作为一名合格的程序猿/媛,对于进程.线程还是有必要了解一点的,本文将从下面几个方向进行梳理,尽量做到知其然并知其所以然: 进程和线程的概念和关系 进程演进 进程间通信 理解底层基础,助力上层应用 进程 ...

  7. strlen 老瓶装新酒

    前言 - strlen 概述 无意间扫到 glibc strlen.c 中代码, 久久不能忘怀. 在一无所知的编程生涯中又记起点点滴滴: 编程可不是儿戏 ❀, 有些难, 也有些不舍. 随轨迹一同重温, ...

  8. JavaScript几种继承方式的总结

    1.原型链继承 直接将子类型的原型指向父类型的实例,即"子类型.prototype = new 父类型();",实现方法如下: //父类构造函数 function father(n ...

  9. Spring IoC componet-scan 节点解析详解

    前言 我们在了解 Spring 容器的扩展功能 (ApplicationContext) 之前,先介绍下 context:componet-scan 标签的解析过程,其作用很大是注解能生效的关键所在. ...

  10. php砍价算法、随机红包金额算法

    /** * 砍价算法-生成砍价金额 * * @param int $people 砍价人数或次数 * @param int $amount 砍价总额 单位元 * @param int $min 最低砍 ...