aaaaaa# 分解Dispatcher:如何把专门的事情交给专门的部件去做?
大家好,我是郭屹。今天我们继续探讨手写MiniSpring框架。
在上一节课中,我们已经实现了IoC(控制反转)与MVC(模型-视图-控制器)模式的结合,并定义了两个关键结构——DispatcherServlet和WebApplicationContext,它们各自独立又相互关联。
本节课的目标是在现有基础之上进一步细化功能分配,确保DispatcherServlet专注于请求解析,而将Bean管理职责明确地交由Context来处理。
两级ApplicationContext的设计
遵循典型的Web应用分层架构,我们的程序应该具备Controller层和服务层(Service)。在MiniSpring项目里,我们将通过DispatcherServlet启动Controller组件,同时使用Listener机制来初始化Service层。
为了更好地组织这些组件,我们计划引入两种类型的ApplicationContext:
DispatcherServlet中的修改
在DispatcherServlet
类内,我们需要添加一个新的成员变量,即对WebApplicationContext
的一个引用,并命名为parentApplicationContext
。这样一来,DispatcherServlet
内部就会持有两个WebApplicationContext
实例的引用,分别代表不同的配置来源或层次。
这种设计有助于清晰地区分不同层面的应用上下文,使得每个上下文可以专责于特定的任务,从而提高代码的可维护性和扩展性。
1
2
|
private WebApplicationContext webApplicationContext;
private WebApplicationContext parentApplicationContext;
|
新增parentApplicationContext 的目的是,把Listener启动的上下文和DispatcherServlet启动的上下文两者区分开来。按照时序关系,Listener启动在前,对应的上下文我们把它叫作parentApplicationContext。
我们调整一下init() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.parentApplicationContext = (WebApplicationContext)
this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION
_CONTEXT_ATTRIBUTE);
sContextConfigLocation =
config.getInitParameter("contextConfigLocation");
URL xmlPath = null;
try {
xmlPath = this.getServletContext().getResource(sContextConfigLocation);
} catch (MalformedURLException e) {
e.printStackTrace();
}
this.packageNames = XmlScanComponentHelper.getNodeValue(xmlPath);
this.webApplicationContext = new
AnnotationConfigWebApplicationContext(sContextConfigLocation,
this.parentApplicationContext);
Refresh();
}
|
在初始化阶段,我们首先从ServletContext
中获取属性WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
。该属性存放的是由先前的Listener设置的parentApplicationContext
。接着,依据contextConfigLocation
配置文件路径,创建一个新的WebApplicationContext
。
为了实现这个过程,我们需要构建一个AnnotationConfigWebApplicationContext
对象。按照常规,此类有一个接受String
类型参数(代表配置文件路径)的构造函数。然而,在我们的场景中,除了指定配置文件路径外,还需要提供parentApplicationContext
作为第二个参数给新的AnnotationConfigWebApplicationContext
实例,以便正确地建立父子上下文关系。
由于标准的AnnotationConfigWebApplicationContext
类并不直接支持带两个参数的构造函数(即同时包含配置文件路径和父上下文),因此需要对AnnotationConfigWebApplicationContext
进行一定的扩展或改造。具体做法是将原本属于DispatcherServlet
中的与包扫描相关的逻辑转移到自定义版本的AnnotationConfigWebApplicationContext
内部。这样不仅能够满足当前需求,还能保持代码的清晰度和模块化。
修改后的AnnotationConfigWebApplicationContext
示例代码结构
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
114
115
116
117
118
119
120
121
122
123
|
package com.minis.web;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
import com.minis.beans.BeansException;
import com.minis.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import com.minis.beans.factory.config.BeanDefinition;
import com.minis.beans.factory.config.BeanFactoryPostProcessor;
import com.minis.beans.factory.config.ConfigurableListableBeanFactory;
import com.minis.beans.factory.support.DefaultListableBeanFactory;
import com.minis.context.AbstractApplicationContext;
import com.minis.context.ApplicationEvent;
import com.minis.context.ApplicationEventPublisher;
import com.minis.context.ApplicationListener;
import com.minis.context.SimpleApplicationEventPublisher;
public class AnnotationConfigWebApplicationContext
extends AbstractApplicationContext implements WebApplicationContext{
private WebApplicationContext parentApplicationContext;
private ServletContext servletContext;
DefaultListableBeanFactory beanFactory;
private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors =
new ArrayList<BeanFactoryPostProcessor>();
public AnnotationConfigWebApplicationContext(String fileName) {
this(fileName, null);
}
public AnnotationConfigWebApplicationContext(String fileName, WebApplicationContext parentApplicationContext) {
this.parentApplicationContext = parentApplicationContext;
this.servletContext = this.parentApplicationContext.getServletContext();
URL xmlPath = null;
try {
xmlPath = this.getServletContext().getResource(fileName);
} catch (MalformedURLException e) {
e.printStackTrace();
}
List<String> packageNames = XmlScanComponentHelper.getNodeValue(xmlPath);
List<String> controllerNames = scanPackages(packageNames);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
this.beanFactory = bf;
this.beanFactory.setParent(this.parentApplicationContext.getBeanFactory());
loadBeanDefinitions(controllerNames);
if (true) {
try {
refresh();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void loadBeanDefinitions(List<String> controllerNames) {
for (String controller : controllerNames) {
String beanID=controller;
String beanClassName=controller;
BeanDefinition beanDefinition=new BeanDefinition(beanID,beanClassName);
this.beanFactory.registerBeanDefinition(beanID,beanDefinition);
}
}
private List<String> scanPackages(List<String> packages) {
List<String> tempControllerNames = new ArrayList<>();
for (String packageName : packages) {
tempControllerNames.addAll(scanPackage(packageName));
}
return tempControllerNames;
}
private List<String> scanPackage(String packageName) {
List<String> tempControllerNames = new ArrayList<>();
URL url =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if(file.isDirectory()){
scanPackage(packageName+"."+file.getName());
}else{
String controllerName = packageName +"." +file.getName().replace(".class", "");
tempControllerNames.add(controllerName);
}
}
return tempControllerNames;
}
public void setParent(WebApplicationContext parentApplicationContext) {
this.parentApplicationContext = parentApplicationContext;
this.beanFactory.setParent(this.parentApplicationContext.getBeanFactory());
}
public ServletContext getServletContext() {
return this.servletContext;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void publishEvent(ApplicationEvent event) {
this.getApplicationEventPublisher().publishEvent(event);
}
public void addApplicationListener(ApplicationListener listener) {
this.getApplicationEventPublisher().addApplicationListener(listener);
}
public void registerListeners() {
ApplicationListener listener = new ApplicationListener();
this.getApplicationEventPublisher().addApplicationListener(listener);
}
public void initApplicationEventPublisher() {
ApplicationEventPublisher aep = new SimpleApplicationEventPublisher();
this.setApplicationEventPublisher(aep);
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
}
public void registerBeanPostProcessors(ConfigurableListableBeanFactory bf) {
this.beanFactory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
}
public void onRefresh() {
this.beanFactory.refresh();
}
public void finishRefresh() {
}
public ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException {
return this.beanFactory;
}
}
|
这段代码的核心是扩充原有的构造方法。通过下面两行代码得到parentApplicationContext和servletContext的引用。
1
2
|
this.parentApplicationContext = parentApplicationContext;
this.servletContext = this.parentApplicationContext.getServletContext();
|
为了保持对原有构造方法的兼容性,在只有一个参数的情况下,我们为WebApplicationContext传入了一个null。可以看到,修改后的AnnotationConfigWebApplicationContext继承自抽象类AbstractApplicationContext,因此具备了上下文的通用功能,例如注册监听器、发布事件等。此外,由于AnnotationConfigWebApplicationContext调用了DefaultListableBeanFactory的setParent方法,我们需要提供相应的实现方法。
1
2
3
4
5
|
ConfigurableListableBeanFactory parentBeanFactory;
public void setParent(ConfigurableListableBeanFactory beanFactory) {
this.parentBeanFactory = beanFactory;
}
|
接下来我们还要改造XmlWebApplicationContext,在继承ClassPathXmlApplicationContext的基础上实现WebApplicationContext接口,基本上我们可以参考AnnotationConfigWebApplicationContext来实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package com.minis.web;
import javax.servlet.ServletContext;
import com.minis.context.ClassPathXmlApplicationContext;
public class XmlWebApplicationContext
extends ClassPathXmlApplicationContext implements WebApplicationContext{
private ServletContext servletContext;
public XmlWebApplicationContext(String fileName) {
super(fileName);
}
public ServletContext getServletContext() {
return this.servletContext;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
|
到这里,我们就进一步拆解了DispatcherServlet,拆分出两级ApplicationContext,当然启动过程还是由Listener来负责。所以最后ContextLoaderListener初始化时是创建XmlWebApplicationContext对象。
1
|
WebApplicationContext wac = new XmlWebApplicationContext(sContextLocation);
|
到这里,Web环境下的两个ApplicationContext都构建完毕了,WebApplicationContext持有对parentApplicationContext的单向引用。当调用getBean()获取Bean时,先从WebApplicationContext中获取,若为空则通过parentApplicationContext获取,你可以看一下代码。
1
2
3
4
5
6
7
|
public Object getBean(String beanName) throws BeansException {
Object result = super.getBean(beanName);
if (result == null) {
result = this.parentBeanFactory.getBean(beanName);
}
return result;
}
|
抽取调用方法
拆解的工作还要继续进行,基本的思路是将专业事情交给不同的专业部件来做,我们来看看还有哪些工作是可以分出来的。从代码可以看到现在doGet()方法是这样实现的。
1
2
3
4
|
Method method = this.mappingMethods.get(sPath);
obj = this.mappingObjs.get(sPath);
objResult = method.invoke(obj);
response.getWriter().append(objResult.toString());
|
程序设计思路
本程序旨在简化URL到方法的映射以及方法的调用过程。我们将仿照Spring框架,通过抽象出两个核心组件:RequestMappingHandlerMapping
和 RequestMappingHandlerAdapter
,来分别处理URL映射和方法调用。以下是详细的实现步骤和结构化内容。
1. 定义接口
首先,我们需要定义两个接口:HandlerMapping
和 HandlerAdapter
。这两个接口将作为后续实现的基础。
HandlerMapping 接口
- 负责将URL映射到对应的处理器(handler)。
HandlerAdapter 接口
2. 实现接口
基于上述接口,我们将实现具体的类来完成URL映射和方法调用的功能。
RequestMappingHandlerMapping
- 实现
HandlerMapping
接口,负责解析URL并找到对应的处理器方法。
RequestMappingHandlerAdapter
- 实现
HandlerAdapter
接口,负责调用处理器方法,并处理方法的返回值,最终写入响应(response)。
3. 反射调用方法
在 RequestMappingHandlerAdapter
中,我们将使用Java反射机制来调用处理器方法,这样可以动态地处理不同的方法调用。
4. 整合到框架中
最后,将这两个组件整合到我们的框架中,确保它们能够协同工作,完成从URL解析到方法调用的整个流程。
总结
通过上述步骤,我们能够清晰地分离URL映射和方法调用的逻辑,使得程序更加模块化和易于维护。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.minis.web.servlet;
import javax.servlet.http.HttpServletRequest;
public interface HandlerMapping {
HandlerMethod getHandler(HttpServletRequest request) throws Exception;
}
package com.minis.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface HandlerAdapter {
void handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
|
其中可以看到,HandlerMapping中定义的getHandler方法参数是http request,返回一个HandlerMethod对象,这个地方就是封装的这种映射关系。你可以看一下HandlerMethod对象的定义。
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
|
package com.minis.web.servlet;
import java.lang.reflect.Method;
public class HandlerMethod {
private Object bean;
private Class<?> beanType;
private Method method;
private MethodParameter[] parameters;
private Class<?> returnType;
private String description;
private String className;
private String methodName;
public HandlerMethod(Method method, Object obj) {
this.setMethod(method);
this.setBean(obj);
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Object getBean() {
return bean;
}
public void setBean(Object bean) {
this.bean = bean;
}
}
|
接下来增加一个MappingRegistry类,这个类有三个属性:urlMappingNames、mappingObjs和mappingMethods,用来存储访问的URL名称与对应调用方法及Bean实例的关系。你可以看一下相关定义。
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
|
package com.minis.web.servlet;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MappingRegistry {
private List<String> urlMappingNames = new ArrayList<>();
private Map<String,Object> mappingObjs = new HashMap<>();
private Map<String,Method> mappingMethods = new HashMap<>();
public List<String> getUrlMappingNames() {
return urlMappingNames;
}
public void setUrlMappingNames(List<String> urlMappingNames) {
this.urlMappingNames = urlMappingNames;
}
public Map<String,Object> getMappingObjs() {
return mappingObjs;
}
public void setMappingObjs(Map<String,Object> mappingObjs) {
this.mappingObjs = mappingObjs;
}
public Map<String,Method> getMappingMethods() {
return mappingMethods;
}
public void setMappingMethods(Map<String,Method> mappingMethods) {
this.mappingMethods = mappingMethods;
}
}
|
在Spring框架中,DispatcherServlet负责处理请求映射,而映射关系通常存储在MappingRegistry中。以下是对RequestMappingHandlerMapping实现的详细解析:
1. 映射关系的存储
- 映射关系以前存储在DispatcherServlet中,现在通过MappingRegistry单独管理。
2. RequestMappingHandlerMapping的实现
- RequestMappingHandlerMapping实现了HandlerMapping接口。
3. 初始化过程
4. getHandler()方法的实现
5. 代码示例
- 相关源代码展示了RequestMappingHandlerMapping如何实现和存储映射关系。
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
|
package com.minis.web.servlet;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import com.minis.beans.BeansException;
import com.minis.web.RequestMapping;
import com.minis.web.WebApplicationContext;
public class RequestMappingHandlerMapping implements HandlerMapping{
WebApplicationContext wac;
private final MappingRegistry mappingRegistry = new MappingRegistry();
public RequestMappingHandlerMapping(WebApplicationContext wac) {
this.wac = wac;
initMapping();
}
//建立URL与调用方法和实例的映射关系,存储在mappingRegistry中
protected void initMapping() {
Class<?> clz = null;
Object obj = null;
String[] controllerNames = this.wac.getBeanDefinitionNames();
//扫描WAC中存放的所有bean
for (String controllerName : controllerNames) {
try {
clz = Class.forName(controllerName);
obj = this.wac.getBean(controllerName);
} catch (Exception e) {
e.printStackTrace();
}
Method[] methods = clz.getDeclaredMethods();
if (methods != null) {
//检查每一个方法声明
for (Method method : methods) {
boolean isRequestMapping =
method.isAnnotationPresent(RequestMapping.class);
//如果该方法带有@RequestMapping注解,则建立映射关系
if (isRequestMapping) {
String methodName = method.getName();
String urlmapping =
method.getAnnotation(RequestMapping.class).value();
this.mappingRegistry.getUrlMappingNames().add(urlmapping);
this.mappingRegistry.getMappingObjs().put(urlmapping,
obj);
this.mappingRegistry.getMappingMethods().put(urlmapping,
method);
}
}
}
}
}
//根据访问URL查找对应的调用方法
public HandlerMethod getHandler(HttpServletRequest request) throws Exception
{
String sPath = request.getServletPath();
if (!this.mappingRegistry.getUrlMappingNames().contains(sPath)) {
return null;
}
Method method = this.mappingRegistry.getMappingMethods().get(sPath);
Object obj = this.mappingRegistry.getMappingObjs().get(sPath);
HandlerMethod handlerMethod = new HandlerMethod(method, obj);
return handlerMethod;
}
}
|
这样我们就得到了独立的RequestMappingHandlerMapping部件,把以前写在DispatcherServlet里的代码移到这里来了。接下来就轮到RequestMappingHandlerAdapter的实现了,它要实现HandlerAdapter接口,主要就是实现handle()方法,基本过程是接受前端传request、 response与handler,通过反射中的invoke调用方法并处理返回数据。
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
|
package com.minis.web.servlet;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.minis.web.WebApplicationContext;
public class RequestMappingHandlerAdapter implements HandlerAdapter {
WebApplicationContext wac;
public RequestMappingHandlerAdapter(WebApplicationContext wac) {
this.wac = wac;
}
public void handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
handleInternal(request, response, (HandlerMethod) handler);
}
private void handleInternal(HttpServletRequest request, HttpServletResponse response,
HandlerMethod handler) {
Method method = handler.getMethod();
Object obj = handler.getBean();
Object objResult = null;
try {
objResult = method.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
try {
response.getWriter().append(objResult.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
aaaaaaa在对现有的程序代码进行重构时,我们已经将一些功能从DispatcherServlet
中分离出来,放入了两个新的组件:HandlerMapping
和HandlerAdapter
。这一变化使得DispatcherServlet
更加专注于其核心职责,并提高了代码的可维护性和复用性。
处理流程概述
DispatcherServlet 的调整
为了适应上述改动,在DispatcherServlet
类内我们需要做出以下更新:
-
增加引用:首先,向DispatcherServlet
类中添加对HandlerMapping
与HandlerAdapter
这两个新组件的引用。
-
初始化方法:接着,在refresh()
方法中引入initHandlerMapping()
和initHandlerAdapter()
方法来初始化这些新增加的引用。
initHandlerMapping()
负责为HandlerMapping
赋值。
initHandlerAdapter()
则负责为HandlerAdapter
设置合适的实例。
这样的设计不仅让各个组件之间的职责划分更加清晰,也便于未来进一步扩展或修改特定部分的功能而不影响整个系统架构。
1
2
3
4
5
6
|
refresh() {
initController();
initHandlerMappings(this.webApplicationContext);
initHandlerAdapters(this.webApplicationContext);
}
|
初始化这两个部件的代码如下:
1
2
3
4
5
6
|
protected void initHandlerMappings(WebApplicationContext wac) {
this.handlerMapping = new RequestMappingHandlerMapping(wac);
}
protected void initHandlerAdapters(WebApplicationContext wac) {
this.handlerAdapter = new RequestMappingHandlerAdapter(wac);
}
|
在Spring MVC框架中,DispatcherServlet
是前端控制器的核心组件,负责接收所有的HTTP请求,并将它们分发给相应的处理器。为了提高灵活性和可扩展性,我们可以对DispatcherServlet
的处理过程进行调整,使得不再直接通过doGet()
或doPost()
等方法来处理请求,而是通过重写service()
方法实现统一入口。以下是修改后的流程概述:
- 请求到达
- 所有请求首先由
DispatcherServlet
的service()
方法接收。
- 调用
doDispatch()
service()
内部调用了doDispatch()
方法来进行具体的请求处理。
- 获取Handler
- 在
doDispatch()
方法内,利用HandlerMapping
找到与当前请求相匹配的HandlerMethod
对象。这一步骤决定了哪个控制器(Controller)中的哪个方法会被用来处理请求。
- 适配器处理
- 获取到正确的
HandlerMethod
后,使用HandlerAdapter
将其转换为可以执行的形式。不同的控制器可能需要不同类型的适配器支持,因此这一步也包括了选择合适的适配器。
- 执行处理逻辑
- 一旦确定了如何调用控制器的方法,就通过所选的
HandlerAdapter
执行该方法以完成业务逻辑处理。
- 返回响应
- 处理完成后,根据处理结果生成适当的HTTP响应发送回客户端。
这样的设计模式不仅让整个请求处理流程更加清晰有序,同时也增强了系统的解耦性和复用能力。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
protected void service(HttpServletRequest request, HttpServletResponse
response) {
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this.webApplicationContext);
try {
doDispatch(request, response);
} catch (Exception e) {
e.printStackTrace();
}
finally {
}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception{
HttpServletRequest processedRequest = request;
HandlerMethod handlerMethod = null;
handlerMethod = this.handlerMapping.getHandler(processedRequest);
if (handlerMethod == null) {
return;
}
HandlerAdapter ha = this.handlerAdapter;
ha.handle(processedRequest, response, handlerMethod);
}
|
课程回顾与总结
在这节课中,我们探讨了如何通过改造DispatcherServlet来简化代码,并提高业务程序的灵活性。以下是课程内容的详细回顾和总结。
1. DispatcherServlet的改造
2. 业务逻辑的独立性
3. Dispatcher设计模式的学习
- 这种称为Dispatcher的设计模式,是值得我们深入学习和掌握的。
4. ApplicationContext的拆解
5. @RequestMapping注解的使用
6. DispatcherServlet功能的分治
- 通过这些拆解工作,我们将DispatcherServlet的功能进行了分治,使得专门的事情由专门的部件完成,有利于未来的扩展。
完整源代码
课后思考题