SSM Chapter 12 SpringMVC扩展和SSM框架整合

SSM Chapter 12 SpringMVC扩展和SSM框架整合 笔记

本章目标:

  • 掌握JSON对象的处理
  • 理解数据转换和格式化
  • 了解本地化
  • 掌握Spring MVC+Spring+MyBatis的框架搭建 , 并在此框架上熟练开发

本章简介 :

经过之前的章节的学习,基本上已经完成了超市订单管理系统的功能改造,但是对于一些功能还可以进一步优化,比如删除功能,文件上传,修改个人密码,增加用户时的同名验证等都可以采用ajax异步实现,以提升用户体验.在本章中,我们将使用Spring MVC 处理JSON对象,并对SpringMVC框架中的数据转换与数据格式化处理进行详细介绍.

同时完成SpringMVC + Spring + MyBatis的空间搭建,并在此框架上熟练开发.

1 . JSON对象的处理

1.1 JSON

1. JSON含义:

JSON的全称是”JavaScript Object Notation”,意思是JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式。

JSON是js的原生内容,也就意味着js可以直接取出json对象中的数据.

JSON是一种与语言无关的数据交换的格式,作用:使用ajax进行前后台数据交换!

(XML也是一种数据交换格式,为什么没有选择XML呢?因为XML虽然可以作为跨平台的数据交换格式,但是在JS(JavaScript的简写)中处理XML非常不方便,同时XML标记比数据多,增加了交换产生的流量,而JSON没有附加的任何标记,在JS中可作为对象处理,所以我们更倾向于选择JSON来交换数据。)

2. JSON定义
var json = {:,:,
    .....
}

说明 :

json中的键 用双引号括起来 值可以是任意类型的数据 ( 严格的json值不会出现function (){…} 严格的json键用双引号括起来)

注意:

json的key是字符串(不能以数字开头) json的value是Object

3. JSON语法:
  • 定义JSON对象:

    var JSON对象 = { "name" : value, "name" : value, …… };
    
  • 定义JSON数组:

    var JSON数组 = [ value, value, …… ];
    
  • 定义JSON对象数组:

    var personArray = [ { "name":"张三","age":30 }, {
    "name":"李四", "age":40 } ];
    
4. 获取JSON数据
json对象.键 或者 json对象["键"]

案例演示如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>测试json对象</title>
    <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            //定义json对象
            var user = {
                "name": "张三",
                "ID": 1,
                "age": 25
            };
            $("#jsonObject").append("ID:" + user.ID 
                                    + "<br> 姓名:" + user.name
                + "<br>年龄:" + user.age);
            //定义json数组
            var arr = ["中国", "美国", "俄罗斯"];
            $(arr).each(function () {
                $("#jsonSel").append("<option>" + 
                                     this + "</option>");
                $("#jsonUl").append("<li>" + this + "</li>");
            });
            var userArr = [
                {
                    "name": "张三",
                    "age": 23,
                    "id": 1
                },
                {
                    "name": "李四",
                    "age": 25,
                    "id": 2
                },
                {
                    "name": "王武",
                    "age": 27,
                    "id": 3
                }
            ];
            //创建table
            var table = $("<table border='1' ></table>")
            .append("<tr><td>id</td>" +
                "<td>姓名</td><td>年龄</td></tr>");
            $(userArr).each(function () {
                table.append("<tr><td>" + this.id + "</td>" +
                    "<td>" + this.name + "</td><td>" 
                             + this.age + "</td></tr>");
            });
            $("#jsonArr").append(table);
        });
    </script>
</head>
<body>
    一:定义JSON对象:
    <div id="jsonObject"></div>
    二:定义JSON格式的字符串数组:
    <select id="jsonSel"></select>
    <ul id="jsonUl"></ul>
    三:定义JSON格式的user数组:
    <div id="jsonArr"></div>
</body>
</html>
5. Java 中 JSON 的使用

类库选择:

Java中并没有内置JSON的解析,因此使用JSON需要借助第三方类库。

下面是几个常用的 JSON 解析类库:

  • Gson: 谷歌开发的 JSON 库,功能十分全面。GitHub地址是:https://github.com/google/gson
  • FastJson: 阿里巴巴开发的 JSON 库,性能十分优秀。GitHub地址是:https://github.com/alibaba/fastjson
  • Jackson: 社区十分活跃且更新速度很快。GitHub地址是:https://github.com/FasterXML/jackson

本案例使用的是FastJson类库,并穿插讲解Jackson

1.2 使用@ResponseBody 实现数据输出

上一章中,我们完成了超市订单管理系统的新增用户功能,,但是在新增用户时,需要进行用户编码(userCode)的同名验证,以确保用户名的唯一性。该功能的实现需要运用ajax异步判断,由控制器的处理方法返回一个结果,告诉用户输入的用户编码是否存在。对于返回结果,我们一般使用JSON对象来表示。

那么在Spring MVC中如何处理JSON对象? 下面我们就结合该实例来深入学习一下,具体实现步骤如下:

1. DAO层 Service层

DAO层(UserDao.java , UserDaoImpl.java) 提供一个通过userCode 获取User对象的方法即可(素材提供,直接使用).Service层(UserService.java , UserServiceImpl.java) 同上

2. 改造Controller层

首先我们要使用JSON,所有需要先在项目中引入处理JSON数据的工具jar文件,本书使用的阿里巴巴的FastJson,pom.xml文件中加入如下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>

若使用的JSON解析类库是Jackson,则在pom.xml文件中加入以下依赖:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

修改UserController.java,增加通过用户名(userCode)进行同名验证的处理方法,关键示例代码如下:

/**
 * 验证用户编码是否存在
 * @param userCode
 * @return
 */
@RequestMapping("/ucexist.html")
@ResponseBody
public String userCodeIsExist(@RequestParam String userCode) {
	logger.debug("userCodeIsExist userCode:"+userCode);
	Map<String,String> map = new HashMap<>();
	if(StringUtils.isNullOrEmpty(userCode)) {
		map.put("userCode", "exist");
	}else {
		User user = userService.selectUserCodeExist(userCode);
		if(null != user) {
			map.put("userCode", "exist");
		}else {
			map.put("userCode", "noexist");
		}
	}
	return JSON.toJSONString(map);
}

@ResponseBody注解的作用是将标注该该注解处理方法的返回结果直接写入 HTTP Response Body(Response对象的body数据区)中.一般情况下,@ResponseBody都会在异步获取数据时使用,被其标注的处理方法返回的数据将输出到响应流中,客户端获取并显示数据.

3. 改造View层

修改useradd.js, 关键示例代码如下:

/*
 * 验证
 * 失焦\获焦
 * jquery的方法传递
 */
userCode.bind("blur",function(){
	//ajax后台验证--userCode是否已存在
	$.ajax({
		type:"GET",//请求类型
		url:path+"/user/ucexist.html",//请求的url
		data:{userCode:userCode.val()},//请求参数
		dataType:"json",//ajax接口(请求url)返回的数据类型
		success:function(data){//data:返回数据(json对象)
			if(data.userCode == "exist"){//账号已存在,错误提示
				validateTip(userCode.next(),{"color":"red"},imgNo+ " 该用户账号已存在",false);
			}else{//账号可用,正确提示
				validateTip(userCode.next(),{"color":"green"},imgYes+" 该账号可以使用",true);
			}
		},
		error:function(data){//当访问时候,404,500 等非200的错误状态码
			validateTip(userCode.next(),{"color":"red"},imgNo+" 您访问的页面不存在",false);
		}
	});
	
	
}).bind("focus",function(){
	//显示友情提示
	validateTip(userCode.next(),{"color":"#666666"},"* 用户编码是您登录系统的账号",false);
}).focus();

在上述代码中 ,当用户在新增用户页面完成用户编码(userCode)的输入之后,即用户编码框中鼠标失焦时,进行ajax异步验证,请求URL “/user/ucexist.html”,请求参数为用户输入的userCode的值.异步调用请求之后,返回的数据类型为JSON类型,若成功,则根据返回的JSON对象,对其进行相应的信息提示.

4. 部署运行

完成以上代码,部署并进行运行测试.登录系统后,先输入已经存在用户名(如:admin),运行结果如图:
在这里插入图片描述
当将鼠标移开时,也就是用户编码的输入失去焦点后,进行了异步验证,输入框显示提示信息:用户账号已存在.再输入一个不存在的用户编码(如:admintest),当鼠标移开后,输入框显示提示信息,该账号可以使用.如图:在这里插入图片描述

1. 3 JSON数据的传递处理

在上个小节中,我们掌握了Spring MVC 框架中,如何实现ajax的异步请求调用,并对于处理方法放回的结果,我们一般都会把它转换成JSON对象,并使用@ResponseBody注解实现了数据的输出.

但是在上述示例中,返回结果并非复杂的数据类型,只是将Map类型转换成JSON对象进行输出,若返回结果为Bean对象,该如何转换成JSON对象进行输出?若Bean对象中含有日期类型数据. Spring MVC 输出JSON数据时,日期又该如何处理?下面通过一个示例来演示

示例需求: 实现查看指定用户的明细信息功能. 具体实现要求:通过ajax异步调用来获取用户信息.在用户列表页,选中用户并单击 "查看" 按钮,页面下方展示出该用户的明细信息,最终实现效果如图:
在这里插入图片描述
实现步骤如下:

1. DAO层 Service层

DAO层(UserDao.java , UserDaoImpl.java) 提供一个通过user id 获取User对象的方法即可(素材提供的getUserById()方法,直接使用).Service层(UserService.java , UserServiceImpl.java) 同上

2. 改造Controller层
/**
 * 根据id 异步获取用户信息
 * @param id
 * @return
 */
@RequestMapping(value="/view.html")
@ResponseBody
public String view(@RequestParam String id) {
	logger.debug("view id=="+id);
	String cjson = "";
	if(null == id || "".equals(id)) {
		return "nodata";
	}else {
		try {
			User user = userService.getUserById(id);
			cjson = JSON.toJSONString(user);
			logger.debug("cjson="+cjson);
		}catch(Exception e) {
			e.printStackTrace();
			return "failed";
		}
		
	}
	return cjson;
}
3. 改造View层

userlist.jsp 增加一个div区域用于用户明细信息的展示,代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@include file="common/head.jsp"%>
        <div class="right">
            <div class="location">
                <strong>你现在所在的位置是:</strong>
                <span>用户管理页面</span>
            </div>
            <div class="search">
           		<form method="post" action="${pageContext.request.contextPath }/user/userlist.html">
			<!-- 省略中间代码 -->
		  	<c:import url="rollpage.jsp">
	          	<c:param name="totalCount" value="${totalCount}"/>
	          	<c:param name="currentPageNo" value="${currentPageNo}"/>
	          	<c:param name="totalPageCount" value="${totalPageCount}"/>
          	</c:import>
             
            <!-- 显示用户信息代码 start -->        
          	<div class="providerAdd">
          		<div>
          			<label>用户编码:</label>
          			<input id="v_userCode" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>用户名称:</label>
          			<input id="v_userName" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>用户性别:</label>
          			<input id="v_gender" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>出生日期:</label>
          			<input type="text" Class="Wdate" id="v_birthday" value=""
					readonly="readonly" οnclick="WdatePicker();">
          		</div>
          		<div>
          			<label>用户电话:</label>
          			<input id="v_phone" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>用户角色:</label>
          			<input id="v_userRoleName" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>用户地址:</label>
          			<input id="v_address" readonly="readonly"	type="text" value="">
          		</div>
          	</div>
            <!--显示用户信息end -->        
        </div>
   </section>
	
<!--省略后面的代码-->

userlist.js中根据id异步获取用户信息,为页面元素进行赋值操作:

/**
 * bind、live、delegate on
*/
$(".viewUser").on(
    "click",
    function() {
        // 将被绑定的元素(a)转换成jquery对象,可以使用jquery方法
        var obj = $(this);
        /*
				 * window.location.href = path + "/user/view/" +
				 * obj.attr("userid");
				 */
        $.getJSON(path + "/user/view.html", "id=" + obj.attr("userid"),function(data) {
            if (data == "failed") {
                alert("操作超时! ");
            } else if (data == "nodata") {
                alert("没有数据..");
            } else {
                $("#v_userCode").val(data.userCode);
                $("#v_userName").val(data.userName);
                $("#v_gender").val(data.gender == "1" ? "女" : "男");
                $("#v_birthday").val(data.birthday);
                $("#v_address").val(data.address);
                $("#v_phone").val(data.phone);
                $("#v_userRoleName").val(data.userRoleName);
            }
        });
    });

4. 部署运行

完成以上的代码后,部署并进行测试,登录系统后,进入用户列表界面,选择用户,单击"查看"按钮,界面效果如图所示:
在这里插入图片描述
从上述的运行界面中可以很明显的发现存在两个问题:中文乱码和出生日期格式显示不正确.下面我们就依次来解决这两个问题.

5. 解决JSON数据传递的中文乱码问题

首先解决中文乱码问题,在Spring MVC 中,控制器的处理方法使用@ResponseBody注解向前台页面以JSON格式进行数据传递的时候,若返回值是中文字符串,则会出现乱码.原因是消息转换器(org.springframework.http.converter.StringHttpMessageConverter)中的固定转换字符编码为 "ISO-8859-1",如图所示:
在这里插入图片描述
扩展:

HttpMessageConverter<T> 是Spring 的一个接口,主要负责将请求信息转换为一个对象(类型为T),通过对象(类型为T) 输出响应信息. 而StringHttpMessageConverter就是它的一个实现类,StringHttpMessageConverter的作用就是将请求信息转换为字符串,由于其默认字符集为ISO-8859-1,故在返回JSON字符串中有中文时则会出现乱码问题.

要解决这个问题,就必须更改字符串编码为"utf-8" . 解决方案有很多种,在此介绍两种方法:

(1) 在控制器处理方法上的@RequestMapping注解中配置produces

produces:指定返回的内容类型.produces={"application/json;charset=utf-8"}: 表示该处理方法将产生JSON格式的数据,此时会根据请求报文头的Accept 进行匹配,若请求报文头"Accept:application/json"时即可匹配,并且字符串的转换编码为"utf-8". 更改示例代码如下:

/**
 * 根据id 异步获取用户信息
 * 或者 produces=MediaType.APPLICATION_JSON_VALUE
 * @param id
 * @return
 */
@RequestMapping(value="/view.html",produces="application/json;charset=utf-8")
@ResponseBody
public String view(@RequestParam String id) {
	logger.debug("view id=="+id);
	String cjson = "";
	if(null == id || "".equals(id)) {
		return "nodata";
	}else {
		try {
			User user = userService.getUserById(id);
			cjson = JSON.toJSONString(user);
			logger.debug("cjson="+cjson);
		}catch(Exception e) {
			e.printStackTrace();
			return "failed";
		}
		
	}
	return cjson;
}

更改完成之后,部署运行测试,选择用户并单击"查看"按钮之后,界面未有任何反应,按F12打开开发者工具窗口,点击网络一栏,如图所示:
在这里插入图片描述

分析错误,发现服务器返回了一个406的状态码(表示客户端浏览器不接受所请求页面的MIME类型).而请求报文头的"Accept: application/json, text/javascript, */*; q=0.01" 与响应报文头"Content-Type: text/html;charset=utf-8"类型不一致,这才是会导致406错误的根源.
Spring MVC 之所以会以HTML的格式来显示响应信息,是因为我们在使用@RequestMapping注解时,手动指定了value属性的后缀为.html.要解决此问题,只需要去掉value中的.html后缀即可. 修改UserController.java 代码如下:

/**
 * 根据id 异步获取用户信息
 * @param id
 * @return
 */
@RequestMapping(value="/view",produces="application/json;charset=utf-8")
@ResponseBody
public String view(@RequestParam String id) {
	logger.debug("view id=="+id);
	String cjson = "";
	if(null == id || "".equals(id)) {
		return "nodata";
	}else {
		try {
			User user = userService.getUserById(id);
			cjson = JSON.toJSONString(user);
			logger.debug("cjson="+cjson);
		}catch(Exception e) {
			e.printStackTrace();
			return "failed";
		}
		
	}
	return cjson;
}

另外,还需要修改userlist.js中单击"查看" 按钮时的异步请求 URL,代码如下:

/**
	 * bind、live、delegate on
	 */
$(".viewUser").on(
	"click",
	function() {
	    // 将被绑定的元素(a)转换成jquery对象,可以使用jquery方法
		var obj = $(this);
		/*
		* window.location.href = path + "/user/view/" +
		* obj.attr("userid");
		*/
		$.getJSON(path + "/user/view", "id="+obj.attr("userid"),
		  function(data) {
            if (data == "failed") {
                alert("操作超时! ");
            } else if (data == "nodata") {
                alert("没有数据..");
            } else {
                $("#v_userCode").val(data.userCode);
                $("#v_userName").val(data.userName);
				$("#v_gender").val(data.gender== "1"?"女":"男");
                $("#v_birthday").val(data.birthday);
				$("#v_address").val(data.address);
                $("#v_phone").val(data.phone);
                $("#v_userRoleName").val(data.userRoleName);
			}
     });
});

完成上述修改之后,重启服务并运行测试,中文问题完美解决.

这种方法比较简单使用,并且可以做到灵活处理.当然,如果想达到一次配置,永久搞定,可以采用第二种解决方案

(2) 装配消息转换器StringHttpMessageConverter,设置字符编码为utf-8

修改配置文件springmvc-servlet.xml,关键配置代码如下:

<!-- 配置消息转换器 -->
<bean id="stringHttpMessageConverter"     class="org.springframework.http.converter.StringHttpMessageConverter">
    <!--统一配置字符编码-->
    <!--<constructor-arg value="utf-8"/>-->
	<property name="defaultCharset" value="utf-8"/>
</bean>
<mvc:annotation-driven>
	<!-- 配置消息转换器 -->
	<mvc:message-converters>
		<!-- 引用配置的消息转换器 -->
		<ref bean="stringHttpMessageConverter"/>
	</mvc:message-converters>
</mvc:annotation-driven>

在Spring MVC 中配置了消息转换器之后,就可以去掉@RequestMapping中配置的produces="application/json;charset=utf-8"了。

使用配置类代替上面的代码如下:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    //配置字符编码消息转换器 解决中文乱码问题
    StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
    stringHttpMessageConverter.setDefaultCharset(Charset.defaultCharset());
    converters.add(stringHttpMessageConverter);
}

修改UserController.java,关键代码如下:

/**
 * 根据id 异步获取用户信息
 * @param id
 * @return
 */
@RequestMapping(value="/view")
@ResponseBody
public String view(@RequestParam String id) {
	//省略其他代码
}

重启服务,并运行测试,中文乱码问题同样得到解决

补充:

若程序中使用Jackson解析JSON数据时 , 则中文不会出现乱码 .具体步骤如下:

1. 修改pom.xml文件

加入Jackson的坐标 , 代码如下:

<!-- jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>
2. 修改UserController.java文件

代码如下:

/**
 * 使用jackson 不会出现中文乱码
 * @param id
 * @return
 */
@RequestMapping(value = "/userView")
@ResponseBody
public User userView(@RequestParam String id){
    User user = userService.getUserById(id);
    return user;
}
6. 解决JSON数据传递的日期格式问题

解决了中文乱码问题之后,接下来解决日期格式问题.

在Spring MVC中使用@ResponseBody返回JSON数据时,日期格式默认显示为时间戳.而在这个示例中,我们需要将它转换为具有可读性的"yyyy-MM-dd" 的日期格式.具体解决方案有多种:

最简单、最直接的方式就是修改userlist.js中关于日期显示的方法,可以调用Date的方法进行日期格式的转换,代码如下:

/**
 * bind、live、delegate on
*/
$(".viewUser").on(
	"click",
	function() {
	    // 将被绑定的元素(a)转换成jquery对象,可以使用jquery方法
		var obj = $(this);
		/*
		* window.location.href = path + "/user/view/" +
		* obj.attr("userid");
		*/
		$.getJSON(path + "/user/view", "id="+obj.attr("userid"),
		  function(data) {
            if (data == "failed") {
                alert("操作超时! ");
            } else if (data == "nodata") {
                alert("没有数据..");
            } else {
                $("#v_userCode").val(data.userCode);
                $("#v_userName").val(data.userName);
				$("#v_gender").val(data.gender == "1"?"女":"男");
                var date = new Date(data.birthday);
                $("#v_birthday").val(date.toLocaleString());
				$("#v_address").val(data.address);
                $("#v_phone").val(data.phone);
                $("#v_userRoleName").val(data.userRoleName);
			}
     });
});

上面那种方式修改比较简单,但是弊端在于每个浏览器显示时间的格式不统一,所以可根据实际项目的需求做调整 .

或者可以用JSON提供的方法来统一指定日期的格式 , 例如修改Controller中的方法 , 代码如下:

@RequestMapping(value = "/view")
@ResponseBody
public String view(@RequestParam String id){
    //System.out.println("id=="+id);
    String cjson = "nodata";
    if (id != null && !id.equals("")){
        User user = userService.getUserById(id);
        cjson = JSON.toJSONStringWithDateFormat(user,"yyyy-MM-dd");
    }
    return cjson;
}

并将之前的JS代码修改回来,同样能达到指定日期格式的问题 . 但是 这种方式最大的弊端在于只能修改当前的方法返回JSON字符串日期的格式

1) 注解方式 : @JSONField(format=“yyyy-MM-dd”)

FastJson对于Date的处理是通过注解来解决,即:在user对象的日期属性(例如birthday)上加上@JSONField(format="yyyy-MM-dd")来进行日期格式化处理.实例代码如下:

public class User {
	private Integer id; //id 
	@JSONField(format="yyyy-MM-dd")
	private Date birthday;  //出生日期
	//省略其他属性以及getter 和 setter
}	

修改完成之后,直接部署运行,选中用户,查看明细信息,出生日期字段的日期格式显示正确.

这种方式比较简单直接,但是它存在一定的缺点:代码具有入侵性,紧耦合,并且修改麻烦,所以在实际开发中,我们不建议采用这种硬编码的方式来处理.一般会采用下面这种方式解决.

2) 配置FastJson的消息转换器: FastJsonHttpMessageConverter

首先修改springmvc-servlet.xml,配置FastJsonHttpMessageConverter消息转换器,关键示例代码如下:

<!--配置StringHttpMessageConverter-->
<bean id="stringHttpMessageConverter"
        class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="defaultCharset" value="utf-8"/>
</bean>
<!--启用mvc的注解-->
<mvc:annotation-driven>
    <mvc:message-converters>
        <ref bean="stringHttpMessageConverter"/>
        <ref bean="fastJsonHttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>

<!--配置fastjson消息转换器-->
<bean id="fastJsonHttpMessageConverter"
        class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
    <!--配置此属性的目的是:解决IE浏览器,执行Ajax返回JSON数据时,
出现的下载文件的问题  -->
    <property name="supportedMediaTypes">
        <list>
            <value>text/html;charset=utf-8</value>
            <value>application/json</value>
            <value>application/xml;charset=utf-8</value>
        </list>
    </property>
    <!--注意:配置完此属性之后要想使得日期格式有效,
    必须使用对象作为返回值,不能再调用其json的方法-->
    <property name="features">
        <array>
            <!--输出Date的日期转换器  -->
            <value>WriteDateUseDateFormat</value>
        </array>
    </property>
</bean>

在上述配置中,通过设置FastJsonHttpMessageConverter中的features属性指定输出时的日期转换器WriteDataUseDataFormat ,就可以按照FastJson默认的日期格式进行转换输出;

> 说明:

FastJson是一个JSON处理工具包,包含序列化和反序列化两部分.它提供了强大的日期处理和识别能力,在序列化时指定格式,支持多种方式实现;在反序列化时也可识别多种格式的日期.关于FastJsonHttpMessageConverter,JSON,SerializerFeature等,此处不做深入讲解,可自行查看源码深入研究.

> 注意:

使用配置文件的方式时,需将Controller中方法的返回值修改为对应的对象类型

修改UserController.java的view()方法.在该方法内,无须再将user对象转换成JSON字符串,直接把获取的user对象返回即可.关键实例代码如下:

@RequestMapping("/view")
@ResponseBody
public User view(@RequestParam String id) {
    logger.debug("view id==" + id);
    User user = null;
    try {
        user = userService.getUserById(id);
        logger.debug("user=" + user);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return user;
}

其次还需要注释掉User.java中的@JSONField注解.修改User.java代码如下:

public class User {
	private Integer id; //id 
	//省略中间代码
	private Date birthday;  //出生日期
	//省略中间代码
	private Date creationDate; //创建时间
	//省略其他代码以及getter setter
}	

再次修改userlist.jsp,为了更好的演示本例中日期类型字段的显示,页面上除了显示出生日期(yyyy-MM-dd)外,还需要把创建时间(yyyy-MM-dd HH:mm:ss)进行输出.关键示例代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@include file="common/head.jsp"%>
        <div class="right">
            <div class="location">
                <strong>你现在所在的位置是:</strong>
                <span>用户管理页面</span>
            </div>
            <div class="search">
           		<form method="post" action="${pageContext.request.contextPath }/user/userlist.html">
			<!-- 省略中间代码 -->
		  	<c:import url="rollpage.jsp">
	          	<c:param name="totalCount" value="${totalCount}"/>
	          	<c:param name="currentPageNo" value="${currentPageNo}"/>
	          	<c:param name="totalPageCount" value="${totalPageCount}"/>
          	</c:import>
          	<div class="providerAdd">
          		<div>
          			<label>用户编码:</label>
          			<input id="v_userCode" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>用户名称:</label>
          			<input id="v_userName" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>用户性别:</label>
          			<input id="v_gender" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>出生日期:</label>
          			<input type="text" Class="Wdate" id="v_birthday" value=""
					readonly="readonly" οnclick="WdatePicker();">
          		</div>
          		<div>
          			<label>用户电话:</label>
          			<input id="v_phone" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>用户角色:</label>
          			<input id="v_userRoleName" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>用户地址:</label>
          			<input id="v_address" readonly="readonly"	type="text" value="">
          		</div>
          		<div>
          			<label>创建日期:</label>
          			<input type="text" Class="Wdate" id="v_creationDate" value=""
					readonly="readonly" οnclick="WdatePicker();">
          		</div>
          	</div>
        </div>
   </section>
	
<!--省略后面的代码-->

最后修改userlist.js,对创建日期进行赋值,代码如下:

/**
* bind、live、delegate on
*/
$(".viewUser").on(
    "click", function() {
        // 将被绑定的元素(a)转换成jquery对象,可以使用jquery方法
        var obj = $(this);
        /*
        * window.location.href = path + "/user/view/" +
        * obj.attr("userid");
        */
        $.getJSON(path + "/user/view", "id=" + obj.attr("userid"),
        function(data) {
        $("#v_userCode").val(data.userCode);
        $("#v_userName").val(data.userName);
        $("#v_gender").val(data.gender == "1" ? "女" : "男");
        $("#v_birthday").val(data.birthday);
        $("#v_address").val(data.address);
        $("#v_phone").val(data.phone);
        $("#v_userRoleName").val(data.userRoleName);
        $("#v_creationDate").val(data.creationDate);
    });
});

部署运行后,选中用户信息,查看其明细信息,界面效果如下:
在这里插入图片描述
从运行结果看,出生日期和创建日期都是按照yyyy-MM-dd HH:mm:ss 的格式进行了转换输出,但是由于出生日期不需要精确到时分秒,只需输出年月日即可.因此对于这种特殊字段的需求,可以通过@JSONField来实现,只需在User.java的birthday属性上增加@JSONField(format="yyyy-MM-dd").修改User.java之后,重启,运行效果如图:
在这里插入图片描述
目测用户界面已经达到了需求.

> 但是:

当我们在配置FastJsonHttpMessageConverter 配置中 features 的属性时,发现此属性已经过期了,这是因为 fastjson 版本升级到 1.2之后,对于此属性已经过时了,虽然不影响使用,但是不建议使用,因为并不知道什么时候就会不能用了,所以还是要修改的.

重新修改springmvc-servlet.xml文件如下:

<!-- 配置消息转换器 -->
<bean id="stringHttpMessageConverter"     class="org.springframework.http.converter.StringHttpMessageConverter">
    <!--统一配置字符编码-->
    <!--<constructor-arg value="utf-8"/>-->
	<property name="defaultCharset" value="utf-8"/>
</bean>
<mvc:annotation-driven>
	<!-- 配置消息转换器 -->
	<mvc:message-converters>
		<!-- 引用配置的消息转换器 -->
		<ref bean="stringHttpMessageConverter"/>
		<!--引用fastjson的消息转换器-->
        <ref bean="fastJsonHttpMessageConverter"/>
	</mvc:message-converters>
</mvc:annotation-driven>

<!--配置fastjson的消息转换器 start-->
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
    <property name="fastJsonConfig" ref="fastJsonConfig"/>
    <property name="supportedMediaTypes">
        <list>
            <value>text/html</value>
            <value>application/json</value>
            <value>application/xml</value>
        </list>
    </property>
</bean>
<!--定义fastJsonConfig-->
<bean id="fastJsonConfig"
class="com.alibaba.fastjson.support.config.FastJsonConfig">
    <!--配置加注解的方式 -->
    <property name="serializerFeatures">
        <array>
        	<value>WriteDateUseDateFormat</value>
        </array>
    </property>
</bean>
<!--配置fastjson的消息转换器 end-->
<!-- 省略其他代码 -->

重新部署,运行测试,效果如上图所示.

扩展:

**上述方法虽然通过配置,解决了日期格式的问题,代码侵入性有所降低.但是在实现项目中,有时对于日期的输出的格式还是以年月日(yyyy-MM-dd) 居多,但是FastJson中默认的日期转换格式为yyyy-MM-dd HH:mm:ss,除了通过通过@JSONField注解去解决,还可以通过在配置的方式解决. **

修改配置文件springmvc-servlet.xml,关键代码如下:

<mvc:annotation-driven>
   <mvc:message-converters>
	  <!--引用Springmvc消息转换器-->	
      <ref bean="stringHttpMessageConverter"/>
       <!--引用fastjson消息转换器-->
      <ref bean="fastJsonHttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
<!--配置springmvc字符编码转换器-->
<bean id="stringHttpMessageConverter"
        class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="defaultCharset" value="utf-8"/>
</bean>

<!--配置fastjson日期转换器开始-->
<bean id="fastJsonHttpMessageConverter"
		class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
	<property name="fastJsonConfig" ref="fastJsonConfig"/>
	<property name="supportedMediaTypes">
		<list>
			<value>text/html</value>
			<value>application/json</value>
			<value>application/xml</value>
		</list>
	</property>
</bean>
<!--定义fastJsonConfig-->
<bean id="fastJsonConfig"
	  class="com.alibaba.fastjson.support.config.FastJsonConfig">
	<!--第一种全局配置日期的方式-->
	<property name="dateFormat" value="yyyy-MM-dd"/>
	<!--第二种是配置加注解的方式 -->
	<!--<property name="serializerFeatures">
		<array>
			<value>WriteDateUseDateFormat</value>
		</array>
	</property>-->
</bean>
<!--配置fastjson日期转换器结束-->

<!-- 省略其他代码 -->

使用配置类代替fastjson日期转换器,代码如下:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    //配置字符编码消息转换器 解决中文乱码问题
    StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
    stringHttpMessageConverter.setDefaultCharset(Charset.defaultCharset());
    FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    //第一种全局配置日期的方式
    fastJsonConfig.setDateFormat("yyyy-MM-dd");
    //第二种是配置加注解的方式 
    //使用的格式是:yyyy-MM-dd HH:mm:dd
    //fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteDateUseDateFormat);
    fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
    fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML,MediaType.APPLICATION_JSON,
                                                                      MediaType.APPLICATION_XML));
    converters.add(stringHttpMessageConverter);
    converters.add(fastJsonHttpMessageConverter);

}

部署运行测试,发现界面的日期格式都转换成了通过的yyyy-MM-dd的格式,而之前使用的@JSONField的注解将不再起任何作用.

当然,关于也可以通过自定义转换器的方式来实现FastJson的消息转换器功能,有兴趣的同学可以参照官方手册以及源码.

> 小结

对于Spring MVC中,使用FastJson 来进行JSON数据的传递处理,简单总结以下几点

若没有配置消息转换器中的<value>WriteDateUseDateFormat</value>,并且也没有加入属性注解@JSONField(yyyy-MM-dd),则在浏览器上输出时间戳.当然浏览器也可以针对时间戳,在javascript中调用其关于日期的函数,来对日期进行格式的转换;

若配置<value>WriteDateUseDateFormat</value>,则会转换输出 yyyy-MM-dd HH:mm:ss格式的日期(注意:FastJson l.2版本之后关于全局日期的配置有所变化,可参考上述配置代码);

若配置了<value>WriteDateUseDateFormat</value>,也增加了属性注解@JSONFiled(yyyy-MM-dd),则会转换输出为属性注解格式,注解优先;

若配置了<property name="dateFormat" value="yyyy-MM-dd"/>,那么关于全局的JSON数据输出的日期格式都会用此属性配置的格式,属性注解也将不再起作用

7. 扩展:

解决使用Jackson出现日期格式问题:

项目中导入Jackson的依赖,代码如下:

<!-- jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

springmvc-servlet.xml配置文件如下:

<mvc:annotation-driven>
    <mvc:message-converters>
        <ref bean="stringHttpMessageConverter"/>
        <!--配置fastjson消息转换器-->
        <!--<ref bean="fastJsonHttpMessageConverter"/>-->
        <!--配置Jackson日期转换器-->
        <ref bean="jackson2HttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
<!--配置springmvc字符编码转换器-->
<bean id="stringHttpMessageConverter"
   class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="defaultCharset" value="utf-8"/>
    <!--<property name="supportedMediaTypes">
        <list>
            <value>application/json;charset=UTF-8</value>
        </list>
    </property>-->
</bean>

<!--配置Jackson 日期转换器开始-->
<bean id="jackson2HttpMessageConverter"
        class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    <property name="objectMapper" ref="objectMapper"/>
	<property name="supportedMediaTypes">
        <list>
            <value>text/html;charset=utf-8</value>
            <value>application/json;charset=utf-8</value>
            <value>application/xml;charset=utf-8</value>
        </list>
     </property>
</bean>
<bean id="objectMapper"
      class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
	<!--配置序列化json字符串的输出缩进-->
	<property name="indentOutput" value="true"/>
    <property name="simpleDateFormat" value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<!--配置Jackson 日期转换器结束-->

可参照官网
在这里插入图片描述
基于配置类实现如下:


    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //配置字符编码消息转换器 解决中文乱码问题
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setDefaultCharset(Charset.defaultCharset());
        //配置Jackson
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
         //       .modulesToInstall(new ParameterNamesModule());//可用于多个模块
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
                new MappingJackson2HttpMessageConverter(builder.build());
        jackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML,MediaType.APPLICATION_JSON,
                MediaType.APPLICATION_XML));
        converters.add(stringHttpMessageConverter);
//        converters.add(fastJsonHttpMessageConverter);
        converters.add(jackson2HttpMessageConverter);

    }

备注:关于Jackson使用的日期转换格式的注解是:@JsonFormat(pattern = "yyyy-MM-dd")

1.4 配置多视图解析器:ContentNegotiatingViewResolver

在查看用户明细功能中,控制器的处理方法view()返回的其实是一个JSON数据内容,并且我们之前示例中已经对标注@ResponseBody的处理方法进行响应信息的转换,由于Spring MVC 可以根据请求报文头的Accept属性值,将处理方法的返回值以xml,json,html等不同形式输出响应,即可以通过设置请求报文头Accept的值来控制服务器返回的数据格式.

而对于该示例功能的实现,其实我们只是希望资源能以JSON纯数据的格式输出,那就可以通过一个强大的多视图解析器ContentNegotiatingViewResolver来进行灵活处理.

> 知识扩展:

ContentNegotiatingViewResolver可以根据请求所要求的MIME类型决定由哪个视图解析器负责处理,即它允许以同样的内容数据来呈现不同的View(HTML,JSON,XML,XLS等).比如我们希望使用以下URL以不同的MIME格式获取相同资源(用户 id 为 12的用户明细信息).

  • /user/view.json?id=12(返回JSON格式的用户明细信息)
  • /user/view.html?id=12(返回一个HTML格式的用户明细信息)
  • /user/view.xml?id=12(返回XML格式的用户明细信息)

通过ContentNegotiatingViewResolver,其实就达到了统一资源(用户明细信息)根据相同的URL访问,并通过设置MIME格式控制服务器返回数据的格式,从而获取不同形式的返回内容.其实这也恰恰是REST的编程风格

在之前的示例代码中,我们采用的视图解析器为InternalResourceViewResolver,它主要用来处理JSP模板类型的视图映射.现在修改springmvc-servlet.xml中有关视图解析器的配置,把之前的InternalResourceViewResolver配置替换为如下代码:

<!-- 配置多视图解析器 -->
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<!-- localhost:8080/user/view.xml -->
	<!--favorParameter:设置为true(默认为false) 则表示支持参数匹配,
		可以根据请求参数的值确定MIME类型,默认的参数为format -->
		<property name="favorParameter" value="true" />
		<!--指定默认解析的视图类型为text/html  -->
		<property name="defaultContentType" value="text/html" />
		<!-- mediaTypes:根据请求参数值和MIME类型的映射列表,
		即contentType以何种格式展示,若请求url后缀为".json",
		则会以application/json的格式进行数据展示 -->
		<property name="mediaTypes">
			<map>
				<entry key="html" value="text/html;charset=utf-8" />
				<entry key="json" value="application/json;charset=utf-8" />
				<entry key="xml" value="application/xml;charset=utf-8" />
			</map>
		</property>
		<!--viewResolvers:表示网页视图解析器  -->
		<property name="viewResolvers">
			<list>
				<!--配置视图解析器 -->
				<bean
					class="org.springframework.web.servlet.view.InternalResourceViewResolver">
					<!--通过配置prefix和suffix,将视图逻辑门解析为:/WEB-INF/jsp/<viewName>.jsp -->
					<property name="prefix" value="/WEB-INF/jsp/" />
					<property name="suffix" value=".jsp" />
				</bean>
			</list>
		</property>
	</bean>

解释 MIME

MIME, 全称为“Multipurpose Internet Mail Extensions”, 比较确切的中文名称为“多用途互联网邮件扩展”。它是当前广泛应用的一种电子邮件技术规范,基本内容定义于RFC 2045-2049

什么是MIME类型?-在把输出结果传送到浏览器上的时候,浏览器必须启动适当的应用程序来处理这个输出文档。这可以通过多种类型MIME(多功能网际邮件扩充协议)来完成。在HTTP中,MIME类型被定义在Content-Type header中。

例 如,假设你要传送一个Microsoft Excel文件到客户端。那么这时的MIME类型就是“application/vnd.ms-excel”。 在大多数实际情况中,这个文件然后将传送给 Execl来处理(假设我们设定Execl为处理特殊MIME类型的应用程序)。

多媒体文件格式MIME

最早的HTTP协议中,并没有附加的数据类型信息,所有传送的数据都被客户程序解释为超文本标记语言HTML 文档,而为了支持多媒体数据类型,HTTP协议中就使用了附加在文档之前的MIME数据类型信息来标识数据类型。

MIME意为多目Internet邮件扩展,它设计的最初目的是为了在发送电子邮件时附加多媒体数据,让邮件客户程序能根据其类型进行处理。然而当它被HTTP协议支持之后,它的意义就更为显著了。它使得HTTP传输的不仅是普通的文本,而变得丰富多彩。

每个MIME类型由两部分组成,前面是数据的大类别,例如声音audio、图象image等,后面定义具体的种类。

常见的MIME类型

超文本标记语言文本 .html,html: text/html

普通文本 .txt : text/plain

RTF文本 .rtf : application/rtf

GIF图形 .gif : image/gif

JPEG图形 .ipeg,.jpg : image/jpeg

au声音文件 .au : audio/basic

MIDI音乐文件 mid,.midi : audio/midi,audio/x-midi

RealAudio音乐文件 .ra, .ram :audio/x-pn-realaudio

MPEG文件 .mpg,.mpeg : video/mpeg

AVI文件 .avi : video/x-msvideo

GZIP文件 .gz : application/x-gzip

TAR文件 .tar : application/x-tar

注意:

由于在实际开发中,JSON数据格式较为常用,XML使用较少,因此我们不再演示XML数据格式的输出.

对于HTML格式的数据输出,由于在本示例中,控制器处理方法view()返回的是一个user对象,并非一个逻辑视图名,因此也无法演示.

控制器中查看用户明细的处理方法时,返回从后台获取的user对象,Spring MVC会根据用户的请求,进行不同形式的展示,因此无需修改控制器的代码.

最后修改userlist.js中ajax异步请求用户明细的URL,关键代码如下:

/**
* bind、live、delegate on
*/
$(".viewUser").on(
    "click", function() {
        // 将被绑定的元素(a)转换成jquery对象,可以使用jquery方法
        var obj = $(this);
        /*
        * window.location.href = path + "/user/view/" +
        * obj.attr("userid");
        */
        $.getJSON(path + "/user/view.json", "id=" + obj.attr("userid"),
        function(data) {
        $("#v_userCode").val(data.userCode);
        $("#v_userName").val(data.userName);
        $("#v_gender").val(data.gender == "1" ? "女" : "男");
        $("#v_birthday").val(data.birthday);
        $("#v_address").val(data.address);
        $("#v_phone").val(data.phone);
        $("#v_userRoleName").val(data.userRoleName);
        $("#v_creationDate").val(data.creationDate);
    });
});

由于我们在配置ContentNegotiatingViewResolverfavorParameter属性为true,因此上述的代码,也可以这样写:

/**
* bind、live、delegate on
*/
$(".viewUser").on(
    "click", function() {
        // 将被绑定的元素(a)转换成jquery对象,可以使用jquery方法
        var obj = $(this);
        /*
        * window.location.href = path + "/user/view/" +
        * obj.attr("userid");
        */
        $.getJSON(path + "/user/view", "id=" + obj.attr("userid")+"&format=json",
        function(data) {
        $("#v_userCode").val(data.userCode);
        $("#v_userName").val(data.userName);
        $("#v_gender").val(data.gender == "1" ? "女" : "男");
        $("#v_birthday").val(data.birthday);
        $("#v_address").val(data.address);
        $("#v_phone").val(data.phone);
        $("#v_userRoleName").val(data.userRoleName);
        $("#v_creationDate").val(data.creationDate);
    });
});

部署运行,发现项目报错,报错信息是:

Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'favorParameter' of bean class [org.springframework.web.servlet.view.ContentNegotiatingViewResolver]: Bean property 'favorParameter' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?

分析原因如下:

ContentNegotiatingViewResolver 中的 favorParameterignoreAcceptHeadermediaTypes等在4.1版时已经删除;在5.1版本中已无这些属性;这些属性已移至 ContentNegotiationManagerFactoryBean 这个类中;故而修改配置文件如下:

<!--配置视图解析器-->
<mvc:view-resolvers>
    <mvc:jsp prefix="/WEB-INF/jsp/" suffix=".jsp"/>
</mvc:view-resolvers>

<!--配置多视图解析器-->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <property name="contentNegotiationManager"
              ref="contentNegotiationManager"/>
    <property name="viewResolvers">
        <list>
			<!--引用视图解析器-->
            <ref bean="mvcViewResolver"/>
        </list>
    </property>
</bean>

<!--配置MIME类型视图解析器-->
<bean id="contentNegotiationManager"
        class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <!--favorParameter:设置为true(默认为false) 则表示支持参数匹配,
    可以根据请求参数的值确定MIME类型,默认的参数为format -->
    <property name="favorParameter" value="true"/>
    <!-- mediaTypes:根据请求参数值和MIME类型的映射列表,
    即contentType以何种格式展示,若请求url后缀为".json",
    则会以application/json的格式进行数据展示 -->
    <property name="mediaTypes">
        <value>
            html=text/html
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

完成以上修改之后,再次部署运行.登录系统成功后,进入用户列表页,选择用户,单击"查看"按钮,界面数据正确显示.

直接在浏览器地址栏输入URL: http://localhost:8080/ssm_demo/user/view.json?id=12 ,界面效果如图:
在这里插入图片描述
在实际项目中,我们会经常使用ContentNegotiatingViewResolver这种多视图解析的方式,它最大的作用就是增加了对MediaType(也称为Content-Type)和后缀的支持.它对于具体的网页视图解析则是使用viewResolvers属性中的ViewResolver来解析.这样可以灵活配置多种视图解析器来分别对应解析JSP,Freemarker等.

但是输入http://localhost:8080/ssm_demo/user/view?id=12&format=json,显示的并不是json数据,这是因为在SpringMVC中如果需要使用“format”作为后缀匹配需要使用<mvc:annotation-driven/>标签中的content-negotiation-manager属性,修改上述代码如下:

<!--开启对spring mvc注解的支持-->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
    <mvc:message-converters>
        <ref bean="stringHttpMessageConverter"/>
        <!--配置fastjson消息转换器-->
        <ref bean="fastJsonHttpMessageConverter"/>
        <!--配置Jackson日期转换器-->
        <!--<ref bean="jackson2HttpMessageConverter"/>-->
    </mvc:message-converters>
</mvc:annotation-driven>
<!--配置springmvc字符编码转换器-->
<bean id="stringHttpMessageConverter"
        class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="defaultCharset" value="utf-8"/>
    <!--<property name="supportedMediaTypes">
        <list>
            <value>text/html;charset=utf-8</value>
            <value>application/json;charset=utf-8</value>
            <value>application/xml;charset=utf-8</value>
        </list>
	</property>-->
</bean>

<!--配置fastjson消息转换器 start-->
<bean id="fastJsonHttpMessageConverter"
        class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
    <property name="supportedMediaTypes">
        <list>
            <value>text/html;charset=utf-8</value>
            <value>application/json;charset=utf-8</value>
            <value>application/xml;charset=utf-8</value>
        </list>
    </property>
    <property name="fastJsonConfig" ref="fastJsonConfig"/>
</bean>
<bean id="fastJsonConfig"
      class="com.alibaba.fastjson.support.config.FastJsonConfig">
    <!--<property name="serializerFeatures">
        <array>
            <value>WriteDateUseDateFormat</value>
        </array>
    </property>-->
    <property name="dateFormat" value="yyyy-MM-dd"/>
</bean>
<!--过滤静态资源-->
<mvc:default-servlet-handler/>


<!--配置Jackson 日期转换器start-->
<bean id="jackson2HttpMessageConverter"
        class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    <property name="objectMapper" ref="objectMapper"/>
    <property name="supportedMediaTypes">
        <list>
            <value>text/html;charset=utf-8</value>
            <value>application/json;charset=utf-8</value>
            <value>application/xml;charset=utf-8</value>
        </list>
    </property>
</bean>
<bean id="objectMapper"
      class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
    <property name="simpleDateFormat" value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<!--配置Jackson 日期转换器end-->


<!--配置视图解析器-->
<mvc:view-resolvers>
    <mvc:jsp prefix="/WEB-INF/jsp/" suffix=".jsp"/>
</mvc:view-resolvers>



<!--配置MIME类型视图解析器-->
<bean id="contentNegotiationManager"
        class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <!--favorParameter:设置为true(默认为false) 则表示支持参数匹配,
    可以根据请求参数的值确定MIME类型,默认的参数为format -->
    <property name="favorParameter" value="true"/>
    <!--<property name="parameterName" value="f"/>-->
    <!-- mediaTypes:根据请求参数值和MIME类型的映射列表,
    即contentType以何种格式展示,若请求url后缀为".json",
    则会以application/json的格式进行数据展示 -->
    <property name="mediaTypes">
        <value>
            html=text/html
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

关于配置MIME类型视图解析器参照官网如下:
在这里插入图片描述

1.5 使用Java类代替配置文件如下

编写AppConfig.java,用来代替applicationContext.xml,代码如下:

@Configuration
@ComponentScan(basePackages = {"cn.smbms.dao","cn.smbms.service"},
        excludeFilters = {@ComponentScan.Filter(classes = Controller.class)})
@PropertySource(value = "classpath:jdbc.properties")
public class AppConfig {
    @Value("${jdbc.driverClassName}")
    private String dirverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(dirverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
    @Bean
    public JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(dataSource());
    }
}

编写WebConfig.java,用来代替springmvc-servlet.xml,代码如下:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "cn.smbms.controller",
        includeFilters = {@ComponentScan.Filter(classes = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public ViewResolver viewResolver(){
        return new InternalResourceViewResolver("/WEB-INF/jsp/",".jsp");
    }

    @Bean(name = "multipartResolver")
    public CommonsMultipartResolver multipartResolver(){
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setDefaultEncoding("utf-8");
        multipartResolver.setMaxUploadSize(5000000);
        return multipartResolver;
    }
    //静态资源目录过滤
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //配置多视图解析器
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorParameter(true);
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("html",MediaType.TEXT_HTML);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
    }

    //配置Jackson消息转换器
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
                new MappingJackson2HttpMessageConverter(builder.build());
        jackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
                MediaType.APPLICATION_JSON,MediaType.TEXT_HTML,MediaType.APPLICATION_XML));
        converters.add(jackson2HttpMessageConverter);
    }
}

编写MyWebAppInitializer.java,用来代替web.xml,代码如下:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter("utf-8",true);
        return new Filter[]{encodingFilter};
    }
}

2 . 数据转换与格式化

在SpringMVC中,在对bean的属性进行数据绑定时页面报400错误,出现BindException,当时的解决方案是在User的日期属性(birthday)上标注了格式化注解@DateTimeFormat(pattern="yyyy-MM-dd").为何会存在这样的问题?要如何解决该类问题?简单分析如下:

在实际操作中,经常会遇到表单中的日期字符串与JavaBean中的日期类型的属性需要自动转换的情况,而Spring MVC框架默认不支持这个格式转换,即在Spring MVC中的时间数据无法实现自动绑定,必须手动配置自定义数据类型的绑定才能实现该功能,这是Spring MVC框架本身的问题. 下面介绍一下 Spring MVC的数据转换和格式化.

通过之前的学习,知道Spring MVC会根据请求方法签名的不同,将请求信息以一定的方式转换并绑定到请求方法的入参中.其实在请求信息真正到达处理方法之前,Spring MVC还完成了许多工作,包括数据转换,数据格式化,以及数据校验等.简单了解一下数据绑定流程,如图:
在这里插入图片描述
Spring MVC将ServletRequest对象以及处理方法的入参对象实例传递给 DataBinder.

DataBinder调用ConversionService组件进行数据的转换,格式化工作,并将ServletRequest中请求消息填充到入参对象中.

然后调用Validator组件对已经绑定了请求数据的入参对象进行数据合法性验证,并最终生成数据绑定结果BindingResult对象.

    1. DataBinder

数据绑定的核心部件.它在整个流程中起到核心调度的作用.

    1. ConversionService

Spring 类型转换体系的核心接口,可以利用org.springframework.context.support.ConversionServiceFactoryBean在Spring的上下文中定义一个ConversionService.Spring会自动识别上下文中的ConversionService,在Bean属性配置和处理方法参数入参绑定时,使用它进行数据的转换.

对于Spring MVC中的前台form表单中时间字符串到后台Date数据类型的转换问题,我们就可以通过ConversionService来解决.具体配置如下:

<bean id="conversionService" 
		class="org.springframework.context.support.ConversionServiceFactoryBean">

注意:

我们在之前的配置中使用的是<mvc:annotation-driven />标签,并没有配置 ConversionService,但是却能通过格式化注解来解决日期的转换问题.这是因为<mvc:annotation-driven />标签是Spring MVC的简化配置.默认情况下,它会创建并注册一个默认的DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例.(此处需注意在Spring MVC 3.1之后,这两个类分别变更为:RequestMappingHandlerMappingRequestMappingHandlerAdapter) 并且会注册一个默认的ConversionService示例:FormattingConversionServiceFactoryBean,那么Spring MVC对处理方法的入参绑定就可以支持注解驱动功能,以满足大部分类型的转换需求.

通过<mvc:annotation-driven/>标签只需在POJO相应的属性上进行格式化注解的标注即可.

    1. BindingResult

BindingResult包含了已完成了数据绑定的入参对象和相应的校验错误对象. Spring MVC会抽取BindingResult中的入参对象及校验错误对象,将它们赋给处理方法的相应入参,这个对象之前已经学过.

2.1 编写自定义转换器

在前面的使用中,我们直接通过<mvc:annotation-driven/>标签来支持注解驱动的功能(@DataTimeFormat(pattern="yyyy-MM-dd")),满足了日期类型的转换需求.现在也可以通过自定义转换器,来规定转换的规则.

Spring 在 org.springframework.core.convert.converter包中定义了最简单的Converter<S, T>转换接口,它仅包括一个接口方法,如图:
在这里插入图片描述
Converter的作用就是将一种类型转换成另一种类型的对象.例如:用户输入的日期可能有多种形式,如"2016-07-08","08/07/2016"等,这些都表示同一个日期.若希望Spring在将用户输入的日期字符串绑定到Date时,使用不同的日期格式,则需要编写一个Converter,才能将字符串转换成日期.具体实现也很简单:首先需要创建一个实现org.springframework.core.convert.converter.Converter<S, T>接口的Java类,然后将实现了Converter接口的自定义转换器注册到ConversionServiceFactoryBean中即可,实现步骤如下:

1) 创建StringToDateConverter.java

定义一个负责将字符串转换成指定格式时间对象Date的自定义转换器,示例代码如下:

/**
 * 自定义转换器 将字符串转换成指定格式的日期对象
 * @author Administrator
 *
 */
public class StringToDateConverter implements Converter<String, Date> {
	//定义默认转换格式的字符串为yyyy-MM-dd
	private String datePattern="yyyy-MM-dd";
    //定义setDatePattern方法,可以通过配置的方式修改此属性的值
	public void setDatePattern(String datePattern) {
		System.out.println("StringToDateConverter datePattern:"+datePattern);
		this.datePattern = datePattern;
	}

	@Override
	public Date convert(String source) {
		Date date = null;
		try {
			date = new SimpleDateFormat(datePattern).parse(source);
			System.out.println("StringToDateConverter date:"+date);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return date;
	}

}

在上述代码中,StringToDateConverter 需要实现Converter<String, Date>接口中的convert()方法,在方法体内完成字符串到java.util.Date类型指定格式的转换.

2) 装配自定义的ConversionService

装配自定义的ConversionService的两种方式:

> 第一种 : 配置ConversionServiceFactoryBean

修改springmvc-servlet.xml,关键示例代码如下:

<bean id="myConversionService" 
		class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="cn.smbms.tools.StringToDateConverter"/>
        </set>
    </property>
</bean>
<mvc:annotation-driven conversion-service="myConversionService">
       <!-- 省略中间的配置代码 -->
</mvc:annotation-driven>

在前面,我们提过<mvc:annotation-driven/>标签会自动注册一个默认的ConversionService,现在由于我们需要注册一个自定义的StringToDateConverter,因此需要显示定义一个ConversionService来覆盖<mvc:annotation-driven/>中的默认实现,通过配置<mvc:annotation-driven/>标签的conversion-service属性来完成.

> 第二种 : 配置FormattingConversionServiceFactoryBean

配置FormattingConversionServiceFactoryBean , 参考官网如下:
在这里插入图片描述
代码如下:

<!--配置字符串到日期格式的转换器-->
<bean id="conversionService"
      class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="cn.smbms.tools.StringToDateConverter"/>
        </set>
    </property>
</bean>
<mvc:annotation-driven conversion-service="conversionService">
    <!--省略中间配置-->
</mvc:annotation-driven>

装配完成StringToDateConverter之后,就可以在任何控制器的处理方法中使用这个转换器了,并且不需要在User.java的birthday属性上进行格式化注解的标注,注释掉@DateTimeFormat(pattern="yyyy-MM-dd")即可.

3) 运行测试

最后,部署运行测试,增加用户信息时,正常保存。

4) 使用配置类代替如下:

修改WebConfig.java,代码如下:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "cn.smbms.controller",
        includeFilters = {@ComponentScan.Filter(classes = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public ViewResolver viewResolver(){
        return new InternalResourceViewResolver("/WEB-INF/jsp/",".jsp");
    }

    @Bean(name = "multipartResolver")
    public CommonsMultipartResolver multipartResolver(){
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setDefaultEncoding("utf-8");
        multipartResolver.setMaxUploadSize(5000000);
        return multipartResolver;
    }
    //静态资源目录过滤
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //配置多视图解析器
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorParameter(true);
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("html",MediaType.TEXT_HTML);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
    }

    //配置Jackson消息转换器
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
                new MappingJackson2HttpMessageConverter(builder.build());
        jackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
                MediaType.APPLICATION_JSON,MediaType.TEXT_HTML,MediaType.APPLICATION_XML));
        converters.add(jackson2HttpMessageConverter);
    }
    //配置字符串到日期格式的转换器
    @Override
    public void addFormatters(FormatterRegistry registry) {
        Converter converter =  new StringToDate();
        registry.addConverter(converter);
    }
}

2.2 使用@InitBinder装配自定义编辑器

对于数据转换,其实还有一种更加灵活的方式,就是通过自定义的编辑器实现数据的转换和格式化处理.下面我们通过@InitBinder添加自定义编辑器,来解决Spring MVC日期类型无法绑定的问题.参照官网如下:
在这里插入图片描述
具体实现步骤如下:

1) 创建BaseController.java,并标注@InitBinder

在Contorller中抽象出一个父类对象BaseController.java,每个Controller都继承自BaseController,而这个父类使用@InitBinder.关键示例代码如下:

@Controller
public class BaseController {
	/**
	 * 标注了@InitBinder注解的方法 会在控制器初始化时调用
	 * 通过dataBinder的registerCustomEditor()方法注册一个编辑器
	 * 第一个参数表示日期类型(Date.class) 第二个参数表示使用自定义
	 * 的日期编辑器(CustomDateEditor),时间格式为yyyy-MM-dd true表示可以为空
	 * @param dataBinder
	 */
	@InitBinder
	public void initBinder(WebDataBinder dataBinder) {
		System.out.println("initBinder==============");
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        //严格解析日期,如果日期不合格就抛异常,不会自动计算
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
	}

}
2) 修改UserController.java

修改UserController.java,继承BaseController.java,关键示例代码如下:

@Controller
@RequestMapping("/user")
public class UserController extends BaseController {
	//....中间代码省略
}
3) 部署运行测试,增加用户信息时,正常保存

注意

在Spring MVC中,Bean定义了Date,double类型,如果没有做任何处理,日期,double都无法自动绑定.上述的两种方案都可以解决Spring MVC的时间类型等问题,当然也可以做更多业务上的需求.

4) 扩展:

使用@InitBinder第二种方式:

public class BaseController {
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        System.out.println("initBinder==============");
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }
}

参考官网如下:
在这里插入图片描述

2.3 使用Java类代替配置文件如下

编写AppConfig.java,用来代替applicationContext.xml,代码如下:

@Configuration
@ComponentScan(basePackages = {"cn.smbms.dao","cn.smbms.service"},
        excludeFilters = {@ComponentScan.Filter(classes = Controller.class)})
@PropertySource(value = "classpath:jdbc.properties")
public class AppConfig {
    @Value("${jdbc.driverClassName}")
    private String dirverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(dirverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
    @Bean
    public JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(dataSource());
    }
}

编写WebConfig.java,用来代替springmvc-servlet.xml,代码如下:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "cn.smbms.controller",
        includeFilters = {@ComponentScan.Filter(classes = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public ViewResolver viewResolver(){
        return new InternalResourceViewResolver("/WEB-INF/jsp/",".jsp");
    }

    @Bean(name = "multipartResolver")
    public CommonsMultipartResolver multipartResolver(){
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setDefaultEncoding("utf-8");
        multipartResolver.setMaxUploadSize(5000000);
        return multipartResolver;
    }
    //静态资源目录过滤
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //配置多视图解析器
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorParameter(true);
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("html",MediaType.TEXT_HTML);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
    }

    //配置Jackson消息转换器
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
                new MappingJackson2HttpMessageConverter(builder.build());
        jackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
                MediaType.APPLICATION_JSON,MediaType.TEXT_HTML,MediaType.APPLICATION_XML));
        converters.add(jackson2HttpMessageConverter);
    }
}

编写MyWebAppInitializer.java,用来代替web.xml,代码如下:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter("utf-8",true);
        return new Filter[]{encodingFilter};
    }
}

3 . 框架整合(Spring MVC+Spring+MyBatis)

到目前为止,我们已经基本上掌握了Spring MVC的相关知识点,目前超市订单管理系统的框架结构为Spring MVC+Spring+JDBC.

从这章开始我们要把DAO层实现替换成MyBatis框架,继续改造该项目框架实现为SpringMVC+Spring+MyBatis,即SSM

基于它的速度快,性能高,配置简单等优势,目前在互联网项目中所占比例也越来也大,掌握SSM框架整合,并能够在该框架上进行熟练的项目开发,是我们最终的目的.

3.1 搭建SSM框架的程序架构

1. SSM 简介

Spring MVC是一个优秀的Web框架,MyBatis是一个ORM数据持久化层框架,它们两个是独立的框架,之间没有直接的联系.

由于Spring 框架提供了IoC和AOP等相当实用的功能,若把Spring MVC 和 MyBatis中的对象交给Spring容器进行解耦合管理,不仅能大大增强系统的灵活性,便于功能扩展,还能通过Spring提供的服务简化代码,减少开发工作量,提高开发效率.

SSM框架整合其实就是分别实现Spring 与 Spring MVC, Spring 与 MyBatis的整合,而实现整合最主要的工作就是把Spring MVC,MyBatis中的对象配置到Spring容器中,交给Spring来管理.

当然对于Spring MVC框架来说,它本身就是Spring为展现层提供的MVC框架,所以在进行框架整合时,可以说Spring MVC 与 Spring 是无缝集成,性能优越.

2. 超市订单管理系统–架构设计

改造超市订单管理系统的架构实现,具体的系统架构设计图如图:
在这里插入图片描述
本系统采用SSM框架设计:

  • (1) 数据存储 : 采用MySQL数据库进行数据存储
  • (2) ORM : 采用MyBatis框架,实现数据的持久化操作
  • (3) Spring Core : 基于IoC 和 AOP 的处理方式,统一管理所有的JavaBean
  • (4) Web 框架: 采用Spring MVC进行Web请求的接受与处理.
  • (5) 前端框架,采用JSP为页面载体,使用jQuery框架以及HTML5和CSS3实现页面展示和交互

下面我们就按照上述的SSM架构设计来搭建框架,并实现超市订单系统的业务功能.

  1. maven项目加入SSM需要的依赖(单模块的方式管理项目)
  2. 构建实体类 DAO接口 DAO配置文件
  3. 编写MyBatis以及Spring 的配置文件
  4. 测试

3.2 整合思路与步骤

1. 新建 Web Project 并导入相关jar文件

搭建SSM框架单模块时所需要的maven依赖如下:

<properties>
    <!--统一定义依赖版本如下:-->
    <spring.version>5.1.5.RELEASE</spring.version>
    <!--省略其他依赖版本的定义-->
</properties>
<dependencyManagement>
    <dependencies>
        <!--定义spring 依赖的-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>${spring.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>

    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <!--mybatis-spring-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.3</version>
    </dependency>

    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
    </dependency>

    <!--dbcp 或者druid 可任选其一-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
        <version>2.6.0</version>
    </dependency>
    <!--servlet-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
		<scope>provided</scope>
    </dependency>

    <!-- com.alibaba/druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!--fastjson-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>

    <!-- log4j-web -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-web</artifactId>
        <version>2.11.1</version>
    </dependency>

    <!-- jstl -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

    <!-- hibernate-validator -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.2.Final</version>
    </dependency>

    <!-- commons-fileupload -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>

    <!-- commons-lang 构建随机数用 jar -->
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>

	<!-- jackson-databind -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.8</version>
    </dependency>

</dependencies>
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
    <plugins>
		<!--定义一个资源拷贝的插件-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
            	<encoding>utf-8</encoding>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <compilerVersion>1.8</compilerVersion>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>utf-8</encoding>
            </configuration>
        </plugin>
        <plugin>
            <!-- tomcat7-maven-plugin -->
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <port>8080</port>
                <path>/</path>
				 <!-- 解决get乱码 -->
                <uriEncoding>utf-8</uriEncoding>
            </configuration>
        </plugin>
    </plugins>
</build>

使用多模块构建项目时,父模块pom.xml文件定义如下:

<properties>
    <!--统一定义依赖版本如下:-->
    <junit.version>4.12</junit.version>
    <spring.version>5.1.5.RELEASE</spring.version>
    <!--省略其他依赖版本的定义-->
</properties>
<dependencyManagement>
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
    </dependency>
    <!--定义spring 依赖的-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-framework-bom</artifactId>
        <version>${spring.version}</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
	<!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.2</version>
    </dependency>
    
	<!--dbcp 或者druid 可任选其一-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
        <version>2.6.0</version>
    </dependency>
    
    <!-- com.alibaba/druid或者dbcp可任选其一 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!-- fastjson或者jackson可任选其一 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>
    
    <!-- mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
    </dependency>
    <!-- log4j-web -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-web</artifactId>
        <version>2.11.1</version>
    </dependency>
    <!-- javax.servlet-api -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>

    <!-- jstl -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

    <!-- hibernate-validator -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.2.Final</version>
    </dependency>
    <!-- commons-fileupload -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>

    <!-- commons-lang 构建随机数用 jar -->
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
    
    <!-- jackson-databind或者fastjson可任选其一 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.8</version>
    </dependency>

</dependencies>
</dependencyManagement>
<build>
<plugins>
    <!--定义一个资源拷贝的插件-->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
            <encoding>utf-8</encoding>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
            <compilerVersion>1.8</compilerVersion>
            <source>1.8</source>
            <target>1.8</target>
            <encoding>utf-8</encoding>
        </configuration>
    </plugin>

    <plugin>
        <!-- tomcat7-maven-plugin -->
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
            <port>8080</port>
            <path>/</path>
            <!-- 解决get乱码 -->
            <uriEncoding>utf-8</uriEncoding>
        </configuration>
    </plugin>
</plugins>
</build>

pojo模块导入的依赖如下:

<dependencies>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
	<!-- jackson-databind或者fastjson可任选其一 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    
</dependencies>

dao模块导入依赖如下:

<dependencies>
    <!--依赖pojo-->
    <dependency>
        <groupId>cn.smbms</groupId>
        <artifactId>smbms_pojo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <!--mybatis jar-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
    </dependency>
    <!--mybaits-spring-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
    </dependency>
    <!--druid 阿里的连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
    </dependency>
    
    <!--dbcp 或者druid 可任选其一-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
    </dependency>
    <!--mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

service模块导入依赖如下:

<dependencies>
    <dependency>
        <groupId>cn.smbms</groupId>
        <artifactId>smbms_dao</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
    </dependency>
</dependencies>

web模块导入依赖如下:

<dependencies>
    <dependency>
        <groupId>cn.smbms</groupId>
        <artifactId>smbms_service</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
    </dependency>
    <!-- commons-fileupload -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
    </dependency>

    <!-- commons-lang 构建随机数用 jar -->
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
    </dependency>
</dependencies>
2. web.xml

在前面的章节中,我们已经在web.xml中配置了Spring MVC的核心控制器 DispatcherServlet,字符编码过滤器,以及指定Spring 配置文件所在的位置并配置ContextLoaderListener等,示例代码如下:

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0"
  metadata-complete="true">
	<display-name>smbms-demo</display-name>
	<welcome-file-list>
		<welcome-file>/WEB-INF/jsp/login.jsp</welcome-file>
	</welcome-file-list>

	<!-- 配置springmvc控制器 -->
	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
                classpath:springmvc-servlet.xml
            </param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
    <!--注意拦截路径是/,不能写其他的-->
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	<!-- 指定spring配置文件所在的位置 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
        	classpath*:applicationContext-*.xml
        </param-value>
	</context-param>
	<listener>
		<listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
	</listener>
	<!-- 字符编码过滤器 -->
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>
            org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>utf-8</param-value>
		</init-param>
		<init-param>
      		<param-name>forceEncoding</param-name>
      		<param-value>true</param-value>
   		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 若项目中用的是Spring的版本是4.0以上,log4j2 可以不用配置log4j2的监听类 -->
	<!-- <listener>
		<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
	</listener> -->
</web-app>
3. 配置文件(resources)
1) applicationContext-mybatis.xml

applicationContext-mybatis.xml是Spring的配置文件,该文件内主要配置信息有数据源对象,事务管理,以及MyBatis的配置信息等.

(1) DBCP数据源配置:
<context:component-scan base-package="cn.smbms.service"/>   
<!--配置DataSource -->
<context:property-placeholder location="classpath:database.properties"/>
<bean id="dataSource"
	class="org.apache.commons.dbcp2.BasicDataSource"
	destroy-method="close" scope="singleton">
	<property name="driverClassName" value="${jdbc.driver}" />
	<property name="url" value="${jdbc.url}" />
	<property name="username" value="${jdbc.user}" />
	<property name="password" value="${jdbc.password}" />
	<!-- 连接初始值,连接池启动时创建的连接数量的初始值  默认值是0 --> 
	<property name="initialSize" value="${jdbc.initialSize}" />
	 <!-- 最小空闲值.当空闲的连接数少于阀值时,
	 	连接池就会预申请去一些连接,以免洪峰来时来不及申请  默认值是0 -->  
	<property name="minIdle" value="${jdbc.minIdle}" />
	 <!-- 最大空闲值.当经过一个高峰时间后,
	 	连接池可以慢慢将已经用不到的连接慢慢释放一部分,
	 	一直减少到maxIdle为止 ,0时无限制  默认值是8 -->  
	<property name="maxIdle" value="${jdbc.maxIdle}" />
	<!-- 连接池的最大值,同一时间可以从池分配的最多
		连接数量,0时无限制   默认值是8 -->  
	<property name="maxTotal" value="${jdbc.maxTotal}" />
	<!--连接在所指定的秒数内未使用才会被删除(秒)  -->
	<property name="removeAbandonedTimeout"
		value="${jdbc.removeAbandonedTimeout}" />
	<!-- sql 心跳 -->
	<property name="maxWaitMillis" value="${jdbc.maxWaitMillis}"/>
	<!--程序中的连接不使用后是否被连接池回收(
		该版本要使用removeAbandonedOnMaintenance
		和removeAbandonedOnBorrow)  -->
	<property name="removeAbandonedOnMaintenance" value="true"/>
	<property name="removeAbandonedOnBorrow" value="true"/>
	<!--是否开启Evict的定时校验,建议配置为true,
		不影响性能,并且保证安全性 -->
	<property name="testWhileIdle" value="true" />
	<!-- testOnBorrow和testOnReturn在生产环境一般是不开启的,
	主要是性能考虑 Borrow:租用,借用 :对拿到的以及返回的连接是否进行校验 -->
	<property name="testOnBorrow" value="false" />
	<property name="testOnReturn" value="false" />
	<property name="validationQuery" value="select 1" />
	<!--定义Evict校验的定时时间间隔-->
	<property name="timeBetweenEvictionRunsMillis" value="60000" />
	<!--定义Evict每次校验连接的数量,一般情况下,
		该值会和maxTotal大小一样,每次可以校验所有的连接-->
	<property name="numTestsPerEvictionRun" value="${jdbc.maxTotal}" />
</bean>

dbcp2 数据库连接池 具体的参数配置说明如下:

参数描述
username通过JDBC建立一个连接所需的用户名
password通过JDBC建立一个连接所需的密码
url通过JDBC建立一个连接所需的URL
driverClassName所使用的JDBC驱动的类全名
connectionProperties连接参数是在建立一个新连接时发送给JDBC驱动的 字符串的格式必须是[参数名=参数值;] 提示:用户名和密码属性是需要明确指出的,所以这两个参数不需要包含在这里
参数缺省值描述
defaultAutoCommitJDBC驱动的缺省值通过这个池创建连接的默认自动提交状态。如果不设置,则setAutoCommit 方法将不被调用。
defaultReadOnlyJDBC驱动的缺省值通过这个池创建连接的默认只读状态。如果不设置,则setReadOnly 方法将不被调用。(部分驱动不支持只读模式,如:Informix)
defaultTransactionIsolationJDBC驱动的缺省值通过这个池创建连接的默认事务策略,设置值为下列中的某一个: (参考 javadoc)NONEREAD_COMMITTEDREAD_UNCOMMITTEDREPEATABLE_READSERIALIZABLE
defaultCatalog通过这个池创建连接的默认缺省的catalog
cacheStatetrue如果设置为true,池化的连接将在第一次读或写,以及随后的写的时候缓存当前的只读状态和自动提交设置。这样就省去了对getter的任何进一步的调用时对数据库的额外查询。如果直接访问底层连接,只读状态和/或自动提交设置改变缓存值将不会被反映到当前的状态,在这种情况下,应该将该属性设置为false以禁用缓存。
参数缺省值描述
initialSize0当这个池被启动时初始化的创建的连接个数,起始生效版本:1.2
maxTotal8可以在这个池中同时被分配的有效连接数的最大值,如设置为负数,则不限制
maxIdle8可以在池中保持空闲的最大连接数,超出设置值之外的空闲连接将被回收,如设置为负数,则不限制
minIdle0可以在池中保持空闲的最小连接数,超出设置值之外的空闲连接将被创建,如设置为0,则不创建
maxWaitMillisindefinitely(如果没有可用连接)池在抛出异常前等待的一个连接被归还的最大毫秒数,设置为-1则等待时间不确定

提示:

如果在高负载的系统中将maxIdle的值设置的很低,则你可能会发现在一个新的连接刚刚被创建的时候就立即被关闭了。这是活跃的线程及时关闭连接要比那些打开连接的线程要快,导致空闲的连接数大于maxIdle。高负载系统中maxIdle的最合适的配置值是多样的,但是缺省值是一个好的开始点。

参数缺省值描述
validationQuery在连接池返回连接给调用者前用来进行连接校验的查询sql。如果指定,则这个查询必须是一个至少返回一行数据的SQL SELECT语句。如果没有指定,则连接将通过调用isValid() 方法进行校验。
testOnCreatefalse指明对象在创建后是否需要被校验,如果对象校验失败,则触发对象创建的租借尝试将失败。
testOnBorrowtrue指明在从池中租借对象时是否要进行校验,如果对象校验失败,则对象将从池子释放,然后我们将尝试租借另一个
testOnReturnfalse指明在将对象归还给连接池前是否需要校验。
testWhileIdlefalse指明对象是否需要通过对象驱逐者进行校验(如果有的话),假如一个对象校验失败,则对象将被从池中释放。
timeBetweenEvictionRunsMillis-1空闲对象驱逐线程运行时的休眠毫秒数,如果设置为非正数,则不运行空闲对象驱逐线程。
numTestsPerEvictionRun3在每个空闲对象驱逐线程运行过程中中进行检查的对象个数。(如果有的话)
minEvictableIdleTimeMillis1000 * 60 * 30符合对象驱逐对象驱逐条件的对象在池中最小空闲毫秒总数(如果有的话)
softMiniEvictableIdleTimeMillis-1符合对象驱逐对象驱逐条件的对象在池中最小空闲毫秒总数,额外的条件是池中至少保留有minIdle所指定的个数的连接。当miniEvictableIdleTimeMillis 被设置为一个正数,空闲连接驱逐者首先检测miniEvictableIdleTimeMillis,当空闲连接被驱逐者访问时,首先与miniEvictableIdleTimeMillis 所指定的值进行比较(而不考虑当前池中的空闲连接数),然后比较softMinEvictableIdleTimeMillis所指定的连接数,包括minIdle条件。
maxConnLifetimeMillis-1一个连接的最大存活毫秒数。如果超过这个时间,则连接在下次激活、钝化、校验时都将会失败。如果设置为0或小于0的值,则连接的存活时间是无限的。
connectionInitSqlsnull在第一次创建时用来初始化物理连接的SQL语句集合。这些语句只在配置的连接工厂创建连接时被执行一次。
lifotrue设置为true表明连接池(如果池中有可用的空闲连接时)将返回最后一次使用的租借对象(最后进入)。设置为false则表明池将表现为FIFO队列——将会按照它们被归还的顺序从空闲连接实例池中获取连接
参数缺省值描述
poolPreparedStatementsfalse设置该连接池的预处理语句池是否生效
maxOpenPreparedStatementsunlimited可以在语句池中同时分配的最大语句数。设置为负数则不限制。

这个设置同时作用于预处理语句池. 当一个可用的语句池被创建给每一个连接时,通过以下方法创建的预处理语句将被池化。

  • public PreparedStatement prepareStatement(String sql)
  • public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)

提示 -要确保你的连接会留下一些资源给其他语句。池化预处理语句可能会在数据库中保持他们的游标,可能会引起连接的游标越界,尤其是maxOpenPreparedStatements的值被设置为默认值(无限的),而且一个应用程序可能会为每个连接打开大量不同的预处理语句。为了避免这个问题maxOpenPreparedStatements应该被设置为一个小于连接可以打开的最大游标数的值。

参数缺省值描述
accessToUnderlyingConnectionAllowedfalse控制PoolGuard是否可以访问底层连接

如果允许访问的话,使用如下代码结构:

    Connection conn = ds.getConnection();    
	Connection dconn =((DelegatingConnection) conn).getInnermostDelegate();    
	//...    
    conn.close()

默认值为false,这是一个有着潜在风险的操作,使用不当可能会导致非常严重的后果。(在守卫连接已被关闭的情况下,关闭底层连接或者继续使用它),只有在你需要直接访问驱动的特有扩展是可以谨慎使用。

NOTE: 除了最原始那个之外,不要关闭底层连接

参数缺省值描述
removeAbandonedfalse标记是否删除超过removeAbandonedTimout所指定时间的被遗弃的连接。 如果设置为true,则一个连接在超过removeAbandonedTimeout所设定的时间未使用即被认为是应该被抛弃并应该被移除的。创建一个语句,预处理语句,可调用语句或使用它们其中的一个执行查询(使用执行方法中的某一个)会重新设置其父连接的lastUsed 属性。 在写操作较少的应用程序中将该参数设置为true可以将数据库连接从连接关闭失败中恢复。
removeAbandonedTimeout300一个被抛弃连接可以被移除的超时时间,单位为秒
logAbandonedfalse标志是否为应用程序中遗弃语句或连接的代码开启日志堆栈追踪。 因为一个堆栈跟踪已被创建,被抛弃的语句和连接相关的日志将被覆盖到打开每个连接或者创建一个Statement时

如果你启用了removeAbandoned,则一个连接被池回收再利用是可能的,因为它被认为是已遗弃 在(getNumIdle() < 2) and (getNumActive() > getMaxTotal() - 3)成立时,这个机制将被触发。

例如, maxTotal=20 ,这里有18个活跃连接,一个限制连接,将触发 “removeAbandoned”。但是只有在活动连接超过 “removeAbandonedTimeout” 所指定的秒数内未使用才会被删除(默认为300秒)。遍历一个结果集并不被统计为被使用,创建一个语句,预处理语句,可调用语句或使用它们其中的一个执行查询(使用执行方法中的某一个)会重新设置其父连接的lastUsed 属性。

> 补充Druid

阿里巴巴数据库事业部出品,为监控而生的数据库连接池。阿里云Data Lake Analytics(https://www.aliyun.com/product/datalakeanalytics)、DRDS、TDDL 连接池powered by Druid https://github.com/alibaba/druid/wiki

DruidDataSource通用配置 如图所示:
在这里插入图片描述
在这里插入图片描述
DruidDataSource大部分属性都是参考DBCP的,如果你原来就是使用DBCP,迁移是十分方便的。但是:Druid中的maxIdle为什么是没用的?

解释如下:

maxIdle是Druid为了方便DBCP用户迁移而增加的,maxIdle是一个混乱的概念。连接池只应该有maxPoolSize和minPoolSize,druid只保留了maxActive和minIdle,分别相当于maxPoolSize和minPoolSize。

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
     <property name="url" value="${jdbc_url}" />
     <property name="username" value="${jdbc_user}" />
     <property name="password" value="${jdbc_password}" />

     <property name="filters" value="stat" />

     <property name="maxActive" value="20" />
     <property name="initialSize" value="1" />
     <property name="maxWait" value="60000" />
     <property name="minIdle" value="1" />

     <property name="timeBetweenEvictionRunsMillis" value="60000" />
     <property name="minEvictableIdleTimeMillis" value="300000" />

     <property name="testWhileIdle" value="true" />
     <property name="testOnBorrow" value="false" />
     <property name="testOnReturn" value="false" />

     <property name="poolPreparedStatements" value="true" />
     <property name="maxOpenPreparedStatements" value="20" />

     <property name="asyncInit" value="true" />
 </bean>
  • 在上面的配置中,通常你需要配置url、username、password,maxActive这三项。
  • Druid会自动跟url识别驱动类名,如果连接的数据库非常见数据库,配置属性driverClassName
  • asyncInit是1.1.4中新增加的配置,如果有initialSize数量较多时,打开会加快应用启动时间
  • 其他配置请参考官网
> DruidDataSource配置属性列表

DruidDataSource配置兼容DBCP,但个别配置的语意有所区别。

配置缺省值说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错。详情-点此处
url连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里
driverClassName根据url自动识别这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxPoolPreparedStatementPerConnectionSize-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句,常用select ‘x’。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
validationQueryTimeout单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
keepAlivefalse (1.0.28)连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
timeBetweenEvictionRunsMillis1分钟(1.0.14)有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun30分钟(1.0.14)不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis连接保持空闲而不被驱逐的最小时间
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
配置proxyFilters缺省值说明类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
(2) 事务管理相关配置

配置事务管理器,采用AOP的方式进行事务管理,定义所有已smbms开头的业务方法都会进行进行事务处理.关键示例代码如下:

 <!-- 事务管理 -->
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  		<property name="dataSource" ref="dataSource"/>
</bean> 
<!-- AOP 事务处理 开始 -->    
<aop:aspectj-autoproxy />
<aop:config  proxy-target-class="true">
	<aop:pointcut expression="execution(* *cn.smbms.service..*(..))" id="transService"/>
	<aop:advisor pointcut-ref="transService" advice-ref="txAdvice" />
</aop:config> 
<tx:advice id="txAdvice" transaction-manager="transactionManager">  
    <tx:attributes>  
       <tx:method name="smbms*"  propagation="REQUIRED" rollback-for="Exception"  />
       <tx:method name="*"/>
    </tx:attributes>  
</tx:advice>
(3) 配置MyBatis的SqlSessionFactoryBean

关键示例代码如下:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	<property name="dataSource" ref="dataSource"/>
    	<property name="configLocation" value="classpath:mybatis-config.xml"/>
 </bean>
(4) 配置MyBatis的MapperScannerConfigurer

关键示例代码如下:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
     <property name="basePackage" value="cn.smbms.dao" />  
</bean>

##### 2) springmvc-servlet.xml

  • (1) 配置<mvc:annotation-driven/> 标签(包括消息转换器配置)
  • (2) 配置 <mvc:resources/>标签配置静态文件访问
  • (3) 配置支持文件上传的 ------- MultipartResolver
  • (4) 配置多视图解析器 ------- contentNegotiationManager
  • (5) 配置SpringMVC的消息转换器----stringHttpMessageConverter
  • (6) 配置jackson消息转换器—jackson2HttpMessageConverter
    • 或者fastjson消息转换器–fastJsonHttpMessageConverter
<context:component-scan base-package="cn.smbms.controller"/>
<!--过滤静态资源-->
<mvc:default-servlet-handler/>
<!--配置消息转换器-->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
    <mvc:message-converters>
        <!--引用关于springmvc解析json数据中文乱码的解析器-->
        <ref bean="stringHttpMessageConverter"/>
        <!--引用json解析器-->
        <ref bean="jackson2HttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
<!--配置多视图解析器-->
<bean id="contentNegotiationManager"
      class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorParameter" value="true"/>
    <property name="mediaTypes">
        <value>
            html=text/html
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

<!--配置jackson 消息转换器 解决关于json数据的日期格式问题 start-->
<bean id="jackson2HttpMessageConverter"
        class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    <property name="objectMapper" ref="objectMapper"/>
    <property name="supportedMediaTypes">
        <list>
            <value>text/html</value>
            <value></value>
            <value>application/xml</value>
        </list>
     </property>
</bean>

<bean id="objectMapper"
      class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
    <property name="simpleDateFormat" value="yyyy-MM-dd"/>
</bean>
<!--配置jackson 消息转换器 解决关于json数据的日期格式问题end-->

<!--配置fastjson日期转换器开始-->
<bean id="fastJsonHttpMessageConverter"
		class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
	<property name="fastJsonConfig" ref="fastJsonConfig"/>
	<property name="supportedMediaTypes">
		<list>
			<value>text/html</value>
			<value>application/json</value>
			<value>application/xml</value>
		</list>
	</property>
</bean>
<!--定义fastJsonConfig-->
<bean id="fastJsonConfig"
	  class="com.alibaba.fastjson.support.config.FastJsonConfig">
	<!--第一种全局配置日期的方式-->
	<property name="dateFormat" value="yyyy-MM-dd"/>
	<!--第二种是配置加注解的方式 -->
	<!--<property name="serializerFeatures">
		<array>
			<value>WriteDateUseDateFormat</value>
		</array>
	</property>-->
</bean>
<!--配置fastjson日期转换器结束-->

<!--配置stringHttpMessageConverter 解决json数据格式的中文乱码-->
<bean id="stringHttpMessageConverter"        	class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="defaultCharset" value="utf-8"/>
</bean>

<!--配置视图解析器-->
<mvc:view-resolvers>
    <mvc:jsp prefix="/WEB-INF/jsp/" suffix=".jsp"/>
</mvc:view-resolvers>

<!--配置文件上传解析器-->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="utf-8"/>
    <property name="maxUploadSize" value="5000000"/>
</bean>
  • (5) 配置拦截器 --------- Interceptors

在接收前端请求时, DispatcherServlet 会将请求交给处理器映射(HandlerMapping),让它找出对应请求的HandlerExecutionChain对象,该对象是一个执行链,它包含处理该请求的处理器(Handler),以及若干个对象请求实施拦截的拦截器(HandlerInterceptor). HandlerInterceptor 是一个接口,它包含三个方法,如图:
在这里插入图片描述

  • preHandle() : 在请求到达Handler之前,先执行该前置处理方法.当该方法返回false时,请求直接返回,若返回true时,请求继续往下传递(HandlerExectionChain中的下一个节点).由于preHandle()会在Controller之前执行,所以我们可以在该方法里进行编码,安全控制等逻辑处理.
  • postHandle() : 在请求被HandlerAdapter执行后,执行后置处理方法. 由于postHandle() 在Controller处理方法生成视图之前执行,因此我们可以在方法中修改ModelAndView.
  • afterCompletion() : 在响应已经被渲染之后,最后执行该方法,可用于释放资源.

注意

在之前改造的超市订单管理系统中存在一个问题,若用户非法进入系统(不进行登录操作而直接通过功能连接进入业务功能界面),从而浏览系统内部的业务信息,并且进行相应的业务操作,这样是存在很大安全隐患的.并且在进行更新操作时,由于在业务处理上都需要记录当前登录用户的id(createdBy),且当前用户信息是从session中获取的,必然会报500的空指针异常.

当然可以在不同的业务方法加入session是否为空的判断,但是最佳的解决方案就是增加系统拦截器,在拦截器中统一进行session判断.

通过上面的分析,可以理解为: Spring MVC 的拦截器是基于 HandlerMapping的,我们可以根据业务需求基于不同的HandlerMapping 来定义多个拦截器.关键示例代码如下:

	<!-- 定义拦截器 拦截用户请求 -->
	<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="/sys/**"/>
			<bean class="cn.smbms.interceptor.SysInterceptor"/>
		</mvc:interceptor>
	</mvc:interceptors>

cn.smbms.interceptor.SysInterceptor:自定义系统拦截器,它的主要作用就是拦截用户请求,进行session判断,我们在后续内容中详细讲解具体实现.

/sys/**: 表示所有以/sys开头的所用请求都需要通过自定义的SysInterceptor拦截器。

拦截器配置可参考官网:
在这里插入图片描述

3) database.properties:

用于dbcp连接池参数的设置

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.user=root
jdbc.password=root
 # 初始连接数
jdbc.initialSize=30
# 最大活跃数
jdbc.maxTotal=30
# 最大空闲数
jdbc.maxIdle=10
# 最小空闲数
jdbc.minIdle=5
# 最长等待时间(毫秒)
jdbc.maxWaitMillis=1000
# 程序中的连接不使用后是否被连接池回收(该版本要使用removeAbandonedOnMaintenance和jdbc.removeAbandonedOnBorrow)
# removeAbandoned=true
jdbc.removeAbandonedOnMaintenance=true
jdbc.removeAbandonedOnBorrow=true
# 连接在所指定的秒数内未使用才会被删除(秒)
jdbc.removeAbandonedTimeout=100
4) log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="off">
	<!-- 文件路径 -->
    <properties>
        <!--设置日志在硬盘上输出的目录${log4j:configParentLocation}使用此查找将日志文件放在相对于log4j配置文件的目录中-->
        <property name="Log_Home">log</property>
    </properties>
    <Appenders>
		<Console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式
            %L::输出代码中的行号。
            %M:输出产生日志信息的方法名。-->
            <!--"%highlight{%d{HH:mm:ss.SSS} %-5level %logger{36}.%M() 
            @%L - %msg%n}{FATAL=Bright Red, ERROR=Bright Magenta, 
            WARN=Bright Yellow, INFO=Bright Green, DEBUG=Bright Cyan, 
            TRACE=Bright White}"-->

            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36}.%M @%L :-> %msg%xEx%n"/>
        </Console>
        <!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${Log_Home}/info.${date:yyyy-MM-dd}.log" immediateFlush="true"
                     filePattern="${Log_Home}/$${date:yyyy-MM}/info-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36}.%M @%L :-> %msg%xEx%n"/>
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <filters>
                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            </filters>
            <Policies>
                <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
        </RollingFile>
    </Appenders>

    <Loggers>
        
        <!-- 
            限制Spring框架日志的输出级别,其它框架类似配置
            或者使用 AppenderRef 标签,将其输出到指定文件中,记得加上 additivity="false"
        -->
        <logger name="org.springframework.core" level="info">
        </logger>
        <logger name="org.springframework.beans" level="info">
        </logger>
        <logger name="org.springframework.context" level="info">
        </logger>
        <logger name="org.springframework.web" level="info">
        </logger>
        <!--建立一个默认的root的logger-->
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo" />
        </root>

    </Loggers>

</Configuration>
5) mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
		<!-- 这个配置使全局的映射器启用或禁用缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!-- :设置驱动程序等待数据库响应的秒数 -->
        <setting name="defaultStatementTimeout" value="5"/>
        <!-- 
         启用从经典数据库列名A_COLUMN到驼峰式经典Java属性名aColumn的自动映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--允许JDBC 生成主键。需要驱动器支持。如果设为了true,
这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 -->
        <setting name="useGeneratedKeys" value="true"/>
	</settings>
    <typeAliases>
        <!--为实体类取别名-->
        <!--<typeAlias type="cn.smbms.pojo.User" alias="user"/>-->
        <package name="cn.smbms.pojo"/>
    </typeAliases>
</configuration>

4. 数据对象模型(cn.smbms.pojo)

所有的数据对象都放置在此包下,如图:
在这里插入图片描述

5. DAO 数据访问接口(cn.smbms.dao)

所有的数据操作全部在dao包下,并按照功能模块划分规则进行包命名,如图:
在这里插入图片描述

6. 系统服务接口(cn.smbms.service)

系统服务接口负责系统的业务逻辑处理,基于接口的编程方式,接口和接口实现类按功能模块放置在同一个包下,命名规则同dao包,如图:
在这里插入图片描述

7. 前端控制器 Controller(cn.smbms.controller)

前端控制器全部在controller包下,如图:
在这里插入图片描述

8. 系统工具类(cn.smbms.tools)

tools 包中放置系统所有的公共对象和资源以及工具类,如分页,常量等,如图:
在这里插入图片描述

9. 前端页面(/WEB-INF/jsp) 和静态资源文件(/webapp/statics)

基于系统安全性考虑,前端的JSP页面全部放置在/WEB-INF/jsp目录下.为了便于js,css,images等静态资源文件的统一管理,把它们统一放置在/webapp/statics目录下,如图:
在这里插入图片描述
通过上述步骤,SSM框架以及搭建完成.下面需要实现系统功能(如登录,注销)来验证框架是否可用.

3.3 使用SSM框架实现登录,注销功能

在SSM框架上实现超市订单管理系统的登录和注销功能,具体实现步骤如下:

1. POJO

直接使用上一示例中的User.java即可

/**
 * 用户类
 */
public class User {
    private Integer id; //id
    //@NotEmpty(message = "用户编码不能为空!")
    private String userCode; //用户编码
    //@NotEmpty(message = "用户名不能为空!")
    private String userName; //用户名称
    //@NotNull(message = "用户密码不能为空!!")
    //@Length(max = 10,min = 6,message = "用户密码长度必须为6-10位!!")
    private String userPassword; //用户密码
    private Integer gender;  //性别
    //@Past(message="必须是过去的时间")
    //@DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;  //出生日期 java.sql.Date
    private String phone;   //电话
    private String address; //地址
    private Integer userRole;    //用户角色
    private Integer createdBy;   //创建者
    private Timestamp creationDate; //创建时间 java.sql.Timestamp;
    private Integer modifyBy;     //更新者
    private Timestamp modifyDate;   //更新时间 java.sql.Timestamp;

    private Integer age;//年龄

    private String userRoleName;    //用户角色名称
    /**证件照*/
    private String idPicPath;
    private String workPicPath;
    public Integer getAge() {
//        Date date = new Date();
//        Integer age = date.getYear()-birthday.getYear();
        //使用默认时区获取当前的日历
        Calendar now = Calendar.getInstance();
        Calendar birthday = Calendar.getInstance();
        //设置日历为生日的日期
        birthday.setTime(this.birthday);
        //获取现在的年份-生日的年份
        return now.get(Calendar.YEAR) - birthday.get(Calendar.YEAR);
    }
	//省略其他属性的getter setter 以及toString
}
2. DAO层

创建UserMapper.java, 关键示例代码如下:

/**
 * 用户DAO映射接口
 * @author Administrator
 *
 */
public interface UserMapper {
	/**
	 * 通过userCode获取User
	 * @param userCode
	 * @return
	 * @throws Exception
	 */
	User getLoginUser(@Param("userCode")String userCode);
}

创建UserMapper.xml,关键示例代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.smbms.dao.user.UserMapper">
	<!-- 通过userCode 获取user -->
	<select id="getLoginUser" resultType="User">
		select * from smbms_user u 
		<where>
			<if test="userCode != null and userCode != ''">
				and u.userCode = #{userCode}
			</if>
		</where>
	</select>
</mapper>
3. Service 层

创建UserService.java,关键示例代码如下:

public interface UserService {
	
	/**
	 * 用户登录
	 * @param userCode
	 * @param userPassword
	 * @return
	 */
	User login(String userCode,String userPassword);	
}

创建UserServiceImpl.java,关键示例代码如下:

@Service
public class UserServiceImpl implements UserService {
	
	@Resource
	private UserMapper userMapper;
	
	@Override
	public User login(String userCode, String userPassword) {
		User user = userMapper.getLoginUser(userCode);
		//匹配密码
		if(null != user && !user.getUserPassword().equals(userPassword)){
			user = null;
		}
		return user;
	}

}
4. 拦截器(interceptor)

之前在springmvc-servlet.xml中进行了拦截器的配置,现在创建自定义的拦截器,创建SysInterceptor.java,关键代码如下:

/**
 *  自定义拦截器
 * @author Administrator
 */
public class SysInterceptor /*extends HandlerInterceptorAdapter*/ implements HandlerInterceptor{
	private Logger logger = LogManager.getLogger(this.getClass());
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		logger.info("SysInterceptor preHandle!!!!");
        Object user = request.getSession().getAttribute(Constants.USER_SESSION);
        //判断不为空
        if(Optional.ofNullable(user).isPresent()){
            return true;
        }
        response.sendRedirect(request.getContextPath()+"/401.jsp");
        return false;
	}

}

在上述代码中,SysInterceptor 继承了 HandlerInterceptorAdapter,HandlerInterceptorAdapter是HandlerInterceptor接口的一个实现类.

参考官网如下:
在这里插入图片描述
根据我们的业务需求,对访问该系统的所有请求(注:登录请求除外) 进行身份验证以保证数据的安全性.因此在preHandle()方法中进行session 判断,若session中存储当前登录用户信息,则返回true,放过请求,进入控制器的处理方法中;反之,拦截该请求,返回false,并重定向到/webapp/401.jsp,进行友好信息提示.

401.jsp示例代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>超市账单管理系统</title>
</head>
<body>
	<div>
		<h2>对不起,您没有权限访问,请返回到<a href="login.html">首页</a></h2>
	</div>
	<div>
		<img src="statics/images/jg.png">
	</div>
</body>
</html>
5. Controller

创建BaseController.java,解决日期格式转换的问题,代码如下:

/**
 * 统一解决关于浏览器到服务器字符串到日期格式的转换问题
 */
public class BaseController {
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }
}

创建LoginController.java,关键代码如下:

@Controller
public class LoginController {
	private Logger logger = LogManager.getLogger(this.getClass());
	@Autowired
	private UserService userService;
	//登录
	@RequestMapping("/login.html")
	public String login() {
		logger.info("LoginController login=====");
		return "login";
	}
	//验证登录
	@RequestMapping("/dologin.html")
	public String doLogin(@RequestParam String userCode,
				@RequestParam String userPassword,
				HttpServletRequest request,HttpSession session) {
		logger.info("doLogin=====");
		User user = userService.login(userCode, userPassword);
		if(null != user) {
			session.setAttribute(Constants.USER_SESSION, user);
			return "redirect:/sys/main.html";
		}else {
			request.setAttribute("error", "用户名或者密码不正确");
			return "login";
		}
	}
	//注销
	@RequestMapping("/logout.html")
	public String logout(HttpSession session) {
		session.removeAttribute(Constants.USER_SESSION);
		return "login";
	}
	//主页面
	@RequestMapping("/sys/main.html")
	public String main() {
		logger.info("main=====");
		return "frame";
	}

}

在上述代码中,需要注意:当登录成功之后,重定向到"/sys/main.html", 而不是 "/main.html", 这样才能达到通过自定义的拦截器对非法请求的有效路径.

6. View层

login.jsp页面内容同前面示例代码,只需修改登录form表单的action请求路径为:action="${pageContext.request.contextPath }/dologin.html";

7. 部署运行

访问系统:http://localhost:8080/smbms-demo/,进行登录和注销测试,运行正常.测试拦截器是否起效,当进行注销退出系统之后,在地址栏中输入:http://localhost:8080/smbms-demo/sys/main.html,界面效果如下:
在这里插入图片描述

3.4 使用Java类代替配置文件如下

编写AppConfig.java,用来代替applicationContext.xml,代码如下:

@Configuration
@ComponentScan(basePackages = {"cn.smbms.dao","cn.smbms.service"},
        excludeFilters = {@ComponentScan.Filter(classes = Controller.class)})
@PropertySource(value = "classpath:jdbc.properties")
@MapperScan(basePackages = "cn.smbms.dao")
@EnableTransactionManagement
public class AppConfig {
    @Value("${jdbc.driverClassName}")
    private String dirverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(dirverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        //省略其他属性的设置
        return dataSource;
    }
    //配置sqlSessionFactory
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        sqlSessionFactoryBean.setTypeAliasesPackage("cn.smbms.pojo");
        //sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("mapper/UserDao.xml"));
        return sqlSessionFactoryBean.getObject();
    }
    //配置事务管理器
    @Bean
    public DataSourceTransactionManager transactionManager(){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource());
        return transactionManager;
    }
}

编写WebConfig.java,用来代替springmvc-servlet.xml,代码如下:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "cn.smbms.controller")
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public ViewResolver viewResolver(){
        return new InternalResourceViewResolver("/WEB-INF/jsp/",".jsp");
    }

    @Bean
    public CommonsMultipartResolver multipartResolver(){
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setDefaultEncoding("utf-8");
        multipartResolver.setMaxUploadSize(5000000);
        return multipartResolver;
    }
    //静态资源目录过滤
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //配置多视图解析器
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorParameter(true);
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("html",MediaType.TEXT_HTML);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
    }

    //配置Jackson消息转换器
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
                new MappingJackson2HttpMessageConverter(builder.build());
        jackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
                MediaType.APPLICATION_JSON,MediaType.TEXT_HTML,MediaType.APPLICATION_XML));
        converters.add(jackson2HttpMessageConverter);
    }
	//配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SysInterceptor()).addPathPatterns("/sys/**");
    }
}

编写MyWebAppInitializer.java,用来代替web.xml,代码如下:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter("utf-8",true);
        return new Filter[]{encodingFilter};
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值