【问题标题】:Using Spring @Cacheable with @PostFilter将 Spring @Cacheable 与 @PostFilter 一起使用
【发布时间】:2020-02-23 21:56:00
【问题描述】:

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

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

下面是相关代码:

配置:

@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.
    }
}

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

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

【问题讨论】:

    标签: java spring caching spring-security spring-cache


    【解决方案1】:

    原来Spring缓存的内容是可变的,@PostFilter注解修改了返回的列表,没有过滤成新的。

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

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

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

    我不敢相信 Spring Cache 是可变的......

    【讨论】:

      猜你喜欢
      • 2020-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-25
      • 2020-12-16
      • 1970-01-01
      • 1970-01-01
      • 2019-12-02
      相关资源
      最近更新 更多