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.
     * 

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 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 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 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 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 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值是否被激活。

你可能感兴趣的:(Spring系列)