Ответ 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 и контролируемых пользователем).