Handling limited number of bean instance copies by CDI
vitek.richard Jul 26, 2016 7:04 PMHi guys,
I've been hammering my brain for two days now with the following problem, so any help or pointing in the right direction is highly appreciated.
What I'd like is to use CDI for instantiation and wiring up of several copies of an object graph for selected classes (I'll refer to them CS). The number of copies in our case is driven by number of records (I'll call it PN) in a database table but no dynamic behaviour required here - it's absolutely OK to restart the application when number of records changes, so you can consider this number (PN) a constant for the application's lifetime.
A little bit of background - this app controls other (distributed - separated JVMs/hosts) components, and each of this component exists PN times. So I need some component-controlling code for each component type but I need PN-instances of each class involved in controlling the particular component type.
I already posted a question on StackOverflow (Custom database-driven CDI Context) with no answer. Since then, I realized that custom context is prob NOT the way to go - there's simply no way how demarcate such a "scope". A colleague of mine came with a nice idea of solving this problem in Spring Framework, which supports hierarchical application contexts and thus it's possible to create a common parent context extended by PN independent copies of the child application contexts, which would only contains CS classes. This way, beans from CS classes exist in PN copies (subject to the particular scope of course) but beans from other classes are normally injected in them as usually - i.e. exactly what I need.
But mimicking something similar in CDI is tricky at least, because CDI builds on concept of BeanDeploymentArchives (JARs, WARs, ...) and BeanManagers controlling beans from this archives. These BeanManagers do have some hierarchy - each BeanManager has a list of accessible BeanManagers to which it delegates requests for beans not accessible directly in its BeanDeploymentArchive. So I tried to use this to my advantage but so far without a real break-through, only with partial success.
My current attempt to solve the issue (follows soon) looks like this (I know I shouldn't rely on Weld implementation details but I don't think I can achieve something like this with pure CDI API only): I have a proof-of-concept class Pod which would serve as the only entry point to a one object graph o CS classes. The plan is to create of course PN of instances of Pod class if the concept works. Because all app is now in one archive, this POD class would have access to all application beans (this could be generalized per module when our application gets split into multiple Maven modules). In beans.xml, we have configured discovery of only annotated classes. The classes, which I want PN copies of, are NOT annotated by any bean-defining annotation and I try to register them manually in a custom BeanManager which "inherits" beans from the normal BeanManager (created by Weld for the main application WAR archive). This is to mimic the proposed Spring-based solution - this custom BeanManager is CDI equivalent of the aforementioned child application context.
I made it to work to some degree - I'm able to register bean classes in the custom BeanManager and obtain refences (client proxies), and I'm able to call toString() on this reference and observe that @ApplicationScoped dependencies of the bean were correctly injected and @PostConstruct method called. What doesn't work though is injection of other beans manually registered in this custom BeanManager into others defined in the same BeanManager. I guess this has something to do with the way I initialize the custom BeanManager - but can't pass this point.
Before the actual sample code, I wanna ask (a response from directly from Weld devs would be awesome) if this could be actually way to go? Or is there any better approach to deal with this problem? As an exit strategy, we could either migrate to Spring (which would not be impossible but still very difficult) or simply instantiate and wire up all the classes from CS set manually (and lose all the CDI goodies).
So, here's what I've got so far:
package com.cloud4com.glue; import java.lang.annotation.Annotation; import java.util.List; import java.util.Map.Entry; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanAttributes; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.InjectionTargetFactory; import org.jboss.weld.bean.attributes.ImmutableBeanAttributes; import org.jboss.weld.bean.builtin.BeanManagerProxy; import org.jboss.weld.bootstrap.api.Service; import org.jboss.weld.bootstrap.api.ServiceRegistry; import org.jboss.weld.bootstrap.api.helpers.SimpleServiceRegistry; import org.jboss.weld.context.WeldCreationalContext; import org.jboss.weld.injection.producer.InjectionTargetService; import org.jboss.weld.manager.BeanManagerImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.cloud4com.glue.adapter.vsphere.VimPortTypeProvider2; public class Pod { private static final Logger LOGGER = LoggerFactory.getLogger(Pod.class); private BeanManagerImpl podBeanMgr; Pod() { super(); } public Pod(long podId, BeanManager normalBeanMgr) { this.podBeanMgr = createPodBeanManager(podId, normalBeanMgr); List<Bean<?>> beanList0 = this.podBeanMgr.getBeans(); Bean<PodIdHolder> bean1 = registerBeanClass(PodIdHolder.class, ApplicationScoped.class); List<Bean<?>> beanList1 = this.podBeanMgr.getBeans(); getBeanRef(bean1, PodIdHolder.class); getBeanRef(bean1, PodIdHolder.class); Bean<VimPortTypeProvider2> bean2 = registerBeanClass(VimPortTypeProvider2.class, ApplicationScoped.class); List<Bean<?>> beanList2 = this.podBeanMgr.getBeans(); getBeanRef(bean2, VimPortTypeProvider2.class); } private BeanManagerImpl createPodBeanManager(long podId, BeanManager normalBeanMgr) { BeanManagerImpl beanMgr = ((BeanManagerProxy) normalBeanMgr).delegate(); ServiceRegistry originalRegistry = beanMgr.getServices(); ServiceRegistry ourRegistry = createServiceRegistry(originalRegistry); BeanManagerImpl podBeanMgr = BeanManagerImpl.newManager(beanMgr, "glue-pod-" + podId, ourRegistry); ourRegistry.add(InjectionTargetService.class, new InjectionTargetService(podBeanMgr)); for (BeanManagerImpl accessibleMgr : beanMgr.getAccessibleManagers()) { podBeanMgr.addAccessibleBeanManager(accessibleMgr); } return podBeanMgr; } private <S extends Service> ServiceRegistry createServiceRegistry(ServiceRegistry originalServices) { SimpleServiceRegistry registry = new SimpleServiceRegistry(); for (Entry<Class<? extends Service>, Service> entry : originalServices.entrySet()) { Class<Service> key = (Class<Service>) entry.getKey(); addService(registry, key, entry.getValue()); } return registry; } private <S extends Service> void addService(SimpleServiceRegistry ourServices, Class<S> serviceClass, S service) { if (serviceClass.equals(InjectionTargetService.class)) { } else { ourServices.add(serviceClass, service); } } private <T> void getBeanRef(Bean<T> bean, Class<T> beanClass) { WeldCreationalContext<T> creationalContext = podBeanMgr.createCreationalContext(bean); Object ref = this.podBeanMgr.getReference(bean, beanClass, creationalContext); try { ref.toString(); } catch (Exception e) { LOGGER.error("Ref err", e); } System.err.println(ref); } private <T> Bean<T> registerBeanClass(Class<T> beanClass, Class<? extends Annotation> scope) { AnnotatedType<T> annotatedType = podBeanMgr.createAnnotatedType(beanClass); return registerAnnotatedType(beanClass, scope, annotatedType); } private <T> Bean<T> registerAnnotatedType(Class<T> beanClass, Class<? extends Annotation> scope, AnnotatedType<T> annotatedType) { BeanAttributes<T> originalAttrs = podBeanMgr.createBeanAttributes(annotatedType); BeanAttributes<T> updatedAttrs = overwriteScope(originalAttrs, scope); InjectionTargetFactory<T> injectionTargetFactory = podBeanMgr.getInjectionTargetFactory(annotatedType); return podBeanMgr.createBean(updatedAttrs, beanClass, injectionTargetFactory); } private <T> ImmutableBeanAttributes<T> overwriteScope(BeanAttributes<T> attrs, Class<? extends Annotation> scope) { return new ImmutableBeanAttributes<>(attrs.getStereotypes(), attrs.isAlternative(), attrs.getName(), attrs.getQualifiers(), attrs.getTypes(), scope); } }
CS classes here are PodIdHolder and VimPortTypeProvider2 but PodIdHolder can't be injected into VimPortTypeProvider2 instance .
package com.cloud4com.glue; public class PodIdHolder { private final long podId; PodIdHolder() { this(1); } public PodIdHolder(long podId) { this.podId = podId; } public long getPodId() { return this.podId; } @Override public String toString() { return super.toString(); } }
package com.cloud4com.glue.adapter.vsphere; import java.util.Map; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.xml.ws.BindingProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.cloud4com.glue.PodAware; import com.cloud4com.glue.PodIdHolder; import com.cloud4com.glue.adapter.vsphere.data.VSphereConfig; import com.cloud4com.glue.settings.ConfigurationService; import com.cloud4com.glue.settings.data.PodEntity; import com.vmware.vim25.VimPortType; import com.vmware.vim25.VimService; public class VimPortTypeProvider2 implements PodAware { private static final Logger LOGGER = LoggerFactory.getLogger(VimPortTypeProvider2.class); @Inject private PodIdHolder podId; @Inject private ConfigurationService configurationService; @Inject private VimService vimService; private VimPortType delegate; @PostConstruct void init() { LOGGER.debug("Invoking @PostContruct method..."); PodEntity podConfig = this.configurationService.getPodConfig(getPodId()); VSphereConfig vSphereConfig = podConfig.getvSphereConfig(); Map<String, Object> configurationKeys = VimPortTypeProvider.getConfigurationKeys(vSphereConfig); delegate = this.vimService.getVimPort(); ((BindingProvider) delegate).getRequestContext().putAll(configurationKeys); } @Override public long getPodId() { return podId.getPodId(); } public VimPortType getDelegate() { return this.delegate; } @Override public String toString() { return super.toString(); } }