aaaaaa# 分解Dispatcher:如何把专门的事情交给专门的部件去做? 大家好,我是郭屹。今天我们继续探讨手写MiniSpring框架。 在上一节课中,我们已经实现了IoC(控制反转)与MVC(模型-视图-控制器)模式的结合,并定义了两个关键结构——DispatcherServlet和WebApplicationContext,它们各自独立又相互关联。 本节课的目标是在现有基础之上进一步细化功能分配,确保DispatcherServlet专注于请求解析,而将Bean管理职责明确地交由Context来处理。

两级ApplicationContext的设计

遵循典型的Web应用分层架构,我们的程序应该具备Controller层和服务层(Service)。在MiniSpring项目里,我们将通过DispatcherServlet启动Controller组件,同时使用Listener机制来初始化Service层。 为了更好地组织这些组件,我们计划引入两种类型的ApplicationContext:

  • XmlWebApplicationContext:用于基于XML配置文件加载Bean。

  • AnnotationConfigWebApplicationContext:支持注解配置,自动扫描并注册带有特定注解的类作为Bean。

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示例代码结构

  • 构造函数:新增一个接受配置文件路径以及父上下文作为参数的构造函数。

  • 包扫描逻辑:从DispatcherServlet迁移过来的相关方法,用于根据配置文件自动发现并加载标注了特定注解的类。

  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框架,通过抽象出两个核心组件:RequestMappingHandlerMappingRequestMappingHandlerAdapter,来分别处理URL映射和方法调用。以下是详细的实现步骤和结构化内容。

1. 定义接口

首先,我们需要定义两个接口:HandlerMappingHandlerAdapter。这两个接口将作为后续实现的基础。

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. 初始化过程

  • 在初始化过程中,RequestMappingHandlerMapping会遍历WebApplicationContext(WAC)中注册的所有Bean。

  • 它处理所有带有@RequestMapping注解的类,并将URL地址与方法和实例的映射关系存储在mappingRegistry中。

4. getHandler()方法的实现

  • RequestMappingHandlerMapping对外提供getHandler()方法。

  • 该方法通过URL获取对应的method调用,实现URL到具体处理方法的映射。

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中分离出来,放入了两个新的组件:HandlerMappingHandlerAdapter。这一变化使得DispatcherServlet更加专注于其核心职责,并提高了代码的可维护性和复用性。

处理流程概述

  • handleInternal()方法通过反射机制调用目标方法,并将该方法的返回值写入到HTTP响应中。

  • 此次重构之前,这部分逻辑是直接包含在DispatcherServlet内的;现在,则将其转移到了独立的组件里。

DispatcherServlet 的调整

为了适应上述改动,在DispatcherServlet类内我们需要做出以下更新:

  1. 增加引用:首先,向DispatcherServlet类中添加对HandlerMappingHandlerAdapter这两个新组件的引用。

  2. 初始化方法:接着,在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()方法实现统一入口。以下是修改后的流程概述:

  1. 请求到达
  • 所有请求首先由DispatcherServletservice()方法接收。
  1. 调用doDispatch()
  • service()内部调用了doDispatch()方法来进行具体的请求处理。
  1. 获取Handler
  • doDispatch()方法内,利用HandlerMapping找到与当前请求相匹配的HandlerMethod对象。这一步骤决定了哪个控制器(Controller)中的哪个方法会被用来处理请求。
  1. 适配器处理
  • 获取到正确的HandlerMethod后,使用HandlerAdapter将其转换为可以执行的形式。不同的控制器可能需要不同类型的适配器支持,因此这一步也包括了选择合适的适配器。
  1. 执行处理逻辑
  • 一旦确定了如何调用控制器的方法,就通过所选的HandlerAdapter执行该方法以完成业务逻辑处理。
  1. 返回响应
  • 处理完成后,根据处理结果生成适当的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的改造

  • 通过改造,我们实现了代码的简化,使得业务程序不再局限于doGet()方法内,而是可以根据业务需求自由使用任何方法名。

  • 同时,这种改造为支持多种请求方式(如POST、PUT、DELETE等)提供了便利。

2. 业务逻辑的独立性

  • 在原始的Servlet规范中,业务逻辑全部写在doGet()doPost()等方法中,每个业务逻辑程序都是一个独立的Servlet。

  • 改造后,我们使用一个唯一的DispatcherServlet来拦截请求,并根据注解定位需要调用的方法,使得我们可以更专注于业务代码的实现。

3. Dispatcher设计模式的学习

  • 这种称为Dispatcher的设计模式,是值得我们深入学习和掌握的。

4. ApplicationContext的拆解

  • 我们拆解了ApplicationContext,形成了两级上下文:一级用于IoC容器(parent上下文),一级用于Web上下文(WebApplicationContext)。

  • WebApplicationContext持有对parent上下文的引用,方便管理和扩展。

5. @RequestMapping注解的使用

  • 我们增加了@RequestMapping注解来声明URL映射。

  • 引入了RequestMappingHandlerMappingRequestMappingHandlerAdapter,分别负责包装URL映射关系和映射后的处理过程。

6. DispatcherServlet功能的分治

  • 通过这些拆解工作,我们将DispatcherServlet的功能进行了分治,使得专门的事情由专门的部件完成,有利于未来的扩展。

完整源代码

  • 完整源代码可以在这里找到。

课后思考题

  • 学完这节课后,尝试增加对POST方法的支持。思考是否需要改变现有的程序结构,并在留言区交流讨论。

  • 欢迎分享这节课给需要的朋友,我们下节课见!