简体   繁体   中英

@ApplicationScoped beans get constructed more than once

I have two managed Java beans:

import javax.annotation.PostConstruct;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

import javax.ws.rs.Path;

@Path("/sync")
@ManagedBean(name="syncService", eager=true)
@ApplicationScoped
public class SyncService {
    @ManagedProperty(value="#{ldapDirectoryAccess}")
    private DirectoryAccess directoryAccess;

    public void setDirectoryAccess(DirectoryAccess directoryAccess) {
        System.out.println("SyncService.setDirectoryAccess()");
        this.directoryAccess = directoryAccess;
    }

    public SyncService() {
        System.out.println("SyncService() - constructed: " + this);
    }

    @PostConstruct
    public void init() {
        System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
    }
    ...
}

@ManagedBean(name="ldapDirectoryAccess", eager=true)
@ApplicationScoped
public class LdapDirectoryAccess implements DirectoryAccess {
    public LdapDirectoryAccess() {
        System.out.println("LdapDirectoryAccess constructed: " + this);
    }
    ...
}

When I deploy the application in Tomcat, I get the following output in catalina.out :

SyncService() - constructed: ...SyncService@705ebb4d
...
LdapDirectoryAccess constructed: ...LdapDirectoryAccess@3c1fd5aa
SyncService.setDirectoryAccess()
DirectoryAccess injected: ...LdapDirectoryAccess@3c1fd5aa in:
                          ...SyncService@705ebb4d
LdapDirectoryAccess constructed: ...LdapDirectoryAccess@59d6a4d1

So, first an instance of each bean is constructed as expected, and the second bean is injected into the first. But then, another instance of the second bean class is created. How is this possible? In this tutorial I found the following:

@ApplicationScoped

Bean lives as long as the web application lives. It gets created upon the first HTTP request involving this bean in the application (or when the web application starts up and the eager=true attribute is set in @ManagedBean) and gets destroyed when the web application shuts down.

So I would expected that one instance of each bean is created when the application is started, and that both instances are destroyed when the application is shut down. But LdapDirectoryAccess is constructed twice.

Moreover, when I open the page that is served by SyncService I see:

SyncService() - constructed: ... SyncService@1cb4a09c

so a second instance of SyncService is built as well, and I cannot understand why. Also, no directoryAccess property is injected this time, and the service throws a null pointer exception.

This means that the first instance of SyncService is built correctly, but then

  1. A second instance of SyncService is created (why?)
  2. No LdapDirectoryAccess is injected into it (why?)
  3. This second instance of SyncService is used to serve the call to my REST API. Why this instance and not the first one that was created?

I have looked at this question and its answers, however:

  • I am using Mojarra 2.2.18
  • My application's web.xml does not contain any tag mentioning com.sun.faces.config.ConfigureListener

So I after several hours investigation I am completely out of ideas. Do you have any hints?

A second instance of SyncService is created (why?)

Because two different frameworks, completely unaware of each other, are being instructed to manage (instantiate and use) it.

  1. JAX-RS, via @Path
  2. JSF, via @ManagedBean

So, in effects, you have one instance of SyncService which is managed by JAX-RS, and you have another instance of SyncService which is managed by JSF, and only in this instance, the also JSF-specific @ManagedProperty is recognized. JAX-RS doesn't understand the @ManagedProperty and thus does nothing with it.

Basically, you're here tight-coupling a JAX-RS resource and a JSF managed bean in the very same class. Tight-coupling is a bad programming practice. You need to split out SyncService into one independent JAX-RS resource and one independent JSF managed bean. And you'd need to convert the LdapDirectoryAccess to use another bean management framework which is recognized by both JAX-RS and JSF, so that a single instance can be injected in both. In modern Java EE 8, that would be a bean managed by CDI's @javax.enterprise.context.ApplicationScoped . In legacy Java EE 6/7, you could abuse EJB's @javax.ejb.Singleton for that.

Given that you're still using the deprecated @ManagedBean instead of its replacement @Named , I'll assume that you're still on legacy Java EE, so I'll show only the EJB approach.

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;

@Singleton
public class LdapDirectoryAccessService implements DirectoryAccess {

    @PostConstruct
    public void init() {
        System.out.println("LdapDirectoryAccess constructed: " + this);
    }
}

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ws.rs.Path;

@Path("/sync")
public class SyncResource {

    @EJB
    private DirectoryAccess directoryAccess;

    @PostConstruct
    public void init() {
        System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
    }
}

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.RequestScoped;
import javax.faces.bean.ManagedBean;

@ManagedBean
@RequestScoped
public class SyncBacking {

    @EJB
    private DirectoryAccess directoryAccess;

    @PostConstruct
    public void init() {
        System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
    }
}

Note that being able to inject an EJB in a JAX-RS resource might require additional configuration in Java EE 6/7, for that see the first link of the list below. And, in case you want to LdapDirectoryAccessService to eagerly initialize during server's startup, add the @javax.ejb.Startup annotation.

See also:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM