1. spring mvc 应用:
1.1 MVC体系:
1.1.1 三层框架
传统的三层框架分为表现层, 业务层, 持久层。
表现层代表我们的web层, 接收客户端请求,向客户端相应结果。表现层分为展示层和控制层, 展示层就是我们说的html,jsp. 控制层就是日常工作中的controller。
业务层, 我们常说的service层, 服务业务处理。
持久层, 我们常说的dao层, 负责数据的持久化。
1.1.2 mvc模型
mvc是模型(model)-视图(view)-控制层(controller)的缩写。是创建web应用程序表现层的模式。
Model: 包含了业务模型和数据模型,数据模型[po,vo,dto…],业务模型用于处理业务。
view:通常指我们的jsp和html。
controller: 就是servlet。
spring mvc是作用于表现层的框架。
1.2 springmvc工作流程:
SpringMVC是⽬前最主流的MVC框架之⼀, servlet, struts需要实现接⼝,才能让java类成为请求控制器。而springmvc中只需要添加注解就可以让⼀个简单的Java 类成为处理请求的控制器,⽆须实现任何接⼝。同时它还⽀持RESTful编程⻛格的请求.
总之: Spring MVC和Struts2⼀样, 都是为了解决表现层问题的web框架,它们都是基于 MVC设计模
式的。⽽这些表现层框架的主要职责就是处理前端HTTP请求。本质是对servlet的封装,简化了serlvet的开发.
1.2.1 常规搭建spring mvc工程:
- 常规的以tomcat为服务器的web项目, 每一个servlet类都需要进行如下的配置, 然后在tomcat启动的时候解析xml, 并将类的全限定名和url放在集合中。以便去寻找对应点额servlet。在mvc框架中, 只有一个核心的servlet(DispatcherServlet). 所有的请求过来进行分发。所以这个servlet需要进行配置。

在web.xml中:
2.开发具体处理业务的handler(controller), 用RequestMapping注解标识. 此外, 这些请求控制器需要被spring管理, 所以需要在项目启动的时候需要去扫描这些类。

- 在业务处理完毕以后, 返回了视图(ModelAndView), 需要解析成对应的jsp文件。所以需要配置视图解析器。
1.2.2 详细的mvc请求处理流程:

第⼀步:⽤户发送请求⾄前端控制器DispatcherServlet
第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截
器(如果 有则⽣成)⼀并返回DispatcherServlet
第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
第五步:处理器适配器执⾏Handler
第六步:Handler执⾏完成给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个
底层对象,包括 Model和View
第⼋步:前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
第九步:视图解析器向前端控制器返回View
第⼗步:前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
第⼗⼀步:前端控制器向⽤户响应结果
1.2.3 url-pattern介绍:
拦截匹配规则的url请求, 进入springmvc框架处理,springmvc就会去找能够处理这个url的handler去执行业务逻辑. 
一般我们在做项目的时候, 都会配置成/ 去拦截web请求和静态文件。但是直接访问静态文件, 会报错(400,找不到匹配的处理请求的适配器)。

解决方案: 
1.2.4 请求参数绑定:
原生的servlet:从request中获取, 并进行类型转换
String data = request.getParameter(“data”);
Integer d = new Integer(data);
MVC简单参数:
直接在handler方法的形参中声明即可。框架会取出参数值并绑定到对应的参数上。其中对于不一致的参数,使用
@RequestParam注解适配


PS: 参数类型推荐使⽤包装数据类型,因为基础数据类型不可以为null,对于布尔类型的参数,请求的参数值为true或false。或者1或0
MVC复杂参数(POJO): 进行json交互
从前端到后端: 前端发送ajax请求, 后端接收pojo参数, 使用@RequestBody注解
从后端前端: 后台直接返回pojo对象,前端直接接收为json对象或者字符串,使⽤注解@ResponseBody
PS: 当然对于复杂参数,也可以进行表单提交, 对于后端来说, 使用POJO接收, 但是需要前后端参数定义要一致。
1.2.5 REST介绍:
常规的url请求:http:localhost:8080/query?id=1. 对于rest风格来说: http:localhost:8080/user/1(在url中定义了操作类型, 参数具体锁定了是哪个数据)
rest认为,互联网中所有东西都是资源, 既然是资源, 就可以唯一标识, 上面代表了用户id是1的信息。通过请求方式来(get 查,post 增,put 改,delete 删)
rest风格带来的直观上的体验: 传参方式的变化, 参数可以在uri中。
2. springmvc 高级使用:
2.1 Interceptor介绍:
2.1.1 拦截器, 过滤器的区别以及使用:
过滤器:过滤器配置比较简单,直接实现Filter接口就可以. 可以通过@WebFilter注解实现对特定的URL进行拦截。
@Order(1)@Component@WebFilter(filterName = "firstFilter", urlPatterns = "/*")public class FirstFilter implements Filter {// 容器每一次请求都会调用, FilterChain来调用下一个Filter@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("Filter 处理中...");chain.doFilter(request, response);}// 在容器启动的时候调用, 在整个生命周期只会调用一次。@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("Filter 前置");}// 容器销毁的时候调用, 一般进行资源关闭。生命周期只会调用一次。@Overridepublic void destroy() {System.out.println("Filter 后置");}}
拦截器: 链式调用, 拦截器配置是实现HandlerInterceptor来实现. 并将自定义的拦截器处理类进行注册, 并设定需要拦截或者排除的url。
@Component
public class FirstInterceptor implements HandlerInterceptor {
// 请求处理之前会调用, 如果返回false则当前请求结束。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("interceptor pre");
return true;
}
// 会在controller方法调用结束之后, DispatchServlet返回渲染视图之前调用。
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("interceptor post");
}
// 在请求结束之后 DispatchServlet返回渲染视图之后 调用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("interceptor after");
}
}
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**");
}
}
对于mvc项目, 使用xml配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.lagou.edu.interceptor.MyIntercepter02"/>
</mvc:interceptor>
</mvc:interceptors>
区别1: 实现原理不一致
filter是基于回调函数执行的, 在每一个过滤器执行完业务逻辑之后,都会调用FilterChain类, 而FilterChain中也会显示的调用下一个过滤器。如图所示:
具体代码逻辑如下:

区别2: 触发实际不同
Filter接口是在Servlet规范中定义的, 也就是说要依赖于Tomcat容器, 所以它只能在web程序中使用, 而Interceptor是一个spring组件, 不依赖tomcat, 运用范围广。
Filter是请求进入容器, 但是还未达到servlet之前进行预处理, 在servlet处理完之后请求才结束。
Interceptor是请求进入servlet之后, 未进入请求处理器(controller之前)进行预处理, Controller 中渲染了对应的视图之后请求结束。

区别3: 拦截返回不一致
Filter会拦截所有资源的访问(servlet, js/css). 拦截器只会拦截控制器方法.
区别4: 控制执行顺序不一致:
拦截器特殊地方, 先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。

2.2 异常处理机制:
使用注解@ExceptionHandler 就可以捕获某种类型的异常, 写一个全局的异常处理器(用@ControllerAdvice标识) 可以捕获所有的controller对象handler方法抛出的异常。所以一般是两者结合起来使用。
/**
* @ControllerAdvice 可以捕获所有的controller对象handler方法抛出的异常
*/
@ControllerAdvice
@Slf4j
public class SgrGlobalExceptionHandler {
/**
* @ExceptionHandler 捕获某一种特定的异常
* @ResponseBody 以字符串的形式返回
*/
@ExceptionHandler(CodedException.class)
@ResponseBody
public Result<String> unknownException(CodedException exception) {
log.warn("CodedException", exception);
return Result.fail(ResultEnum.SYSTEM_ERROR.getCode(), ResultEnum.SYSTEM_ERROR.getMsg());
}
/**
* 可以直接以response的形式返回回去
*/
@ExceptionHandler(CrmException.class)
public void capException(CrmException exception, HttpServletResponse response) {
try {
response.getWriter().write("---->" + exception.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 返回到固定的页面 error
*
* @param exception
* @return
*/
@ExceptionHandler(Exception.class)
public ModelAndView codedException(Exception exception) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("msg", exception.getMessage());
return modelAndView;
}
}
3. 手撕mvc:
3.1 mvc大致执行流程:
第一步: tomcat加载web.xml, 读取相关的servlet(在mvc中只有一个唯一的DispatchServlet) , 这里我们写一个自己的servlet。

第二步: 对于mvc框架来说, @Controller @RequestMapping注解是必不可少的。被@Controller标识的类会被spring容器管理, 被@RequestMapping标识的方法会被认定为是一个个处理具体业务的handler。我们需要定义自己的这些注解。



第三步: 在mvc容器中, 被@Controller, @Service标识的类都需要被容器管理, 被@AutoWired标识的字段, 都需要进行依赖注入维护。


第四步: 在mvc中的一个核心组件(处理器映射器), 实质就是建立url与handler的映射关系。
第五步, 上述一些都是初始化的操作, 真实的请求会调到dispatchservlet中, 并在里面进行分发。(原理其实就是从request中拿URI,继而找到相应的处理方法, 利用反射的机制进行调用)
4. springmvc 源码分析:
4.1 DispatcherServlet的继承结构:
4.2 源码分析mvc请求处理的大致流程:
所有的http请求都是进入到DispatcherServlet的dispatch方法:
正如mvc的流程图中所示, 请求在进到dispatch方法中的时候, 首先去找处理器映射器, 寻找对应的处理器。
处理器映射器会返回能够处理当前请求的执⾏链 HandlerExecutionChain(Handler+拦截器)。并在之后寻找Handler适配器。并执行拦截器前置方法。

实际处理器处理请求,返回结果视图对象
在handler返回结果后, 接着会执行拦截器的postHandler方法。
调⽤processDispatchResult()⽅法完成视图渲染跳转,并在最后调拦截器的afterCompletion
4.3 handle⽅法剖析:
进行适配器调用:
对参数进行处理, 将request参数转为handler的参数形式。并反射对目标方法进行调用。

4.4 九大组件初始化:
/* MultipartResolver used by this servlet. /
// 多部件解析器
private MultipartResolver multipartResolver;
// 区域化 国际化解析器
private LocaleResolver localeResolver;
// 主题解析器
private ThemeResolver themeResolver;
// 处理器映射器组件
private List
// 处理器适配器组件
@Nullableprivate List
// 异常解析器组件
private List
// 默认视图名转换器组件
private RequestToViewNameTranslator viewNameTranslator;
// flash属性管理组件
private FlashMapManager flashMapManager;
// 视图解析器
private List
九⼤组件都是定义了接⼝,接⼝其实就是定义了该组件的规范. 可扩展。
4.4.1 处理器映射器初始化:

如果按照类型和按照固定id从ioc容器中找不到对应组件,则会按照默认策略进⾏注册初始化,默认策略在DispatcherServlet.properties⽂件中配置

5. 补充:
5.1 RequestMapping的注册以及查找过程
5.1.1 注册:
在4.4中提到, HandlerMapping是处理器映射器. 它是一个接口, 有很多的实现类(RequestMappingHandlerMapping, BeanNameUrlHandlerMapping
�,RouterFunctionMapping…)。这些实现类都会被容器实例化. 以常用的RequestMappingHandlerMapping举例, 其继承的抽象类AbstractHandlerMethodMapping有一个成员变量MappingRegistry, 该成员变量存放RequestMappingInfo和HandlerMethod的映射关系。

步骤1: RequestMappingHandlerMapping实现了InitializingBean接口. spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPropertiesSet方法。
下述方法有一个判断, 该bean是一个处理器(有Controller或者RequestMapping注解)
根据Handler,Method,RequestMappingInfo将映射关系注册到map中
5.1.2 发现:
在4.2中介绍了mvc请求处理的大致流程, 其中关键的一步是查找处理器映射器集合, 寻找对应的处理器映射器.
重点流程如下:
AbstractHandlerMapping#getHandler
AbstractHandlerMethodMapping#getHandlerInternal
AbstractHandlerMethodMapping#lookupHandlerMethod
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
// 匹配结果列表
List<Match> matches = new ArrayList<>();
// 直接匹配
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
// 如果有匹配的,就添加进匹配列表中
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
// 还没有匹配,就遍历所有的处理方法去找
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
// 匹配结果不为空
if (!matches.isEmpty()) {
// 匹配结果比较器,直接使用匹配信息进行比较
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
// 对多个匹配结果进行排序
matches.sort(comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
// 最佳匹配
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
// 第一个元素和第二个元素进行比较
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
// 结果为0,至少有2个最佳匹配
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
// 多个最佳匹配,抛出异常
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
// 返回最佳匹配对应的处理器方法
return bestMatch.handlerMethod;
}
else {
// 没有匹配结果,执行无处理器匹配方法,可执行一些特殊逻辑
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
最终实现逻辑就是, 在MappingRegistry对象中根据请求获取相应的HandlerMethod对象.

