使用 Spring @Cacheable 和 @PostFilter

Using Spring @Cacheable with @PostFilter

我正在尝试在 Spring 中同时使用 @Cacheable@PostFilter 注释。期望的行为是应用程序将缓存完整的、未过滤的段列表(这是一个非常小且非常频繁引用的列表,因此性能是期望的),但是用户将只能根据他们的角色访问某些段。

我一开始在一个方法上同时使用 @Cacheable@PostFilter,但当它不起作用时,我将它们分成两个单独的 类 这样我就可以有一个每个方法的注释。但是,无论哪种方式,它的行为似乎都相同,也就是说,当用户 A 第一次访问该服务时,他们获得了正确的过滤列表,然后当用户 B 接下来访问该服务时,他们得不到任何结果,因为缓存仅存储用户 A 的过滤结果,用户 B 无权访问任何结果。 (因此 PostFilter 仍在运行,但缓存似乎存储的是过滤后的列表,而不是完整列表。)

所以这里是相关代码:

配置:

@Configuration
@EnableCaching
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class BcmsSecurityAutoConfiguration { 

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
                new ConcurrentMapCache("bcmsSegRoles"),
                new ConcurrentMapCache("bcmsSegments")
        ));
        return cacheManager;
    }
}

服务:

@Service
public class ScopeService {

    private final ScopeRepository scopeRepository;

    public ScopeService(final ScopeRepository scopeRepository) {
        this.scopeRepository = scopeRepository;
    }

    // Filters the list of segments based on User Roles. User will have 1 role for each segment they have access to, and then it's just a simple equality check between the role and the Segment model.
    @PostFilter(value = "@bcmsSecurityService.canAccessSegment( principal, filterObject )")
    public List<BusinessSegment> getSegments() {
        List<BusinessSegment> segments = scopeRepository.getSegments();
        return segments; // Debugging shows 4 results for User A (post-filtered to 1), and 1 result for User B (post-filtered to 0)
    }
}

存储库:

@Repository
public class ScopeRepository {
    private final ScopeDao scopeDao; // This is a MyBatis interface.

    public ScopeRepository(final ScopeDao scopeDao) {
        this.scopeDao = scopeDao;
    }

    @Cacheable(value = "bcmsSegments")
    public List<BusinessSegment> getSegments() {
        List<BusinessSegment> segments = scopeDao.getSegments(); // Simple SELECT * FROM TABLE; Works as expected.
        return segments; // Shows 4 results for User A, breakpoint not hit for User B cache takes over.
    }
}

有谁知道为什么缓存似乎在过滤器运行后存储服务方法的结果,而不是像我一样在存储库级别存储完整的结果集'我期待它应该?或者有另一种方法来实现我想要的行为?

如果您知道我如何在服务中的同一方法上优雅地实现缓存和过滤,则加分。我只构建了多余的存储库,因为我认为拆分方法可以解决缓存问题。

原来Spring缓存的内容是可变的,@PostFilter注释修改了返回的列表,它不会过滤成新的。

所以当@PostFilter 运行 在我上面的服务方法调用之后,它实际上是从缓存中存储的列表中删除项目,所以第二个请求只有 1 个结果开始,第三个请求将有零。

我的解决方案是将服务修改为 return new ArrayList<>(scopeRepo.getSegments());,这样 PostFilter 就不会更改缓存列表。

(注意,这当然不是深度克隆,所以如果有人修改了服务上游的 Segment 模型,它也可能会更改缓存中的模型。所以这可能不是最好的解决方案,但是它适用于我的个人用例。)

我不敢相信Spring缓存是可变的...