Spring高级装配之条件化装配

根据环境变量装配Bean 我们将了怎么使用@Profile来装配对应的Bean,现在有时候,我们需要更细的条件来判断是否要装配某个Bean,比如在应用的类路径下有特定的库,某个环境变量是我们要的值等等,该怎么做呢?

@Conditional

Spring提供给了我们一个注解@Conditional来做到这个事情,我们举个例子,例子的逻辑是如果环境变量里有magic属性,我们才会创建MagicBean。
创建一个Bean的Class:

public class MagicBean {
}

创建一个Conditional类,用来控制这个Bean的创建:

public class MagicExistsCondition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    return env.containsProperty("magic");
  }
}

创建ConfigJava:

@Configuration
public class MagicConfig {
  @Bean
  @Conditional(MagicExistsCondition.class)
  public MagicBean magicBean() {
    return new MagicBean();
  }
}

写一个测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=MagicConfig.class)
public class MagicExistsTest {

  @Autowired
  private ApplicationContext context;
  @Test
  public void shouldNotBeNull() {
    assertTrue(context.containsBean("magicBean"));
  }
}

如果我们环境变量里有magic这个值,测试就会通过,如果没有,测试代码会抛出java.lang.AssertionError。

到底是怎么做到这个呢?通过上面的例子,我们看到@Conditional注解有个Class参数,这个参数要需要一个Condition接口类,这个接口有一个需要实现的方法:

    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

ConditionContext

Condition接口的参数里需要的第一个参数是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();

通过它我们可以做到以下几点:
1. 借助getRegistry()方法返回的BeanDefinitionRegistry来检测Bean定义。
2. 借助getBeanFactory()方法返回的ConfigurableListableBeanFactory获取到某个Bean,来检查Bean是否存在,甚至可以检查Bean的属性等。
3. 借助getEnvironment()方法返回的Environment来检查环境变量以及它的值。
4. 借助方法getResourceLoader()返回的ResourceLoader检查当前Spring加载的资源。
5. 借助方法getClassLoader返回的ClassLoader加载并检查类是否存在。

AnnotatedTypeMetadata

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

    /**
     * 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()方法我们可以判断注解的方法是否还有其他注解,并且可以借助其他方法检查对应注解的是否有某些属性。

回到Profile

回到Profile,我们打开Profile的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
    String[] value();
}

Profile居然就是基于Conditional做的一个注解,并且引用了ProfileCondition作为@Conditional的判断类,我们打开这个类:

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;
    }

}

matches()方法的内部逻辑就是通过AnnotatedTypeMetadata获取到@Profile注解的value值,通过acceptsProfiles方法检测Profile的Value值是否被激活。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值