很多时候,开发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实现类: