实战Spring高级装配中条件化的bean

2021-04-25 14:50发布

条件化的 bean,当某个条件触发时创建的 bean。


什么是条件化的 bean

比如:


当应用中包含了特定的库,才创建。

当某个 非当前 bean 声明后,才创建。

当某个 特定的环境变量设置后,才创建。

Spring 4 之前很难实现,Spring 4引入了一个新的@Conditional注解。


条件化地配置bean

  @Bean

  @Conditional(MagicExistsCondition.class)

  public MagicBean magicBean() {

    return new MagicBean();

  }

@Conditional 注解就是指定了条件类,就是 MagicExistsCondition。@Conditional将会 通过Condition接口进行条件对比:


Condition 接口是 spring4 源码中的类:


public interface Condition {

 

/**

* Determine if the condition matches.

* @param context the condition context

* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}

* or {@link org.springframework.core.type.MethodMetadata method} being checked.

* @return {@code true} if the condition matches and the component can be registered

* or {@code false} to veto registration.

*/

boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

 

}

可以看出来,这个接口实现起来很简单直接,只需提 供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返 回false,将不会创建这些bean。


这里有两个参数,一个是环境条件,一个是注解类型。


在Condition中检查是否存在magic属性


我们需要检查 magic 属性是否存在,就要实现刚才那个接口,Condition,调用 matches 方法来检查:


public class MagicExistsCondition implements Condition {

 

  @Override

  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

    Environment env = context.getEnvironment();

    return env.containsProperty("magic");

  }

  

}

ConditionContext 可以获取当前的环境,调用 getEnvironment 方法,返回当前应用程序正在运行的{@link环境},如果没有可用的环境,则返回{@code null}。如果有就会返回一个类 Enviroment,其中这个类 containsProperty 可以用来比较 需要的 当前 环境中是否存在 magic 这个环境。


如果条件满足,也就意味着环境符合,所有@Conditional注解上引用MagicExistsCondition的 bean都会被创建。


ConditionContext 接口

 

package org.springframework.context.annotation;

 

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.core.env.Environment;

import org.springframework.core.io.ResourceLoader;

 

/**

 * Context information for use by {@link Condition}s.

 *

 * @author Phillip Webb

 * @since 4.0

 */

public interface ConditionContext {

 

/**

* Return the {@link BeanDefinitionRegistry} that will hold the bean definition

* should the condition match or {@code null} if the registry is not available.

* @return the registry or {@code null}

*/

BeanDefinitionRegistry getRegistry();

 

/**

* Return the {@link ConfigurableListableBeanFactory} that will hold the bean

* definition should the condition match or {@code null} if the bean factory

* is not available.

* @return the bean factory or {@code null}

*/

ConfigurableListableBeanFactory getBeanFactory();

 

/**

* Return the {@link Environment} for which the current application is running

* or {@code null} if no environment is available.

* @return the environment or {@code null}

*/

Environment getEnvironment();

 

/**

* Return the {@link ResourceLoader} currently being used or {@code null}

* if the resource loader cannot be obtained.

* @return a resource loader or {@code null}

*/

ResourceLoader getResourceLoader();

 

/**

* Return the {@link ClassLoader} that should be used to load additional

* classes or {@code null} if the default classloader should be used.

* @return the class loader or {@code null}

*/

ClassLoader getClassLoader();

 

}

以上接口根据顺序分别含义为:


借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;

借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;

借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;

读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;

借助getClassLoader()返回的ClassLoader加载并检查类是否存在。

都不存在的话,返回的是 null。


AnnotatedTypeMetadata 接口

能让我们检查带有@Bean 接口注解的方法上,还有什么其他的注解。


package org.springframework.core.type;

 

import java.util.Map;

 

import org.springframework.util.MultiValueMap;

 

/**

 * Defines access to the annotations of a specific type ({@link AnnotationMetadata class}

 * or {@link MethodMetadata method}), in a form that does not necessarily require the

 * class-loading.

 *

 * @author Juergen Hoeller

 * @author Mark Fisher

 * @author Mark Pollack

 * @author Chris Beams

 * @author Phillip Webb

 * @author Sam Brannen

 * @since 4.0

 * @see AnnotationMetadata

 * @see MethodMetadata

 */

public interface AnnotatedTypeMetadata {

 

/**

* Determine whether the underlying type has an annotation or

* meta-annotation of the given type defined.

* <p>If this method returns {@code true}, then

* {@link #getAnnotationAttributes} will return a non-null Map.

* @param annotationType the annotation type to look for

* @return whether a matching annotation is defined

*/

boolean isAnnotated(String annotationType);

 

/**

* Retrieve the attributes of the annotation of the given type,

* if any (i.e. if defined on the underlying class, as direct

* annotation or as meta-annotation).

* @param annotationType the annotation type to look for

* @return a Map of attributes, with the attribute name as key (e.g. "value")

* and the defined attribute value as Map value. This return value will be

* {@code null} if no matching annotation is defined.

*/

Map<String, Object> getAnnotationAttributes(String annotationType);

 

/**

* Retrieve the attributes of the annotation of the given type,

* if any (i.e. if defined on the underlying class, as direct

* annotation or as meta-annotation).

* @param annotationType the annotation type to look for

* @param classValuesAsString whether to convert class references to String

* class names for exposure as values in the returned Map, instead of Class

* references which might potentially have to be loaded first

* @return a Map of attributes, with the attribute name as key (e.g. "value")

* and the defined attribute value as Map value. This return value will be

* {@code null} if no matching annotation is defined.

*/

Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);

 

/**

* Retrieve all attributes of all annotations of the given type, if any (i.e. if

* defined on the underlying type ({@link AnnotationMetadata class} or

* {@link MethodMetadata method}), as direct annotation or as meta-annotation).

* @param annotationType the annotation type to look for

* @return a MultiMap of attributes, with the attribute name as key (e.g. "value")

* and a list of the defined attribute values as Map value. This return value will

* be {@code null} if no matching annotation is defined.

* @see #getAllAnnotationAttributes(String, boolean)

*/

MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);

 

/**

* Retrieve all attributes of all annotations of the given type, if any (i.e. if

* defined on the underlying type ({@link AnnotationMetadata class} or

* {@link MethodMetadata method}), as direct annotation or as meta-annotation).

* @param annotationType the annotation type to look for

* @param classValuesAsString  whether to convert class references to String

* @return a MultiMap of attributes, with the attribute name as key (e.g. "value")

* and a list of the defined attribute values as Map value. This return value will

* be {@code null} if no matching annotation is defined.

* @see #getAllAnnotationAttributes(String)

*/

MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);

 

}

isAnnotated()方法,能够判断带有@Bean注解的方法是不是还有其他特定的注解。


其他方法则是 根据不同参数 返回 不同数据接口的值。


@Profile注解

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE, ElementType.METHOD})

@Documented

@Conditional(ProfileCondition.class)

public @interface Profile {

String[] value();

}

从 Spring 4 开始,Profile 注解 进行了重构,使其基于@Conditional和Condition实现。


当前接口,也用了 @Conditional 注解,然后他的条件 类为 ProfileCondition,再看一下这个类,同样是继承了 Condition接口。


ProfileCondition检查某个bean profile是否可用


当前条件,基于 Profile 这个类 就行匹配,看下源码:


package org.springframework.context.annotation;

 

import org.springframework.core.type.AnnotatedTypeMetadata;

import org.springframework.util.MultiValueMap;

 

/**

 * {@link Condition} that matches based on the value of a {@link Profile @Profile}

 * annotation.

 *

 * @author Chris Beams

 * @author Phillip Webb

 * @author Juergen Hoeller

 * @since 4.0

 */

class ProfileCondition implements Condition {

 

@Override

public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

if (context.getEnvironment() != null) {

MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());

if (attrs != null) {

for (Object value : attrs.get("value")) {

if (context.getEnvironment().acceptsProfiles(((String[]) value))) {

return true;

}

}

return false;

}

}

return true;

}

 

}

我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性。借助该信息,它会明确地检查value属性,该属性包含了bean的profile名称。然后,它根据通过ConditionContext得到的Environment来检查[借助acceptsProfiles()方法]该profile是否处于激活状态。


————————————————

版权声明:本文为CSDN博主「Soinice」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/Soinice/article/details/115631186