# 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:提交的请求类型,例如getpost,该值不区分大小写;
  • dataType:服务器端响应的结果的数据类型,在服务器端响应时,响应头(Response Headers)中会包含Content-Type,确定了响应的数据类型,根据此处的类型,从而决定当前属性的值是jsonxmltext
  • 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.dodata应该是username=?&password=?,此处2个参数值都可以通过$("#username").val()此类语法获取输入框中的值,type属性为postdataType属性为json,最后,在success处理时,应该对obj.state进行判断,如果值为1,可通过location.href="index.html"进行跳转,如果值为-1或-2时,可以通过$("#username_hint").html(obj.message)进行提示。

为了避免有多余的提示信息残留在页面中,可以在点击事件的其实位置,就通过$("#username_hint").html("")直接清楚2个提示区域的内容!

# ----------------------

  1. 什么是AJAX 异步 JavaScript 和 XML,通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

  2. @ResponseBody注解 使用@ResponseBody添加在处理请求的方法之前,如果返回Strign类型,则表示返回值将成为响应正文,而不再是转发或重定向。

  3. 什么是json 一种轻量级的数据交换格式,是javascript默认的一种数据类型,每个属性的值,可以是数值、字符串、布尔,也可以是对象和数组

  4. json的语法特征:

  • 整个数据是使用大括号框住的;
  • 各组属性之间使用逗号分割;
  • 使用冒号分隔属性名称与属性值;
  • 属性名称使用双引号框住;
  • 如果属性值是字符串,也需要使用双引号框住;
  1. 使用JSON.parase(str)可以吧字符串str转换为JSON对象。

  2. 在服务端如何高效的返回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. 通常,业务层的方法,相对持久层的方法,只多,不少;
  2. 通常,每个持久层的方法,在业务层中,都有一个调用它的方法;
  3. 通常,业务层的方法,都是通俗易懂,简单易用的;
  4. 对于不允许外部直接访问的方法,应该声明为私有的,而允许外部直接访问的方法,应该在业务层接口中声明,然后再在实现类中实现;
  5. 在设计业务层方法时,返回值应该是操作成功的情况下返回的结果,而操作失败时,均抛出自定义异常。

# 1.5. 注册-控制器层

首先,设计请求:

请求路径:/user/handle_reg.do
请求类型:POST
请求参数:User
响应方式:ResponseResult

则,先创建cn.tedu.project.entity.ResponseResult类,在其中声明Integer stateString 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);
			}
		}
	});
});
Last Updated: 11/20/2024, 2:55:49 PM