简介:轻量封装Spring MVC
因为本人在国内最大的电子商务公司工作期间,深感一个好的Web框架可以大大提高工作效率,而一个不好的Web框架,又可以大大的降低开发效率。所以,在根据笔者在从事电子商务开发的这几年中,对各个应用场景而开发的一个轻量封装Spring MVC的一个Web框架。
笔者工作的这几年之中,总结并开发了如下几个框架: summercool-web(Web框架,已经应用于某国内大型网络公司的等重要应用)、summercool-hsf(基于Netty实现的RPC框架,已经应用国内某移动互联网公司)、summercool-ddl(基于Mybaits的分表分库框架,已经应用国内某移动互联网公司);相继缓存方案、和消息系统解决方案也会慢慢开源。summercool框架做为笔者的第一个开源框架
框架地址:http://summercool.googlecode.com/svn/trunk/summercool-web
应用地址:http://summercool.googlecode.com/svn/trunk/summercool-petstore
工具地址:http://summercool.googlecode.com/svn/trunk/summercool-tools
说明:此框架要用到spring-tools文件夹中的security文件夹中的文件,使用此框架的人员请将security文件夹的内容替换到JDK中的security文件夹中
一、summercool-petstore应用结构
1. petstore-module.xml配置文件说明
<bean name="petstore" class="org.summercool.web.module.WebModuleConfigurer">
<property name="moduleName" value="petstore" />
<property name="uriExtension" value=".htm" />
<property name="moduleBasePackage" value="org.summercool.platform.web.module" />
<property name="context" value="/" />
<property name="contextPackage" value="/petstore/" />
</bean>
<bean class="org.summercool.web.module.WebModuleUriExtensionConfigurer">
<property name="uriExtensions">
<util:list>
<value>.htm</value>
</util:list>
</property>
</bean>
说明:1) 该配置项的详细说明,笔者已经在上一篇进行了详细说明,地址是:http://dragonsoar.iteye.com/blog/1454095
2) 现在笔者就拿summercool-petstore应用来进行详细的说明:
A. 应用模块扫描
moduleBasePackage + contextPackage = 完整的该应用模块的招扫描路径
扫描该完整包路径下面的:controllers和widgets文件夹
比如说:summercool-petstore应用下面的controllers的文件夹下面的IndexController.java --> /index.htm
规则如下:
1. moduleBasePackage + contextPackage = org.summercool.platform.web.module.petstore
2. context = /
3. org.summercool.platform.web.module.petstore.controllers = /
4. uriExtension = .htm
所以:
1. org.summercool.platform.web.module.petstore.controllers.IndexController.java = /index.htm
该问应用地址:http://127.0.0.1:8080 或 http://127.0.0.1:8080/index.htm
B. 应用处理的请求
1. org.summercool.web.module.WebModuleUriExtensionConfigurer --> list --> .htm
2. 上面这个配置中,配置的是只有url地址扩展名为.htm的才会交给summercool框架处理
3. 如,我们要是访问: http://127.0.0.1:8080/index.php
二、summercool-petstore的Controller和Widget开发
1. Controller开发
1) Controller的扫描规则是: /IndexController.java --> /index.htm
2) 扫描的附件条件是,被扫描加载的Controller类,必须是实现Sprng MVC的标准Controller接口和类名必须是以Controller结尾。
2. 笔者拿/LoginController.java举例
package org.summercool.platform.web.module.petstore.controllers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
public class IndexController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
return new ModelAndView("/petstore/views/index");
}
}
1) 因为IndexController继承自AbstractController,而该类实现了Controller接口,所以可以被扫描进来
2) ModelAndView接口不细说了,因为AbstractController是Spring MVC的标准类,所以不做详细介绍了。
说明:笔者强烈建议应用的请求,如果只是页面展示不涉及到表单提交的请继承AbstractController,AbstractController的处理流程图如下:
3. 笔者再拿/LoginController.java进行表单提交的举例
package org.summercool.platform.web.module.petstore.controllers;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.summercool.platform.web.module.petstore.config.cookie.CookieUtils;
import org.summercool.platform.web.module.petstore.config.cookie.UserDO;
import org.summercool.platform.web.module.petstore.formbean.LoginFormBean;
@SuppressWarnings("deprecation")
public class LoginController extends SimpleFormController {
public LoginController() {
setBindOnNewForm(true);
setCommandName("loginFormBean");
setCommandClass(LoginFormBean.class);
}
@Override
protected void onBindOnNewForm(HttpServletRequest request, Object command) throws Exception {
LoginFormBean loginFormBean = (LoginFormBean) command;
loginFormBean.setUserName("请输入用户名和密码");loginFormBean.setPassword("");
}
@Override
protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors) throws Exception {
LoginFormBean loginFormBean = (LoginFormBean) command;
//
if (StringUtils.isEmpty(loginFormBean.getUserName()) || StringUtils.isEmpty(loginFormBean.getPassword())) {
errors.reject("-1", "用户名或密码不能为空");
return;
}
if ("admin".equals(loginFormBean.getUserName()) && "111111".equals(loginFormBean.getPassword())) {
UserDO userInfo = new UserDO();
userInfo.setId(0L);
userInfo.setUserName("admin");
userInfo.setPassword("111111");
CookieUtils.clearCookie(request);
CookieUtils.writeCookie(request, userInfo);
} else {
errors.reject("-2", "用户名或密码错误");
}
}
@Override
protected ModelAndView onSubmit(Object command) throws Exception {
return new ModelAndView("redirect:/index.htm");
}
}
1) 笔者认为SimpleFormController是Spring MVC中最经典的一个Controller接口的抽象实现。为什么呢?因为SimpleFormController考虑到了很多来应对各种复杂的场景,下面笔者就一一的做出说明。
2) 比如说,有一种应用场景;在用户登录时要给出一句提示语,如:“请输入用户名和密码”等这样的字眼显示在form表单中的userName的输入字段,但是在password字段则每次登录页面都必须要清空,不能让浏览器记录我们的登录用户名和密码。
那么,LoginController中下面代码片段可以实现上面的需求:
public LoginController() {
setBindOnNewForm(true);
setCommandName("loginFormBean");
setCommandClass(LoginFormBean.class);
}
@Override
protected void onBindOnNewForm(HttpServletRequest request, Object command) throws Exception {
LoginFormBean loginFormBean = (LoginFormBean) command;
loginFormBean.setUserName("请输入用户名和密码");loginFormBean.setPassword("");
}
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>Summercool, Petstore</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link href="/css/bootstrap.css" rel="stylesheet">
<style type="text/css">
body {
padding-top: 60px;
padding-bottom: 40px;
}
#tb td ,#tb th{
border-top: 0px;
}
</style>
<link href="/css/bootstrap-responsive.css" rel="stylesheet">
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
${widget("/petstore/widgets/header")}
<div class="container">
<form method="post">
<fieldset>
<@spring.bind "loginFormBean.*"/>
<div class="input">
用户: <input type="text" size="30" name="userName" id="userName" class="medium" value="${(loginFormBean.userName)!}">
</div>
<div class="input">
密码: <input type="password" size="30" name="password" id="password" class="medium" value="${(loginFormBean.password)!}">
</div>
<#if (status.error)!>
<#list status.errors.allErrors as error>
<div class="alert alert-error">
${error.defaultMessage}
</div>
</#list>
</#if>
<div class="actions">
<input type="submit" value="提交" class="btn"> <button class="btn" type="reset">取消</button>
</div>
</fieldset>
</form>
<hr>
${widget("/petstore/widgets/footer")}
</div>
</body>
</html>
说明:setBindOnNewForm(true); 代表,当进入且第一次页面请求显示Login表单时,是否执行onBindOnNewForm()函数。
什么是当进入且第一次页面请求显示Login表单呢?意思就是说,当请求一个Login表单时,那么这就是一次请求,如果在请求/login.htm时的请求是get请求,那么Spring MVC会认为这是进入且第一次页面请求。反则,如果已经显示此页面,然后提交form表单,那么这是一次post请求,那么Spring MVC会认为这不是第一次页面请求,则不会在重新返回显示页面的时候再执行onBindOnNewForm()函数了。
在/index.ftl的页面模版中,我们可以看出,每次"userName"和"password"输入框都是通过“CommandObject”对象的值拿出来的,所以就可以做到每次第一次进入登录页面的时候,都给出指定的提示信息和密码为空的设置了。
3) 上面提到了会在Form表单提交后还会返回/login.htm页面,那么会在什么情况下返回到/login.htm页面呢?那当然是在提表交单发生错误的情况下了。笔者认为SimpleFormController在处理错误处理这块也非常的经典;如:在Form 表单提交的时,SimpleFromController会先执行onBindAndValidate()函数,如果该函数在校验输入参数的时候发生异常,则使用errors对象添加错误信息;当该函数返回后,Spring MVC会判断errors对象是否为空,如果为空,则执行onSubmit()函数,如果不为空,则返回到/index.htm页面。
SimpleFormController笔者认为真的是非常经典,里面还有两个函数需要介绍:refferenceData()和showForm()函数。
refferenceData():当每次显示form表单页面的时候都执行此函数
showForm():当onSubmit()函数执行完成后想继续返回到Form表单的提交页面,而直接执行此函数即可返回到Form表单的提交页面。
SimpleFormController处理流程图如下:
4. Widget函数讲解
1) 首先,我们还是先看一下/login.ftl页面模版文件,如下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>Summercool, Petstore</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link href="/css/bootstrap.css" rel="stylesheet">
<style type="text/css">
body {
padding-top: 60px;
padding-bottom: 40px;
}
#tb td ,#tb th{
border-top: 0px;
}
</style>
<link href="/css/bootstrap-responsive.css" rel="stylesheet">
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
${widget("/petstore/widgets/header")}
<div class="container">
<form method="post">
<fieldset>
<@spring.bind "loginFormBean.*"/>
<div class="input">
用户: <input type="text" size="30" name="userName" id="userName" class="medium" value="${(loginFormBean.userName)!}">
</div>
<div class="input">
密码: <input type="password" size="30" name="password" id="password" class="medium" value="${(loginFormBean.password)!}">
</div>
<#if (status.error)!>
<#list status.errors.allErrors as error>
<div class="alert alert-error">
${error.defaultMessage}
</div>
</#list>
</#if>
<div class="actions">
<input type="submit" value="提交" class="btn"> <button class="btn" type="reset">取消</button>
</div>
</fieldset>
</form>
<hr>
${widget("/petstore/widgets/footer")}
</div>
</body>
</html>
2) 上面有两个${widget()}的freemarker函数笔者请各位码农注意一下:
${widget("/petstore/widgets/header")}
${widget("/petstore/widgets/footer")}
说明:通过查看大家可能也知道这两个函数具体是什么意思了;对!是加载局部的ftl页面模版。那么,码农们可能又要怀疑了,freemarker不是有<#include>标签吗?为什么还要再弄一个${widget()}函数呢,是不是多此一举了。答案当然是否定的;因为显示一个局部页面的时候,我们肯定还要在局部页面里面加一些业务逻辑,而<#include>标签是无法做到的,而${widget()}函数则可以。
规则:${widget("/petstore/widgets/header")} --先加载--> /petstore/widgets/HeaderWidget.java --再加载--> /petstore/widgets/header.ftl (HeaderWidget.java可选,即如果没有Widget类,则直接加载header.ftl页面)
而我们想在显示"header.ftl"局部页面的时候,我们可以将一部分显示的业务逻辑和"header.ftl"局部页面需要显示的页面数据通过此类加载;那么我们查看下"HeaderWidget.java"类,如下:
package org.summercool.platform.web.module.petstore.widgets;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.summercool.platform.web.module.petstore.config.cookie.CookieUtils;
import org.summercool.web.servlet.view.freemarker.FreeMarkerWidget;
public class HeaderWidget implements FreeMarkerWidget {
public void referenceData(HttpServletRequest request, Map<String, Object> model) {
model.put("userName", CookieUtils.getUserName(request));
}
}
说明:上面的代码中可以看出,"header.ftl"页面中会显示登录人的用户名信息,那么用户名的信息则就是通过referenceData()函数加载的,model是"header.ftl"的页面变量信息map。
补充:从此篇结束后,summercool框架的基本使用则就不是任何问题了,下一篇笔者会介绍AroundPipeline、PreProcessPipeline、PostProcessPipeline和ExceptionPipeline,并会结合权限处理和异常处理等给出实例的应用实例和相关解决方案。