【问题标题】:Get caller class in Java CDI Interceptor在 Java CDI 拦截器中获取调用者类
【发布时间】:2019-10-27 21:20:44
【问题描述】:

我正在尝试实现一个缓存,它保存来自特定业务方法调用的结果,然后每 30 分钟刷新一次。

我能够通过使用预定方法的单例 EJB 来实现这一点;但是,现在调用该业务方法的每个类都必须改为从公开缓存结果的单例中调用该方法。

我想避免这种行为并保持这些类中的代码原样,所以我想使用一个拦截器来拦截对该特定业务方法的每次调用,并从缓存单例中返回结果。

但是,由于单例调用被拦截的业务方法本身来缓存其结果,因此该解决方案使应用程序停止,因此拦截器拦截调用(原谅重复)并尝试返回暴露缓存的单例方法的结果值,而单例仍在等待对业务方法的调用继续进行。

最明显的解决方案是从拦截器中获取方法调用者,并检查它是否 类对应于单例的;如果是,则继续调用,否则从单例返回缓存的结果。但是,拦截器使用的InvocationContext 对象似乎没有公开任何方法来访问有关被拦截方法的调用者的信息。有没有其他方法可以访问调用者的类,或者有解决这个问题的方法?

这是我的单例类:

@Singleton
@Startup
public class TopAlbumsHolder {

    private List<Album> topAlbums;

    @Inject
    private DataAgent dataAgent;

    @PostConstruct
    @Schedule(hour = "*", minute = "*/30", persistent = false)
    private void populateCache() {
        this.topAlbums = this.dataAgent.getTopAlbums();
    }

    @Lock(LockType.READ)
    public List<Album> getTopAlbums() {
        return this.topAlbums;
    }

}

这是我的拦截器:

@Interceptor
@Cacheable(type = "topAlbums")
public class TopAlbumsInterceptor {

    @Inject
    private TopAlbumsHolder topAlbumsHolder;

    @AroundInvoke
    public Object interceptTopAlbumsCall(InvocationContext invocationContext) throws Exception {
        // if the caller's class equals that of the cache singleton, then return invocationContext.proceed(); 
        // otherwise:
        return this.topAlbumsHolder.getTopAlbums();
    }

}

注意@Cacheable注解是自定义拦截器绑定,而不是javax.persistence.Cacheable

编辑:我这样修改了拦截器方法:

@AroundInvoke
public Object interceptTopAlbumsCall(InvocationContext invocationContext) throws Exception {
    for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace())
        if (TopAlbumsHolder.class.getName().equals(stackTraceElement.getClassName()))
            return invocationContext.proceed();
    return this.topAlbumsHolder.getTopAlbums();
}

但我怀疑这是最干净的解决方案,我不知道它是否便携。

编辑 2: 如果不够清楚,我需要访问有关被拦截方法的 invoker 类的信息,而不是具有其方法的被调用类的信息截获;这就是为什么我要遍历堆栈跟踪以访问调用者的类,但我认为这不是一个优雅的解决方案,即使它有效。

【问题讨论】:

  • dataAgent.getTopAlbums()是你口中的拦截业务方法吗?能贴出截取的业务方法的代码吗?此外,您是否希望缓存每 30 分钟刷新一次,即使没有客户端感兴趣,还是希望在客户端请求且上次刷新时间 > 30 分钟时刷新缓存?

标签: java caching singleton ejb cdi


【解决方案1】:

对于您需要做的事情,我会说使用拦截器或装饰器。 但是,您的拦截器是错误的。首先,您缺少基本部分,即对InvocationContext.proceed() 的调用,它将调用转发到下一个在线拦截器(如果有的话)或方法调用本身。其次,您放置在那里的注入点非常具体,只有在您拦截这种类型的 bean 时才会对您有所帮助。通常,环绕调用拦截器方法如下所示:

    @AroundInvoke
    Object intercept(InvocationContext ctx) throws Exception {
        // do something before the invocation of the intercepted method
        return ctx.proceed(); // this invoked next interceptor and ultimately the intercepted method
        // do something after the invocation of the intercepted method
    }

此外,如果您想要关于拦截了哪个 bean 的元数据信息,每个拦截器都可以为此注入一个特殊的内置 bean。从元数据中,您可以收集有关您当前正在拦截的 bean 的信息。以下是获取元数据的方法:

    @Inject
    @Intercepted
    private Bean<?> bean;

请注意,拦截器不知道它们拦截的是什么类型,它可以是任何类型,因此您通常需要对普通的Object 进行操作。 如果你需要更具体的东西,CDI 提供了一个装饰器模式,它基本上是一个类型感知的拦截器。它有一个特殊的注入点(一个委托),可以让您直接访问装饰的 bean。它可能更适合您的场景,请查看this part of CDI specification explaining Decorators

【讨论】:

  • 我不需要访问有关被拦截 bean 的元数据,我需要访问有关调用被拦截方法的调用者的信息。
【解决方案2】:

有误会。 您不会将被拦截的对象注入拦截器,而是使用 invocationContext。 你只需要调用invocationContext.proceed() 就没有递归了。 可以缓存proceed()的结果。

【讨论】:

  • 拦截器中注入的对象不是包含被拦截方法的类的实例;它是调用被拦截方法的类之一的实例,如果调用者的类是注入对象的类(TopAlbumsHolder),我需要明确排除对该方法的调用被拦截。
  • 为了在技术上实现这一点,第二个拦截器和 ThreadLocal 会有所帮助。因此,第二个拦截器将指示 threadlocal,即在不希望第一个拦截器正常工作的情况下输入了一个方法,并且在这种情况下,第一个拦截器只会继续执行。
【解决方案3】:

遍历堆栈跟踪以检查 TopAlbumsHolder 是否存在不是一个好方法。
为了避免在从DataAgent 类调用getTopAlbums() 期间调用拦截器,您可以直接在DataAgent 中指定调度程序,该调度程序收集数据并将其推送到TopAlbumsHolder。你可以用另一种方式来做,但你的主要观点是直接在 DataAgent bean 中调用 getTopAlbums() 而没有参与代理(在这种情况下,拦截器将不适用)。

附:请注意,缓存的数据应该是不可变的(集合及其对象)。

【讨论】:

    猜你喜欢
    • 2020-06-17
    • 2014-03-10
    • 2018-09-04
    • 2012-06-15
    • 2014-09-10
    • 2015-11-14
    • 1970-01-01
    • 2016-02-06
    • 1970-01-01
    相关资源
    最近更新 更多