4

I have some beans (of multiple types, CDI, @Stateless and @Singleton beans). Some of their fields shall get injected from database values.

public class MyBean {

    @Inject
    @DbConfigValue(MyConfig.HOST)
    String host;
}

So I added a custom @Qualifier (DbConfigValue) used by a Producer. The producer reads and caches config values from a database and injects them into the beans.

@Singleton
@Lock(LockType.READ)
public class Configuration {

    @Produces
    @Dependent
    @DbConfigValue
    public String getDbConfigValue(InjectionPoint point) {
        // get key for the config-value from qualifier-annotation of the injected field
        String key = point.getAnnotated().getAnnotation(DbConfigValue.class).value();
        // i have read+cached database config values in @PostConstruct before
        return cachedConfigValues.get(key);
    }
}

This works well for initial injection / bean construction. Some web tutorials out there are suggesting this approach.

Now, I think it is reasonable to assume that config values, if stored in DB, might change at runtime. So, whenever an admin changes a database config value, I currently do fire a CDI-event.

Question: is there any way to re-inject values into fields of already-initialized bean-instances? Or is injection always related to instance-creation only?

E.g. I had s.th. similar to this in mind:

public class MyEventListener {

    @Inject
    BeanManager beanManager;

    @Asynchronous
    public void onDbConfigValueChangedEvent (@Observes(during = TransactionPhase.AFTER_SUCCESS) DbConfigValueChangedEvent event) {
        try {
            // could be filtered by custom qualifier:
            Set<Bean<?>> beans = beanManager.getBeans(Object.class,new AnnotationLiteral<Any>() {});

            for (Bean<?> bean : beans) {
                Set<InjectionPoint> points = bean.getInjectionPoints();
                // What now? javax.enterprise.inject.spi.Bean is the 
                // bean-representation only. 
                // Can I somehow resolve the actual bean-instances here?
                // Then update Field via Reflection?
            }
        }
        catch(Exception e){
            // ...
        }
    }
}

I also considered DeltaSpike which has some methods for injection-control. However, I did only find methods to inject into new bean instances, or even with new- or null-CreationalContexts (beans not CDI-managed afterwards)

Please note: I am aware that I can solve this specific use-case by injecting the configuration and explicitly getting the current values on each request like this:

public class MyBean {

    @Inject
    Configuration config;

    public void someMethod(){
        String host = config.getConfig(MyConfig.HOST);
        // ...
    }
}

However, I am wondering about the question in general: is there any support for re-injection? Or if not, do the specs (CDI or Java EE) forbid it?

Ben
  • 150
  • 4
  • 14

1 Answers1

2

Depending on how fast/slow your db is, this may be expensive. You could probably leverage some cacheing mechanism in the producer method. Leverage on Instance injection mechanims, which lazily loads the actual injected bean.

Your Producer (Probably leveraging on some of cache to avoid db calls all the tome)

@Singleton
@Lock(LockType.READ)
public class Configuration {

    @Produces
    @RequestScoped //May fail if not in a request-context, but for ejb-calls, it is guaranteed to work as CDI has EJB Request Context
    @DbConfigValue
    public String getDbConfigValue(InjectionPoint point) {
        // get key for the config-value from qualifier-annotation of the injected field
        String key = point.getAnnotated().getAnnotation(DbConfigValue.class).value();
        // i have read+cached database config values in @PostConstruct before
        return cachedConfigValues.get(key);
    }
}

And the injection points:

@SessionScoped
public class MyBean {

    @Inject
    @DbConfigValue(MyConfig.HOST)
    private Instance<String> host;

    public void doSomething() {
        String myHost = host.get(); // of course will throw exception if value is failing. It will be resolved with every request.
    }
}
maress
  • 3,533
  • 1
  • 19
  • 37
  • Thx, helpful, but not exactly what I wanted: this asks for injection (via `Instance.get()`) upon each request (each `doSomething` call). It is therefore similar to my last snippet (`config.getConfig()`), although you managed to involve the Producer method again. But I rather wanted to re-inject only when the CDI-event was fired. And asked myself if it is possible to re-inject from **outside** (`MyEventListener`) **into** all running bean-instances. – Ben Jun 23 '16 at 14:32
  • Ideally, the producer may keep the value in a cache, which can be updated at any time (when it changes, only the producer may listen to the cdi event, and updates the cache), and the update will be picked up by all instances. Reinjecting, imo just does not look right in this context – maress Jun 23 '16 at 14:37
  • Yes, values are cached and updated only after change. In fact, I even already changed my code to a solution similar to this for this particular issue. However, as mentioned in the question, I asked myself in general whether there is any support for re-injection in JavaEE/CDI. Just out of curiosity and for possible future use. So, if you say 'not right in this context', do you know about solutions for re-injection (for different contexts)? – Ben Jun 23 '16 at 14:49
  • The only way to do something close to re-injection is using ```Instance``` with some small scope which can allow you to use different instance of the bean that may change between context – maress Jun 23 '16 at 14:55
  • String is a final class. You cant have an instance of a final class as bean in a normal scope (@RequestScope, @SessionScope, @ApplicationScope), because these require Proxies. It can only be in a pseudo scope (@Dependent, @Singleton). – k5_ Jun 24 '16 at 17:02
  • @maress how often the produces method is called and based on what? – florin Jul 02 '20 at 09:55
  • 1
    @florin it depends on the scope of your bean. – maress Jul 03 '20 at 12:41