SpringMVC第一部分

MVC( Model-View-Controller )是一种程序设计思想,通过分离模型、视图和控制器在应用中的角色实现解耦,MVC允许各个组分单独改变而不会相互影响


环境搭建

配置流程

配置依赖项与构建项pom.xml

<dependencies>
<!-- spring web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!-- spring mvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!-- web servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>

<build>
<plugins>
<!-- 编译环境插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- jetty插件 -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.27.v20200227</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<!-- 设置端口 -->
<httpConnector>
<port>8080</port>
</httpConnector>
<!-- 设置项目路径 -->
<webAppConfig>
<contextPath>/path</contextPath>
</webAppConfig>
</configuration>
</plugin>
</plugins>
</build>

配置web.xml

<web-app id="webApp_ID" version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

<!-- 编码过滤 utf-8 -->
<filter>
<description>char encoding filter</description>
<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>

<!-- servlet请求分发器 -->
<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:servlet-context.xml</param-value>
</init-param>
<!-- 表示启动容器时初始化该Servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<!-- 这是拦截请求, "/"代表拦截所有请求, "*.do"拦截所有.do请求 -->
<!-- <url-pattern>/</url-pattern> -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>

servlet请求分发由servlet-context.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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 开启扫描器 -->
<context:component-scan base-package="com.xxxx.springmvc.controller"/>

<!-- 使用默认的Servlet来响应静态文件 -->
<mvc:default-servlet-handler/>

<!-- 开启注解驱动 -->
<mvc:annotation-driven/>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!-- 前缀:在WEB-INF目录下的jsp目录下 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 后缀:以.jsp结尾的资源 -->
<property name="suffix" value=".jsp"/>
</bean>

</beans>

简单使用

编写页面控制器

@Controller
public class HelloController {

/**
* 请求映射地址 /hello.do
* @return
*/
@RequestMapping("/hello")
public ModelAndView hello() {
ModelAndView mv = new ModelAndView();
mv.addObject("hello", "hello spring mvc");
mv.setViewName("hello");
return mv;
}
}

WEB-INF目录下新建jsp文件夹,新建hello.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath =
request.getScheme() + "://" +
request.getServerName() + ":" +
request.getServerPort() + path + "/";
%>
<!DOCTYPE HTML>
<html>
<head>
<base href="<%=basePath %>">
<title>My JSP 'hello.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<!-- EL表达式接收参数值 -->
${hello}
</body>
</html>

启动jetty服务器即可


URL地址映射

通过注解@RequestMapping 将请求地址与方法进行绑定,可以在类级别和方法级别声明。类级别的注解负责将一个特定的请求路径映射到一个控制器上,将url和类绑定;通过方法级别的注解可以细化映射,能够将一个特定的请求路径映射到某个具体的方法上,将url和类的方法绑定

映射单个URL

使用@RequestMapping("")或指定value属性

@RequestMapping("/test01")
// @RequestMapping(value = "/test01")
public ModelAndView test01() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("hello", "test01");
modelAndView.setViewName("hello");
return modelAndView;
}

映射多个URL

使用@RequestMapping("","")或指定value为数组类型即可

@RequestMapping({"/test03_01", "/test03_02"})
// @RequestMapping(value = {"/test03_01", "/test03_02"})
public ModelAndView test03() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("hello", "test03");
modelAndView.setViewName("hello");
return modelAndView;
}

映射URL在控制器上

提供了一种层级化的访问方式,需要先访问类层级路径才能访问方法层级

@Controller
@RequestMapping("/url")
public class UrlController {
@RequestMapping("/test04")
public ModelAndView test04() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("hello", "test04");
modelAndView.setViewName("hello");
return modelAndView;
}
}

此时要访问到test04()方法需要访问/url/test04才可

设置URL映射请求格式

可以通过method属性设置支持的请求方式,如method=RequestMethod.POST如设置多种请求方式,以大括号包围,逗号隔开即可

@RequestMapping(value = "/test05", method = RequestMethod.POST)
public ModelAndView test05() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("hello", "test05");
modelAndView.setViewName("hello");
return modelAndView;
}

通过参数名称映射URL

设置params属性,该方法要求客户端通过特定的参数去访问

@RequestMapping(params = "test06")
public ModelAndView test06() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("hello", "test06");
modelAndView.setViewName("hello");
return modelAndView;
}

参数绑定

用于将后端使用的参数与前端传入的参数进行绑定,使用@RequestParam注解放在形参前表示绑定

该注解的name属性可与前端的的参数名进行绑定,defaultValue属性用于设置默认值,required用于明确该参数是否必须,请求中若没有该参数会向客户端抛出400错误

基本类型

基本类型传递时如果为空,可能会产生空指针异常

public void test(@RequestParam(name="age", defaultValue=0) int num) {}

包装类型

使用包装类型的优点在于其默认值为null,此时可以不设置defaultValue属性

public void test(Integer num) {}

可以不使用注解,但还是感觉用了会使代码逻辑更清晰

public void test(@RequestParam(name="age", defaultValue=0) Integer num) {}

数组类型

public void test(@RequestParam(name="age") Integer[] num) {}

适用于客户端传递数组参数,类似age=12 &age=18 &age=22

JavaBean类型

public void test(User user) {}

bean类型参数不适用@RequestParam注解,客户端传递该对象的属性参数,若该User类有idrole两个属性,客户端传入类似id=1 &role=admin

集合类型

对于集合类型,需要先传入一个JavaBean类型,后端再通过JavaBean对象中的属性进行接收

List列表

若传递元素为基本类型,在bean中设置集合属性

public class Test {
private List<Integer> list;

public List<Integer> getList() {
return list;
}

public void setList(List<Integer> list) {
this.list = list;
}
}

接收test对象

public void test(Test test) {}

客户端发送的参数需为list=1 &list=2 &list=3

若传递元素为JavaBean类型,在bean中设置集合属性

public class Test {
private List<User> list;

public List<User> getList() {
return list;
}

public void setList(List<User> list) {
this.list = list;
}
}

客户端发送的请求则应为list[0].id=1 &list[0].role=admin &list[1].id=2 &list[1].role=user

Set集合

绑定Set数据时,必须先在Set对象中添加相应数量的模型对象

public class Test {
private Set<User> set = new HashSet<User>();

public Test() {
set.add(new User());
set.add(new User());
set.add(new User());
}

public Set<User> getList() {
return set;
}

public void setList(Set<User> set) {
this.Set = set
}
}

Map集合

设置bean载体

public class test {
private Map<String,User> map =new HashMap<>();

public Map<String, User> getMap() {
return map;
}

public void setMap(Map<String, User> map) {
this.map = map;
}
}

客户端发送的格式类似map['a'].id=1 &map['a'].role=admin &map['b'].id=2 &map['b'].role=user


请求域对象

ModelAndView

在先前的测试中

@Controller
public class HelloController {

/**
* 请求映射地址 /hello.do
* @return
*/
@RequestMapping("/hello")
public ModelAndView hello() {
ModelAndView mv = new ModelAndView();
mv.addObject("hello", "hello spring mvc");
mv.setViewName("hello");
return mv;
}
}

这里的mv就是一个请求域对象,它是要渲染的内容的载体,而返回值用于明确要渲染的模板。由于ModelAndView类可以用setViewName()方法指定要渲染的模板,所以可以直接返回对象,而除此之外还有许多方式可以达到一样的效果

ModelMap

ModelMap类使用addAttribute()方法设置jsp中的引用名attributeName和渲染内容attributeValue

@RequestMapping("/hello")
public String hello(ModelMap modelMap) {
modelMap.addAttribute("hello","Model");
return "hello";
}

Model

Model类的使用方法与ModelMap几乎一致

@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("hello","ModelMap");
return "hello";
}

Map

由于map没有addAttribute()方法,但put()方法也可以实现一样的功能

@RequestMapping("/hello")
public String hello(Map map) {
Map.put("hello","Map");
return "hello";
}

HttpServletRequest

@RequestMapping("/hello")
public String hello(HttpServletRequest request) {
request.setAttribute("hello", "HttpServletRequest");
return "hello";
}

其实以上方法究其根本还是把属性放入了一次请求HttpServletRequest的 作用域


重定向与请求转发

重定向

返回值以redirect:开头会重定向到相应的jsp页面

@RequestMapping("/hello")
public String hello(HttpServletRequest request) {
return "redirect:halo";
}

这样会重定向到halo这个Controller,此时会再次建立一个HttpServletRequest,也可以传递参数比如redirect:halo?test=1

也可以通过RedirectAttributes类设置重定向的传递参数,该方法可以解决传参含中文乱码的问题

@RequestMapping("/hello")
public String hello(RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute("测试", "test");
return "redirect:halo";
}

ModelAndView类也可以实现

@RequestMapping("/hello")
public String hello(ModelAndView modelAndView) {
modelAndView.addObject("测试", "test");
modelAndView.setViewName("redirect:halo");
return modelAndView;
}

请求转发

请求转发直接调用跳转的页面,此时不会销毁前一次建立的HttpServletRequest,不存在丢失数据的问题,请求转发使用forward:前缀,也可以不使用,spring默认使用请求转发

@RequestMapping("/hello")
public String hello(HttpServletRequest request) {
return "forward:halo";
}

请求转发也可以传递参数,并且此时可以直接传中文


JSON

JSON作为一种数据传输格式在实际开发中获得了良好的应用,可以利用其直接返回对象、集合等类型

@ResponseBody注解

该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区,返回的数据不是 html 标签的页面,而是其他某种格式的数据时(如 json、xml 等)使用(通常用于ajax 请求)

@RequestBody注解

该注解用于读取 Request 请求的 body 部分数据,使用系统默认配置的 HttpMessageConverter 进行解析,然后把相应的数据绑定到要返回的对象上,再把 HttpMessageConverter 返回的对象数据绑定到 controller 中方法的参数上

配置

添加依赖

<!-- 添加json 依赖jar包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.10.0</version>
</dependency>

设置servlet-context.xml

<!-- mvc 请求映射 处理器与适配器配置 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>

使用

@ResponseBody注解的使用

可以将该注解标记在方法上

@RequestMapping("/test")
@ResponseBody
public User Test() {
User user = new User();
user.setId(1);
user.setName("admin");
return user;
}

或方法签名的访问修饰符右侧

@RequestMapping("/test")
public @ResponseBody User Test() {
User user = new User();
user.setId(1);
user.setName("admin");
return user;
}

此时访问/test路径会返回格式化的JSON文本,若没有引入该注解,该页面无法正常访问

通过Ajax接收JSON数据

前面的例子中,我们实现了后端返回JSON格式数据,但此时是直接打印在页面上的,想要前端接收数据并实现页面渲染需要使用一个接收器Ajax去接收JSON数据,要使用Ajax需要下载其js文件并在jsp文件中进行引入

ajax调用函数

function testAjax(){
$.ajax({
type:"POST",
url:"test",
data:{
"testParam":"testValue"
},
success: function(data){
console.log(data);
}
});
}

后端响应方法

@RequestMapping("/test")
@ResponseBody
public User testRes() {
User user = new User();
user.setId(1);
user.setUserName("admin");
return user;
}

调用testAjax()方法后,则会在浏览器控制台打印{id:1,userName:"admin"}

@RequestBody注解的使用

该注解用来处理content-typeapplication/json格式的字符串

可以将该注解标记在方法上

@RequestBody
public Void Test(User user) {
System.out.println(user);
}

或者标记在形参左侧

public Void Test(@RequestBody User user) {
System.out.println(user);
}

前端使用Ajax发送JSON数据

function testAjax(){
$.ajax({
type:"POST",
url:"test",
contentType:"application/json",
data:'{"id":1,"userName":"admin"}',
success: function(data){
console.log(data);
}
});
}