1 http请求过来了,经过springmvc发生了哪些事情?
- http请求,数据进servlet
- servlet的输入是HttpServletRequest,里面封装了请求的上下文
- servlet的输出是HttpServletResponse,输出的是文本数据或二进制数据。
没错,servlet就是做了这些事情。
2 如果没有springmvc,我们应该如何手写代码呢?
看看原始的Servlet编程模型:
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.getWriter().println("Hello World!");
}
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException{
res.getWriter().println("Hello World!");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException{
res.getWriter().println("Hello World!");
}
- 我们要手动解析httprequest了
- 我们要手动映射Model对象了
- 我们要手动输出响应了,如果是JSON格式的还好,如果要返回HTML,这个怎么搞?
- 如果要统一处理参数,这个相对要麻烦很多了
- 如果要统一处理异常,这个也许会好一点,通过filter搞。
- 没有springmvc的拦截器,我们只能通过filter去搞一些前置和后置工作了
- 我们要手动维护url和handler之间的关系了
3 有了springmvc,我们将有什么收获呢?
3.1 自动帮你填充HttpServletRequest参数
/**
* springMvc会自动给request赋值
* 如:
POST /login?mobile=1234567890&passowrd=123456
自动把url里面的全部上下文都入到request对象中
**/
public String demo(HttpServletRequest request){
String mobile = request.getParameter("mobile");
String password = request.getParameter("password");
return "demo";
}
3.2 自动帮你填充普通变量
简单变量,可以自动赋值,省去从request里面取参数。
如请求:
POST /user?id=12&name=heha
springMVC自动把url查询参数绑定id绑定到控制器的demo方法id入参上,自动把url里的查询参数name绑定到demo方法的入参name上。
public String demo(long id,String name){}
3.3 自动帮你填充对象
如下所示,springmvc会自动将User对象赋值,如果User是一个复杂的表单对象,想想能省多少事。
如请求 POST /user?id=12&name=hhhaaa,springMVC自动把请求的路径参数绑定到demo方法的入参user对象中。
public String demo(User user){}
3.4 自动帮你转换响应
如果是返回的是对象,而且不是ModelAndView对象,则SpringMVC推断返回的是json数据,会自动转换为json数据。
Map autoConvertMaptoJson(HttpServletRequest request){}
或
User autoConvertObjectToJson(HttpServletRequest request){}
如果返回的是String,那么,SpringMVC会推断是不是返回页面,如果是返回页面,那么springMVC会先找到路径,然后根据返回值拼接模板文件。根据模板文件的类型,找到视图解析器,
通过调用解析器,将模板文件渲染,输出到response的writer里面。
如:
String index(){
return "index";
}
3.5 自动异常处理
通过@ControllerAdvice,@RestControllerAdvice注解,可以处理全局的异常。
如果不需要返回json数据,而是返回页面,可以这么搞。
@ExceptionHandler(value = MyException.class)
public ModelAndView myErrorHandlerOfHtml(MyException ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("code", ex.getCode());
modelAndView.addObject("msg", ex.getMsg());
modelAndView.addObject("extra",ex.getExtra());
return modelAndView;
}
如果要返回json数据,则更简单了。
@ExceptionHandler(value = MyException.class)
public Map myErrorHandlerOfJson(MyException ex) {
Map result = new HashMap();
result.put("code", ex.getCode());
result.put("msg", ex.getMsg());
result.put("extra",ex.getExtra());
return result;
}
3.6 丰富的拦截器
- 在这里加解密
- 在这里做安全验证
- 在这里做规则路由
- 在这里搞点日志之类的东东
拦截器有白名单和黑名单配置,通过黑白名单机制,灵活路由。
3.7 自定义转换参数
参考前面的参数绑定,可以实现更复杂的参数绑定功能,将一些不存在的参数自动绑定到handler方法的入参中。
3.8 支持多种视图
json,jsp,FreeMarker,thymeleaf等,统统支持。
4 那么,springMvc是如何具备这种能力的呢?
springMVC通过整合9大组件,来实现对外提供url路由,参数绑定,业务处理前后责任链式打点拦截,业务处理,结果返回,异常结果处理,视图渲染等工作。
4.1 springMVC源码目录结构简介
以spring-webmvc-4.3.5.RELEASE为例,反编译如下。
spring-webmvc是springMVC的一部分,还有大量框架代码在spring-web里面实现。
spring-webmvc目录结构简介如下:
- config: 和spring紧密结合的类,主要是支持各种Bean的解析工作,如XML配置和注解配置。
- handler: 具体干活的,如URL路由,异常处理等。
- i18n:国际化的,忽略
- mvc:做各种忙前忙后的工作,这种工具,是通过spring-web:4.3.5.RELAESE来开展开的。
- view: 支持不同的模板引擎,如freemarker,jsp,thymeleaf等
这是干活的包,@Controller和@RestController就是在这里调用的。
这个包里的业务逻辑,好多是调用spring-web:4.3.5.RELEASE实现的。
这是view包,用于渲染和种模板引擎用的。
DispatchServlet是核心调度类,所有的请求,都会类DispatchServlet类处理。
DispatchServlet有2大功能:
- 初始化
- 处理http请求
4.2 springMVC初始化
springMVC环境初始化时,会初始化9大组件。
容器初始时,会调用OnRefresh()接口,在OnRefresh()里面,执行初始化。初始化的9大组件有:
- initMultipartResolver(context); 如果是上传的文件,在这里绑定文件到request里面
- initLocaleResolver(context); 国际化相关的
- initThemeResolver(context); 国际化相关的
- initHandlerMappings(context); springMVC要面向不同的URL,handleringMapping是用于路由的,通过 url来查找对应的控制器。每个handler其实就是控制器下面的方法。即标注为@RequestMapping的东西。初始化的时候,肯定会用通过@RequestMapping来生成一个维护URL和method的Map结构的。
- initHandlerAdapters(context); 从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。 小结:Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
- initHandlerExceptionResolvers(context); 如果出现了异常怎么办?HandlerExceptionResolvers来帮忙,想想咱们以前用过的@ControllerAdvice,@RestControllerAdvice,在初始化的时候,会根据这2个注解,来维护内部的异常处理解析器关系的。
- initRequestToViewNameTranslator(context); handler返回的是一个字符串,如何从字符串找到对应的页面模板文件,RequestToViewNameTranslator就是干这种活的。
- initViewResolvers(context); ViewResolver将页面模板文件,用Model里面的数据结合,渲染输出页面。
- initFlashMapManager(context); 用来管理FlashMap的,FlashMap主要用在redirect中传递参数。
如果没有外部指定,springMVC会执行默认初始化测试,读取同目录下的DispatcherServet.properties文件,完成初始化工作。
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
初始化9大组件的过程,非常复杂,也非常精彩,里面用到了spring bean非常多的功能,有兴趣的可以详细看看。
4.3 处理HTTP请求
springMVC环境初始化完成后,可以接受外界请求了,这时候,HPPT请求实际上请求是通过Servlet进来的。然后通过模板方法的模式,在DispatcherServlet的doDispatch()方法中,执行实际的处理逻辑。
doDispatch()的处理逻辑如下:
- 看是不是有文件上传进来了
- 通过request,来查表,找到对应干活的handler.
- 拿到handler之后,来获取HandlerAdapter,由HandlerAdapter由干具体的活,这时候,handlerAdapter还没有干活哦
- 执行拦截器的前置拦截,还记得preHander么,在这里拦,如果被拦截了,就直接返回了。
- HandlerAdapter要执行主体业务逻辑了
- 执行拦截器的后置拦截,还记得postHander么
- 检查是否有异常,如果有异常,则通过异常解析器,进行异常处理,还记录@ControllerAdvice,@RestControllerAdvice么
- 如果没有异常,那么,如果返回HTML页面,则渲染视图;如果返回josn数据,则序列化返回结果。
主业务流程如下图所示。
网上有图,盗图一张。
4.3.1 HandlerMapping
url和拦截器的绑定关系,在初始化阶段完成了,所以呢?初始化很慢的。debug的时候就能发现,debug太慢了。 拦截器只认request,不认各种解析后的参数。
4.3.2 HandlerAdapter
HandlerAdapter处理主体业务逻辑,这里面内容太多了。HandlerAdapter简直是个大管家。它要:
- handler干活前,需要将干活的参数准备好,将url里面的东西,转化为Controller的方法里面的入参。
- handler干活前,需要执行前置的工作,如@ModelAttribute注解的东西,将数据放model里面。
- handler干完活后,还需要执行后置工作,对返回值进行处理。
从HandlerAdapter的初始化就能看到这些。如下图所示。
我们在浏览器中,输入URL看看
http://localhost:8080/rest/demo?user=jpnie&age=18
在这里,我们看看参数是如何绑定的。
4.3.2.1 拿handlerAdapter, 找项目经理
handlerAdapter是项目经理,负责各路资源调度。handler是民工,只负责根据method的参数,执行业务逻辑,然后再返给handlerAdapter去整合。
4.3.2.2 输入参数绑定
invokeForRequest()里面,会拿httprequest,去获取方法参数,即getMethodArgumetnValues(),将request里面的参数中,提取出方法参数,如前面所述的简单变量,复杂对象等。
4.3.2.3 返回值处理
controller的method方法,返回的一般是String,Map或者是PO对象,也可能是ModelAndView.HandlerAdapter通过handler拿到返回值的,可以对返回值进行加工处理。如下图所示。
在这里,对返回值进行链式调用处理。责任链模板经典应用。
返回值的类型实在是太多了,所以有很多的returnValueHandlers,根据返回值的类型,来处理相应返回值。
如果是返回json之类的,则可以直接往response里面写响应数据了。写响应数据的时候,会调用不同的MessageConvert。如下图所示。
这时候,总体流程基本上是结束了。如下图所示。
4.4 异常处理
异常处理,是非常有用的功能,异常处理对后台服务至关重要。
我们来个异常DEMO看看。
handler的源码如下:
@RequestMapping("/rest/exception")
public String restExceptionDemo() throws Exception {
throw new Exception("exception demo");
}
自定义异常处理器的源码如下:
@RestControllerAdvice
public class ExceptionHandlerDemo {
@ResponseBody
@ExceptionHandler
public Map<String,String> handlerException(Exception ex){
Map map = new HashMap();
map.put("code", -200);
map.put("msg", ex.getMessage());
return map;
}
}
在浏览器中输入:
会进行异常处理流程。
restExceptionDemo()返回的异常,会传入到异常处理流程中。如果异常不为null,则在processHandlerException()方法中处理异常。如下图所示。
异常处理器有多个,所以需要进行异常处理器路由。
异常处理器由成功后,执行咱们自定义的逻辑。
4.5 页面HTTP响应是如何处理的呢
页面响应,总体流程和json类型差不多,不同在于,页面类型,有很多模板引擎,需要通过model去结合模板,通过模板引擎进行渲染。
看个例子,源码如下:
@RequestMapping("/index")
public String index(HttpServletRequest request, HttpServletResponse response,Model model){
model.addAttribute("hello","SPRING MVC DEMO ");
return "index";
}
在浏览器中输入:
进入断点,看看springmvc的执行逻辑。
收到http请求后,执行Servlet的doService()接口服务。
看,看来了咱们的网页应用业务逻辑。
咱们的网页应用返回的是html类型,通过"index"去找模板文件.
拿到模板文件"index.html",内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>hello,spring mvc ${hello}</p>
</body>
</html>
找到视图文件后,拿model去填充index.html,渲染输出。
5 思维导图
springMVC注解
DispatcherServlet分解
6 总结
springMVC在初始化的时候,和beanFactory进行了很多的交互,通过beanFactory提供的各种便利,整合各种handler.
http请求处理的各种阶段,在每个阶段,都有大量的责任链应用,这应该会继续给我们一些启示。
7 问题
- 用户的HTTP请求到达SpringMVC后,发生了什么事情?
- @RequestMapping,@Controller,@ResponseBody,@RequestParam,@Valid等等,这些东西是如何工作的?
- 返回的对象,是如何转化为josn格式的
- url是如何关联到controller的?
- 返回的路径,是如何转换为HTML的?
- url后面的parameter,是如何自动转化为对象的?
- Servlet是输入是url和parameter,输出是字节流,为什么springmvc走了这么多道流程
- springMVC的9大组件是什么?
- Filter是如何集成的
- HandlerInterceptor和Filter的区别? https://www.cnblogs.com/junzi2099/p/8022058.html
- @RestContoller,@ResonponseBody视图是怎么渲染的呢?