文章目录
SpringBoot自动装配系列文章 |
---|
@EnableXXX注解+@Import轻松实现SpringBoot的模块装配 |
带你拿捏SpringBoot自动装配的核心技术?模块装配(@EnableXXX注解+@Import)+ 条件装配(@ConditionalXXX) |
深入探究Spring Boot自动配置原理及SPI机制:实现灵活的插件化开发 |
之前我们这篇文章@EnableXXX注解+@Import轻松实现SpringBoot的模块装配 提到了 SpringBoot自动装配的核心技术就是模块装配 + 条件装配!!!
在这篇文章我们完整的学习了模块装配的核心使用方法,通过模块装配,咱可以通过一个注解,一次性导入指定场景中需要的组件和配置。那么只靠模块装配的内容,就可以把这些装配都考虑到位吗?
只要配置类中声明了 @Bean
注解的方法,那这个方法的返回值就一定会被注册到 IOC 容器成为一个 Bean 。
所以,有没有办法解决这个问题呢?当然是有,总共有两种方式:Profile和@ConditionalXXX
先来学习第一种方式:Profile 。
Profile
通过Profile可以实现一套代码在不同环境启用不同的配置和功能。
@Profile
注解可以标注在组件上,当一个配置属性(并不是文件)激活时,它才会起作用,而激活这个属性的方式有很多种(启动参数、环境变量、web.xml
配置等)。
profile 提供了一种可以理解成“基于环境的配置”:根据当前项目的运行时环境不同,可以动态的注册当前运行环境匹配的组件。
例如,我们分别定义开发、测试和生产这3个环境:
- dev
- test
- production
创建某个Bean时,Spring容器可以根据注解@Profile
来决定是否创建。我们这里还是拿上一章节的例子导入配置类中的LogBeanConfiguration
@Configuration
public class LogBeanConfiguration {
@Bean
@Profile("!dev")
public MyLog myLog() {
return new MyLog();
}
@Bean
public LogUtil logUtil() {
return new LogUtil();
}
}
如果当前的Profile设置为test
和production
,则Spring容器才会调用myLog()
创建MyLog
类,而如果是dev
环境,则这个类不会被创建
激活指定配置文件
主配置文件中指定激活的profile
spring:
profiles:
active: dev # 指定激活哪个配置文件
命令行激活
在linux生产环境中直接使用命令行启动项目,启动的同时可以指定激活的profile:
java -jar --spring.profiles.active=dev my-spring-boot-app.1.0.0.jar
设置虚拟机参数激活
在运行程序时,加上JVM参数-Dspring.profiles.active=test
就可以指定以test
环境启动。
命令行指定的方式和虚拟机参数设置的方式指定,都可以在IDEA的运行设置中进行配置,如下图:
profile控制不到的地方
使用Profile能根据不同的Profile进行条件装配,但是Profile控制比较糙, profile 控制的是整个项目的运行环境,无法根据单个 Bean 的因素决定是否装配。也是因为这个问题,出现了第二种条件装配的方式:@Conditional
注解。
Spring原生的条件装配注解@Conditional
@Conditional接口讲解
要使用@Conditional
注解,必须先了解一下Conditiona
接口,它与@Conditional
注解配合使用,通过源码我们也可以看出,使用@Conditional
注解必须要指定实现Conditiona
接口的class。
@Conditional
注解可以指定匹配条件,而被@Conditional
注解标注的 组件类 / 配置类 / 组件工厂方法 必须满足@Conditional
中指定的所有条件,才会被创建 / 解析。@Conditional
是在 SpringFramework 4.0 版本正式推出的,它可以让 Bean 的装载基于一些指定的条件,换句话说,被标注@Conditional
注解的 Bean 要注册到 IOC 容器时,必须全部满足@Conditional
上指定的所有条件才可以。
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
在Conditiona
接口中,只定义了一个方法matches
,spring在注册组件时,也正是根据此方法的返回值TRUE/FALSE
来决定是否将组件注册到spring容器中
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context 条件判断的上下文环境
* @param metadata 正在检查的类或方法的注解元数据
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
在matches
中我们可以获取到ConditionContext
接口,根据此接口对象可以获取BeanDefinitionRegistry
,ConfigurableListableBeanFactory
等重要对象信息,根据这些对象就可以获取和检查spring容器初始化时所包含的所有信息,再结合业务需求,就可以实现组件注册时的自定义条件判断。
案例讲解
LogBeanConfiguration
我们将之前的@Profile("!dev")
改为@Conditional(OnMyLogCondition.class)
@Configuration
public class LogBeanConfiguration {
@Bean
@Conditional(OnMyLogCondition.class)
public MyLog myLog() {
return new MyLog();
}
@Bean
public LogUtil logUtil() {
return new LogUtil();
}
}
OnMyLogCondition
里面从ConditionContext
获取ConfigurableListableBeanFactory
,从而去判断需要有LogUtil Bean定义信息才会去创建MyLog类
public class OnMyLogCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getBeanFactory().containsBeanDefinition(LogUtil.class.getName());
}
}
Spring Boot封装的条件装配注解@ConditionalXXX
Spring本身提供了条件装配@Conditional
,但是要自己编写比较复杂的Condition
来做判断,比较麻烦。Spring Boot则为我们准备好了几个非常有用的条件:
- @ConditionalOnProperty:如果有指定的配置,条件生效;
- @ConditionalOnBean:如果有指定的Bean,条件生效;
- @ConditionalOnMissingBean:如果没有指定的Bean,条件生效;
- @ConditionalOnMissingClass:如果没有指定的Class,条件生效;
- @ConditionalOnWebApplication:在Web环境中条件生效;
- @ConditionalOnExpression:根据表达式判断条件是否生效。
我们以比较常用的@ConditionalOnBean
为例,之前@Conditional(OnMyLogCondition.class)
是使用@Conditional 直接传入Condition接口的实现类进行判断是否要创建MyLog,现在使用@ConditionalOnBean
可以直接传入LogUtil.class
,它会帮我们实现这个判断!
@Configuration
public class LogBeanConfiguration {
@Bean
@ConditionalOnBean(LogUtil.class)
//@Conditional(OnMyLogCondition.class) 原来的是传入自定义Conditional实现类
public MyLog myLog() {
return new MyLog();
}
@Bean
public LogUtil logUtil() {
return new LogUtil();
}
}
自己实现ConditionalOnBean
以刚刚的ConditionalOnBean为例,我们自己动手造轮子实现一下,这个注解,看下他是怎么实现的呢!
首先自定义注解ConditionalOnBean,定义有默认的Class 数组类型的value以及String数组类型的beanNames
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(OnMyBeanCondition.class)
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] beanNames() default {};
}
接着就是实现这个OnMyBeanCondition类
public class OnMyBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnBean.class.getName());
// 匹配类型
Class<?>[] classes = (Class<?>[]) attributes.get("value");
for (Class<?> clazz : classes) {
if (!context.getBeanFactory().containsBeanDefinition(clazz.getName())) {
return false;
}
}
// 匹配beanName
String[] beanNames = (String[]) attributes.get("beanNames");
for (String beanName : beanNames) {
if (!context.getBeanFactory().containsBeanDefinition(beanName)) {
return false;
}
}
return true;
}
}
使用的时候就只需要传入对应的.class即可,原来的是直接传入Condition接口的实现类,现在这个ConditionalOnBean注解相当于封装了一层
@Configuration
public class LogBeanConfiguration {
@Bean
@ConditionalOnBean(LogUtil.class)
//@Conditional(OnMyLogCondition.class) 原来的是传入自定义Conditional实现类
public MyLog myLog() {
return new MyLog();
}
@Bean
public LogUtil logUtil() {
return new LogUtil();
}
}
发现是不是其实springboot帮我们做的东西也不难,只是封装套了一层
SpringBoot 源码案例注解
@ConditionalOnBean({DataSource.class})
@ConditionalOnClass({JpaRepository.class})
@ConditionalOnMissingBean({JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class})
@ConditionalOnProperty(
prefix = "spring.data.jpa.repositories",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
public class JpaRepositoriesAutoConfiguration {
public JpaRepositoriesAutoConfiguration() {
}
}
这段代码是一个基于Spring框架的Java代码片段,它定义了一个名为JpaRepositoriesAutoConfiguration
的类,并使用了多个条件注解来控制这个类的自动配置。
@ConditionalOnBean({DataSource.class})
注解表示只有在Spring容器中存在DataSource
的Bean时,才会启用这个自动配置类。DataSource
通常用于配置数据库连接。
@ConditionalOnClass({JpaRepository.class})
注解表示只有在类路径中存在JpaRepository
类时,才会启用这个自动配置类。JpaRepository
是Spring Data JPA提供的接口,用于简化数据库访问的操作。
@ConditionalOnMissingBean({JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class})
注解表示只有在容器中不存在JpaRepositoryFactoryBean
和JpaRepositoryConfigExtension
这两个Bean时,才会启用这个自动配置类。
@ConditionalOnProperty
注解表示只有当指定的属性满足特定条件时,才会启用这个自动配置类。在这里,属性spring.data.jpa.repositories.enabled
的值必须为true
,或者如果该属性不存在时,也会启用这个自动配置类。
总结起来,这段代码定义了一个自动配置类JpaRepositoriesAutoConfiguration
,它会根据一系列条件来判断是否要应用该自动配置。这些条件包括是否存在DataSource
的Bean、是否存在JpaRepository
类、是否缺少JpaRepositoryFactoryBean
和JpaRepositoryConfigExtension
这两个Bean,以及是否满足指定的属性条件。根据条件的不同,这个自动配置类可能会在Spring容器中自动配置一些与JPA相关的Bean。