简体   繁体   English

使用 Spring @Cacheable 和 @PostFilter

[英]Using Spring @Cacheable with @PostFilter

I'm attempting to use both the @Cacheable and @PostFilter annotations in Spring.我正在尝试在 Spring 中同时使用@Cacheable@PostFilter注释。 The desired behavior is that the application will cache the full, unfiltered listed of Segments (it's a very small and very frequently referenced list so performance is the desire), but that a User will only have access to certain Segments based on their roles.期望的行为是应用程序将缓存完整的、未过滤的 Segments 列表(这是一个非常小且非常频繁引用的列表,因此需要性能),但用户将只能根据其角色访问某些 Segments。

I started out with both @Cacheable and @PostFilter on a single method, but when that wasn't working I broke them out into two separate classes so I could have one annotation on each method.我开始在一个方法上同时使用@Cacheable@PostFilter ,但是当它不起作用时,我将它们分成两个单独的类,这样我就可以在每个方法上都有一个注释。 However, it seems to behave the same either way I do it, which is to say when User A hits the service for the first time they get their correct filtered list, then when User B hits the service next they get NO results because the cache is only storing User A's filtered results, and User B does not have access to any of them.但是,无论我这样做,它的行为似乎都是一样的,也就是说,当用户 A 第一次点击服务时,他们得到了正确的过滤列表,然后当用户 B 下次点击服务时,他们没有得到任何结果,因为缓存仅存储用户 A 的过滤结果,用户 B 无权访问其中任何一个。 (So the PostFilter still runs, but the Cache seems to be storing the filtered list, not the full list.) (所以 PostFilter 仍在运行,但 Cache 似乎存储的是过滤后的列表,而不是完整的列表。)

So here's the relevant code:所以这里是相关的代码:

configuration:配置:

@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:服务:

@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:存储库:

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

Does anyone know why the Cache seems to be storing the result of the Service method after the filter runs, rather than storing the full result set at the Repository level as I'm expecting it should?有谁知道为什么缓存似乎在过滤器运行存储 Service 方法的结果,而不是像我期望的那样在存储库级别存储完整的结果集? Or have another way to achieve my desired behavior?或者有另一种方法来实现我想要的行为?

Bonus points if you know how I could gracefully achieve both caching and filtering on the same method in the Service.如果您知道我如何在服务中的相同方法上优雅地实现缓存和过滤,则可以加分。 I only built the superfluous Repository because I thought splitting the methods would resolve the caching problem.我只构建了多余的存储库,因为我认为拆分方法可以解决缓存问题。

Turns out that the contents of Spring caches are mutable, and the @PostFilter annotation modifies the returned list, it does not filter into a new one.原来 Spring 缓存的内容是可变的, @PostFilter注释修改了返回的列表,它没有过滤成新的列表。

So when @PostFilter ran after my Service method call above it was actually removing items from the list stored in the Cache, so the second request only had 1 result to start with, and the third would have zero.因此,当@PostFilter 在我上面的 Service 方法调用之后运行时,它实际上是从存储在缓存中的列表中删除项目,因此第二个请求只有 1 个结果开始,而第三个请求将为零。

My solution was to modify the Service to return new ArrayList<>(scopeRepo.getSegments());我的解决方案是修改服务以return new ArrayList<>(scopeRepo.getSegments()); so that PostFilter wasn't changing the cached list.这样 PostFilter 就不会更改缓存列表。

(NOTE, that's not a deep clone of course, so if someone modified a Segment model upstream from the Service it would likely change in the model in the cache as well. So this may not be the best solution, but it works for my personal use case.) (注意,这当然不是深度克隆,所以如果有人在服务上游修改了段 model,它可能也会在缓存中的 model 中发生变化。所以这可能不是最好的解决方案,但它适用于我个人用例。)

I can't believe Spring Caches are mutable...我不敢相信 Spring 缓存是可变的......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM