在 Shiro 领域中自动装配时,具有可缓存方法的 Spring 服务在没有缓存的情况下被初始化

chk*_*chk 5 spring caching autowired shiro

在这个问题上花了 2 天后,我真的无法自己取得更多进展。我正在使用 Spring 开发用于依赖注入等的标准 Web 应用程序。我还使用 Spring 来缓存我经常使用的几种昂贵的方法。

在我为安全层引入 Apache Shiro 之后,我遇到了一个奇怪的问题,@Cacheable某个服务中的方法不再被缓存。到目前为止,我已经能够将问题剥离到其核心,但仍有很多代码需要您查看 - 抱歉……

首先,我配置所有相关的包(下面显示的所有类都在其中一个)。

@Configuration
@ComponentScan(basePackages = {
        "my.package.config",
        "my.package.controllers",
        "my.package.security",
        "my.package.services",
})
public class AppConfiguration {

}
Run Code Online (Sandbox Code Playgroud)

这是缓存的配置文件。

@Configuration
@EnableCaching
public class CacheConfiguration {
    @Bean(name = "cacheManager")
    public SimpleCacheManager cacheManager() {
        SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
        simpleCacheManager.setCaches(Arrays.asList(
                new ConcurrentMapCache("datetime")
        ));
        return simpleCacheManager;
    }
}
Run Code Online (Sandbox Code Playgroud)

对于我的最小示例,我使用了一个非常简单的服务,它只返回当前时间戳。这Impl门课和你想象的一样简单。

public interface DateService {
    @Cacheable("datetime")
    LocalDateTime getCurrent();
}
Run Code Online (Sandbox Code Playgroud)

我将此服务注入控制器。

@Controller
@RequestMapping("/v1/date")
public class DateController {
    @Autowired
    DateService dateService;

    @RequestMapping(value = "/current", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<String> getCurrent() {
        Subject s = SecurityUtils.getSubject();
        s.login(new MyToken());
        return new ResponseEntity<>(dateService.getCurrent().toString(), HttpStatus.OK);
    }
}
Run Code Online (Sandbox Code Playgroud)

该应用程序是通过 Jetty 设置和启动的,到目前为止一切都按预期工作。<api-url>/v1/date/current第一次调用时返回当前时间戳,但之后总是收到缓存的结果。

现在,我用另一个配置文件介绍 Shiro。

@Configuration
public class ShiroSecurityConfiguration {

    @Bean
    @Autowired
    public DefaultSecurityManager securityManager(MyRealm realm) {
        List<Realm> realms = new ArrayList<>();
        // MyToken is a static stub for this example
        realm.setAuthenticationTokenClass(MyToken.class);
        realms.add(realm);
        DefaultSecurityManager manager = new DefaultSecurityManager(realms);
        SecurityUtils.setSecurityManager(manager);
        return manager;
    }

    // other Shiro related beans that are - at least to me - irrelevant here

    // EDIT 2: I figured out that the described problem only occurs with this bean
    // (transitively depending on DateService) in the application
    // the bean is required for annotations such as @RequiresAuthentication to work
    @Bean
    @Autowired
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,这也取决于我的服务的领域来了。

@Component
public class MyRealm extends AuthenticatingRealm {
    private static final String REALM_NAME = "MyRealm";
    @Autowired
    private DateService dateService;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("User authenticated at "+dateService.getCurrent());
        return new SimpleAuthenticationInfo("",token.getCredentials(),REALM_NAME);
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,我的整个应用程序中的缓存就被破坏了。没有错误消息,它只是不再使用缓存。我能够实施一种解决方法,但我现在正在寻求更好的解决方案,也许还有一些建议以更好地理解我的问题的本质。所以,这里是解决方法。

@Component
public class MyRealm extends AuthenticatingRealm {
    private static final String REALM_NAME = "MyRealm";
    private DateService dateService;
    @Autowired
    private ApplicationContext applicationContext;

    private void wireManually() {
        if (dateService == null) {
            dateService = applicationContext.getBean(DateService.class);
        }
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        wireManually();
        System.out.println("User authenticated at "+dateService.getCurrent());
        return new SimpleAuthenticationInfo("",token.getCredentials(),REALM_NAME);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在它恢复工作了,我能够调试原因。Shiro 因此MyRealm很早就初始化了,甚至在我SimpleCacheManager和所有相关内容(cacheInterceptor 等)的整个缓存加载之前。因此,在使用@Autowired. 使用上面显示的解决方法,在一切设置正确并且服务第一个请求之前不会注入服务,然后就没有问题了。

简单地说,只要我MyRealm依赖DateService(注释MyRealmwith的最后一个版本@DependsOn("dateServiceImpl")就足以破坏应用程序),它就会过早地初始化(即在设置缓存之前)。

所以我需要推迟 的初始化MyRealm,但我不知道该怎么做。我试过了@DependsOn("cacheManager"),但这无济于事,因为缓存所需的其他 bean 稍后加载。或者 - 从另一个角度来看是相同的 - 我可以确保整个缓存基础设施(我还不够专家来详细描述它)提前初始化。不幸的是,我也不知道该怎么做......

提前感谢所有做到这一点的人。期待任何输入,无论是以更好的方式修复代码的想法,还是解释为什么 Spring 不能自己解决这个问题。

chk*_*chk 5

我终于弄清楚了问题所在,并且至少可以更详细地解释其原因,尽管我提出的解决方案仍然有点hacky。

在 Spring 中启用缓存方面引入了 a org.springframework.cache.interceptor.CacheInterceptor,它本质上是由实现 的org.aopalliance.aop.Advicea 使用的。org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisororg.springframework.aop.Advisor

org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor为 Shiro 引入的 I 是另一个传递Advisor依赖于DateServiceviaDefaultSecurityManager和 的I MyRealm

所以我有两个Advisor用于两个不同方面的s - 缓存和安全性 - 其中安全性的部分首先被初始化。事实上,每当我引入任何Advisor依赖项时DateService(即使它只是一个虚拟实现,如下例所示),缓存就不再起作用,原因与添加 Shiro 时缓存被破坏的原因相同。这会导致DateService在缓存方面准备好之前加载,因此无法应用。

@Bean
@Autowired
public Advisor testAdvisor(DateService dateService) {
    return new StaticMethodMatcherPointcutAdvisor() {
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            return false;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

因此,唯一正确的解决方法是更改​​方面初始化的顺序。我知道多个 s 适用于特定连接点的情况的@Order(Ordered.LOWEST_PRECEDENCE)相应注释,但对我来说不是这种情况,所以这没有帮助。由于其他原因,初始化的顺序也很重要。@Order(Ordered.HIGHEST_PRECEDENCE)Advisor

添加以下代码DateServiceImpl实际上可以解决问题:

@Autowired
BeanFactoryCacheOperationSourceAdvisor waitForCachingAspect;
Run Code Online (Sandbox Code Playgroud)

这样,服务始终会在初始化之前等待缓存,即使在实现中的任何地方都没有使用此依赖项。所以现在一切都正常工作,因为依赖关系树现在包含Shiro --> DateService --> Cache这使得 Shiro Advisor 等待足够长的时间。

它仍然没有我希望的那么好和干净,但是尽管如此,我认为这个解释有助于理解问题的核心,并且“如何更改在 Spring 中初始化 Advisor 的顺序”是一个单独的部分我在这里发布的问题。