通过单一的Servlet拦截请求分派任务
这里我们的目标是通过一个 Controller 来拦截用户请求,找到相应的处理类进行逻辑处理,然后将处理的结果发送给客户端。
通过单一的Servlet拦截请求并分派任务,通常被称为前端控制器模式,也就是在一个Servlet中集中处理所有的请求,并根据请求的内容将任务分发给相应的处理逻辑。
创建前端控制器Servlet: 首先,你需要创建一个Servlet,该Servlet将作为前端控制器,拦截所有的请求并进行任务分派。在下面的配置文件中,这个Servlet的类是 com.minis.web.DispatcherServlet
。
配置Servlet映射: 在下面配置文件中,有 <servlet>
和 <servlet-mapping>
部分。<servlet>
部分定义了前端控制器Servlet的配置,而 <servlet-mapping>
部分将请求路径映射到该前端控制器Servlet。
在下面的配置文件中,前端控制器Servlet的名称是 “minisMVC”,它会拦截根路径 “/“ 的请求。这意味着所有的请求都会被这个Servlet处理。
任务分派: 在前端控制器Servlet中,你需要根据请求的内容,将任务分派给不同的处理逻辑。这通常涉及到根据请求的URL或其他参数来确定要执行的操作。这里通过 Servlet 拦截所有请求,处理映射关系,调用业务逻辑代码,处理返回值回递给浏览器。
初始化参数和配置: 在下面的配置文件中,使用了 <init-param>
来为前端控制器Servlet提供初始化参数。这些参数可以在Servlet中读取,用于配置或传递信息。
配置文件
首先是minisMVC-servlet.xml
一开始的minisMVC-servlet.xml ,是一个Bean 配置,只是把 id 设置成了一个 URL 的形式,来匹配后端的程序,访问 /helloworld 的时候,对应调用 HelloWorldBean 类里的 doGet() 方法。属性有 id、class 与 value :
1 2 3 4
| <?xml version="1.0" encoding="UTF-8" ?> <beans> <bean id="/helloworld" class="com.minis.test.HelloWorldBean" value="doGet"/> </beans>
|
后来的minisMVC-servlet.xml可以简化成如下样式,即在 minisMVC-servlet.xml 里新增 <components>
和 <component-scan
两个标签,分别表示组件配置以及组件的扫描配置。也就是说,扫描一个包,自动配置包内满足条件的类,省去手工配置过程。
下述文件将扫描 com.minis.test 里所有的类文件,加载并实例化它们(需要配合引入的 @RequestMapping
使用)。
1 2 3 4
| <?xml version="1.0" encoding="UTF-8" ?> <components> <component-scan base-package="com.minis.test"/> </components>
|
然后是web.xml ,下面的配置文件的意思就是当 Servlet 容器启动的时候,先读取 web.xml 配置,加载配置文件中的 servlet,也就是 DispatcherServlet,并规定它拦截所有的 HTTP 请求,所以它就是控制器。
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
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID"> <servlet> <servlet-name>minisMVC</servlet-name> <servlet-class>com.minis.web.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/minisMVC-servlet.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>minisMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
|
我们注意到这个控制器 DispatcherServlet 有一个参数 contextConfigLocation,它配置了控制器要找的逻辑处理类的文件
1 2 3 4 5 6 7
| <init-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/minisMVC-servlet.xml </param-value> </init-param>
|
因此,为了启动这个 servlet,我们要提前解析 minisMVC-servlet.xml 文件。
即类似于在解析JavaBean时的ClassPathXmlResource,这里也定义一个ClassPathXmlResource,使用SaxReader依次解析xml文件,读取 minisMVC-servlet.xml
中的属性 id、class 与 value,并存入定义实体类 MappingValue 里的三个属性:uri、clz 与 method
引入@RequestMapping
引入@RequestMapping是为了手工地将映射关系写到 XML 配置文件里,省去我们的手工配置工作。
即使用如下简化的minisMVC-servlet.xml扫描一个包,自动配置包内满足条件的类,省去手工配置过程。
1 2 3 4
| <?xml version="1.0" encoding="UTF-8" ?> <components> <component-scan base-package="com.minis.test"/> </components>
|
@RequestMapping
的实现目的是将 URL 和业务处理类中的某个方法对应起来, 在 Spring 框架里, @RequestMapping 注解可支持定义在类上,但我们这里暂时不支持该注解定义在类上,只定义在方法上。
我们看一下注解定义。
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.minis.web;
import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy;
@Target(value={ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String value() default ""; }
|
我们提供一个新类 XmlScanComponentHelper,专门用来解析简化的minisMVC-servlet.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 34 35 36 37 38 39 40 41 42 43 44
| package com.minis.web;
import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map;
import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader;
public class XmlScanComponentHelper { public static List<String> getNodeValue(URL xmlPath) { List<String> packages = new ArrayList<>(); SAXReader saxReader=new SAXReader(); Document document = null; try { document = saxReader.read(xmlPath); } catch (DocumentException e) { e.printStackTrace(); } Element root = document.getRootElement(); Iterator it = root.elementIterator();
while (it.hasNext()) { Element element = (Element) it.next(); packages.add(element.attributeValue("base-package")); }
return packages; }
}
|
实现DispatcherSevlet
前面引入的配置文件和 @RequestMapping
注解已经完成了配置文件的加载和自动扫描,接下来就可以实现MVC 的核心启动类DispatcherSevlet 。
在 Spring Framework 中, DispatcherServlet
是一个关键类,用于处理Web应用程序中的请求分发和处理。它可以看作是一个前端控制器,根据请求的内容将任务分发给不同的处理器,并协调整个请求-响应周期中的各个环节。
即MVC 的基本思路是屏蔽 Servlet 的概念,让程序员主要写业务逻辑代码。
浏览器访问的 URL 通过映射机制找到实际的业务逻辑方法。这里通过 Servlet 拦截所有请求,处理映射关系,调用业务逻辑代码,处理返回值回递给浏览器。程序员写的业务逻辑程序,也叫做 Bean。
这里的主要步骤是 :
Servlet 初始化方法 . 主要作用是处理从外部传入的资源,将 XML 文件内容解析后,相应的包存入 packageNames 内。然后用 Refresh()
函数创建 Bean
Refresh()
函数: 第一步初始化 controller方法 initController()
,, 第二步则是初始化 URL 映射 initMapping()
。
initController()
: 主要功能是对扫描到的每一个类进行加载和实例化,让 类与类名
, 类与对象
建立映射关系,分别存在 controllerClasses
和 controllerObjs
这两个 map 里,类名就是 key 的值。
initMapping()
: 主要功能是根据上一步中得到的类与类名
,类与对象
的映射关系 ,
先通过类与类名
的映射关系,遍历类的方法, 初始化URL 映射 . 过程是先找到使用了注解 @RequestMapping
的方法, 再将其URL 存放到 urlMappingNames
里,url与方法的映射关系存放到 mappingMethods
里。
再通过类与对象
的映射关系 得到实例化对象,将url与对象的映射关系放到 mappingObjs
中.
doGet()
方法. 由Servlet容器(如Tomcat)自动触发的方法,用于处理HTTP GET请求。当客户端发送一个HTTP GET请求到与该Servlet映射的URL时,Servlet容器会调用**doGet()**方法来处理这个请求。
主要是通过 mappingMethods
和 mappingObjs
获取与请求路径对应的方法对象 ,依赖反射机制进行调用。
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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
| package com.minis.web;
import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;
import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import com.minis.test.HelloWorldBean;
public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private String sContextConfigLocation; private List<String> packageNames = new ArrayList<>(); private Map<String,Object> controllerObjs = new HashMap<>(); private List<String> controllerNames = new ArrayList<>(); private Map<String,Class<?>> controllerClasses = new HashMap<>(); private List<String> urlMappingNames = new ArrayList<>(); private Map<String,Object> mappingObjs = new HashMap<>(); private Map<String,Method> mappingMethods = new HashMap<>();
public DispatcherServlet() { super(); }
@Override public void init(ServletConfig config) throws ServletException { super.init(config); sContextConfigLocation = config.getInitParameter("contextConfigLocation");
URL xmlPath = null; try { xmlPath = this.getServletContext().getResource(sContextConfigLocation); } catch (MalformedURLException e) { e.printStackTrace(); } this.packageNames = XmlScanComponentHelper.getNodeValue(xmlPath); Refresh();
}
protected void Refresh() { initController(); initMapping(); }
protected void initController() { this.controllerNames = scanPackages(this.packageNames);
for (String controllerName : this.controllerNames) { Object obj = null; Class<?> clz = null;
try { clz = Class.forName(controllerName); this.controllerClasses.put(controllerName,clz); } catch (ClassNotFoundException e) { e.printStackTrace(); } try { obj = clz.newInstance(); this.controllerObjs.put(controllerName, obj); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
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; }
protected void initMapping() { for (String controllerName : this.controllerNames) { Class<?> clazz = this.controllerClasses.get(controllerName); Object obj = this.controllerObjs.get(controllerName); Method[] methods = clazz.getDeclaredMethods(); if(methods!=null){ for(Method method : methods){ boolean isRequestMapping = method.isAnnotationPresent(RequestMapping.class); if (isRequestMapping){ String methodName = method.getName(); String urlmapping = method.getAnnotation(RequestMapping.class).value(); this.urlMappingNames.add(urlmapping); this.mappingObjs.put(urlmapping, obj); this.mappingMethods.put(urlmapping, method); } } } } }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String sPath = request.getServletPath(); System.out.println("in doGet(),sPath :" + sPath); if (!this.urlMappingNames.contains(sPath)) { return; }
Object obj = null; Object objResult = null; try { Method method = this.mappingMethods.get(sPath); obj = this.mappingObjs.get(sPath); objResult = method.invoke(obj); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } response.getWriter().append(objResult.toString()); }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
|
测试文件
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
| package com.minis.test;
import com.minis.beans.BeansException; import com.minis.context.ClassPathXmlApplicationContext; import com.minis.web.RequestMapping;
public class HelloWorldBean { @RequestMapping("/test1") public String doTest1() { return "test 1, hello world!"; } @RequestMapping("/test2") public String doTest2() { return "test 2, hello world!"; }
@RequestMapping("/test3") public String doTest3() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("annotationTest.xml"); AService aService; try { aService = (AService)ctx.getBean("aservice"); aService.sayHello(); } catch (BeansException e) { e.printStackTrace(); } return "aService.sayHello();"; } }
|
启动Tomcat, 在浏览器输入框内键入:localhost:8080/test,即可看到结果.