如何在 Jakarta EE9 的定时器中实现拦截器

How to implement an interceptor in a timer in jakarta EE9

我想在 JAKARTA EE9 中有一个无状态会话 bean,我每 10 秒在控制台上打印一次“hello”。这是通过以下代码实现的。

@jakarta.ejb.Stateless(name = "MyTimerEJB")
public class MyTimerBean {
    @Schedule(hour ="*", minute = "*", second = "*/10")
    public void printTime() {
        System.out.println("good day");
    }
}

但是现在,我还想添加一个拦截器,在周六和周日拦截这个,然后打印“周末好”。我尝试了很多东西,但似乎没有任何效果。不幸的是,文档也有一些限制。

If you check out the EJB specs, section 7.4, you will find about the AroundTimeout annotation.

In short, all you need is a normal, enabled interceptor for the bean you are interested in, with a method annotated with @AroundTimeout.

Having said that, I hope that the use case you describe is only an example. As a matter of fact I find it bad practice to use an interceptor to change the behavior of the program as radically as you describe. I would find it very hard to reason about the functionality of such a program. Interceptors are good to implement cross-cutting concerns (e.g. transactions, logging, authorization) that do not mess with the core logic of the intercepted code.

If I were to implement this use case I would:

  • Create a CDI SayHello bean that can knows how to decide what message to output depending on the day (or whatever)
    • Preferably, I would implement the "clock" functionality in another bean I can mock for unit tests
  • Use the scheduler only to invoke SayHello, nothing more

Since it proved to be useful, I am briefly re-iterating the main points of the JEE tutorial on interceptors:

WARNING: the following is a brief summary, please follow the tutorials and documentation for concrete usage

To define an interceptor, use one of the interceptor metadata annotations: javax.interceptor.AroundConstruct, javax.interceptor.AroundInvoke, javax.interceptor.AroundTimeout, javax.annotation.PostConstruct, javax.annotation.PreDestroy on a method defined on a component that the container will recognize. Could be the same component as the one containing the intercepted method or a different one.

If the interceptor is in a different class, use javax.interceptor.Interceptors to activate it. This annotation can go to a specific method, or on an entire class, in which case all business methods are intercepted. Remember, interceptors do not take effect on non-business methods (private/protected ones are definitely NOT business and a common mistake). Interceptors do NOT take effect when business calling methods of this object, another very common mistake, beware!

Alternatively you can define an interceptor binding annotation, e.g. (from the tutorial):

@InterceptorBinding // this makes it an interceptor binding annotation
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Inherited
pubic @interface Logged { ... }

Now annotating a class or method with @Logged will activate the interceptor defined as:

@Logged
@Interceptor
public class LoggingInterceptor {
    @AroundInvoke
    public Object logInvocation(InvocationContext ctx) throws Exception {
        ...
    }
    ...
}

To actually activate the interceptor you have to use the deployment descriptor (depending on whether you are using it on EJB or CDI or both) or use the @Priority annotation.