Creating application scoped class member with a pr

2019-06-01 05:43发布

问题:

Is it true to say that in the code bellow, the Hazelcast instance will be application scoped ?

@ApplicationScoped
public class Producer {

    private HazelcastInstance instance;

    @PostConstruct
    public void afterCreate() {
        instance = Hazelcast.newHazelcastInstance();
    }

    @Produces
    public HazelcastInstance getInstance() {
        return instance;
    }
}

EDIT

This solution:

  1. Ensure produced been are application scoped.
  2. Provide graceful Hazelcast shut down.
@ApplicationScoped
public class Producer {

    private HazelcastInstance instance;

    private Producer() {}

    @PostConstruct
    public void afterCreate() {
        instance = Hazelcast.newHazelcastInstance();
    }

    @PreDestroy
    public void beforeDestroy() {
        if(instance != null) {
            instance.shutdown();
        }
    }

    @Produces
    @ApplicationScoped
    public HazelcastInstance getInstance() {
        return instance;
    }
}

回答1:

You Producer bean will be application scoped, that much is clear. However, HazelnutInstance bean will be @Dependent.

Therefore, if you, somewhere in your code, do @Inject HazelnutInstance it will inject a dependent instance from CDI viewpoint.

But you always return the same instance, never create new object in your producer, so theoretically, you are sharing that one instance.

However, look out for bean lifecycles! @Dependent beans will be destroyed when the bean where they were injected is destroyed. So now assume it comes to such destruction attempt - Weld will try to destroy your dependent bean and will call @PreDestroy (on "classic" beans) or @Disposes (on beans with producers) method on it.

Therefore, in your case, if there is a disposer method somewhere, which handles HazelcastInstance, that might cause a trouble as it would be invoked every time Weld attempts to destroy/dispose of that dependent bean.

IMO you would be better off if you make HazelcastInstance application scoped. E.g.

@Produces
@ApplicationScoped
public HazelcastInstance getInstance() {
   return instance;
}


回答2:

I wanted to get a unit test prove the expected behaviors.

Code and results can be found below.

Environment

UT launched against WebLogic 12.2.1 remote container using arquillian

1. ApplicationScoped class and producer, new instance: PASS

As expected the test pass: the produced bean is application scoped, and as such the same instance is injected.

@ApplicationScoped
public class Producer {

    private Producer() {}

    @Produces
    @ApplicationScoped
    public HazelcastInstance getInstance() {
        return Hazelcast.newHazelcastInstance();
    }
}

2. ApplicationScoped class and Dependent producer, new instance: FAIL

As expected the test fail: the produced bean being @Dependent, a different instance is injected. (Note: @Dependent being the default scope, it can or not be present, the same result will be the same).

@ApplicationScoped
public class Producer {

    private Producer() {}

    @Produces
    public HazelcastInstance getInstance() {
        return Hazelcast.newHazelcastInstance();
    }
}

3. ApplicationScoped class and Dependent producer, post construct instance: PASS

As expected the test pass: the produced instance bean is @Dependent. However, its lifecycle is indirectly bound to the Producer lifecycle (through @PostConstruct called once) and as such, the same instance is returned at each call, and dereferenced when the Producer bean is destructed.

Note: Hazelcast hasn't been gracefully shut down in this implementation.

@ApplicationScoped
public class Producer {

    private HazelcastInstance instance;

    private Producer() {}

    @PostConstruct
    public void afterCreate() {
        instance = Hazelcast.newHazelcastInstance();
    }

    @Produces
    public HazelcastInstance getInstance() {
        return instance;
    }
}

4. ApplicationScoped class and ApplicationScoped producer, post construct instance: PASS

As expected the test pass: the produced bean is @ApplicationScoped and the same instance is returned during the application life cycle.

@ApplicationScoped
public class Producer {

    private HazelcastInstance instance;

    private Producer() {}

    @PostConstruct
    public void afterCreate() {
        instance = Hazelcast.newHazelcastInstance();
    }

    @Produces
    @ApplicationScoped
    public HazelcastInstance getInstance() {
        return instance;
    }
}

5. Dependent class and ApplicationScoped producer, post construct instance: PASS

This result is surprising: Producer is annotated @Dependent, its producer @ApplicationScoped and the result is that the same instance is returned by each Producer instance. Reason: CDI use scoped contexts

@Dependent
public class Producer {

    private HazelcastInstance instance;

    private Producer() {}

    @PostConstruct
    public void afterCreate() {
        instance = Hazelcast.newHazelcastInstance();
    }

    @Produces
    @ApplicationScoped
    public HazelcastInstance getInstance() {
        return instance;
    }
}

6. Dependent class and producer, post construct instance: FAIL

As expected the test fail: the produced bean being @Dependent, a different instance is injected. (Note: @Dependent being the default scope, it can or not be present, the same result will be the same) as each Producer instance will invoke its own afterCreate method, leading to different instance instances

@Dependent
public class Producer {

    private HazelcastInstance instance;

    private Producer() {}

    @PostConstruct
    public void afterCreate() {
        instance = Hazelcast.newHazelcastInstance();
    }

    @Produces
    public HazelcastInstance getInstance() {
        return instance;
    }
}

Unit test

@RunWith(Arquillian.class)
public class ProducerTest {

    @Deployment
    public static WebArchive createDeployment() {

         // Import Maven runtime dependencies
        File[] files = Maven.resolver()
                            .loadPomFromFile("pom.xml")
                            .importRuntimeDependencies()
                            .resolve()
                            .withTransitivity()
                            .asFile();
        // Create deploy file    
        WebArchive war =  ShrinkWrap.create(WebArchive.class, ProducerTest.class.getName() + ".war")
            .addClass(Producer.class)
            .addClass(ProducerTest.class)
            .addAsLibraries(files);

        // Show the deploy structure
        System.out.println(war.toString(true)); 

        return war;
    }

    @Inject
    private HazelcastInstance hzInstance1;

    @Inject
    private HazelcastInstance hzInstance2;

    @Before
    @After
    public void cleanup() {
        Hazelcast.shutdownAll();
        assertEquals(emptySet(), getAllHazelcastInstances());
    }

    @Test
    public void producerTest() {
        assertTrue(hzInstance1.equals(hzInstance2));
    }
}

arquillian.xml

<?xml version="1.0"?>
<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://jboss.org/schema/arquillian"
    xsi:schemaLocation="http://jboss.org/schema/arquillian http://www.jboss.org/schema/arquillian/arquillian_1_0.xsd">
    <container qualifier="wls-remote" default="true">
        <configuration>
            <property name="wlHome">PATH_TO_WLSERVER_DIRECTORY</property>
            <property name="adminUrl">ADMIN_URL</property>
            <property name="adminUserName">USER_NAME</property>
            <property name="adminPassword">PASSWORD</property>
            <property name="target">TARGET_SERVER</property>
        </configuration>
    </container>
</arquillian>

hazelcast.xml

<?xml version="1.0" encoding="UTF-8"?>
<hazelcast
        xsi:schemaLocation="http://www.hazelcast.com/schema/config
        http://www.hazelcast.com/schema/config/hazelcast-config-3.5.xsd"
        xmlns="http://www.hazelcast.com/schema/config"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <group>
        <name>producer</name>
        <password>producer_pass</password>
    </group>

    <network>
        <port auto-increment="false">10710</port>
        <join>
            <multicast enabled="false" />
            <tcp-ip enabled="true">
                <member>localhost</member>
            </tcp-ip>
        </join>
    </network>

</hazelcast>

For the sake of the completeness, the POM

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>HeyStackExchange</groupId>
    <artifactId>cdi-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <url>https://stackoverflow.com/questions/46559523/scoped-instance</url>

    <name>Arquillian WLS 12.1.x/12.2.x - CDI Example</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>

        <arquillian.version>1.1.5.Final</arquillian.version>

        <!-- JEE -->
        <jboss-javaee-7.0.version>1.1.0.Final</jboss-javaee-7.0.version>

        <!-- Arquillian -->
        <arquillian-wls-12.1.x.version>1.0.1.Final</arquillian-wls-12.1.x.version>

        <!-- Cache -->
        <hazelcast.version>3.8.3</hazelcast.version>

        <!-- UT -->
        <junit.version>4.8.2</junit.version>

        <!-- Logging -->
        <slf4j-api.version>1.7.25</slf4j-api.version>
        <slf4j-log4j12.version>1.7.9</slf4j-log4j12.version>

    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>${arquillian.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- JEE -->
        <dependency>
            <groupId>org.jboss.spec</groupId>
            <artifactId>jboss-javaee-7.0</artifactId>
            <version>${jboss-javaee-7.0.version}</version>
            <type>pom</type>
            <scope>provided</scope>
        </dependency>

        <!-- Cache -->
        <dependency>
            <groupId>com.hazelcast</groupId>
            <artifactId>hazelcast</artifactId>
            <version>${hazelcast.version}</version>
        </dependency>

        <!-- Arquillian -->
        <dependency>
            <groupId>org.jboss.shrinkwrap.resolver</groupId>
            <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.arquillian.container</groupId>
            <artifactId>arquillian-wls-remote-12.1.x</artifactId>
            <!--artifactId>arquillian-wls-managed-12.1.x</artifactId -->
            <version>${arquillian-wls-12.1.x.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- UT -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- Logging -->

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j-api.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j-log4j12.version}</version>
        </dependency>

    </dependencies>

</project>


回答3:

Yes the Producer will receive the Scope you applied with the Annotation @ApplicationScoped. This will result in only having one instance for the whole CDI Application. Every Request to the class Producer will come to the same instance of the class since it will be only initialized once and then kept active. Since the Producer is application scoped the instance of Hazelcastwill not change, as long as you don't change it in the class itself ( e.g setter )

An object which is defined as @ApplicationScoped is created once for the duration of the application.

taken from here