Servlet优化2:实现一个Servlet组件调度所有的controller
一、优化
-
优化逻辑图
-
将原先的Servlet组件简化为controller
/* 1.去除Controller的继承 2.将各方法内,都有通过req.getParameter获取的参数的相同功能的代码块,将这些参数变量全部放在方法 头的参数列表上,在dispatcher内通过反射统一获取,使controller类尽量只实现业务逻辑功能 */ public class CustomersController { private ListIpm listIpm = new ListIpm(); protected String index(String oper,String keyword,String page,HttpServletRequest req) { Connection conn = null; try { conn = JdbcUtils.getConnection(); HttpSession session = req.getSession(); int pageNo = 1; // 1.此处的oper来自参数列表 if (StringUtil.isNotEmpty(oper) && "search".equals(oper)) { pageNo = 1; // 2.此处的keyword来自参数列表 if (StringUtil.isEmpty(keyword)) { keyword = ""; } session.setAttribute("k",keyword); }else { Object keyObj = session.getAttribute("k"); if (keyObj == null) { keyword = ""; }else { keyword = (String) keyObj; } } if (page != null) { // 3.此处的page来自参数列表 pageNo = Integer.parseInt(page); } session.setAttribute("pageOn",pageNo); List<Customers> custList = listIpm.getList(conn,"%"+ keyword +"%",pageNo); session.setAttribute("cl",custList); long count = 0L; count = listIpm.getCount(conn,"%"+keyword+"%"); int max = (int) (count+2-1)/2; session.setAttribute("max",max); // 4.将父类的模板处理方法返回,在dispatcher上进行处理 return "index"; } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.closeResource(conn,null); } return null; } private String add(String name,String email,String birth) { Connection conn = null; try { // 此处的birth、name、email来自参数列表 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date = format.parse(birth); conn = JdbcUtils.getConnection(); listIpm.addCustomer(conn,name,email,date); // 将请求路径返回,在dispatcher上进行处理 return "redirect:customers.do"; } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.closeResource(conn,null); } return null; } private String delete(String sid) { // 此处的sid来自参数列表 if (StringUtil.isNotEmpty(sid)) { Connection conn = null; try { int id = Integer.parseInt(sid); conn = JdbcUtils.getConnection(); listIpm.delById(conn,id); // 将请求路径返回,在dispatcher上进行处理 return "redirect:customers.do"; } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.closeResource(conn,null); } } return null; } private String edit(String cid,HttpServletRequest req) { Connection conn = null; try { conn = JdbcUtils.getConnection(); // 此处的cid来自参数列表 if (StringUtil.isNotEmpty(cid)) { int id = Integer.parseInt(cid); Customers customer = listIpm.getCustomerById(conn, id); req.setAttribute("cu" ,customer); // 将模板返回,在dispatcher上进行处理 return "edit"; } } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.closeResource(conn,null); } return null; } private String update(String id,String name,String email,String birth) { Connection conn = null; try { // 此处的id、birth、name、email来自参数列表 int idInt = Integer.parseInt(id); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date = format.parse(birth); conn = JdbcUtils.getConnection(); listIpm.updateById(conn,name,email,date,idInt); // 将请求路径返回,在dispatcher上进行处理 return "redirect:customers.do"; } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.closeResource(conn,null); } return null; } }
-
新建一个Servlet组件,实现dispatcher调度功能
// 注解处的*代表任意值 @WebServlet("*.do") public class DispatcherServlet extends ViewBaseServlet{ // 创建HashMap集合,将bean内的元素以键值对的形式存储在该集合内 private Map<String,Object> beanMap = new HashMap<>(); // 一、在init方法中,解析相关联的xml配置文件 @Override public void init() throws ServletException { super.init(); try { // 1.getResourceAsStream():以流的形式获取一个资源,即将cml文件转换为输入流 InputStream is = getClass().getClassLoader().getResourceAsStream("applicationContext.xml"); // 2.DocumentBuilderFactory类:使应用程序能够从XML文档中获取生成DOM对象树的解析器 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); /* 3.可以从DocumentBuilderFactory.newDocumentBuilder()方法获得该类的实例。 获得此类的实例后,可以从各种输入源解析XML。 这些输入源是InputStreams, Files,URL和SAX InputSources */ DocumentBuilder builder = factory.newDocumentBuilder(); /* 4.从资源转化为的输入流中提取Document对象,Document接口表示整个HTML或XML文档。 从概念上讲,它是文档树的根,并提供对文档数据的主要访问。 */ Document document = builder.parse(is); // 5.通过Document对象,提取xml内指定标签的元素 NodeList beanNodeList = document.getElementsByTagName("bean"); for (int i = 0; i < beanNodeList.getLength() ; i++) { Node beanNode = beanNodeList.item(i); if (beanNode.getNodeType() == Node.ELEMENT_NODE) { /* 6.将Node类型的数据转换为Element类型 Element接口表示HTML或XML文档中的元素。元素可能具有与之关联的属性; 由于Element接口继承自Node,因此通用Node接口属性attributes可用于检索元 素的所有属性的集合。 Element接口上有方法可以按名称检索Attr对象,或按名称检索属性值。 在XML中,属性值可能包含实体引用,应检索Attr对象以检查表示属性值的可能相当 复杂的子树。 另一方面,在HTML中,所有属性都具有简单的字符串值,可以安全地使用直接访问 属性值的方法作为方便。 */ Element beanElement = (Element)beanNode; String beanId = beanElement.getAttribute("id"); String className = beanElement.getAttribute("class"); Object beanObj = Class.forName(className).newInstance(); // 存入集合中 beanMap.put(beanId,beanObj); } } } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { req.setCharacterEncoding("UTF-8"); // 二、解析出请求路径中的请求标识 /* 1.获取servlet请求路径,来自tomcat的url配置或html/js文件内的url配置 将设:url = "http://localhost.8080/optimization_2/customers.do" 那么:ServletPath = "/customers.do" */ String servletPath = req.getServletPath(); // 2.获取最后出现该后缀名字符串的索引值 int lastDotIndex = servletPath.lastIndexOf(".do"); /* 3.使用String substring(int beginIndex, int endIndex)方法,将servletPath头部 的斜杠和后缀的.do去除 servletPath.substring(1):表示从索引1处开始,故索引0处的值就被截取掉了 */ servletPath = servletPath.substring(1,lastDotIndex); // 三、指定具体的controller // 通过从在请求路径中解析出的请求标识,传入beanMap中,即可指定具体的controller Object controllersObj = beanMap.get(servletPath); // 四、匹配controller内方法 // 1.通过req获取html/js文件内的operate的值 String operate = req.getParameter("operate"); // 2.如果operate的值为空,就赋初始值index if (StringUtil.isEmpty(operate)) { operate = "index"; } // 3.使用反射,解析出指定controller内的所有方法 try { Method[] methods = controllersObj.getClass().getDeclaredMethods(); // 4.使用增强for循环,解析出每个方法的参数 for (Method method : methods) { // 4.1 使用operate值,指定具体的方法 if (operate.equals(method.getName())) { // 4.2 利用反射,获取指定方法内的所有参数 Parameter[] parameters = method.getParameters(); // 4.3 创建一个Object数组,用于存放通过反射得到的各参数的对应值 Object[] parameterValues = new Object[parameters.length]; // 4.4 将所有参数存入Object数组 for (int i = 0; i < parameters.length; i++) { Parameter p = parameters[i]; // 4.5 获取每个参数的名称 String parameterName = p.getName(); // 4.6 将这些参数名分情况赋值给parameterValues数组 if ("req".equals(parameterName)) { parameterValues[i] = req; } else if ("resp".equals(parameterName)) { parameterValues[i] = resp; } else if ("session".equals(parameterName)) { parameterValues[i] = req.getSession(); } else { String parameterValue = req.getParameter(parameterName); parameterValues[i] = parameterValue; } } method.setAccessible(true); Object returnObj = method.invoke(controllersObj, parameterValues); // 5.视图处理(重定向及模板处理) // 5.1 获取该方法返回的重定向字符串 String retrunStr = (String) returnObj; // 5.2 截取重定向内字符串内包含的请求路径(例如:index.do) if (retrunStr.startsWith("redirect:")) { String redirectStr = retrunStr.substring("redirect:".length()); resp.sendRedirect(redirectStr); } else { // 5.3 将controller内返回的模板传入父类的模板处理方法 super.processTemplate(retrunStr,req,resp); } } } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
-
html/js/css文件不变
二、错误提示
-
参数类型不匹配
// 控制台错误提示 java.lang.IllegalArgumentException: argument type mismatch // 错误代码,将字符串req赋给了参数数组,导致参数错误 for (int i = 0; i < parameters.length; i++) { Parameter p = parameters[i]; String parameterName = p.getName(); if ("req".equals(parameterName)) { parameterValues[i] = “req”; /* 正确写法: 应该将service方法头部的形参HttpServlet req 赋值给参数数组,才能通过req.getParameter()方法 实现对index方法头其它参数的正确调用 */ if ("req".equals(parameterName)) { parameterValues[i] = req;
-
在Controller类上加了@WebServlet注解,导致404错误