SpringMVC
拦截器
-
创建一个新的工程用来测试代码,前面已经创建过很多次了,初始代码就是那些,所以这里就不写了,可看下面的大致架构
Controller 和 html 页面可以先创建好,后面需要写了在写;web、spring-mvc、pom 都是和之前一样的
什么时候会触发拦截器?
拦截器的配置
- SpringMVC 中的拦截器用于拦截控制器方法的执行。
- SpringMVC 中的拦截器需要实现
HandlerInterceptor
接口。 - SpringMVC 的拦截器必须在 SpringMVC 的配置文件中进行配置
拦截器的三个方法
SpringMVC 中的拦截器有三个方法:
preHandle
:控制器方法执行之前执行 preHandle(),其 boolean 类型的返回值表示是否拦截或放行,返回 true 为放行,即调用控制器方法;返回 false 表示拦截,即不调用控制器方法。postHandle
:控制器方法执行之后执行 postHandle() 方法afterComplation
:处理完视图和模型数据,渲染视图完毕之后执行 afterComplation() 方法
DispatcherServlet 部分源码
- 在该源码中会默认帮我们去调用所有的拦截器(SpringMVC默认有一个拦截器,我们也可以手动创建拦截器)
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 调用所有拦截器的 preHandle()方法(在控制器方法执行前调用)
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用所有拦截器的 postHandle() 方法(在控制器方法执行之后调用)
mappedHandler.applyPostHandle(processedRequest, response, mv);
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
if (mv != null && !mv.wasCleared()) {
// 渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
if (mappedHandler != null) {
// 调用所有拦截器的 afterCompletion() 方法(在视图渲染完之后调用)
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
}
拦截器案例代码
-
在 index.html 中编写超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <a th:href="@{/interceptor}">测试拦截器</a> </body> </html>
success.html 页面就弄个文字就好了,因为只是用来跳转的。
-
编写对应的控制器方法
package com.laoyang.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @ClassName InterceptorController * @Description: SpringMVC 拦截器 * @Author Laoyang * @Date 2022/1/16 21:16 */ @Controller public class InterceptorController { @RequestMapping("/**/interceptor") public String interceptor() { return "success"; } }
-
编写拦截器
package com.laoyang.mvc.config; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @ClassName InterceptorConfig * @Description: 配置拦截器 * @Author Laoyang * @Date 2022/1/16 21:29 */ public class InterceptorConfig implements HandlerInterceptor { /** * 该方法在控制器执行之前执行 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("在控制器执行之前执行"); return true; } /** * 该方法在控制器执行之后执行 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("在控制器执行之后执行"); } /** * 该方法在视图渲染之后执行 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("在视图渲染之后执行"); } }
-
在 spring-mvc.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"> <!-- 配置拦截器 --> <mvc:interceptors> <!-- 方式一:bean标签 方式二:ref标签(使用这种方式的时候必须要把自己写的拦截器注入到 IOC 容器中,否则会导致找不到对应的拦截器) > 这两种方式都是对所有的请求进行拦截 方式三:mvc:interceptor标签 > 这种方式可以指定拦截规则 --> <!-- <bean class="com.laoyang.mvc.config.InterceptorConfig"></bean>--> <!-- <ref bean="interceptorConfig"></ref>--> <mvc:interceptor> <!-- 哪些请求被拦截 /* 表示拦截上下文路径下一层目录中的所有请求 比如:上下文路径为 springmvc,那么/*拦截的就是 springmvc/1、springmvc/abc...这样的请求 如果我访问 springmvc/1/abc,那么就不会被拦截! /** 表示拦截所有请求(不管有几层目录,都会被拦截) --> <mvc:mapping path="/**"/> <!-- 哪些请求不拦截 --> <mvc:exclude-mapping path="/"/> <!-- 指定拦截器 --> <bean class="com.laoyang.mvc.config.InterceptorConfig"></bean> </mvc:interceptor> </mvc:interceptors> </beans>
这里有三种方式,大家都可以进行测试,然后查看对应的效果;使用方式二的时候要注意一下,因为要把拦截器注入到 IOC 容器中,所以我们可以在
拦截器类
添加一个 @Component 注解,或者直接在 spring-mvc.xml 配置文件中使用<bean>
标签进行注入。还有就是
/*
和/**
要区分开,不要搞错了!!! -
启动Tomcat查看效果
- 浏览器的效果差不多就是显示不出来数据(拦截了对应请求后无法进行访问,页面就不会显示任何东西)
- 主要还是看控制台打印的效果,如果打印了拦截器中三个方法的数据,那么就表示该请求被拦截了,如果没有打印的话,就表示该请求没有被拦截。
- 因为截图可能不太容易看懂,所以建议大家还是自己测试一下,这样对自己帮助应该是比较大的。
多个拦截器的执行顺序
-
若每个拦截器的 preHandle() 都返回 true
此时多个拦截器的执行顺序和拦截器在 SpringMVC 的配置文件的配置顺序有关:
preHandle() 会按照配置的顺序执行,而 postHandle() 和 afterComplation() 会按照配置的反序执行(详细可看下面的部分源码)
-
若某个拦截器的 preHandle() 返回了 false
preHandle() 返回 false 和它之前的拦截器的 preHandle() 都会执行,postHandle() 都不执行,返回 false 的拦截器之前的拦截器的afterComplation() 会执行
比如 A 拦截器返回 true,B 拦截器返回 false,加上默认的一个拦截器,目前有三个拦截器;
然后 A 拦截器和默认拦截器返回的都是 true,B 返回的是 false,那么执行到 B 拦截器的时候就会进入到
applyPreHandle()
方法中的 if 结构中,然后在该 if 结构中会执行 A 拦截器和默认拦截器的triggerAfterCompletion()
方法。如果有 A、B、C、D、E 这几个拦截器,其中 C 返回的是 false,其它都返回 true,那么执行到 C 的时候就会结束整个方法,后面的 D 和 E 拦截器就不会被执行了,就可以理解为目前的请求是不符合规定的,就会被拦截。
HandlerExecutionChain 部分源码
- 该源码是
DispatcherServlet 部分源码
中三个方法的内部细节,根据这些代码就可以了解拦截器的执行顺序
public class HandlerExecutionChain {
// 拦截器索引
private int interceptorIndex = -1;
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 使用我们配置的拦截器和默认的拦截器处理当前的请求,如果符合规则,则放行(SpringMVC有一个默认的拦截器)
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
// 如果不符合规则,则会被拦截,但是在返回之前还会执行一次triggerAfterCompletion()方法
triggerAfterCompletion(request, response, null);
return false;
}
// 这个值对于执行的顺序比较关键
this.interceptorIndex = i;
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
// 注意,这个地方的 i=所有拦截器的数量-1,且 i--,这说明该方法是从最后一个拦截器开始执行的
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
/*
注意,这个地方是 i--,且 i=interceptorIndex(该值在applyPreHandle()方法中被赋过值)
也就相当于该方法是从最后一个拦截器开始执行的
*/
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
感兴趣的小伙伴可以自己进入底层代码中进行查看及测试。
案例(拦截器全部为true的效果)
-
将刚才创建的拦截器打印信息改成这样:
package com.laoyang.mvc.config; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @ClassName InterceptorConfig * @Description: 配置拦截器 * @Author Laoyang * @Date 2022/1/16 21:29 */ @Component public class InterceptorConfig implements HandlerInterceptor { /** * 该方法在控制器执行之前执行 * 返回true表示放行,返回false表示拦截 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("InterceptorConfig在控制器执行之前执行"); return true; } /** * 该方法在控制器执行之后执行 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("InterceptorConfig在控制器执行之后执行"); } /** * 该方法在视图渲染之后执行 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("InterceptorConfig在视图渲染之后执行"); } }
-
然后在创建一个拦截器,和上面那个差不错,把打印信息稍微换一下就行
package com.laoyang.mvc.config; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @ClassName SecondInterceptorConfig * @Description: 测试多个拦截器的执行顺序 * @Author Laoyang * @Date 2022/1/18 15:15 */ @Component public class SecondInterceptorConfig implements HandlerInterceptor { /** * 该方法在控制器执行之前执行 * 返回true表示放行,返回false表示拦截 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("SecondInterceptorConfig在控制器执行之前执行"); return true; } /** * 该方法在控制器执行之后执行 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("SecondInterceptorConfig在控制器执行之后执行"); } /** * 该方法在视图渲染之后执行 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("SecondInterceptorConfig在视图渲染之后执行"); } }
-
在 spring-mvc.xml 文件中配置这俩拦截器
<!-- 配置拦截器 --> <mvc:interceptors> <!-- interceptorConfig 拦截器在上 --> <ref bean="interceptorConfig" /> <ref bean="secondInterceptorConfig" /> <!-- secondInterceptorConfig 拦截器在上 --> <!-- <ref bean="secondInterceptorConfig" /> --> <!-- <ref bean="interceptorConfig" /> --> </mvc:interceptors>
这里我就写关键的代码了,大家把这段代码加到配置文件中就好了
-
启动 Tomcat 查看控制台效果
-
interceptorConfig 拦截器在上的效果
我们可以看到,刚开始是按正序执行 interceptorConfig 拦截器的 preHandle() 方法的,然后在执行的 secondInterceptorConfig 拦截器的 preHandle() 方法,而后面的两个方法则是先执行的 secondInterceptorConfig 拦截器,在执行 interceptorConfig 拦截器,这就是刚才源码中说的拦截器执行顺序
-
secondInterceptorConfig 拦截器在上的效果
-
注意:这里说的 “在上” 指的是 spring-mvc.xml 配置文件中拦截器的配置顺序,大家可自行尝试
-
如果大家想看一下返回 false 的效果,那么可以将我们配置的两个拦截器中的某一个拦截器的
preHandle()
返回值设置为 false,然后在进行测试,看控制台的效果即可。
异常处理器
基于配置的异常处理
-
SpringMVC 提供了一个处理控制器方法执行过程中所出现的异常的接口:
HandlerExceptionResolver
-
HandlerExceptionResolver 接口的实现类有:
DefaultHandlerExceptionResolver
和SimpleMappingExceptionResolver
-
SpringMVC 提供了自定义的异常处理器
SimpleMappingExceptionResolver
DefaultHandlerExceptionResolver 说明
- 默认的异常处理
- 如果出现对应的异常,就会返回一个 ModelAndView 对象,代替我们原本要正常访问的 ModelAndView 对象
我们看到的那些 500、404 之类的报错页面就是它处理的
部分源码:
public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
@Nullable
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported(
(HttpMediaTypeNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable(
(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
}
else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable(
(MissingPathVariableException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter(
(MissingServletRequestParameterException) ex, request, response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException(
(ServletRequestBindingException) ex, request, response, handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported(
(ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch(
(TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable(
(HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable(
(HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException(
(MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException(
(MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException(
(NoHandlerFoundException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
return null;
}
}
SimpleMappingExceptionResolver 说明
- 自定义异常处理
- 我们可以使用配置文件或注解的方式配置异常处理
基于配置文件实现异常处理
-
在 spring-mvc.xml 文件中配置异常处理
<!-- 配置异常处理 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!-- exceptionMappings 参数存储的是键值对的数据 这里 key=java.lang.ArithmeticException(算术异常) value=error(视图名称) --> <prop key="java.lang.ArithmeticException">error</prop> </props> </property> </bean>
-
在 index.html 中编写超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <a th:href="@{/testException}">测试自定义异常处理</a> </body> </html>
-
创建 error.html 页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>错误页面</title> </head> <body> <h1>页面出现问题了,程序员该加班了!</h1> </body> </html>
-
编写对应的控制器方法
package com.laoyang.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class InterceptorController { @RequestMapping("/testException") public String testException() { // 故意编写一个会导致算术异常的代码 int i = 10 / 0; return "success"; } }
-
启动Tomcat查看效果
获取异常信息
-
在 spring-mvc.xml 文件的异常处理中在设置一个属性
<!-- 配置异常处理 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!-- exceptionMappings 参数存储的是键值对的数据 这里 key=java.lang.ArithmeticException(算术异常) value=error(视图名称) key 表示处理器方法执行过程中出现的异常 value 表示若出现 key中指定的异常,则跳转到指定页面 --> <prop key="java.lang.ArithmeticException">error</prop> </props> </property> <!-- 将异常信息共享在请求域中的键 value为键(可随便取名,见名知意即可),异常信息为值,所以这里就表示:ex=异常信息 > 如果要在页面获取请求域的异常信息,直接使用 ex 即可。 > 简单理解:给 exceptionAttribute 属性设置一个属性名,属性名为 ex,然后该属性的值就是异常信息 --> <property name="exceptionAttribute" value="ex" /> </bean>
-
在 error.html 页面调用这个属性
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>错误页面</title> </head> <body> <h1>页面出现问题了,程序员该加班了!</h1> <!-- 获取放在请求域中的异常信息 --> <p th:text="${ex}"></p> </body> </html>
-
测试效果
基于注解实现异常处理
-
注释掉 spring-mvc.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.laoyang.mvc" /> <!-- 配置 Thymeleaf 视图解析器 --> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <!-- 优先级 --> <property name="order" value="1"/> <!-- 字符编码 --> <property name="characterEncoding" value="UTF-8"/> <!-- 模板 --> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀 --> <property name="prefix" value="/WEB-INF/templates/" /> <!-- 视图后缀 --> <property name="suffix" value=".html"/> <!-- 模板模型 --> <property name="templateMode" value="HTML5"/> <!-- 页面编码格式 --> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> <!-- 配置视图控制器 --> <mvc:view-controller path="/" view-name="index" /> <!-- 开放对静态资源的访问 --> <mvc:default-servlet-handler /> <!-- 开启mvc注解驱动 --> <mvc:annotation-driven /> <!-- 配置拦截器 --> <mvc:interceptors> <ref bean="interceptorConfig" /> <ref bean="secondInterceptorConfig" /> </mvc:interceptors> <!-- 配置异常处理 --> <!-- <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">--> <!-- <property name="exceptionMappings">--> <!-- <props>--> <!-- <!–--> <!-- exceptionMappings 参数存储的是键值对的数据--> <!-- 这里 key=java.lang.ArithmeticException(算术异常)--> <!-- value=error(视图名称)--> <!-- key 表示处理器方法执行过程中出现的异常--> <!-- value 表示若出现 key中指定的异常,则跳转到指定页面--> <!-- –>--> <!-- <prop key="java.lang.ArithmeticException">error</prop>--> <!-- </props>--> <!-- </property>--> <!-- <!–--> <!-- 将异常信息共享在请求域中的键--> <!-- value为键(可随便取名,见名知意即可),异常信息为值,所以这里就表示:ex=异常信息--> <!-- > 如果要在页面获取请求域的异常信息,直接使用 ex 即可。--> <!-- > 简单理解:给 exceptionAttribute 属性设置一个属性名,属性名为 ex,然后该属性的值就是异常信息--> <!-- –>--> <!-- <property name="exceptionAttribute" value="ex" />--> <!-- </bean>--> </beans>
为了确保大家里面的配置没有写错,我这里把完整的代码展示一下
-
编写一个新的 Controller,并编写对应的方法(使用注解修饰)
package com.laoyang.mvc.controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; /** * @ClassName ExceptionController * @Description: 使用注解进行异常处理 * @Author Laoyang * @Date 2022/1/18 17:43 */ //该注解可拆分为 Controller 和 Advice 进行理解 //可用来实现:捕获全局的异常、进行数据的绑定、全局数据的预处理 @ControllerAdvice public class ExceptionController { /** * 程序运行中只要出现了 @ExceptionHandler 注解中设置的异常,就会执行该方法,然后跳转到对应的处理页面中 */ @ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class}) public String testExceptionHandler(Exception ex, Model model) { model.addAttribute("ex", ex); return "error"; } }
-
启动Tomcat查看效果
如果大家想看空指针异常的效果,可以在控制器方法中加入以下代码:
// 空指针异常 String str = null; System.out.println(str.toString());
注解配置 SpringMVC
- 使用配置类和注解代替 web.xml 和 spring-mvc.xml 配置文件的功能
创建一个新的工程,导入之前的依赖、创建 webapp 、WEB-INF 、templates 文件夹即可
创建初始化类,代替 web.xml 文件
说明
- 在 Servlet3.0 环境中,容器会在类路径中查找实现
javax.servlet.ServletContainerInitializer
接口的类,如果找到的话就用它来配置 Servlet 容器。 - Spring 提供了这个接口的实现,名为
SpringServletContainerInitializer
,这个类反过来又会查找实现WebApplicationInitializer
的类并将配置的任务交给它们来完成。 - Spring3.2 引入了一个便利的
WebApplicationInitializer
基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer
,当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer 并将其部署到 Servlet3.0 容器的时候,容器会自动发现它,并用它来配置 Servlet 上下文。
实现
-
在 config 包下创建配置类替代 web.xml 配置文件
package com.laoyang.mvc.config; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import javax.servlet.Filter; /** * @ClassName WebInit * @Description: Web初始化类 * @Author Laoyang * @Date 2022/1/19 11:33 * * 说明:该类是用来代替 web.xml 配置文件的 */ public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 指定 Spring 的配置类 * 没有对应配置类的时候会返回一个长度为 0 的 Class 数组 * 有对应配置类的时候就将对应的配置了加入到 Class 数组中 */ @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } /** * 指定 SpringMVC 的配置类 * 没有对应配置类的时候会返回一个长度为 0 的 Class 数组 * 有对应配置类的时候就将对应的配置了加入到 Class 数组中 */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMVCConfig.class}; } /** * 指定 DispatcherServlet 的映射规则 */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } /** * 注册过滤器 */ @Override protected Filter[] getServletFilters() { // 字符编码过滤器 CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); characterEncodingFilter.setForceEncoding(true); // 处理 put和delete请求的过滤器 HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter}; } }
包别导错了
创建 SpringConfig 配置类,代替 Spring 的配置文件
- 因为现在是使用 SpringMVC,所以这个配置类也可以不创建,如果创建的话,内容不配置也是可以的(因为现在写的都是SpringMVC的配置)
package com.laoyang.mvc.config;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName SpringConfig
* @Description: Spring配置类
* @Author Laoyang
* @Date 2022/1/19 11:38
*/
@Configuration
public class SpringConfig {
}
创建 SpringMVCConfig 配置类,代替 SpringMVC 的配置文件
配置视图解析器
-
在 config 包下创建配置类代替 spring-mvc.xml 配置文件并配置视图解析器
package com.laoyang.mvc.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.spring5.view.ThymeleafViewResolver; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ServletContextTemplateResolver; /** * @ClassName SpringMVCConfig * @Description: SpringMVC 配置类 * @Author Laoyang * @Date 2022/1/19 11:38 * <p> * 说明:该配置类用来代替 spring-mvc.xml 配置文件 */ // 将当前类标识为一个配置类 @Configuration // 开启组件扫描 @ComponentScan(value = {"com.laoyang.mvc"}) // 开启 mvc 注解驱动 @EnableWebMvc public class SpringMVCConfig { /** * 配置生成模板解析器(视图解析器) */ @Bean public ITemplateResolver templateResolver() { WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext(); // ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过 WebApplicationContext 的方法获得 ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver( webApplicationContext.getServletContext()); templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); templateResolver.setCharacterEncoding("UTF-8"); templateResolver.setTemplateMode(TemplateMode.HTML); return templateResolver; } /** * 生成模板引擎并为模板引擎注入模板解析器 */ @Bean public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); return templateEngine; } /** * 生成视图解析器并未解析器注入模板引擎 */ @Bean public ViewResolver viewResolver(SpringTemplateEngine templateEngine) { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setCharacterEncoding("UTF-8"); viewResolver.setTemplateEngine(templateEngine); return viewResolver; } }
包别导错了,视图解析器基本上都是固定写法
-
在 templates 目录下创建 index.html 页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> </body> </html>
-
创建对应的控制器
package com.laoyang.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @RequestMapping("/") public String doIndex() { return "index"; } }
-
启动Tomcat查看效果
此时我们启动 Tomcat 就可以访问到首页了
配置拦截器
-
创建 hello.html 页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello</title> </head> <body> <h1>Hello World!</h1> </body> </html>
-
在 index.html 中编写超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <a th:href="@{/doHello}">测试拦截器</a> </body> </html>
-
编写对应的控制器方法
package com.laoyang.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @RequestMapping("/doHello") public String doHello() { return "hello"; } }
-
创建拦截器
package com.laoyang.mvc.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @ClassName SpringMvcInterceptor * @Description: 拦截器 * @Author Laoyang * @Date 2022/1/19 16:49 */ public class SpringMvcInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle 被执行"); return true; } @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 被执行"); } }
-
在配置类中配置拦截器
package com.laoyang.mvc.config; import com.laoyang.mvc.interceptor.SpringMvcInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.*; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.spring5.view.ThymeleafViewResolver; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ServletContextTemplateResolver; /** * @ClassName SpringMVCConfig * @Description: SpringMVC 配置类 * @Author Laoyang * @Date 2022/1/19 11:38 * <p> * 说明:该配置类用来代替 spring-mvc.xml 配置文件 */ // 将当前类标识为一个配置类 @Configuration // 开启组件扫描 @ComponentScan(value = {"com.laoyang.mvc"}) // 开启 mvc 注解驱动 @EnableWebMvc public class SpringMVCConfig implements WebMvcConfigurer { /** * 拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { SpringMvcInterceptor interceptor = new SpringMvcInterceptor(); /* addInterceptor:注册拦截器 addPathPatterns:拦截的请求路径 excludePathPatterns:放行的请求路径 */ registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns("/").excludePathPatterns("/doHello"); } }
-
启动Tomcat查看效果
配置视图控制器
-
在配置类中配置视图控制器
package com.laoyang.mvc.config; import com.laoyang.mvc.interceptor.SpringMvcInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.*; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.spring5.view.ThymeleafViewResolver; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ServletContextTemplateResolver; /** * @ClassName SpringMVCConfig * @Description: SpringMVC 配置类 * @Author Laoyang * @Date 2022/1/19 11:38 * <p> * 说明:该配置类用来代替 spring-mvc.xml 配置文件 */ // 将当前类标识为一个配置类 @Configuration // 开启组件扫描 @ComponentScan(value = {"com.laoyang.mvc"}) // 开启 mvc 注解驱动 @EnableWebMvc public class SpringMVCConfig implements WebMvcConfigurer { /** * 视图控制器 */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/doHello").setViewName("hello"); } }
-
注释控制器中路径为
/doHello
的方法package com.laoyang.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { // @RequestMapping("/doHello") // public String doHello() { // return "hello"; // } }
-
启动Tomcat查看效果
配置文件上传解析器
-
导入相关依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.laoyang.mvc</groupId> <artifactId>springmvc-demo6</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!-- SpringMVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.1</version> </dependency> <!-- 日志 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- ServletAPI --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <!-- 依赖范围 provided:表示打成 war 包的时候,这个依赖不会一起打包 --> <scope>provided</scope> </dependency> <!-- Spring5和Thymeleaf整合包 --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.12.RELEASE</version> </dependency> <!-- 文件上传 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> </dependencies> </project>
需要导入最后那个文件上传的依赖,不然待会会报 500
-
在配置类中配置文件上传解析器
package com.laoyang.mvc.config; import com.laoyang.mvc.interceptor.SpringMvcInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.spring5.view.ThymeleafViewResolver; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ServletContextTemplateResolver; import java.util.List; import java.util.Properties; /** * @ClassName SpringMVCConfig * @Description: SpringMVC 配置类 * @Author Laoyang * @Date 2022/1/19 11:38 * <p> * 说明:该配置类用来代替 spring-mvc.xml 配置文件 */ // 将当前类标识为一个配置类 @Configuration // 开启组件扫描 @ComponentScan(value = {"com.laoyang.mvc"}) // 开启 mvc 注解驱动 @EnableWebMvc public class SpringMVCConfig implements WebMvcConfigurer { /** * 配置文件上传解析器 */ @Bean public CommonsMultipartResolver multipartResolver(){ return new CommonsMultipartResolver(); } }
如果没有什么需要添加的配置,直接返回即可,如果有需要添加的配置,可以在里面创建一个对象,然后设置对应的值即可。
-
启动Tomcat查看效果
- 这个 demo4 中有案例代码,大家可自行查看,这里就不给大家演示了,最后的效果都是可以正常实现的(基本上就是在页面创建一个文件域,然后在控制器方法中进行操作)
配置异常处理器
-
编写对应的控制器方法,手动制造异常
package com.laoyang.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @RequestMapping("/doHello") public String doHello() { int i = 10 / 0; return "hello"; } }
-
在配置类中配置异常处理器
package com.laoyang.mvc.config; import com.laoyang.mvc.interceptor.SpringMvcInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.spring5.view.ThymeleafViewResolver; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ServletContextTemplateResolver; import java.util.List; import java.util.Properties; /** * @ClassName SpringMVCConfig * @Description: SpringMVC 配置类 * @Author Laoyang * @Date 2022/1/19 11:38 * <p> * 说明:该配置类用来代替 spring-mvc.xml 配置文件 */ // 将当前类标识为一个配置类 @Configuration // 开启组件扫描 @ComponentScan(value = {"com.laoyang.mvc"}) // 开启 mvc 注解驱动 @EnableWebMvc public class SpringMVCConfig implements WebMvcConfigurer { /** * 配置异常处理器 */ @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); // 出现 ArithmeticException 异常的时候跳转到 error 页面进行处理 properties.setProperty("java.lang.ArithmeticException", "error"); resolver.setExceptionMappings(properties); // 将异常信息共享到请求域中 resolver.setExceptionAttribute("ex"); resolvers.add(resolver); } }
-
创建 error.html 页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>异常处理</title> </head> <body> <h1>程序存在风险,请谨慎操作!</h1> <p th:text="${ex}"></p> </body> </html>
-
启动Tomcat查看效果