CUBA injection : always singleton ?

Hi

I have a LogInterceptor service that I need to run early in the application startup sequence. So I injected it in AppLifecycle Context listener to initialize it.


@Component(LogService.NAME)
public class LogInterceptor extends AbstractService implements LogService {

    private static final org.slf4j.Logger log = LoggerFactory.getLogger(LogInterceptor.class);

    //---

    @Inject
    public GlobalEventBus eventBus;

    @Inject
    BusyConfig conf;

@Component("busy_AppLifecycle")
public class AppLifecycle implements AppContext.Listener {

    //---

    @Autowired
    LogService logService;

As you can see, it injects an instance of GlobalEventBus, I was expecting it to be a singleton for the whole life of the application.

Then later, I need the GlobalEventBus in another class ExtAppMainWindow :



public class ExtAppMainWindow extends AppMainWindow {

    private final static Logger log = LoggerFactory.getLogger(ExtAppMainWindow.class);

    @Inject
    GlobalEventBus bus;

I noticed instance of GlobalEventBus was different than the one injected when initializing the LogService earlier, which breaks what I’m trying to do. So I tried to inject instances of LogService and GlobalEventBus in Editor screens to see which ones I would have. I also tried with custom @Component classes.

My conclusion is that Injection reliabily provides Singletons each time except the first one, and I’m not sure to understand why.

This is confirmed with the log :

2017-04-26 11:21:52.280 INFO  [localhost-startStop-1] com.busy.app.service.LogInterceptor - init com.busy.app.service.LogInterceptor@457cdf4b
2017-04-26 11:21:52.280 INFO  [localhost-startStop-1] com.busy.app.service.LogInterceptor - init EB com.busy.app.events.GlobalEventBus@815cb90
2017-04-26 11:22:01.075 INFO  [http-nio-8080-exec-3/app/admin] com.busy.app.web.screens.ExtAppMainWindow - EAMWLS : com.busy.app.service.LogInterceptor@38a1e44f
2017-04-26 11:22:01.076 INFO  [http-nio-8080-exec-3/app/admin] com.busy.app.web.screens.ExtAppMainWindow - EAMWLSEB : com.busy.app.events.GlobalEventBus@75a88945
2017-04-26 11:22:09.151 INFO  [http-nio-8080-exec-11/app/admin] com.busy.app.web.customer.CustomerEdit - CE : com.busy.app.events.GlobalEventBus@75a88945
2017-04-26 11:22:09.152 INFO  [http-nio-8080-exec-11/app/admin] com.busy.app.web.customer.CustomerEdit - CELS : com.busy.app.service.LogInterceptor@38a1e44f
2017-04-26 11:22:09.152 INFO  [http-nio-8080-exec-11/app/admin] com.busy.app.web.customer.CustomerEdit - CELSEB : com.busy.app.events.GlobalEventBus@75a88945

You can see the the two first instances are different than the ones injected afterwards. I suspect that this is because the first injections are done through direct Spring, and the subsequent ones are done through CUBA own injection mechanism. And they are not sharing the same list of singletons. Am I right ?

Working with this assumption, I thought about using @Autowired everywhere, even if less elegant, in order to bypass CUBA injection. But CUBA classes are not @Component so it will not work. Thus I’m stuck here.

Any guidance appreciated. If I cannot make it work with injection, I’ll have to rely on the old and ugly Singleton anti-pattern with static ‘getInstance()’.

Mike

You can see the the two first instances are different than the ones injected afterwards.

Sometimes, Spring decides to Inject proxies instead of concrete instances, so your experiment is incorrect. We obtain beans from Spring context using ApplicationContext.getBean(java.lang.Class) in com.haulmont.cuba.gui.ControllerDependencyInjector, in this case Spring can produce proxies too.

In fact, @Inject is also provided by Spring if we are talking about beans. All @Autowired / @Named / @Inject in cuba screens are handled by our ControllerDependencyInjector.

Please note, that you cannot convert a screen to Spring bean, since they have completely custom life cycle.

One additional thing: your application consist of two applications - core and web. If you define your beans in global module Spring will create two instances - one in middleware application and one in web application.

Sometimes, Spring decides to Inject proxies instead of concrete instances, so your experiment is incorrect.

I have confirmed through debugging that it was distinct instances with different attributes values, which would not be the case with proxies

One additional thing: your application consist of two applications - core and web

Good point, here I follow you, thus I need to manage this case

Thanks Yuriy

In case you really want to implement application scope Singleton bean I recommend that you create:

  1. Bean class in core module with @Component annotation
  2. Service (using Studio or manually) that will give access to this singleton to clients (web / rest)
  3. (Optional) Common interface that will provide access to singleton in core using Service (from clients) or direct bean access (from core).

Using this technique we implemented DataManager. It is a bean that uses DataService if called from web or DataWorker if called from middleware.

But! If you target cluster environments do not forget to implement cluster sync mechanisms or use shared data storage, for instance database.

As you noticed I have open a new thread with a small sample project here in order to clarify this point.

This topic can be cleared.