Disable @Alternative classes

a 夏天 提交于 2019-12-12 04:23:11

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!