Marco's Java【SpringMVC进阶(一) 之 Web开发模式RestFul】

前言

不同的编程语言可能有着不同的设计模式,比方说我们最熟悉的Java有二十三种设计模式(单例、工厂、适配器、桥接模式等等),我们今天要讲的开发模式RestFul并不属于这二十三种设计模式,就和AOP以及IoC一样,更多的是一种设计理念,或者说是一种架构思想,本节我们就来聊聊关于Web的开发模式RestFul

什么是RestFul

看到"rest",我的第一反应就是"rest"的意思,restful是"宁静的"意思,不知道为什么看到rest,我就想睡觉zzzz
但是仔细看,有三个字母是小写,那么他注定就是一个缩写英文单词啦~
rest即Representational State Transfer,表现层状态转化 ,互联网上资源(是服务)细化理解为一个url,如果访问某个资源通过http url访问。 我们把”资源”具体呈现出来的形式,叫做它的”表现层”(Representation)。
表现层其实是我们经常接触到的层面,它对用户展示的形式有:html、json、xml、pdf、图片。

由于http协议的限制,服务器和客户端不能实现主动通信,只能有客户端发起请求服务端响应请求,也就是将请求方法和参数都包含url,其实不管是不是使用到了RestFul设计模式,规则是永远不会变的,只不过RestFul制定了对url更加友好的规划,或者说是一种规范,这个规则将方法和资源分开看起来做的很简单,但是能够让我们更好的理解对于网络资源的操作。

那说了半天,到底什么是表现层状态转化 呢?
其实说白了,我们在网上看到的任何一个资源,都是一个独一无二的url,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是”表现层状态转化”。


RESTful开发中URL的规范
URL规范

用户登录:http请求方法设置为get
我们来看看对比下原始url和规范后的url,发现有几个地方都被缺省了,一个是.action,再者就是我们的属性名
原始url:http://www……/login.action?username=marco&password=123456
规范后的url:http://www…/item/marco/123456
如果这个还不明显,我们再看看几个例子

商品删除:http请求方法设置为delete

规范后的url:url:http://www…/item/100/86表示对100分类 下的86号商品进行删除
我们发现是不是通过这种方式,我们请求数据的url、删除数据的url、亦或者是修改数据的url都变得一致了!

rest向客户端发送数据

在请求时指定服务端给客户端响应的内容类型是什么?
实现:在请求时设置http头中Accept
对商品查询,以json格式展示:
rest设置:
url为:http://www…/item/101/1
客户端请求此url并且设置Accept为”applicatoin/json”

服务端处理方法:
接收请求,解析Accept,根据指定类型返回不同的内容格式。
如果解析到Accept为”applicatoin/json”,服务端就将内容转成json输出
如果解析到Accept为”applicatoin/xml”,服务端就将内容转成xml输出


使用SpringMVC+Json构建基于Restful风格的应用

理论总归是枯燥的,那我们还是拿案例来演示下加深理解吧~

第一步:导包
因为这次需要使用到Json,因此我们需要导入SpringMVC的解析Json的jar包(三个jackson的jar)
在这里插入图片描述
第二步:创建实体类Person

package com.marco.domain;

public class Person {
	
	private Integer id;
	private String name;
	private String address;
	
	public Person() {
		// TODO Auto-generated constructor stub
	}
	
	public Person(Integer id, String name, String address) {
		super();
		this.id = id;
		this.name = name;
		this.address = address;
	}


	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", address=" + address + "]";
	}
}

第三步:配置web.xml
前面两步就不需要我再说了吧~ 大家注意看web.xml的变化

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">
	<display-name>01_springmvc_hello</display-name>

	<!-- 编码过滤器 开始-->
	<filter>
		<filter-name>EncodeingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<!-- 注入编码类型 -->
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	
	<filter-mapping>
		<filter-name>EncodeingFilter</filter-name>
		<!-- <url-pattern>*.action</url-pattern> -->
		<servlet-name>springmvc</servlet-name>
	</filter-mapping>
	<!-- 编码过滤器 结束-->

	<!-- 配置前端控制器 开始 -->
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- 注入核心配置文件的地址 -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
		<!-- 启动时就要创建DispatcherServlet对象 -->
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	<!-- 配置前端控制器 结束 -->
</web-app>

这个区别不太好找,眼尖的朋友应该发现了这个文件和之间的有什么不同。
这个差别就是<url-pattern>/</url-pattern>,我们之前在里面配置的是.action,这里的/表示拦截所有,但是不包括.jsp这样的静态文件,关于为什么不能使用/*的原因呢,之前在 Marco’s Java【SpringMVC入门(二) 之 SpringMVC的入门配置及项目测试】中有讲解过,不懂得朋友可以去回顾下咯。

第四步:创建PersonController控制器

那在这里我将基本的请求类型都列举出来了,我们的请求的种类一般有四种: POSTGETDELETEPUT
大家也许对后面两种比较陌生,他们分别是删除和修改的意思。那肯定有人会有疑问了,我一个POST可以搞定所有的事情,怎么还来个DELETEPUT凑热闹呢?
那么我们接着往下面看,看完以后兴许会有结论了~

package com.marco.controller;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.marco.domain.Person;

@RestController
public class PersonController {
	/*创造10条数据,模拟后台JDBC获取数据的操作*/
	private static List<Person> persons=new ArrayList<>();
	static {
		for (int i = 1; i <=10; i++) {
			persons.add(new Person(i, "marco"+i, "武汉"+i));	
		}
	}
	
	/**
	 * 测试say,produces="text/html;charset=utf-8"主要是为了保证请求过来的参数不会乱码(针对请求中有中文)
	 */
	@RequestMapping(value="say/{name}", method= {RequestMethod.POST}, produces="text/html;charset=utf-8")
	public String say(@PathVariable("name")String name) {
		return "你好:"+name;
	}
	
	/**
	 * 根据id查询用户
	 */
	@RequestMapping(value= "person/{id}", method= {RequestMethod.GET})
	public Person getPerson(@PathVariable(value="id")Integer id) {
		Person p=null;
		for (Person person : persons) {
			if(id==person.getId()){
				p=person;
				break;
			}
		}
		return p;
		
	}
	
	/**
	 * 使用post添加用户
	 */
	@RequestMapping(value= {"person"}, method= {RequestMethod.POST})
	public String addPerson(Person person) {
		persons.add(person);
		println();
		return "success";
	}
	
	
	/**
	 * 根据id删除用户
	 */	
	@RequestMapping(value= {"person/{id}"}, method= {RequestMethod.DELETE})
	public String deletePerson(@PathVariable(value="id")Integer id) {
		Person p=null;
		for (Person person : persons) {
			if(id==person.getId()){
				persons.remove(person);
				break;
			}
		}
		println();
		return "success";
		
	}
	
	/**
	 * 使用PUT请求更新用户信息
	 */
	@RequestMapping(value= {"person"}, method= {RequestMethod.PUT})
	public String updatePerson(Person person) {
		for (Person p : persons) {
			if(p.getId() == person.getId()) {
				BeanUtils.copyProperties(person, p);
			}
		}
		println();
		return "success";
	}
	
	/**
	 * 批量查询
	 */
	@RequestMapping(value= {"person"}, method= {RequestMethod.PATCH})
	public List<Person> loadPerson(Person person) {
		return persons;
	}
	
	
	public void println() {
		for (Person person : persons) {
			System.out.println(person);
		}
	}
}

仔细看完上面的代码,比较之后,我们发现几个特点
特点一: RequestMapping请求映射中的value值,都是person或者person/{xxx}的形式
特点二: RequestMapping中多添加了一个属性method,并且使用RequestMethod枚举类型可以获取到不同的请求类型作为method的值
特点三: 当方法中的参数中包含基本数据包装类型的时候,都加上了注解@PathVariable

那我们一一的来分析讲解
先从第一个特点开始吧,还记得上面我们提供的一个原始url和规范后之后url对比的栗子吗?
原始url:http://www……/login.action?username=marco&password=123456
规范后的url:http://www…/item/marco/123456
再结合我们的注解中的这个值,我们是不是发现使用person/{userName}的形式,就相当于约定了页面上按url上的/分割的参数顺序的属性名称,我们在url上只用展现它的值,这样做是不是变相的让数据传输更加的安全了呢?并且,url也更加简洁了

那么特点二,使用RequestMethod枚举的中枚举参数对不同操作类型的方法加以区分,我们之前不是还遗留一个问题没有解决吗?
POST可以搞定所有的事情,为什么还需要使用DELETEPUT
其实大家应该看出来了,我们一个方法就对应了一个操作,而加上了DELETEPUT等标签后,就约束了我们的方法只能够接收请求方式为delete、put的请求
因此,可以我们可以使用不同的请求方式,来区分Controller处理器中的操作,而且看到上面的枚举,也能够轻易的区别每一个方法的作用是什么

那最后一个特点三,使用@PathVariable的原因是在Spring 3.0后,没有办法获取方法上的参数的名称,怎么说呢,大家使用最新版的sts也发现了,当我们使用一个类实现了接口,自动重写的方法中的参数的名称是arg0、arg1、arg2或者是param1、param2、param3等等,因此,我们可以使用@PathVariable的方式,约定方法中的参数的名称,这个就类似与我们之前在Mybatis中讲到的@Param的注解的使用方式,相信大家应该都能懂啦~

第五步:新建jsp页面测试RestFul应用

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
<input type="button" id="btn1" value="say">
<input type="button" id="btn2" value="得到id=2的Person 使用get方法">
<input type="button" id="btn3" value="添加Person 使用post方法">
<input type="button" id="btn4" value="删除id=4的Person 使用delete">
<input type="button" id="btn5" value="更新Person PUT">
<input type="button" id="btn6" value="全查询使用  PATCH">

<script type="text/javascript">
	$("#btn1").click(function(){
		$.ajax({
			url:'say/marco',
			type:'post',
			success:function(obj){
				alert(obj);
			},
			error:function(){
				alert("请求失败");
			}
		})
	});
	$("#btn2").click(function(){
		$.ajax({
			url:'person/2',
			type:'get',
			success:function(obj){
				alert(obj.name);
			},
			error:function(){
				alert("请求失败");
			}
		})
	});
	$("#btn3").click(function(){
		$.ajax({
			url:'person',
			type:'post',
			data:{
				id:11,
				name:'川普',
				address:'美国'
			},
			success:function(obj){
				alert(obj);
			},
			error:function(){
				alert("请求失败");
			}
		})
	});
	$("#btn4").click(function(){
		$.ajax({
			url:'person/4',
			type:'delete',
			success:function(obj){
				alert(obj);
			},
			error:function(){
				alert("请求失败");
			}
		})
	});
	$("#btn5").click(function(){
		$.ajax({
			url:'person',
			type:'put',
			data:{
				id:1,
				name:'普京',
				address:'俄罗斯'
			},
			success:function(obj){
				alert(obj);
			},
			error:function(){
				alert("请求失败");
			}
		})
	});
	$("#btn6").click(function(){
		$.ajax({
			url:'person',
			type:'patch',
			success:function(obj){
				alert(obj);
			},
			error:function(){
				alert("请求失败");
			}
		})
	});
</script>
</body>
</html>

我们来随便找几个测试下,比方说第二个,成功获取到了id=2的person的名字
在这里插入图片描述
再比方说第三个,是不是成功的添加了川普到我的队伍中?
在这里插入图片描述
在这里插入图片描述
还有一个问题差点忘了提了,就是关于PUT请求的问题,因为这个请求有bug,无法成功的执行该请求方式的request,因此Spring官方团队给到了一个补救的方案,就是在springmvc.xml中加上这个过滤器的配置

<filter>
	<filter-name>HttpPutFormContentFilter</filter-name>
	<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>HttpPutFormContentFilter</filter-name>
	<servlet-name>springmvc</servlet-name>
</filter-mapping>

那配置完成后,我们再来测试下
在这里插入图片描述
在这里插入图片描述
当然也是no problem的啦,不成功的代码我会放上来吗?哈哈哈,那这一节就讲到这里~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值