新建一个maven项目,并设置pom.xml文件,设置当前项目为web项目,将packaging的属性值设置为war方式,添加spring mvc的依赖包, spring-webmvc(4.3.6),分别添加插件,jdk和tomcat。
4.0.0
com
SpringMVC
1.0-SNAPSHOT
war
org.springframework
spring-webmvc
4.3.6.RELEASE
javax.servlet.jsp
jsp-api
2.2
provided
javax.servlet
javax.servlet-api
3.0.1
provided
jstl
jstl
1.2
junit
junit
4.12
org.apache.maven.plugins
maven-compiler-plugin
3.6.1
1.8
1.8
org.apache.tomcat.maven
tomcat7-maven-plugin
2.2
/
8081
在项目中添加web元素,webapp, WEB-INF以及web.xml,其中在web.xml里面要添加spring mvc的引入,添加DispatcherServlet,这个是spring mvc的核心的前端控制器,注意还要设置DispatcherServlet的contextConfigLocation,如果不设置该属性,则Spring MVC会自动的在WEB-INF下查找[servlet-name]-servlet.xml文件来作为SpringMVC的配置文件。
aaa
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-mvc.xml
aaa
/
配置Spring MVC的配置文件,classpath下的spring-mvc.xml文件,该文件在本案例中分别配置了视图解析器、消息资源、缺省servlet处理器、注解驱动器、上下文包扫描。
视图解析器:InternalResourceViewResolver, 该属性里可以分别配置前缀和后缀,为了保证程序的安全性,可以将页面放在/WEB-INF/view/下,所以前缀可以直接配置为该值。如果没有这方面的需求,不配置该属性,则前缀为/,代表的是webapp目录,后缀可以根据项目需要设置为.jsp或者.html
消息资源:ReloadableResourceBundleMessageSource,该bean的配置有一个要求,id必须叫做messageSource,Spring MVC框架会读取该id所对应的bean对象来读取资源配置文件,里面设置了basename属性,用作读取该文件,该文件的配置只需要文件名,不能加后缀,为了更好的实现国际化,我们可以在msg文件后面拼接语言和国家,比如msg_zh_CN, msg_en_US以及其他国家的语言均可以按照这种方式来设定。有些ide环境可能只认识resources,则可以将msg文件放入resources目录下,否则不同的ide环境找不到该文件
缺省servlet处理器:mvc:default-servlet-handler,该配置可以保证Spring MVC项目可以直接访问静态资源,比如可以直接访问index.html
注解驱动器:mvc:annotation-driven,该配置使得当前项目可以使用注解来完成配置。在控制器类之上,可以添加Controller注解,里面还有RequestMapping,GetMapping,PostMapping,PathVariable等注解,可以完成各自的功能
上下文的包扫描:context:component-scan,使用该配置,可以使得该basePackage所对应的包下的所有Component组件直接被扫描出来使用,前提是需要在类之上添加@Component注解,但是我们的Controller以及后面要用的Service和Repositoy也都是Component组件,所以可以直接被扫描出来进行使用
该配置文件还配置了两个bean,里面是name和class,那么要注意,name里对应的值是url,name里面允许存放特殊字符,因为路径字符串前面会有一个路径符号/,所以这里只能使用name而不能使用id,意思是该url请求发出来之后,会自动交给后面的控制器类来实现处理的功能,该控制器类是实现了Controller接口的类,该类中有一个返回值为ModelAndView对象的方法名为handlerRequest的包含HttpServletRequest和HttpServletResponse两个参数的方法。
ModelAndView对象是一个可以同时包含视图和模型对象的对象,但是在使用的过程中,有时候只需要显示页面,有时候可能在显示页面的同时,还需要数据的传递。
注意:Controller接口与Controller注解是两个不同的东西。
public class Emp {
private int eid;
private String name;
private double salary;
public Emp() { }
public Emp(int eid, String name, double salary) {
this.eid = eid;
this.name = name;
this.salary = salary;
}
public int getEid() { return eid; }
public void setEid(int eid) { this.eid = eid; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
@Override
public String toString() {
return "Emp{" +
"eid=" + eid +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
在controller包下新增EmpController类,用到的注解有Controller,RequestMapping,GetMapping, PostMapping,PathVariable
Controller,代表当前类是一个控制器类,注意,通过查看源码,我们发现该类也是一个Component,所以刚刚的配置包扫描可以直接扫描到当前类,并将其作为一个组件来使用
RequestMapping,请求映射,目的是将某一个请求,映射到具体方法之上。该注解可以使用在类之上,也可以使用在方法之上。如果类和方法都有该配置,那么访问该方法的时候,需要同时拼接类之上的路径和方法之上的路径才能够访问该具体的方法。该注解可以使用method来区分不同的请求,method = RequestMethod.POST,或者GET可以分别来处理post和get请求
PostMapping和GetMapping也代表请求映射,使用起来会更直观,分别代表处理post和get的请求方式,但是这俩属性只能用于spring 4.3之后的版本。
PathVariable:路径变量,可以用来做路径传参功能,该功能相对于问号传参更加方便,可以直接指定变量的数据类型,而无需再做数据类型的转换,也可以实现传入多个参数,/{abc}/{xyz},方法里面可以使用 public String getPath(@PathVariable int abc,@PathVariable String xyz)方式来接收。注意路径传参会多一级目录,要注意访问路径
该类中的updateEmp(Emp e)方法再特别说一下:该方法可以自动接收表单里面的数据并将其封装为一个Emp对象,注意表单中的控件名一定要和Bean中的Emp类的属性要完全一致,否则找不到某些属性,这个也是Spring MVC中非常便利的地方,可以省去类型转换和封装对象的过程
该类中的方法都参数均很灵活,在需要的地方添加参数就可以直接使用
该类中的方法都返回值为String的都代表最终的展示页面。如果带有redirect,则代表重定向,意思是重定向到某一个具体的请求。
一个控制器里可以同时存在相同的路径url但是是不同的请求方式
关于校验这里,第一个GetMapping("/saveEmp")代表以get方式请求该资源,里面写了一个ModelAndView对象,传了三个参数,第一个是viewname,视图名,拼接上前后缀可以得到真正的物理视图,来打开该物理视图所对应的页面,第二个参数为modelname,模型名,相当于给模型起名字,这里要注意,该模型名意识要被叫做bean对象的小写形式Emp(emp) ,第三个参数为modelObject,模型对象,将该对象通过模型名传递给第一个参数viewname所对应的页面,在那个页面中可以渲染该数据
关于校验的第二个PostMapping("/saveEmp"),该注解的意思是页面上的表单通过post请求将saveEmp的请求来在这里进行处理。该方法包含有三个参数,第一个是Emp对象,可以自动封装表单中的属性为Bean对象,第二个参数为BindingResult对象,该对象我们通过源码可以发现是Spring中的Errors的子接口,可以用来接收并存储错误信息,这个对象可以接收从EmpValidate校验类中产生的错误信息,存储以交给错误页面的f:errors标签来展示错误信息,第三个参数是Model对象,可以用来储存对象,目的是可以使的bean对象的错误数据进行回显
@Controller
@RequestMapping("/emp")
public class EmpController {
private IEmpService empService = new EmpServiceImpl();
/**
* 如果有了users请求,那么该方法会被调用,返回值为将来要渲染的页面
* @return
*/
@RequestMapping("/emps")
public String getUsersPage(Model model, HttpSession session){
List list = empService.getAllEmps();
model.addAttribute("list", list);
session.setAttribute("list", list);
return "emp.jsp";
}
@RequestMapping("/getEmpByEid")
public String getEmpByEid(HttpServletRequest request, Model model){
String seid = request.getParameter("eid");
int eid = seid == null ? -1 : Integer.parseInt(seid);
Emp emp = empService.getEmpByEid(eid);
model.addAttribute("emp", emp);
return "updateEmp.jsp";
}
//@RequestMapping(value = "/updateEmp", method = RequestMethod.POST)
@PostMapping("/updateEmp")
//public String updateEmp(HttpServletRequest request){
public String updateEmp(Emp e){
//System.out.println(request.getParameter("eid"));
System.out.println(e);
boolean flag = empService.updateEmp(e);
if(flag){
return "redirect:/emp/emps";
}
return "";
}
@GetMapping("/deleteByEid/{eid}")
public String deleteByEid(@PathVariable int eid){
//System.out.println(eid);
boolean flag = empService.deleteEmpByEid(eid);
if(flag){
return "redirect:/emp/emps";
}
return "";
}
@GetMapping("/saveEmp")
public ModelAndView saveEmp(){
return new ModelAndView("saveEmp.jsp", "emp", new Emp());
}
/**
* 完成表单中Emp对象的存储
* @param e 要存储的Emp对象
* @param errors,收集错误信息的对象
* @param model
* @return
*/
@PostMapping("/saveEmp")
public String saveEmp(Emp e, BindingResult errors, Model model){
/**
* 调用自己写好的校验类来完成对于Emp对象的校验
*/
EmpValidate ev = new EmpValidate();
ev.validate(e, errors);
if(errors.hasErrors()){
model.addAttribute("emp", e);
return "saveEmp.jsp";
}
return "redirect:/emp/emps";
}
}
IEmpService.java, Service接口
public interface IEmpService {
List getAllEmps();
Emp getEmpByEid(int eid);
boolean updateEmp(Emp emp);
boolean deleteEmpByEid(int eid);
}
EmpServiceImpl.java, service实现类
public class EmpServiceImpl implements IEmpService {
private IEmpDao empDao = new EmpDaoImpl();
@Override
public List getAllEmps() {
return empDao.getAllEmps();
}
@Override
public Emp getEmpByEid(int eid) {
return empDao.getEmpByEid(eid);
}
@Override
public boolean updateEmp(Emp emp) {
return empDao.updateEmp(emp);
}
@Override
public boolean deleteEmpByEid(int eid) {
return empDao.deleteEmpByEid(eid);
}
}
public interface IEmpDao {
List getAllEmps();
Emp getEmpByEid(int eid);
boolean updateEmp(Emp emp);
boolean deleteEmpByEid(int eid);
}
EmpDaoImpl.java dao的实现类,使用List模拟一套数据源,可以完成对于Emp对象的CRUD操作,注意如果服务器重新启动,则数据会恢复到最原始的状态
public class EmpDaoImpl implements IEmpDao {
private static List emps = new ArrayList<>();
static {
for(int i = 0; i < 20; i++){
emps.add(new Emp(i + 1, "name " + i, 8000 + i * 100));
}
}
@Override
public List getAllEmps() {
return emps;
}
@Override
public Emp getEmpByEid(int eid) {
return emps.get(eid - 1);
}
@Override
public boolean updateEmp(Emp emp) {
try{
emps.set(emp.getEid() - 1, emp);
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}
@Override
public boolean deleteEmpByEid(int eid) {
try{
emps.remove(eid -1 );
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}
}
EmpValidate.java,用来对于Emp做校验使用,有非空校验,有合法性校验。
/**
* 用来完成对于Emp类的校验,有非空校验,合法性校验
*
* 里面包含两个方法,supports和validate
* supports方法的意思是当前类用来对于哪个类实现校验
* Emp.class.isAssignableFrom(clazz);意思是完成对与Emp类的校验
* validate方法,完成真正的校验,一定是满足了supports方法之后才会进入该方法来进行校验
*/
public class EmpValidate implements Validator {
/**
* 指定当前类是否支持指定类型的校验
* @param clazz
* @return
*/
@Override
public boolean supports(Class> clazz) {
return Emp.class.isAssignableFrom(clazz);
}
/**
* 真正的校验方法
* @param target,校验对象
* @param errors,存储错误信息
*/
@Override
public void validate(Object target, Errors errors) {
Emp e = (Emp) target;
/**
*
* 非空校验
*
* 使用ValidationUtils工具类来实现对于某些非空字段的校验,该方法包含三个参数:
* 1. 错误对象,用来收集并存储错误信息
* 2. 要校验的字段
* 3. 错误码,在msg配置文件中配置的key信息
*/
ValidationUtils.rejectIfEmpty(errors, "eid", "emp.eid");
ValidationUtils.rejectIfEmpty(errors, "name", "emp.name");
ValidationUtils.rejectIfEmpty(errors, "salary", "emp.salary");
double salary = e.getSalary();
/**
* 合法性校验,使用errors对象的rejectValue()方法完成合法性校验,里面包含了两个参数
* 1. field,在哪个字段上完成校验
* 2. 错误码,会在msg文件中找到该key对应的错误信息
*/
if(salary < 0){
errors.rejectValue("salary", "emp.salary.invalidate");
}
}
}
/WEB-INF/view/emp.jsp文件,用来展示所有的员工信息的页面,该页面包含两个超链接,修改和删除,修改使用的时候问号传参,删除使用的是路径传参
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
user
this is users page.
eid
name
salary
manage
${e.eid}
${e.name}
${e.salary}
update delete
/WEB-INF/view/updateEmp.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
update Emp
this is emp update page.
/WEB-INF/view/saveEmp.jsp, 该页面要注意,引入了Spring MVC的form标签,表单使用的就是SpringMVC的form表单
f:form:使用的是Spring MVC的form标签,里面有一个属性叫做commandName,这个值是从后端传递过来的对象名,注意要与bean的小写方式一致
f:input类似于html中的input标签,但是将name换成了path,代表的是属性名
f:errors,这个标签可以用来展示如果当前表单有错误信息时,可以在对应的域之上进行回显,一般都被放在对应的f:input标签之后,用来描述该属性的错误信息
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="f" uri="http://www.springframework.org/tags/form" %>
Title
<%----%>
eid:
name:
salary:
RESTful风格
一、说明
REST表示 Representational State Transfer(表示性状态转换)。它是可以用来设计web services的框架,可以被不同的客户端调用。
REST是一种架构风格,其核心是面向资源,REST专门针对网络应用设计和开发方式,以降低开发的复杂性,提高系统的可伸缩性。
二、REST提出设计概念和准则
网络上的所有事物都可以被抽象为资源(resource)
每一个资源都有唯一的资源标识(resource identifier),对资源的操作不会改变这些标识
所有的操作都是无状态的 使用简单的HTTP协议来实现调用,而不是CORBA, RPC 或者 SOAP等负责的机制
三、RESTful API 设计指南
1、概述
网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备......)。 因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。
1.协议 API与用户的通信协议,总是使用HTTPs协议。
2.域名 应该尽量将API部署在专用域名之下。 https://api.example.com 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。 https://example.org/api/
3.版本(Versioning) 应该将API的版本号放入URL。 https://api.example.com/v1/ 另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。
4.路径(Endpoint) 路径又称"终点"(endpoint),表示API的具体网址。 在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词, 而且所用的名词往往与数据库的表格名对应。 一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
5.HTTP动词 对于资源的具体操作类型,由HTTP动词表示。 常用的HTTP动词 GET(SELECT):从服务器取出资源(一项或多项)。 POST(CREATE):在服务器新建一个资源。 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。 DELETE(DELETE):从服务器删除资源。 动词举例 GET /zoos:列出所有动物园 POST /zoos:新建一个动物园 GET /zoos/ID:获取某个指定动物园的信息 PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息) PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息) DELETE /zoos/ID:删除某个动物园 GET /zoos/ID/animals:列出某个指定动物园的所有动物 DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
6.过滤信息(Filtering) 如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。 常见的参数 ?limit=10:指定返回记录的数量 ?offset=10:指定返回记录的开始位置。 ?page=2&per_page=100:指定第几页,以及每页的记录数。 ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。 ?animal_type_id=1:指定筛选条件 参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。 比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
7.状态码(Status Codes) 服务器向用户返回的状态码和提示信息 常见的状态码 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 202 Accepted - []:表示一个请求已经进入后台排队(异步任务) 204 NO CONTENT - [DELETE]:用户删除数据成功。 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。 401 Unauthorized - [ ]:表示用户没有权限(令牌、用户名、密码错误)。 403 Forbidden - [] 表示用户得到授权(与401错误相对),但是访问是被禁止的。 404 NOT FOUND - [ ]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
8.错误处理(Error handling) 如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。 eg: { error: "Invalid API key" }
9.返回结果 针对不同操作,服务器向用户返回的结果应该符合以下规范。 GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档
10.Hypermedia API RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
11.其他 (1)API的身份认证应该使用OAuth 2.0框架。 (2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
2、返回数据类型
尽管没有限制必须返回的类型,但是一般基于Web services的Rest返回JSON或者XML作为响应。
客户端可以指定(使用HTTP Accept header)他们想要的资源类型吗,服务器返回需要的资源。 指明资源的Content-Type。
3、REST API
GET 方式请求 /api/user/ 返回用户列表
GET 方式请求 /api/user/1返回id为1的用户
POST 方式请求 /api/user/ 通过user对象的JSON 参数创建新的user对象
PUT 方式请求 /api/user/3 更新id为3的发送json格式的用户对象
DELETE 方式请求/api/user/4删除 ID为 4的user对象
DELETE 方式请求/api/user/删除所有user
4、Spring4 Rest 注解
@RestController 此注解避免了每个方法都要加上@ResponseBody注解。也就是说@RestController 自己戴上了 @ResponseBody注解,看以看作是 @Controller 和 @ResponseBody的结合体。
@RestController,表明该类的每个方法返回对象而不是视图。 它实际就是@Controller和@ResponseBody混合使用的简写方法。
@RequestBody 如果方法参数被 @RequestBody注解,Spring将绑定HTTP请求体到那个参数上。 如果那样做,Spring将根据请求中的ACCEPT或者 Content-Type header(私下)使用 HTTP Message converters 来将http请求体转化为domain对象。
@ResponseBody 如果方法加上了@ResponseBody注解,Spring返回值到响应体。如果这样做的话,Spring将根据请求中的 Content-Type header(私下)使用 HTTP Message converters 来将domain对象转换为响应体。
@ResponseBody的作用是将返回的对象放入响应消息体中 ResponseEntity 是一个真实数据.它代表了整个 HTTP 响应(response). 它的好处是你可以控制任何对象放到它内部。 可以指定状态码、头信息和响应体。它包含你想要构建HTTP Response 的信息。
@PathVariable 此注解意味着一个方法参数应该绑定到一个url模板变量[在'{}'里的一个]中 MediaType 带着 @RequestMapping 注解,通过特殊的控制器方法你可以额外指定,MediaType来生产或者消耗。
5、REST测试
POSTMAN测试 使用RestTemplate编写测试用例
HTTP GET : getForObject, getForEntity
HTTP PUT : put(String url, Object request, String…urlVariables)
HTTP DELETE : delete
HTTP POST : postForLocation(String url, Object request, String… urlVariables), postForObject(String url, Object request, ClassresponseType, String… uriVariables)
HTTP HEAD : headForHeaders(String url, String… urlVariables)
HTTP OPTIONS : optionsForAllow(String url, String… urlVariables)
HTTP PATCH and others : exchange execute
6、others
REST的优势 由于REST强制所有的操作都必须是stateless的,这就没有上下文的约束,如果做分布式,集群都不需要考虑上下文和会话保持的问题。
极大的提高系统的可伸缩性 Webservice选择 SOAP偏向于面向活动,有严格的规范和标准,包括安全,事务等各个方面的内容, 同时SOAP强调操作方法和操作对象的分离,有WSDL文件规范和XSD文件分别对其定义。
REST强调面向资源 只要我们要操作的对象可以抽象为资源即可以使用REST架构风格。
REST ful 应用问题 是否使用REST就需要考虑资源本身的抽象和识别是否困难,如果本身就是简单的类似增删改查的业务操作,那么抽象资源就比较容易,而对于复杂的业务活动抽象资源并不是一个简单的事情。
比如校验用户等级,转账,事务处理等,这些往往并不容易简单的抽象为资源。 其次如果有严格的规范和标准定义要求,而且前期规范标准需要指导多个业务系统集成和开发的时候,SOAP风格由于有清晰的规范标准定义是明显有优势的。我们可以在开始和实现之前就严格定义相关的接口方法和接口传输数据。