我正在尝试实现一个缓存,该缓存保存特定业务方法调用的结果,然后每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
。
EDIT:我以这种方式修改了拦截器方法:
@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();
}
但是我怀疑这是最干净的解决方案,并且我不知道它是否可移植。
存在误解。您无需将被拦截的对象注入拦截器,而应使用invocationContext。您只需要调用invocationContext.proceed()
,就没有递归。您可以缓存的proced()结果。
迭代堆栈跟踪以检查TopAlbumsHolder
是否存在是一种好方法。为了避免在从getTopAlbums()
类调用DataAgent
期间调用拦截器,您可以直接在DataAgent
中指定调度程序,该调度程序将收集数据并将其推入TopAlbumsHolder
。您可以使用另一种方法来做,但是要点是直接在没有参与代理的情况下直接调用DataAgent bean中的getTopAlbums()
(在这种情况下,拦截器将不适用)。
P.S。请注意,缓存的数据应该是不变的(集合及其对象)。