1-2 构建web项目
1-2-1 创建普通的maven项目
1-2-2 添加依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.18.RELEASE</version>
</dependency>
1-2-3配置tomcat
1、给项目添加WEB特性
2、给项目设置打包
打包后的名字就是之后URL中的项目名
3、配置tomcat
1-3 SpringMVC代码初识
1-3-1 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
version="3.0">
<!--
监听器:监听Web容器上下文的创建,会去初始化Spring的ioc容器
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
给WEB上下文中添加属性
用于给上面那个监听器(ContextLoaderListener)提供Spring的XML配置文件路径
跟上面那个监听器是配套的
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</context-param>
<!--
前端控制器:所有的请求都会经过此控制器,然后通过此控制器分发到各个分控制器
总控本质上还是一个Servlet,因为SpringMVC底层就是使用Servlet编写的
-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 创建前端控制器的时候读取springmvc配置文件启动ioc容器 -->
<!--
跟上面那个很像,这个是提供SpringMVC容器的XML配置文件路径
当创建前端控制器的时候,会调用servlet的初始化方法,方法中会根据下面的属性读取到springmvc配置文件,启动ioc容器
现在Spring和SpringMVC是使用同一个文件,后面会分开。
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</init-param>
<!--
优先级;
Tomcat启动就创建此对象
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置拦截路径url,所有的请求都会被前端控制器拦截处理 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
1-3-2 配置application.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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 自动扫包 -->
<context:component-scan base-package="com.temp" />
<!-- mvc注解生效 -->
<mvc:annotation-driven />
<!-- 让SpringMVC不对静态资源做处理 -->
<mvc:default-servlet-handler />
<!--
处理器映射器
-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<!--
处理器适配器
-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<!--
视图解析器
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!-- 通过set方法,给这两个属性赋值 -->
<!-- 给URL中添加前缀 -->
<property name="prefix" value="/WEB-INF/" />
<!-- 给URL中添加后缀 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
1-3-3 配置controller类
// @Controller:把这个对象注入到Spring容器中
@Controller
public class MyController {
// @RequestMapping:指定映射路径
@RequestMapping(value = "/hello01")
public ModelAndView temp01() {
ModelAndView modelAndView = new ModelAndView();
// 添加参数
modelAndView.addObject("msg","hello hi1");
// 设置视图名称,就是要转到那个视图。
// 视图解析器会给传入的字符串加上前缀和后缀,形成完整的路径,然后再去解析。
modelAndView.setViewName("hello");
return modelAndView;
}
}
1-3-4 运行结果
1-3-3 配置hello.jsp文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${msg}
</body>
</html>
1-4 SpringMVC工作流程
1-4-1 SpringMVC组件解析
-
前端控制器:DispatcherServlet
用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由 它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。
注意:在前端控制器的doService方法中调用了一个核心方法doDispatch,这个方法也是前端控制器自己的方法,doDispatch方法中去调用自己的getHandler方法并传入一个对象,这个对象包含了URL;getHandler方法中会获取一个处理器映射器,处理器映射器通过URL获取到对应的执行链并返回;
获取到执行链后会调用自己的getHandlerAdapter方法获取到一个处理器适配器,处理器适配器会去调用handle方法,handle方法中会去执行执行链中的处理器,并且返回一个ModelAndView对象;
在执行handle方法之前执行链会去调用自己的applyPreHandle方法,就是用来遍历执行链中的拦截器数组并执行拦截方法,被其中一个拦截器拦截到,doDispatch核心方法就不会继续执行,直接return了,如果没有被拦截到,就会放行。
在执行handle方法之后执行链会去调用自己的applyPostHandle方法,就是用来遍历执行链中的拦截器数组,用来执行放行后的代码
放行后会调用自己的processDispatchResult方法,方法中会调用render方法,用来视图解析器来渲染modeAndView
执行链就是一个对象,里面封装了一个处理器对象,和多个拦截器对象(拦截器数组) -
处理器映射器:HandlerMapping HandlerMapping
负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的 映射方式,例如:配置文件方式,实现接口方式,注解方式等。 -
处理器适配器:HandlerAdapter
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理 器进行执行。 -
处理器:Handler
它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由 Handler 对具体的用户请求进行处理。 -
视图解析器:View Resolver
View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即 具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。 -
视图:View
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最 常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程 序员根据业务需求开发具体的页面
1-4-2 SpringMVC流程
下面两个都是一样的
1-5 三个上下文
-
什么是上下文对象
用来存储全局范围信息的对象,一个web应用只有唯一的一个上下文对象 -
上下文对象的创建时机
服务器启动的时候,为每一个web应用创建一个上下文对象 -
上下文对象的销毁时机
服务器关闭的时候,上下文对象就销毁了.
1-5-1 servletContext上下文
就是当一个web应用启动的时候,tomcat服务器(web容器)会为web应用创建一个唯一的全局上下文,这个上下文就是servletContext,为后面的Spring Ioc容器提供宿主环境。servletContext上下文是存放在web容器中的。
1-5-1 Spring上下文
web.xml中提供ContextLoaderListener监听器,当web容器(tomcat服务器)启动时,会触发servletContext上下文初始化事件,此时ContextLoaderListener监听器监听到这个事件后,会调用自己的初始化方法,用来创建Spring Ioc容器(Spring上下文)。这个上下文又被称为“根上下文”。其中web.xml中配置了【context-param】配置指定了spring的xml配置文件的路径,默认路径:/WEB-INF/applicationContext.xml
<!-- web.xml -->
<!--
监听器:监听Web容器上下文的创建,会去初始化Spring的ioc容器
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
给WEB上下文中添加属性
用于给上面那个监听器(ContextLoaderListener)提供Spring的XML配置文件路径
跟上面那个监听器是配套的
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</context-param>
Spring上下文创建好后,spring以【WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE】为属性的key,value就是Spring上下文对象,将其存储到servletContext上下文中。
1-5-1 Spring MVC上下文
可以有多个前端控制器,每个前端控制器都会创建一个独立的SpringMVC上下文。存放在ServletContext上下文当中。
<!-- web.xml -->
<!--
前端控制器:所有的请求都会经过此控制器,然后通过此控制器分发到各个分控制器
总控本质上还是一个Servlet,因为SpringMVC底层就是使用Servlet编写的
-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 创建前端控制器的时候读取springmvc配置文件启动ioc容器 -->
<!--
跟上面Spring的上下文配置路径很像,这个是提供SpringMVC容器的XML配置文件路径
当创建前端控制器的时候,会调用servlet的初始化方法,方法中会根据下面的属性读取到springmvc配置文件,启动ioc容器
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</init-param>
<!--
优先级;
Tomcat启动就创建此对象
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置拦截路径url,所有的请求都会被前端控制器拦截处理 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
1-6 重定向与请求转发
添加了重定向或请求转发的关键字,就不会走视图解析器。
视图解析器默认情况下是请求转发,会经过视图解析器,给uri加上视图解析器设置好的前缀和后缀后再转发。
如果是在前面加请求转发的关键字(forward)就不会经过视图解析器,而是直接转发
请求转发:forward:
重定向:redirect:
@Controller
public class MyController {
// 方法中写什么参数,当方法被调用时处理器适配器会传入相应的参数
// 里面也可以用来接收请求中的参数,但是参数名要和请求中的参数名一致,如果没有就是null
// 请求转发
@RequestMapping("/hi")
public String temp03(Model model) {
model.addAttribute("msg","hi");
return "forward:/hi01";
}
// 重定向
@RequestMapping("/hi01")
public String temp04(Model model) {
return "redirect:https://www.baidu.com";
}
}
1-7@requestMapping详解
@requestMapping中有8个属性:
1、name:可以用来当注释用,注释该方法或者类的作用
2、value:设置该方法或类的URL路径,可以有多个,别名:path(value和path是功能是一样的。)
3、path:设置该方法或类的URL路径,可以有多个,别名:value(value和path是功能是一样的。)
4、method:设置能接收哪种请求(如:POST、GET),可以有多个
5、params:设置请求参数中必须有这些参数,才让该方法处理。可以有多个
6、headers:设置请求中包含指定的请求头,才让该方法处理。可以有多个
7、consumes:指定请求中媒体的类型(Content-Type),如果不是,就拒绝执行该方法。例如:application/json、text/html等。可以有多个
8、produces:指定响应返回媒体的类型。可以有多个
如有多个,可以使用大括号{}包裹住,用逗号隔开。
1-7-1 @requestMapping的衍生注解
- @GetMapping:用于接收get请求
- @PostMapping:用于接收Post请求
- @PutMapping:用于接收修改请求
- @DeleteMapping:用于接收删除请求
1-8 uri模式匹配
uri模式匹配:就是给你一个uri,通过uri匹配到一个controller。
下面的通配符除了用在方法的uri上,类上的uri也是可以用的。
pathPattern比AntPathMatcher性能高一些
1-8-1 ?通配符
?:用于匹配路径中的一个字符,多一个字少一个都不行
@GetMapping(value = "/hi?1", name = "hi01")
public String hi01(Model model) {
model.addAttribute("msg","hi01被访问了!");
return "hello";
}
结果:
1-8-2 *通配符
*:用于匹配零个、一个或多个字符,但是不能匹配多个字段
如:a / *
可以匹配:a/abc、a/
不可以匹配:a/abc/abc
@GetMapping(value = "/hi02/*", name = "hi02")
public String hi02(Model model) {
model.addAttribute("msg","hi02被访问了!");
return "hello";
}
结果:
1-8-3 **通配符
**:用于匹配多个字段
@GetMapping(value = "/hi03/**", name = "hi03")
public String hi03(Model model) {
model.addAttribute("msg","hi03被访问了!");
return "hello";
}
结果:
1-8-4 {}参数通配符
{}:用于匹配一个字段,并且可以拿到这个字段的值
如果想拿到那段uri,要在方法参数中加上一个注解:@PathVariable 类型 参数名
参数名必须和通配符中的名字一样。
或者在@PathVariable中添加。如:@PathVariable(“通配符中的名字”) 类型 参数名
// 方式一
@GetMapping(value = "/{uri}/hi04", name = "hi04")
public String hi04(@PathVariable String uri, Model model) {
model.addAttribute("msg","hi04被访问了 uri:" + uri);
return "hello";
}
// ------------------------------------------------------------------
// 方式二
@GetMapping(value = "/{uri}/hi04", name = "hi04")
public String hi04(@PathVariable("uri") String lj, Model model) {
model.addAttribute("msg","hi04被访问了 uri:" + lj);
return "hello";
}
结果:
1-9 uri匹配规则优先级
当一个请求进来,uri是:/a/b/c
但是有多个可以controller进行匹配,这里就体现了优先级重要性。如:
/a/b/c //优先级第一
/a/b/? //优先级第二
/a/b/{uri} //优先级第三
/a/b/* //优先级第四
/a/b/** //优先级第五
1-9-1源码分析 (AntPathMatcher类中的静态内部类AntPatternComparator)
结构:PatternInfo是AntPatternComparator的内部类,
AntPatternComparator是AntPathMatcher中的静态内部类
静态内部类AntPatternComparator实现了Comparator比较接口。
通过debug发现,每一次请求,都会调用compare来获取最优先的controller的uri
protected static class AntPatternComparator implements Comparator<String> {
// 请求中的uri
private final String path;
// 构造方法,初始化uri
public AntPatternComparator(String path) {
this.path = path;
}
// 比较方法,用于比较两个controller中的uri,来判断用哪个uri优先级高
// pattern1:第一个controller中的uri
// pattern2:第二个controller中的uri
// 返回值:如果是0,代表两个uri相同
// 如果是1,代表优先匹配第二个uri(pattern2)
// 如果是-1,代表优先匹配第一个uri(pattern1)
public int compare(String pattern1, String pattern2) {
PatternInfo info1 = new PatternInfo(pattern1);
PatternInfo info2 = new PatternInfo(pattern2);
// isLeastSpecific方法用于判断info对象的uri是不是/**,如果两个都是就返回0
if (info1.isLeastSpecific() && info2.isLeastSpecific()) {
return 0;
// 如果info1对象的uri是/**,返回1,因为/**优先级最低
} else if (info1.isLeastSpecific()) {
return 1;
// 如果info2对象的uri是/**,返回-1,因为/**优先级最低
} else if (info2.isLeastSpecific()) {
return -1;
// 如果上面都不符合,执行下面代码
} else {
// 变量赋值,判断contrller的uri跟请求中的uri是否一致。
boolean pattern1EqualsPath = pattern1.equals(this.path);
boolean pattern2EqualsPath = pattern2.equals(this.path);
// 如果两个contrller的uri跟请求中的uri一致,返回0
if (pattern1EqualsPath && pattern2EqualsPath) {
return 0;
// 如果contrller1的uri跟请求中的uri一致,返回-1
} else if (pattern1EqualsPath) {
return -1;
// 如果contrller2的uri跟请求中的uri一致,返回1
} else if (pattern2EqualsPath) {
return 1;
// 判断两个的uri是不是以/**结尾,如果是,哪个uri的length长,哪个就优先
} else if (info1.isPrefixPattern() && info2.isPrefixPattern()) {
return info2.getLength() - info1.getLength();
// 如果info1是以/**结尾,并且info2中没有**通配符,就返回1
} else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {
return 1;
// 如果info2是以/**结尾,并且info1中没有**通配符,就返回-1
} else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {
return -1;
// 如果两个uri的权重不一样,两个权重相减,权重小的优先
// getTotalCount方法返回两个uri的权重
} else if (info1.getTotalCount() != info2.getTotalCount()) {
return info1.getTotalCount() - info2.getTotalCount();
// 如果两个uri的length变量不一样,两个长度相减,长度大的优先
} else if (info1.getLength() != info2.getLength()) {
return info2.getLength() - info1.getLength();
// 哪个uri中*通配符少,哪个就优先
// getSingleWildcards方法返回uri中有多少个*通配符
} else if (info1.getSingleWildcards() < info2.getSingleWildcards()) {
return -1;
// 哪个uri中*通配符少,哪个就优先
} else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {
return 1;
// 哪个uri中{}参数通配符少,哪个就优先
// 如果info1中{}参数通配符少与info2,返回-1
} else if (info1.getUriVars() < info2.getUriVars()) {
return -1;
} else {
// 如果info2的{}参数通配符个数小于info1,返回1,
// 否则两个的{}参数通配符个数相同
// 因为上面已经判断了一种可能,所以这里就可以判断出另外两个种情况
return info2.getUriVars() < info1.getUriVars() ? 1 : 0;
}
}
}
// 模式信息类
private static class PatternInfo {
// controller中的uri
@Nullable
private final String pattern;
// 记录有多少个{}参数通配符
private int uriVars;
// 有多少个*通配符
private int singleWildcards;
// 有多少个**通配符
private int doubleWildcards;
// 判断自己的uri是不是/**
private boolean catchAllPattern;
// 判断自己的uri是不是以/**为后缀结尾
private boolean prefixPattern;
// uri的长度
@Nullable
private Integer length;
// 模式信息类的构造方法
public PatternInfo(@Nullable String pattern) {
// 初始化uri
this.pattern = pattern;
// 判断uri是否为null
if (this.pattern != null) {
// 用于初始化其他变量
this.initCounters();
// 判断自己的uri是不是/**的路径,并赋值
this.catchAllPattern = this.pattern.equals("/**");
// 判断自己的uri是不是以/**为后缀结尾,并赋值
this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**");
}
// 如果有{}参数通配符,或者uri为null。length为0
// 没有{}参数通配符,就把uri的length赋值给当前对象的length
if (this.uriVars == 0) {
this.length = this.pattern != null ? this.pattern.length() : 0;
}
}
// 初始化计数器
protected void initCounters() {
// 用于记录当前遍历到第几个字符的下标
int pos = 0;
// 如果为false就结束方法
if (this.pattern != null) {
while(true) {
// 遍历uri的每一个字符
while(pos < this.pattern.length()) {
// 如果有{符号uriVars和pos就加1
if (this.pattern.charAt(pos) == '{') {
++this.uriVars;
++pos;
// 判断是不是*号
} else if (this.pattern.charAt(pos) == '*') {
// 判断pos后一个字符是不是*,就是判断是不是**通配符
// 如果后面一个是*,那么doubleWildcards加1,pos加2
if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') {
++this.doubleWildcards;
pos += 2;
// 判断是不是以.*为后缀结尾,如果不是,因为前面有个取反,所以singleWildcards和pos加1
// substring方法用于截取字符串中的一段
// 参数中只有一个int参数,参数代表字符的下标,代表截取下标和下标后面的所有字符
} else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) {
++this.singleWildcards;
++pos;
// 上面都不符合,pos加1
} else {
++pos;
}
// 既不是{号,也是不是*号,pos就加1,遍历下一个字符
} else {
++pos;
}
}
// 第二层while循环结束就结束方法
return;
}
}
}
// 下面都是get方法
public int getUriVars() {
return this.uriVars;
}
public int getSingleWildcards() {
return this.singleWildcards;
}
public int getDoubleWildcards() {
return this.doubleWildcards;
}
public boolean isLeastSpecific() {
return this.pattern == null || this.catchAllPattern;
}
public boolean isPrefixPattern() {
return this.prefixPattern;
}
// 获取uri的权重
public int getTotalCount() {
// {}参数通配符的数量 + *通配符的数量+(2 乘 **通配符的数量)
return this.uriVars + this.singleWildcards + 2 * this.doubleWildcards;
}
// 返回当前对象的length变量
public int getLength() {
// 如果length为null,会重新初始化length属性
// length为null的情况是因为uri中有{}参数通配符
// 通过下面的代码初始化。具体就是把{}参数通配符记为一个字符加进去。
if (this.length == null) {
this.length = this.pattern != null ? AntPathMatcher.VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0;
}
return this.length;
}
}
}
1-9-2 总结
AntPatternComparator静态内部类的比较顺序:
- ①两个处理器的uri都是/**,返回0。一个处理器的uri是/**,那么肯定是另一个处理器uri的优先级高,因为/**优先级最低
- ②两个处理器的uri和请求中的uri相同,返回0。两个处理器中其中一个uri和请求中的uri相同,相同的优先级高,完全匹配的优先级最高。
- ③两个处理器的uri都是以**结尾的,哪个uri的length长,哪个就优先。或者两个处理器的uri其中一个以**结尾,并且另一个uri中没有**通配符,那么没有**通配符的处理器优先
- ④上面都没有被匹配到,就会根据uri的权重来比较,权重小的优先。
权重计算方式:{}参数通配符的数量 + *通配符的数量+(2 乘 **通配符的数量)- ⑤如果uri的长度不一样,长度大的优先。
- ⑥uri中*通配符少的优先
- ⑦uri中{}参数通配符数量少的优先
1-10 Model对象
作用:作为数据流转的载体,SpringMVC官方提供的一个对象。
使用: 在单元方法上声明Model类型的形参即可。
特点:
请求转发:
model对象中存储的数据,相当于存储到了request对象中我们在jsp中直接按照request对象作用域取值的方式来获取数据即可。
重定向:
在重定向中,会将第一次请求中model对象的数据作为第二次请求的请求参数携带,第一次请求的model对象销毁。只能携带基本类型的数据。
1-11 请求中的参数
参数可能存在的位置:
- url中的参数
- 请求头中的参数
- 请求体里的参数
- cookie里面的参数
在处理请求的方法中添加参数,处理器映射器会去根据参数类型和参数名来匹配传入的数据。如果是Servlet里面的类(如:session对象),如果没有会直接创建一个赋值进去
如果匹配找不到,就赋值为null,如果是基本数据类型的参数就会报错,因为null不能赋给int类型,所以要使用基本数据类型的参数,必须使用基本数据类型包装类
// 运行下面的代码,需要把视图解析器中添加前缀和后缀的配置删掉
/*
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!-- <property name="prefix" value="/WEB-INF/" /> -->
<!-- <property name="suffix" value=".jsp" /> -->
</bean>
*/
@GetMapping("/hi01")
public String temp01(Model model, HttpServletRequest request, HttpServletResponse response, HttpSession session, Integer temp) {
request.setAttribute("name","张三");
session.setAttribute("age","age");
System.out.println(temp); // 因为前端没有传入以temp命名的参数,所以为null
return "/hi02";
}
@GetMapping(value = "/hi02")
public String hi04(Model model, HttpServletRequest request, HttpSession session) {
System.out.println(request.getAttribute("name"));
System.out.println(session.getAttribute("age"));
System.out.println("请求转发到hi02");
model.addAttribute("msg","请求转发到hi02");
return "/WEB-INF/hello.jsp";
}
结果:
1-11-1 一个个参数来接收
注意:参数名和请求中的参数名必须一致。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>用户登录</h1>
<form action="/app/login" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required><br>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required><br>
<input type="submit" value="登录">
</form>
</body>
</html>
@PostMapping("/login")
public String login(String username, String password, String email){
System.out.println("用户名:"+username);
System.out.println("密码:"+password);
System.out.println("邮箱:"+email); // 因为前端没有给数据,所以这里邮箱是null
return "/";
}
结果:
1-11-2 用对象接收参数
注意:用对象来接收参数,对象的变量名必须和请求中的参数名一致,且对象的变量必须有set方法。因为处理器适配器是通过调用对象的set方法赋值的。
@PostMapping("/login")
public String login(Login login){
System.out.println(login);
return "/";
}
public class Login {
private String username;
private String password;
// get和set方法太长就省略了
@Override
public String toString() {
return "Login{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
结果:
使用下面注解的注意事项
- 使用下面注解,当请求中没有方法参数中需要的参数,会给浏览器抛出异常。
解决:下面每个注解中,都有required属性,改成false,默认是true。
为true时,必须有这个参数,否会给浏览器抛出异常。
为false时,这个参数可有可无,不会报错
1-11-3 @RequestParam注解
当请求的参数名和方法的参数名不一致时,可以用@RequestParam注解来标识
在参数名一致的情况下,最好也加上@RequestParam注解,因为可以知道这个参数从哪里来。
@PostMapping("/login")
public String login(@RequestParam(value="username",required = false) String name,@RequestParam(value="password",required = false) String pass, String email){
System.out.println("用户名:"+name);
System.out.println("密码:"+pass);
System.out.println("邮箱:"+email);
return "/";
}
下面使用Postman来代替浏览器去请求
1-11-4 @RequestHeader注解
用于获取请求头中的参数
不管跟请求头中的参数名是否一致,都要加@RequestHeader,否则就不会去请求头中找;
Get请求会去URL中找,POST请求会去URL和请求体中找
@GetMapping("/header")
public String getHeader(@RequestHeader(value="Content-Type",required = false) String contentType,@RequestHeader String name){
System.out.println("请求头中name的参数:"+name);
System.out.println("请求头中的Content-Type参数:"+contentType);
return "/";
}
结果:
1-11-5 @Cookievalue注解
用于获取请求中cookie的属性
@GetMapping("/addCookie")
public String cookieValue(HttpServletResponse response){
Cookie cookie = new Cookie("name", "root");
response.addCookie(cookie);
return "/getValue";
}
@GetMapping("/getValue")
public String getValue(@CookieValue(value = "name",required = false) String value){
System.out.println("cookie中name属性:"+value);
return "/";
}
1-11-6 @SessionAttribute注解和 @RequestAttribute注解
@SessionAttribute注解和 @RequestAttribute注解分别去Sesion对象和request对象中去拿属性
@GetMapping("/addAttr")
public String addAttr(HttpServletRequest request, HttpSession session){
session.setAttribute("name","root");
request.setAttribute("pass","123456");
return "/getValue";
}
@GetMapping("/getValue")
public String getValue(@RequestAttribute(value = "pass", required = false) String password,
@SessionAttribute(value = "name",required = false) String username){
System.out.println("cookie中name属性:"+username);
System.out.println("request中pass属性:"+password);
return "/";
}
1-11-7 @ModelAttribute注解
作用:用来个Model对象赋值的。
两种用法:
- 放在方法上:放在方法上用来获取方法的返回值,赋值给model对象
- 放在形参上:放在形参上用来把从前端传入的数据解析成形参的类型,并传给Model对象
注意:请求转发中,放在Model上的参数,会放到Request对象中。
1-11-7-1 放在方法上
放在方法上面,必须配合视图解析器。
流程:通过URI对应到这个方法,这个方法运行完返回值,传给model对象属性为name。再通过视图解析器给URI加上前缀和后缀,展示到具体的页面上。
/*
<!--
视图解析器
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jsp" />
</bean>
*/
@RequestMapping("/hello")
@ModelAttribute("name")
public String modelAttribute() {
return "huang shao chun";
}
1-11-7-2 放在形参上
@RequestMapping("/model")
public String modelAttribute(@ModelAttribute("user") User user, Model model) {
System.out.println(model.getAttribute("user"));
System.out.println(user);
return "forward:/temp";
}
@RequestMapping("/temp")
public String temp(HttpServletRequest request) {
Object user = request.getAttribute("user");
System.out.println(user);
return "/";
}
<form action="model" method="post">
<label>Username:</label>
<input type="text" name="userName" />
<label>Password:</label>
<input type="password" name="password" />
<button type="submit">Submit</button>
</form>
结果:
1-11-8 @SessionAttributes注解
作用:将Model对象中的属性同步到Session当中去。
@SessionAttributes注解写在类上面
@Controller
@SessionAttributes({"temp","user"})
public class MyController {
@RequestMapping("/model")
public String modelAttribute(User user, Model model) {
// 这里添加数据到Model的时候,会同步到Session中
model.addAttribute("temp","temp");
// 这里添加数据到Model的时候,会同步到Session中
model.addAttribute("user",user);
return "forward:/temp";
}
@RequestMapping("/temp")
public String temp(@SessionAttribute(required = false) String temp) {
System.out.println(temp);
return "/";
}
}
<form action="model" method="post">
<label>Username:</label>
<input type="text" name="userName" />
<label>Password:</label>
<input type="password" name="password" />
<button type="submit">Submit</button>
</form>
注意
@SessionAttributes注解中的值不要和@ModelAttribute注解的值相同。会报HttpSessionRequiredException异常
@Controller
@SessionAttributes({"temp","user"})
public class MyController {
@RequestMapping("/model")
public String modelAttribute(@ModelAttribute("user") User user, Model model) {// 这里加了@ModelAttribute("user")
model.addAttribute("temp","temp");
model.addAttribute("user",user);
return "forward:/temp";
}
@RequestMapping("/temp")
public String temp(@SessionAttribute(required = false) String temp) {
System.out.println(temp);
return "/";
}
}
1-12 数组的传递
用于接收前端发送过来的数据
@RequestMapping("/list")
public String temp(@RequestParam(value = "ids",required = false) Long[] ids) {
System.out.println(Arrays.toString(ids));
return "/";
}
结果:
1-13 复杂参数传参
// Vo对象
@Data
public class TempVo {
private String sort;
private String[] ids;
private User user;
private List<User> userList;
private Map<String,User> userMap;
}
// Controller类中的方法
/*
如果用对象接收参数,就不能使用@RequestParam注解,
因为属性都被封装到类里面,所以类里面的属性名必须跟请求参数中的参数名一致。
*/
@RequestMapping("/tempVo")
public String temp(TempVo tempVo) {
System.out.println(tempVo);
return "/";
}
<form method="post" action="/app/tempVo">
排序<br><!-- 注意name的名字,名字跟VO类中的属性名保持一致。 -->
<input type="text" name="sort"><br>
数组<br><!-- 注意name的名字,名字跟VO类中的属性名保持一致。 -->
<input type="text" name="ids[0]"><br>
<input type="text" name="ids[1]"><br>
user对象<br><!-- 注意name的名字,名字跟VO类中的属性名保持一致。 -->
<input type="text" name="user.userName"><br>
<input type="password" name="user.password"><br>
List集合<br><!-- 注意name的名字,名字跟VO类中的属性名保持一致。 -->
<input type="text" name="userList[0].userName">
<input type="password" name="userList[0].password"><br>
<input type="text" name="userList[1].userName">
<input type="password" name="userList[1].password"><br>
map集合<br><!-- 注意name的名字,名字跟VO类中的属性名保持一致。 -->
<input type="text" name="userMap['map1'].userName">
<input type="password" name="userMap['map1'].password"><br>
<input type="text" name="userMap['map2'].userName">
<input type="password" name="userMap['map2'].password"><br>
<input type="submit" value="登录">
</form>
结果:
会发现有乱码,后面章节会处理。
1-14 VO、DTO、DO、PO
1-14-1 转换(BeanUtils类)
VO、DTO、PO、DO之间转换
// VO类
@Data
public class TempVo {
private String sort;
private String[] ids;
private User user;
private List<User> userList;
private Map<String,User> userMap;
}
// DTO类
@Data
public class TempDto {
private String userName;
}
// PO类
@Data
public class TempPo {
private String userName;
private String password;
}
// main方法
public static void main(String[] args) {
TempVo tempVo = new TempVo();
tempVo.setUser(new User("root","123456"));
TempPo tempPo = new TempPo();
TempDto tempDto = new TempDto();
// 使用Spring中的BeanUtils类来进行拷贝。
// 拷贝和被拷贝类属性的名字和类型一致的属性才拷贝
BeanUtils.copyProperties(tempVo.getUser(),tempPo);
BeanUtils.copyProperties(tempPo,tempDto);
System.out.println(tempPo);
System.out.println(tempDto);
}
1-15 解决乱码
<!-- 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>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 任何请求都会被字符过滤器捕获到 -->
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
添加好后,运行1-13章的案例:
会发现,乱码解决了
1-16 返回JSON数据(序列化)
返回JSON数据(序列化):就是把对象转换成JSON格式的字符串传给前端。
设置响应头中媒体类型为application/json;charset=utf-8
1-16-1 使用 fastjson 来转化
<!-- 添加 fastjson依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
用法一:
// @RequestMapping中produces属性,修改请求头中的Content-Type:的类型。
@RequestMapping(value = "/fastjson",produces = "application/json;charset=utf-8")
@ResponseBody
public String temp() {
User[] users = {new User("root","123"), new User("admin","admin")};
// 把对象转换成JSON格式的字符串。
String json = JSONArray.toJSONString(users);
return json;
}
结果:
用法二:
<!-- application.xml文件 -->
<!-- mvc注解生效 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean id="fastjson" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!-- 顺序不能反,text/html必须写在前面,不然IE浏览器下会出现下载提示 -->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
@RequestMapping(value = "/fastjson")
@ResponseBody
public User[] temp() {
User[] users = {new User("root","123"), new User("admin","admin")};
return users;
}
结果:
区别
- 方法一:Handel方法的返回值JSON格式的字符串,需要使用JSONArray的toJSONString静态方法进行转换,另外需要手动设置响应头的媒体类型(Content-Type)。
- 方法二:Handel方法的返回值直接是对象,对象会自动进行转换,不需要手动转了。另外需要手动设置响应头的媒体类型(Content-Type)。
注意
需要配合**@ResponseBody**注解使用。
如果报错NoClassDefFoundError
原因:在启动的时候依赖存在,而项目运行的时候依赖没有打包到war包中,tomcat运行的时候因为没有这个依赖,所以会抛出NoClassDefFoundError错误。
解决方案:把依赖添加到war包去
1-16-2 使用 jackson 来转化
SpringMVC默认使用jackson
<!-- 添加jackson依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
添加依赖后就可以直接使用了,但是如果需要进行一些自定义配置的话,就还需要下面的步骤。
设置空值不序列化:值为null的属性,不会被序列化。因为当你有多个值为null,会浪费带宽。
// 添加CustomObjectMapper类,用来对序列化过程进行额外的一些配置
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
// 去掉默认的时间戳格式
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
// 设置为东八区
setTimeZone(TimeZone.getTimeZone("GMT+8"));
// 设置日期转换yyyy-MM-dd HH:mm:ss
setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 设置:禁止把Pojo中值为null的字段映射到json字段中
configure(SerializationFeature.WRITE_NULL_MAP_VALUES,false);
// 空值不序列化
setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 反序列化时,属性不存在兼容处理
getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 序列化枚举是以toString()来输出,默认false,即默认以name()输出。
configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);
}
}
<!-- 把自己写的CustomObjectMapper加入到容器中 -->
<bean id="customObjectMapper" class="com.temp.config.CustomObjectMapper" />
<!-- application.xml文件 -->
<!-- mvc注解生效 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean id="fastjson" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<!-- 把上面注入的customObjectMapper注入到这个对象中 -->
<property name="objectMapper" ref="customObjectMapper" />
<property name="supportedMediaTypes">
<list>
<!-- 顺序不能反,text/html必须写在前面,不然IE浏览器下会出现下载提示 -->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
// @RequestMapping中produces属性,修改请求头中的Content-Type:的类型。
@RequestMapping(value = "/fastjson",produces = "application/json;charset=utf-8")
@ResponseBody
public User[] temp() {
User[] users = {new User("root","123"),
new User(null,"admin"),
new User(null,null)}; // 这里其中一个值为null
return users;
}
结果:会发现,最后一个对象是空的,没有属性。
1-17 @ResponseBody和@RequestBody
- @ResponseBody:写在方法上,加上后不会经过视图解析器,会把数据直接传到前端;一般在异步获取数据时使用;
- @RequestBody:写在方法的参数上,用来接收json格式的数据,通过配置好的转化器把JSON转换为对应的数据类型,再传给对应的参数。
1-18 接收JSON数据(反序列化)
前提:添加配置了jackson转换器。
// User类
public class User {
private String userName;
private String password;
// set和get此次省略。。。
}
// 注意:参数中使用的是@RequestBody,而不是@ResponseBody
@RequestMapping(value = "/fastjson")
public String temp(@RequestBody User user) {
System.out.println(user);
return "/";
}
结果:
注意
- 如果json格式中有一个属性是这个接收对象中没有的属性会报错
- 错误信息:JSON parse error: Unrecognized field “temp” (class com.temp.entity.User), not marked as ignorable; 意思是说无法识别temp这个字段。
1-19 类型转换器
1-19-1 全局的转换器
自己写一个String转Date类型的转换器,并且添加进去。
1、添加一个类型转换器
// 时间转换器
public class DateTimeConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date parse = null;
try {
parse = simpleDateFormat.parse(s);
} catch (ParseException e) {
throw new RuntimeException(e);
}
return parse;
}
}
<!-- application.xml -->
<!-- mvc注解生效 -->
<!-- 文件中有annotation-driven这个标签,conversion-service这个属性也是可以写在里面的 -->
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<!-- 这里把我们写的转换器加进去 -->
<bean id="stringToDateConverter" class="com.temp.config.DateTimeConverter" />
</set>
</property>
</bean>
2、修改User对象
public class User{
private String userName;
private String password;
// 添加了一个日期属性
private Date date;
// set和get方法省略了。。。。
}
@RequestMapping(value = "/fastjson")
public String temp(User user) {
System.out.println(user);
return "/";
}
结果:
1-19-2 局部的转换器
有两个注解:
@JsonFormat:使用再JSON和对象互相转化的
@DateTimeFormat:从requestParam中获取参数并转换。
使用位置:在全局变量上
注意:一定要引入jackson的包,因为这两个都是jackson的注解。
public class User{
private String userName;
private String password;
@JsonFormat(
pattern = "yyyy-MM-dd",
timezone = "GMT-8"
)@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
}
总结
1-20数据校验
1、添加依赖
<!--
Bean Validation的API规范,定义了标准的验证注解和接口。
但它不包含具体的实现
-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!--
这是对javax.validation API的一个实现,提供了实际的校验逻辑。
-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.7.Final</version>
</dependency>
2、bean注入
<!-- application.xml文件 -->
<bean id="localValidator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" >
<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
</bean>
<!-- 启用mvc注解 -->
<mvc:annotation-driven validator="localValidator" />
3、实体类添加注解
public class User{
@NotNull(message = "userName不能为空!")
private String userName;
@NotNull(message = "password不能为空!")
private String password;
@Email(message = "email格式不对!")
private String email;
@JsonFormat(
pattern = "yyyy-MM-dd",
timezone = "GMT-8"
)@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
// set和get方法省略。。。
}
4、Handel处理方法
// @Validated:表示这个对象需要校验,如果不写,就不会进行校验
// BindingResult:有校验不通过的,会把注解中的message传到这个对象中。
@RequestMapping("/sjjy")
public String temp(@Validated User user, BindingResult br) {
List<ObjectError> allErrors = br.getAllErrors();
// 判断有没有校验不通过的,有就打印不通过的信息,没有就打印user对象
if (allErrors.size() > 0) {
for (ObjectError allError : allErrors) {
//getDefaultMessage():获取到注解中message的值。
System.out.println(allError.getDefaultMessage());
}
} else {
System.out.println(user);
}
return "/";
}
结果:
注意:一定要写上set方法,因为传参的时候都是set方法传入的。如果没有set方法就没有办法传进去,没有数据进去就不会被校验到。
1-21 视图解析器链
视图解析器链:是由多个视图解析器组成,优先级高的在前面,低的在后面,当前面其中一个视图解析器解析成功,后面的视图解析器就不会执行。
当配置了两个视图解析器,会根据两个的优先级来决定哪个视图解析器先执行。
<!-- application.xml -->
<!--
视图解析器
ThymeleafViewResolver:是第三方的视图解析器。
-->
<bean class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<!-- 优先级,越小优先级越大 -->
<property name="order" value="1" />
<property name="viewNames" value="*.html,*.xhtml" />
</bean>
<!--
视图解析器
InternalResourceViewResolver:这个是Springmvc自带的视图解析器。
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jsp" />
<!-- 优先级,越小优先级越大 -->
<property name="order" value="5" />
</bean>
ThymeleafViewResolver:是第三方的视图解析器。
Thymeleaf官网使用教程:链接: https://www.thymeleaf.org/doc/tutorials/3.1/thymeleafspring.html
1-22 全局捕获异常
把所有的错误向上抛,抛到前端控制器,由前端控制器HanderExceptionResolver(全局异常处理器接口)统一处理。就不会把错误抛到浏览器让用户展示错误信息。
第一种方法 继承HanderExceptionResolver
1、写一个类继承HanderExceptionResolver
// 继承HandlerExceptionResolver,并且重写里面的resolveException方法。
public class MyHanderlExcepthon implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
// 报错了,前端控制器会转发到error.html中
modelAndView.setViewName("/error.html");
return modelAndView;
}
// 根据类型选择转发页面
// @Override
// public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
// ModelAndView modelAndView = new ModelAndView();
// 你可以根据你的异常类型来选择转发到哪个页面
// if (e instanceof 异常类型) {
// 处理业务
// modelAndView.setViewName("/error.html");
// } else if (e instanceof 异常类型) {
// 处理业务
// modelAndView.setViewName("/error.html");
// } else {
// 处理业务
// modelAndView.setViewName("/error.html");
// }
// return modelAndView;
// }
}
2、把类加载到Spring容器中
<bean id="myHanderlExcepthon" class="com.temp.config.MyHanderlExcepthon" />
除了在xml中配置,也可以使用注解自动注入。
@Component:在类上添加注解
@Component
public class MyHanderlExcepthon implements HandlerExceptionResolver {
3、error.html
<!-- error.html文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>ERROR</h1>
</body>
</html>
4、搞出一个异常
@RequestMapping("/errortemp")
public String temp() {
System.out.println(1/0);// 这里会抛一个异常
return "/";
}
结果:
如果没有配置全局捕获异常,会把服务器的报错信息抛到浏览器段,如下:
第二种方法 @ControllerAdvice
使用到两个注解
@ControllerAdvice:跟@Controller类似,写在类上
@ExceptionHandler:跟@RequestMapping类似,写在方法上
前端控制器会根据@ExceptionHandler传入的参数中的异常类型,来选择哪个处理方法,方法根据类型来决定去哪个页面
写一个异常的控制器
@ControllerAdvice
public class ErrorController {
// 数学类异常会被这里匹配到
@ExceptionHandler(ArithmeticException.class)
public String temp(ArithmeticException a) {
System.out.println("抛出了ArithmeticException数学类异常");
System.out.println(a);
return "/error.html";
}
// Exception类型可以用来兜底的,其他类型都不匹配就使用这个异常父类
@ExceptionHandler(Exception.class)
public String temp(Exception e) {
System.out.println("抛出了异常!!!");
System.out.println(e);
return "/error.html";
}
}
接口
@RequestMapping("/errortemp")
public String temp() {
System.out.println(1/0);// 这里会抛出一个数学类异常
return "/";
}
结果:
1-23 静态资源的处理
两个标签二选一:
- default-servlet-handler标签:用于将静态资源请求交给容器的默认 Servlet 处理。在 Web 容器中,通常会有一个默认的 Servlet 来处理静态资源的请求,如 Tomcat 中的 DefaultServlet,Jetty 中的DefaultServlet等。该标签会将静态资源的请求委托给默认的 Servlet,以便其能够正确处理静态资源。这个标签相对于 resources标签 更加简单,但是功能也相对较少。
- resources标签:用于指定静态资源的映射路径,例如可以将 /static/** 映射到 /WEB-INF/static/ 目录下
都是用于处理静态资源的
default-servlet-handler标签
<!-- spring的配置文件 -->
<mvc:default-servlet-handler />
resources标签
<!-- spring的配置文件 -->
<mvc:resources mapping="/js/**" location="/static/js/" />
<mvc:resources mapping="/css/**" location="/static/css/" />
<mvc:resources mapping="/image/**" location="/static/image/" />
在选择使用哪个标签时,需要根据具体的需求进行判断。如果需要对静态资源进行更为细致的控制和处理,应该选择 resources 标签;如果只是简单地将静态资源的请求转交给默认的 Servlet 处理,可以选择 default-servlet-handler 标签。
1-24 拦截器Interceptor
Interceptor拦截器和filter过滤器很像,都是用于拦截(过滤)请求的。但是两个的位置不一样。
filter过滤器:是位于servlet之前的
Interceptor拦截器:是位于Controller之前
Interceptor拦截器生命周期:
preHandle处理前、postHandle处理后、afterCompletion视图渲染完毕后
步骤一
继承HandlerInterceptor接口,并重写里面的方法。
public class MyInterceptor implements HandlerInterceptor {
/*
handerl方法执行之前会被调用
返回值:true通过、false被拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("--------------------preHandle");
return true;
}
/*
handerl方法执行之后会被调用
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("--------------------postHandle");
}
/*
视图渲染完毕后被调用
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("--------------------afterCompletion");
}
}
步骤二
把我们写的拦截器加载到spring容器中
<!-- Spring配置文件 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截的路径 -->
<mvc:mapping path="/**"/>
<!-- 哪些路径不拦截 -->
<mvc:exclude-mapping path="/toLogin"/>
<mvc:exclude-mapping path="/login"/>
<!-- 把我们写的拦截器加载进来 -->
<bean id="myInterceptor" class="com.temp.interceptor.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
运行
@RequestMapping("/temp")
public String temp() {
System.out.println("Handerl方法执行了!");
return "/";
}
结果:
为什么被运行了两次拦截器?
因为handerl方法执行完后,又请求转发到了/首页中,首页又会被拦截一次
读源码
这个是DispatcherServlet前端控制器中的核心方法doDispatch。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
// 处理器执行链
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 处理器适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
// 处理器执行链中包含了一个处理器,和多个拦截器
// 遍历调用执行链中的所有拦截器中的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 处理器适配器去执行handerl方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
// 遍历调用执行链中的所有拦截器中的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
// 遍历调用执行链中的所有拦截器中的afterCompletion方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
1-25 Restful风格
下面遵循的是Restful风格
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 获取全部 Get请求
* @return
*/
@GetMapping
@ResponseBody
public List<User> getAll() {
// 业务逻辑
return new ArrayList<User>();
}
/**
* 根据id获取 Get请求
* @param id
* @return
*/
@GetMapping("/{id}")
@ResponseBody
public User getOne(@PathVariable Integer id) {
// 业务逻辑
return new User();
}
/**
* 传入 Post请求
* @return
*/
@PostMapping
@ResponseBody
public String insert() {
// 业务逻辑
return "添加成功!";
}
/**
* 删除 Delete请求
* @param id
* @return
*/
@DeleteMapping("/{id}")
@ResponseBody
public String delete(@PathVariable Integer id) {
// 业务逻辑
return "删除成功!";
}
/**
* 修改 Put请求
* @param user
* @param id
* @return
*/
@PutMapping("/{id}")
@ResponseBody
public String modify(@RequestParam User user, @PathVariable Integer id) {
// 业务逻辑
return "修改成功!";
}
}
@RestController注解
如果采用Restful风格,可以使用@RestController注解,方法中就不用写@ResponseBody注解了,因为@RestController中就有了
如下:
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 获取全部 Get请求
* @return
*/
@GetMapping
public List<User> getAll() {
// 业务逻辑
return new ArrayList<User>();
}
/**
* 根据id获取 Get请求
* @param id
* @return
*/
@GetMapping("/{id}")
public User getOne(@PathVariable Integer id) {
// 业务逻辑
return new User();
}
/**
* 传入 Post请求
* @return
*/
@PostMapping
public String insert() {
// 业务逻辑
return "添加成功!";
}
/**
* 删除 Delete请求
* @param id
* @return
*/
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id) {
// 业务逻辑
return "删除成功!";
}
/**
* 修改 Put请求
* @param user
* @param id
* @return
*/
@PutMapping("/{id}")
public String modify(@RequestParam User user, @PathVariable Integer id) {
// 业务逻辑
return "修改成功!";
}
}
1-26 统一结果返回
注意:要添加jackson,要不然会报错,因为没有一个转换器把统一返回对象转换成JSON。
package com.temp.entity;
public class User{
private String userName;
private String password;
public User() {
}
public User(String userName, String password) {
this.userName = userName;
this.password = password;
}
// set、get、toString省略
}
package com.temp.common;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
// 统一返回的对象
@Data
@Builder
public class R {
public static final Integer CODE_SUCCESS = 2000;
public static final Integer CODE_FAIL = 1000;
public static final String MESSAGE_SUCCESS = "操作成功!";
public static final String MESSAGE_FAIL = "操作失败!";
private Integer code;
private String message;
private Object data;
public static R success() {
return R.builder().code(CODE_SUCCESS).message(MESSAGE_SUCCESS).build();
}
public static R success(Object obj) {
return R.builder().code(CODE_SUCCESS).message(MESSAGE_SUCCESS).data(obj).build();
}
public static R fail() {
return R.builder().code(CODE_FAIL).message(MESSAGE_FAIL).build();
}
public static R fail(Object obj) {
return R.builder().code(CODE_FAIL).message(MESSAGE_FAIL).data(obj).build();
}
}
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 获取全部 Get请求
* @return
*/
@GetMapping
public R getAll() {
List<User> users = new ArrayList();
users.add(new User("root","root"));
users.add(new User("admin","123456"));
users.add(new User("temp","123"));
return R.success(users);
}
/**
* 根据id获取 Get请求
* @param id
* @return
*/
@GetMapping("/{id}")
public R getOne(@PathVariable Integer id) {
// 业务逻辑
return R.success();
}
/**
* 传入 Post请求
* @return
*/
@PostMapping
public R insert() {
// 业务逻辑
return R.builder().code(R.CODE_SUCCESS).message("操作成功!").build();
}
/**
* 删除 Delete请求
* @param id
* @return
*/
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id) {
// 业务逻辑
return R.success();
}
/**
* 修改 Put请求
* @param user
* @param id
* @return
*/
@PutMapping("/{id}")
public R modify(@RequestParam User user, @PathVariable Integer id) {
// 业务逻辑
return R.success();
}
}
结果:
1-27 跨域
两种请求
CORS:跨域资源共享
简单请求
非简单请求
代码实战视频地址:链接
1-28 配置类
配置类代替Spring配置文件(XML)
@Configuration:表示这个类是Spring容器的一个配置类
注意:如果使用的是set注入,就可以直接new出来,通过set方法给对象赋值。
如果使用的是构造方法,就必须new的时候传值
property标签:set注入
constructor-arg标签:构造方法注入
// 配置类
@Configuration
public class CommonConfiguration {
// 处理器映射器
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
return new BeanNameUrlHandlerMapping();
}
// 处理器适配器
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
// 前端控制器
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/");
internalResourceViewResolver.setSuffix(".jsp");
internalResourceViewResolver.setOrder(5);
return internalResourceViewResolver;
}
/*
如果一个bean中需要另一个bean,可以写到形参中,或者直接调用上面的方法
如下面方法要前端控制的对象(直接调用上面的方法)
@Bean
public InternalResourceViewResolver temp() {
InternalResourceViewResolver irv = internalResourceViewResolver(); // 调用上面写好的方法
irv.setPrefix("/WEB-INF/");
irv.setSuffix(".jsp");
irv.setOrder(5);
return irv;
}
如下面方法要前端控制的对象(放在形参中)
@Bean
public InternalResourceViewResolver temp(InternalResourceViewResolver irv) {
irv.setPrefix("/WEB-INF/");
irv.setSuffix(".jsp");
irv.setOrder(5);
return irv;
}
*/
}
配置了上面的代码,application.xml中就不需要配置处理器适配器、处理器映射器、前端控制器等。
1-29 文件上传
文件上传: 链接
文件下载: 链接