自己实现spring核心功能 三
前言
前两篇已经基本实现了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 }
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 }
StudentService类
1 @JCService 2 public class StudentService implements IStudentService{ 3 @Override 4 public String sayHi(){ 5 return "Hello world!"; 6 } 7 }
无参请求过程
根据上面的图,我们在浏览器发起请求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 }
返回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框架已经完成了。