经过之前的章节的学习,基本上已经完成了超市订单管理系统的功能改造,但是对于一些功能还可以进一步优化,比如删除功能,文件上传,修改个人密码,增加用户时的同名验证等都可以采用ajax异步实现,以提升用户体验.在本章中,我们将使用Spring MVC 处理JSON对象,并对SpringMVC框架中的数据转换与数据格式化处理进行详细介绍.
同时完成SpringMVC + Spring + MyBatis的空间搭建,并在此框架上熟练开发.
JSON的全称是”JavaScript Object Notation”,意思是JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式。
JSON是js的原生内容,也就意味着js可以直接取出json对象中的数据.
JSON是一种与语言无关的数据交换的格式,作用:使用ajax进行前后台数据交换!
(XML也是一种数据交换格式,为什么没有选择XML呢?因为XML虽然可以作为跨平台的数据交换格式,但是在JS(JavaScript的简写)中处理XML非常不方便,同时XML标记比数据多,增加了交换产生的流量,而JSON没有附加的任何标记,在JS中可作为对象处理,所以我们更倾向于选择JSON来交换数据。)
var json = {
键 : 值,
键 : 值,
.....
}
说明 :
json中的键 用双引号括起来 值可以是任意类型的数据 ( 严格的json值不会出现function (){…} 严格的json键用双引号括起来)
注意:
json的key是字符串(不能以数字开头) json的value是Object
定义JSON对象:
var JSON对象 = { "name" : value, "name" : value, …… };
定义JSON数组:
var JSON数组 = [ value, value, …… ];
定义JSON对象数组:
var personArray = [ { "name":"张三","age":30 }, {
"name":"李四", "age":40 } ];
json对象.键 或者 json对象["键"]
案例演示如下:
<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
+ "
姓名:" + user.name
+ "
年龄:" + user.age);
//定义json数组
var arr = ["中国", "美国", "俄罗斯"];
$(arr).each(function () {
$("#jsonSel").append(" +
this + "");
$("#jsonUl").append("" + this + "");
});
var userArr = [
{
"name": "张三",
"age": 23,
"id": 1
},
{
"name": "李四",
"age": 25,
"id": 2
},
{
"name": "王武",
"age": 27,
"id": 3
}
];
//创建table
var table = $("
")
.append("id " +
"姓名 年龄 ");
$(userArr).each(function () {
table.append("" + this.id + " " +
"" + this.name + " "
+ this.age + " ");
});
$("#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>
类库选择:
Java中并没有内置JSON的解析,因此使用JSON需要借助第三方类库。
下面是几个常用的 JSON 解析类库:
https://github.com/google/gson
https://github.com/alibaba/fastjson
https://github.com/FasterXML/jackson
本案例使用的是FastJson类库,并穿插讲解Jackson。
@ResponseBody
实现数据输出上一章中,我们完成了超市订单管理系统的新增用户功能,,但是在新增用户时,需要进行用户编码(userCode)的同名验证,以确保用户名的唯一性。该功能的实现需要运用ajax异步判断,由控制器的处理方法返回一个结果,告诉用户输入的用户编码是否存在。对于返回结果,我们一般使用JSON对象来表示。
那么在Spring MVC中如何处理JSON对象? 下面我们就结合该实例来深入学习一下,具体实现步骤如下:
DAO层(UserDao.java , UserDaoImpl.java) 提供一个通过userCode 获取User对象的方法即可(素材提供,直接使用).Service层(UserService.java , UserServiceImpl.java) 同上
首先我们要使用JSON,所有需要先在项目中引入处理JSON数据的工具jar文件,本书使用的阿里巴巴的FastJson,pom.xml
文件中加入如下依赖:
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
若使用的JSON解析类库是Jackson,则在pom.xml
文件中加入以下依赖:
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
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
都会在异步获取数据时使用,被其标注的处理方法返回的数据将输出到响应流中,客户端获取并显示数据.
修改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对象,对其进行相应的信息提示.
完成以上代码,部署并进行运行测试.登录系统后,先输入已经存在用户名(如:admin),运行结果如图:
当将鼠标移开时,也就是用户编码的输入失去焦点后,进行了异步验证,输入框显示提示信息:用户账号已存在.再输入一个不存在的用户编码(如:admintest),当鼠标移开后,输入框显示提示信息,该账号可以使用.如图:
在上个小节中,我们掌握了Spring MVC 框架中,如何实现ajax的异步请求调用,并对于处理方法放回的结果,我们一般都会把它转换成JSON对象,并使用@ResponseBody
注解实现了数据的输出.
但是在上述示例中,返回结果并非复杂的数据类型,只是将Map类型转换成JSON对象进行输出,若返回结果为Bean对象,该如何转换成JSON对象进行输出?若Bean对象中含有日期类型数据. Spring MVC 输出JSON数据时,日期又该如何处理?下面通过一个示例来演示
示例需求: 实现查看指定用户的明细信息功能. 具体实现要求:通过ajax异步调用来获取用户信息.在用户列表页,选中用户并单击 "查看"
按钮,页面下方展示出该用户的明细信息,最终实现效果如图:
实现步骤如下:
DAO层(UserDao.java , UserDaoImpl.java) 提供一个通过user id 获取User对象的方法即可(素材提供的getUserById()方法,直接使用).Service层(UserService.java , UserServiceImpl.java) 同上
/**
* 根据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;
}
userlist.jsp 增加一个div区域用于用户明细信息的展示,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@include file="common/head.jsp"%>
你现在所在的位置是:
用户管理页面
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
是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">
<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的坐标 , 代码如下:
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
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
消息转换器,关键示例代码如下:
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="utf-8"/>
bean>
<mvc:annotation-driven>
<mvc:message-converters>
<ref bean="stringHttpMessageConverter"/>
<ref bean="fastJsonHttpMessageConverter"/>
mvc:message-converters>
mvc:annotation-driven>
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=utf-8value>
<value>application/jsonvalue>
<value>application/xml;charset=utf-8value>
list>
property>
<property name="features">
<array>
<value>WriteDateUseDateFormatvalue>
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"%>
你现在所在的位置是:
用户管理页面
最后修改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">
<property name="defaultCharset" value="utf-8"/>
bean>
<mvc:annotation-driven>
<mvc:message-converters>
<ref bean="stringHttpMessageConverter"/>
<ref bean="fastJsonHttpMessageConverter"/>
mvc:message-converters>
mvc:annotation-driven>
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="fastJsonConfig" ref="fastJsonConfig"/>
<property name="supportedMediaTypes">
<list>
<value>text/htmlvalue>
<value>application/jsonvalue>
<value>application/xmlvalue>
list>
property>
bean>
<bean id="fastJsonConfig"
class="com.alibaba.fastjson.support.config.FastJsonConfig">
<property name="serializerFeatures">
<array>
<value>WriteDateUseDateFormatvalue>
array>
property>
bean>
重新部署,运行测试,效果如上图所示.
扩展:
**上述方法虽然通过配置,解决了日期格式的问题,代码侵入性有所降低.但是在实现项目中,有时对于日期的输出的格式还是以年月日(yyyy-MM-dd) 居多,但是FastJson中默认的日期转换格式为yyyy-MM-dd HH:mm:ss,除了通过通过@JSONField
注解去解决,还可以通过在配置的方式解决. **
修改配置文件springmvc-servlet.xml,关键代码如下:
<mvc:annotation-driven>
<mvc:message-converters>
<ref bean="stringHttpMessageConverter"/>
<ref bean="fastJsonHttpMessageConverter"/>
mvc:message-converters>
mvc:annotation-driven>
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="utf-8"/>
bean>
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="fastJsonConfig" ref="fastJsonConfig"/>
<property name="supportedMediaTypes">
<list>
<value>text/htmlvalue>
<value>application/jsonvalue>
<value>application/xmlvalue>
list>
property>
bean>
<bean id="fastJsonConfig"
class="com.alibaba.fastjson.support.config.FastJsonConfig">
<property name="dateFormat" value="yyyy-MM-dd"/>
bean>
使用配置类代替fastjson日期转换器,代码如下:
@Override
public void configureMessageConverters(List> 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数据的传递处理,简单总结以下几点
若没有配置消息转换器中的WriteDateUseDateFormat
,并且也没有加入属性注解@JSONField(yyyy-MM-dd)
,则在浏览器上输出时间戳.当然浏览器也可以针对时间戳,在javascript中调用其关于日期的函数,来对日期进行格式的转换;
若配置WriteDateUseDateFormat
,则会转换输出 yyyy-MM-dd HH:mm:ss格式的日期(注意:FastJson l.2版本之后关于全局日期的配置有所变化,可参考上述配置代码);
若配置了WriteDateUseDateFormat
,也增加了属性注解@JSONFiled(yyyy-MM-dd)
,则会转换输出为属性注解格式,注解优先;
若配置了
,那么关于全局的JSON数据输出的日期格式都会用此属性配置的格式,属性注解也将不再起作用
7. 扩展:
解决使用Jackson出现日期格式问题:
项目中导入Jackson的依赖,代码如下:
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
dependency>
springmvc-servlet.xml
配置文件如下:
<mvc:annotation-driven>
<mvc:message-converters>
<ref bean="stringHttpMessageConverter"/>
<ref bean="jackson2HttpMessageConverter"/>
mvc:message-converters>
mvc:annotation-driven>
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="utf-8"/>
bean>
<bean id="jackson2HttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=utf-8value>
<value>application/json;charset=utf-8value>
<value>application/xml;charset=utf-8value>
list>
property>
bean>
<bean id="objectMapper"
class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="indentOutput" value="true"/>
<property name="simpleDateFormat" value="yyyy-MM-dd HH:mm:ss"/>
bean>
@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">
<property name="favorParameter" value="true" />
<property name="defaultContentType" value="text/html" />
<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>
<property name="viewResolvers">
<list>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<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);
});
});
由于我们在配置ContentNegotiatingViewResolver
的favorParameter
属性为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
中的 favorParameter
、ignoreAcceptHeader
、mediaTypes
等在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>
<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>
完成以上修改之后,再次部署运行.登录系统成功后,进入用户列表页,选择用户,单击"查看"
按钮,界面数据正确显示.
直接在浏览器地址栏输入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”
作为后缀匹配需要使用
标签中的content-negotiation-manager
属性,修改上述代码如下:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<ref bean="stringHttpMessageConverter"/>
<ref bean="fastJsonHttpMessageConverter"/>
mvc:message-converters>
mvc:annotation-driven>
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="utf-8"/>
bean>
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=utf-8value>
<value>application/json;charset=utf-8value>
<value>application/xml;charset=utf-8value>
list>
property>
<property name="fastJsonConfig" ref="fastJsonConfig"/>
bean>
<bean id="fastJsonConfig"
class="com.alibaba.fastjson.support.config.FastJsonConfig">
<property name="dateFormat" value="yyyy-MM-dd"/>
bean>
<mvc:default-servlet-handler/>
<bean id="jackson2HttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=utf-8value>
<value>application/json;charset=utf-8value>
<value>application/xml;charset=utf-8value>
list>
property>
bean>
<bean id="objectMapper"
class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="simpleDateFormat" value="yyyy-MM-dd HH:mm:ss"/>
bean>
<mvc:view-resolvers>
<mvc:jsp prefix="/WEB-INF/jsp/" suffix=".jsp"/>
mvc:view-resolvers>
<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>
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对象.
-
- DataBinder
数据绑定的核心部件.它在整个流程中起到核心调度的作用.
-
- 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">
注意:
我们在之前的配置中使用的是
标签,并没有配置 ConversionService,但是却能通过格式化注解来解决日期的转换问题.这是因为
标签是Spring MVC的简化配置.默认情况下,它会创建并注册一个默认的DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter
实例.(此处需注意在Spring MVC 3.1之后,这两个类分别变更为:RequestMappingHandlerMapping
和RequestMappingHandlerAdapter
) 并且会注册一个默认的ConversionService示例:FormattingConversionServiceFactoryBean
,那么Spring MVC对处理方法的入参绑定就可以支持注解驱动功能,以满足大部分类型的转换需求.
通过
标签只需在POJO相应的属性上进行格式化注解的标注即可.
-
- BindingResult
BindingResult包含了已完成了数据绑定的入参对象和相应的校验错误对象. Spring MVC会抽取BindingResult中的入参对象及校验错误对象,将它们赋给处理方法的相应入参,这个对象之前已经学过.
2.1 编写自定义转换器
在前面的使用中,我们直接通过
标签来支持注解驱动的功能(@DataTimeFormat(pattern="yyyy-MM-dd")
),满足了日期类型的转换需求.现在也可以通过自定义转换器,来规定转换的规则.
Spring 在 org.springframework.core.convert.converter
包中定义了最简单的Converter
转换接口,它仅包括一个接口方法,如图:
Converter的作用就是将一种类型转换成另一种类型的对象.例如:用户输入的日期可能有多种形式,如"2016-07-08","08/07/2016"
等,这些都表示同一个日期.若希望Spring在将用户输入的日期字符串绑定到Date时,使用不同的日期格式,则需要编写一个Converter
,才能将字符串转换成日期.具体实现也很简单:首先需要创建一个实现org.springframework.core.convert.converter.Converter
接口的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
接口中的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>
在前面,我们提过
标签会自动注册一个默认的ConversionService,现在由于我们需要注册一个自定义的StringToDateConverter
,因此需要显示定义一个ConversionService来覆盖
中的默认实现,通过配置
标签的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
架构设计来搭建框架,并实现超市订单系统的业务功能.
- maven项目加入SSM需要的依赖(单模块的方式管理项目)
- 构建实体类 DAO接口 DAO配置文件
- 编写MyBatis以及Spring 的配置文件
- 测试
3.2 整合思路与步骤
1. 新建 Web Project 并导入相关jar文件
搭建SSM
框架单模块时所需要的maven依赖如下:
<properties>
<spring.version>5.1.5.RELEASEspring.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-framework-bomartifactId>
<version>${spring.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.2version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.38version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-dbcp2artifactId>
<version>2.6.0version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-webartifactId>
<version>2.11.1version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
<version>5.4.2.Finalversion>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
dependency>
dependencies>
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<version>3.1.0version>
<configuration>
<encoding>utf-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<compilerVersion>1.8compilerVersion>
<source>1.8source>
<target>1.8target>
<encoding>utf-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
<configuration>
<port>8080port>
<path>/path>
<uriEncoding>utf-8uriEncoding>
configuration>
plugin>
plugins>
build>
使用多模块构建项目时,父模块pom.xml
文件定义如下:
<properties>
<junit.version>4.12junit.version>
<spring.version>5.1.5.RELEASEspring.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-framework-bomartifactId>
<version>${spring.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.6version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-dbcp2artifactId>
<version>2.6.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.38version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-webartifactId>
<version>2.11.1version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
<version>5.4.2.Finalversion>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<version>3.1.0version>
<configuration>
<encoding>utf-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<compilerVersion>1.8compilerVersion>
<source>1.8source>
<target>1.8target>
<encoding>utf-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
<configuration>
<port>8080port>
<path>/path>
<uriEncoding>utf-8uriEncoding>
configuration>
plugin>
plugins>
build>
pojo模块导入的依赖如下:
<dependencies>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
dependencies>
dao模块导入依赖如下:
<dependencies>
<dependency>
<groupId>cn.smbmsgroupId>
<artifactId>smbms_pojoartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-dbcp2artifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
dependencies>
service模块导入依赖如下:
<dependencies>
<dependency>
<groupId>cn.smbmsgroupId>
<artifactId>smbms_daoartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
dependency>
dependencies>
web模块导入依赖如下:
<dependencies>
<dependency>
<groupId>cn.smbmsgroupId>
<artifactId>smbms_serviceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
dependency>
dependencies>
2. web.xml
在前面的章节中,我们已经在web.xml中配置了Spring MVC的核心控制器 DispatcherServlet,字符编码过滤器,以及指定Spring 配置文件所在的位置并配置ContextLoaderListener等,示例代码如下:
<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-demodisplay-name>
<welcome-file-list>
<welcome-file>/WEB-INF/jsp/login.jspwelcome-file>
welcome-file-list>
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
servlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>
classpath:springmvc-servlet.xml
param-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>
classpath*:applicationContext-*.xml
param-value>
context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
listener-class>
listener>
<filter>
<filter-name>encodingFilterfilter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
filter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>utf-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>encodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>
3. 配置文件(resources)
1) applicationContext-mybatis.xml
applicationContext-mybatis.xml是Spring的配置文件,该文件内主要配置信息有数据源对象,事务管理,以及MyBatis的配置信息等.
(1) DBCP数据源配置:
<context:component-scan base-package="cn.smbms.service"/>
<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}" />
<property name="initialSize" value="${jdbc.initialSize}" />
<property name="minIdle" value="${jdbc.minIdle}" />
<property name="maxIdle" value="${jdbc.maxIdle}" />
<property name="maxTotal" value="${jdbc.maxTotal}" />
<property name="removeAbandonedTimeout"
value="${jdbc.removeAbandonedTimeout}" />
<property name="maxWaitMillis" value="${jdbc.maxWaitMillis}"/>
<property name="removeAbandonedOnMaintenance" value="true"/>
<property name="removeAbandonedOnBorrow" value="true"/>
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="validationQuery" value="select 1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="numTestsPerEvictionRun" value="${jdbc.maxTotal}" />
bean>
dbcp2 数据库连接池 具体的参数配置说明如下:
参数
描述
username
通过JDBC建立一个连接所需的用户名
password
通过JDBC建立一个连接所需的密码
url
通过JDBC建立一个连接所需的URL
driverClassName
所使用的JDBC驱动的类全名
connectionProperties
连接参数是在建立一个新连接时发送给JDBC驱动的 字符串的格式必须是[参数名=参数值;] 提示:用户名和密码属性是需要明确指出的,所以这两个参数不需要包含在这里
参数
缺省值
描述
defaultAutoCommit
JDBC驱动的缺省值
通过这个池创建连接的默认自动提交状态。如果不设置,则setAutoCommit 方法将不被调用。
defaultReadOnly
JDBC驱动的缺省值
通过这个池创建连接的默认只读状态。如果不设置,则setReadOnly 方法将不被调用。(部分驱动不支持只读模式,如:Informix)
defaultTransactionIsolation
JDBC驱动的缺省值
通过这个池创建连接的默认事务策略,设置值为下列中的某一个: (参考 javadoc)NONEREAD_COMMITTEDREAD_UNCOMMITTEDREPEATABLE_READSERIALIZABLE
defaultCatalog
通过这个池创建连接的默认缺省的catalog
cacheState
true
如果设置为true,池化的连接将在第一次读或写,以及随后的写的时候缓存当前的只读状态和自动提交设置。这样就省去了对getter的任何进一步的调用时对数据库的额外查询。如果直接访问底层连接,只读状态和/或自动提交设置改变缓存值将不会被反映到当前的状态,在这种情况下,应该将该属性设置为false以禁用缓存。
参数
缺省值
描述
initialSize
0
当这个池被启动时初始化的创建的连接个数,起始生效版本:1.2
maxTotal
8
可以在这个池中同时被分配的有效连接数的最大值,如设置为负数,则不限制
maxIdle
8
可以在池中保持空闲的最大连接数,超出设置值之外的空闲连接将被回收,如设置为负数,则不限制
minIdle
0
可以在池中保持空闲的最小连接数,超出设置值之外的空闲连接将被创建,如设置为0,则不创建
maxWaitMillis
indefinitely
(如果没有可用连接)池在抛出异常前等待的一个连接被归还的最大毫秒数,设置为-1则等待时间不确定
提示:
如果在高负载的系统中将maxIdle的值设置的很低,则你可能会发现在一个新的连接刚刚被创建的时候就立即被关闭了。这是活跃的线程及时关闭连接要比那些打开连接的线程要快,导致空闲的连接数大于maxIdle。高负载系统中maxIdle的最合适的配置值是多样的,但是缺省值是一个好的开始点。
参数
缺省值
描述
validationQuery
在连接池返回连接给调用者前用来进行连接校验的查询sql。如果指定,则这个查询必须是一个至少返回一行数据的SQL SELECT语句。如果没有指定,则连接将通过调用isValid() 方法进行校验。
testOnCreate
false
指明对象在创建后是否需要被校验,如果对象校验失败,则触发对象创建的租借尝试将失败。
testOnBorrow
true
指明在从池中租借对象时是否要进行校验,如果对象校验失败,则对象将从池子释放,然后我们将尝试租借另一个
testOnReturn
false
指明在将对象归还给连接池前是否需要校验。
testWhileIdle
false
指明对象是否需要通过对象驱逐者进行校验(如果有的话),假如一个对象校验失败,则对象将被从池中释放。
timeBetweenEvictionRunsMillis
-1
空闲对象驱逐线程运行时的休眠毫秒数,如果设置为非正数,则不运行空闲对象驱逐线程。
numTestsPerEvictionRun
3
在每个空闲对象驱逐线程运行过程中中进行检查的对象个数。(如果有的话)
minEvictableIdleTimeMillis
1000 * 60 * 30
符合对象驱逐对象驱逐条件的对象在池中最小空闲毫秒总数(如果有的话)
softMiniEvictableIdleTimeMillis
-1
符合对象驱逐对象驱逐条件的对象在池中最小空闲毫秒总数,额外的条件是池中至少保留有minIdle所指定的个数的连接。当miniEvictableIdleTimeMillis 被设置为一个正数,空闲连接驱逐者首先检测miniEvictableIdleTimeMillis,当空闲连接被驱逐者访问时,首先与miniEvictableIdleTimeMillis 所指定的值进行比较(而不考虑当前池中的空闲连接数),然后比较softMinEvictableIdleTimeMillis所指定的连接数,包括minIdle条件。
maxConnLifetimeMillis
-1
一个连接的最大存活毫秒数。如果超过这个时间,则连接在下次激活、钝化、校验时都将会失败。如果设置为0或小于0的值,则连接的存活时间是无限的。
connectionInitSqls
null
在第一次创建时用来初始化物理连接的SQL语句集合。这些语句只在配置的连接工厂创建连接时被执行一次。
lifo
true
设置为true表明连接池(如果池中有可用的空闲连接时)将返回最后一次使用的租借对象(最后进入)。设置为false则表明池将表现为FIFO队列——将会按照它们被归还的顺序从空闲连接实例池中获取连接
参数
缺省值
描述
poolPreparedStatements
false
设置该连接池的预处理语句池是否生效
maxOpenPreparedStatements
unlimited
可以在语句池中同时分配的最大语句数。设置为负数则不限制。
这个设置同时作用于预处理语句池. 当一个可用的语句池被创建给每一个连接时,通过以下方法创建的预处理语句将被池化。
- public PreparedStatement prepareStatement(String sql)
- public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
提示 -要确保你的连接会留下一些资源给其他语句。池化预处理语句可能会在数据库中保持他们的游标,可能会引起连接的游标越界,尤其是maxOpenPreparedStatements的值被设置为默认值(无限的),而且一个应用程序可能会为每个连接打开大量不同的预处理语句。为了避免这个问题maxOpenPreparedStatements应该被设置为一个小于连接可以打开的最大游标数的值。
参数
缺省值
描述
accessToUnderlyingConnectionAllowed
false
控制PoolGuard是否可以访问底层连接
如果允许访问的话,使用如下代码结构:
Connection conn = ds.getConnection();
Connection dconn =((DelegatingConnection) conn).getInnermostDelegate();
//...
conn.close()
默认值为false,这是一个有着潜在风险的操作,使用不当可能会导致非常严重的后果。(在守卫连接已被关闭的情况下,关闭底层连接或者继续使用它),只有在你需要直接访问驱动的特有扩展是可以谨慎使用。
NOTE: 除了最原始那个之外,不要关闭底层连接
参数
缺省值
描述
removeAbandoned
false
标记是否删除超过removeAbandonedTimout所指定时间的被遗弃的连接。 如果设置为true,则一个连接在超过removeAbandonedTimeout所设定的时间未使用即被认为是应该被抛弃并应该被移除的。创建一个语句,预处理语句,可调用语句或使用它们其中的一个执行查询(使用执行方法中的某一个)会重新设置其父连接的lastUsed 属性。 在写操作较少的应用程序中将该参数设置为true可以将数据库连接从连接关闭失败中恢复。
removeAbandonedTimeout
300
一个被抛弃连接可以被移除的超时时间,单位为秒
logAbandoned
false
标志是否为应用程序中遗弃语句或连接的代码开启日志堆栈追踪。 因为一个堆栈跟踪已被创建,被抛弃的语句和连接相关的日志将被覆盖到打开每个连接或者创建一个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
initialSize
0
初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive
8
最大连接池数量
maxIdle
8
已经不再使用,配置了也没效果
minIdle
最小连接池数量
maxWait
获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements
false
是否缓存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)方法
testOnBorrow
true
申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn
false
归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testWhileIdle
false
建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
keepAlive
false (1.0.28)
连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
timeBetweenEvictionRunsMillis
1分钟(1.0.14)
有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun
30分钟(1.0.14)
不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
连接保持空闲而不被驱逐的最小时间
connectionInitSqls
物理连接初始化的时候执行的sql
exceptionSorter
根据dbType自动识别
当数据库抛出一些不可恢复的异常时,抛弃连接
filters
属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
配置proxyFilters
缺省值
说明类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
(2) 事务管理相关配置
配置事务管理器,采用AOP的方式进行事务管理,定义所有已smbms开头的业务方法都会进行进行事务处理.关键示例代码如下:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<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) 配置
标签(包括消息转换器配置)
- (2) 配置
标签配置静态文件访问
- (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>
<ref bean="stringHttpMessageConverter"/>
<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>
<bean id="jackson2HttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
<property name="supportedMediaTypes">
<list>
<value>text/htmlvalue>
<value>value>
<value>application/xmlvalue>
list>
property>
bean>
<bean id="objectMapper"
class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="simpleDateFormat" value="yyyy-MM-dd"/>
bean>
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="fastJsonConfig" ref="fastJsonConfig"/>
<property name="supportedMediaTypes">
<list>
<value>text/htmlvalue>
<value>application/jsonvalue>
<value>application/xmlvalue>
list>
property>
bean>
<bean id="fastJsonConfig"
class="com.alibaba.fastjson.support.config.FastJsonConfig">
<property name="dateFormat" value="yyyy-MM-dd"/>
bean>
<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
<Configuration status="off">
<properties>
<property name="Log_Home">logproperty>
properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36}.%M @%L :-> %msg%xEx%n"/>
Console>
<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"/>
<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>
<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 level="info">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo" />
root>
Loggers>
Configuration>
5) mybatis-config.xml
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="defaultStatementTimeout" value="5"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="useGeneratedKeys" value="true"/>
settings>
<typeAliases>
<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
)
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,关键示例代码如下:
<mapper namespace="cn.smbms.dao.user.UserMapper">
<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"%>
超市账单管理系统
对不起,您没有权限访问,请返回到首页
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};
}
}