Бины @ApplicationScoped создаются более одного раза - PullRequest
0 голосов
/ 19 ноября 2018

У меня есть два управляемых Java-бина:

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);
    }
    ...
}

При развертывании приложения в Tomcat я получаю следующий вывод в catalina.out:

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

Итак, сначала создается экземпляр каждого компонента, как и ожидалось, а второй компонент вводится в первый. Но затем создается другой экземпляр второго класса бинов. Как это возможно? В этом уроке я нашел следующее:

@ ApplicationScoped

Бин живет столько, сколько живет веб-приложение. Это создается на первый HTTP-запрос, включающий этот компонент в приложении (или когда веб-приложение запускается, и атрибут eager = true устанавливается в @ManagedBean) и уничтожается при закрытии веб-приложения.

Таким образом, я ожидал, что один экземпляр каждого компонента создается при запуске приложения, и оба экземпляра уничтожаются при завершении работы приложения. Но LdapDirectoryAccess строится дважды.

Более того, когда я открываю страницу, обслуживаемую SyncService, я вижу:

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

поэтому второй экземпляр SyncService также построен, и я не могу понять, почему. Кроме того, на этот раз свойство directoryAccess не вводится, и служба генерирует исключение нулевого указателя.

Это означает, что первый экземпляр SyncService построен правильно, но затем

  1. Создается второй экземпляр SyncService (почему?)
  2. Нет LdapDirectoryAccess вводится в него (почему?)
  3. Этот второй экземпляр SyncService используется для обслуживания вызова моего REST API. Почему этот экземпляр, а не первый, который был создан?

Я посмотрел на этот вопрос и его ответы, однако:

  • Я использую Мохарру 2.2.18
  • В моем приложении web.xml нет тегов с упоминанием com.sun.faces.config.ConfigureListener

Так что я после нескольких часов расследования у меня совершенно нет идей. У вас есть какие-нибудь подсказки?

1 Ответ

0 голосов
/ 19 ноября 2018

Создан второй экземпляр SyncService (почему?)

Поскольку две разные структуры, совершенно не осведомленные друг о друге, получают инструкции по управлению (созданию и использованию) этого.

  1. JAX-RS, через @Path
  2. JSF, через @ManagedBean

Таким образом, в результате у вас есть один экземпляр SyncService, который управляется JAX-RS, и у вас есть другой экземпляр SyncService, который управляется JSF, и только в этом случае, также специфичный для JSF @ManagedProperty признается. JAX-RS не понимает @ManagedProperty и поэтому ничего с ним не делает.

По сути, вы здесь тесно связываете ресурс JAX-RS и управляемый компонент JSF в одном и том же классе. Тесная связь - плохая практика программирования. Вам нужно разделить SyncService на один независимый ресурс JAX-RS и один независимый управляемый компонент JSF. И вам нужно преобразовать LdapDirectoryAccess, чтобы использовать другую инфраструктуру управления bean-компонентами, которая распознается как JAX-RS, так и JSF, чтобы в обоих случаях можно было внедрить один экземпляр. В современной Java EE 8 это будет bean-компонент, управляемый CDI @javax.enterprise.context.ApplicationScoped. В устаревшей Java EE 6/7 вы можете использовать для этого EJB @javax.ejb.Singleton.

Учитывая, что вы все еще используете устаревший @ManagedBean вместо его замены @Named, я предполагаю, что вы все еще используете устаревшую Java EE, поэтому я покажу только подход EJB.

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);
    }
}

Обратите внимание, что для вставки EJB в ресурс JAX-RS может потребоваться дополнительная настройка в Java EE 6/7, для этого см. Первую ссылку в списке ниже. И, если вы хотите LdapDirectoryAccessService для активной инициализации во время запуска сервера, добавьте аннотацию @javax.ejb.Startup.

Смотри также:

...