概述
- 实现MVC设计模式的框架
- Spring框架的分支,以Spring IoC容器为基础,用容器特性来简化配置
- 为Spring的子模块,不用和Spring整合,直接搞就行
MVC
- Model:数据库存取,获取模型数据
- Controller:调用业务模型
- View:展示模型,人机交互
总结起来就是:Controller接收客户端请求,调用Model生成业务数据,传递给View
核心组件
-
DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性【总指挥官】
-
Handler:处理器,完成具体的业务逻辑,相当于Servlet、Action…
-
HandlerMapping:DispatcherServlet 接收到请求后,通过HandlerMapping映射到不同的Handler
-
HandlerInterceptor:处理器拦截器,是个接口,做拦截处理
-
HandlerExecutionChain:处理器执行链,包括两部分:Handler 和 HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)
-
HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到JavaBean等,这些操作都是由HandlerAdapter来完成的,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet通过HandlerAdapter执行不同的Handler
-
ModelAndView:装载模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet
-
ViewResolver:视图解析器,DispatcherServlet通过它将逻辑视图解析为物理视图,最终将渲染结果相应给客户端
实际上我们开发者只要集中精力处理Handler
和View
就可以了
工作流程
-
客户端请求被DispatcherServlet接收
-
根据HandlerMapping映射到Handler
-
生成Handler 和 HandlerInterceptor
-
Handler 和 HandlerInterceptor 以 HandlerExecutionChain的形式一并返回给DispatcherServlet
-
DispatcherServlet 通过HandlerAdapter 调用 Handler 的方法完成业务逻辑处理
-
Handler 返回一个 ModelAndView 给 DispatcherServlet
-
DispatcherServlet 将获取的 ModelAndView 传给 ViewResolver 做视图解析
-
ViewResolver 返回一个View 给 DispatcherServlet
-
DispatcherServlet 根据 View 进行视图渲染(将Model模型数据填充到视图View中)
-
DispatcherServlet 将渲染的结果响应给客户端
流程复杂,但是开发简单,因为大部分不需要开发者创建、管理,只需要通过配置文件的方式完成配置即可
特点
- 清晰角色划分
- 灵活的配置功能
- 提供了大量的控制器接口和实现类
- 分离View层的实现
- 国际化支持
- 面向接口编程【策略模式】
启动项目
-
创建Maven工程,勾中archetype,选择archetype-webapp【看到BUILD SUCCESS才算成功】
- 创建的慢的话,把archetype-catalog.xml弄在本地
- 在conf/setting.xml里面配置一下景象
-
在main下创建java文件夹,选择Sources Root,放Java代码
-
在main下创建resources文件夹,选择Resources Root,放配置文件
-
配置pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
- 在web.xml中配置DispatcherServlet
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>dispatcherServlet</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>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 在resources下新建springmvc.xml,并配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"
default-autowire="byName">
<!-- 自动扫描 -->
<context:component-scan base-package="com.microsoft"></context:component-scan>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
- 创建Handler
package com.microsoft.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/hello") //访问的时候要"/hello/index"
public class HelloHandler {
@RequestMapping("/index")
public String index(){
System.out.println("执行了index...");
return "index";
}
}
- 配置Tomcat
- 选择好Tomcat
- Deployment下要选择+,勾选springmvc:war
注解
-
@RequestMapping:将URL请求与业务方法进行映射,在Handler的类定义处(前),方法定义处都可以添加(后),类定义处添加,相当于多一层访问路径
- value【默认值】:指定URL请求的实际地址
- method:指定请求的method类型(指定后只能接收这种请求),GET POST PUT DELETE
- params:制定请求中必须包含某些参数(可以是int之类的基本数据类型[HandlerAdapter完成数据类型转换],但是参数名要一致,实在不一样在传入参数前用@RequestParam),否则无法调用该方法
params = {"name","id=1"}
package com.microsoft.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/hello") public class HelloHandler { @RequestMapping(value = "/index",method = RequestMethod.GET,params = {"id"}) public String index(int id){ System.out.println(id); System.out.println("执行了index..."); return "index"; } }
-
@Contrller:在类定义处添加,将该类交给IoC容器管理,(结合xml中的自动扫描配合使用)同时使其成为控制器,可以接受客户端请求
-
@CookieValue:获取cookie的值(用的不多)
@RequestMapping("/cookie")
public String cookie(@CookieValue(value = "JSESSIONID") String sessionId){
System.out.println(sessionId);
return "index";
}
过滤器
- 解决中文乱码(不用自己写filter,在web.xml里配置)
<filter>
<filter-name>encodingFilter</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>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
数据绑定
-
定义:在后端的业务方法中直接获取客户端HTTP中的请求参数,将请求参数映射到业务方法的形参中
-
使用JavaBean绑定参数
-
根据请求参数名和JavaBean属性名进行自动匹配,自动为对象填充属性值,同时支持级联属性(对象套娃)
-
User实体类
package com.microsoft.entity;
import lombok.Data;
@Data
public class User {
private long id;
private String name;
private Address address;
}
- Address实体类
package com.microsoft.entity;
import lombok.Data;
@Data
public class Address {
private String name;
}
- register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>注册</title>
</head>
<body>
<form action="/hello/save" method="post">
用户id:<input type="text" name="id"/><br>
用户名:<input type="text" name="name"/><br>
地址:<input type="text" name="address.name"/><br>
<input type="submit" value="注册"/>
</form>
</body>
</html>
- JSP页面的转发和重定向
- SpringMVC默认转发
@RequestMapping("/forward")
public String forward(){
return "forward:/index.jsp";
// default: return "index";
}
- 重定向
@RequestMapping("/redirect")
public String redirect(){
return "redirect:/index.jsp";
}
- 基本数据类型
/*
@ResponseBody不当业务逻辑处理,直接将返回值响应给客户端;如果不加,则是给DispatcherServlet
*/
@RequestMapping("/baseType")
@ResponseBody
public String baseType(int id){
return id+"";
}
但存在一定问题,基本数据没有null,万一没传参就直接甩你500
,所以我们可以用包装类
- 包装类
@RequestMapping("/packageType")
@ResponseBody
public String packageType(Integer id){
return id+"";
}
解决异常了,但还有个保险的做法,我可以加个默认值,还可以指定参数名称(required
是是否必须填)
@RequestMapping("/packageType")
@ResponseBody
public String packageType(@RequestParam(value = "num",required = false,defaultValue = "0") Integer id){
return id+"";
}
- 数组
@RequestMapping("/array")
@ResponseBody
public String array(String[] name){
String str = Arrays.toString(name);
return str;
}
不难发现,每一个方法都有个@ResponseBody的注解,怎么优化呢???
直接在类定义处写一个@RestController就行了
package com.microsoft.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
@RestController
@RequestMapping("/data")
public class DataBindHandler {
/*
不当业务逻辑处理,直接返回
*/
@RequestMapping("/baseType")
public String baseType(int id){
return id+"";
}
@RequestMapping("/packageType")
public String packageType(@RequestParam(value = "num",required = false,defaultValue = "0") Integer id){
return id+"";
}
@RequestMapping("/array")
public String array(String[] name){
String str = Arrays.toString(name);
return str;
}
}
@RestController的优缺点鲜明!!!
优点:简单方便,如果只是单纯的数据返回显示
缺点:我们一般要返回的是视图,那就莫得法子了,我们还是得@Controller交给视图解析器
- List
SpringMVC不支持List的直接转换,需要定义包装类来包装
import lombok.Data;
import java.util.List;
@Data
public class UserList {
private List<User> users;
}
业务方法(StringBuffer资源优化)
@RequestMapping("/list")
public String list(UserList userList){
StringBuffer str = new StringBuffer();
for(User user : userList.getUsers()){
str.append(user.toString());
}
return str.toString();
}
JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加用户集合</title>
</head>
<body>
<form action="/data/list" method="post">
用户1编号:<input type="text" name="users[0].id"/><br>
用户1名称:<input type="text" name="users[0].name"/><br>
用户2编号:<input type="text" name="users[1].id"/><br>
用户2名称:<input type="text" name="users[1].name"/><br>
用户3编号:<input type="text" name="users[2].id"/><br>
用户3名称:<input type="text" name="users[2].name"/><br>
<input type="submit" value="注册"/>
</form>
</body>
</html>
在springmvc.xml中处理@ResponseBody乱码问题
<mvc:annotation-driven>
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
- Map
自定义封装类
package com.microsoft.entity;
import lombok.Data;
import java.util.Map;
@Data
public class UserMap {
private Map<String,User> users;
}
业务方法
@RequestMapping("/map")
public String map(UserMap userMap){
StringBuffer str = new StringBuffer();
for(String key : userMap.getUsers().keySet()){
User user = userMap.getUsers().get(key);
str.append(user);
}
return str.toString();
}
JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加用户map</title>
</head>
<body>
<form action="/data/map" method="post">
用户1编号:<input type="text" name="users['a'].id"/><br>
用户1名称:<input type="text" name="users['a'].name"/><br>
用户2编号:<input type="text" name="users['b'].id"/><br>
用户2名称:<input type="text" name="users['b'].name"/><br>
用户3编号:<input type="text" name="users['c'].id"/><br>
用户3名称:<input type="text" name="users['c'].name"/><br>
<input type="submit" value="注册"/>
</form>
</body>
</html>
- JSON(需要在webapp下创建js目录,并导入jQuery文件)
开始必然会404,是因为前置控制器DispatcherServlet把它拦截了,静态资源无法加载
解决方法:在web.xml中加入这串代码
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
var user = {
"id":1,
"name":"王帅真"
};
$.ajax({
url:"/data/json",
data:JSON.stringify(user),
type:"POST",
contentType:"application/json;charset=UTF-8",
dataType:"JSON",
success:function (data) {
alert(data.id+"----------"+data.name);
}
})
});
</script>
</head>
<body>
</body>
</html>
业务方法
@RequestMapping("/json")
public User json(@RequestBody User user){
System.out.println(user);
user.setId(6);
user.setName("张六");
return user;
}
我们会发现对象数据无法封装传回来,直接甩了415???
这时候我们要用工具啦!!!!!!——fastjson,JSON和JavaBean的转换就靠它
pom.xml中添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.32</version>
</dependency>
配置springmvc.xml
<mvc:annotation-driven>
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
<!-- 配置fastjson -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
模型数据解析
JSP 四大作用域对应的内置对象:pageContext、request、session、application
-
定义:把模型数据绑定到域对象里,再从域对象传到jsp,并解析出来
-
实现:添加模型数据后,交给ViewResolver来绑定
-
方式
- Map
- Model
- ModelAndView
- @SessionAttribute
- @ModelAttribute
-
Map
业务方法
@RequestMapping("/map")
public String map(Map<String,User> map){
User user = new User();
user.setId(1L);
user.setName("张三");
// 类似request的key,value
map.put("user",user);
return "view";
}
JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${requestScope.user}
</body>
</html>
- Model(和map及其相似,jsp内容同上)
@RequestMapping("/model")
public String model(Model model){
User user = new User();
user.setId(1L);
user.setName("张三");
model.addAttribute("user",user);
return "view";
}
- ModelAndView(前两者的结合,jsp同上)
// 方式一
@RequestMapping("/modelAndView")
public ModelAndView modelAndView(){
User user = new User();
user.setId(1L);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("view");
return modelAndView;
}
// 方式二
@RequestMapping("/modelAndView2")
public ModelAndView modelAndView2(){
User user = new User();
user.setId(1L);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
View view = new InternalResourceView("/view.jsp");
modelAndView.setView(view);
return modelAndView;
}
// 方式三
@RequestMapping("/modelAndView3")
public ModelAndView modelAndView3(){
User user = new User();
user.setId(1L);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView("view");
modelAndView.addObject("user",user);
return modelAndView;
}
// 方式四
@RequestMapping("/modelAndView4")
public ModelAndView modelAndView4(){
User user = new User();
user.setId(1L);
user.setName("张三");
View view = new InternalResourceView("/view.jsp");
ModelAndView modelAndView = new ModelAndView(view);
modelAndView.addObject("user",user);
return modelAndView;
}
// 方式五
@RequestMapping("/modelAndView5")
public ModelAndView modelAndView5(){
User user = new User();
user.setId(1L);
user.setName("张三");
Map<String,User> map = new HashMap<>();
map.put("user",user);
ModelAndView modelAndView = new ModelAndView("view",map);
return modelAndView;
}
// 方式六
@RequestMapping("/modelAndView6")
public ModelAndView modelAndView6(){
User user = new User();
user.setId(1L);
user.setName("张三");
Map<String,User> map = new HashMap<>();
map.put("user",user);
View view = new InternalResourceView("/view.jsp");
ModelAndView modelAndView = new ModelAndView(view,map);
return modelAndView;
}
// 方式七
@RequestMapping("/modelAndView7")
public ModelAndView modelAndView7(){
User user = new User();
user.setId(1L);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView("view","user",user);
return modelAndView;
}
像上面类似的组合还有很多很多…可以自己排列组合一下
- HttpServletRequest
@RequestMapping("/request")
public String request(HttpServletRequest request){
User user = new User();
user.setId(1L);
user.setName("张三");
request.setAttribute("user",user);
return "view";
}
- @ModelAttribute(定义一个方法,该方法专门用来返回要填充到模型数据中的对象)
// 先于业务方法执行,执行后就会向request中添加模型
@ModelAttribute
public User getUser(){
User user = new User();
user.setId(1L);
user.setName("张三");
return user;
}
// 业务方法中无需再处理模型数据,只需返回视图就行
@RequestMapping("/modelAttribute")
public String modelAttribute(){
return "view";
}
当然啦,也可以结合map
@ModelAttribute
public void getUser(Map<String,User> map){
User user = new User();
user.setId(1L);
user.setName("张三");
map.put("user",user);
}
可以map,就说明可以model
@ModelAttribute
public void getUser(Model model){
User user = new User();
user.setId(1L);
user.setName("张三");
model.addAttribute("user",user);
}
- 再看看session
@RequestMapping("/session")
public String session(HttpServletRequest request){
HttpSession session = request.getSession();
User user = new User();
user.setId(1L);
user.setName("张三");
session.setAttribute("user",user);
return "view";
}
还能简化一下
@RequestMapping("/session2")
public String session2(HttpSession session){
User user = new User();
user.setId(3L);
user.setName("张三");
session.setAttribute("user",user);
return "view";
}
当然啦,还能更简便!!!直接在类定义处淦@SessionAttributes(value = "user")
,相当于把对象塞进session里面。只要我在业务方法里面对value的值(可能是map的key)加载到域对象,那么就会进到session。
除此之外,还可以加载类@SessionAttributes(types = User.class)
,但尽量不要去用这类全局的,毕竟用request用的多,session的注释会造成一种资源浪费,用上述代码块就行了
- 当然啦,少不了最大的application
@RequestMapping("/application")
public String application(ServletContext application){
User user = new User();
user.setId(1L);
user.setName("张三");
application.setAttribute("user",user);
return "view";
}
哎呀??500了??为什么??
因为HandlerAdapter对ServletContext的创建要无参构造,而这边不是,我们只能去间接创建
@RequestMapping("/application")
public String application(HttpServletRequest request){
ServletContext application = request.getServletContext();
User user = new User();
user.setId(1L);
user.setName("张三");
application.setAttribute("user",user);
return "view";
}
自定义数据转换器
自定义数据转换器呀,就是将HTTP请求中的参数,转化为业务方法中定义的形参,自定义表示开发者可以自主设计转换的方式(HandlerAdapter已经提供了通用的转换,String转int,String转double,表单数据的封装)但是在特殊的业务场景下,HandlerAdapter无法转换,需要开发者自定义转换器。
客户端输入String类型的数据"2020-04-06",自定义转换器将该数据转为Date类型的对象
- 创建DateConverter转换器,实现Converter接口
package com.microsoft.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateConverter implements Converter<String, Date> {
private String pattern;
public DateConverter(String pattern){
this.pattern = pattern;
}
/*
String 转成 Date
*/
@Override
public Date convert(String s) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.pattern);
Date date = null;
try {
date = simpleDateFormat.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
- 在springmvc.xml配置转换器,并在mvc驱动中注册
<!-- 配置自定义转换器 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.microsoft.converter.DateConverter">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
</bean>
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService">
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
<!-- 配置fastjson -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/converter/date" method="post">
Please input the date:<input type="text" name="date"/>(yyyy-MM-dd)<br>
<input type="submit" value="submit"/>
</form>
</body>
</html>
- 业务方法
@RequestMapping("/date")
public String date(Date date){
return date.toString();
}
来看另一个例子,客户端输入String类型的数据"1-张三-18",自定义转换器将该数据转为Student类型的对象
- 首先写个转换器
package com.microsoft.converter;
import com.microsoft.entity.Student;
import org.springframework.core.convert.converter.Converter;
public class StudentConverter implements Converter<String, Student> {
@Override
public Student convert(String s) {
String[] args = s.split("-");
Student student = new Student();
student.setId(Long.parseLong(args[0]));
student.setName(args[1]);
student.setAge(Integer.parseInt(args[2]));
return student;
}
}
- springmvc.xml的配置
<!-- 配置自定义转换器 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.microsoft.converter.DateConverter">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
</bean>
<bean class="com.microsoft.converter.StudentConverter"></bean>
</list>
</property>
</bean>
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/converter/student" method="post">
请输入学生信息:<br>
<input type="text" name="student"/>(id-name-age)<br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
- 业务方法
@RequestMapping("/student")
public String student(Student student){
return student.toString();
}
RESTful架构
-
REST:Representational State Transfer,资源表现层状态转换
-
特点:结构清晰、标准规范、易于理解、便于拓展
-
资源(Resource)
- 网络上的一个实体/具体信息(一首歌、一张图、一个视频等等)
- 可以用一个URI(统一资源定位符)指向它,每个资源都有一个特定的URI,获取即访问
-
URI比较
- 传统类型:http://localhost:8080/hello/index?name=zhangsan&id=1
- REST:http://localhost:8080/hello/index/zhangsan/1
-
数据绑定
@RequestMapping("/rest/{name}/{id}")
public String rest(@PathVariable("name") String name, @PathVariable("id") int id){
System.out.println(name);
System.out.println(id);
return "index";
}
通过 @PathVariable 完成请求参数与形参的映射
- 表现层(REST:Representation)
资源具体呈现形式(比如HTML、JSON、txt等等)
- 状态转换(State Transfer)
客户端如果希望操作服务器中的某个资源,就需要通过某种方式让服务端发生状态转换,而这种状态转换是建立在表现层之上的,所以叫“表现层状态转换”
-
特点
- URL更加简洁
- 有利于不同系统间的资源共享,只需要遵守一定的规范,不需要进行其他配置即可实现资源共享
-
使用(类似CRUD)
- GET 获取资源
- POST 新建资源
- PUT 修改资源
- DELETE 删除资源
看个例子
- Student实体类(保证自动扫描扫得到)
package com.microsoft.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private long id;
private String name;
private int age;
}
- CRUD接口
package com.microsoft.repository;
import com.microsoft.entity.Student;
import java.util.Collection;
public interface StudentRepository {
public Collection<Student> findAll();
public Student findById(long id);
public void saveOrUpdate(Student student);
public void deleteById(long id);
}
- CRUD实现类
package com.microsoft.repository.impl;
import com.microsoft.entity.Student;
import com.microsoft.repository.StudentRepository;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class StudentRepositoryImpl implements StudentRepository {
private static Map<Long,Student> studentMap;
static {
studentMap = new HashMap<>();
studentMap.put(1L,new Student(1L,"王帅真",20));
studentMap.put(2L,new Student(2L,"王帅假",21));
studentMap.put(3L,new Student(3L,"王丑真",22));
}
@Override
public Collection<Student> findAll() {
return studentMap.values();
}
@Override
public Student findById(long id) {
return studentMap.get(id);
}
@Override
public void saveOrUpdate(Student student) {
studentMap.put(student.getId(),student);
}
@Override
public void deleteById(long id) {
studentMap.remove(id);
}
}
- 再写业务方法
package com.microsoft.controller;
import com.microsoft.entity.Student;
import com.microsoft.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
@RestController
@RequestMapping("/rest")
public class RESTHandler {
@Autowired
private StudentRepository studentRepository;
@RequestMapping("/findAll")
public Collection<Student> findAll(){
return studentRepository.findAll();
}
}
哈哈,跑一下
是不是对象的中文字符乱码了???
诶,我不是配置了消息转换器了吗???
@GetMapping("/findAll")
public Collection<Student> findAll(HttpServletResponse response){
response.setContentType("text/json;charset=UTF-8");
return studentRepository.findAll();
}
只好手动自己设置咯…记住REST要写的规范一点,查询就是GET
- 补全CRUD(调试用postman)
package com.microsoft.controller;
import com.microsoft.entity.Student;
import com.microsoft.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
@RestController
@RequestMapping("/rest")
public class RESTHandler {
@Autowired
private StudentRepository studentRepository;
//@RequestMapping(value = "/findAll",method = RequestMethod.GET)
@GetMapping("/findAll")
public Collection<Student> findAll(HttpServletResponse response){
response.setContentType("text/json;charset=UTF-8");
return studentRepository.findAll();
}
@GetMapping("/findById/{id}")
public Student findById(@PathVariable("id") long id){
return studentRepository.findById(id);
}
@PostMapping("/save")
public void save(@RequestBody Student student){
studentRepository.saveOrUpdate(student);
}
@PutMapping("/update")
public void update(@RequestBody Student student){
studentRepository.saveOrUpdate(student);
}
@DeleteMapping("/deleteById/{id}")
public void deleteById(@PathVariable("id") long id){
studentRepository.deleteById(id);
}
}
文件上传/下载
单文件上传
- 底层是用Apache的fileupdate组件完成上传,springMVC对这种方式进行了封装
- 添加依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
- 配置springmvc.xml
<!-- 配置上传组件 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>上传</title>
</head>
<body>
<form action="/file/upload" method="post" enctype="multipart/form-data">
<input type="file" name="img"/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
-
在tomcat目录下的webapps\ROOT内创建一个file文件夹(在tomcat运行时创建,每次重启文件夹就没了)
-
业务方法
package com.microsoft.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Controller
@RequestMapping("/file")
public class FileHandler {
@PostMapping("/upload")
public String upload(MultipartFile img, HttpServletRequest request){
if(img.getSize()>0){
// 获取保存文件的绝对路径
String path = request.getServletContext().getRealPath("file");
// 获取上传文件名
String name = img.getOriginalFilename();
File file = new File(path,name);
try {
// IO流被封装了,其实就是把img的内容转给file
img.transferTo(file);
} catch (IOException e) {
e.printStackTrace();
}
}
return "upload";
}
}
我们如果要改进一下,上传后显示呢???
- 业务方法
@PostMapping("/upload")
public String upload(MultipartFile img, HttpServletRequest request){
if(img.getSize()>0){
// 获取保存文件的路径
String path = request.getServletContext().getRealPath("file");
// 获取上传文件名
String name = img.getOriginalFilename();
File file = new File(path,name);
try {
// IO流被封装了
img.transferTo(file);
// 保存上传之后的文件路径
request.setAttribute("path","/file/"+name);
} catch (IOException e) {
e.printStackTrace();
}
}
return "upload";
}
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>上传</title>
</head>
<body>
<form action="/file/upload" method="post" enctype="multipart/form-data">
<input type="file" name="img"/>
<input type="submit" value="上传"/>
</form>
<img src="${path}">
</body>
</html>
哎呀,图片404,怎么回事???
是因为tomcat这边默认是动态资源,而我们访问jpg是静态资源,那怎么做呢???
记得jQuery我怎么怎么操作的吗??对!!!就是web.xm
l中配置default
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
是不是就行了??
多文件上传
- 因为要在JSP中遍历,所以直接用JSTL
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
- 业务方法
@PostMapping("/uploads")
public String uploads(MultipartFile[] imgs, HttpServletRequest request){
List<String> files = new ArrayList<>();
for (MultipartFile img : imgs){
if(img.getSize()>0){
// 获取保存文件的路径
String path = request.getServletContext().getRealPath("file");
// 获取上传文件名
String name = img.getOriginalFilename();
File file = new File(path,name);
try {
// IO流被封装了
img.transferTo(file);
// 保存上传之后的文件路径
files.add("/file/"+name);
} catch (IOException e) {
e.printStackTrace();
}
}
}
request.setAttribute("files",files);
return "uploads";
}
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>上传</title>
</head>
<body>
<form action="/file/uploads" method="post" enctype="multipart/form-data">
file1:<input type="file" name="imgs"/><br>
file2:<input type="file" name="imgs"/><br>
file3:<input type="file" name="imgs"/><br>
<input type="submit" value="上传"/><br>
</form>
<c:forEach items="${files}" var="file">
<img src="${file}" width="300px">
</c:forEach>
</body>
</html>
下载
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>下载</title>
</head>
<body>
<a href="/file/download/1">1.jpg</a>
<a href="/file/download/2">2.jpg</a>
</body>
</html>
- 业务方法
@GetMapping("/download/{name}")
public void download(@PathVariable("name") String name, HttpServletRequest request, HttpServletResponse response){
if (name != null){
name += ".jpg";
String path = request.getServletContext().getRealPath("file");
File file = new File(path,name);
OutputStream outputStream = null;
if(file.exists()){
response.setContentType("application/forc-download");
response.setHeader("Content-Disposition","attachment;filename="+name);
try {
outputStream = response.getOutputStream();
outputStream.write(FileUtils.readFileToByteArray(file));
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
表单标签库
-
对表单的简化
-
业务方法
@GetMapping("/get")
public ModelAndView get(){
ModelAndView modelAndView = new ModelAndView("tag");
Student student = new Student(1L,"张三",18);
modelAndView.addObject("student",student);
return modelAndView;
}
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>学生信息</h1>
<!-- modelAttribute和模型数据的key对应 -->
<form:form modelAttribute="student">
学生ID:<form:input path="id"/><br>
学生姓名:<form:input path="name"/><br>
学生年龄:<form:input path="age"/><br>
<input type="submit" value="提交"/>
</form:form>
</body>
</html>
常用的表单标签
- form
<form:form modelAttribute="student">
渲染的是HTML中的<form>
,通过modelAttribute绑定模型数据
- input
<form:input path="id"/>
渲染的是HTML中的<input type="text">
,input绑定模型数据中的具体属性,通过path来绑定(支持级联)
<form:input path="address.name"/>
- password
<form:password path="password"/>
渲染的是HTML中的<input type="password">
- checkbox复选框(选中即默认勾选)
<form:checkbox path="hobby" value="读书"/>
渲染的是HTML中的<input type="checkbox">
,可以绑定boolean、数组和集合
若绑定boolean,true为选中,false为不选中
若绑定数组/集合,数组/集合中的元素等于checkbox的value值为选中,否则不选中【取交集】
但是前端代码较多,所以有了checkboxes
- checkboxes【多选】
<form:checkboxes items="${student.hobby}" path="selectHobby"/>
渲染的是HTML中的一组<input type="checkbox">
,是对<form:checkbox/>
的一种简化,需要结合items和path属性来使用,items为全部可选集合/数组【可选的全部选项】,path为默认选中集合/数组【打勾的】
- rabiobutton
<form:rabiobutton path="radioId" value=0/>
渲染的是HTML中的一个<input type="radio">
,绑定的数据与value相等就选中,否则不选
- rabiobuttons【单选】
<form:rabiobuttons items="${student.grade}" path="selectGrade"/>
渲染的是HTML中的一组<input type="radio">
,是对<form:radio/>
的一种简化,需要结合items和path属性来使用,items为全部可选选项【可选的全部选项】,path为默认选中的选项属性【打勾的】
- select 下拉框
<form:select items="${student.cities}" path="selectCity"/>
渲染的是HTML中的一个<select/>
,需要结合items和path属性来使用,items为全部可选选项【可选的全部选项】,path为默认选中的选项属性【选中的】,和rabiobuttons类似
- options
form:select
结合form:options
的使用,select只要定义个path,不写items,在select加个子标签options
所在城市:<form:select path="selectCity"/>
<form:options items="${student.cities}"></form:options>
</form:select>
- option
也是和select搭配,value相等就选中
所在城市:<form:select path="selectCity"/>
<form:option value="1">厦门</form:options>
<form:option value="2">福州</form:options>
<form:option value="3">泉州</form:options>
</form:select>
- textarea【一个比较大的文本输入框】
渲染的是HTML中的一个<textarea/>
,path绑定模型数据的属性值,作为文本输入域的默认值
- errors
处理错误信息,一般用在数据校验,比如密码输入错误要告知用户,要在前台显示出来,该标签需要和springmvc的验证器结合起来使用
数据校验
两种方式:1.Validator接口 2.使用Annotation JSR - 303标准
- 基于Validator接口的方式需要自定义Validator验证器,每一条数据的验证规则需要开发者手动完成
- 使用Annotation JSR - 303标准不需要自定义,用注解的方式可以直接在实体类中添加每个属性的验证规则(推荐这种,更方便)
基于Validator接口
前端+后端双重验证,从后台进入login界面,需要先数据绑定(这样才能从)
- 定义实体类
package com.microsoft.entity;
import lombok.Data;
@Data
public class Account {
private String name;
private String password;
}
- 自定义验证器 AccountValidator,实现Validator接口
package com.microsoft.validator;
import com.microsoft.entity.Account;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class AccountValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return Account.class.equals(aClass);
}
@Override
public void validate(Object o, Errors errors) {
ValidationUtils.rejectIfEmpty(errors,"name",null,"姓名不能为空");
ValidationUtils.rejectIfEmpty(errors,"password",null,"密码不能为空");
}
}
- 配置springmvc.xml
<!-- 基于Validator的配置 -->
<bean id="accountValidator" class="com.microsoft.validator.AccountValidator"></bean>
<mvc:annotation-driven validator="accountValidator"></mvc:annotation-driven>
- 控制器
package com.microsoft.controller;
import com.microsoft.entity.Account;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/validator")
public class ValidatorHandler {
// 创建空对象,传给前台,主要是要绑定一个模型数据
@GetMapping("/login")
public String login(Model model){
model.addAttribute("account",new Account());
return "login";
}
@PostMapping("/login")
public String login(@Validated Account account, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return "login";
}
return "index";
}
}
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<h1>用户信息</h1>
<form:form modelAttribute="account" action="/validator/login" method="post">
姓名:<form:input path="name"/><form:errors path="name"></form:errors><br>
密码:<form:input path="password"/><form:errors path="password"></form:errors><br>
<input type="submit" value="提交"/>
</form:form>
</body>
</html>
使用Annotation JSR - 303标准
-
不用谢验证器,注解就行
-
添加依赖,我们这里使用Hibernate Validator
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.6.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.3.2.Final</version>
</dependency>
- 实体类
package com.microsoft.entity;
import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Size;
@Data
public class Person {
@NotEmpty(message = "用户名不能为空")
private String username;
@Size(min = 6,max = 12,message = "密码是6-12位")
private String password;
}
- 业务方法
@GetMapping("/register2")
public String register(Model model){
model.addAttribute("person",new Person());
return "register2";
}
@PostMapping("/register2")
public String register(@Valid Person person, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return "register2";
}
return "index";
}
- springmvc.xml的配置(让注解生效即可,啥也不用写,但需要单独再写一次)
<mvc:annotation-driven></mvc:annotation-driven>
- JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>注册</title>
</head>
<body>
<h1>用户信息</h1>
<form:form modelAttribute="person" action="/validator/register2" method="post">
姓名:<form:input path="username"/><form:errors path="username"/><br>
密码:<form:password path="password"/><form:errors path="password"/><br>
<input type="submit" value="提交"/>
</form:form>
</body>
</html>
校验规则
-
@Null:被注解的元素必须为null
-
@NotNull:被注解的元素不能为null
-
@Min:被注解的元素必须是一个数字,且值必须大于等于指定的最小值
-
@Max:被注解的元素必须是一个数字,且值必须小于等于指定的最大值
-
@Email:被注解的元素必须是电子邮箱地址
-
@Pattern:被注解的元素必须符合对应的“正则表达式”
-
@Length:被注解的元素的大小必须在指定范围内
-
@NotEmpty:被注解的字符串的值必须非空
Null和Empty不是一个东西,String str = null,str是null,String str = “”,str不是null,但其值是空