Bean properties are shared across different sessions - spring

I am using JSF Mojarra 2.1.13, PrimeFaces 3.5 and Spring 3.2.3 for my application. For DI I am using Spring approach (not CDI). I am following the tutorial on PrimeFaces demos with the collector: http://www.primefaces.org/showcase/ui/collector.jsf
Everything is working fine, I can add my values to the list, get them, etc.. The problem is that for e.g. if I open two browsers and adding some values into the list, then in another browser I am adding a few values as well, and if I refresh browsers I am seeing the all the values which has been entered in both browsers. So if I enter two values in one browser two in the other, after refreshing them I am seeing four values in both browsers. I want my values to not be shared across different sessions.
My bean looks like this:
#Component
#ManagedBean
public class ClientBean extends BaseBean {
private Client client = new Client();
private List<Client> clients = new LinkedList<>();
public String reInit() {
client = new Client();
return null;
}
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
public List<Client> getClients() {
return clients;
}
public void setClients(List<Client> clients) {
this.clients = clients;
}
}
I know that I am creating global variables:
private Client client = new Client();
private List<Client> clients = new LinkedList<>();
But this is showed in tutorial. So how can I handle this situation to make collector work so that those variables won't be shared across different sessions?
EDIT
I have tried to annotate my bean with: #RequestScoped or #SessionScoped - didn't work. The same problem remains.

Not really sure why you configured the #ManagedBean as a #Component to begin with. This problem is because Spring handles a single instance of #Component across your application (or at least that's how it looks from your explanation). Remove it and use #ViewScoped in your managed bean to make this work as expected. Note that if you use Spring to manage your JSF managed beans, then you have to add this configuration in your faces-config.xml (from mkyong tutorial):
<application>
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver
</el-resolver>
</application>
But doing this you will loss the power of #ViewScoped managed beans. To solve this error, you have to implement the #ViewScoped in Spring. There are plenty examples on the net about this, and looks that the most popular is from Cagatay's
More info about JSF managed bean scopes: Communication in JSF 2: Managed bean scopes

Related

Cannot access jsf page when using spring security, 403 occurs

When I am using jsf with spring boot there is no problem to access the jsf bean, but when I add spring security I get access denied 403 when trying to access pages using jsf bean function, I can only access the pages with the url.
I've been searching a lot to solve this issue but nothing did work, please if someone can help me solve this issue.
here is my code:
jsf BeanProduit.java
#ManagedBean
#Component
#SessionScoped
public class BeanProduit {
#Autowired
#Qualifier("produitsPrixServiceImpl")
private CrudService<ProduitsPrix> produitsPrixService;
#Autowired
#Qualifier("produitsStockServiceImpl")
private CrudService<ProduitsStock> produitsStockService;
private List<ProduitsStock> produits;
private Logger logger = Logger.getLogger(getClass().getName());
public BeanProduit() {
produits = new ArrayList<ProduitsStock>();
}
#PostConstruct
public void init() {
produits = getListProductsFinal();
}
public String loadProduct(int codePdt) {
logger.info("loading product: " + codePdt);
try {
// get product from database
ProduitsPrix product = produitsPrixService.findById(codePdt);
// put in the request attribute ... so we can use it on the form page
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
Map<String, Object> requestMap = externalContext.getRequestMap();
requestMap.put("product", product);
} catch (Exception exc) {
// send this to server logs
logger.log(Level.SEVERE, "Error loading product id:" + codePdt, exc);
// add error message for JSF page
addErrorMessage(exc);
return null;
}
return "/pages/form-validation";
}
}
config file of spring security DemoSecurityConfig.java
#EnableWebSecurity
public class DemoSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource securityDataSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(securityDataSource);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/assets/**")
.permitAll()
.antMatchers("/authentication/login.xhtml?logout").hasAnyRole("EMPLOYEE")
.antMatchers("/**").hasRole("ADMIN")
.and().formLogin().loginPage("/authentication/login.xhtml")
.loginProcessingUrl("/authenticateTheUser").permitAll()
.defaultSuccessUrl("/", true)
.and().logout().permitAll()
.and().exceptionHandling().accessDeniedPage("/error/error-403.xhtml");
}
}
snippet code from the view
<h:form>
<ui:repeat value="#{beanProduit.produits}" var="produit">
<tr>
<td>#{produit.codePdt}</td>
<td>#{produit.nomPdt}</td>
<td>#{produit.prixPdt}</td>
<td>#{produit.qtePdt}</td>
<td class="text-center">
<h:commandButton class="btn btn-primary" value="Acheter" action="#{beanProduit.loadProduct(produit.codePdt)}" />
</td>
</tr>
</ui:repeat>
</h:form>
Let me start by addressing all of your comment from 2020-06-05 13:41:36Z above:
... if i take off "#ManagedBean" it won't be a bean
Wrong, something 'being' a (java)bean or not has nothing to do with annotations. What it takes for a plain java class to become a javabean can be read here:
What is a JavaBean exactly?
When a java bean becomes a real managed bean, managed by A container (Spring, JSF, CDI, EJB, ...) is a different ballgame
What is difference between JavaBean and ManagedBean
and it cannot return a view.
Wrong again, something, in the jsf sense, being able to 'return a view' or not is dependent on the managed bean (Spring, JSF, CDI, ...) being accessed from a JSF 'page'returning a String or not AND as long as the managed bean is available in the EL resolvers used by facelets/jsf
Navigating to another page JSF
Actually a JSF component on a facelets page is the better description
What is the difference between JSF and Facelets?
What is the difference between JSF, Servlet and JSP?
and if i take of "#Autowired" i cannot use injection right.
Wrong again... #Autowired is the old spring annotation for 'injecting' other managed beans. Other bean managers/containers have the same features.
JSF has (or rather had) #ManagedProperty (long deprecated in comination with its #ManageBean in favour of CDI managed beans), CDI has #Inject with #Named which spring now also supports as an alias to #Autowired and #Component/#Controller
What is the difference between #Inject and #Autowired in Spring Framework? Which one to use under what condition?
Both #Component and #Named for the same bean class
Effectively your misunderstandings from the comment are all addressed in
Spring JSF integration: how to inject a Spring component/service in JSF managed bean?
The last part of your comment
I want to add access to jsf bean if spring security config but i don't know how ?
Wrong again, you access a facelets/jsf page and you (may) control access to it via spring security (or other more independent ways like JBoss/RedHat KeyCloak or Apache Shiro or the standardized javaee-8 security api
So why is this not an answer to your question (but I'll leave it here anyway). In your question you state
I cannot access pages using bean, I can only access the pages with the url.
Then debug this... Set breakpoints, are there redirects, any errors, how does the url look like when you type it in, how does it look like when returned from method call to a bean, with and without spring security. Those are relevant details. Make a real 'https://stackoverflow.com/help/minimal-reproducible-example'
And lastly
I've been searching a lot to solve this issue but nothing did work,
How to ask states to search and keep track and unfortunately it only implies to mention in the question what you found (if you don't mention it, it cannot help the ones who try to help you in narrowing things down). and 'nothing did work' is not the best description. Did you get 404's? 500's?
I was missing this line in the form:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

how override spring framework beans?

I want to customize some of the codes of OAuth authorization server provided by spring security. the code responsible for generating /oauth/authorize is a bean named AuthorizationEndpoint. in AuthorizationServerEndpointsConfiguration class the following code creates a bean of AuthorizationEndpoint class:
#Bean
public AuthorizationEndpoint authorizationEndpoint() throws Exception {
AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());
authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
authorizationEndpoint.setTokenGranter(tokenGranter());
authorizationEndpoint.setClientDetailsService(clientDetailsService);
authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());
return authorizationEndpoint;
}
I want to override it by a new custom bean. I have created a class which extends AuthorizationEndpoint. for now I have pasted the same code inside this new class.
public class AuthorizationEndpointCustom extends AuthorizationEndpoint {
creating the bean:
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
AuthorizationServerEndpointsConfiguration asec;
#Bean
// #Order(value = Ordered.LOWEST_PRECEDENCE)
#Primary
public AuthorizationEndpoint authorizationEndpoint () {
AuthorizationEndpointCustom authorizationEndpoint = new AuthorizationEndpointCustom();
FrameworkEndpointHandlerMapping mapping = asec.getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
authorizationEndpoint.setProviderExceptionHandler(asec.getEndpointsConfigurer().getExceptionTranslator());
authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
authorizationEndpoint.setTokenGranter(asec.getEndpointsConfigurer().getTokenGranter());
authorizationEndpoint.setClientDetailsService(clientDetailsService);
authorizationEndpoint.setAuthorizationCodeServices(asec.getEndpointsConfigurer().getAuthorizationCodeServices());
authorizationEndpoint.setOAuth2RequestFactory(asec.getEndpointsConfigurer().getOAuth2RequestFactory());
authorizationEndpoint.setOAuth2RequestValidator(asec.getEndpointsConfigurer().getOAuth2RequestValidator());
authorizationEndpoint.setUserApprovalHandler(asec.getEndpointsConfigurer().getUserApprovalHandler());
return authorizationEndpoint;
}
private String extractPath(FrameworkEndpointHandlerMapping mapping, String page) {
String path = mapping.getPath(page);
if (path.contains(":")) {
return path;
}
return "forward:" + path;
}
when I try to create a bean of this new class I encounter the following error:
APPLICATION FAILED TO START
Description:
The bean 'authorizationEndpoint', defined in
org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration,
could not be registered. A bean with that name has already been
defined in class path resource
[com/example/demo/AuthorizationServerConfig.class] and overriding is
disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true
the error goes away by adding the suggested config to application.properties. but the new bean does not replace the framework bean. in another part of my code I accessed the AuthorizationEndpoint from applicationContext. I called the .getClass() of this object and it is the same bean from the framework:
"org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint"
how can I force spring to use my bean?
You need a Configuration class
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AppConfig {
#Bean
public AuthorizationEndpoint authorizationEndpoint() {
if(...) return new AuthorizationEndpoint();
else return new AuthorizationEndpointCustom();
}
}
I red an article about overriding beans and it seems so messy and unpredictable. read here
it's best to avoid doing so. The solution to disable framework bean lies in excluding the configuration class which creates it. but this means we have to implement the hole thing ourselves.
#SpringBootApplication(exclude=<AuthorizationServerEndpointsConfiguration>.class)
but the solution to overriding the framework endpoints is much easier.all we have to do is create a controller with mapping for /oauth/authorize
Customizing the UI Most of the Authorization Server endpoints are used
primarily by machines, but there are a couple of resource that need a
UI and those are the GET for /oauth/confirm_access and the HTML
response from /oauth/error. They are provided using whitelabel
implementations in the framework, so most real-world instances of the
Authorization Server will want to provide their own so they can
control the styling and content. All you need to do is provide a
Spring MVC controller with #RequestMappings for those endpoints, and
the framework defaults will take a lower priority in the dispatcher.
In the /oauth/confirm_access endpoint you can expect an
AuthorizationRequest bound to the session carrying all the data needed
to seek approval from the user (the default implementation is
WhitelabelApprovalEndpoint so look there for a starting point to
copy). You can grab all the data from that request and render it
however you like, and then all the user needs to do is POST back to
/oauth/authorize with information about approving or denying the
grant. The request parameters are passed directly to a
UserApprovalHandler in the AuthorizationEndpoint so you can interpret
the data more or less as you please. The default UserApprovalHandler
depends on whether or not you have supplied an ApprovalStore in your
AuthorizationServerEndpointsConfigurer (in which case it is an
ApprovalStoreUserApprovalHandler) or not (in which case it is a
TokenStoreUserApprovalHandler). The standard approval handlers accept
the following:
read more here.
there is another question related to this subject: read here

JAX-RS, Spring & ServletConfig: How to access Servlet Config in Configurator

I have troubles getting a javax.servlet.ServletConfig into a class annotated with org.springframework.context.annotation.Configuration.
My team decided that we should use spring for dependency injection and I'm trying to use it to migrate one of our simple Rest services.
My constraints are:
JAX-RS: We have several REST Services implemented JAX-RS and we don't really want to change that.
Not bound to a specific implementation of JAX-RS (Jersey & RESTEasy work fine for us and we can change from one to the other without changing underlying code)
Import as few dependencies as possible from spring: at the moment I import only org.springframework:spring-context from the spring project.
No API breakage: Deprecated is fine but the service should keep working during the transition, using our old way of doing things.
A string parameter is defined in the service's web.xml. I need to get it, instantiate a Bean with it and inject the resulting bean at several place in the code.
I don't want to mess with Spring Boot/MVC/... as the service already works and I just want the Dependency Injection part.
What I already have:
The code use javax.ws.rs.core.Application, with a class that look like that:
public class MyApplication extends Application {
#Context
private ServletConfig cfg;
public DSApplication() {
}
#Override
public Set<Class<?>> getClasses() {
return new HashSet<>();
}
#Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<>();
String injectionStr = cfg.getInitParameter("injection");
boolean injection = false;
if (null != injectionStr && !injectionStr.isEmpty()) {
injection = Boolean.valueOf(injectionStr);
}
if (injection) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
DSServiceProducer.class,
CContextBeanProvider.class
);
IDSService service = context.getBean(IDSService.class);
set.add(service);
} else {
set.add(new DSService()); //Old way
}
return set;
}
}
I need the servlet config in CContextBeanProvider, which look like:
#Configuration
public class CContextBeanProvider {
private ServletConfig cfg; // How to get this here ?
#Bean
public CContextBean cContextBean() {
String bean = cfg.getInitParameter("cpuContext");
return new CContextBean(bean);
}
}
CContextBean is a setting bean initialized from a string found in the web.xml of the service.
Is it possible ?
Do you have any idea how ?
Would it be easier with CDI, knowing that we run on base Tomcat ? (I've already find this if I need to use tomcat with CDI)
Could you please try to add all jersey CDI related jars to your applications ?

Wiring multiple beans with the same dependency via Spring Boot #Configuration

In older Spring MVC apps, where you'd specify application.xml and declare your app's beans so that Spring DI could instantiate them and wire them together, you might have something like this:
<bean id="chargeFactory" class="com.example.myapp.ChargeFactory" />
<bean id="paymentService" class="com.example.myapp.DefaultPaymentService">
<ref id="chargeFactory"/>
</bean>
<bean id="chargeAuditor" class="com.example.myapp.ChargeAuditor">
<ref id="chargeFactory"/>
</bean>
Which might help wire up the following code:
public interface PaymentService {
public void makePayment(Payment payment);
}
public class DefaultPaymentService implements PaymentService {
#Autowired
private ChargeFactory chargeFactory;
#Override
public void makePayment(Payment payment, String key) {
Charge charge = chargeFactory.createCharge(key);
charge.doCharge(payment);
}
}
public class ChargeAuditor {
#Autowired
private ChargeFactory chargeFactory;
public void auditAllCharges(String key) {
List<Charge> charges = chargeFactory.getAllCharges(key);
// blah whatever
}
}
How do you accomplish the same bean wiring in Spring Boot with the #Configuration class? For example:
#Configuration
public class MyAppInjector {
#Bean
public ChargeFactory chargeFactory() {
return new ChargeFactory();
}
#Bean
public PaymentService paymentService() {
return new DefaultPaymentService(chargeFactory());
}
#Bean
public ChargeAuditor chargeAuditor() {
return new ChargeAuditor(chargeFactory());
}
}
This might work but introduces some issues:
It would force me to have to write value constructors for all my classes, which goes against everything I see in literally every tutorial/article I've come across. Plus, if I had to do that, then there's no real value to #Autowired anyways...
At this point I'm essentially doing "DIY DI", which is OK, except I'm trying to deliberately use Spring DI :-)
Every time I call chargeFactory() I'm getting a new (prototype-style) ChargeFactory instance. Maybe I want a singleton. Using this approach I have to hand-roll my own singleton implementation.
Sure, I can do all of this, but I feel like I'm flagrantly misusing/misunderstanding how #Configuration is supposed to be used, because it seems like I'm introducing a whole lot of DIY/homegrown code to solve something Spring DI should be able to do for me.
How would I reference the chargeFactory bean and wire it into both the "provider methods" for the paymentService and chargeAuditor beans? Again, looking for the Java-based #Configuration solution instead of writing an XML document to define the wirings.
I found this article which seems to be the only tutorial/documentation (surprisingly) on wiring Spring Boot apps via #Configuration (which leads me to believe there might be other/better methods...), but it does not address:
How to specify singleton vs prototype bean instantiation patterns
If multiple instances of a bean-class exist, how do I specify which instance gets wired into which dependency?
How do I get around not defining value constructors for all my classes, and just let Spring/#Autowired inject fields automagically?
When you call chargeFactory() , spring won't create new instance everytime. Give it a try and see. Same object will be returned. Anyways
You can do something like this.
#Bean
public PaymentService paymentService(ChargeFactory chargeFactory) { return new DefaultPaymentService(chargeFactory); }

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

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);
}
}

Resources