I'm trying to grasp Spring's FactoryBean and I've had and issue. Could you please see my sources below and answer. It's my app context:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:annotation-config/>
<bean id="SHADigest" class="com.dtoryanik.spring.factorybean.MessageDigestFactoryBean">
<property name="algorithmName">
<value>SHA1</value>
</property>
</bean>
<bean id="defaultDigest" class="com.dtoryanik.spring.factorybean.MessageDigestFactoryBean"/>
<bean id="digester" class="com.dtoryanik.spring.factorybean.MessageDigester">
<property name="messageDigest1">
<ref local="SHADigest"/>
</property>
<property name="messageDigest2">
<ref local="defaultDigest"/>
</property>
</bean>
</beans>
It's a factory bean actually:
public class MessageDigestFactoryBean implements FactoryBean<MessageDigest>{
private String algorithmName = "MD5";
private MessageDigest messageDigest = null;
#Override
public MessageDigest getObject() throws Exception {
System.out.println("<> MessageDigestFactoryBean.getObject()");
return messageDigest;
}
#Override
public Class<?> getObjectType() {
System.out.println("<> MessageDigestFactoryBean.getObjectType()");
return MessageDigest.class;
}
#Override
public boolean isSingleton() {
System.out.println("<> MessageDigestFactoryBean.isSingleton()");
return true;
}
#PostConstruct
public void postConstructHandler() throws NoSuchAlgorithmException {
System.out.println("<> MessageDigestFactoryBean.postConstructHandler()");
messageDigest = MessageDigest.getInstance(algorithmName);
}
public void setAlgorithmName(String algorithmName) {
this.algorithmName = algorithmName;
}
}
There is another class - MessageDigester but it does not do anything helpful for topic. And I have a main-method class:
public class MessageDigestDemo {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-ctx.xml");
ctx.refresh();
MessageDigester messageDigester = (MessageDigester) ctx.getBean("digester");
messageDigester.digest("Hello World!");
}
}
The issue is in my output. It seems I have a double instantiating. Methods isSingleton(), getObject() are called two times for each bean (although I retrieve only 2 instances from factory). Why does it occur? Maybe I do something wrong?
<> MessageDigestFactoryBean.postConstructHandler()
<> MessageDigestFactoryBean.postConstructHandler()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObject()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObject()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObjectType()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObjectType()
I've modified your MessageDigestFactoryBean class so that it outputs algorithmName as well, this will help to clear out the case. After that change, the output is:
<> MessageDigestFactoryBean.postConstructHandler() SHA1
<> MessageDigestFactoryBean.postConstructHandler() MD5
<> MessageDigestFactoryBean.isSingleton() SHA1
<> MessageDigestFactoryBean.getObject() SHA1
<> MessageDigestFactoryBean.isSingleton() MD5
<> MessageDigestFactoryBean.getObject() MD5
<> MessageDigestFactoryBean.isSingleton() SHA1
<> MessageDigestFactoryBean.getObjectType() SHA1
<> MessageDigestFactoryBean.isSingleton() MD5
<> MessageDigestFactoryBean.getObjectType() MD5
Let's try to analyze this output.
You have declared two MessageDigestFactoryBean instances, so when Spring discovers them in context, it initializes them, and in process calls the method annotated with #PostConstruct - MessageDigestFactoryBean.postConstructHandler().
Then as Spring discovers digester bean, it tries to obtain it's dependencies. Spring sees that dependency is FactoryBean, so it eventually calls FactoryBeanRegistrySupport.getObjectFromFactoryBean. This method first checks if the bean is singleton, calling MessageDigestFactoryBean.isSingleton(). If the bean is singleton, it first tries to obtain reference to it from factory bean objects cache. Since this is the first time this bean is referenced, it's not yet cached, so reference is obtained via MessageDigestFactoryBean.getObject(), cached and then returned. Since you have two factory beans referenced in digester, obviously this process is repeated for each one.
After initialization is complete, Spring tries to publish ContextRefreshedEvent to the lifecycle processor, so it searches all bean definitions for an instance that implements Lifecycle interface. Basically Spring loops through all beans defined in context, checking for singleton bean of matching type (actually there are more checks, but we're interested only in these). In process of this, for factory bean MessageDigestFactoryBean.isSingleton() is called to determine whether object is singleton, and MessageDigestFactoryBean.getObjectType() is called to check whether object's type is assignable from Lifecycle interface. Again, since you have two instances of MessageDigestFactoryBean, each of these methods is called twice.
That's what happens when you call ctx.refresh(). This is just over the top look and obviously a lot more is done by Spring under the hood, but that's all I can see related to your output. Hope this answers your first question.
Now for the second question - no, you did not do anything wrong, the output you see just reflects how Spring functions internally.
Related
I have a spring mvc 3.0 web application and am using a conversion-service as follows...
<bean id="applicationConversionService"
class="com.myer.reporting.converter.ApplicationConversionServiceFactoryBean"/>
<context:component-scan base-package="com.myer.reporting.controller" />
<context:component-scan base-package="com.myer.reporting.manager" />
<context:component-scan base-package="com.myer.reporting.dao"/>
<context:component-scan base-package="com.myer.reporting.dao.mapper"/>
<mvc:annotation-driven conversion-service="applicationConversionService"/>
In my controllers when i use the conversion service it all works fine.
#Autowired
private ApplicationConversionServiceFactoryBean applicationConversionService; '
But for the first time I am now trying to use the applicationConversionService in my mapper using the same method but the applicationConversionService is always coming back as null.
package com.myer.reporting.dao.mapper;
public class ImportHistoryRowMapper implements RowMapper<ImportHistory> {
private static final String BUSINESS_DATE = "businessdate";
private static final String TOTAL_TRAN_COUNT = "total_tran_count";
#Autowired
private ApplicationConversionServiceFactoryBean applicationConversionService;
public ImportHistory mapRow(ResultSet rs, int rowNum) throws SQLException {
Date businessDate = rs.getDate(BUSINESS_DATE);
Integer totalTranCount = rs.getInt(TOTAL_TRAN_COUNT);
ImportHistory importHistory = new ImportHistory();
importHistory.setBusinessDate(businessDate);
importHistory.setTransactionCount(totalTranCount);
// status
TrafficLightStatus status =
applicationConversionService.
getImportThresholdToStatusConverter()
.convert(totalTranCount);
importHistory.setStatus(status);
return importHistory;
}
}
I tried to add #Component to the top of the class but that doesn't seem to work either. I think I am lacking some understanding of the #Autowired annotation and therefore can't get this to work.
Can someone give me some advice on this please.
thanks
You need to make the ImportHistoryRowMapper spring managed for this to work.
I believe your dao class making the reference to this mapper class is singleton. For every request (i.e., call to method getJdbcTemplate().query ( sql, object array, new instance of ImportHistoryRowMapper);), you need to create new instance of ImportHistoryRowMapper i.e., it should be prototype.
You can inject prototype bean into singleton in 2 ways:
1) Lookup Method injection
2) Scoped proxies
Take a look at this.
I am new for spring security. I've seen many posts on how to inject values via annotation from external property file. I've tried many ways, but I always end up with java.lang.IllegalArgumentException: Could not resolve placeholder 'val.id' exception.
Can you provide me some tips how to handle this exception please?
My java class is the following one:
#Controller
public class Employee {
#Value("${val.id}")
public String valId;
public String getValId() {
return valId;
}
public void setValId(String valId) {
this.valId = valId;
}
My property file is called val.properties which is located under WEB-INF, and its content is
val.id=xyz
I put the following in my main context bean.
<context:property-placeholder location="/WEB-INF/*.properties" />
<bean id="valProp" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:location="/WEB-INF/val.properties"/>
A continuous question:
The injecting values from properties file to annotated beans works fine as I accepted the answer above. However, I cannot able to inject it to #PreAuthorize(...) annotation by following the same procedure.
Assume I want to secure a method called 'update'. This method is allowed if and only if valId is equal to empId. values of valId and empId are initialized in the val.properties file.
my java bean is:
public class Employee {
public String valId;
public String empId;
public String getValId() {
return valId;
}
public void setValId(String valId) {
this.valId = valId;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
}
my property file contains:
val.id=nn
emp.id=nn
I have the place holder configuration in my main context file:
<context:property-placeholder location="/WEB-INF/*.properties" />
<bean id="valProp" class="org.springframework.beans.factory.config.PropertiesFactoryBean"
p:location="/WEB-INF/val.properties"/>
My PreAuthorize annotation (method security) is:
#PreAuthorize("(#{valProp['val.id']} == #{valProp['emp.id']})")
public boolean update(){
//if accessable
return true;
}
But the expression #{valProp['val.id']} == #{valProp['emp.id']} is not evaluated.
Did I do any mistake to inject values? It was worked when I annotate member variables, but it doesn't work here. Any idea please? Thanks in advance.
try to consider the following
1). change your annotation to:
#Value("#{valProp['val.id']}")
2). Replace PropertyPlaceholderConfigurer by PropertiesFactoryBean.
Hope this will resolve the exception.
The reason why the exception is thrown is, because the property placeholder by default throws an exception when a values cannot be resolved.
Furthermore you have two property placeholders, via which probably not all values can be resolved.
You can change this behaviour via setting the ignore-unresolvable property:
<context:property-placeholder location="/WEB-INF/*.properties" ignore-unresolvable="true" />
<bean id="valProp" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:location="/WEB-INF/val.properties" p:ignoreUnresolvablePlaceholders="true" />
Note however that b< turning off this feature typos in a property file will not be detected.
I've to manage the association between bean instance and some type of resource. More specifically, when I receive some data associated to Resource A, I have to dispatch the data to the create an instance of some bean type every time I receive data associated with a specified resource. For example, if I receive some data associated with Resource "a", then i have to dispatch this data to a Bean instance associated with "a". If there isn't a Bean instance associated to Resource "a", then it has to be created. The number of Resource isn't statically known, and it will be known only during execution.
I understand I need some type of dictionary to store the association between Resource id and bean instance, building something like a Factory to manage bean creation.
How can I implement this in Spring? What's the proper way?
the Spring ApplicationContext can be referred to in this 'dictionary' fashion. that is, you specify to the application context to 'setup' the 'resource beans'. you then map the 'received data' via way of a Map object, that, in turn, holds a reference to the bean. once the data maps to the bean, you can then retrieve the bean and process the received data. here's a brief example
here's a resource service interface
package de.incompleteco.spring.service;
public interface ResourceService {
public void processData(Object data);
}
here's an implementation to process 'TypeA' data
package de.incompleteco.spring.service;
public class TypeAResourceService implements ResourceService {
public void processData(Object data) {
//do something for 'type A' data
}
}
here's a 'delegate' service
package de.incompleteco.spring.service;
import java.util.Map;
import de.incompleteco.spring.domain.TypeA;
import de.incompleteco.spring.domain.TypeB;
public class DelegateResourceService implements ResourceService {
private Map<String,ResourceService> services;
public void processData(Object data) {
if (data instanceof TypeA) {
services.get(TypeA.class.getSimpleName()).processData(data);
} else if (data instanceof TypeB) {
services.get(TypeA.class.getSimpleName()).processData(data);
} else {
throw new IllegalArgumentException("...");
}
}
public void setServices(Map<String, ResourceService> services) {
this.services = services;
}
}
the 'delegate' service is responsible for retrieving the right service from a map to process the right data (keyed by Class Name)
here's the spring configuration to support
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- type 'a' bean -->
<bean id="typeA" class="de.incompleteco.spring.service.TypeAResourceService"/>
<!-- type 'b' bean -->
<bean id="typeB" class="de.incompleteco.spring.service.TypeBResourceService"/>
<util:map id="dictionary">
<entry key="TypeA" value-ref="typeA"/>
<entry key="TypeB" value-ref="typeB"/>
</util:map>
<bean class="de.incompleteco.spring.service.DelegateResourceService">
<property name="services" ref="dictionary"/>
</bean>
</beans>
this configuration does the following;
sets up services for type A and B data
maps those services to keys
sets up the delegate service, setting the map to be used
this pattern is not the only way of doing it, but gives you the following;
ability to configure the service beans independantly
the delegate only needs a map to process
the constraint is that the service objects have to be of the same interface
you can think of the Spring ApplicationContext as a giant factory, but (in many ways) a whole lot simpler to setup.
I have an application running with Spring, and I'm using AOP in some places. Since I want to use the #Transactional annotation at interface level, I have to allow Spring to create JDK proxies. So, I don't set the proxy-target-class property to true. On the other hand, I don't want to create an interface for every single class I want advised: if the interface just doesn't make sense, I want to have just the implementation, and Spring should create a CGLIB proxy.
Everything was working perfectly, just as I described. But I wanted to have some other annotations (created by me) going in interfaces and being "inherited" by the implementation classes (just like the #Transactional one). Turns out that I can't do that with the built-in support for AOP in Spring (at least I could not figure it out how to do it after some research. The annotation in the interface is not visible in the implementation class, and hence that class does not get advised).
So I decided to implement my own pointcut and interceptor, allowing other method annotations to go on interfaces. Basically, my pointcut look for the annotation on the method and, until not found, in the same method (same name and parameter types) of the interfaces that the class or its superclasses implements.
The problem is: when I declare a DefaultAdvisorAutoProxyCreator bean, that will properly apply this pointcut/interceptor, the behavior of advising classes with no interfaces is broken. Apparently something goes wrong and Spring tries to proxy my classes twice, once with CGLIB and then with JDK.
This is my configuration file:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<!-- Activates various annotations to be detected in bean classes: Spring's
#Required and #Autowired, as well as JSR 250's #Resource. -->
<context:annotation-config />
<context:component-scan base-package="mypackage" />
<!-- Instruct Spring to perform declarative transaction management automatically
on annotated classes. -->
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
<bean id="logger.advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg>
<bean class="mypackage.MethodAnnotationPointcut">
<constructor-arg value="mypackage.Trace" />
</bean>
</constructor-arg>
<constructor-arg>
<bean class="mypackage.TraceInterceptor" />
</constructor-arg>
</bean>
</beans>
This is the class I want to be proxied, with no interfaces:
#Component
public class ServiceExecutorImpl
{
#Transactional
public Object execute(...)
{
...
}
}
When I try to autowire it in some other bean, like:
public class BaseService {
#Autowired
private ServiceExecutorImpl serviceExecutorImpl;
...
}
I get the following exception:
java.lang.IllegalArgumentException: Can not set mypackage.ServiceExecutorImpl field mypackage.BaseService.serviceExecutor to $Proxy26
This are some lines of the Spring output:
13:51:12,672 [main] DEBUG [org.springframework.aop.framework.Cglib2AopProxy] - Creating CGLIB2 proxy: target source is SingletonTargetSource for target object [mypackage.ServiceExecutorImpl#1eb515]
...
13:51:12,782 [main] DEBUG [org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'serviceExecutorImpl' with 0 common interceptors and 1 specific interceptors
13:51:12,783 [main] DEBUG [org.springframework.aop.framework.JdkDynamicAopProxy] - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [mypackage.ServiceExecutorImpl$$EnhancerByCGLIB$$2eb5f51#5f31b0]
I could supply the full output if someone thinks it will help. I have no idea why Spring is trying to "double-proxy" my class, and why this just happens when I declare the DefaultAdvisorAutoProxyCreator bean.
I have been struggling with this for some time now, and any help or ideas would be very much appreciated.
EDIT:
This is my interceptor source code, as requested. It basically log the method execution (only methods annotated with #Trace get intercepted). If the method is annotated with #Trace(false), the logging is suspended until the method returns.
public class TraceInterceptor
implements
MethodInterceptor
{
#Override
public Object invoke(
MethodInvocation invocation )
throws Throwable
{
if( ThreadExecutionContext.getCurrentContext().isLogSuspended() ) {
return invocation.proceed();
}
Method method = AopUtils.getMostSpecificMethod( invocation.getMethod(),
invocation.getThis().getClass() );
Trace traceAnnotation = method.getAnnotation( Trace.class );
if( traceAnnotation != null && traceAnnotation.value() == false ) {
ThreadExecutionContext.getCurrentContext().suspendLogging();
Object result = invocation.proceed();
ThreadExecutionContext.getCurrentContext().resumeLogging();
return result;
}
ThreadExecutionContext.startNestedLevel();
SimpleDateFormat dateFormat = new SimpleDateFormat( "dd/MM/yyyy - HH:mm:ss.SSS" );
Logger.log( "Timestamp: " + dateFormat.format( new Date() ) );
String toString = invocation.getThis().toString();
Logger.log( "Class: " + toString.substring( 0, toString.lastIndexOf( '#' ) ) );
Logger.log( "Method: " + getMethodName( method ) );
Logger.log( "Parameters: " );
for( Object arg : invocation.getArguments() ) {
Logger.log( arg );
}
long before = System.currentTimeMillis();
try {
Object result = invocation.proceed();
Logger.log( "Return: " );
Logger.log( result );
return result;
} finally {
long after = System.currentTimeMillis();
Logger.log( "Total execution time (ms): " + ( after - before ) );
ThreadExecutionContext.endNestedLevel();
}
}
// Just formats a method name, with parameter and return types
private String getMethodName(
Method method )
{
StringBuffer methodName = new StringBuffer( method.getReturnType().getSimpleName() + " "
+ method.getName() + "(" );
Class<?>[] parameterTypes = method.getParameterTypes();
if( parameterTypes.length == 0 ) {
methodName.append( ")" );
} else {
int index;
for( index = 0; index < ( parameterTypes.length - 1 ); index++ ) {
methodName.append( parameterTypes[ index ].getSimpleName() + ", " );
}
methodName.append( parameterTypes[ index ].getSimpleName() + ")" );
}
return methodName.toString();
}
}
Thanks!
I found a solution using the 'scoped-proxy' suggested by Bozho.
Since I'm using almost only annotations, my ServiceExecutor class now looks like this:
#Component
#Scope( proxyMode = ScopedProxyMode.TARGET_CLASS )
public class ServiceExecutor
{
#Transactional
public Object execute(...)
{
...
}
}
Until now everything seens to be working fine. I don't know why I have to explicitly tell Spring this class should be proxied using CGLIB, since it does not implement any interface. Maybe it's a bug, I don't know.
Thanks a lot, Bozho.
Something doesn't match here - if ther is a $ProxyXX, it means there is an interface. Make sure there is no interface. Some other notes:
in your pointcut you can check if the target object is already a proxy using (x instanceof Advised), then you can cast to Advised
you can use <aop:scoped-proxy /> to define the proxy strategy per-bean
Is it possible to create a factory or proxy that can decide if thread is running in (Web)Request or background-process (ie. scheduler) and then depending on that information, it creates a session bean or a prototype bean?
Example (pseudo Spring config :)
<bean id="userInfoSession" scope="session" />
<bean id="userInfoStatic" scope="prototype" />
<bean id="currentUserInfoFactory" />
<bean id="someService" class="...">
<property name="userInfo" ref="currentUserInfoFactory.getCurrentUserInfo()" />
</bean>
I hope this makes my question easier to understand...
My Solution
It's never to late to update own questions ;). I solved it with two different instances of client session, one SessionScoped client session and one SingletonScoped session. Both are normal beans.
<bean id="sessionScopedClientSession" class="com.company.product.session.SessionScopedClientSession" scope="session">
<aop:scoped-proxy />
</bean>
<bean id="singletonScopedClientSession" class="com.company.product.session.SingletonScopedClientSession" />
<bean id="clientSession" class="com.company.product.session.ClientSession">
<property name="sessionScopedClientSessionBeanName" value="sessionScopedClientSession" />
<property name="singletonScopedClientSessionBeanName" value="singletonScopedClientSession" />
</bean>
The ClientSession will then decide if singleton or session scope:
private IClientSession getSessionAwareClientData() {
String beanName = (isInSessionContext() ? sessionScopedClientSessionBeanName : singletonScopedClientSessionBeanName);
return (IClientSession) ApplicationContextProvider.getApplicationContext().getBean(beanName);
}
Where session type could be gathered through this:
private boolean isInSessionContext() {
return RequestContextHolder.getRequestAttributes() != null;
}
All the classes implement a interface called IClientSession. Both singletonScoped and sessionScoped beans extends from a BaseClientSession where the implementation is found.
Every service then can use the client session ie:
#Resource
private ClientSession clientSession;
...
public void doSomething() {
Long orgId = clientSession.getSomethingFromSession();
}
Now if we go one step further we can write something like a Emulator for the session. This could be done by initializing the clientSession (which is in no context of a request) the singleton session. Now all services can use the same clientSession and we still can "emulate" a user ie:
clientSessionEmulator.startEmulateUser( testUser );
try {
service.doSomething();
} finally {
clientSessionEmulator.stopEmulation();
}
One more advice: take care about threading in SingletonScoped clientSession instance! Wouw, I thought I could do it with less lines ;) If you like to know more about this approach feel free to contact me.
I created small universal workaround to inject beans depends on context.
Guess we have two beans:
<bean class="xyz.UserInfo" id="userInfo" scope="session" />
<bean class="xyz.UserInfo" id="userInfoSessionLess" />
We want to use "userInfo" bean for web user actions and "userInfoSessionLess" bean for background services for example.
Wa also want to write code and don't want to think about context, for example:
#Autowired
//You will get "java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request?" for session less services.
//We can fix it and autowire "userInfo" or "userInfoSessionLess" depends on context...
private UserInfo userInfo;
public save(Document superSecureDocument) {
...
superSecureDocument.lastModifier = userInfo.getUser();
...
}
Now we need create custom session scope to make it worked:
public class MYSessionScope extends SessionScope implements ApplicationContextAware {
private static final String SESSION_LESS_POSTFIX = "SessionLess";
private ApplicationContext applicationContext;
public Object get(String name, ObjectFactory objectFactory) {
if (isInSessionContext()) {
log.debug("Return session Bean... name = " + name);
return super.get(name, objectFactory);
} else {
log.debug("Trying to access session Bean outside of Request Context... name = " + name + " return bean with name = " + name + SESSION_LESS_POSTFIX);
return applicationContext.getBean(name.replace("scopedTarget.", "") + SESSION_LESS_POSTFIX);
}
}
private boolean isInSessionContext() {
return RequestContextHolder.getRequestAttributes() != null;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Register new scope:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="mySession">
<bean class="com.galantis.gbf.web.MYSessionScope" />
</entry>
</map>
</property>
</bean>
Now we need modify beans definions like this:
<bean class="xyz.UserInfo" id="userInfo" scope="mySession" autowire-candidate="true"/>
<bean class="xyz.UserInfo" id="userInfoSessionLess" autowire-candidate="false"/>
That's all. Bean with name "SessionLess" will be used for all "mySession" scoped beans if we use bean outside of actual web request thread.
Your rephrase is indeed considerably simpler :)
Your currentUserInfoFactory could make use of RequestContextHolder.getRequestAttributes(). If a session is present and associated with the calling thread, then this will return a non-null object, and you can then safely retrieve the session-scoped bean from the context. If it returns a null, then you should fetch the prototype-scoped bean instead.
It's not very neat, but it's simple, and should work.
Create two custom context loaders that bind the same scope defintion to different implementations:
public final class SessionScopeContextLoader extends GenericXmlContextLoader {
protected void customizeContext(final GenericApplicationContext context) {
final SessionScope testSessionScope = new SessionScope();
context.getBeanFactory().registerScope("superscope", testSessionScope);
}
...
}
Then you make a corresponding one for singleton (make your own scope with just statics)
Then you just specify the appropriate context loader in the xml startup for each of the two contexts.