9

I want to get session from a common class. Using @Autowired didn't work.

public class TMessageHandlerFactory implements MessageHandlerFactory {

    @Autowired
    private HttpSession session;

    @Override
    public void data(InputStream data) {
        int userId = (int)session.getAtrribute("key"); //session null
        ....        //do sth
    }
}

The constructor also didn't work

@Component
public class SMTPRunner implements ApplicationRunner {

    @Autowired
    private UserService userService;    // userService can access

    @Autowired
    private HttpSession session;        // session can't access

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        TMessageHandlerFactory myFactory = new TMessageHandlerFactory(session);
        ....
    }
}

I also tried using SpringBeanFactory, it also didn't work.

@Component
public class SpringBeanFactoryUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringBeanFactoryUtil.applicationContext == null) {
            SpringBeanFactoryUtil.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }
}


SpringBeanFactoryUtil only can get my custom bean, can't get HttpSession.
What should I do?

informatik01
  • 16,038
  • 10
  • 74
  • 104
Julius Wang
  • 361
  • 1
  • 6
  • 14

3 Answers3

9

If I understand you right, you want to access something in the session scope from a component that a broader scope (singleton), as such the system can't know which one of the potential concurrent sessions in ther server you are interrested in an practically it would say at spring init time that the session scope isn't defined.

You can get arround that with the ObjectFactory pattern (1 of the possible solution)

import `org.springframework.beans.factory.ObjectFactory`;
// ...
// ...

@Autowired
ObjectFactory<HttpSession> httpSessionFactory;

And then when you need it, from a thread that is bound to the session:

HttpSession session = httpSessionFactory.getObject();

This way spring bind a receipe to get the object you need at the type you call the getObject() method rather than the actual object that is not yet available.

Please understand that if there no session bound to the current thread when you run the code, this will fail (return null) as no session is available. This mean either you call this code from a thread that you failed to forward the request thread local information of the request/session or you call this code from a context where it doesn't make sense.

Matheus Moreira
  • 2,338
  • 4
  • 24
  • 31
Nicolas Bousquet
  • 3,990
  • 1
  • 16
  • 18
  • I totally agree with the last paragraph, but I didn't get how injecting `ObjectFactory` can help or be a workaround – Andrew Tobilko Jan 28 '18 at 19:46
  • This is a matter of scope. A singleton (typical of component or service) should not autorwire directly narrower scope. Bet it request/session or even prototype. The field would never change for the various callers that have different session associated to them. – Nicolas Bousquet Jan 28 '18 at 20:40
  • When a method of the component is called, one can actually access the currently bound object associated to the current session/request. That can be a direct call the application context or you can use this ObjectFactory. So it is not called at init time as the object is not available and there no correct value for it but when the service is used and the object is really available. As the instance may change for each session/request, it cannot be stored, the getObject() method (or getBean of application contex) must be called each time. – Nicolas Bousquet Jan 28 '18 at 20:48
  • thank you, finally I found I shouldn't get session in that case, I shoud do this in some range that have RequestContext. So I handler the data(need session) in controller, and I finally success. – Julius Wang Jan 30 '18 at 06:07
1

Create session scope bean in pojo style. Inject it and use where you need set or get data from HttpSession. Spring will automatically set corresponding data into HttpSession. Eg:

@Component
@Scope(proxyMode= ScopedProxyMode.TARGET_CLASS, value=WebApplicationContext.SCOPE_SESSION)
public class SessionData {
    private Currency currency;

    public Currency getCurrency() {
        return currency;
    }

    public void setCurrency(Currency currency) {
        this.currency = currency;
    }
}


@Service
public class UserService {

   @Autowired
   private SessionData sessionData;

   public Currency getCurrentCurrency() {
       return sessionData.getCurrency();
   }

   public void setCurrentCurrency(Currency currency) {
       sessionData.setCurrency(currency);
   }
}

In such way getting/setting current currency using UserService will reflect in current HttpSession with corresponding attribute name of session bean property ('currency').

1

@Autowired HttpSession session in a Singleton class will inject a AutowireUtils$ObjectFactoryDelegatingInvocationHandler - Spring seems clever enough to automatically create a proxy for a narrower scoped object.

lathspell
  • 3,040
  • 1
  • 30
  • 49