Implementing a service that can be used in Core and Client apps (DataManager pattern)

Hi

Follow-up of this thread and this thread.

Under guidance of Yuriy I analysed how DataManager was implemented and tried to replicate the pattern. My goal is to create a GlobalEventBus service that can be used in Core (collocated) or Client modules (remote). And in fine, I would like one common interface centralizing the behavior whatever the mean was used to get the implementation (through remote Service or local Bean).

I have created a project with the following classes/interfaces :

  • GlobalEventBus, GlobalEventBusService interfaces in global module
  • GlobalEventBusBean (component), doing the job in core module
  • GlobalEventBusServiceBean (service) delegating to injected (collocated) GlobalEventBus in core module
  • GlobalEventBusClientImpl (component) in client (web) module, delegating to injected GlobalEventBusService in client (web) module (Spring injection)
  • GuiEventBus (component) in client (web) module, delegating to injected GlobalEventBus (Spring injection)
  • ExtAppMainWindow using an injected GuiEventBus (CUBA specific injection in a non Spring component)

I then meet the following exception at startup :


13:33:09.582 ERROR c.h.cuba.web.sys.WebAppContextLoader - Error initializing application
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'test_GlobalEventBus': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Service app-core/test_GlobalEventBusService is not registered in LocalServiceDirectory
Caused by: java.lang.IllegalArgumentException: Service app-core/test_GlobalEventBusService is not registered in LocalServiceDirectory
        at com.haulmont.cuba.web.sys.remoting.LocalServiceProxy$LocalServiceInvocationHandler.invoke(LocalServiceProxy.java:102) ~[cuba-web-6.4.2.jar:6.4.2]

We are in GlobalEventBusClientImpl here who is trying to call toString() on the injected Proxy for GlobalEventBusService. Injection did work, but I’m not sure why this call fails.

I checked web-spring.xml and the service is correctly registered.


       <property name="remoteServices">
            <map>
                <entry key="test_GlobalEventBusService"
                       value="com.company.test.service.GlobalEventBusService"/>
            </map>
        </property>
    </bean>

Project files attached.

test1334.zip (278.1K)

Message from Michael restored from email:
Hi
I’ve finally managed to narrow the issues, and made another project (attached) to illustrate different injection & construction scenarii.
I started with the DataManager pattern :

  • RemoteComponent interface + @Component RemoteComponentBean implementation in Core
  • RemoteComponentService interface + @Service RemoteComponentService Bean implementation in Core
  • @Component RemoteComponentClientImpl in Client, injecting RemoteComponentService
  • ExtAppMainWindow (CUBA class hence CUBA injection mechanism) injecting RemoteComponent
  • @Component ClientComponent retrieved through Spring (AppBeans.get()) injecting RemoteComponent (Spring injection)It works fine, whoever is injecting, CUBA or Spring
Then I wanted a common interface, but from a design perspective, I did not want to have a third one, so I made RemoteComponentService extends RemoteComponent interface. Then injection of RemoteComponent still works in ExtAppMainWindow but not any more in ClientComponent. Error is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tstsrv_ClientComponentGotBySpring': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Service app-core/tstsrv_RemoteComponentService is not registered in LocalServiceDirectoryat org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:355) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]Caused by: java.lang.IllegalArgumentException: Service app-core/tstsrv_RemoteComponentService is not registered in LocalServiceDirectoryat com.haulmont.cuba.web.sys.remoting.LocalServiceProxy$LocalServiceInvocationHandler.invoke(LocalServiceProxy.java:99) ~[cuba-web-6.5.0.jar:6.5.0]at com.sun.proxy.$Proxy54.equals(Unknown Source) ~[na:na]at java.util.concurrent.ConcurrentHashMap.containsValue(ConcurrentHashMap.java:985) ~[na:1.8.0_131] I was surprised CUBA injection tolering this case while Spring injection does not. Then I thought it was reminding me the old Local/Remote interface situation in JEE or the equivalent collocation principle in CORBA. So why Spring would not be smart enough to inject a collocated bean when using the RemoteComponent in Middleware, and inject a remoting proxy in Client ? So I tried to simply inject RemoteComponentService in a Middleware service and it just worked. Not necessary to have a Bean + a Service anymore, great. Also, during my tests, I made other findings about constraints of @PostConstruct method ( probably no news for confirmed Spring users, but I'm a recent one) :
  • you cannot use an injected Service or a Bean using an injected Service in a @PostConstruct method
  • but you can use an injected Bean not using services (i.e local) in a @PostConstruct method
And the recap : 1) You can have a Service extending an interface of a Bean, with two distincts implementation, but injection of one or the other will work only through CUBA not through Spring (not sure if this was intended ?) 2) You can inject a Service into a Service in Core module, or more generally can use Service interfaces everywhere without a specific pattern like DataManager 3) You can use an injected Service in a @PostConstruct method of a Service in Core module 4) But you can not use injected Services in a @PostConstruct method of a Bean in Client module 5) Same for an injected Bean using an injected Service in Client module 6) But you can use an injected Bean not using services in a @PostConstruct method in Client module (of course) Not sure which of these are Spring or CUBA specifics, I would be interested by some light on the matter. Mike

Please note that @PostConstruct method is invoked when all injection is done for this bean, not after all beans are initialized. It explains why you cannot invoke services from such methods of client side beans.
If you need to execute some code after all initialization of the application is done, use AppContext.Listener.