做下面的操作之前,先设置一下这个,可以让接下来的反射可以获取类中方法的参数。
如果说,当前的项目中不止有一个FruitServlet,而是还有UserServlet,ProductServlet等等不同的Servlet,且这些Servlet中的代码几乎都是相同的,我们就没必要写那么多个Servlet,我们可以只写一个Dispatcher Servlet,然后再根据请求判断该请求要发给原本的哪个Servlet(现在叫做Controller,哪个Controller)。
我们现在需要做的是什么呢?就是比如说//假设我们的url是: http://localhost:8080/pro15/Fruit.do,(或者说我们请求的Fruit.do)那么我们就能找到FruitController,如果我们请求(User.do),那么我们就能找到UserController。
而且我们可以用DispatcherServlet的统一处理,这样可以更好的配合反射(虽然我还没没学框架,但是已经能隐约感受出这大概是框架的灵魂)。
讲后面的案例就会大概有个思路。
首先是DispatcherServlet的service方法,也就是当DispatcherServlet中央过滤器收到请求时,它要做的第一件事是根据不同请求,让它对应到不同的Servlet。
思路是: // 第1步: /hello.do -> hello 或者 /fruit.do -> fruit 123
String servletPath = request.getServletPath(); servletPath = servletPath.substring(1); int lastDotIndex = servletPath.lastIndexOf(".do") ; servletPath = servletPath.substring(0,lastDotIndex); // 第一步:hello.do -> hello 或者 /fruit.do -> fruit 12345
这一步的实际作用就是将hello.do截取到只剩下hello
然后再下一步再根据hello去寻找与hello对应的Servlet,这些在那对应呢?
这就要讲到xml文件了:
xml文件首先,我们讲一下xml文件。
1.概念 HTML : 超文本标记语言 XML : 可扩展的标记语言 HTML是XML的一个子集 1234
2.XML包含三个部分: 1) XML声明 , 而且声明这一行代码必须在XML文件的第一行 2) DTD 文档类型定义 3) XML正文 1234
<beans> <!-- 这个bean标签的作用是 将来servletpath中涉及的名字对应的是fruit,那么就要FruitController这个类来处理 --> <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/> </beans> 1234
xml文件是配置文件,里面是标签语言。
你可以写不同的标签,然后我们就可以在代码中取到这些标签中的内容,就比如上面的bean,就是我们直接定义的标签,而里面的id,class这些也是我们可以定义的。我们要先取到bean,再去找到id和class的对应,都是可以的。
比如我们上面写了一个id=“fruit” class=“com.atguigu.fruit.controllers.FruitController”/意思就是将它们对应在一起。待会讲下面的例子就懂了。
所以我们的第二部就是得通过 hello -> HelloController 或者 fruit -> FruitController。
不过在这之前,我们得先加载一下xml文件:
我们写在DispatcherServlet的init函数中:
public void init() throws javax.servlet.ServletException { super.init(); /* 这个初始化就是解析xml文件,将其中对应的名称和类传进map中 */ try { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml"); //1.创建DocumentBuilderFactory DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); //2.创建DocumentBuilder对象 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ; //3.创建Document对象 Document document = documentBuilder.parse(inputStream); //4.获取所有的bean节点 //可以理解这个document可以解析xml文件内的所有标签,所以只需要给出标签名! //就可以获取以该标签名的所有的行,获取后返回一个NodeList NodeList beanNodeList = document.getElementsByTagName("bean"); for(int i = 0 ; i<beanNodeList.getLength() ; i++){ //将每一行取出来 Node beanNode = beanNodeList.item(i); if(beanNode.getNodeType() == Node.ELEMENT_NODE){ Element beanElement = (Element)beanNode ;// 取出每一个bean标签行后面的值,因为我们在里面写的是id=?class=? //所以下面就根据id,class来获取。 String beanId = beanElement.getAttribute("id"); String className = beanElement.getAttribute("class"); //获取到对应的类之后,就获取该类的实例,然后将名称和实例名传进map Class controllerBeanClass = Class.forName(className); //创建该类的实例 Object beanObj = controllerBeanClass.newInstance() ; //将名称和实例传进map beanMap.put(beanId , beanObj) ; //比如说xml文件中写的是: <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/> 那么取出来后就是map中创建一个映射 fruit 对应 FruitController的实例 } } } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657就是我们要取得hello到helloServlet的对应还是需要做一系列操作的,这些操作就可以写在初始化函数init中,这些操作简而言之,就是新建了一个map,然后存入了hello和helloServlet的映射。
所以在这之后,service方法中就可以通过刚刚截取到的hello然后在init初始化函数时创建的map中取到相应对应的实例
//创建具体对应的实例对象 Object controllerBeanObj = beanMap.get(servletPath); //进行不同请求时都会传一个方法名过来 123
那么获取到一个实例后,接下来要做什么呢?
接下来要做的事就重要了。
Dispatcher真正重要的事才刚刚开始,它真正的目的只有一个,就是反射!
其实我们是需要Dispatcher帮我们过滤吗?并不是,我们原本就可以在不同的Servlet中写注射,它们自身就可以被区分开来。我们真正要实现的事,任何的请求都到我DispatcherServlet中来,然后需要 FruitController干什么事,或者需要UserController干什么事,DispatcherServlet再调用它们的方法就行了。
那么如果有很多个类似FruitController的类,且其中的方法都不尽相同,难道我们要一个个实例化,然后一个个创建其中的函数吗?这样显然比原本还耦合复杂,如何将其变得更加简单,那么就用反射机制。
这就是反射的灵魂,而反射就是框架的灵魂。理解了这些,也就渐渐能懂得了框架。
我们要做的事,就是让Controller尽可能的简单,最好让其变为一个普通的类,有关于任何Http,Servlet的代码实现都交由DisPatcherServlet去完成,有没有可能?有,利用好反射机制,都有可能。
好,所以我们接下来的目标就是将将FruitController改为不含http的类。
首先,我们看原本的FruitController。
第一部分:
首先使原本的service方法
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(this,request,response); return; } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } throw new RuntimeException("operate值非法") }
12345678910111213141516171819202122232425这部分是可以很简单地可以搬到disPatcherServlet中去的,因为
String operate = request.getParameter(“operate”);这里的参数是从request中获取的,我们也可以在disPatcherServlet直接获取就好。Method[] methods=this.getClass().getDeclaredMethods();这个methods是通过该FruitServlet的反射机制找到该方法数组的,而我们刚刚在disPatcherServlet可以通过xml文件中获得FruitServlet的实例,所以也可以获得FruitServlet的方法数组,且有实例指针可以调用里面的方法。所以FruitServlet的service方法甚至可以不要了。
其二,看里面的函数,比如说edit,可否改成与http无关的。
private void edit(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException { String fidStr = request.getParameter("fid"); if(StringUtil.isNotEmpty(fidStr)){ int fid = Integer.parseInt(fidStr); Fruit fruit = fruitDAO.getFruitByFid(fid); request.setAttribute("fruit",fruit); super.processTemplate("edit",request,response); } } 123456789
仔细观察一下,与http有关的由两部分,一是通过request来获取各种参数,这里我们想,能否从disPatcherServlet获取好参数,然后再传进来,可以的。
第二是 super.processTemplate(“edit”,request,response);这里与request和resonpse都有关,但是disPatcherServlet都有request和resonpse,能否让其在disPatcherServlet中处理?也是可以的。只需要传个参数过去。
所以我们可以将其改为这样:
private String edit(Integer fid , HttpServletRequest request){ if(fid!=null){ Fruit fruit = fruitDAO.getFruitByFid(fid); request.setAttribute("fruit",fruit); //super.processTemplate("edit",request,response); return "edit"; } return "error" ; } 123456789
private void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.设置编码 request.setCharacterEncoding("utf-8"); //2.获取参数 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"); //3.执行更新 fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark )); response.sendRedirect("fruit.do"); }
12345678910111213141516171819private String update(Integer fid , String fname , Integer price , Integer fcount , String remark ){ //3.执行更新 fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark )); //4.资源跳转 return "redirect:fruit.do"; } 123456
而看原本的update,它原本最后一句是 response.sendRedirect(“fruit.do”);
我们要区分一下原本 super.processTemplate(“edit”,request,response)和response.sendRedirect(“fruit.do”),所以传不同的字符串过去让disPatcherServlet处理。
但是Index简化到最后还是得有一个request参数,因为其在代码逻辑中需要session域保存值,所以无法再删减
private String index(String oper , String keyword , Integer pageNo , HttpServletRequest request ) { HttpSession session = request.getSession() ; if(pageNo==null){ pageNo = 1; } if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){ pageNo = 1 ; if(StringUtil.isEmpty(keyword)){ keyword = "" ; } session.setAttribute("keyword",keyword); }else{ Object keywordObj = session.getAttribute("keyword"); if(keywordObj!=null){ keyword = (String)keywordObj ; }else{ keyword = "" ; } } // 重新更新当前页的值 session.setAttribute("pageNo",pageNo); FruitDAO fruitDAO = new FruitDAOImpl(); List<Fruit> fruitList = fruitDAO.getFruitList(keyword , pageNo); session.setAttribute("fruitList",fruitList); //总记录条数 int fruitCount = fruitDAO.getFruitCount(keyword); //总页数 int pageCount = (fruitCount+5-1)/5 ; session.setAttribute("pageCount",pageCount); return "index" ; }
123456789101112131415161718192021222324252627282930313233343536然后现在主要看disPatcherServlet那边如何实现了。
一点一点讲吧。
String operate = request.getParameter("operate"); if(StringUtil.isEmpty(operate)){ operate = "index" ; } 1234
首先,和之前一样,获取传过来的方法名,判断是要实行什么方法。
然后刚刚上面不是通过
Object controllerBeanObj = beanMap.get(servletPath); 1
映射获得了要请求的control的实例。
所以就获得该实例的类的所有方法
Method[] methods = controllerBeanObj.getClass().getDeclaredMethods(); 1
(慢慢感受到xml文件搭配反射的强大之处了吧。不要写具体实现,简化了很多代码)
for(Method method : methods){ if(operate.equals(method.getName())){ //1.统一获取请求参数 //1-1.获取当前方法的参数,返回参数数组 Parameter[] parameters = method.getParameters(); //1-2.parameterValues 用来承载参数的值 Object[] parameterValues = new Object[parameters.length]; 12345678
遍历这些方法,如果有一个方法和我们请求的方法名相同的话,
就获取该方法的参数名(反射有这个机制),然后返回一个数组。
private String add(String fname , Integer price , Integer fcount , String remark ) { Fruit fruit = new Fruit(0,fname , price , fcount , remark ) ; fruitDAO.addFruit(fruit); return "redirect:fruit.do"; } 12345
就比如刚刚的add函数,如上图,那么返回的数组的组成就是 fname,price,fcount,remark(参数名数组)
Object[] parameterValues = new Object[parameters.length];
然后创建一个参数值数组,来存放参数数组中各参数的值。
for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; String parameterName = parameter.getName() ; //如果参数名是request,response,session 那么就不是通过请求中获取参数的方式了 if("request".equals(parameterName)){ parameterValues[i] = request ; }else if("response".equals(parameterName)){ parameterValues[i] = response ; }else if("session".equals(parameterName)){ parameterValues[i] = request.getSession() ; 12345678910
从参数名数组中一个个获得参数的名字,比如刚刚的add函数例子,就依次获得 fname,price,fcount,remark
因为刚说了比如index方法,有些参数可能是request,response,session,所以如果是request,response,session,那么在存放参数值的数组内就直接写入它们。
而如果不是的话,比如刚刚的add例子,参数:fname,price,fcount,remark
}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 ; } 123456789101112131415
那么比如说取到fname,就从request请求中去获取fname的值,然后存到参数值数组当中去
同时还要注意,从request获取值,获取的值都是String类型,所以如果判断到当前的参数的类型是int类型或其它类型的话
比如是int类型,那么得将该String转为Integer,然后再传入参数值数组中。
那么我们要这个参数值数组有何用呢?
因为在反射机制中,我们要调用一个实例的方法,需要传实例和传参数进去。所以下面:
//2.controller组件中的方法调用 method.setAccessible(true); Object returnObj = method.invoke(controllerBeanObj,parameterValues); 123
让它执行方法,而且我们刚刚fruitController不是还有一些跳转页面的操作需要在外面处理吗?
下面就是了:
//3.视图处理 String methodReturnStr = (String)returnObj ; if(methodReturnStr.startsWith("redirect:")){ //比如: redirect:fruit.do String redirectStr = methodReturnStr.substring("redirect:".length()); response.sendRedirect(redirectStr); }else{ super.processTemplate(methodReturnStr,request,response); // 比如: "edit" } } } 12345678910
所以总的disPatcher的文件就是这样了:
@WebServlet("*.do") //只要是什么.do都找到这里来 public class DispatcherServlet extends ViewBaseServlet{ private Map<String,Object> beanMap = new HashMap<>(); public DispatcherServlet(){ } public void init() throws javax.servlet.ServletException { super.init(); /* 这个初始化就是解析xml文件,将其中对应的名称和类传进map中 */ try { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml"); //1.创建DocumentBuilderFactory DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); //2.创建DocumentBuilder对象 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ; //3.创建Document对象 Document document = documentBuilder.parse(inputStream); //4.获取所有的bean节点 //可以理解这个document可以解析xml文件内的所有标签,所以只需要给出标签名! //获取一系列标签名为bean的行 NodeList beanNodeList = document.getElementsByTagName("bean"); for(int i = 0 ; i<beanNodeList.getLength() ; i++){ //将每一行取出来 Node beanNode = beanNodeList.item(i); if(beanNode.getNodeType() == Node.ELEMENT_NODE){ Element beanElement = (Element)beanNode ; // 取出每一行id后面的值和class后面的值 String beanId = beanElement.getAttribute("id"); String className = beanElement.getAttribute("class"); //返回对应的类 Class controllerBeanClass = Class.forName(className); //创建该类的实例 Object beanObj = controllerBeanClass.newInstance() ; //将名称和实例传进map beanMap.put(beanId , beanObj) ; } } } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置编码 request.setCharacterEncoding("UTF-8"); //假设url是: http://localhost:8080/pro15/hello.do //那么servletPath是: /hello.do // 我的思路是: // 第1步: /hello.do -> hello 或者 /fruit.do -> fruit // 第2步: hello -> HelloController 或者 fruit -> FruitController 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())){ //1.统一获取请求参数 //1-1.获取当前方法的参数,返回参数数组 Parameter[] parameters = method.getParameters(); //1-2.parameterValues 用来承载参数的值 Object[] parameterValues = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; String parameterName = parameter.getName() ; //如果参数名是request,response,session 那么就不是通过请求中获取参数的方式了 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 ; } } //2.controller组件中的方法调用 method.setAccessible(true); Object returnObj = method.invoke(controllerBeanObj,parameterValues); //3.视图处理 String methodReturnStr = (String)returnObj ; if(methodReturnStr.startsWith("redirect:")){ //比如: redirect:fruit.do String redirectStr = methodReturnStr.substring("redirect:".length()); response.sendRedirect(redirectStr); }else{ super.processTemplate(methodReturnStr,request,response); // 比如: "edit" } } } /* }else{ throw new RuntimeException("operate值非法!"); } */ } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } // 常见错误: IllegalArgumentException: argument type mismatch
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145@WebServlet(“*.do”) //注意最上面要给其一个注解,只要是什么.do都找到这里来。
然后FruitController的代码:
package com.atguigu.fruit.controllers; import com.atguigu.fruit.dao.FruitDAO; import com.atguigu.fruit.dao.impl.FruitDAOImpl; import com.atguigu.fruit.pojo.Fruit; import com.atguigu.myssm.util.StringUtil; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.List; public class FruitController { private FruitDAO fruitDAO = new FruitDAOImpl(); private String update(Integer fid , String fname , Integer price , Integer fcount , String remark ){ //3.执行更新 fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark )); //4.资源跳转 return "redirect:fruit.do"; } private String edit(Integer fid , HttpServletRequest request){ if(fid!=null){ Fruit fruit = fruitDAO.getFruitByFid(fid); request.setAttribute("fruit",fruit); //super.processTemplate("edit",request,response); return "edit"; } return "error" ; } private String del(Integer fid ){ if(fid!=null){ fruitDAO.delFruit(fid); return "redirect:fruit.do"; } return "error"; } private String add(String fname , Integer price , Integer fcount , String remark ) { Fruit fruit = new Fruit(0,fname , price , fcount , remark ) ; fruitDAO.addFruit(fruit); return "redirect:fruit.do"; } private String index(String oper , String keyword , Integer pageNo , HttpServletRequest request ) { HttpSession session = request.getSession() ; if(pageNo==null){ pageNo = 1; } if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){ pageNo = 1 ; if(StringUtil.isEmpty(keyword)){ keyword = "" ; } session.setAttribute("keyword",keyword); }else{ Object keywordObj = session.getAttribute("keyword"); if(keywordObj!=null){ keyword = (String)keywordObj ; }else{ keyword = "" ; } } // 重新更新当前页的值 session.setAttribute("pageNo",pageNo); FruitDAO fruitDAO = new FruitDAOImpl(); List<Fruit> fruitList = fruitDAO.getFruitList(keyword , pageNo); session.setAttribute("fruitList",fruitList); //总记录条数 int fruitCount = fruitDAO.getFruitCount(keyword); //总页数 int pageCount = (fruitCount+5-1)/5 ; session.setAttribute("pageCount",pageCount); return "index" ; } }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384总结一下:
首先,在xml文件中写下不同的名称和controller的对应
无论什么请求,都找到disPatcherServlet(disPatcher的初始化函数中将xml文件中的名称和对应的实例写在map中)
当请求到来时,dispatcher先是截取其来源,找到其想要请求的Controller。然后找到该controller的实例。
通过该实例获得该类的方法数组,然后从请求中获取请求的方法,将方法与方法数组一一对应,知道找到要执行的方法。
通过反射查询该方法的参数名,然后通过这些参数名到请求中获取参数,保存下来,然后再通过反射,将实例和这些参数的值一起传进去执行。
所以就是实现了,先找到对应的controller,再找到对应的方法然后执行的过程。
相关知识
水果仓库管理主要是做什么
erp系统在哪里查看库存
花店库存管理有哪些
盆景花卉库存管理方案
童装店铺库存管理方案1篇
园林花卉库存管理方案怎么写4篇
{“title”:“低温乳品库存管理技巧 最新保鲜技术提升管理效率”}
超市水果如何盘点 仓储管理技巧 最新盘点方法全解
花都仓库管理系统
鲜花进货库存管理方案范本2篇
网址: 水果库存管理系统 https://m.huajiangbk.com/newsview546624.html
上一篇: 物流链微商订单管理系统开发 |
下一篇: 商品发布数量=库存数量+预售数量 |