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

相关文章

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

创建Java keystore文件的完整指南及详细步骤

《创建Javakeystore文件的完整指南及详细步骤》本文详解Java中keystore的创建与配置,涵盖私钥管理、自签名与CA证书生成、SSL/TLS应用,强调安全存储及验证机制,确保通信加密和... 目录1. 秘密键(私钥)的理解与管理私钥的定义与重要性私钥的管理策略私钥的生成与存储2. 证书的创建与

浅析Spring如何控制Bean的加载顺序

《浅析Spring如何控制Bean的加载顺序》在大多数情况下,我们不需要手动控制Bean的加载顺序,因为Spring的IoC容器足够智能,但在某些特殊场景下,这种隐式的依赖关系可能不存在,下面我们就来... 目录核心原则:依赖驱动加载手动控制 Bean 加载顺序的方法方法 1:使用@DependsOn(最直

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问