在特定的时间间隔后使特定的托管Bean实例失效

5
我有两个JSF管理的bean,AB,我需要在2分钟后使A过期/销毁,在5分钟后使B过期/销毁。我查看了这个相关问题Timing out from a bean,但它会过期整个会话。我不想过期整个会话。
如何使用自定义范围实现这一点?

如果容器正在管理您的Bean,则无法这样做。 - Trash Can
@Dummy 它们是 JSF/管理 Bean,不是 EJBs!! 我正在管理 JSF Bean 的作用域!! - Junaid
好的,你没有明确说明,从技术上讲,你可以使用会话Bean作为JSF后备Bean。如果是这样,你有没有考虑过计时器服务? - Trash Can
你真的需要销毁它们吗?还是有其他用例,你认为销毁它们是唯一的解决方案?这是多年来我第一次听到这个要求。 - Kukeltje
@Kukeltje 我真的想摧毁这些豆子。 - Junaid
显示剩余2条评论
1个回答

7
考虑到您正在使用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(),因为后者与操作系统时间相关,而不是与硬件时间相关(因此对夏令时和终端用户控制的时间更敏感)。

那么没有办法自动支持 @PreDestroy 吗?我实际上希望在 bean 的时间结束时立即调用 @PreDestroy - Junaid
也许您错过了更新的答案,或者实际上并没有尝试,或者没有逐行阅读代码? - BalusC
我仔细阅读了您的更新答案和代码,我的理解是 @PreDestroy 支持是手动的而不是自动的!它能够自动执行吗?例如,当 Bean 超时时,它会自动调用 get@PreDestroy 吗?如果我在您的回答中漏掉了任何内容,请告诉我。谢谢。 - Junaid
2
作为提示:在正式的英语中,永远不要使用“!!”或“??”,也不要过度使用粗体格式。这样你会显得很讨厌,我很想删除答案并忽略你未来的问题。至于评论中的问题,代码不会以编程方式触发@PostConstruct,因为JSF已经自动完成了这个任务。代码只会手动触发@PreDestroy,因为JSF不会自动完成这个任务。publishEvent()行就是这样做的。在提出新问题之前,请先尝试一下。 - BalusC
谢谢回复和提示。我会在以后注意的。 - Junaid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接