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 进行包装或者修改之后的对象)。

  

 

posted @ 2018-04-08 20:14  QiaoZhi  阅读(4981)  评论(0编辑  收藏  举报