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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介:

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

写在前面
当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

原文地址https://www.cnblogs.com/binghe001/p/13084108.html

相关文章
|
1天前
|
缓存 Java 数据库
SpringBoot缓存注解使用
Spring Boot 提供了一套方便的缓存注解,用于简化缓存管理。通过 `@Cacheable`、`@CachePut`、`@CacheEvict` 和 `@Caching` 等注解,开发者可以轻松地实现方法级别的缓存操作,从而提升应用的性能和响应速度。合理使用这些注解可以大大减少数据库的访问频率,优化系统性能。
109 79
|
2月前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
175 73
|
2月前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
54 21
|
2月前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
2月前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
275 77
|
23天前
|
Ubuntu NoSQL Linux
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
108 6
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
|
2月前
|
监控 Docker 容器
在Docker容器中运行打包好的应用程序
在Docker容器中运行打包好的应用程序
|
2月前
|
Ubuntu Linux 开发工具
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成标准化单元(容器),确保在任何支持 Docker 的操作系统上一致运行。容器共享主机内核,提供轻量级、高效的执行环境。本文介绍如何在 Ubuntu 上安装 Docker,并通过简单步骤验证安装成功。后续文章将探讨使用 Docker 部署开源项目。优雅草央千澈 源、安装 Docker 包、验证安装 - 适用场景:开发、测试、生产环境 通过以上步骤,您可以在 Ubuntu 系统上成功安装并运行 Docker,为后续的应用部署打下基础。
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
|
2月前
|
存储 Kubernetes 开发者
容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档
Docker 是一种开源的应用容器引擎,允许开发者将应用程序及其依赖打包成可移植的镜像,并在任何支持 Docker 的平台上运行。其核心概念包括镜像、容器和仓库。镜像是只读的文件系统,容器是镜像的运行实例,仓库用于存储和分发镜像。Kubernetes(k8s)则是容器集群管理系统,提供自动化部署、扩展和维护等功能,支持服务发现、负载均衡、自动伸缩等特性。两者结合使用,可以实现高效的容器化应用管理和运维。Docker 主要用于单主机上的容器管理,而 Kubernetes 则专注于跨多主机的容器编排与调度。尽管 k8s 逐渐减少了对 Docker 作为容器运行时的支持,但 Doc
154 5
容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档
|
23天前
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
96 11