一、拦截器HandlerInterceptor
1、HandlerInterceptor接口
拦截器:作用在控制器方法执行前后,过滤器是在浏览器与服务器之间
SpringMVC中的拦截器需要实现HandlerInterceptor接口
其中有三个抽象方法,都有默认的实现。
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所处理的所有请求,不管是不是多层目录都拦截
配置方式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源码为证:
第1056行这里有个mappedHandler,它是处理器执行链,类型是HandlerExecutionChain
mappedHandler里面有三样东西:
- handler 浏览器发送的请求所匹配的控制器方法
- 拦截器集合interceptorList,处理控制器方法的所有拦截器集合,可能会有springmvc自己创建的拦截器
- 拦截器索引interceptorIndex,默认从-1开始,控制拦截器afterCompletion()的执行
1. 在控制器方法执行前,按正序执行preHandle(),看这里的是i++
2.在控制器方法执行之后,反序执行postHandle(),这里的是i--
3.页面渲染之后,或者某一个拦截器的 preHandle()方法返回false,则反序执行afterCompeltion,
也是i--,但是这里要注意的是afterCompletion()执行跟拦截器索引有关系,和拦截器集合无关
故有如下结论:若某个拦截器的preHandle()返回了false
preHandle()返回false和它之前的拦截器的preHandle()都会按配置顺序执行,
postHandle()都不执行,
返回false的拦截器之前的拦截器的afterComplation()会按配置反序执行
二、异常处理器
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);
}
该接口的实现类
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>
测试结果:
三、注解配置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的映射路径 getServletMappings4.设置过滤器 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常用组件:
- DispatcherServlet:统一处理请求和响应,整个流程控制的中心
- HandlerMapping:把请求和控制器方法匹配(映射)
- Handler:即控制器方法,具体的处理请求和响应的过程
- HandlerAdapter:调用控制器方法 ha.handle()
- ViewResolver:进行视图解析,得到相应的视图
- View:将动态数据展示在页面中
DispatcherServlet初始化过程:
DispatcherServlet 本质上是一个 Servlet,天然的遵循 Servlet 的生命周期
几经曲折:
GenericServlet做的事:
init(ServletConfig config) 调用一个空参的init()
HttpServletBean做的事:
init()去调用 initServletBean()
FrameworkServlet做的事:
initServletBean()去调用initWebApplicationContext()
这里这个方法initWebApplicationContext() 就开始干正事了
- 创建ioc和父容器
- 刷新容器
- 把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调用组件处理请求
图还是那张图,这次来看处理请求的过程
几经曲折:
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) 将渲染结果返回给客户端。