Spring MVC篇
Spring MVC执行流程
四大组件
- 前端控制器DispatcherServlet
- 处理器映射器HandlerMapping
- 处理器适配器HandlerAdaptor
- 视图解析器ViewResolver
视图阶段(JSP)
-
请求先到前端控制器DispatcherServlet
-
DispatcherServlet将根据该请求的路径去处理器映射器HandlerMapping查handler(controller的一个方法)
-
映射器HandlerMapping找到后返回一个处理器执行链HandlerExecutionChain对象(封装了handler + 可能存在的拦截器链)
-
如果没有拦截器,DispatcherServlet通过该处理器执行链对象发请求给处理器适配器HandlerAdaptor 调用处理器Handler
为什么不直接发请求给处理器Handler,而是要走个处理器适配器HandlerAdaptor再转发给它呢?
markdown1. 你的请求参数被接口接收有多种方式吧?(restful风格接收、参数位直接接收...)这里主要就是通过处理器适配器中一些处理参数的类型转换器,你的方法才能正常的接收这些参数。 1. 你的返回值也多种形式吧?(String、MoudleAndView....),这些的返回值数据的转换也要靠处理器适配器中一些处理返回值的类型转换器来实现。
-
处理器响应数据给处理器适配器,它转换后返回个MoudleAndView给前端控制器
-
前端控制器拿着这个MoudleAndView找视图解析器ViewResolver去把逻辑视图转换为真正视图,返回个视图对象View给前端控制器
-
前端控制器就拿着这个视图对象去渲染视图JSP给前端页面
前后端分离阶段 (接口开发,异步请求)
- 请求先到前端控制器DispatcherServlet
- DispatcherServlet将根据该请求的路径去处理器映射器HandlerMapping查handler(controller的一个方法)
- 映射器HandlerMapping找到后返回一个处理器执行链HandlerExecutionChain对象(封装了handler + 可能存在的拦截器链)
- 如果没有拦截器,DispatcherServlet通过该处理器执行链对象发请求给处理器适配器HandlerAdaptor 调用处理器Handler
- 如果这个方法是添加了@ResponseBody注解,则通过HttpMessageConverter来返回结果转换为JSON并响应
- 如果这个方法没有添加该注解,视图解析器来拼接视图前后缀到返回数据上,然后到/resources/static下找到对应的静态资源丢给视图解析器,解析到实际的视图对象后,DispatcherServlet 将结果数据传递给视图对象,视图对象负责将数据渲染成最终的响应内容。视图对象渲染完成后,DispatcherServlet 将响应返回给前端
过滤器和拦截器的区别
相同点
- 拦截器与过滤器都是体现了AOP的思想,对方法实现增强,都可以拦截请求方法。
- 拦截器和过滤器都可以通过Order注解设定执行顺序
不同点
- 过滤器是在 Servlet 容器接收到请求之后,但在 Servlet被调用之前运行的;而拦截器则是在 Servlet 被调用之后,但在响应被发送到客户端之前运行的。
-
过滤器是运行在Web服务器和Servlet容器之间的组件,可以拦截所有进出该容器的请求和响应;而拦截器则是针对具体的控制器方法进行拦截处理的,只在控制器内部执行。
-
过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。
-
过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用;拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。
-
过滤器主要用于对请求进行预处理和过滤,例如设置字符集、登录验证、日志记录等操作;而拦截器则主要用于对请求进行流程控制,例如权限验证、参数注入、异常处理等操作。
Spring MVC的异常处理
在 Spring MVC 中,异常处理可以通过以下几种方式来实现:
-
异常处理器(ExceptionHandler)注解 :通过在 Controller 类中的方法上添加
@ExceptionHandler
注解来指定要处理的异常。当抛出该类型的异常时,会夹带异常信息调用对应的异常处理方法。 -
控制器增强(ControllerAdvice)注解 :本质上就是个Controller,将捕捉到的异常数据像发请求一样落到一个handler上。在这个类中,可以定义
@ExceptionHandler
注解的方法来处理不同类型的异常,这样可以实现全局统一的异常处理。scala@RestControllerAdvice public class AGlobalExceptionHandlerController extends ABaseController { @ExceptionHandler(value = Exception.class) Object handleException(Exception e, HttpServletRequest request) { logger.error("请求错误,请求地址{},错误信息:", request.getRequestURL(), e); ResponseVO ajaxResponse = new ResponseVO(); xxxxx设置一大堆要发给前端展示的消息到这个封装类ResponseVO中返给前端。XXXXXX return ajaxResponse; } }
-
异常处理器接口(HandlerExceptionResolver) :可以自定义实现
HandlerExceptionResolver
接口来定义异常处理器。该接口允许实现多个异常处理器,在异常发生时根据优先级选择合适的处理器。 -
异常映射(@ResponseStatus)注解: 使用
@ResponseStatus
注解可以将特定异常映射到指定的HTTP响应状态码或自定义的错误页面。当抛出该异常时,会根据注解中的配置来进行相应的处理。
业务进行流程:
- 后端出现异常
- 抛出自定义异常
- 异常处理器@Exceptional捕获到该异常信息
- 执行异常处理器中自定义的方法
- 将捕捉到的自定义异常中的友好提示信息及其相关的响应码设置到统一的响应类对象中进行响应
- 前端接收到响应数据,提取响应信息用于展示给用户
SpringMvc的Controller是不是单例模式?
Spring MVC 的 Controller 默认是单例模式的。这意味着在应用程序的整个生命周期内,只会创建一个 Controller 实例,并且该实例会被多个请求共享。这样做的好处是可以减少对象创建和销毁的开销,提高性能。
然而需要注意的是,由于 Controller 是单例的,所以它必须是无状态的,不应该包含任何可以改变状态的成员变量。如果需要在 Controller 中保存状态,可以使用作用域为请求的成员变量或者使用其他方式来管理状态,比如使用 Session 或者数据库。
当然,如果需要每次请求都创建新的 Controller 实例,可以使用 @Scope
注解来设置为原型模式(Prototype),这样每次请求都会创建一个新的实例。
SpringMVC 用什么对象从后台向前台传递数据的?
不使用@responseBody / @RestController注解修饰
从后台向前台传递数据使用的是 ModelAndView 对象。ModelAndView 对象包含了数据模型(Model)和视图(View),其中数据模型是一个 Map 类型的对象,用于存储后台数据,而视图则是要展示数据的页面。
使用@responseBody / @RestController注解修饰
在使用 @ResponseBody
后,Spring 会自动将返回值转换为特定的格式,例如 JSON、XML 等,然后将其写入 HTTP 响应。需要注意的是,如果采用了 @ResponseBody
,则需要使用 Jackson 库或其他类库来进行序列化(JSON 转换)。Spring 默认使用 Jackson 序列化 JSON 数据,如果需要使用其他序列化类库,可以在 Spring 配置文件中进行配置。
Spring MVC 拦截器
拦截器则主要用于对请求进行流程控制,例如权限验证、参数注入、异常处理等操作。
拦截器 Interceptor 定义
拦截器不仅可以在处理器之前执行,还可以在处理器之后执行。先看拦截器 Interceptor 在 Spring MVC 中的定义。
less
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
前后端分离之后,Spring MVC 中的处理器方法执行后通常不会再返回视图,而是返回表示 json 或 xml 的对象,@Controller 方法返回值类型如果为 ResponseEntity 或标注了 @ResponseBody 注解,此时处理器方法一旦执行结束,Spring会将返回值转换为 json 或 xml,然后写入响应,后续也不会进行视图渲染,这时postHandle
将没有机会修改响应体内容。
如果需要更改响应内容,可以定义一个实现 ResponseBodyAdvice 接口的类,然后将这个类直接定义到 RequestMappingHandlerAdapter 中的 requestResponseBodyAdvice 或通过 @ControllerAdvice 注解添加到 RequestMappingHandlerAdapter。
拦截器配置
- 自定义拦截器类,实现HandlerInterceptor 接口和
Ordered
接口 - 自定义的拦截器选择性重写三大函数,重写顺序函数
- 注册为Bean
- 实现WebMvcConfigurer接口,注册自定义的拦截器
java
public class LoginInterceptor implements HandlerInterceptor, Ordered {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("已登录");
return true;
}
@Override
public int getOrder() {
return 2;
}
}
less
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor());
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");
}
}
typescript
@Configuration
public class MvcConfig {
@Order(2)
@Bean
public MappedInterceptor loginInterceptor() {
return new MappedInterceptor(new String[]{"/**"}, new String[]{"/login"}, new LoginInterceptor());
}
@Order(1)
@Bean
public MappedInterceptor logInterceptor() {
return new MappedInterceptor(null, new LoginInterceptor());
}
}