Spring service with cacheable methods gets initialized without cache when autowired in Shiro realm - spring

After spending 2 days on this issue I really can't make any more progress on my own. I am working on a standard web application with Spring for dependency injection and the likes. I am also using Spring to cache several expensive methods I use a lot.
After I introduced Apache Shiro for the security layer, I was experiencing a strange issue where #Cacheable methods in a certain service no longer got cached. To this point, I was able to strip the problem down to its core, but there's still a lot of code for you to look at - sorry for that...
First, I configure all relevant packages (all classes shown in the following are in one of those).
#Configuration
#ComponentScan(basePackages = {
"my.package.config",
"my.package.controllers",
"my.package.security",
"my.package.services",
})
public class AppConfiguration {
}
Here is the configuration file for caching.
#Configuration
#EnableCaching
public class CacheConfiguration {
#Bean(name = "cacheManager")
public SimpleCacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("datetime")
));
return simpleCacheManager;
}
}
For my minimal example, I am using a very simple service that only returns the current timestamp. The Impl class is as simple as you would imagine.
public interface DateService {
#Cacheable("datetime")
LocalDateTime getCurrent();
}
I inject this service into a controller.
#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);
}
}
The application is set up and started via Jetty, and everything works as expected so far. When calling <api-url>/v1/date/current for the first time the current timestamp is returned, but afterwards one always receives the cached result.
Now, I introduce Shiro with yet another config file.
#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;
}
}
Finally, here comes the realm which also depends on my service.
#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);
}
}
With that, the caching is broken in my entire application. There is no error message, it just doesn't use the cache anymore. I was able to implement a workaround, but I am now seeking for a better solution and maybe also some advice to better understand the essence of my issue. So, here comes the workaround.
#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);
}
}
Now it's back to working, and I was able to debug the reason for that. Shiro and hence MyRealm gets initialized very early, even before the whole caching with my SimpleCacheManager and all the related stuff (cacheInterceptor etc.) is loaded. Therefore, there is no proxy to wrap around the service when it gets initialized before the realm when using #Autowired. With the workaround shown above, the service is not injected before everything is set up properly and the first request is being served, and then there is no problem.
Simply put, as soon as I make MyRealm dependent on DateService (annotating the last version of MyRealm with #DependsOn("dateServiceImpl") is enough to break the application) it gets initialized too early (i.e. before caching is set up).
So I would need to either postpone the initialization of MyRealm, but I don't know how to do that. I tried #DependsOn("cacheManager"), but that doesn't help as the other beans required for caching are loaded later nonetheless. Or - which is the same from another perspective - I could make sure the whole caching infrastructure (I am not enough of an expert to describe it in detail) is initialized earlier. Unfortunately, I also don't know how to do that...
Thanks in advance to everyone who made it to this point. Looking forward to any input, no matter if it's an idea to fix the code in a better way or an explanation why exactly Spring can't get this right on its own.

I finally figured out what the problem is and can at least explain its cause in more detail, even though my proposed solution is still a bit hacky.
Enabling the caching aspect in Spring introduces a org.springframework.cache.interceptor.CacheInterceptor, which is essentially an org.aopalliance.aop.Advice used by a org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor that implements org.springframework.aop.Advisor.
The org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor I introduced for Shiro is another Advisor which transitively depends on the DateService via DefaultSecurityManager and MyRealm.
So I have two Advisors for two different aspects - Caching and Security - of which the one for security is initialized first. In fact, whenever I introduce any Advisor dependent on DateService - even if its only a dummy implementation as in the following example - the caching doesn't work anymore for the same reason as it was broken when adding Shiro. This causes the DateService to be loaded before the caching aspect is ready, so it cannot be applied.
#Bean
#Autowired
public Advisor testAdvisor(DateService dateService) {
return new StaticMethodMatcherPointcutAdvisor() {
#Override
public boolean matches(Method method, Class<?> targetClass) {
return false;
}
};
}
Hence, the only proper fix for that is to change the order of aspect initialization. I am aware of the #Order(Ordered.LOWEST_PRECEDENCE) respectively #Order(Ordered.HIGHEST_PRECEDENCE) annotation for the case the multiple Advisors are applicable at a specific joinpoint, but this is not the case for me so this doesn't help. The order of initialization matters for other reasons.
Adding the following code in DateServiceImpl actually solves the problem:
#Autowired
BeanFactoryCacheOperationSourceAdvisor waitForCachingAspect;
With that, the service always waits for the cache before it can be initialized even though this dependency is not used anywhere in the implementation. So now everything is working as it should because the dependency tree now includes Shiro --> DateService --> Cache which makes the Shiro Advisor wait long enough.
It is still not as nice and clean as I would like it to be, but nevertheless, I think this explanation helps to understand the core of the problem and "How can I change the order in which Advisors are initialized in Spring" is a separate question I posted here.

Since Spring 4, #Lazy can be used to achieve the same behavior as in the original question in a more declarative way (see Spring 4 JavaDoc and compare it with earlier versions).
Tested this and it works.
#Component
public class MyRealm extends AuthenticatingRealm {
private static final String REALM_NAME = "MyRealm";
#Autowired
#Lazy
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);
}
}

Related

Why does #Cachable(...) work with #Bean return mock() but not with #MockedBean

Why does the cache get filled with Values when using
#Autowired
ServiceXY serviceXY
#TestConfiguration
static class AppDefCachingTestConfiguration {
#Bean
public ServiceXY ServiceXYMock() {
return mock(ServiceXY.class);
}
}
But not with
#MockBean
ServiceXY serviceXY
When using #MockBean i get a NullPointerException when accessing the cache values like that in my test:
#Autowired
ConcurrentMapCacheManager cmcm;
#Test
void anTest(){
when(serviceXY.methodThatFillsCache(anyString()).thenReturn("ABC");
serviceXY.methodThatFillsCache("TEST1");
cmcm.getCache("Cachename").get("TEST1",String.class).equals("ABC");
...
}
Caching is implemented using a proxy that intercepts calls to the cacheable method. When you use #MockBean, Spring Boot intentionally disables proxying. One consequence of this is that no caching is performed. Someone recently made the point that this isn't very well documented so we may update the docs in the future.
If you're want to test that caching is working as expected, you should either use a genuine implementation of your service, or create the mock yourself via a #Bean method as you have done in the first example in your question.

Spring #Cachable method within the same class (self-invocation, proxy issue) - What is the best way to solve it?

I'm trying to call a #Cacheable method from within the same class.
And it didn't work. Because of:
In proxy mode (the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object that calls another method of the target object) does not lead to actual caching at runtime even if the invoked method is marked with #Cacheable. Consider using the aspectj mode in this case. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, #PostConstruct).
It means, #Cachable(also #Transactional) works by proxy classes which is Spring AOP in. a internal call in the same class make call by 'this' instead of proxy classes.
To solve the problem, I should call a method by proxy or using AspectJ(another AOP).
So, I found 4 solutions.
What is your choice? and why others are not recommended?
Please, share your opinion!
using AspectJ (another AOP)
get the Bean from ApplicationContext and use it
#Service
public class UserService implements Service {
#Autowired
private ApplicationContext applicationContext;
private Service self;
#PostConstruct
private void init() {
self = applicationContext.getBean(UserService.class);
}
}
self-autowiring using #Resource //since Spring 4.3
#Component
#CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {
/**
* 1. Self-autowired reference to proxified bean of this class.
*/
#Resource
private SphereClientFactory self;
#Override
#Cacheable(sync = true)
public SphereClient createSphereClient(#Nonnull TenantConfig tenantConfig) {
// 2. call cached method using self-bean
return self.createSphereClient(tenantConfig.getSphereClientConfig());
}
#Override
#Cacheable(sync = true)
public SphereClient createSphereClient(#Nonnull SphereClientConfig clientConfig) {
return CtpClientConfigurationUtils.createSphereClient(clientConfig);
}
}
make the Bean scope of the class as 'prototype' instead of 'singleton'
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {
private final AService _aService;
#Autowired
public AService(AService aService) {
_aService = aService;
}
#Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = _aService.getEmployeeData(date);
...
}
}
I'm a newbie in spring :)
Actually, I choose the 4th solution, but I felt it isn't a good way. because I just need to call the caching method by proxy, and it make several beans to achieve it.
After reading articles, I think AspectJ is the best choice. It looks cool, Spring recommends it, and many people also recommend too.
But I don't understand how to AspectJ works (I will study) and I also don't know why others is not recommended.
references
Spring Cache #Cacheable - not working while calling from another method of the same bean
Spring cache #Cacheable method ignored when called from within the same class
https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring
https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache

#EnableRedisHttpSession + Spring Boot ignoring server.session.timeout on application.yml

I have a project with Spring Boot 1.3.3 [another stuff] and Redis configurated to manage sessions, i.e., #EnableRedisHttpSession. The application works well and stores the information on Redis regularly.
The problem that I'm facing is that, different from what documentation says, whether I define or not a server.session.timeout, the Redis always is using the default value for its annotation attribute (maxInactiveIntervalInSeconds) that is: 1800
Here, the documentation that I followed: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-session.html
I've also tried the approach defined by #rwinch here https://github.com/spring-projects/spring-session/issues/110 but also without success.
Updating ......
My configuration file as requested:
#First attempt (server.session.timeout) following the Spring documentation mentioned
server:
session:
timeout: 10
spring:
#session timeout under spring (as mentioned by M Deinum in comment - unfortunately doesnt work)
session:
timeout: 10
redis:
host: 192.168.99.101
port: 6379
Beside that, I've also tried to implement a SessionListener that was in charge of setting the timeout (something like this):
public class SessionListener implements HttpSessionListener {
#Value(value = "${server.session.timeout}")
private int timeout;
#Override
public void sessionCreated(HttpSessionEvent event) {
if(event!=null && event.getSession()!=null){
event.getSession().setMaxInactiveInterval(timeout);
}
}
...
It still didn't result in a correct scenario. I'm really racking my brain :|
Please guys, am I missing some point? Does anyone else have faced it?
Thanks in advance.
Another solution:
#EnableRedisHttpSession
public class HttpSessionConfig {
#Value("${server.session.timeout}")
private Integer maxInactiveIntervalInMinutes;
#Inject
private RedisOperationsSessionRepository sessionRepository;
#PostConstruct
private void afterPropertiesSet() {
sessionRepository.setDefaultMaxInactiveInterval(maxInactiveIntervalInMinutes * 60);
}
In this way you use the default configuration, and just add your timeout. So you maintain the default HttpSessionListener, and you don't need to use an ApplicationListener to set the time out, just one time, in the application lifecycle.
Well, just in case someone is facing the same situation, we have 2 ways to workaround:
I. Implement the following:
#EnableRedisHttpSession
public class Application {
//some other codes here
#Value("${spring.session.timeout}")
private Integer maxInactiveIntervalInSeconds;
#Bean
public RedisOperationsSessionRepository sessionRepository( RedisConnectionFactory factory) {
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(factory);
sessionRepository.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
return sessionRepository;
}
Unfortunately, I had to implement a listener in order to perform additional actions when a session expires. And, when you define a RedisOperationsSessionRepository, you don't have a HttpSessionListener anymore (instead of it, you have a SessionMessageListener, as described here: http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository). Because of this question, the 2nd approach was required.
II. To overcome the problem:
#EnableRedisHttpSession
public class Application implements ApplicationListener{
#Value("${spring.session.timeout}")
private Integer maxInactiveIntervalInSeconds;
#Autowired
private RedisOperationsSessionRepository redisOperation;
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
redisOperation.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
}
}
...
Assuming that none of them are the desirable out-of-box setup, at least they allow me to continue in my PoC.
#EnableRedisHttpSession(maxInactiveIntervalInSeconds = 60)
You can remove EnableRedisHttpSession annotation, instead, set the property:
spring.session.store-type=redis
Both spring.session.timeout and server.servlet.session.timeout will work. Please note spring.session.timeout will override server.servlet.session.timeout per my test.
Extend RedisHttpSessionConfiguration and do init in #PostConstruct method.
#Configuration
public class HttpSessionConfig extends RedisHttpSessionConfiguration {
#Value("${spring.session.timeout}")
private Integer sessionTimeoutInSec;
#Value("${spring.session.redis.namespace}")
private String sessionRedisNamespace;
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
#PostConstruct
public void initConfig() throws Exception {
this.setMaxInactiveIntervalInSeconds(sessionTimeoutInSec);
this.setRedisNamespace(sessionRedisNamespace);
}
}

ClassBridge with DAO class injected

I have a Hibernate Search ClassBridge where I want to use #Inject to inject a Spring 4.1 managed DAO/Service class. I have annotated the ClassBridge with #Configurable. I noticed that Spring 4.2 adds some additional lifecycle methods that might do the trick, but I'm on Spring 4.1
The goal of this is to store a custom field into the index document based on a query result.
However, since the DAO, depends on the SessionFactory getting initialized, it doesn't get injected because it doesn't exist yet when the #Configurable bean gets processed.
Any suggestions on how to achieve this?
You might try to create a custom field bridge provider, which could get hold of the Spring application context through some static method. When provideFieldBridge() is called you may return a Spring-ified instance of that from the application context, assuming the timing is better and the DAO bean is available by then.
Not sure whether it'd fly, but it may be worth trying.
Hibernate Search 5.8.0 includes support for bean injection. You can see the issue https://hibernate.atlassian.net/browse/HSEARCH-1316.
However I couldn't make it work in my application and I had implemented a workaround.
I have created an application context provider to obtain the Spring application context.
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
ApplicationContextProvider.context = context;
}
}
I have added it to the configuration class.
#Configuration
public class RootConfig {
#Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
}
}
Finally I have used it in a bridge to retrieve the spring beans.
public class AttachmentTikaBridge extends TikaBridge {
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
// get service bean from the application context provider (to be replaced when HS bridges support beans injection)
ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
ExampleService exampleService = applicationContext.getBean(ExampleService .class);
// use exampleService ...
super.set(name, content, document, luceneOptions);
}
}
I think this workaround it's quite simple in comparision with other solutions and it doesn't have any big side effect except the bean injection happens in runtime.

Which #Scope should I choose for a stateful(?) #Service

I'm new to Spring.
I'm working on a library project which depends on spring-context.
#Scope(value = "##?")
#Service
public class MyService {
#PostConstruct private void constructed() {
}
#PreDestroying private void destroying() {
resource.clear();
}
public void doSome() throws IOException {
// try{}finally{} is not the case
resource = getSome();
doSome(resource); // may throw an IOException
resource.clear();
}
private transient MyResource resource;
}
I want to free the resource in every time this instance being destroyed.
According to #Scope, there four options that I can choose.
ConfigurableBeanFactory.SCOPE_SINGLETON
ConfigurableBeanFactory.SCOPE_PROTOTYPE
WebApplicationContext.SCOPE_REQUEST
WebApplicationContext.SCOPE_SESSION
I found that WebApplicationContext is not available from my dependency tree. (I'm not depends on spring-webmvc)
I'm planning to choose ConfigurableBeanFactory.SCOPE_PROTOTYPE.
Is it true that the scope I choose will make MyService safe? I mean any two or more clients can't be injected with the same service instance? Will the Spring container take care of it?
Indeed, Request, Session, Global-session and Application scopes are only available within Web aware application context.
Singleton (single instance per Spring container) is a default scope used by Spring, so using prototype scope will guarantee that new instance will be created and returned to the client, so yes Prototype is what you need in this case.

Resources