为了账号安全,请及时绑定邮箱和手机立即绑定

在 Java CDI 拦截器中获取调用者类

在 Java CDI 拦截器中获取调用者类

蛊毒传说 2024-01-05 15:00:33
我正在尝试实现一个缓存,该缓存保存特定业务方法调用的结果,然后每 30 分钟刷新一次。我能够通过使用调度方法使用单例 EJB 来实现这一点;但是,现在每个调用该业务方法的类都必须从公开缓存结果的单例中调用方法。我想避免这种行为并保持这些类中的代码不变,因此我想到使用一个拦截器来拦截对该特定业务方法的每个调用,并返回缓存单例的结果。但是,此解决方案会导致应用程序停止,因为单例调用拦截的业务方法本身来缓存其结果,因此拦截器会拦截该调用(请原谅重复)并尝试返回公开缓存值的单例方法的结果,而单例仍在等待对业务方法的调用继续进行。最明显的解决方案是从拦截器中获取方法调用者,并检查其类是否对应于单例;如果是,则继续调用,否则返回单例的缓存结果。但是,拦截器使用的对象似乎InvocationContext没有公开任何方法来访问有关被拦截方法的调用者的信息。有没有其他方法可以访问调用者的类,或者有解决此问题的方法吗?这是我的单例类:@Singleton@Startuppublic 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.编辑:我这样修改了拦截器方法:@AroundInvokepublic 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:如果还不够清楚,我需要访问有关被拦截方法的调用者类的信息,而不是访问其方法被拦截的被调用类的信息;这就是为什么我要迭代堆栈跟踪来访问调用者的类,但我认为这不是一个优雅的解决方案,即使它有效。
查看完整描述

3 回答

?
饮歌长啸

TA贡献1951条经验 获得超3个赞

对于你需要做的事情,我会说使用拦截器或装饰器。然而你的拦截器是错误的。首先,您缺少基本部分,即对将调用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。它可能更适合您的场景,请查看CDI 规范解释装饰器的这一部分。


查看完整回答
反对 回复 2024-01-05
?
慕容3067478

TA贡献1773条经验 获得超3个赞

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

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


查看完整回答
反对 回复 2024-01-05
?
慕村225694

TA贡献1880条经验 获得超4个赞

有一个误会。您不将被拦截的对象注入到拦截器中,而是使用调用上下文。只需调用即可invocationContext.proceed(),无需递归。您可以缓存proceed() 的结果。



查看完整回答
反对 回复 2024-01-05
  • 3 回答
  • 0 关注
  • 112 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信