在SpringMVC中自定义上下文的一些想法

很多时候,开发Web应用时各种前后台交互让人很烦闷,尤其是各种权限验证啦,购物车商品信息啦等等……

大家第一时间想到的是: 采用HttpSession来存这些对象.然后就是各种参数从Controller传到Service再传到持久层方法.一直传一直传.

在现阶段需求变化极快的前提下,如果架构没事先想好各种参数的传递,很容易导致我们需要的一些参数要通过方法层层传递才能用到.为什么不想一个简单点的,直接通过自定义上下文轻松拿到的方法来实现我们随时随地获取这些对象的方式呢?

改变传统的参数层层传递,让大家写出简洁优美的代码是我的最终理想.所以我做了一点试验,仅供参考.

 

首先,写一个自定义的简单上下文接口,也可以称之为简单的对象缓存接口.接口加上实现类,大约不到200行代码,很轻松.

然后就是让我们的上下文存点东西.这里要注意在多线程环境下的对象隔离.如果不是前后端分离的做法,可以采用ThreadLocal办到,一个工具类搞定.如果是前后端分离的方式,导致的线程无法跟踪的问题,我们在后面讨论.

接下来就是把自定义的上下文接口用起来.我们先来传统的.大概三种做法:

 

一. 通过方法参数传入HttpServletRequest对象或者HttpSession对象

自Spring2.5的annotation使得 controller 摆脱了 Servlet API 对方法参数的限制,这里就不赘述了.Spring对annotationed的 action 的参数提供自动绑定支持的参数类型包括 Servlet API 里面的 Request/Response/HttpSession(包含Request、Response在Servlet API 中声明的具体子类)。于是开发人员可以通过在 controller 的方法参数中声明 Request 对象或者 HttpSession 对象,来让容器注入相应的对象。

例如:

@RequestMapping
public void hello(HttpSession session){
    User user = (User)session.getAttribute("currentUser");
}

优点:
1. 程序中直接得到底层的 Request/HttpSession 对象,直接使用 Servlet API 规范中定义的方法操作这些对象中的属性,直接而简单。
2. controller 需要访问哪些具体的 Session 属性,是由自己控制的,真正精确到 Session 中的每个特定属性。
缺点:
1. 程序对 Servlet API 产生依赖。虽然 controller 类已经不需要从 HttpServlet 继承,但仍需要 Servlet API 才能完成编译运行,乃至测试。
2. 暴露了底层 Servlet API,暴露了很多并不需要的底层方法和类,开发人员容易滥用这些 API。

 

 

二. 通过定制拦截器(Interceptor)在controller类级别注入需要的上下文对象

Interceptor 是 Spring 提供的扩展点之一,SpringMVC 会在 handle 某个 request 前后调用在配置中定义的 Interceptor 完成一些切面的工作,比如验证用户权限、处理分发等,类似于 AOP。那么,我们可以提取这样一个“横切点”,在 SpringMVC 调用方法前,在 Interceptor 的 preHandle 方法中给 controller 注入上下文成员变量,使之具有自定义上下文对象。
此外还需要给这些特定 controller 声明一类 interface,比如 IContextAware。这样开发人员就可以只针对这些需要注入自定义上下文对象的 controller 进行注入增强。

IContextAware接口:

public interface IContextAware {
    public void setContext(MyApplicationContext context);
}

UserController类:

@Controller
@RequestMapping(value="/user")
@Scope(value="prototype")
public class UserController implements IContextAware{
    private static final Logger log = LoggerFactory.getLogger(UserController.class);
    @Autowired
    private IUserService service;
    private MyApplicationContext context;
   
    @Override
    public void setContext(MyApplicationContext context) {
        this.context = context;
    }
   
    @RequestMapping(value="/get/{id}")
    @ResponseBody
    public User getUser(@PathVariable("id") String id){
        log.info("Find user with id={}", id);
        User user = null;
        try {
            user = service.findUserById(id);
            if (user != null) {
                context.setAttribute("currentUser", user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (User) context.getAttribute("currentUser");
    }

}

HandlerInterceptor实现类:

public class MyInterceptor implements HandlerInterceptor {
    private static final Logger log = LoggerFactory.getLogger(MyInterceptor.class);
    private static final String MY_APPLICATION_CONTEXT = "MYAPPLICATIONCONTEXT";
   
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        IContextAware controllerAware = null;
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Class<?> clazz = handlerMethod.getBeanType();
        Type[] interfaces = clazz.getGenericInterfaces();
        if (interfaces != null && interfaces.length > 0) {
            for (int i = 0; i < interfaces.length; i++) {
                if (interfaces[i] == IContextAware.class) {
                    controllerAware = (IContextAware) handlerMethod.getBean();
                }
            }
        }
        HttpSession session = request.getSession();
        if (session == null) {
            session = request.getSession(true);
        }
        log.info("当前HttpSession的sessionId={}", session.getId());
        MyApplicationContext context = (MyApplicationContext) session.getAttribute(MY_APPLICATION_CONTEXT);
        if (context == null) {
            context = new MyApplicationContextImpl();
            session.setAttribute(MY_APPLICATION_CONTEXT, context);
        }
        log.info("当前自定义上下文对象hashcode={}", context.hashCode());
        controllerAware.setContext(context);
        return true;
    }

    ……

}

为了让 SpringMVC 能调用我们定义的 Interceptor,我们还需要在 SpringMVC 配置文件中声明该 Interceptor

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="myContextInterceptor"/>
            </list>
        </property>
</bean>
<bean id="myContextInterceptor" class="net.fantesy84.interceptor.MyInterceptor"></bean>

优点:
1. 对 Servlet API 的访问被移到了自 SpringMVC API 扩展的 Interceptor,controller 不需要关心自定义上下文对象如何得到。
2. 开发人员可以通过随时添加或移除 Interceptor 来完成对不同参数在某一类型 controller 上的注入。
3. controller 的自定义上下文对象通过外界注入,测试时开发人员可以很容易地注入自己想要的自定义上下文对象。
4. controller 类去掉了对 Servlet API 的依赖,更贴近 POJO 和通用。
5. controller 类是通过对 interface 的声明来辅助完成注入的,并不存在任何继承依赖。
缺点:
1. SpringMVC 对 controller 默认是按照单例(singleton)处理的,在 controller 类中添加一个成员变量,可能会引起多线程的安全问题。不过我们可以通过声明@Scope(value="prototype")来解决;
2. 因为自定义上下文对象是定义为 controller 的成员变量,而且是通过 setter 注入进来,在测试时需要很小心地保证对controller 注入了自定义上下文对象,否则有可能我们拿到的就不一定是一个“好公民”(Good Citizen)。

 

三. 通过方法参数处理类(MethodArgumentResolver)在方法级别注入自定义上下文对象

正如前面所看到的,SpringMVC 提供了不少扩展点给开发人员扩展,让开发人员可以按需索取,plugin 上自定义的类或 handler。那么,在 controller 类的层次上,SpringMVC 提供了 Interceptor 扩展,在 action 上有没有提供相应的 handler 呢?如果我们能够对方法实现注入,出现的种种不足了。
通过查阅 SpringMVC API 文档,SpringMVC 其实也为方法级别提供了方法参数注入的 Resolver 扩展,允许开发人员给 HandlerMapper 类 set 自定义的 MethodArgumentResolver。

UserController类:

@Controller
@RequestMapping(value="/user")
@Scope(value="prototype")
public class UserController{
    private static final Logger log = LoggerFactory.getLogger(UserController.class);
    @Autowired
    private IUserService service;
   
    @RequestMapping(value="/get/{id}")
    @ResponseBody
    public User getUser(@PathVariable("id") String id, MyApplicationContext context){
        log.info("Find user with id={}", id);
        User user = null;
        try {
            user = service.findUserById(id);
            if (user != null) {
                context.setAttribute("currentUser", user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (User) context.getAttribute("currentUser");
    }

}

WebArgumentResolver接口实现类:

public class ContextArgResolver implements WebArgumentResolver {
    private static final String MY_APPLICATION_CONTEXT = "MYAPPLICATIONCONTEXT";
   
    @Override
    public Object resolveArgument(MethodParameter methodParameter,NativeWebRequest webRequest) throws Exception {
        if (methodParameter.getParameterType() == MyApplicationContext.class) {
            return webRequest.getAttribute(MY_APPLICATION_CONTEXT, RequestAttributes.SCOPE_SESSION);
        }
        return UNRESOLVED;
    }

}

配置文件的相关配置如下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <ref bean="jacksonConverter"/>
            </list>
        </property>
        <property name="customArgumentResolvers">
            <list>
                <ref bean="contextArgResolver"/>
            </list>
        </property>
</bean>
<bean id="contextArgResolver" class="net.fantesy84.resolver.ContextArgResolver"></bean>
<bean id="jacksonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    <property name="supportedMediaTypes">
        <list>
            <value>application/json;charset=UTF-8</value>
        </list>
    </property>
</bean>

优点:
1. 具备第二种方案的所有优点
2. 真正做到了按需分配,只在真正需要对象的位置注入具体的对象,减少其他地方对该对象的依赖。
3. 其他人能很容易地从方法的参数列表得知方法所需要的依赖,API 更清晰易懂。
4. 对于很多方法需要的某一类参数,可以在唯一的设置点用很方便一致的方式进行注入。
不足:
1. 对象依赖注入是针对所有方法的,注入粒度还是较粗。不能做到具体方法访问具体的自定义上下文属性;

 

以上三种方法,在前后端统一的时候是好用的,但是如果遇到前后端分离的时候,就会变得很痛苦! 针对前后端分离的处理方式,我们下一篇再详细的剖析.

时间: 2024-10-09 00:30:30

在SpringMVC中自定义上下文的一些想法的相关文章

springmvc中自定义拦截器以及拦截器的执行过程

1.拦截器在一次请求中的执行流程 2.拦截器入门案例 2.1springMVC.xml的编写 <?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:m

详解Springboot中自定义SpringMVC配置

详解Springboot中自定义SpringMVC配置 WebMvcConfigurer接口 ? 这个接口可以自定义拦截器,例如跨域设置.类型转化器等等.可以说此接口为开发者提前想到了很多拦截层面的需求,方便开发者自由选择使用.由于Spring5.0废弃了WebMvcConfigurerAdapter,所以WebMvcConfigurer继承了WebMvcConfigurerAdapter大部分内容. WebMvcConfigurer接口中的方法 举例1:configurePathMatch配置

详解SpringMVC中Controller的方法中参数的工作原理

前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html SpringMVC中Controller的方法参数可以是Integer,Double,自定义对象,ServletRequest,ServletResponse,ModelAndView等等,非常灵活.本文将分析SpringMVC是如何对这些参数进行处理的,

详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析] good

目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口的具体应用 常用HandlerMethodArgumentResolver介绍 常用HandlerMethodReturnValueHandler介绍 本文开头现象解释以及解决方案 编写自定义的HandlerMet

【SpringMVC学习07】SpringMVC中的统一异常处理

我们知道,系统中异常包括:编译时异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试通过手段减少运行时异常的发生.在开发中,不管是dao层.service层还是controller层,都有可能抛出异常,在springmvc中,能将所有类型的异常处理从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护.这篇博文主要总结一下SpringMVC中如何统一处理异常. 1. 异常处理思路 首先来看一下在spr

SpringMVC中使用Interceptor拦截器

SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那样子判断当前时间是否是购票时间. 一.定义Interceptor实现类 SpringMVC 中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的.在SpringMVC 中定义一个Interceptor 非常简单,主要有两种方式,第一种方式是要定义的Intercep

【SpringMVC学习05】SpringMVC中的参数绑定总结

众所周知,springmvc是用来处理页面的一些请求,然后将数据再通过视图返回给用户的,前面的几篇博文中使用的都是静态数据,为了能快速入门springmvc,在这一篇博文中,我将总结一下springmvc中如何接收前台页面的参数,即springmvc中的参数绑定问题. 1. 参数绑定的过程 我们可以回忆一下,在struts2中,是通过在Action中定义一个成员变量来接收前台传进来的参数,而在springmvc中,接收页面提交的数据是通过方法形参来接收的.从客户端请求的key/value数据,经

【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html SpringMVC中Controller的方法参数可以是Integer,Double,自定义对象,ServletRequest,ServletResponse,ModelAndView等等,非常灵活.本文将分析SpringMVC是如何对这些参数进行处理的,

详解SpringMVC中Controller的方法中参数的工作原理——基于maven

转自:http://www.tuicool.com/articles/F7byQn 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html SpringMVC中Controller的方法参数可以是Integer,Double,自定义对象,ServletRequest,ServletResponse,ModelA