4 手写实现SpringMVC,第四节:匹配用户请求、执行映射方法

本文主要是介绍4 手写实现SpringMVC,第四节:匹配用户请求、执行映射方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在上一篇我们已经完成了配置的url到方法的映射,并且完成了method的各参数的注解、参数名、类型等的映射配置。

这一篇就很简单了,就是通过获取request的请求地址和参数,和已经加载好的映射进行比对,如果匹配上了就执行对应的方法。

直接上代码:

@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//根据请求的URL去查找对应的methodtry {boolean isMatcher = pattern(req, resp);if (!isMatcher) {out(resp,"404 not found");}} catch (Exception ex) {ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream();ex.printStackTrace(new java.io.PrintWriter(buf, true));String expMessage = buf.toString();buf.close();out(resp, "500 Exception" + "\n" + expMessage);}}

private boolean pattern(HttpServletRequest request, HttpServletResponse response) throws Exception {if (handlerMapping.isEmpty()) {return false;}//用户请求地址String requestUri = request.getRequestURI();String contextPath = request.getContextPath();//用户写了多个"///",只保留一个requestUri = requestUri.replace(contextPath, "").replaceAll("/+", "/");//遍历HandlerMapping,寻找url匹配的for (Map.Entry<String, HandlerModel> entry : handlerMapping.entrySet()) {if (entry.getKey().equals(requestUri)) {//取出对应的HandlerModelHandlerModel handlerModel = entry.getValue();Map<String, Integer> paramIndexMap = handlerModel.paramMap;//定义一个数组来保存应该给method的所有参数赋值的数组Object[] paramValues = new Object[paramIndexMap.size()];Class<?>[] types = handlerModel.method.getParameterTypes();//遍历一个方法的所有参数[name->0,addr->1,HttpServletRequest->2]for (Map.Entry<String, Integer> param : paramIndexMap.entrySet()) {String key = param.getKey();if (key.equals(HttpServletRequest.class.getName())) {paramValues[param.getValue()] = request;} else if (key.equals(HttpServletResponse.class.getName())) {paramValues[param.getValue()] = response;} else {//如果用户传了参数,譬如 name= "wolf",做一下参数类型转换,将用户传来的值转为方法中参数的类型String parameter = request.getParameter(key);if (parameter != null) {paramValues[param.getValue()] = convert(parameter.trim(), types[param.getValue()]);}}}//激活该方法handlerModel.method.invoke(handlerModel.controller, paramValues);return true;}}return false;}

由于用户传来的都是String,我们需要根据参数的具体类型,进行转换

/*** 将用户传来的参数转换为方法需要的参数类型*/private Object convert(String parameter, Class<?> targetType) {if (targetType == String.class) {return parameter;} else if (targetType == Integer.class || targetType == int.class) {return Integer.valueOf(parameter);} else if (targetType == Long.class || targetType == long.class) {return Long.valueOf(parameter);} else if (targetType == Boolean.class || targetType == boolean.class) {if (parameter.toLowerCase().equals("true") || parameter.equals("1")) {return true;} else if (parameter.toLowerCase().equals("false") || parameter.equals("0")) {return false;}throw new RuntimeException("不支持的参数");}else {//TODO 还有很多其他的类型,char、double之类的依次类推,也可以做List<>, Array, Map之类的转化return null;}}

以上就OK了。

下面还是贴个完整代码吧

package com.tianyalei.mvc;import com.tianyalei.mvc.annotation.*;
import com.tianyalei.mvc.util.Play;import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** Created by wuwf on 17/6/28.* 入口Sevlet*/
public class DispatcherServlet extends HttpServlet {private List<String> classNames = new ArrayList<>();private Map<String, Object> instanceMapping = new HashMap<>();private Map<String, HandlerModel> handlerMapping = new HashMap<>();@Overridepublic void init(ServletConfig config) throws ServletException {System.out.println("我是初始化方法");scanPackage(config.getInitParameter("scanPackage"));doInstance();//注入值doAutoWired();doHandlerMapping();System.out.println("初始化完毕");}/*** 扫描包下的所有类*/private void scanPackage(String pkgName) {//获取指定的包的实际路径url,将com.tianyalei.mvc变成目录结构com/tianyalei/mvcURL url = getClass().getClassLoader().getResource("/" + pkgName.replaceAll("\\.", "/"));//转化成file对象File dir = new File(url.getFile());//递归查询所有的class文件for (File file : dir.listFiles()) {//如果是目录,就递归目录的下一层,如com.tianyalei.mvc.controllerif (file.isDirectory()) {scanPackage(pkgName + "." + file.getName());} else {//如果是class文件,并且是需要被spring托管的if (!file.getName().endsWith(".class")) {continue;}//举例,className = com.tianyalei.mvc.controller.WebControllerString className = pkgName + "." + file.getName().replace(".class", "");//判断是否被Controller或者Service注解了,如果没注解,那么我们就不管它,譬如annotation包和DispatcherServlet类我们就不处理try {Class<?> clazz = Class.forName(className);if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {classNames.add(className);}} catch (ClassNotFoundException e) {e.printStackTrace();}}}}/*** 实例化*/private void doInstance() {if (classNames.size() == 0) {return;}//遍历所有的被托管的类,并且实例化for (String className : classNames) {try {Class<?> clazz = Class.forName(className);//如果是Controllerif (clazz.isAnnotationPresent(Controller.class)) {//举例:webController -> new WebControllerinstanceMapping.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance());} else if (clazz.isAnnotationPresent(Service.class)) {//获取注解上的值Service service = clazz.getAnnotation(Service.class);//举例:QueryServiceImpl上的@Service("myQueryService")String value = service.value();//如果有值,就以该值为keyif (!"".equals(value.trim())) {instanceMapping.put(value.trim(), clazz.newInstance());} else {//没值时就用接口的名字首字母小写//获取它的接口Class[] inters = clazz.getInterfaces();//此处简单处理了,假定ServiceImpl只实现了一个接口for (Class c : inters) {//举例 modifyService->new ModifyServiceImpl()instanceMapping.put(lowerFirstChar(c.getSimpleName()), clazz.newInstance());break;}}}} catch (Exception e) {e.printStackTrace();}}}/*** 给被AutoWired注解的属性注入值*/private void doAutoWired() {if (instanceMapping.isEmpty()) {return;}//遍历所有被托管的对象for (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {//查找所有被Autowired注解的属性// getFields()获得某个类的所有的公共(public)的字段,包括父类;// getDeclaredFields()获得某个类的所有申明的字段,即包括public、private和proteced,但是不包括父类的申明字段。Field[] fields = entry.getValue().getClass().getDeclaredFields();for (Field field : fields) {//没加autowired的不需要注值if (!field.isAnnotationPresent(Autowired.class)) {continue;}String beanName;//获取AutoWired上面写的值,譬如@Autowired("abc")Autowired autowired = field.getAnnotation(Autowired.class);if ("".equals(autowired.value())) {//例 searchService。注意,此处是获取属性的类名的首字母小写,与属性名无关,可以定义@Autowired SearchService abc都可以。beanName = lowerFirstChar(field.getType().getSimpleName());} else {beanName = autowired.value();}//将私有化的属性设为true,不然访问不到field.setAccessible(true);//去映射中找是否存在该beanName对应的实例对象if (instanceMapping.get(beanName) != null) {try {field.set(entry.getValue(), instanceMapping.get(beanName));} catch (IllegalAccessException e) {e.printStackTrace();}}}}}/*** 建立url到方法的映射*/private void doHandlerMapping() {if (instanceMapping.isEmpty()) {return;}//遍历托管的对象,寻找Controllerfor (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {Class<?> clazz = entry.getValue().getClass();//只处理Controller的,只有Controller有RequestMappingif (!clazz.isAnnotationPresent(Controller.class)) {continue;}//定义urlString url = "/";//取到Controller上的RequestMapping值if (clazz.isAnnotationPresent(RequestMapping.class)) {RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);url += requestMapping.value();}//获取方法上的RequestMappingMethod[] methods = clazz.getMethods();//只处理带RequestMapping的方法for (Method method : methods) {if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);//requestMapping.value()即是在requestMapping上注解的请求地址,不管用户写不写"/",我们都给他补上String realUrl = url + "/" + methodMapping.value();//替换掉多余的"/",因为有的用户在RequestMapping上写"/xxx/xx",有的不写,所以我们处理掉多余的"/"realUrl = realUrl.replaceAll("/+", "/");//获取所有的参数的注解,有几个参数就有几个annotation[],为毛是数组呢,因为一个参数可以有多个注解……Annotation[][] annotations = method.getParameterAnnotations();//由于后面的Method的invoke时,需要传入所有参数的值的数组,所以需要保存各参数的位置/*以Search方法的这几个参数为例 @RequestParam("name") String name, HttpServletRequest request, HttpServletResponse response未来在invoke时,需要传入类似这样的一个数组["abc", request, response]。"abc"即是在Post方法中通过request.getParameter("name")来获取Request和response这个简单,在post方法中直接就有。所以我们需要保存@RequestParam上的value值,和它的位置。譬如 name->0,只有拿到了这两个值,才能将post中通过request.getParameter("name")得到的值放在参数数组的第0个位置。同理,也需要保存request的位置1,response的位置2*/Map<String, Integer> paramMap = new HashMap<>();//获取方法里的所有参数的参数名(注意:此处使用了ASM.jar 版本为asm-3.3.1,需要在web-inf下建lib文件夹,引入asm-3.3.1.jar,自行下载)//如Controller的add方法,将得到如下数组["name", "addr", "request", "response"]String[] paramNames = Play.getMethodParameterNamesByAsm4(clazz, method);//获取所有参数的类型,提取Request和Response的索引Class<?>[] paramTypes = method.getParameterTypes();for (int i = 0; i < annotations.length; i++) {//获取每个参数上的所有注解Annotation[] anns = annotations[i];if (anns.length == 0) {//如果没有注解,则是如String abc,Request request这种,没写注解的//如果没被RequestParam注解// 如果是Request或者Response,就直接用类名作key;如果是普通属性,就用属性名Class<?> type = paramTypes[i];if (type == HttpServletRequest.class || type == HttpServletResponse.class) {paramMap.put(type.getName(), i);} else {//参数没写@RequestParam注解,只写了String name,那么通过java是无法获取到name这个属性名的//通过上面asm获取的paramNames来映射paramMap.put(paramNames[i], i);}continue;}//有注解,就遍历每个参数上的所有注解for (Annotation ans : anns) {//找到被RequestParam注解的参数,并取value值if (ans.annotationType() == RequestParam.class) {//也就是@RequestParam("name")上的"name"String paramName = ((RequestParam) ans).value();//如果@RequestParam("name")这里面if (!"".equals(paramName.trim())) {paramMap.put(paramName, i);}}}}HandlerModel model = new HandlerModel(method, entry.getValue(), paramMap);handlerMapping.put(realUrl, model);}}}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//根据请求的URL去查找对应的methodtry {boolean isMatcher = pattern(req, resp);if (!isMatcher) {out(resp,"404 not found");}} catch (Exception ex) {ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream();ex.printStackTrace(new java.io.PrintWriter(buf, true));String expMessage = buf.toString();buf.close();out(resp, "500 Exception" + "\n" + expMessage);}}private boolean pattern(HttpServletRequest request, HttpServletResponse response) throws Exception {if (handlerMapping.isEmpty()) {return false;}//用户请求地址String requestUri = request.getRequestURI();String contextPath = request.getContextPath();//用户写了多个"///",只保留一个requestUri = requestUri.replace(contextPath, "").replaceAll("/+", "/");//遍历HandlerMapping,寻找url匹配的for (Map.Entry<String, HandlerModel> entry : handlerMapping.entrySet()) {if (entry.getKey().equals(requestUri)) {//取出对应的HandlerModelHandlerModel handlerModel = entry.getValue();Map<String, Integer> paramIndexMap = handlerModel.paramMap;//定义一个数组来保存应该给method的所有参数赋值的数组Object[] paramValues = new Object[paramIndexMap.size()];Class<?>[] types = handlerModel.method.getParameterTypes();//遍历一个方法的所有参数[name->0,addr->1,HttpServletRequest->2]for (Map.Entry<String, Integer> param : paramIndexMap.entrySet()) {String key = param.getKey();if (key.equals(HttpServletRequest.class.getName())) {paramValues[param.getValue()] = request;} else if (key.equals(HttpServletResponse.class.getName())) {paramValues[param.getValue()] = response;} else {//如果用户传了参数,譬如 name= "wolf",做一下参数类型转换,将用户传来的值转为方法中参数的类型String parameter = request.getParameter(key);if (parameter != null) {paramValues[param.getValue()] = convert(parameter.trim(), types[param.getValue()]);}}}//激活该方法handlerModel.method.invoke(handlerModel.controller, paramValues);return true;}}return false;}/*** 将用户传来的参数转换为方法需要的参数类型*/private Object convert(String parameter, Class<?> targetType) {if (targetType == String.class) {return parameter;} else if (targetType == Integer.class || targetType == int.class) {return Integer.valueOf(parameter);} else if (targetType == Long.class || targetType == long.class) {return Long.valueOf(parameter);} else if (targetType == Boolean.class || targetType == boolean.class) {if (parameter.toLowerCase().equals("true") || parameter.equals("1")) {return true;} else if (parameter.toLowerCase().equals("false") || parameter.equals("0")) {return false;}throw new RuntimeException("不支持的参数");}else {//TODO 还有很多其他的类型,char、double之类的依次类推,也可以做List<>, Array, Map之类的转化return null;}}private void out(HttpServletResponse response, String str) {try {response.setContentType("application/json;charset=utf-8");response.getWriter().print(str);} catch (IOException e) {e.printStackTrace();}}private class HandlerModel {Method method;Object controller;Map<String, Integer> paramMap;public HandlerModel(Method method, Object controller, Map<String, Integer> paramMap) {this.method = method;this.controller = controller;this.paramMap = paramMap;}}private String lowerFirstChar(String className) {char[] chars = className.toCharArray();chars[0] += 32;return String.valueOf(chars);}}

我再对Controller修改一下,加个不带RequestParam注解的方法

package com.tianyalei.mvc.controller;import com.tianyalei.mvc.annotation.*;
import com.tianyalei.mvc.service.ModifyService;
import com.tianyalei.mvc.service.QueryService;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** Created by wuwf on 17/6/28.*/
@Controller
@RequestMapping("/web")
public class WebController {@Autowired("myQueryService")private QueryService queryService;@Autowiredprivate ModifyService modifyService;@RequestMapping("/search")public void search(@RequestParam("name") String name, HttpServletRequest request, HttpServletResponse response) {String result = queryService.search(name);out(response, result);}@RequestMapping("/add")public void add(@RequestParam("name") String name,@RequestParam("addr") String addr,HttpServletRequest request, HttpServletResponse response) {String result = modifyService.add(name, addr);out(response, result);}@RequestMapping("/update")public void update(String name, boolean flag,HttpServletRequest request, HttpServletResponse response) {out(response, "我是name:" + name + "flag为:" + flag);}@RequestMapping("/remove")public void remove(@RequestParam("name") Integer id,HttpServletRequest request, HttpServletResponse response) {String result = modifyService.remove(id);out(response, result);}private void out(HttpServletResponse response, String str) {try {response.setContentType("application/json;charset=utf-8");response.getWriter().print(str);} catch (IOException e) {e.printStackTrace();}}
}


重启Tomcat,测试一下。

把里面的方法都试一下,发现基本已经OK了,只要参数传对,整个流程是能走通的。

还有一些遗留问题,譬如flag不传值时,注入时默认为null,而方法中定义的是boolean,所以会报错。这里就牵扯到一个require的问题了,就是说该参数是否是必传的,还有是否需要我们赋默认值的问题。

当然了,扩展起来还是很简单的,譬如SpringMVC在遇到小写的boolean或者int时,而用户又不传值时会赋默认值,做法应该就是遍历参数值数组,将为null的赋初值。如果是大写的Boolean就不赋值。如果在RequestParam上加了require为true,那么当为null时,我们应该直接抛出异常给用户。

还有一些比较难点的扩展,譬如/web/query/{userId},@PathVariable, @ModelAttribute,还有正则匹配/web/*,ModelMap,ModelAndView还有参数校验Hibernate Valider等等,SpringMVC非常强大,但是原理基本就是这样。在这个基础上,我们也是可以完成上面那些扩展的。





这篇关于4 手写实现SpringMVC,第四节:匹配用户请求、执行映射方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/879947

相关文章

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Python实现图片分割的多种方法总结

《Python实现图片分割的多种方法总结》图片分割是图像处理中的一个重要任务,它的目标是将图像划分为多个区域或者对象,本文为大家整理了一些常用的分割方法,大家可以根据需求自行选择... 目录1. 基于传统图像处理的分割方法(1) 使用固定阈值分割图片(2) 自适应阈值分割(3) 使用图像边缘检测分割(4)

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Java中Switch Case多个条件处理方法举例

《Java中SwitchCase多个条件处理方法举例》Java中switch语句用于根据变量值执行不同代码块,适用于多个条件的处理,:本文主要介绍Java中SwitchCase多个条件处理的相... 目录前言基本语法处理多个条件示例1:合并相同代码的多个case示例2:通过字符串合并多个case进阶用法使用

Mysql用户授权(GRANT)语法及示例解读

《Mysql用户授权(GRANT)语法及示例解读》:本文主要介绍Mysql用户授权(GRANT)语法及示例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql用户授权(GRANT)语法授予用户权限语法GRANT语句中的<权限类型>的使用WITH GRANT

Java中的Lambda表达式及其应用小结

《Java中的Lambda表达式及其应用小结》Java中的Lambda表达式是一项极具创新性的特性,它使得Java代码更加简洁和高效,尤其是在集合操作和并行处理方面,:本文主要介绍Java中的La... 目录前言1. 什么是Lambda表达式?2. Lambda表达式的基本语法例子1:最简单的Lambda表

Java中Scanner的用法示例小结

《Java中Scanner的用法示例小结》有时候我们在编写代码的时候可能会使用输入和输出,那Java也有自己的输入和输出,今天我们来探究一下,对JavaScanner用法相关知识感兴趣的朋友一起看看吧... 目录前言一 输出二 输入Scanner的使用多组输入三 综合练习:猜数字游戏猜数字前言有时候我们在

C# foreach 循环中获取索引的实现方式

《C#foreach循环中获取索引的实现方式》:本文主要介绍C#foreach循环中获取索引的实现方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、手动维护索引变量二、LINQ Select + 元组解构三、扩展方法封装索引四、使用 for 循环替代