Истекает конкретный управляемый экземпляр bean после временного интервала

У меня есть 2 управляемых JSF beans A и B, и мне нужно истечь/разрушить/уничтожить A через 2 минуты и B через 5 минут. Я проверил этот связанный вопрос Вывод из bean, но истекает весь сеанс. Я не хочу истекать целую сессию.

Как я могу достичь этого с помощью настраиваемой области?

Ответы

Ответ 1

Учитывая, что вы используете средство управления JSF bean (и, следовательно, не CDI, для которого требуется совершенно другой ответ), вы можете достичь этого с помощью @CustomScoped. Значение @CustomScoped должно ссылаться на реализацию Map в более широкой, обычно существующей области.

Что-то вроде:

@ManagedBean
@CustomScoped("#{timeoutScope}")
public class TimeoutBean {}

Поскольку аннотация @CustomScoped не поддерживает передачу дополнительных аргументов, установка таймаута может быть выполнена только с помощью дополнительной пользовательской аннотации, как показано ниже:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Timeout {

    /** Minutes. */
    int value();

}
@ManagedBean
@CustomScoped("#{timeoutScope}")
@Timeout(5) // Expires after 5 minutes.
public class TimeoutBean {}

Теперь вот пример запуска, как выглядит #{timeoutScope}, включая поддержку @PostConstruct (автоматически) и @PreDestroy (вручную):

@ManagedBean
@SessionScoped
public class TimeoutScope extends HashMap<String, Object> {

    private static final long serialVersionUID = 1L;

    @Override
    public Object put(String name, Object bean) {
        Timeout timeout = bean.getClass().getAnnotation(Timeout.class);

        if (timeout == null) {
            throw new IllegalArgumentException("@Timeout annotation is required on bean " + name);
        }

        Long endtime = System.nanoTime() + (timeout.value() * (long) 6e10);
        Object[] beanAndEndtime = new Object[] { bean, endtime };
        return super.put(name, beanAndEndtime);
    }

    @Override
    public Object get(Object key) {
        Object[] beanAndEndtime = (Object[]) super.get(key);

        if (beanAndEndtime == null) {
            return null;
        }

        Object bean = beanAndEndtime[0];
        Long endtime = (Long) beanAndEndtime[1];

        if (System.nanoTime() > endtime) {
            String name = (String) key;
            ScopeContext scope = new ScopeContext("timeoutScope", Collections.singletonMap(name, bean));
            FacesContext context = FacesContext.getCurrentInstance();
            context.getApplication().publishEvent(context, PreDestroyCustomScopeEvent.class, scope);
            return null;
        }

        return bean;
    }

}

Понимаете, это сеанс с областью действия и реализует Map. Что касается области действия, то она привязана к определенному сеансу пользователя, а не ко всему приложению. Если вы действительно хотите разделить bean на все пользовательские сеансы в приложении, тогда вместо этого сделайте его областью действия. Что касается Map, то, как JSF должен найти управляемый bean, он сначала пытается get(). Если он возвращает null (т.е. bean еще не существует), он автоматически создаст управляемый экземпляр bean и выполнит put().

Внутри put() это вопрос извлечения и вычисления тайм-аута и сохранения его на карте. Внутри get() вы просто проверяете таймаут и возвращаете null, чтобы указать JSF, что bean больше не существует. Затем JSF просто автоматически создаст его и вернется в put() и т.д.

Заметьте, что я использую System#nanoTime() вместо System#currentTimeMillis(), поскольку последний привязан к времени ОС (операционной системы), а не к аппаратурному времени (и, таким образом, он чувствителен к изменениям времени DST и контролируемых пользователем).