1. 概念入门
概念: springmvc是spring框架的控制层技术,核心是前端控制器 DispatcherServlet,负责管理和调用其它组件以处理用户的http请求,降低组件间的耦合度:
- 当浏览器请求符合前端控制器规则时,WEB服务器会将其转交给前端控制器。
- 前端控制器调用处理器映射器
HandlerMapping:- 根据请求URL找到某自定义Handler,将其和拦截器(若有)封装成执行链并返回。
- 前端控制器调用处理器适配器
HandlerAdapter:- 执行执行链中拦截器和Handler并返回一个ModelAndView对象。
- 前端控制器调用视图解析器
ViewResolver:- 将ModelAndView中的逻辑视图名拼接前后缀后解析为物理视图名,并返回View对象。
- 前端控制器根据View组装HTML页面响应给浏览器,浏览器渲染页面:
- 渲染:浏览器解析HTML,构建DOM树,整合CSS和JS,布局,绘制等过程。
z-image/一个核心三个组件.png
1.1 配通
流程: 添加 spring-webmvc 依赖:
- 在web.xml中配置前端控制器类
o.s.w.s.DispatcherServlet:- 使用
<init-param>注入contextConfigLocation=springmvc主配文件:- 默认加载
/WEB-INF/配对名-servlet.xml。
- 默认加载
- 使用
<url-pattern>设置前端控制器拦截规则,可设置多个:*.do/.action:仅拦截以.do或.action结尾的请求。/:拦截除JSP外的一切请求,静态资源请求需在web.xml中手动放行。/*:拦截包括JSP的一切请求,静态资源请求需在web.xml中手动放行,不建议。
- 使用
- 开发springmvc主配文件:
- 使用
<mvc:annotation-driven />驱动管理处理器映射器类和处理器适配器类。 - 使用
<context:component-scan />包扫描@Component。 - 管理视图解析器类:
o.s.w.s.v.InternalResourceViewResolver:- 注入
prifix以配置响应路径前缀,可选。 - 注入
suffix以配置响应路径后缀,可选。
- 注入
- 使用
- 开发动作类:添加
@Controller。 - 开发动作方法:添加
@RequestMapping设置方法路由,可省略两端/和.do后缀:- 修饰符必须
public,方法名随意。 - 若返回字符串,会自动拼接视图解析器前后缀并执行请求转发。
- 修饰符必须
- 测试:请求URL中不可省略
.do后缀(若有)。
源码: /springmvc4/
- res:
pom.xml
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--spring-webmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
- res:
WEB-INF/web.xml
<!--其他请求限定-->
<!-- <filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>-->
<!--dispatcherServlet-->
<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/springmvc.xml</param-value>
</init-param>
<!-- Tomcat版本支持-->
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!-- <url-pattern>*.action</url-pattern>-->
<url-pattern>/</url-pattern>
</servlet-mapping>
- res:
classpath:spring/springmvc.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--注解驱动:管理处理器映射器和处理器适配器-->
<mvc:annotation-driven/>
<!--管理视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/view/"/>
<property name="suffix" value=".html"/>
</bean>
<!--包扫描-->
<context:component-scan base-package="com.yap.controller"/>
</beans>
- src:
c.y.controller.StartController
package com.yap.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author yap
*/
@Controller
public class StartController {
@RequestMapping("/api/start")
public void start() {
System.out.println("start()...");
}
}
1.2 @RequestMapping
概念: 注解式处理器映射器会 @ResquestMapping 标记的方法进行映射和寻找:
value:设置1或N个方法路由,自动以类上的路由为前缀(若有):?:匹配一个字符,如/?/one。*:匹配任意字符,如/*/two。**:匹配多层路径,如/**/three。
method:限定1或N种请求方式,值为枚举类RequestMethod:RequestMethod.GET:仅接收GET请求,其余请求报错,其余同理。- 特殊请求限定方式参考
z-res/特殊请求限定.md。
params:限定1或N种请求参数,不满足直接报错:age/!age:必须有/没有name属性,值无所谓。age=18/age!=18:必须有age=18键值对,若有age则值必须不能为18。
源码: /springmvc4/
- src:
c.y.controller.RequestMappingController
package com.yap.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @author yap
*/
@RequestMapping("/api/request-mapping")
@Controller
public class RequestMappingController {
@RequestMapping({"value-a", "value-b"})
public String value() {
System.out.println("value()...");
return "success";
}
@RequestMapping("/**/user-*/?")
public String valueMatch() {
System.out.println("valueMatch()...");
return "success";
}
@RequestMapping(value = "/method", method = RequestMethod.GET)
public String method() {
System.out.println("method()...");
return "success";
}
@RequestMapping(value = "/params", params = {"!name", "age=18", "gender!=1"})
public String params() {
System.out.println("params()...");
return "success";
}
}
2. 接值
概念: 因默认值问题,不建议使用任何基本类型接值:
- Cookie参数:对方法参数使用
@CookieValue可以获取Cookie中指定key值信息。 - 请求头参数:对方法参数使用
@RequestHeader可以获取请求头指定key值信息。 - REST参数:对方法参数使用
@PathVariable可以获取方法路由中的指定占位符对应的信息:- 路由中需使用
{}标记占位符,对应请求URL的REST参数,与@PathVariable指定key值同名。 - REST参数和请求后缀如
.do写法不兼容。
- 路由中需使用
- 键值对简单参数:对方法参数使用
@RequestParam可以获取指定key的键值对请求参数:- 使用简单类型如
Integer/Double/Boolean/String等接收请求参数在同名时可省略注解。 - 使用布尔类型接收请求参数时可自动将
1/0转型为true/false。 - 使用数组类型如
Integer[]可批量接收同名请求参数。 - 使用实体类接收请求参数时,要求实体类属性和请求参数同名。
- 使用VO中实体类可接收格式为
VO中实体类属性名.key的请求参数。 - 使用VO中List可接收格式为
VO中List属性[下标].key的请求参数,注意GET请求不识别[]。
- 使用简单类型如
- POST请求中文乱码:在web.xml中配置编码过滤器
o.s.w.f.CharacterEncodingFilter:- 使用
<init-param>注入encoding=utf-8。
- 使用
以上注解均可使用
required和defaultValue设置必填和默认值。
源码: /springmvc4/
- src: `c.y.pojo
package com.yap.pojo;
import lombok.Data;
/**
* @Author Yap
*/
@Data
public class User {
private String name;
private Integer age;
private Boolean gender;
}
- src: `c.y.vo
package com.yap.vo;
import com.yap.pojo.User;
import lombok.Data;
import java.util.List;
/**
* @Author Yap
*/
@Data
public class UserVo {
private User user;
private Integer[] ids;
private List<User> users;
}
- src:
c.y.controller.ParamController
package com.yap.controller;
import com.yap.pojo.User;
import com.yap.vo.UserVo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
* @author yap
*/
@RequestMapping("/api/param")
@Controller
public class ParamController {
@RequestMapping("cookie-value")
public String cookieValue(
@CookieValue(value = "name", required = false, defaultValue = "admin") String cookieName) {
System.out.println("name: " + cookieName);
return "success";
}
@RequestMapping("request-header")
public String requestHeader(
@RequestHeader(value = "host") String requestHeaderHost) {
System.out.println("host: " + requestHeaderHost);
return "success";
}
@RequestMapping("path-variable/{id}/{age}")
public String pathVariable(
@PathVariable("id") Integer restA,
@PathVariable("age") Integer restB) {
System.out.println("id: " + restA);
System.out.println("age: " + restB);
return "success";
}
@RequestMapping("request-param")
public String requestParam(
@RequestParam("username") String name,
Integer age, Boolean gender, Integer[] ids,
User userA, User userB,
UserVo userVoA, UserVo userVoB) {
System.out.println("name: " + name);
System.out.println("age: " + age);
System.out.println("gender: " + gender);
System.out.println("ids: " + Arrays.toString(ids));
System.out.println("userA: " + userA);
System.out.println("userB: " + userB);
System.out.println("userVoA: " + userVoA);
System.out.println("userVoB: " + userVoB);
return "success";
}
}
3. 存值
概念: 动作方法形参中可直接使用原生请求和会话对象存值,除此外:
- 请求域存值:动作方法形参中可直接使用
ModelAndView/Model/ModelMap/Map实例:ModelAndView支持手动实例化,且可以获取Model/ModelMap实例。ModelAndView存值的生效前提是必须配合ModelAndView转页。
- 会话域存值:在类上使用
@SessionAttributes可将请求域中的指定属性上升到会话域:value="name":将请求域中名为name的属性复制到会话域,支持数组。types=String.class:将请求域中所有String类型的属性复制到会话域,支持数组。
- 前置方法:
@ModeAttribute标记的方法会在本类所有业务方法前执行:- 前置方法可直接获取请求参数,返回值
void。 - 前置方法和业务方法处于同次请求中,且和所有业务方法共享请求域。
- 前置方法可直接获取请求参数,返回值
源码: /springmvc4/
- src:
c.j.controller.ScopeController
package com.yap.controller;
import com.yap.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;
/**
* @author yap
*/
@SessionAttributes(value = {"name", "info"}, types = Integer.class)
@RequestMapping("/api/scope")
@Controller
public class ScopeController {
@RequestMapping("request-scope")
public ModelAndView requestScope(
HttpServletRequest req, ModelAndView mv, Model model,
ModelMap modelMap, Map<String, Object> map) {
req.setAttribute("key-request", "value-request");
mv.addObject("key-mv", "value-mv");
model.addAttribute("key-model", "value-model");
modelMap.addAttribute("key-model-map", "value-model-map");
map.put("key-map", "value-map");
// forward:避免拼接前后缀
mv.setViewName("forward:/view/request-value.jsp");
return mv;
}
@RequestMapping("session-scope")
public ModelAndView sessionScope(HttpSession session, ModelAndView mv) {
session.setAttribute("id", "9527");
mv.addObject("name", "admin");
mv.addObject("gender", 1);
mv.addObject("age", 18);
mv.addObject("info", "管理员");
mv.setViewName("forward:/view/session-value.jsp");
return mv;
}
@RequestMapping("model-attribute")
public void modelAttribute(User user, Map<String, Object> map) {
System.out.println("modelAttribute(): " + user);
System.out.println("map: " + map.get("key"));
}
@ModelAttribute
public void before(User user, Map<String, Object> map) {
System.out.println("before(): " + user);
map.put("key", "before()");
}
}
- web:
view/request-value.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<section>
<h1>request域测试结果</h1>
<p>request: <%=request.getAttribute("key-request")%>
<p>request: <%=request.getAttribute("key-mv")%>
<p>request: <%=request.getAttribute("key-model")%>
<p>request: <%=request.getAttribute("key-model-map")%>
<p>request: <%=request.getAttribute("key-map")%>
</section>
</body>
</html>
- web:
view/session-value.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<section>
<h1>session域测试结果</h1>
<p>request: <%=request.getAttribute("id")%>
<p>request: <%=request.getAttribute("name")%>
<p>request: <%=request.getAttribute("gender")%>
<p>request: <%=request.getAttribute("age")%>
<p>request: <%=request.getAttribute("info")%>
<p>session: <%=session.getAttribute("id")%>
<p>session: <%=session.getAttribute("name")%>
<p>session: <%=session.getAttribute("gender")%>
<p>session: <%=session.getAttribute("age")%>
<p>session: <%=session.getAttribute("info")%>
</section>
</body>
</html>
4. 转页
概念: 对视图解析器的 <bean> 注入 prefix/suffix 以配置某些响应路径的前/后缀:
- 返回
void:需配合原生请求/响应进行请求转发,重定向或IO回写,不拼接前后缀。 - 返回
String/ModelAndView:直接return请求转发路径或ModelAndView对象,拼接前后缀:forward::请求转发到页面或动作类,不拼接前后缀,支持查询串。redirect::重定向到页面或动作类,不拼接前后缀,不支持查询串。
- 返回JSON:添加
jackson-core/jackson-databind/jackson-annotations依赖:- 在主配文件中使用
<mvc:annotation-driven />驱动jackson框架。 - 对动作方法添加
@ResponseBody后返回的实体类和集合均可自动转为JSON格式。
- 在主配文件中使用
- 实体类属性注解:生效于将数据转为JSON字符串的过程中:
@JsonIgnore标记的属性不参与装换过程。@JsonFormat标记的属性可设置自定义格式,常用于日期数据。@JsonInclude(JsonInclude.Include.NON_NULL)标记的属性若为null则不参与序列化。@JsonProperty标记的属性可设置别名,以保护数据库字段隐私。
@ResponseBody返回中文字符串乱码:- 局部设置:设置
@RequestMapping的produces="text/html;charset=UTF-8。 - 全局设置:设置
<mvc:annotation-driven />添加子标签<mvc:message-converters >:- 添加内部
<bean class="o.s.h.c.StringHttpMessageConverter">。 - 内部
<bean>注入supportedMediaTypes=text/html;charset=UTF-8。
- 添加内部
- 局部设置:设置
源码: /springmvc4/
- res:
pom.xml
<!--jackson-core-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.7</version>
</dependency>
<!--jackson-annotations-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.7</version>
</dependency>
<!--jackson-databind-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
</dependency>
- res:
spring/springmvc.xml
<!--配置@ResponseBody直接返回字符串的编码-->
<!--
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
-->
- src:
c.y.controller.ForwardController
package com.yap.controller;
import com.yap.pojo.Student;
import com.yap.util.JsonData;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author yap
*/
@RequestMapping("/api/forward")
@Controller
public class ForwardController {
@RequestMapping("request")
public void requestForward(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("requestForward()...");
req.getRequestDispatcher("/api/forward/redirect-to-response-writer").forward(req, resp);
}
@RequestMapping("redirect-to-response-writer")
public void responseRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("responseRedirect()...");
resp.sendRedirect(req.getContextPath() + "/api/forward/response-writer");
}
@RequestMapping("response-writer")
public void responseWriter(HttpServletResponse resp) throws IOException {
System.out.println("responseWriter()...");
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print("response-writer");
}
@RequestMapping("string")
public String stringForward() {
System.out.println("stringForward()...");
return "forward:redirect-to-model-and-view";
}
@RequestMapping("redirect-to-model-and-view")
public String stringRedirect() {
System.out.println("stringRedirect()...");
return "redirect:/api/forward/model-and-view";
}
@RequestMapping("model-and-view")
public ModelAndView modelAndView(ModelAndView modelAndView) {
System.out.println("modelAndView()...");
modelAndView.setViewName("success");
return modelAndView;
}
@ResponseBody
@RequestMapping("response-body-json")
public JsonData responseBodyJson() {
System.out.println("responseBodyJson()...");
List<Student> students = new ArrayList<>();
Student zhaosi = new Student();
zhaosi.setId(1);
zhaosi.setName("赵四");
zhaosi.setBirthday(new Date(1_111_111_111L));
Student liuneng = new Student();
liuneng.setId(2);
liuneng.setBirthday(new Date(9_999_999_999L));
students.add(zhaosi);
students.add(liuneng);
return new JsonData(students);
}
@ResponseBody
@RequestMapping(value = "response-body-string", produces = "text/html;charset=utf-8")
public String responseBodyString() {
System.out.println("responseBodyString()...");
return "中文";
}
}
- src:
c.y.util.JsonData
package com.yap.util;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author yap
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonData implements Serializable {
private Object data;
private Integer status;
private String msg;
public JsonData(Object data) {
if (data != null) {
status = 200;
msg = "ok";
} else {
status = 500;
msg = "err";
}
this.data = data;
}
}
- src:
c.y.pojo.Student
package com.yap.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
* @author yap
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
@JsonIgnore
private static final long serialVersionUID = 1L;
@JsonProperty("primary-key")
private Integer id;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String name;
@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss", locale="zh", timezone="UTC")
private Date birthday;
}