自己实现spring核心功能 三

  • 2019 年 10 月 3 日
  • 筆記

前言

 前两篇已经基本实现了spring的核心功能,下面讲到的参数绑定是属于springMvc的范畴了。本篇主要将请求到servlet后怎么去做映射和处理。首先来看一看dispatherServlet的基本流程,这我在以前的博客里面也讲过,传送门

这里先给个我们的简易处理流程

 

准备工作

 为了能将请求传递,我们需要写一个控制器类来接收请求,写两个接口来处理请求

HomeController类

 1 @JCController   2 @JCRequestMapping("/home")   3 public class HomeController {   4     @JCAutoWrited   5     private IHomeService homeService;   6   7     @JCRequestMapping("/sayHi")   8     public String sayHi() {   9         return homeService.sayHi();  10     }  11  12     @JCRequestMapping("/getName")  13     public String getName(Integer id,String no) {  14         return homeService.getName(id,no);  15     }  16     @JCRequestMapping("/getRequestBody")  17     public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {  18         return homeService.getRequestBody(id,no,userInfo);  19     }  20 }

View Code

HomeService类

 1 @JCService   2 public class HomeService  implements IHomeService{   3   4     @JCAutoWrited   5      StudentService studentService;   6     @Override   7     public String sayHi() {   8       return   studentService.sayHi();   9     }  10  11     @Override  12     public String getName(Integer id,String no) {  13         return "SB0000"+id;  14     }  15  16     @Override  17     public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {  18         return "userName="+userInfo.getName()+" no="+no;  19     }  20 }

View Code

StudentService类

1 @JCService  2 public class StudentService  implements IStudentService{  3     @Override  4     public String sayHi(){  5         return "Hello world!";  6     }  7 }

View Code

 

无参请求过程

根据上面的图,我们在浏览器发起请求localhost:8080/home/sayHi,请求会到达JCDispatherServlet类,由于我们是GET请求

 @Override   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {       doPost(req, resp);   }
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatcherServlet(req, resp);
} catch (Exception e) {
e.printStackTrace();
}

}

会走到doDispatcherServlet方法里面处理请求

 1     void doDispatcherServlet(HttpServletRequest req, HttpServletResponse resp) throws Exception {   2         String url = req.getRequestURI();   3         url = url.replace(req.getContextPath(), "").replaceAll("/+", "/");   4         if (!urlMapping.containsKey(url)) {   5             resp.getWriter().write("404! url is not found!");`   6             return;   7         }   8   9         Method method = urlMapping.get(url);  10         String className =  11         method.getDeclaringClass().getSimpleName();  12         className = firstLowerCase(className);  13         if (!ioc.containsKey(className)) {  14             resp.getWriter().write("500! claas not defind !");  15             return;  16         }  17         Object[] args=null ;  18  19         //调用目标方法  20         Object res = method.invoke(ioc.get(className), args);  21  22         resp.setContentType("text/html;charset=utf-8");  23         resp.getWriter().write(res.toString());  24     }

第九行代码会以url为key从HashMap取出数据,返回Method对象,它对应到我们在HomeController中定义的方法public String sayHi() 。

public Object invoke(Object obj, Object... args) 

通过反射调用方法,需要2个参数,第一个是方法所在类的对象,一个是方法所需要的参数。

下面的代码就是在ioc容器中取HomeController类对象,如果没有,就抛500错误。

Method method = urlMapping.get(url);
String className = method.getDeclaringClass().getSimpleName();
className = firstLowerCase(className);
if (!ioc.containsKey(className)) {
resp.getWriter().write("500! claas not defind !");
return;
}

//调用目标方法
Object res = method.invoke(ioc.get(className), null);
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write(res.toString());

 

可以看到,无参请求args传过去null,然后把调用结果返回,浏览器打印结果,证明无参可以使用了。

接下来就需要接收参数的传递了。

参数传递

 我们常用参数传递大致分为三种,GET传参,POST方式form传参,POST方式json传参。

 

  前两种传参都可以通过HttpServletRequest的getParameterMap()获取到,它返回的是Map<String, String[]>结构,我们需要遍历map,拿到里面的key和值。

 @JCRequestMapping("/getName")    public String getName(Integer id,String no) {          return homeService.getName(id,no);      }

我们在浏览器请求输入http://localhost:8080/home/getName?id=11&no=lisi

传过来的是这样一个数据,里面有字段名称,有字段的value

 而json格式的参数,需要从输入流里面获取 req.getInputStream() 

 知道了传进来的字段名称后,现在有2个问题,一个是方法参数的类型,一个是方法参数的顺序,这就涉及到了参数的绑定

 

参数绑定

 什么叫参数绑定,举个例子

从这个方法的声明,可以看到,第一个参数要求是名称为id且类型为Integer,第二个参数要求名称为no且类型为String。

我们需要把request传进来的参数列表,按照方法的要求,一个一个传进去,不能少也不能类型错乱。

要完成这个要求,我们首先需要获得方法的形参列表,其次要把参数按顺序,按类型给组装好。

1.获取形参列表

2.按要求组装好参数

 

获取形参列表

从这里可以看到 获取到的参数个数是正常的,类型也没有问题,但字段名称显然是错误的,咱们正确的字段名称应该是id、no

通过网上可以查到,这个需要达到3个要求才能正常使用。

1.jdk1.8 

2.在idea设置参数 -parameters

3.Build->RebuildProject

 设置方法博客地址

 还有个前提,每次JCDispatherServlet代码变更,需要重新编译项目Build->RebuildProject 生成最新的代码

 重新编译后,调试结果如下

到此获取形参的工作已经做好了,只需要循环parameters数组就好了。

类型转换

不过还有个比较棘手的问题

我们发现,从request获取到的实参都是String数组类型,需要根据形参转成指定类型,而且只能通过反射转换。

 所以一番折腾后,把入参转换成指定类型的实参的代码如下

 1   Object getInstanceField(Parameter parameter, String value) {   2         if (parameter.getName().isEmpty()) {   3             return null;   4         }   5         Class typeClass = parameter.getType();   6         Constructor con = null;   7         try {   8             con = typeClass.getConstructor(value.getClass());   9             return con.newInstance(value);  10         } catch (InvocationTargetException e) {  11             e.printStackTrace();  12         } catch (IllegalAccessException e) {  13             e.printStackTrace();  14         } catch (InstantiationException e) {  15             e.printStackTrace();  16         } catch (NoSuchMethodException e) {  17             e.printStackTrace();  18         }  19         return null;  20     }  21  22     Object[] doPostParam(HttpServletRequest req, Method method) {  23         Parameter[] parameters = method.getParameters();  24         Object[] requestParam = new Object[parameters.length];  25         int i = 0;  26         for (Parameter p : parameters) {  27             requestParam[i] = null;  28             if (!p.getName().isEmpty()) {  29                 requestParam[i] = getInstanceField(p, req.getParameter(p.getName()));  30             }  31             i++;  32         }  33         return requestParam;  34     }  35  36     Object[] doJsonParam(String json, Method method) {  37         if (null == json || json.isEmpty()) {  38             return null;  39         }  40         Parameter[] parameters = method.getParameters();  41         Object[] requestParam = new Object[parameters.length];  42         JSONObject jsonObject = JSONObject.parseObject(json);  43         int i = 0;  44         for (Parameter p : parameters) {  45             Object val = jsonObject.getObject(p.getName(), p.getType());  46             requestParam[i] = val;  47             i++;  48         }  49         return requestParam;  50     }  51  52     Object[] doGetParam(Map<String, String[]> map, Method method) {  53         if (null == map || map.size() == 0) {  54             return null;  55         }  56         Parameter[] parameters = method.getParameters();  57         int i = 0;  58         Object[] requestParam = new Object[parameters.length];  59         for (Parameter p : parameters) {  60             requestParam[i] = null;  61             if (map.containsKey(p.getName())) {  62                 String[] values = map.get(p.getName());  63                 requestParam[i] = getInstanceField(p, values[0]);  64             }  65             i++;  66         }  67         return requestParam;  68     }

View Code

 

返回Object[],里面的类型和顺序需要保证准确 

浏览器调用结果Get请求

 

也可以用PostMan 发起Post请求

 

 这两种都每办法传对象,而我们开发者需要传递对象。所以,再加个接口,测试包含对象时的混合绑定

 

对象类型参数绑定

 

   @JCRequestMapping("/getRequestBody")      public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {          return homeService.getRequestBody(id,no,userInfo);      }

 
doDispatcherServlet() 处理请求需要根据请求方式做不同处理,改造后如下
 1       Object[] args;   2         if ("GET".equalsIgnoreCase(req.getMethod())) {   3             args = doGetParam(req.getParameterMap(), method);   4         } else if ("POST".equalsIgnoreCase(req.getMethod()) && req.getContentType().contains("json")) {   5             String str = getJson(req);   6             args = doJsonParam(str, method);   7         } else {   8             args = doPostParam(req, method);   9         }  10         //调用目标方法  11         Object res = method.invoke(ioc.get(className), args);

传对象只能通过json方式传进来,所以我们postMan请求json格式数据

处理json请求

请求地址 请求方式 请求参数
http://localhost:8080/home/getRequestBody application/json {“id”:11,”no”:”SB00011″,”userInfo”:{“name”:”小提莫”,”age”:20}}

 

 

 

json请求核心代码就是使用fastjson根据字段名取值

 1     Object[] doJsonParam(String json, Method method) {   2         if (null == json || json.isEmpty()) {   3             return null;   4         }   5         Parameter[] parameters = method.getParameters();   6         Object[] requestParam = new Object[parameters.length];   7         JSONObject jsonObject = JSONObject.parseObject(json);   8         int i = 0;   9         for (Parameter p : parameters) {  10             Object val = jsonObject.getObject(p.getName(), p.getType());  11             requestParam[i] = val;  12             i++;  13         }  14         return requestParam;  15     }

 返回结果:

 结束

到这里,servlet处理请求,并响应已经得到验证,能够正常的对外提供服务。一个微型的springMvc框架已经完成了。

完整代码