SpringMVC拦截器与异常处理器(含注解配置SpringMVC)

拦截器

  • 创建一个新的工程用来测试代码,前面已经创建过很多次了,初始代码就是那些,所以这里就不写了,可看下面的大致架构

    在这里插入图片描述

Controller 和 html 页面可以先创建好,后面需要写了在写;web、spring-mvc、pom 都是和之前一样的

什么时候会触发拦截器?

在这里插入图片描述

拦截器的配置

  • SpringMVC 中的拦截器用于拦截控制器方法的执行。
  • SpringMVC 中的拦截器需要实现 HandlerInterceptor 接口。
  • SpringMVC 的拦截器必须在 SpringMVC 的配置文件中进行配置

拦截器的三个方法

SpringMVC 中的拦截器有三个方法

  1. preHandle:控制器方法执行之前执行 preHandle(),其 boolean 类型的返回值表示是否拦截或放行,返回 true 为放行,即调用控制器方法;返回 false 表示拦截,即不调用控制器方法。
  2. postHandle:控制器方法执行之后执行 postHandle() 方法
  3. 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 接口的实现类有:DefaultHandlerExceptionResolverSimpleMappingExceptionResolver

  • 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>-->
    <!--                &lt;!&ndash;-->
    <!--                    exceptionMappings 参数存储的是键值对的数据-->
    <!--                    这里 key=java.lang.ArithmeticException(算术异常)-->
    <!--                        value=error(视图名称)-->
    <!--                     key 表示处理器方法执行过程中出现的异常-->
    <!--                     value 表示若出现 key中指定的异常,则跳转到指定页面-->
    <!--                 &ndash;&gt;-->
    <!--                <prop key="java.lang.ArithmeticException">error</prop>-->
    <!--            </props>-->
    <!--        </property>-->
    <!--        &lt;!&ndash;-->
    <!--           将异常信息共享在请求域中的键-->
    <!--           value为键(可随便取名,见名知意即可),异常信息为值,所以这里就表示:ex=异常信息-->
    <!--           > 如果要在页面获取请求域的异常信息,直接使用 ex 即可。-->
    <!--           > 简单理解:给 exceptionAttribute 属性设置一个属性名,属性名为 ex,然后该属性的值就是异常信息-->
    <!--         &ndash;&gt;-->
    <!--        <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查看效果

    在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值