前文實現了基於 ThreadLocal 的請求級緩存,遺留關鍵問題:請求結束後如何準確清理緩存,防止內存洩漏與數據污染。最佳時機是在業務方法執行完成後統一釋放。
項目基於 Dubbo 框架,利用其 Filter 機制(類似 AOP 環繞通知)在服務提供方攔截調用鏈,於 invoker.invoke() 返回前清理 ThreadLocal 緩存。
@Activate
public class ThreadLocalCacheFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ThreadLocalCacheFilter.class);
@Autowired
private CacheManager cacheManager;
private void clearCache() {
// 因 Filter 加載機制限制,Spring 注解注入可能失效,採用手動獲取 Bean
if (cacheManager == null) {
ApplicationContext ctx = ApplicationContextUtils.getApplicationContext();
cacheManager = ctx.getBean(ThreadLocalCacheManager.class);
}
Collection<String> cacheNames = cacheManager.getCacheNames();
if (cacheNames != null) {
for (String name : cacheNames) {
cacheManager.getCache(name).clear();
}
}
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result = invoker.invoke(invocation); // 執行業務邏輯
logger.info("release cache start");
clearCache(); // 業務結束,清理緩存
logger.info("release cache end");
return result;
}
}
group:指定生效端(provider/consumer)value:Filter 名稱,用於配置文件引用before/after/order:控制 Filter 執行順序resources/META-INF/dubbo/(或 services/、dubbo/internal/)創建文件 com.alibaba.dubbo.rpc.FilterthreadLocalCacheFilter=com.filter.ThreadLocalCacheFilter<dubbo:provider filter="threadLocalCacheFilter" />Dubbo 內部 ProtocolFilterWrapper.buildInvokerChain() 負責加載所有 Filter,按排序構建調用鏈:
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
.getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
@Override public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation); // 責任鏈傳遞
}
// ... 其他方法委託給 last
};
}
}
return last;
}
每個 Filter 的 invoke() 可決定是否調用 next.invoke() 繼續鏈路,最終到達真實業務邏輯。我們的清理邏輯置於 next.invoke() 之後,確保業務完成後執行。
ApplicationContextUtils 手動獲取 Bean 為通用變通方案。