SpringCacheBasedUserCache 为空

Posted

技术标签:

【中文标题】SpringCacheBasedUserCache 为空【英文标题】:SpringCacheBasedUserCache is null 【发布时间】:2017-04-07 13:25:08 【问题描述】:

我有使用 Spring Boot、Spring Security 和 Spring Data 的网络应用程序。它是无状态的。

我想避免总是为用户访问调用 db。所以我想使用 SpringCacheBasedUserCache。

@Configuration
@EnableCaching
public class CacheConfig 

    @Bean
    CacheManager cacheManager() 
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("city"), new ConcurrentMapCache("userCache")));
        return cacheManager;
    

    @Bean
    public UserCache userCache() throws Exception 

        Cache cache = (Cache) cacheManager().getCache("userCache");
        return new SpringCacheBasedUserCache(cache);
    



@EnableCaching
@Configuration
@EnableWebSecurity
public class ApplicationSecurity extends WebSecurityConfigurerAdapter 
    @Bean
    public PasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception 
        return new UserServiceImpl(commerceReposiotry, repository, defaultConfigRepository);
    
    ...

我有一个实现 UserDetails 的类和另一个实现 UserDetailsS​​ervice 的类

@Service
public class UserServiceImpl implements UserDetailsService, UserService 

    private final CommerceRepository commerceReposiotry;
    private final UserAppRepository repository;
    private final DefaultConfigRepository defaultConfigRepository;

    @Autowired
    private UserCache userCache;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public UserServiceImpl(final CommerceRepository commerceReposiotry, final UserAppRepository repository, final DefaultConfigRepository defaultConfigRepository) 
        this.commerceReposiotry = commerceReposiotry;
        this.repository = repository;
        this.defaultConfigRepository = defaultConfigRepository;
    


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 

        UserDetails user = userCache.getUserFromCache(username);
        UserApp userapp = null;

        if (user == null) 
            userapp = repository.findByUsername(username);
        

        if (userapp == null) 
            throw new UsernameNotFoundException("Username " + username + " not found");
        

        userCache.putUserInCache(user);

        return new CustomUserDetails(userapp);
    
    ...

在 loadUserByUsername 方法中,userCache 为空

【问题讨论】:

您的 UserServiceImpl 不是 Spring 管理的 bean,因此不会注入任何内容。旁边你的代码有缺陷。您放入缓存中的user 将始终为null 不要自己缓存,只需将自己的 UserDetailsService 包装在 CachingUserDetailsService 中,这将为您完成繁重的工作。 【参考方案1】:

要么将@Bean 放在userDetailsServiceBean 方法上,要么(如建议的那样)从UserDetailsService 中完全删除缓存并将其包装在CachingUserDetailsService 中,而是简单地覆盖userDetailsService 方法。

@Configuration
@EnableWebSecurity
public class ApplicationSecurity extends WebSecurityConfigurerAdapter 

    @Autowired
    private UserCache userCache;

    @Bean
    public PasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Override
    public UserDetailsService userDetailsService() throws Exception 

        UserServiceImpl userService = new UserServiceImpl(commerceReposiotry, repository, defaultConfigRepository);
        CachingUserDetailsService cachingUserService = new CachingUserDetailsService(userService);
        cachingUserService.setUserCache(this.userCache);
        return cachingUserService;
    
    ...

您的其他配置中已经有@EnableCaching,因此无需再次使用。只需将缓存注入配置类并构造一个CachingUserDetailsService,它代表您的UserDetailsService 来检索用户。

当然,您必须从自己的UserDetailsService 中删除缓存,现在可以专注于用户管理/检索,而不是与缓存混合。

Edit(1): 构造函数不是公开的,这使得创建 bean 变得更加困难。这可以使用BeanUtilsClassUtils 来实现。用以下代码替换对new 的调用应该会创建一个实例。

private UserDetailsService cachingUserDetailsService(UserDetailsService delegate) 
    Constructor<CachingUserDetailsService> ctor = ClassUtils.getConstructorIfAvailable(CachingUserDetailsService.class, UserDetailsService.class);
    return BeanUtils.instantiateClass(ctor, delegate);

编辑(2):显然我已经遇到过一次(大约 2 年前)并为此注册了this issue。

【讨论】:

CachingUserDetailsS​​ervice(UserDetailsS​​ervice) 不公开 该死的错过了构造函数不是public。奇怪的是 xml 很容易配置,但对于基于 java 的配置显然只能为jdbcAuthentication 配置(轻松)。您可以通过使用 BeanUtilsClassUtils 创建实例来解决它。 (见修改后的答案)。 好的,如果用户修改了密码,我需要删除缓存用户吗? 那么你要么需要让你的 UserDetailsService 由 Spring 管理(正如在方法上放置 @Bean 注释所提到的那样)来获取缓存并自己进行失效等。或者您可以使用 spring ApplicationListener 和发布者使其事件驱动。\ @M.Deinum 当ClassUtils.getConstructorIfAvailable() 找不到构造函数时,我错过了什么吗?我看到 3.2.x 中有一个修复程序有望很快发布

以上是关于SpringCacheBasedUserCache 为空的主要内容,如果未能解决你的问题,请参考以下文章