问题
In my Java EE 7 program, I want to use @Alternative
to inject different implementation depending on the context, production or test for example. What I did is to declare my class annotated with @Alternative
in my beans.xml file. It works great and my alternative class is injected wherever I want instead of the default one. But I don't know if there is a way to skip this behavior and inject the default class other than removing the declaration in the beans.xml file. Which is not possible easily when the application is packaged. It would be great if I can choose if I want to use the default classes or the alternative ones in a configuration file, for example in my standalone.xml file of my WildFly server. Is this possible?
回答1:
Elaborating on my comment, you can do the following.
Define a single qualifier
@Inherited
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER})
public @interface BeanSelector {
@NonBinding
private String environment;
}
Define an annotation literal
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;
}
}
Create a producer that reads from the 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();
}
}
The negative side of this implementation is that all your beans will be in service. The other option of defining different beans.xml for every environment is probably a better option.
回答2:
I'm afraid it's not possible to be achieved with the plain @Alternative annotation. See below a couple of approaches you could try:
Using different beans.xml
files
You could consider having a different beans.xml
files for each environment and then pack the right one according to, for example, a Maven profile.
Writing your own alternative stereotype
You can define your own alternative stereotype and manage the injection with a CDI extension.
This approach is mentioned in this post from NightSpawN. I tested it on WildFly 10 and it worked as expected. Find the steps below:
Define an enumeration with your environment types:
public enum EnvironmentType {
DEVELOPMENT, TESTING, STAGING, PRODUCTION;
}
Create your own @Alternative stereotype to hold meta-information about the environment:
@Stereotype
@Alternative
@Target(TYPE)
@Retention(RUNTIME)
public @interface EnvironmentAlternative {
EnvironmentType[] value();
}
And declare the alternative stereotype in the 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>
For example purposes, let's define a sample service:
public interface GreetingService {
String sayGreeting();
}
Define a default implementation:
@Default
public class DefaultGreetingService implements GreetingService {
@Override
public String sayGreeting() {
return "Hey!";
}
}
Also define some alternative implementations using the @EnvironmentAlternative
stereotype:
@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!";
}
}
The @EnvironmentAlternative
annotation also supports an array with multiple environment types:
@EnvironmentAlternative({ TESTING, STAGING })
Here's where the magic happens!
Create a CDI Extension to observe CDI lifecycle events. The processAnotated()
method gets called for each annotated type the container processes, and if it is annotated with @EnvironmentAlternative
and the current environemnt is not in the specified environments, event's veto()
method is called, preventing the type from being processed any further:
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;
}
}
The default implementation will be used when no suitable alternatives are found.
Next, register the CDI extension as a service provider by creating a file named javax.enterprise.inject.spi.Extension
under the META-INF/services
folder. The content of the file will be just the canonical name of the extension class:
com.example.EnvironmentAlternativesExtension
Finally inject and use the above defined service:
@Inject
private GreetingService service;
String greeting = service.sayGreeting();
In a real application you won't hardcode the value of the currentEnvironment
field. You could, for example, use a system property to determine the environemnt where the application is running.
To set a system property in the standalone.xml
, use the <system-properties>
tag under the <server>
tag:
<server xmlns="urn:jboss:domain:4.2">
...
<system-properties>
<property name="environment" value="PRODUCTION"/>
</system-properties>
...
</server>
Then use the following piece of code to get the value of the environemnt
variable and set the value of the currentEnvironment
field:
String environment = System.getProperty("environment");
currentEnvironment = EnvironmentType.valueOf(environment);
回答3:
In my opinion the simpliest solution was to create a maven profile like it's mentionned in some comments.
In my pom.xml file, I added a resources filtering section and a profile:
<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>
The beans.xml file is now like that:
<beans ...>
<alternatives>
${MyBean}
</alternatives>
</beans>
Finally I just need to execute the maven command with the useful profil: mvn package -P alternative
.
来源:https://stackoverflow.com/questions/42250622/disable-alternative-classes