Creating and wiring related instances at runtime with CDI
Asked Answered
R

1

6

I have a Service in a Java SE application (without any application server) which creates Algorithm instances and run them.

  • Every Algorithm instance needs a new (separate) ActionExecutor and a new (separate) AlgorithmState.
  • ActionExecutor also needs an AlgorithmState instance and this instance have to be the same as Algorithm gets.

How can I achieve this with CDI? I've tried constructor injection and @New on both parameters of Algorithm but I guess this is not what I want.

Service class:

import java.util.ArrayList;
import java.util.List;

import javax.enterprise.inject.Instance;
import javax.inject.Inject;

public class Service {

    @Inject
    private Instance<Algorithm> algorithmInstance;

    public void run() {
        final List<Algorithm> algorithms = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            final Algorithm algorithm = algorithmInstance.get();
            algorithms.add(algorithm);
        }

        for (final Algorithm algorithm: algorithms) {
            algorithm.doSomething();
        }
    }

}

Algorithm class:

import java.util.concurrent.atomic.AtomicInteger;

import javax.enterprise.inject.New;
import javax.inject.Inject;

public class Algorithm {

    private static final AtomicInteger counter = new AtomicInteger(100);

    private final ActionExecutor actionExecutor;
    private final AlgorithmState algorithmState;

    private final int id;

    @Inject
    public Algorithm(@New final ActionExecutor actionExecutor, @New final AlgorithmState algorithmState) {
        this.actionExecutor = actionExecutor;
        this.algorithmState = algorithmState;
        id = counter.incrementAndGet();
        System.out.println("algorithm ctor#" + id);
    }

    public void doSomething() {
            System.out.printf("do something, algorithm id: #%d: executor id%sd, stateId: %d, executor->stateId: %d%n", id,
            actionExecutor.getId(), algorithmState.getId(), actionExecutor.getAlgorithmStateId());
    }
}

ActionExecutor class:

import java.util.concurrent.atomic.AtomicInteger;

import javax.inject.Inject;

public class ActionExecutor {

    private static AtomicInteger counter = new AtomicInteger(200);

    private final AlgorithmState algorithmState;

    private final int id;

    @Inject
    public ActionExecutor(final AlgorithmState algorithmState) {
        this.algorithmState = algorithmState;
        id = counter.incrementAndGet();
    }

    public int getId() {
        return id;
    }

    public int getAlgorithmStateId() {
        return algorithmState.getId();
    }
}

AlgorithmState class:

import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

public class AlgorithmState {

    private static final AtomicInteger counter = new AtomicInteger(300);

    private final int id;

    @Inject
    public AlgorithmState() {
        id = counter.incrementAndGet();
    }

    @PostConstruct
    public void start() {
        System.out.println("state start#" + id);
    }

    public int getId() {
        return id;
    }
}

And a ServiceMain class for testing:

import java.util.List;

import javax.enterprise.event.Observes;
import javax.inject.Inject;

import org.jboss.weld.environment.se.bindings.Parameters;
import org.jboss.weld.environment.se.events.ContainerInitialized;

public class ServiceMain {

    @Inject
    private Service service;

    public void printHello(
            @Observes final ContainerInitialized event,
            @Parameters final List<String> parameters) {
        System.out.println("ServiceMain:" + service);
        service.run();
    }

    public static void main(final String[] args) {
        org.jboss.weld.environment.se.StartMain.main(args);
    }

}

Currently it prints the following:

do something, algorithm id: #101: executor id201d, stateId: 302, executor->stateId: 301
do something, algorithm id: #102: executor id202d, stateId: 304, executor->stateId: 303
do something, algorithm id: #103: executor id203d, stateId: 306, executor->stateId: 305

What I need is that the stateId and executor->stateId are the same:

do something, algorithm id: #101: executor id201d, stateId: 301, executor->stateId: 301
do something, algorithm id: #102: executor id202d, stateId: 302, executor->stateId: 302
do something, algorithm id: #103: executor id203d, stateId: 303, executor->stateId: 303

edit:

Currently I'm getting the AlgorithmState from the AlgorithmExecutor but it messes up the model, I'd like to avoid that.

Represent answered 13/3, 2014 at 11:7 Comment(1)
Not familiar with CDI-SE. But for EE these problems are solved with scopes and qualifiers.Marchand
F
3

First of all, the @New qualifier was replaced with the @Dependant scope, which is the default scope anyway. I'd imagine it was changed in CDI 1.1 due to the confusion that it causes. Plus, @New is a qualifier, not a scope, and is from a separate standard that can be used for multiple purposes (I don't think @New is really used by any standards).

It looks like you'd want to use @Produces in AlgorithmState instead of @Inject. Check out the Weld documentation on the subject for some more details. You might want to create a specific scoped type like so:

@ScopeType
@Retention(RUNTIME)
@Target({TYPE, METHOD, CONSTRUCTOR})
public @interface AlgorithmScoped {}

Then you could modify the AlgorithmState constructor:

@Produces @AlgorithmScoped
public AlgorithmState() {
    // ...
}

Add that scope to AlgorithmExecutor, and then also to Algorithm. I don't think you should be trying to inject AlgorithmState into Algorithm; get that from the AlgorithmExecutor instance! In fact, if you do that, that should solve the entire problem in a nutshell.

Fides answered 15/3, 2014 at 19:15 Comment(8)
Thanks for the answer! I've seen the custom scope but it needs a custom Context object which looks to complicated. I guess I would be happy with simple example but I haven't seen any simple ones yet. (I've updated the question too.)Represent
Have you looked at javax.enterprise.concurrent? More details about the context of your problem might help (e.g., are you just using Weld on its own? or are you using a full Java EE environment? or is it like Tomcat + libraries?).Fides
It's and old Java SE application with a main method (which calls Weld's main method). We don't have any appserver nor Tomcat. I haven't seen javax.enterprise.concurrent yet. Would it help?Represent
Well, if it's not in an application server, there's no need to use managed concurrency. You could just use java.util.concurrent instead to get real executors and such which could help your architecture.Fides
I guess you mean AlgorithmExecutor. It uses a ThreadPool internally but its responsibility is to collect some diagnostic data about the submitted tasks. (We have other similar use-cases in the application when we have to wire a group of objects runtime.)Represent
It might be the case that you can't @Inject the state into the executor if you want to inject them both into the algorithm. This could actually be a design limitation of the current CDI spec. Have you tried injecting the state into the executor as a method instead of a constructor? Avoid constructors tends to help a lot in beans.Fides
Calling setters works, I've created a PoC for that and I've played with custom scope/context as well but both requires some more work.Represent
@New is a perfectly valid CDI annotation, typically used in CDI producers as a parameter annotation to indicate that the producer produces a new instance each time.Fecundity

© 2022 - 2024 — McMap. All rights reserved.