SpringMVC核心流程解析

课程概要

  1. spring mvc 设计思想与体系结构组成
  2. mvc 执行流程解析
  3. 自定义MVC的框架实现

spring mvc 功能特性

回顾servlet 与jsp 执行过程

image2

流程说明:

  1. 请求Servlet
  2. 处理业务逻辑
  3. 设置业务Model
  4. forward jsp Servlet
  5. jsp Servlet 解析封装html 返回

spring mvc 功能特性:

spring mvc本质上还是在使用Servlet处理,并在其基础上进行了封装简化了开发流程,提高易用性、并使用程序逻辑结构变得更清晰

  1. 基于注解的URL映射
  2. 表单参数映射,参数接受
  3. 全局统一异常拦截处理
  4. 文件上传与下载
  5. MVC拦截器 HandlerInterceptor

请求处理流程

  1. Web 发起request请求
  2. dispatchServlet通过HandlerMapping映射关系查找到对应的Handler
  3. HandlerAdapter适配handler并且执行handler逻辑
  4. handler设置业务model
  5. handler设置返回页面
  6. dispatchServlet接受到模型视图对象
  7. dispatchServlet通过viewAdapter找到view
  8. view基于模板与模型将mv渲染成写成html
  9. 渲染结果通过response返回Web前端

使用示例

目录结构

image-20210601213936301

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
</dependencies>

spring-mvc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<bean name="/hello" class="study.springmvc.controller.MyController"/>
<bean name="/hello1" class="study.springmvc.controller.MyRequestHandler"/>

<!-- 注解驱动 -->
<mvc:annotation-driven/>
<context:component-scan base-package="study.springmvc"/>

<!-- 视图仓库 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
</bean>
</beans>

controller

1
2
3
4
5
6
7
8
public class MyController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView("userView");
modelAndView.addObject("name", "24kHandsome");
return modelAndView;
}
}

userView.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
the man is
${name}
</body>
</html>

mvc 体系结构详解

spring mvc 框架解决的问题

从技术角度去思考 任何一个现存的框架都有其存在理由,而这个理由就是解决实际的问题。或者提供更好的解决问题的方案。spring mvc 它解决了什么问题呢?

  1. URL映射
  2. 表单参数映射
  3. 调用目标Control
  4. 数据模型映射视图解析
  5. 异常处理

上术解决在spring mvc 中都体现在如下组件当中

  • HandlerMapping ‘hændlə ‘mæpɪŋ
    • url与控制器的映谢
  • HandlerAdapter ‘hændlə ə’dæptə
    • 控制器执行适配器
  • ViewResolver vjuː riː’zɒlvə
    • 视图仓库
  • view
    • 具体解析视图
  • HandlerExceptionResolver ‘hændlə ɪk’sepʃ(ə)n riː’zɒlvə
    • 异常捕捕捉器
  • HandlerInterceptor ‘hændlə ɪntə’septə
    • 拦截器

其对应具体uml如下 图:

image3

mvc 各组件执行流程
image4

SpringMVC默认引入组件

根据 org/springframework/web/servlet/DispatcherServlet.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

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

HandlerMapping 详解

其为mvc 中url路径与Control对像的映射,DispatcherServlet 就是基于此组件来寻找对应的Control,如果找不到就会报 No mapping found for HTTP request with URI的异常。

HandlerMapping 接口结构分析:

HandlerMapping 作用是通过url找到对应的Handler ,但其HandlerMapping.getHandler()方法并不会直接返回Handler 对像,而是返回 HandlerExecutionChain 对像在通过 HandlerExecutionChain.getHandler() 返回最终的handler

image-20210601235555218

目前主流的三种mapping 如下:

  1. SimpleUrlHandlerMapping:基于手动配置 url 与control 映谢
  2. BeanNameUrlHandlerMapping: 基于ioc name 中已 “/“ 开头的Bean时行 注册至映谢.
  3. RequestMappingHandlerMapping:基于@RequestMapping注解配置对应映谢

SimpleUrlHandlerMapping

SimpleUrlHandlerMapping体系结构:基于手动配置 url 与control 映谢

image-20210602000051935

初始化SimpleUrlHandlerMapping流程关键源码:

1
2
3
4
5
6
>org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#setUrlMap
>org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#initApplicationContext
>org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#registerHandlers
// /表示根路径 /* 表示默认路径
>org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler()

获取 Handler流程关键源码:

1
2
3
4
5
6
7
8
9
>org.springframework.web.servlet.DispatcherServlet#doService>org.springframework.web.servlet.DispatcherServlet#doDispatch
>org.springframework.web.servlet.DispatcherServlet#getHandler>org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
>org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal
// 获取URL路径
>org.springframework.web.util.UrlPathHelper#getPathWithinApplication
// 查找handler
>org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler
// 封装执行链
>org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping 实现上与 SimpleUrlHandlerMapping 一至,唯一区别在于 继承自AbstractDetectingUrlHandlerMapping ,通过对应detectHandlers 可以在无配置的情况下发现url 与handler 映射。

RequestMappingHandlerMapping(后续补充)

其基于注解实现,在后续章节讲解注解映谢的时候在详细讲

HandlerAdapter

在 AbstractUrlHandlerMapping 我们可以看到存储handler 的Map 值类型是Object ,是否意味着所有的类都可以做来Handler 来使用?

image10

Handler 对应类型如下如图:

image11

  1. Controller
  2. HttpRequestHandler
  3. HttpServlet
  4. @RequestMapping

可以看出 Handler 没有统一的接口,当dispatchServlet获取当对应的Handler之后如何调用呢?调用其哪个方法?这里有两种解决办法,

一是用instanceof 判断Handler 类型然后调用相关方法 。

二是通过引入适配器实现,每个适配器实现对指定Handler的调用。

spring 采用后者。

HandlerAdapter详解

这里spring mvc 采用适配器模式来适配调用指定Handler,根据Handler的不同种类采用不同的Adapter,其Handler与 HandlerAdapter 对应关系如下:

Handler类别 对应适配器 描述
Controller SimpleControllerHandlerAdapter 标准控制器,返回ModelAndView
HttpRequestHandler HttpRequestHandlerAdapter 业务自行处理 请求,不需要通过modelAndView 转到视图
Servlet SimpleServletHandlerAdapter 基于标准的servlet 处理
HandlerMethod RequestMappingHandlerAdapter 基于@requestMapping对应方法处理

HandlerAdapter 接口方法

image-20210602232832491

HandlerAdapter 接口结构图

image-20210602232931062

上述例子中当IOC中实例化这些类之后DispatcherServlet就会通过org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter() 方法查找对应handler的适配器 ,如果找不到就会报 如下异常 。

javax.servlet.ServletException: No adapter for handler [com.tuling.control.SimpleControl@3c06b5d5]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handlerorg.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1198)org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)

ViewResolver 与View

找到应的Adapter 之后就会基于适配器调用业务处理,处理完之后业务方会返回一个ModelAndView ,在去查找对应的视图进行处理。

其在org.springframework.web.servlet.DispatcherServlet#resolveViewName() 中遍历 viewResolvers 列表查找,如果找不到就会报一个 Could not resolve view with name 异常。

image-20210602233734756

BeanNameViewREsolver示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
添加自定义视图:
public class MyView implements View {
@Override
public void render(Map<String, ?> model, HttpServletRequest
request, HttpServletResponse response) throws Exception {
response.getWriter().print("hello luban good man.");
}
}

修改视图跳转方法 :
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("myView");
mv.addObject("name", "24kHandsome is good man");
return mv;
}
1
2
3
4
5
6
7
8
9
配置视图解析器:
<bean name="myView" class="com.tuling.control.MyView"/>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<!--资源解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
</bean>

在下一步就是基于ViewResolver**.**resolveViewName() 获取对应View来解析生成Html并返回 。

对应VIEW结构如下:

image-20210602234106079

MVC异常拦截处理HandlerExceptionResolver

HandlerExceptionResolver的类关系图

image-20210605144323346

  • ResponseStatuExceptionResolver
    • 用于解析带@ResponseStatus的自定义异常
  • DefaultHandlerExceptionResolver
    • spring mvc 默认异常处理
  • SimpleMappingExceptionResolver
    • 异常映射,将指定异常与错误页面相对应

该组件用于指示 当出现异常时 mvc 该如何处理。 dispatcherServlet 会调用org.springframework.web.servlet.DispatcherServlet#processHandlerException() 方法,遍历 handlerExceptionResolvers 处理异常,处理完成之后返回errorView 跳转到异常视图。

自定义异常解析器

1
2
3
4
5
6
7
8
9
10
11
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {

ModelAndView mv = new ModelAndView("argumentError");
mv.addObject("stack",ex);
return mv;
}
}

SimpleMappingExceptionResolver 示例:

1
2
3
4
5
6
7
8
9
10
11

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="error"/>
<property name="defaultStatusCode" value="500"/>
<property name="exceptionMappings">
<map>
<entry key="java.lang.RuntimeException" value="error"/>
<entry key="java.lang.IllegalArgumentException" value="argumentError"/>
</map>
</property>
</bean>

提问:

IllegalArgumentException 是 RuntimeException子类,如果IllegalArgumentException 异常同时满足映射的两个条件,这时会怎么选择跳转的视图?

答案是会选择IllegalArgumentException 这个子类,而不是根据先后顺序

HandlerInterceptor 调用拦截

HandlerInterceptor 用于对请求拦截,

HandlerInterceptor 常常用于对某些路径进行特殊配置,可用于

  • 参数打印 业务处理前 后
  • 登陆/权限拦截 业务处理前
  • 数字验签 业务处理前
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SimpleHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}

preHandle :业务处理前执行

postHandle:业务处理后(异常则不执行)

afterCompletion:视图处理后

Filter与Interceptor联系与区别

  • 拦截器不依赖servlet容器,过滤器依赖于servlet容器。
  • 拦截器只能对action(handler)起作用,而过滤器可以对几乎所有的请求起作用(可以保护资源)。
  • 拦截器可以访问action上下文,堆栈里面的对象,而过滤器不可以。
  • 执行顺序:过滤前-拦截前-Action处理-拦截后-过滤后。
  • 细粒度的不同拦截器提供更精细的控制,可以在controller对请求处理之前或之后被调用,也可以在渲染视图呈现给用户之后调用
  • 中断链执行的难易程度不同拦截器可以 preHandle方法内返回 false 进行中断,过滤器就比较复杂,需要处理请求和响应对象来引发中断,需要额外的动作,比如将用户重定向到错误页面

image-20210605230700202

image-20210605231527804

三者使用场景

三者功能类似,但各有优势,从过滤器–》拦截器–》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。

**一般情况下数据被过滤的时机越早对服务的性能影响越小**,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。

比如权限校验,一般情况下(经供参考)

所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验

日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。

@RequestMapping的使用与原理

演示基于注解配置mvc mapping

1
2
3
4
5
6
7
8
9
10
11
<!-- 注解驱动 -->
<mvc:annotation-driven/>
<context:component-scan base-package="study.springmvc"/>

<!-- 视图仓库 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
</bean>
1
2
3
4
5
6
public class MyRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("userView").forward(request,response);
}
}

提问

​ 为什么基于<mvc:annotation-driven /> 配置就能实现mvc 的整个配置了

之前所提到的 handlerMapping 、与handlerAdapter 组件都不适用了?只要查看以类的源就可以知晓其中原因:认识 NamespaceHandler 接口查看 MvcNamespaceHandler查看AnnotationDrivenBeanDefinitionParser

结论

​ 在<mvc:annotation-driven /> 对应的解析器,自动向ioc 里面注册了两个BeanDefinition。

​ 分别是:RequestMappingHandlerMappingBeanNameUrlHandlerMapping

![image2 (2)](/images/SpringMVC核心流程解析/image2 (2).png)

  • RequestMappingHandlerMapping :URL 映射器
  • RequestMappingHandlerAdapter:执行适配器
  • InvocableHandlerMethod:Control目标对象,包含了control Bean 及对应的method 对像,及调用方法
    • HandlerMethodArgumentResolverComposite:参数处理器
    • ParameterNameDiscoverer:参数名称处理器
    • HandlerMethodReturnValueHandlerComposite:返回结构处理器

查找mapping源码解析

1
2
3
4
5
// 基于注解查找 mapping
org.springframework.web.servlet.DispatcherServlet#getHandler
>org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
>org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
>org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl

调用执行过程源码解析

1
2
3
4
5
>org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
>org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
>org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
>org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
>org.springframework.web.method.support.InvocableHandlerMethod#doInvoke

自定义MVC的框架实现

框架需求与目标

框架需求:

​ 框架的需求包含功能性需求非功能性需求,功能性需求框架本身所提供的功能,而非功能性需求通常也指定体验性需求,即该框架对于开发者而言,是否易上手,是否需要较多的学习成本,以及在开发时需要过多的配置。

​ 有个时候两者是互相矛盾冲突的,比如当我们想让框架支持更的功能时,那么它的结构设计将会更复杂,抽像的层次将会越多,带来的负面影响时对框架使用者的学习成本增加了。

image-20210606174425301

到底该选择更多的功能,还是更好的体验?这就需要框架作者要作出准确的定位与范围。

定位是该框架要完成什么目标?范围是实现该目标需实现哪些功能?两者清晰之后 自然知道哪些是必须做的,哪些是可以做的。而体验则是在保证必须功能的情况越高越好,甚至可以为了提供体验可以牺牲部分功能的完整性。

功能性需求用例图:

image-20210606175802297
  • URL映射
    • 基于注解自动匹配调用方法
  • 参数自动解析
    • Form表单参数自动转换成一般对像和复杂对像
  • 请求调用
    • 基于反射调用目标方法
  • 视图支持
    • 基于返回的结果跳转至视图处理
    • 支持的有jsp 视图,freemarke视图,Json视图
  • 统一异常处理
    • 出现异常统一处理,并跳转到异常页面

非功能性需求与目标:

  • 接近于零的配置
  • 更少的学习成本
    • 尽可能使用用户之前习惯
    • 概念性的东西要少一些
  • 支持动态装载

框架设计与编码实现

框架环境依赖:

​ 框架名称:study-myspringMVC

​ jdk:1.8

​ 依赖包:spring、freemarker、java-servlet-api

框架流程分解:

image-20210606221522334

实现组件:

  • FreemakerView
    • freemarke视图
  • HandlerServlet(原DispatchServlet)
    • 请求参数封装,请求转发
  • MvcBeanFactory(类似HandlerMapping 持有 url对应handler的map)
    • Mvc bean 工厂 ,从spring ioc 中扫描类装载MVC Bean
  • MvcBean(原HandlerMethod,因为只有一种handler,所以无需HandlerAdaptor)
    • MVC 业务执行
  • MvcMaping(原@RequestMapping)
    • MVC注解,用于注解MVC Bean,并配置url 路径

代码:

1、模拟DispatchServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class HandlerServlet extends HttpServlet {
//IOC父容器
private WebApplicationContext context;
//MVC子容器
private MvcBeanFactory beanFactory;
//asm字节码解析器,用户解析请求参数对应的key
final ParameterNameDiscoverer parameterUtil = new LocalVariableTableParameterNameDiscoverer();

private Configuration freemarkeConfig;

@Override
public void init() throws ServletException {
context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
beanFactory = new MvcBeanFactory(context);

Configuration freemarkeConfig = null;
try {
freemarkeConfig = context.getBean(Configuration.class);
} catch (NoSuchBeanDefinitionException e) {
}
if (freemarkeConfig == null) {
freemarkeConfig = new Configuration(Configuration.VERSION_2_3_23);
freemarkeConfig.setDefaultEncoding("UTF-8");
freemarkeConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
try {
freemarkeConfig.setDirectoryForTemplateLoading(new File(getServletContext().getRealPath("/WEB-INF/ftl/")));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
this.freemarkeConfig = freemarkeConfig;
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doHandler(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doHandler(req, resp);
}

// TODO 处理静态文件
public void doHandler(HttpServletRequest req, HttpServletResponse resp) {
String uri = req.getServletPath();
if (uri.equals("/favicon.ico")) {
return;
}

//获取Hadnler
MvcBeanFactory.MvcBean mvcBean = beanFactory.getMvcBean(uri);
if (mvcBean == null) {
throw new IllegalArgumentException(String.format("not found %s mapping", uri));
}
//利用asm字节码工具解析参数
Object[] args = buildPrams(mvcBean, req, resp);
try {
//反射运行目标对象 handlerMethod
Object result = mvcBean.run(args);
//封装返回结果
processResult(result, resp);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (TemplateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

private Object[] buildPrams(MvcBeanFactory.MvcBean mvcBean, HttpServletRequest req, HttpServletResponse resp) {
Method method = mvcBean.getTargetMethod();
List<String> paramNames = Arrays.asList(parameterUtil.getParameterNames(method));
Class<?>[] paramTypes = method.getParameterTypes(); //反射
// a.b.c
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < paramNames.size(); i++) {
if (paramTypes[i].isAssignableFrom(HttpServletRequest.class)) {
args[i] = req;
} else if (paramTypes[i].isAssignableFrom(HttpServletResponse.class)) {
args[i] = resp;
} else {
if (req.getParameter(paramNames.get(i)) == null) {
args[i] = null;
} else {
args[i] = convert(req.getParameter(paramNames.get(i)), paramTypes[i]);
}
}
}
return args;
}
}

2、模拟@RequstMapping:MvcMapping和Mvc子容器持有HandleMapping:MvcBeanFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface MvcMapping {
public String value();

public String contentType() default "JSON";
}

public class MvcBeanFactory {
private ApplicationContext applicationContext;

public MvcBeanFactory(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "argument 'applicationContext' must not be null");
this.applicationContext = applicationContext;
loadApiFromSpringBeans();
}

// API 接口住的地方
private HashMap<String, MvcBean> apiMap = new HashMap<String, MvcBean>();


private void loadApiFromSpringBeans() {
apiMap.clear();
// ioc 所有BEan
// spring ioc 扫描
String[] names = applicationContext.getBeanDefinitionNames();
Class<?> type;
for (String name : names) {
type = applicationContext.getType(name);
for (Method m : type.getDeclaredMethods()) {
// 通过反射拿到HttpMapping注解
MvcMapping MvcMapping = m.getAnnotation(MvcMapping.class);
if (MvcMapping != null) {
// 封装成一个 MVC bean
addApiItem(MvcMapping, name, m);
}
}
}
}

public MvcBean getMvcBean(String apiName) {
return apiMap.get(apiName);
}

/**
* 添加api <br/>
*
* @param MvcMapping api配置
* @param beanName beanq在spring context中的名称
* @param method
*/
private void addApiItem(MvcMapping MvcMapping, String beanName, Method method) {
MvcBean apiRun = new MvcBean();
apiRun.apiName = MvcMapping.value();
apiRun.targetMethod = method;
apiRun.targetName = beanName;
apiRun.context = this.applicationContext;
apiMap.put(MvcMapping.value(), apiRun);
}


public boolean containsApi(String apiName, String version) {
return apiMap.containsKey(apiName + "_" + version);
}

public ApplicationContext getApplicationContext() {
return applicationContext;
}


// 用于执行对应的API方法,
//
public static class MvcBean {
String apiName; //bit.api.user.getUser

String targetName; //ioc bean 名称

Object target; // UserServiceImpl 实例
Method targetMethod; // 目标方法 getUser
ApplicationContext context;


public Object run(Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 懒加载
if (target == null) {
// spring ioc 容器里面去服务Bean 比如GoodsServiceImpl
target = context.getBean(targetName);
}
return targetMethod.invoke(target, args);
}

public Class<?>[] getParamTypes() {
return targetMethod.getParameterTypes();
}

public String getApiName() {
return apiName;
}

public String getTargetName() {
return targetName;
}

public Object getTarget() {
return target;
}

public Method getTargetMethod() {
return targetMethod;
}

}
}

3、FreeMaker试图解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class FreemarkeView {
private String ftlPath;
private Map<String, Object> models = new HashMap<>();

public FreemarkeView(String ftlPath) {
this.ftlPath = ftlPath;
}

public FreemarkeView(String ftlPath, Map<String, Object> model) {
this.ftlPath = ftlPath;
this.models = model;
}

public void setModel(String key, Object model) {
models.put(key, model);
}

public void removeModel(String key) {
models.remove(key);
}

public String getFtlPath() {
return ftlPath;
}

public void setFtlPath(String ftlPath) {
this.ftlPath = ftlPath;
}

public Map<String, Object> getModels() {
return models;
}

public void setModels(Map<String, Object> models) {
this.models = models;
}
}

4、web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<!-- root ioc -->
<display-name>my spring mvc </display-name>


<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>

<listener>
<description>spring监听器</description>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>com.study.mvc.HandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>/index.html</welcome-file>
</welcome-file-list>

</web-app>