一、拦截器HandlerInterceptor

1、HandlerInterceptor接口

拦截器:作用在控制器方法执行前后,过滤器是在浏览器与服务器之间

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_拦截器

SpringMVC中的拦截器需要实现HandlerInterceptor接口

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_拦截器_02

其中有三个抽象方法,都有默认的实现。
 1、preHandle():在控制器方法执行之前执行,该方法的返回值为boolean,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
 2、postHandle():在控制器方法执行之后执行
 3、afterComplation():页面渲染之后执行

public class FirstInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("FirstInterceptor-->preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("FirstInterceptor-->postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("FirstInterceptor-->afterCompletion"); } }


2、SpringMVC配置拦截器

要想拦截器起效,需要在SpringMVC的配置文件“springMVC.xml”中进行配置

配置方式1: <bean> 把某一个类设置为拦截器

<!--配置拦截器-->
    <mvc:interceptors>
        <!--这样配置拦截器,拦截器会拦截DispatcherServlet所处理的所有请求-->
        <bean class="com.atguigu.interceptor.FirstInterceptor"/>
    </mvc:interceptors>

拦截器会拦截DispatcherServlet所处理的所有请求,不管是不是多层目录都拦截

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_java_03


配置方式2:<ref>引用某一个bean

给拦截器FirstInterceptor标注@Component注解,在这里使用<ref>引用,这种方式配置的拦截器也是会拦截DispatcherServlet所处理的所有请求,不管是不是多层目录都拦截

<!--配置拦截器-->
    <mvc:interceptors>
        <!--这样配置拦截器,拦截器也会拦截DispatcherServlet所处理的所有请求-->
        <ref bean="firstInterceptor"/>
    </mvc:interceptors>

配置方式3:自定义需要拦截和拦截排除的请求

这里,要注意我为什么配置“/**”,“/*” 在过滤器是过滤所有请求。但是在拦截器里就不是这样子了

/*  :拦截上下文下任意一层目录的请求
/** : 拦截任意层目录的请求,即所有请求

<!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--设置需要拦截的请求,/**表示所有请求-->
            <mvc:mapping path="/**"/>
            <!--设置需要排除的请求,即拦截器不会拦截的请求-->
            <mvc:exclude-mapping path="/hello"/>
            <!--通过ref或bean标签设置拦截器,-->
            <ref bean="firstInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

经过我自己测试认证了:不管什么请求都会被拦截器拦截,只有"/hello"请求,是拦截器不会拦截的!管用


3、多个拦截器的执行顺序

多个过滤器,过滤器执行顺序跟 filter-mapping有关系。

多个拦截器的执行顺序只跟在SpringMVC的配置文件中配置的顺序有关

 1、每个拦截器的preHandle()返回true的情况
    preHandle()会按照拦截器配置的顺序执行

    而postHandle()和afterCompletion()会按照反序执行
 2、某个拦截器的preHandle()返回false的情况
    返回false的拦截器和它之前的拦截器的preHandle()都会执行
    所有拦截器的postHandle()都不执行
    返回false的拦截器之前的拦截器的afterCompletion()会执行

假设有5个拦截器,拦截器的配置顺序是 a b c d e ,只有c拦截器的preHandle()返回false,那么?

preHandle():只有abc执行
postHandle():都不行执行
afterCompeltion():只有ab执行

看DispatcherServlet源码为证:

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_spring_04

第1056行这里有个mappedHandler,它是处理器执行链,类型是HandlerExecutionChain

mappedHandler里面有三样东西:

  1. handler 浏览器发送的请求所匹配的控制器方法
  2. 拦截器集合interceptorList,处理控制器方法的所有拦截器集合,可能会有springmvc自己创建的拦截器
  3. 拦截器索引interceptorIndex,默认从-1开始,控制拦截器afterCompletion()的执行

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_mvc_05

1. 在控制器方法执行前,按正序执行preHandle(),看这里的是i++

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_java_06

 2.在控制器方法执行之后,反序执行postHandle(),这里的是i--

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_java_07

3.页面渲染之后,或者某一个拦截器的 preHandle()方法返回false,则反序执行afterCompeltion,

也是i--,但是这里要注意的是afterCompletion()执行跟拦截器索引有关系,和拦截器集合无关

故有如下结论:若某个拦截器的preHandle()返回了false
preHandle()返回false和它之前的拦截器的preHandle()都会按配置顺序执行,
postHandle()都不执行,
返回false的拦截器之前的拦截器的afterComplation()会按配置反序执行

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_拦截器_08


二、异常处理器

1、注解方式(推荐)

@ControllerAdvice+@ExceptionHandler:异常集中处理,更好的使业务逻辑与异常处理剥离开;

ExceptionHandler 的使用场景就是在 Controller 中捕获异常,全局统一处理,而不是在每个 handler 中都进行繁琐的异常捕获操作,优点就是代码整洁。

-----------------------------------

@ControllerAdvice将当前类标识为异常处理的组件

@ExceptionHandler表示控制器方法出现指定的异常(这个异常可以是自定义异常),就会执行注解标识的这个方法

@ExceptionHandler注释的方法可以具有灵活的输入参数(详细参见Spring API):

  • 异常参数:包括一般的异常或特定的异常(即自定义异常),如果注解没有指定异常类,会默认进行映射。
  • 请求或响应对象 (Servlet API or Portlet API): 你可以选择不同的类型,如ServletRequest/HttpServletRequest或PortleRequest/ActionRequest/RenderRequest
  • Session对象(Servlet API or Portlet API): HttpSession或PortletSession。
  • WebRequest或NativeWebRequest 
  • Locale
  • InputStream/Reader 
  • OutputStream/Writer 
  • Model

方法返回值可以为:

  • ModelAndView对象
  • Model对象
  • Map对象
  • View对象
  • String对象
  • 还有@ResponseBody、HttpEntity<?>或ResponseEntity<?>,以及void
@ControllerAdvice //将类标识为异常处理的组件
public class ExceptionController {

    //@ExceptionHandler表示控制器方法出现指定异常,使用注解标识的方法处理异常
    @ExceptionHandler(ArithmeticException.class)
    public String testArithmeticException(Exception ex, Model model){
        //ex表示当前请求处理中出现的异常对象
        model.addAttribute("ex", ex);
        return "error";
    }
}

错误页面error.html换一下内容

<h1>总为浮云能蔽日,长安不见使人愁 - 苏烈</h1> <p th:text="${ex}"></p>


2、HandlerExceptionResolver

接口HandlerExceptionResolver:SpringMVC提供的一个处理控制器方法执行过程中所出现的异常

该接口只有一个方法:resolveException

public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, @Nullable Object var3, Exception var4);
}

该接口的实现类

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_拦截器_09

DefaultHandlerExceptionResolver :处理一些默认的异常,如405

SimpleMappingExceptionResolver:让我们去用的


3、xml方式(扩展)

xml方式配置bean,来创建异常处理器

拿捏它: 出现什么异常的时候跳转到什么页面,并给请求域共享什么异常信息

<!--配置异常解析器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!--设置异常映射,即出现指定异常时,设置要跳转的页面的逻辑视图-->
        <property name="exceptionMappings">
            <props>
                <!--key设置要处理的异常类型,value表示当出现该异常时要跳转页面所对应的逻辑视图-->
                <prop key="java.lang.ArithmeticException">error</prop>
            </props>
        </property>
        <!--若出现异常,设置将异常信息共享到请求域中的属性名,相当于setAttribute("ex",异常信息)-->
        <property name="exceptionAttribute" value="ex"></property>
    </bean>

我们故意在控制器方法里写一行1/0,并写好要跳转的错误页面error.html

<h1>与其借风而行,不如猎风于掌中 - 吕布</h1> <p th:text="${ex}"></p>

测试结果:

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_mybatis_10


三、注解配置SpringMVC

使用配置类和注解代替web.xml和SpringMVC配置文件的功能

web.xml 部署描述符,用来配置servlet容器。

第一点:

在Servlet3.0(指tomcat环境)环境中,容器(tomcat)会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器


第二点:

Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。

1.创建初始化类WebInit,代替web.xml(用的不多)

1.设置spring的配置类 getRootConfigClasses ----SpringConfig.java
2.设置SpringMVC的配置类 getServletConfigClasses-----WebConfig.java
3.设置DispatcherServlet的映射路径  getServletMappings

4.设置过滤器   getServletFilters

public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    //设置Spring的配置类
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    @Override
    //设置SpringMVC的配置类
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    //设置前端控制器DispatcherServlet的映射路径,即url-pattern
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    //设置过滤器
    protected Filter[] getServletFilters() {
        //编码过滤器
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter("UTF-8", true);
        //处理请求方式的过滤器
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }
}

2.创建SpringConfig配置类,代替spring的配置文件

代替配置文件的类,必须加@Configuration

@Configuration:将当前类标识为配置类,代替spring的配置文件

@Bean 将方法的返回值交给ioc容器管理,方法名就是bean的id

//将类标识为配置类
@Configuration
public class SpringConfig {
 //ssm整合之后,spring的配置信息写在此类中
}

3.创建WebConfig配置类,代替SpringMVC的配置文件

在原来的springMVC.xml中,我们配置过的有 :扫描组件、视图解析器、默认的servlet、开启mvc的注解驱动、视图控制器、文件上传解析器、拦截器、异常处理器

//将类标识为配置类
@Configuration
//扫描组件
@ComponentScan("com.atguigu.controller")
//开启mvc的注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    //使用默认的servlet处理静态资源
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    //配置视图控制器
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }

    @Bean
    //设置文件上传解析器
    public CommonsMultipartResolver multipartResolver(){
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        return multipartResolver;
    }

    @Override
    //配置拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        TestInterceptor testInterceptor = new TestInterceptor();
        registry.addInterceptor(testInterceptor).addPathPatterns("/**").excludePathPatterns("/hello");
    }

    @Override
    //配置异常处理器
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
        Properties prop = new Properties();
        prop.setProperty("java.lang.ArithmeticException", "error");
        exceptionResolver.setExceptionMappings(prop);
        exceptionResolver.setExceptionAttribute("ex");
        resolvers.add(exceptionResolver);
    }

    //配置生成模板解析器
    @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;
    }
}

四、springMVC执行流程

springmvc常用组件:

  1. DispatcherServlet:统一处理请求和响应,整个流程控制的中心
  2. HandlerMapping:把请求和控制器方法匹配(映射)
  3. Handler:即控制器方法,具体的处理请求和响应的过程
  4. HandlerAdapter:调用控制器方法 ha.handle() 
  5. ViewResolver:进行视图解析,得到相应的视图
  6. View:将动态数据展示在页面中

DispatcherServlet初始化过程:

DispatcherServlet 本质上是一个 Servlet,天然的遵循 Servlet 的生命周期

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_java_11

几经曲折:

GenericServlet做的事:

init(ServletConfig config) 调用一个空参的init()

HttpServletBean做的事:

init()去调用 initServletBean()

FrameworkServlet做的事:

initServletBean()去调用initWebApplicationContext()

这里这个方法initWebApplicationContext() 就开始干正事了

  1. 创建ioc和父容器
  2. 刷新容器
  3. 把ioc容器共享到了应用域

spring ioc容器是springmvc ioc容器的父容器
子容器可以访问父容器的bean/父容器不可以访问子容器的bean

DispatcherServlet做的事:

重写刷新容器:onRefresh()方法,初始化策略,对组件进行了初始化

protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

DispatcherServlet调用组件处理请求

图还是那张图,这次来看处理请求的过程

springboot拦截器修改返回值HandlerInterceptor postHandle springmvc拦截器返回false_java_11

几经曲折:

HttpServlet做的事:

重写了:service(ServletRequest req, ServletResponse res)
调用了重载的 service(HttpServletRequest req, HttpServletResponse resp)

FrameworkServlet做的事:

重写了service(HttpServletRequest request, HttpServletResponse response)
几经折腾,调用processRequest(HttpServletRequest request, HttpServletResponse response)
在processRequest()里又调用了抽象方法doService(request, response);

DispatcherServlet做的事:

重写doService(HttpServletRequest request, HttpServletResponse response)
调用了doDispatch(HttpServletRequest request, HttpServletResponse response);

doDispatch方法才是做正事的,

//mappedHandler:处理器执行链
mappedHandler = getHandler(processedRequest);//1037
//通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//1044
//调用拦截器的preHandle()
mappedHandler.applyPreHandle(processedRequest, response)//1056
//由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//1061
//调用拦截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);//1068
//后续处理:处理模型数据和渲染视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);//1078

doDispatch()调用processDispatchResult()做的事:

// 处理模型数据和渲染视图
render(mv, request, response);//1139

// 调用拦截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, null);//1157

面试题:springmvc执行流程

低调使用!!!我怕我们老师拿着30米大刀来找我,虽然听他时候自己脾气好,但是他也说了一句要是真发起火来,能做到你想象不到的事情。。。

1) 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。

2) DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:

a) 不存在

i. 再判断是否配置了mvc:default-servlet-handler

ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误

iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误

b) 存在则执行下面的流程

3) 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。

4) DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。

5) 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】

6) 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

6.1 HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息

6.2 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

6.3 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

6.4 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

7) Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。

8) 此时将开始执行拦截器的postHandle(...)方法【逆向】。

9) 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。

10) 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。

11) 将渲染结果返回给客户端。