0

每 x 分钟我想查询新实例并缓存结果。我目前只需要一个简单的缓存解决方案,所以我想更新Set我的@ApplicationScoped CacheBean

我试过一个:

ScheduledExecutorService scheduler = Executors
        .newScheduledThreadPool(1);
    ScheduledFuture<?> sf = scheduler.scheduleAtFixedRate(new Runnable() {
        public void run() {
//.................

但是创建的线程无法访问任何上下文实例(InvocationException)。

那么如何以 CDI/JPA 方式做到这一点呢?

使用 Tomcat 7、焊接、JPA2 - 休眠。

4

3 回答 3

2

我的建议是尝试已集成 CDI 和 JPA 的 Tomcat 版本(TomEE)。它带有 OpenJPA,但您可以使用 Hibernate。然后使用这样的类进行缓存:

@Singleton
@Startup
public class CachingBean {

    @Resource 
    private BeanManager beanManager;

    @Schedule(minute = "*/10", hour = "*")
    private void run() {
        // cache things
    }
}

该组件将在应用程序启动时自动启动,并每十分钟运行一次上述方法。有关详细信息,请参阅计划文档。

更新

给你举了一个例子。使用良好的 CDI/EJB 组合来安排 CDI 事件。

BeanManager.fireEvent(Object,Annotations...)实际上,这是对添加ScheduleExpression到混合中的方法的简单包装。

@Singleton
@Lock(LockType.READ)
public class Scheduler {

    @Resource
    private TimerService timerService;

    @Resource
    private BeanManager beanManager;

    public void scheduleEvent(ScheduleExpression schedule, Object event, Annotation... qualifiers) {

        timerService.createCalendarTimer(schedule, new TimerConfig(new EventConfig(event, qualifiers), false));
    }

    @Timeout
    private void timeout(Timer timer) {
        final EventConfig config = (EventConfig) timer.getInfo();

        beanManager.fireEvent(config.getEvent(), config.getQualifiers());
    }

    // Doesn't actually need to be serializable, just has to implement it
    private final class EventConfig implements Serializable {

        private final Object event;
        private final Annotation[] qualifiers;

        private EventConfig(Object event, Annotation[] qualifiers) {
            this.event = event;
            this.qualifiers = qualifiers;
        }

        public Object getEvent() {
            return event;
        }

        public Annotation[] getQualifiers() {
            return qualifiers;
        }
    }
}

然后使用它,Scheduler作为 EJB 注入并安排离开。

public class SomeBean {

    @EJB
    private Scheduler scheduler;

    public void doit() throws Exception {

        // every five minutes
        final ScheduleExpression schedule = new ScheduleExpression()
                .hour("*")
                .minute("*")
                .second("*/5");

        scheduler.scheduleEvent(schedule, new TestEvent("five"));
    }

    /**
     * Event will fire every five minutes
     */ 
    public void observe(@Observes TestEvent event) {
        // process the event
    }

}

完整的源代码和工作示例,在这里

你必须知道

  • CDI 事件不是多线程的

如果有 10 个观察者,每个观察者需要 7 分钟执行,那么一个事件的总执行时间是 70 分钟。将事件安排为比 70 分钟更频繁地触发对您绝对没有好处。

如果你这样做了会发生什么?取决于@Singleton @Lock政策

  • @Lock(WRITE)是默认值。在这种模式下,该timeout方法将基本上被锁定,直到前一次调用完成。即使您每 70 分钟只能处理一次,但每 5 分钟触发一次,最终会导致所有池化计时器线程都在您的 Singleton 上等待。
  • @Lock(READ)允许并行执行该timeout方法。事件将并行触发一段时间。然而,由于它们实际上每个都需要 70 分钟,所以在一个小时左右的时间内,我们将用完计时器池中的线程,就像上面一样。

优雅的解决方案是使用然后在方法上@Lock(WRITE)指定一些短的超时。当接下来的 5 分钟调用被触发时,它会等到 1 分钟才能访问 Singleton,然后才放弃。这将使您的计时器池不会被备份的作业填满——“溢出”被简单地丢弃。@AccessTimeout(value = 1, unit = TimeUnit.MINUTES)timeout

于 2012-10-18T04:53:37.247 回答
0

与其将 new Runnable() {....} 传递给 scheduler.scheduleAtFixedRate ,不如创建一个实现 Runnable 的 CDI bean 和 @Inject 该 bean,然后将其传递给 scheduler.scheduleAtFixedRate

于 2012-10-19T06:31:37.803 回答
0

在与 David Blevins 聊了一会儿之后,我可以承认他的回答是一个很棒的回答,我投了赞成票。非常感谢这一切。尽管大卫你忘了宣布你参与 TomEE,但我知道这总是会打扰别人。

无论如何,我所寻求的解决方案是由 Mark Struberg 在#Deltaspike (freenode) 中提出的。

作为一个 deltaspike 用户,我很高兴使用 deltaspike 来做到这一点。此博客文章中概述了解决方案:

http://struberg.wordpress.com/2012/03/17/controlling-cdi-containers-in-se-and-ee/

我不得不切换到 OWB 见https://issues.apache.org/jira/browse/DELTASPIKE-284

干杯

于 2012-10-21T08:48:18.170 回答