spring的普通类中获取session和request对像
在使用spring时,经常需要在普通类中获取session,request等对像.
1.第一钟方式,针对Spring和Struts2集成的项目:
在有使用struts2时,因为struts2有一个接口使用org.apache.struts2.ServletActionContext即可很方便的取到session对像.
用法:
ServletActionContext.getRequest().getSession();
例如:
// 整合了Struts,所有用这种方式获取session中属性(亲测有效) User user = (User) ServletActionContext.getRequest().getSession().getAttribute("userinfo");//获取session中的user对象进而获取操作人名字
2.但在单独使用spring时如何在普通类中获取session,reuqest(亲测有效,注意:与struts整合之后下面方法失效,获取不到session中的值)
首先要在web.xml增加如下代码:(网上说需要这一步,我在IDEA中没有用这一步也成功了)
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener>
第一种方式采用自动注入:
接着在普通类中:
@Autowired//自动注入request private HttpServletRequest request; @Autowired private HttpSession session;
例如我的测试:
package cn.xm.jwxt.controller.system; import cn.xm.jwxt.bean.system.User; import cn.xm.jwxt.service.system.UserService; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.sql.SQLException; import java.util.List; @Controller public class UserAction { private Logger logger = Logger.getLogger(UserAction.class); @Autowired private UserService userService; @Autowired//自动注入request private HttpServletRequest request; @Autowired private HttpSession session; /** * 根据userCode查询user * @param userCode * @return */ @RequestMapping("/findUserById.action") public @ResponseBody User findUserById(String userCode){ User user = null; try { user = userService.findUserByUsercode(userCode); Object username = session.getAttribute("username"); System.out.println(username.toString()); } catch (SQLException e) { logger.error("根据userCode查询user出错",e); } return user; } /** * 测试环境 * @return */ @RequestMapping("/test.action") public @ResponseBody String testEnv() { //通过自动注入session之后保存一个属性 session.setAttribute("username","qlq"); return "success"; } }
第二种方式使用RequestContextHolder获取:
//获取request与session HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpSession session = request.getSession(false);
例如我的测试:
package cn.xm.jwxt.controller.system; import cn.xm.jwxt.bean.system.User; import cn.xm.jwxt.service.system.UserService; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.sql.SQLException; import java.util.List; @Controller public class UserAction { private Logger logger = Logger.getLogger(UserAction.class); @Autowired private UserService userService; /** * 根据userCode查询user * @param userCode * @return */ @RequestMapping("/findUserById.action") public @ResponseBody User findUserById(String userCode){ User user = null; try { user = userService.findUserByUsercode(userCode); //获取request与session HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpSession session = request.getSession(false); Object username = session.getAttribute("username"); System.out.println(username.toString()); } catch (SQLException e) { logger.error("根据userCode查询user出错",e); } return user; }/** * 测试环境 * @return */ @RequestMapping("/test.action") public @ResponseBody String testEnv() { //获取request与session HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpSession session = request.getSession(false); session.setAttribute("username","qlq"); return "success"; } }
补充: 原理查看
1. SpringMVC 启动之后会注入一个 RequestContextFilter 对象。 org.springframework.web.filter.RequestContextFilter:(条件是缺少Listener或者Filter 的时候注入该filter)
@Bean @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class }) @ConditionalOnMissingFilterBean(RequestContextFilter.class) public static RequestContextFilter requestContextFilter() { return new OrderedRequestContextFilter(); }
2. 容器启动过程中调用 org.springframework.boot.web.servlet.ServletContextInitializerBeans#getOrderedBeansOfType(org.springframework.beans.factory.ListableBeanFactory, java.lang.Class<T>, java.util.Set<?>) 对所有动态注册的Filter进行排序, 排序完之后维护起来,最后动态注册到ServletContext 中:
private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type, Set<?> excludes) { String[] names = beanFactory.getBeanNamesForType(type, true, false); Map<String, T> map = new LinkedHashMap(); String[] var6 = names; int var7 = names.length; for(int var8 = 0; var8 < var7; ++var8) { String name = var6[var8]; if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) { T bean = beanFactory.getBean(name, type); if (!excludes.contains(bean)) { map.put(name, bean); } } } List<Entry<String, T>> beans = new ArrayList(map.entrySet()); beans.sort((o1, o2) -> { return AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()); }); return beans; }
然后调用到: org.springframework.core.OrderComparator#doCompare
private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderComparator.OrderSourceProvider sourceProvider) { boolean p1 = o1 instanceof PriorityOrdered; boolean p2 = o2 instanceof PriorityOrdered; if (p1 && !p2) { return -1; } else if (p2 && !p1) { return 1; } else { int i1 = this.getOrder(o1, sourceProvider); int i2 = this.getOrder(o2, sourceProvider); return Integer.compare(i1, i2); } }
可以看到是正序排序。
3. Filter 源码如下:
package org.springframework.boot.web.servlet.filter; import org.springframework.web.filter.RequestContextFilter; public class OrderedRequestContextFilter extends RequestContextFilter implements OrderedFilter { private int order = -105; public OrderedRequestContextFilter() { } public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } }
org.springframework.web.filter.RequestContextFilter:
public class RequestContextFilter extends OncePerRequestFilter { private boolean threadContextInheritable = false; /** * Set whether to expose the LocaleContext and RequestAttributes as inheritable * for child threads (using an {@link java.lang.InheritableThreadLocal}). * <p>Default is "false", to avoid side effects on spawned background threads. * Switch this to "true" to enable inheritance for custom child threads which * are spawned during request processing and only used for this request * (that is, ending after their initial task, without reuse of the thread). * <p><b>WARNING:</b> Do not use inheritance for child threads if you are * accessing a thread pool which is configured to potentially add new threads * on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}), * since this will expose the inherited context to such a pooled thread. */ public void setThreadContextInheritable(boolean threadContextInheritable) { this.threadContextInheritable = threadContextInheritable; } /** * Returns "false" so that the filter may set up the request context in each * asynchronously dispatched thread. */ @Override protected boolean shouldNotFilterAsyncDispatch() { return false; } /** * Returns "false" so that the filter may set up the request context in an * error dispatch. */ @Override protected boolean shouldNotFilterErrorDispatch() { return false; } @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { ServletRequestAttributes attributes = new ServletRequestAttributes(request, response); initContextHolders(request, attributes); try { filterChain.doFilter(request, response); } finally { resetContextHolders(); if (logger.isTraceEnabled()) { logger.trace("Cleared thread-bound request context: " + request); } attributes.requestCompleted(); } } private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) { LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable); RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); if (logger.isTraceEnabled()) { logger.trace("Bound request context to thread: " + request); } } private void resetContextHolders() { LocaleContextHolder.resetLocaleContext(); RequestContextHolder.resetRequestAttributes(); } }
可以看到核心逻辑是在: doFilterInternal
1》 创建ServletRequestAttributes 对象
2》 调用org.springframework.web.context.request.RequestContextHolder#setRequestAttributes(org.springframework.web.context.request.RequestAttributes, boolean) 维护到ThreadLocal 中。 这样可以在当前线程环境中使用。
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) { if (attributes == null) { resetRequestAttributes(); } else { if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } } private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<>("Request context");
4. 另外:在Spring 的DispatcherServlet 执行doService 方法之前,走的org.springframework.web.servlet.FrameworkServlet#processRequest 父类的模板方法:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); try { doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); publishRequestHandledEvent(request, response, startTime, failureCause); } } @Nullable protected ServletRequestAttributes buildRequestAttributes(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable RequestAttributes previousAttributes) { if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) { return new ServletRequestAttributes(request, response); } else { return null; // preserve the pre-bound RequestAttributes instance } }
可以看到在执行dispatcherServlet 之前,有一个RequestContextHolder.getRequestAttributes(); 获取到之前的属性; 然后调用org.springframework.web.servlet.FrameworkServlet#initContextHolders 重置ThreadLocal 内部维护的相关对象:
private void initContextHolders(HttpServletRequest request, @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) { if (localeContext != null) { LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable); } if (requestAttributes != null) { RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); } }
这样可确保我们在 Controller 中用到的是最新的Request 和 Response (可能是经过filter 进行包装或者修改之后的对象)。