# 1. AJAX
# 1.1. 响应数据而不再响应页面
传统的WEB响应时处理方式有转发或重定向,无论是哪一种,最终,客户端都将得到一个新的页面!这样的处理方式存在比较明显的问题:
- 客户端要得到响应结果(页面),产生的流量消耗较大;
- 访问速度较慢;
- 不便于扩展,例如可能不适用于其他客户端,例如手机端;
- .....
理想的解决方案是:服务器端值负责处理响应,并且,结果是成功与否,或客户端所需要的数据,而并不响应整个页面!
# 关于@ResponseBody
使用@ResponseBody
添加在处理请求的方法之前,如果返回Strign
类型,则表示返回值将成为响应正文,而不再是转发或重定向。
在实际使用时,默认情况下,并不支持中文!
# 1.3.
使用XML组织数据存在的问题:解析过程较复杂,且数据量较大,例如:
<users></users>
JSON也是一种组织数据的格式,它更加轻量级,例如:
{"id":1,"user":"Jack"}
它的语法特征是:
- 整个数据是使用大括号框住的;
- 各组属性之间使用逗号分割;
- 使用冒号分隔属性名称与属性值;
- 属性名称使用双引号框住;
- 如果属性值是字符串,也需要使用双引号框住;
基于以上规则,如果需要添加一个age
属性,则应该是:
{"id":1,"name":"Jack","age":23}
可以发现,这种组织数据的格式,相比XML更加简洁,并且,JSON格式是javascript默认识别的一种数据类型!
<script type="text/javascript">
var json = {"id":1,"name":"Jack","age":23};
alert(json.age)
</script>
在JSON数据中,每个属性的值,也可以是另一个对象:
{
"id":1,
"name":"Jack",
"age":23,
"from":{
"name":"beijing",
"zip":"110000"
}
}
在数据读取方面,直接通过.
调用对象中的属性即可:
<script type="text/javascript">
var json = {
"id":1,
"name":"Jack",
"age":23,
"from":{
"name":"beijing",
"zip":"110000"
}
}
alert(json.from.name)
</script>
在JSON中,还支持数组,用于组织批量数据:
{
"id":1,
"name":"Jack",
"age":23,
"from":{
"name":"beijing",
"zip":"110000"
},
"skill":[
{"id":1,"name":"Java"},
{"id":2,"name":"Spring"},
{"id":3,"name":"SpringMVC"}
]
}
数组的元素可以是基本值,也可以是另一个对象!与Java中的数组一样,各元素之间使用逗号进行分隔即可!
在使用JSON数组时,在Javascript中也可以使用循环语法:
for(var i=0; i<json.skill.length; i++){
console.log("id="+json.skill[i].id);
console.log("name="+json.skill[i].name);
console.log("---------");
}
在实际应用中,可能无法直接得到JSON对象,而是JSON格式的字符串,则可以通过JSON.parse(str)
将字符串转化成JSON对象。
var str = '{"id":1,"name":"jack"}';
var json = JSON.parse(str);
alert(json.name);
小结
- JSON是一种比XML更加轻量级的组织数据的方式,数据量更小,且解析更加方便;
- JavaScript默认识别JSON类型的数据;
- 在JSON数据中,使用
{}
框住整个对象,所有属性之间使用逗号跟个,属性名称均使用双引号框住,属性值可以是基本类型的数据(数值型,字符串,布尔值),也可以是另一个对象 (使用{}
),或数组(使用[]
); - 使用
JSON.parase(str)
可以吧字符串str
转换为JSON对象。
# 1.4. 在服务端如何高效的返回JSON
在服务端,添加新的依赖Jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
</dependency>
当添加了Jackson依赖后,如果控制器(Controller)中处理请求的方法返回的数据格式不是SpringMVC默认识别的格式(例如不是String
等),SpringMVC会自动应用Jackson框架,把返回的对象(例如自定义的ResponseResult
)以JSON格式来组织,返回给客户端!
除此以外,Jackson框架还会在响应头(Response Headers)中配置Content-type为application/json,charset=utf-8
,即声明响应的内容是JSON数据,且字符编码是UTF-8,解决了原有默认编码是ISO-8859-1导致的乱码问题。
由于Jackson框架还是设置响应头,默认是不支持的,所以,在Spring的配置文件中还需要添加注册驱动的配置:
<mvc:annotation-driven/>
# 1.5. 异步请求
在传统的开发模式中,浏览器向服务器提交请求的方式有: 改变浏览器地址栏中的URL、FORM表单,这2种方式付出请求后,由浏览器直接处理服务器的响应,而浏览器的响应方式就是直接把响应正文显示在浏览器中!
为了使得客户端可以自行发出请求,并自行处理结果,而不是由浏览器把响应的结果直接显示出来,则需要使用到异步请求!
AJAX是在网页客户端通过Javascript发出异步请求的做法,传统的AJAX开发比较麻烦,通常会结合jQuery框架来实现开发过程:
<script type="text/javascript">
$("#register").click(function(){
//将请求提交到哪里去
var url = "user/handle_reg.do";
//data: 请求参数,格式为: xx=xx&xx=xx&xx=xx
// $("#username"),id选择器
var data = "username=" + $("#username").val();
//type: 请求类型
var type= "POST";
//datatype: 服务端响应的数据类型,
//例如:json/text/xml,取决于Response Headers(回来的信息)
var dataType = "json";
//发出请求,处理响应结果
$.ajax({
"url":url,
"data":data,
"type":type,
"dataType":dataType,
"success": function(obj){
//obj就是服务器端响应的JSON字符串转换得到的JSON对象
if(obj.state == 1){
//注册成功,则登录
location.href="login.html";
}else{
//注册失败,提示
alert(obj.message);
}
}
});
});
</script>
在以上AJAX处理中,主要是使用jQuery中的$.ajax()
函数来实现,该函数的参数是一个JSON对象,所以,通过{}
来组织其中的内容,通常,需要指定5项属性:
- url:将请求提交到哪里
- data:提交的请求参数,参数的数据格式例如:
xx=xx&xx=xx&xx=xx
,如果某些参数的值来自输入框等控件,可以通过$("#控件id").val()
获取; - type:提交的请求类型,例如
get
或post
,该值不区分大小写; - dataType:服务器端响应的结果的数据类型,在服务器端响应时,响应头(Response Headers)中会包含Content-Type,确定了响应的数据类型,根据此处的类型,从而决定当前属性的值是
json
或xml
或text
; - success:当服务器端正确的响应时,将如何处理响应结果,该属性的值是一个函数,通常使用匿名函数,且,函数中可以添加参数,该参数就是服务器响应的内容,如果服务器端响应的是JSON字符串,则在该函数内部,可以将参数直接当作JSON对象来使用;
- error:当服务器端的响应码是3xx、4xx、5xx、时,
$.ajax()
并不会调用success
对应的函数,而调用error
对应的函数。
# 1.6. 练习
题目
假设正确的用户名是root
,匹配的密码是1234
,请设计登录页面login.html
,当提交登录时,服务器端完成用户名与密码的验证,并响应json结果,客户端获取结果,如果登录成功,重定向到index.html
,如果失败,则在对象的输入框右侧显示错误信息。
开发-1-服务器端
在控制器类UserController
中添加处理登录的方法:
@RequestMapping("/handle_login.do")
@ResponseBody
public ResponseResult handleLogin(
@RequestParam("username")String username,
@RequestParam("password")String password){
//...
}
注意:由于登录成功、用户名不存在、密码错误时,均有不同的处理方式,所以,在不用的分支,返回结果的state属性值应该是不相同的!
完成后,通过http://localhost:8080/AJAX-01-SAMPLE/user/handle_login.do?username=root&password=1234
路径进行测试,观察是否得到了正确的JSON数据,测试完成后,调整:
@RequestMapping(value="/handle_login.do",method=RequestMethod.POST)
开发-2-设计前端显示
当前登录页面至少应该包括:
- 用户名的输入框
- 密码的输入框
- 用户名出错时的提示区域
- 密码错误时的提示区域
- 登录按钮
由于将使用AJAX提交,所以,可以不需要<form>
标签,也可以不需要submit
类型的按钮。
由于需要从2个输入框中获取值,可能有提示信息要显示到2个提示区域,按钮也要绑定Javascript事件,所以,这5个标签都应该设置id属性。
<div>请输入用户名:</div>
<div>
<input id="username" name="username"/>
<span id="username_hint"></span>
</div>
<div>请输入密码:</div>
<div>
<input id="password" name="password"/>
<span id="password_hint"></span>
</div>
<div><input id="login" type="button" value="登录" /></div>
开发-3-请求与响应
由于将使用jQuery框架的$.ajax()
函数,所以,应该先添加jQuery文件的引用。
然后,通过$("#login").click()
为登录按钮绑定点击事件。
注意:在HTML中<script>
标签必须是成对出现的,例如<script></script>
,如果写成<script/>
这样是错误的。
注意:使用$("#login").click()
语法直接出现在<script>
标签中,表示当浏览器解析代码到这一行时,就立即执行,所以,这些代码必须添加在原有的HTML代码之后!
在$.ajax()
函数中,url
应该是user/handle_login.do
,data
应该是username=?&password=?
,此处2个参数值都可以通过$("#username").val()
此类语法获取输入框中的值,type
属性为post
,dataType
属性为json
,最后,在success
处理时,应该对obj.state
进行判断,如果值为1,可通过location.href="index.html"
进行跳转,如果值为-1或-2时,可以通过$("#username_hint").html(obj.message)
进行提示。
为了避免有多余的提示信息残留在页面中,可以在点击事件的其实位置,就通过$("#username_hint").html("")
直接清楚2个提示区域的内容!
# ----------------------
什么是AJAX 异步 JavaScript 和 XML,通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
@ResponseBody注解 使用@ResponseBody添加在处理请求的方法之前,如果返回Strign类型,则表示返回值将成为响应正文,而不再是转发或重定向。
什么是json 一种轻量级的数据交换格式,是javascript默认的一种数据类型,每个属性的值,可以是数值、字符串、布尔,也可以是对象和数组
json的语法特征:
- 整个数据是使用大括号框住的;
- 各组属性之间使用逗号分割;
- 使用冒号分隔属性名称与属性值;
- 属性名称使用双引号框住;
- 如果属性值是字符串,也需要使用双引号框住;
使用JSON.parase(str)可以吧字符串str转换为JSON对象。
在服务端如何高效的返回JSON 当添加了Jackson依赖后,如果控制器(Controller)中处理请求的方法返回的数据格式不是SpringMVC默认识别的格式(例如不是String等),SpringMVC会自动应用Jackson框架,把返回的对象(例如自定义的ResponseResult)以JSON格式来组织,返回给客户端!还会在响应头配置Content-type,设置字符编码为UTF-8,从而解决了乱码问题。 由于Jackson框架设置响应头,需在Spring配置文件中添加注册驱动
# 1. 案例
# 1.1. 目标
实现用户注册、登录功能,用户的数据将需要写入到数据库中,并且,注册时,用户名必须是唯一的!前端界面将通过AJAX提交请求并处理结果。
# 1.2. 分析
在每项功能的开发过程中,应该:持久层(建库建表、实体类、增删改查),业务层,控制器层,前端界面。
MVC = Model(DAO + Service) + View +Controller
# 1.3. 注册-持久层
应该先确定数据库、数据表、然后创建与数据表对应的实体类。
检查 spring-dao.xml
中的配置是否正确。
然后,创建cn.tedu.project.UserMapper
接口,然后添加抽象方法:
Integer insert(User user);
接下来,在src/main/resources
下创建mappers
文件夹,并复制映射文件到此文件夹中,确认文件名为UserMapper.xml
,并配置以上抽象方法的映射:
<!-- 向用户数据表中插入用户数据 -->
<!-- Integer insert(User user) -->
<insert id="insert" parameterType="cn.tedu.project.entity.User"
useGeneratedKeys="true" keyProperty="id">
insert into t_user(
username, password, age, phone, email
)values(
#{username}, #{password},
#{age}, #{phone}, #{email}
)
</insert>
为了保证用户名的唯一,应该实现根据用户名查询用户数据的功能,从而,后续在业务层中可以先查询尝试注册的用户名是否已经被注册,如果没有被注册,则允许,否则,如果查询到对应的数据,表示用户名已经被占用,不允许注册!
所以,还应该在持久层的接口中声明:
User findUserByUsername(String username);
然后配置映射:
<!-- 根据用户名查询用户数据 -->
<!-- User findUserByUsername(String username) -->
<select id="findUserByUsername"
resultType="cn.tedu.project.entity.User">
select
id
from
t_user
where
username=#{username}
</select>
以上查询过程中,只查询了id
,主要原因是本次查询根本就不关注结果中User
对象的各项属性值,而只需要关注返回的结果是否为null
即可!
# 1.4. 注册-业务层
每个用户名都是唯一的,不允许注册已经被占用的用户名。
关于业务层的设计,首先,应该创建cn.tedu.project.service.IUserService
业务层接口,然后,在该接口中,声明抽象方法:
User reg(User user) throws UsernameConflictException;
通常抽象方法的名称,与需要执行的业务(需要实现的功能)是对应的!
关于方法的返回值,依据操作成功时需要返回什么数据来设计,而不考虑可能存在的操作失败!并且,关于操作失败的设计,统统抛出异常!
当设计成这样,方法的调用者可能会:
try{
User user = userService.reg(XX);
//继续后续的操作
}catch(xx异常){
//处理操作异常
}catch(xx异常){
//处理操作异常
}
所以,方法的调用者既可以获取操作成功时的数据,并在此基础上继续编程,也可以知晓是否出现某种错误,并对错误进行处理!
关于异常,如果是自定义异常,必须继承自RuntimeException,或其子孙类异常
所以,还需要创建cn.tedu.project.service.ex.UsernameConflictException
,继承自
RuntimeException
。
然后,创建类cn.tedu.project.service.impl.UserServiceImpl
类,实现以上接口,并重写其中的抽象方法:
public User reg(User user) throws UsernameConflictException{
//根据用户名查询数据
//判断是否查询到有效数据(数据是否为null)
//是:用户名没有被占用,执行注册
//否:用户名已经被占用,抛出异常
}
关于业务层的方法
- 通常,业务层的方法,相对持久层的方法,只多,不少;
- 通常,每个持久层的方法,在业务层中,都有一个调用它的方法;
- 通常,业务层的方法,都是通俗易懂,简单易用的;
- 对于不允许外部直接访问的方法,应该声明为私有的,而允许外部直接访问的方法,应该在业务层接口中声明,然后再在实现类中实现;
- 在设计业务层方法时,返回值应该是操作成功的情况下返回的结果,而操作失败时,均抛出自定义异常。
# 1.5. 注册-控制器层
首先,设计请求:
请求路径:/user/handle_reg.do
请求类型:POST
请求参数:User
响应方式:ResponseResult
则,先创建cn.tedu.project.entity.ResponseResult
类,在其中声明Integer state
和String message
。
然后,在cn.tedu.project.controller.UserController
控制器类,该类应该是被SpringMVC所管理的,所以,需要添加@Controller
注解,并且,检查spring-mvc.xml
中的组件扫描的根包是否是cn.tedu.project.controller
。
然后,在该类的声明之前添加@RequestMapping("/user")
。
由于控制器中实现功能都依赖于业务层对象,所以,声明全局变量:
@Autowired
private IUserService userService;
然后,添加处理请求的方法:
@RequestMapping(value="/handle_reg.do",method=RequestMethod.GET)
@ResponseBody
public ResponseResult handleReg(User user){
//声明返回值
//try{
//调用业务层对象的reg()方法
//在返回值对象中封装state值为1
//} catch(XXXException e){
//在返回值对象中封装state值为0
//在返回值对象中封装message值为e.getMessage
//}
//返回
}
编写完成后的代码如下:
@RequestMapping(value="/handle_reg.do",method=RequestMethod.POST)
@ResponseBody
public ResponseResult handleReg(User user){
//声明返回值
ResponseResult rr = new ResponseResult();
try{
//调用业务层对象的reg()方法
userService.reg(user);
//在返回值对象中封装state值为1
rr.setState(1);
} catch(UsernameConflictException e){
//在返回值对象中封装state值为0
rr.setState(0);
//在返回值对象中封装message值为e.getMessage
rr.setMessage(e.getMessage());
}
//返回
return rr;
}
最后,请检查spring-mvc.xml
中是否添加了注解驱动:
<mvc:anotation-driven />
并且,在依赖中,必须有Jackson框架。
全部完成后,通过Http//localhost:8080/SSM-AJAX-01-SAMPLE/user/handle_reg.do?username=mybatis&password=1234
,如果测试无误,将处理请求的方法限制为:只允许提交POST请求。
# 1.6. 注册-前端页面
由于使用AJAX提交请求,只需要在前端页面发出并获取JSON结果即可,关于AJAX的处理流程,相对比较固定,核心代码是:
var url="user/handle_reg.do";
var data=$("form-id").serialize();//根据控件的name和
value拼接出参数数据
$.ajax({
"url":url,
"data":data,
"type":"POST",
"dataType":"json",
"success":function(json){
}
})
# 1.6. 登录-持久层
不用重新写,只需要检查findUserByUsername()
的返回结果中是否包含所需要的数据,如果没有,则在SQL语句中添加对应的字段!
即将原有的select id
调整为select id,username,passowrd
。
# 1.7. 登录-业务层
在接口中声明新的抽象方法:
User login(String username,String password) throws
UserNotFoundException,
PasswordNotMatchException;
然后,声明以上抛出的2个异常,他们都应该继承自RuntimeException
。
然后 ,在实现类实现以上方法:
User login(String username,String password) throws
UserNotFoundException,
PasswordNotMatchException{
//调用当前类fingUserByUsername获取用户数据
//判断是否获取到了用户数据
//是:用户名存在,判断密码是否匹配
//是:密码正确,返回用户数据
//否:密码错误,抛出异常
//否:用户名不存在,抛出异常
}
# 1.8. 注册-控制器层
首先,设计请求:
请求路径:/user/handle_login.do
请求类型:POST
请求参数:username,password,HttpSession
响应方式:ResponseResult
在cn.tedu.project.controller.UserController
添加处理请求的方法:
@RequestMapping(value="/handle_login.do",method=RequestMethod.GET)
@ResponseBody
public ResponseResult handleLogin(String username,
String password,HttpSession session){
//声明返回值
//try{
//调用业务层对象的login()方法,并获取返回值
//在session中封装必要的数据
//在返回值对象中封装state值为1
//} catch(XXXException e){
//在返回值对象中封装state值为0
//在返回值对象中封装message值为e.getMessage
//}
//返回
}
编写完成后的代码如下:
@RequestMapping(value="/handle_login.do",method = RequestMethod.POST)
@ResponseBody
public ResponseResult handleLogin(String username,
String password, HttpSession session) {
System.out.println("username:"+username);
System.out.println("password:"+password);
//声明返回值
ResponseResult rr = null;
try{
//调用业务层对象的login()方法
User result = userService.login(username, password);
//在session中封装比要的数据
session.setAttribute("uid", result.getId());
session.setAttribute("username", result.getUsername());
//在返回值对象中封装state值为1
rr = new ResponseResult(1);
} catch(UserNotFoundException e){
//在返回值对象中封装state值为0
//在返回值对象中封装message值为e.getMessage
rr = new ResponseResult(-1,e);
}catch(PasswordNotFoundException e){
//在返回值对象中封装state值为0
//在返回值对象中封装message值为e.getMessage
rr = rr = new ResponseResult(-2,e);
}
//返回
return rr;
}
以上代码需要添加ResponseResult
构造方法:
public ResponseResult() {
super();
}
public ResponseResult(Integer state) {
super();
this.state = state;
}
public ResponseResult(Integer state, Throwable e) {
super();
this.state = state;
this.message = e.getMessage();
}
全部完成后,通过Http//localhost:8080/SSM-AJAX-01-SAMPLE/user/handle_reg.do?username=mybatis&password=1234
,如果测试无误,将处理请求的方法限制为:只允许提交POST请求。
# 1.9. 登录-前端页面
由于使用AJAX提交请求,只需要在前端页面发出并获取JSON结果即可,关于AJAX的处理流程,相对比较固定,核心代码是:
$("#log_btn").click(function(){
var url = "user/handle_login.do";
var data = $("#log_form").serialize();
$.ajax({
"url":url,
"data":data,
"type":"POST",
"dataType":"json",
"success":function(json){
if(json.state==1){
alert("登录成功");
}else{
alert("登录失败,"+json.message);
}
}
});
});