问题分析
servlet规范与我们的业务无关,但现在Request、Response、视图渲染工具类、Redirect等等这些api“打扰”了程序员。
程序员的精力是很宝贵的,我们希望在开发项目时,程序员更专注于业务,而不要看到这些与业务无关的servlet规范api。
我们的目标就是,我们写的代码类就是普通的pojo类。这样的pojo类就是常用Web框架SpringMVC中创建的Controller类。
在使用SpringMVC前,我们在之前的代码基础上,实现一个简易版的SpringMVC框架,加深对框架底层原理和功能的理解。
1 利用switch语句-解决servlet类爆炸问题
现在一个方法就定义一个servlet类,类数量过多,不好维护。

我们把Fruit相关的方法都放到一个servlet类中,该类中根据前端传的operate参数,调用对应的方法。

2 引入反射-解决switch判断语句过长问题
通过反射来匹配前端operate参数和对应的处理方法
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
| @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8");
String operate = request.getParameter("operate"); if(StringUtil.isEmpty(operate)){ operate = "index" ; } Method[] methods =this.getClass().getDeclaredMethods(); for(Method m:methods){ String methodName =m.getName(); if(operate.equals(methodName)){ try { m.invoke( obj:this,request,response); return; } catch(IllegalAccessExceptione){ e.printstackTrace(); } catch(InvocationTargetExceptione){ e.printstackTrace(); } } } throw new RuntimeException("operate值非法!");
}
|
3 引入中央处理器-解决多个业务的servlet需要重复编写反射相关代码的问题

4 消除控制器类与视图渲染、重定向、servlet规范的耦合
之前的控制器方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| private void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8");
String fidStr = request.getParameter("fid"); Integer fid = Integer.parseInt(fidStr); String fname = request.getParameter("fname"); String priceStr = request.getParameter("price"); int price = Integer.parseInt(priceStr); String fcountStr = request.getParameter("fcount"); Integer fcount = Integer.parseInt(fcountStr); String remark = request.getParameter("remark");
fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark ));
response.sendRedirect("fruit.do"); }
|
新的控制器方法【消除servlet规范api相关代码后】,一下子清爽了很多。
1 2 3 4 5 6
| private String update(Integer fid , String fname , Integer price , Integer fcount , String remark ){ fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark )); return "redirect:fruit.do"; }
|
servlet规范api相关代码都放在中央控制器处理
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
| @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String servletPath = request.getServletPath(); servletPath = servletPath.substring(1); int lastDotIndex = servletPath.lastIndexOf(".do") ; servletPath = servletPath.substring(0,lastDotIndex);
Object controllerBeanObj = beanMap.get(servletPath);
String operate = request.getParameter("operate"); if(StringUtil.isEmpty(operate)){ operate = "index" ; }
try { Method[] methods = controllerBeanObj.getClass().getDeclaredMethods(); for(Method method : methods){ if(operate.equals(method.getName())){
Parameter[] parameters = method.getParameters(); Object[] parameterValues = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; String parameterName = parameter.getName() ; if("request".equals(parameterName)){ parameterValues[i] = request ; }else if("response".equals(parameterName)){ parameterValues[i] = response ; }else if("session".equals(parameterName)){ parameterValues[i] = request.getSession() ; }else{ String parameterValue = request.getParameter(parameterName); String typeName = parameter.getType().getName();
Object parameterObj = parameterValue ;
if(parameterObj!=null) { if ("java.lang.Integer".equals(typeName)) { parameterObj = Integer.parseInt(parameterValue); } }
parameterValues[i] = parameterObj ; } } method.setAccessible(true); Object returnObj = method.invoke(controllerBeanObj,parameterValues);
String methodReturnStr = (String)returnObj ; if(methodReturnStr.startsWith("redirect:")){ String redirectStr = methodReturnStr.substring("redirect:".length()); response.sendRedirect(redirectStr); }else{ super.processTemplate(methodReturnStr,request,response); } } }
} catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
|
5 review
- 最初的做法是: 一个请求对应一个Servlet,这样存在的问题是servlet太多了
- 把一些列的请求都对应一个Servlet, IndexServlet/AddServlet/EditServlet/DelServlet/UpdateServlet -> 合并成FruitServlet
通过一个operate的值来决定调用FruitServlet中的哪一个方法
使用的是switch-case
- 在上一个版本中,Servlet中充斥着大量的switch-case,试想一下,随着我们的项目的业务规模扩大,那么会有很多的Servlet,也就意味着会有很多的switch-case,这是一种代码冗余
因此,我们在servlet中使用了反射技术,我们规定operate的值和方法名一致,那么接收到operate的值是什么就表明我们需要调用对应的方法进行响应,如果找不到对应的方法,则抛异常
- 在上一个版本中我们使用了反射技术,但是其实还是存在一定的问题:每一个servlet中都有类似的反射技术的代码。因此继续抽取,设计了中央控制器类:DispatcherServlet
DispatcherServlet这个类的工作分为两大部分:
1.根据url定位到能够处理这个请求的controller组件:
1)从url中提取servletPath : /fruit.do -> fruit
2)根据fruit找到对应的组件:FruitController , 这个对应的依据我们存储在applicationContext.xml中
<bean id=”fruit” class=”com.atguigu.fruit.controllers.FruitController/>
通过DOM技术我们去解析XML文件,在中央控制器中形成一个beanMap容器,用来存放所有的Controller组件
3)根据获取到的operate的值定位到我们FruitController中需要调用的方法
2.调用Controller组件中的方法:
- 获取参数
获取即将要调用的方法的参数签名信息: Parameter[] parameters = method.getParameters();
通过parameter.getName()获取参数的名称;
准备了Object[] parameterValues 这个数组用来存放对应参数的参数值
另外,我们需要考虑参数的类型问题,需要做类型转化的工作。通过parameter.getType()获取参数的类型
- 执行方法
Object returnObj = method.invoke(controllerBean , parameterValues);
- 视图处理
String returnStr = (String)returnObj;
if(returnStr.startWith(“redirect:”)){
….
}else if…..