在本文中,我想描述一个非常有用且经常使用的条件注释和条件接口。
Spring上下文是各种bean的巨大容器,包括spring本身和定制bean。 您始终想为此仓库动物园提供灵活的管理工具。 为此仅创建了@Conditional批注 。
管理Spring上下文的最常见方法是通过配置文件。 它们使您可以快速轻松地控制Bean的创建。 但是有时可能需要进行更精细的调整。
例如,在测试过程中会出现一个问题:在开发人员的机器上进行单元测试需要X型容器才能完成工作,在构建服务器上运行相同的测试时,需要Y-bin,而在生产环境中则需要Z-bin。决定。 就像在不同步的团队中工作时一样,有人没有时间在截止日期之前完成其修订,因此您的功能已准备就绪。 有必要适应这些条件并改变行为。 也就是说,增加了无需重新编译即可更改应用程序上下文的功能,例如,仅更改配置中的一个参数。
请更详细地考虑此注释。 在源代码的每个bin上方,我们可以添加@Conditional ,spring在创建此批注时将自动检查此批注中指定的条件。
在官方文档中,声明如下:
@Target(value={TYPE,METHOD}) @Retention(value=RUNTIME) @Documented public @interface Conditional
同时,您需要向其中转移一组条件:
Class<? extends Condition>[]
条件是其中包含方法的功能接口
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
让我们通过一个生动的例子来检查它在实践中是如何工作的。 我们的应用程序具有以soap / rest-service形式和JMS形式的接口。 但是管理员没有时间及时准备适当的基础结构-我们无法使用JMS。
在我们的项目中,JMS有一些Java配置:
@Configuration public class JmsConfig { ... }
Spring找到此配置并开始对其进行初始化。 接下来,拉出所有其他从属bean,例如,从队列中读取。 要禁用此配置的创建,我们将使用条件导数注释-ConditioanalOnProperty
@ConditionalOnProperty( value="project.mq.enabled", matchIfMissing = false) @Configuration public class JmsConfig { ... }
在这里,我们告诉上下文构建器,只有在设置文件中存在project.mq.enabled常量的正值时,我们才创建此配置。
现在,让我们继续从属bean并用ConditioanalOnBean批注对其进行标记,这将防止弹簧创建依赖于我们的配置的bean。
@ConditionalOnBean(JmsConfig.class) @Component public class JmsConsumer { ... }
因此,使用单个参数,我们可以禁用不需要的应用程序组件,然后通过更改配置将它们添加到上下文中。
与该框架一起,存在大量现成的注释,它们满足了开发人员99%的需求(将在本文稍后进行介绍)。 但是,如果您需要处理某些特定情况该怎么办。 为此,您可以在Spring中添加自己的自定义逻辑。
假设我们有一些bean- SuperDBLogger ,只有在我们的任何容器上都有@Loggable批注时,才要创建。 它在代码中的外观:
@Component @ConditionalOnLoggableAnnotation public class SuperDBLogger …
考虑@ConditionalOnLoggableAnnotation批注的工作方式 :
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(OnLoggableAnnotation.class) public @interface ConditionalOnLoggableAnnotation { }
我们不需要任何其他参数,现在让我们继续进行逻辑本身-OnLoggableAnnotation类的内容。 在其中,我们重新定义了matchs方法,在该方法中,我们实现了对程序包中标记的bean的搜索。
public class OnLoggableAnnotation implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassPathScanner scanner = new ClassPathScanner(); scanner.addIncludeFilter(new AnnotationTypeFilter(Loggable.class)); Set<BeanDefinition> bd = scanner.findInPackage("ru.habr.mybeans"); if (!bd.isEmpty()) return true; return false; } }
因此,我们根据Spring现在创建的规则创建了SuperDBLogger 。 对于SpringBoot的奉献者,框架的创建者创建了SpringBootCondition ,它是Condition的后继者。 它与重新定义的方法的签名不同:
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
也就是说,除了答案,我们是否需要一个垃圾箱,您可以添加一条消息,然后可以在spring日志中看到该消息,了解为什么创建垃圾箱或不创建垃圾箱。
为了创建更复杂的条件,可以组合各种条件;这些机制提供了AnyNestedCondition,AllNestedConditions和NoneNestedConditions类 。 假设我们要创建两个条件,以便在执行其中一个条件时创建我们的bean。 为此,请创建您自己的类并从AnyNestedCondition继承它。
public class AnnotationAndPropertyCondition extends AnyNestedCondition { public AnnotationAndPropertyCondition() { super(REGISTER_BEAN); } @ConditionalOnProperty(value = "db.superLogger") static class Condition1 {} @ConditionalOnLoggableAnnotation static class Condition2 {} }
该类无需额外标记任何注释; spring本身将找到它并正确处理它。 用户只需要指出在配置的哪个阶段满足条件: ConfigurationPhase .REGISTER_BEAN-创建常规bean时, ConfigurationPhase .PARSE_CONFIGURATION-使用配置时(即,标有@Configuration批注的bin)。
同样,对于类AllNestedConditions和NoneNestedConditions ,第一个监视所有条件,第二个确保不满足任何条件。
另外,为了检查几个条件,您可以将几个带有条件的类传递给@Conditional 。 例如, @Conditional({OnLoggableAnnotation.class,AnnotationAndPropertyCondition.class}) 。 两者都必须返回true,以便满足条件并创建Bean。
正如我上面提到的,弹簧已经有许多现成的解决方案,如下表所示。
所有这些都可以一起应用于一个bean的定义。
因此, @ Conditional是一个功能非常强大的上下文配置工具,使应用程序更加灵活。 但是值得考虑的一个事实是,您需要仔细使用此批注,因为上下文的行为变得不像使用配置文件时那样明显-使用大量已配置的bin,您很快就会感到困惑。 建议在您的项目中仔细记录并记录其应用程序,否则代码支持会造成困难。