В предыдущем посте мы видели, что мы можем использовать функцию конфигурации Java в Spring для загрузки bean-компонентов в наше приложение Grails. Мы можем использовать @Profile
аннотацию для условной загрузки бинов на основе текущего активного профиля Spring. Например, мы можем использовать системное свойство Java spring.profiles.active
при запуске приложения Grails, чтобы сделать профиль активным. Но разве не было бы неплохо, если бы мы могли использовать параметр среды Grails для условной загрузки bean-компонентов из конфигурации Java?
Оказывается, это не так сложно достичь. Мы должны реализовать matches
метод из Condition
интерфейса из org.springframework.context.annotation
пакета. Затем мы создаем новый интерфейс аннотации, где мы делегируем в основном нашему классу реализации.
Начнем с написания реализации Condition
интерфейса:
// File: src/groovy/com/mrhaki/grails/context/annotation/GrailsEnvCondition.groovy package com.mrhaki.grails.context.annotation import grails.util.Environment import org.springframework.context.annotation.Condition import org.springframework.context.annotation.ConditionContext import org.springframework.core.type.AnnotatedTypeMetadata import org.springframework.util.Assert import org.springframework.util.MultiValueMap /** * <p>{@link Condition} that matches based on the value of * a {@link GrailsEnv @GrailsEnv} annotation.</p> * * <p>The value of the current Grails environment is compared to given * Grails environments set via the {@link GrailsEnv @GrailsEnv} annotation.</p> * * @author Hubert A. Klein Ikkink aka mrhaki * @see GrailsEnv */ class GrailsEnvCondition implements Condition { @Override public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) { final MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(GrailsEnv.class.name) if (attributes != null) { final List<String> value = attributes.get('value') return value ? value.any { acceptsEnvironments(it) } : false } return true } protected boolean acceptsEnvironments(final String... environments) { Assert.notEmpty(environments, "Must specify at least one environment") for (environment in environments) { if (isNegateEnvironment(environment)) { if (!isProfileActive(environment[1..-1])) { return true } } else if (isProfileActive(environment)) { return true } } return false } private boolean isNegateEnvironment(final String environment) { environment != null && environment.length() > 0 && environment[0] == '!' } protected boolean isProfileActive(final String profile) { validateEnvironment(profile); final String currentEnvironment = Environment.current.name profile == currentEnvironment } protected void validateEnvironment(final String environment) { if (!environment || environment.allWhitespace) { throw new IllegalArgumentException("Invalid environment [$environment]: must contain text"); } if (environment[0] == '!') { throw new IllegalArgumentException("Invalid environment [$environment]: must not begin with ! operator"); } } }
Далее мы создаем новую аннотацию @GrailsEnv
. Аннотация принимает String
значение или массив String
значений с именами сред Grails, для которых bean-компонент должен быть зарегистрирован или исключен из:
// File: src/java/com/mrhaki/grails/context/annotation/GrailsEnv.java package com.mrhaki.grails.context.annotation; import org.springframework.context.annotation.Conditional; import java.lang.annotation.*; /** * <p>Indicates that a component is eligible for registration when one * or more {@linkplain #value specified Grails environments} are active.</p> * * <p>The Grails environment can be set via the Java system property * <em>grails.env</em>.</p> * * <p>The {@code @GrailsEnv} annotation may be used in any of the following ways: * <ul> * <li>as a type-level annotation on any class directly or indirectly annotated with * {@code @Component}, * including {@link org.springframework.context.annotation.Configuration @Configuration} classes</li> * <li>as a meta-annotation, for the purpose of composing custom stereotype annotations</li> * <li>as a method-level annotation on * any {@link org.springframework.context.annotation.Bean @Bean} method</li> * </ul> * * <p>If a {@code @Configuration} class is marked with {@code @GrailEnv}, * all of the {@code @Bean} methods and * {@link org.springframework.context.annotation.Import @Import} annotations associated with that class * will be bypassed unless one or more of the specified Grails environments are active.</p> * * <p>If a given Grails environment is prefixed with the NOT operator ({@code !}), * the annotated beans or components will be registered if the Grails environment * is <em>not</em> active. e.g., for {@code @GrailsEnv("!production")}, registration will occur * if Grails environment 'production' is not active.</p> * * <p>If the {@code @GrailsEnv} annotation is omitted, registration will occur, regardless * of which Grails environment is active. * * @author Hubert A. Klein Ikkink aka mrhaki * @see org.springframework.context.annotation.Profile * @see com.mrhaki.grails.annotation.context.GrailsEnvCondition */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Conditional(GrailsEnvCondition.class) public @interface GrailsEnv { /** * Annotation attribute value to set Grails environments. * Use an array of String value to determine Grails environments * or use a single String value (is automatically put in array). * Environment maybe prefixed with {@code !} to register component when environment * is NOT active. */ String[] value(); }
Мы можем использовать нашу новую аннотацию в классе конфигурации Spring Java. В следующем примере показаны разные значения для @GrailsEnv
аннотации. Аннотация применяется к методам в примере кода, но также может применяться к классу.
// File: src/groovy/com/mrhaki/grails/configuration/BeansConfiguration.groovy package com.mrhaki.grails.configuration import com.mrhaki.grails.context.annotation.GrailsEnv import org.springframework.context.annotation.* @Configuration class BeansConfiguration { // Load for Grails environments // development or test @Bean @GrailsEnv(['development', 'test']) Sample sampleBean() { new Sample('sampleBean') } // Load for every Grails environment NOT // being production. @Bean @GrailsEnv('!production') Sample sample() { new Sample('sample') } // Load for custom environment name qa. @Bean @GrailsEnv('qa') Sample sampleQA() { new Sample('QA') } }
Мы также можем использовать аннотацию для классов, аннотированных @Component
аннотацией:
// File: src/groovy/com/mrhaki/grails/Person.groovy package import com.mrhaki.grails.context.annotation.GrailsEnv import org.springframework.stereotype.Component @Component @GrailsEnv('development') class Person { String name String email }
Реализация для GrailsEnv
и GrailsEnvCondition
основана на существующих классах Spring Profile
и ProfileCondition
.
Код, написанный с помощью Grails 2.4.2.