禁用@Alternative 类
Disable @Alternative classes
在我的 Java EE 7 程序中,我想使用 @Alternative
根据上下文、生产或测试注入不同的实现。我所做的是在我的 beans.xml 文件中声明我的 class 用 @Alternative
注释。它工作得很好,我的替代 class 被注入到我想要的任何地方而不是默认的。但我不知道除了删除 beans.xml 文件中的声明之外,是否有办法跳过此行为并注入默认值 class。当应用程序被打包时,这是不可能的。如果我可以选择是使用默认的 classes 还是配置文件中的替代文件,例如在我的 WildFly 服务器的 standalone.xml 文件中,那就太好了。这可能吗?
恐怕无法使用普通的 @Alternative
注释来实现。请参阅以下几种您可以尝试的方法:
使用不同的 beans.xml
个文件
您可以考虑为每个环境准备不同的 beans.xml
个文件,然后根据 Maven profile.
等文件打包正确的文件
写下你自己的替代刻板印象
您可以定义自己的替代原型并使用 CDI 扩展管理注入。
NightSpawN 的 post 中提到了这种方法。我在 WildFly 10 上对其进行了测试,它按预期工作。找到以下步骤:
用您的环境类型定义一个枚举:
public enum EnvironmentType {
DEVELOPMENT, TESTING, STAGING, PRODUCTION;
}
创建您自己的 @Alternative
刻板印象以保持 meta-information 对环境的看法:
@Stereotype
@Alternative
@Target(TYPE)
@Retention(RUNTIME)
public @interface EnvironmentAlternative {
EnvironmentType[] value();
}
并在beans.xml
中声明替代构造型:
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
<stereotype>com.example.EnvironmentAlternative</stereotype>
</alternatives>
</beans>
出于示例目的,让我们定义一个示例服务:
public interface GreetingService {
String sayGreeting();
}
定义默认实现:
@Default
public class DefaultGreetingService implements GreetingService {
@Override
public String sayGreeting() {
return "Hey!";
}
}
还使用 @EnvironmentAlternative
构造型定义了一些替代实现:
@EnvironmentAlternative(DEVELOPMENT)
public class DevelopmentGreetingService implements GreetingService {
@Override
public String sayGreeting() {
return "Hey from a development environment!";
}
}
@EnvironmentAlternative(PRODUCTION)
public class ProductionGreetingService implements GreetingService {
@Override
public String sayGreeting() {
return "Hey from a production environment!";
}
}
@EnvironmentAlternative
注解还支持具有多种环境类型的数组:
@EnvironmentAlternative({ TESTING, STAGING })
这里是奇迹发生的地方!
创建 CDI Extension
以观察 CDI 生命周期事件。容器处理的每个注释类型都会调用 processAnotated()
方法,如果用 @EnvironmentAlternative
注释并且当前环境不在指定环境中,则会调用事件的 veto()
方法,防止进一步处理的类型:
public class EnvironmentAlternativesExtension implements Extension {
private EnvironmentType currentEnvironment = PRODUCTION;
public <T> void processAnotated(@Observes ProcessAnnotatedType<T> event) {
EnvironmentAlternative alternative =
event.getAnnotatedType().getJavaClass()
.getAnnotation(EnvironmentAlternative.class);
if (alternative != null && !containsCurrentEnvironment(alternative.value())) {
event.veto();
}
}
private boolean containsCurrentEnvironment(EnvironmentType[] environments) {
for (EnvironmentType environment : environments) {
if (environment == currentEnvironment) {
return true;
}
}
return false;
}
}
当找不到合适的替代方案时,将使用默认实现。
接下来,通过在 META-INF/services
文件夹下创建一个名为 javax.enterprise.inject.spi.Extension
的文件,将 CDI 扩展注册为服务提供者。文件的内容将只是扩展名的规范名称 class:
com.example.EnvironmentAlternativesExtension
最后注入并使用上面定义的服务:
@Inject
private GreetingService service;
String greeting = service.sayGreeting();
在实际应用程序中,您不会对 currentEnvironment
字段的值进行硬编码。例如,您可以使用系统 属性 来确定应用程序所在的环境 运行。
要在 standalone.xml
中设置系统 属性,请使用 <server>
标签下的 <system-properties>
标签:
<server xmlns="urn:jboss:domain:4.2">
...
<system-properties>
<property name="environment" value="PRODUCTION"/>
</system-properties>
...
</server>
然后使用下面的一段代码获取environemnt
变量的值并设置currentEnvironment
字段的值:
String environment = System.getProperty("environment");
currentEnvironment = EnvironmentType.valueOf(environment);
详细说明我的评论,您可以执行以下操作。
定义单个限定符
@Inherited
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER})
public @interface BeanSelector {
@NonBinding
private String environment;
}
定义注释文字
public class BeanSelectorImpl extends AnnotationLiteral<BeanSelector> implements BeanSelector {
private final String environment;
public BeanSelectorImpl(final String environment) {
this.environment = environment;
}
public String environment() {
return this.environment;
}
}
创建一个从环境中读取的生产者
@ApplicationScoped
public class BeanSelectorProducer {
@Any
@Inject
private Instance<MyBeanService> myBeanServices;
@Produces
@BeanSelector(environment = "")
public MyBeanService produceService() {
final String env = System.getProperty("env.property");
final BeanSelector beanSelector = new BeanSelectorImpl(env);
//You may wish to handle exceptions.
return myBeanServices.select(beanSelector).get();
}
}
此实现的不利方面是您的所有 bean 都将处于服务状态。
为每个环境定义不同 beans.xml 的另一种选择可能是更好的选择。
在我看来,最简单的解决方案是像某些评论中提到的那样创建一个 maven 配置文件。
在我的 pom.xml 文件中,我添加了资源过滤部分和配置文件:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
<profiles>
<profile>
<id>default</id>
<properties>
<MyBean></MyBean>
</properties>
</profile>
<profile>
<id>alternative</id>
<properties>
<MyBean><![CDATA[<class>com.MyBean</class>]]></MyBean>
</properties>
</profile>
</profiles>
beans.xml 文件现在是这样的:
<beans ...>
<alternatives>
${MyBean}
</alternatives>
</beans>
最后我只需要使用有用的配置文件执行 maven 命令:mvn package -P alternative
.
在我的 Java EE 7 程序中,我想使用 @Alternative
根据上下文、生产或测试注入不同的实现。我所做的是在我的 beans.xml 文件中声明我的 class 用 @Alternative
注释。它工作得很好,我的替代 class 被注入到我想要的任何地方而不是默认的。但我不知道除了删除 beans.xml 文件中的声明之外,是否有办法跳过此行为并注入默认值 class。当应用程序被打包时,这是不可能的。如果我可以选择是使用默认的 classes 还是配置文件中的替代文件,例如在我的 WildFly 服务器的 standalone.xml 文件中,那就太好了。这可能吗?
恐怕无法使用普通的 @Alternative
注释来实现。请参阅以下几种您可以尝试的方法:
使用不同的 beans.xml
个文件
您可以考虑为每个环境准备不同的 beans.xml
个文件,然后根据 Maven profile.
写下你自己的替代刻板印象
您可以定义自己的替代原型并使用 CDI 扩展管理注入。
NightSpawN 的 post 中提到了这种方法。我在 WildFly 10 上对其进行了测试,它按预期工作。找到以下步骤:
用您的环境类型定义一个枚举:
public enum EnvironmentType {
DEVELOPMENT, TESTING, STAGING, PRODUCTION;
}
创建您自己的 @Alternative
刻板印象以保持 meta-information 对环境的看法:
@Stereotype
@Alternative
@Target(TYPE)
@Retention(RUNTIME)
public @interface EnvironmentAlternative {
EnvironmentType[] value();
}
并在beans.xml
中声明替代构造型:
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
<stereotype>com.example.EnvironmentAlternative</stereotype>
</alternatives>
</beans>
出于示例目的,让我们定义一个示例服务:
public interface GreetingService {
String sayGreeting();
}
定义默认实现:
@Default
public class DefaultGreetingService implements GreetingService {
@Override
public String sayGreeting() {
return "Hey!";
}
}
还使用 @EnvironmentAlternative
构造型定义了一些替代实现:
@EnvironmentAlternative(DEVELOPMENT)
public class DevelopmentGreetingService implements GreetingService {
@Override
public String sayGreeting() {
return "Hey from a development environment!";
}
}
@EnvironmentAlternative(PRODUCTION)
public class ProductionGreetingService implements GreetingService {
@Override
public String sayGreeting() {
return "Hey from a production environment!";
}
}
@EnvironmentAlternative
注解还支持具有多种环境类型的数组:
@EnvironmentAlternative({ TESTING, STAGING })
这里是奇迹发生的地方!
创建 CDI Extension
以观察 CDI 生命周期事件。容器处理的每个注释类型都会调用 processAnotated()
方法,如果用 @EnvironmentAlternative
注释并且当前环境不在指定环境中,则会调用事件的 veto()
方法,防止进一步处理的类型:
public class EnvironmentAlternativesExtension implements Extension {
private EnvironmentType currentEnvironment = PRODUCTION;
public <T> void processAnotated(@Observes ProcessAnnotatedType<T> event) {
EnvironmentAlternative alternative =
event.getAnnotatedType().getJavaClass()
.getAnnotation(EnvironmentAlternative.class);
if (alternative != null && !containsCurrentEnvironment(alternative.value())) {
event.veto();
}
}
private boolean containsCurrentEnvironment(EnvironmentType[] environments) {
for (EnvironmentType environment : environments) {
if (environment == currentEnvironment) {
return true;
}
}
return false;
}
}
当找不到合适的替代方案时,将使用默认实现。
接下来,通过在 META-INF/services
文件夹下创建一个名为 javax.enterprise.inject.spi.Extension
的文件,将 CDI 扩展注册为服务提供者。文件的内容将只是扩展名的规范名称 class:
com.example.EnvironmentAlternativesExtension
最后注入并使用上面定义的服务:
@Inject
private GreetingService service;
String greeting = service.sayGreeting();
在实际应用程序中,您不会对 currentEnvironment
字段的值进行硬编码。例如,您可以使用系统 属性 来确定应用程序所在的环境 运行。
要在 standalone.xml
中设置系统 属性,请使用 <server>
标签下的 <system-properties>
标签:
<server xmlns="urn:jboss:domain:4.2">
...
<system-properties>
<property name="environment" value="PRODUCTION"/>
</system-properties>
...
</server>
然后使用下面的一段代码获取environemnt
变量的值并设置currentEnvironment
字段的值:
String environment = System.getProperty("environment");
currentEnvironment = EnvironmentType.valueOf(environment);
详细说明我的评论,您可以执行以下操作。
定义单个限定符
@Inherited
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER})
public @interface BeanSelector {
@NonBinding
private String environment;
}
定义注释文字
public class BeanSelectorImpl extends AnnotationLiteral<BeanSelector> implements BeanSelector {
private final String environment;
public BeanSelectorImpl(final String environment) {
this.environment = environment;
}
public String environment() {
return this.environment;
}
}
创建一个从环境中读取的生产者
@ApplicationScoped
public class BeanSelectorProducer {
@Any
@Inject
private Instance<MyBeanService> myBeanServices;
@Produces
@BeanSelector(environment = "")
public MyBeanService produceService() {
final String env = System.getProperty("env.property");
final BeanSelector beanSelector = new BeanSelectorImpl(env);
//You may wish to handle exceptions.
return myBeanServices.select(beanSelector).get();
}
}
此实现的不利方面是您的所有 bean 都将处于服务状态。 为每个环境定义不同 beans.xml 的另一种选择可能是更好的选择。
在我看来,最简单的解决方案是像某些评论中提到的那样创建一个 maven 配置文件。
在我的 pom.xml 文件中,我添加了资源过滤部分和配置文件:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
<profiles>
<profile>
<id>default</id>
<properties>
<MyBean></MyBean>
</properties>
</profile>
<profile>
<id>alternative</id>
<properties>
<MyBean><![CDATA[<class>com.MyBean</class>]]></MyBean>
</properties>
</profile>
</profiles>
beans.xml 文件现在是这样的:
<beans ...>
<alternatives>
${MyBean}
</alternatives>
</beans>
最后我只需要使用有用的配置文件执行 maven 命令:mvn package -P alternative
.