1.前期准备
在上篇博客BookAction的基础上,这一期我们新加一个订单(Order)类。
先在web包下创建一个OrderAction类,跟BookAction一样是Action的子类,继承Action类。然后在其里面写增删改查方法。增删改查方法写完之后,把OrderAction通过Map放到中央控制器DispatherServlet中去。
具体详情请见:
2.中央控制器动态加载存储子控制器
上篇分享到,我们如何优化不同的对象在想要实现不同功能时为了方便编码进行的创建了MVC框架进行优化,但是还是不够方便,所以我们接着上篇的用XML建模反射优化一下代码:
代码出现的问题:中央控制器每次初始化都需要手动添加。如下:
@WebServlet("*.action") public class DispatherServlet extends HttpServlet { public Map<String, Action> actionmap = new HashMap<String,Action>();//通过Map集合去拿到Action类 @Override public void init() throws ServletException {// 通过Map集合去拿到Action类之后,写一个初始化的方法init(),把所有的action放进去。(案例只有一个BookAction) actionmap.put("/book",new BookAction()); actionmap.put("/order", new OrderAction()); }
分析问题并解决:
- 因为最后的framework包需要打成jar包,原本的.java文件会变成.class文件,而.class文件不可修改,这也导致了我们不能添加子控制器,也就等同于子控制器是定死了的。那么怎么解决这个问题呢?——XML建模跟反射。★★★
- 通过XML建模我们可以知道,如果我们把放在Map集合中的子控制器转移到mvc.xml中呢?最终的configModel对象会包含.xml文件中的所有子控制器信息。后续的操作跟原本Map集合类似,通过uri=/book 在configModel对象中找值,而不是在Map里找,这不就解决了吗?★★
所以我们需要用到XML建模的知识对原本代码进行改造:
mvc.xml:
<?xml version="1.0" encoding="UTF-8"?> <config> <action path="/Kissship1" type="com.Kissship.web.OrderAction"> </action> <action path="/Kissship2" type="com.Kissship.web.BookAction"> <forward name="list" path="/res.jsp" redirect="false" /> <forward name="toList" path="/res.jsp" redirect="true" /> </action> </config>
DispatherServlet:
package com.Kissship.framework; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.management.RuntimeErrorException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.Kissship.web.BookAction; import com.Kissship.web.OrderAction; /** * 对应图中的ActionServlet:中央控制器 * * @author jj * */ @WebServlet("*.action") public class DispatherServlet extends HttpServlet { // public Map<String, Action> actionmap = new HashMap<String,Action>();//通过Map集合去拿到Action类 // 以前所有的子控制器都是放到Map集合中,现在所有的子控制器在mvc.xml中,其实就是放到configModel private ConfigModel configModel; @Override public void init() throws ServletException {// 通过Map集合去拿到Action类之后,写一个初始化的方法init(),把所有的action放进去。(案例只有一个BookAction) // actionmap.put("/book",new BookAction()); // actionmap.put("/order", new OrderAction()); try { // configModel包含了所有的子控制器 configModel = ConfigModelFactory.build();// 初始化 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // book -> BookAction -> add String uri = request.getRequestURI();// 获取URL地址 uri = uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf("."));// 截取 // Action action = // actionmap.get(uri);//把所有的action放进init()方法之后,就可以通过map拿到uri,在这里返回一个action对象后续要通过它去抓取add等方法去提供服务 ActionModel actionModel = configModel.pop(uri);// 要通过uri=/book 在configModel对象中找值,而不是在Map里找 if (actionModel == null) throw new RuntimeException("action not config");// 抛异常 // 返回的type值就是拿到com.Kissship.web.OrderAction路径 String type = actionModel.getType(); // BookAction bookaction = new BookAction();对象实例化 Action action; try { action = (Action) Class.forName(type).newInstance();// 反射实例化 action.execute(request, response);// 通过实例化的返回值action拿到execute反射代码块 } catch (Exception e) { e.printStackTrace(); } } }
JSP页面代码:
<p>优化1</p> <a href="order.action?methodName=add">增加</a> <a href="order.action?methodName=del">删除</a> <a href="order.action?methodName=upd">修改</a> <a href="order.action?methodName=list">查询</a>
优化后的页面效果:
控制台结果:
做到这里我们就可以实现不需要改动任何代码只用修改XML配置文件就可以添加子控制器了。
注意★:
if (actionModel == null)
throw new RuntimeException("action not config");//抛异常
以上代码的展示如下:
但是我们依然执行,结果如下:
3.优化方法调用结果集跳转问题
3.1优化前
按照我们之前的代码写法要想实现页面跳转一般会是这样,代码如下:
DispatherServlet:
package com.Kissship.framework; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.management.RuntimeErrorException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.Kissship.web.BookAction; import com.Kissship.web.OrderAction; /** * 对应图中的ActionServlet:中央控制器 * * @author jj * */ @WebServlet("*.action") public class DispatherServlet extends HttpServlet { // public Map<String, Action> actionmap = new HashMap<String,Action>();//通过Map集合去拿到Action类 // 以前所有的子控制器都是放到Map集合中,现在所有的子控制器在mvc.xml中,其实就是放到configModel private ConfigModel configModel; @Override public void init() throws ServletException {// 通过Map集合去拿到Action类之后,写一个初始化的方法init(),把所有的action放进去。(案例只有一个BookAction) // actionmap.put("/book",new BookAction()); // actionmap.put("/order", new OrderAction()); try { // configModel包含了所有的子控制器 configModel = ConfigModelFactory.build();// 初始化 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // book -> BookAction -> add String uri = request.getRequestURI();// 获取URL地址 uri = uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf("."));// 截取 // Action action = // actionmap.get(uri);//把所有的action放进init()方法之后,就可以通过map拿到uri,在这里返回一个action对象后续要通过它去抓取add等方法去提供服务 ActionModel actionModel = configModel.pop(uri);// 要通过uri=/book 在configModel对象中找值,而不是在Map里找 if (actionModel == null) throw new RuntimeException("action not config");//抛异常 // 返回的type值就是拿到com.Kissship.web.OrderAction路径 String type = actionModel.getType(); // BookAction bookaction = new BookAction();对象实例化 Action action; try { action = (Action) Class.forName(type).newInstance();// 反射实例化 //具体业务代码执行后的返回值————add/upd/del/list的返回值-》list/toList String res = action.execute(request, response);// 通过实例化的返回值action拿到execute反射代码块 // //要通过返回值拿到,该方法结果是重定向还是转发,还是跳转哪个页面 // ForwardModel forwardModel = actionModel.pop(res); // if(forwardModel != null) { // boolean redirect = forwardModel.isRedirect(); // String path = forwardModel.getPath(); // if(redirect) { // response.sendRedirect(request.getContextPath() + "/" + path); // }else { // request.getRequestDispatcher(path).forward(request, response); // } // } } catch (Exception e) { e.printStackTrace(); } } }
BookAction:
package com.Kissship.web; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.Kissship.framework.Action; public class BookAction extends Action{ public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("BookAction.add..."); request.setAttribute("content", "Kissship"); response.sendRedirect("res.jsp"); // return "toList"; } public void del(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("BookAction.del..."); request.setAttribute("content", "Kissship"); response.sendRedirect("res.jsp"); // return "toList"; } public void upd(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("BookAction.upd..."); request.setAttribute("content", "Kissship"); response.sendRedirect("res.jsp"); // return "toList"; } public void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("BookAction.list..."); request.setAttribute("content", "Kissship"); request.getRequestDispatcher("res.jsp").forward(request, response); // return "list"; } }
index.jsp:
<p>版本5</p> 常识:查询必然转发,增删改使用重定向 弊端:中央控制器的action容器加载不可以灵活配置 <hr> <a href="book.action?methodName=add">增加</a> <a href="book.action?methodName=del">删除</a> <a href="book.action?methodName=upd">修改</a> <a href="book.action?methodName=list">查询</a>
效果图:
控制台输出结果:
3.2优化后
★★★解决方法是将我们的跳转路径也一样的放置在XML配置文件中对子控制器进行改造,将我们的跳转路径也一样放在xml配置文件中。
将子控制器的操作方法返回类型变成"String"返回相应的字符串forward转发或redirect重定向,代码如下:
Action:
package com.Kissship.framework; import java.io.IOException; import java.lang.reflect.Method; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 子控制器 * 真正做事,处理浏览器发送的请求的类 * @author jj * */ public class Action { protected String execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String methodName = request.getParameter("methodName"); String res = ""; try { Method m = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); m.setAccessible(true); res = (String) m.invoke(this, request, response); } catch (Exception e) { e.printStackTrace(); } return res; } }
ForwardModel:
package com.Kissship.framework; /** * 对应forward标签 * @author jj * */ public class ForwardModel { // name、path、redirect private String name; private String path; private boolean redirect; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public boolean isRedirect() { return redirect; } public void setRedirect(boolean redirect) { this.redirect = redirect; } }
DispatherSerlet:
package com.Kissship.framework; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.management.RuntimeErrorException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.Kissship.web.BookAction; import com.Kissship.web.OrderAction; /** * 对应图中的ActionServlet:中央控制器 * * @author jj * */ @WebServlet("*.action") public class DispatherServlet extends HttpServlet { // public Map<String, Action> actionmap = new HashMap<String,Action>();//通过Map集合去拿到Action类 // 以前所有的子控制器都是放到Map集合中,现在所有的子控制器在mvc.xml中,其实就是放到configModel private ConfigModel configModel; @Override public void init() throws ServletException {// 通过Map集合去拿到Action类之后,写一个初始化的方法init(),把所有的action放进去。(案例只有一个BookAction) // actionmap.put("/book",new BookAction()); // actionmap.put("/order", new OrderAction()); try { // configModel包含了所有的子控制器 configModel = ConfigModelFactory.build();// 初始化 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // book -> BookAction -> add String uri = request.getRequestURI();// 获取URL地址 uri = uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf("."));// 截取 // Action action = // actionmap.get(uri);//把所有的action放进init()方法之后,就可以通过map拿到uri,在这里返回一个action对象后续要通过它去抓取add等方法去提供服务 ActionModel actionModel = configModel.pop(uri);// 要通过uri=/book 在configModel对象中找值,而不是在Map里找 if (actionModel == null) throw new RuntimeException("action not config");//抛异常 // 返回的type值就是拿到com.Kissship.web.OrderAction路径 String type = actionModel.getType(); // BookAction bookaction = new BookAction();对象实例化 Action action; try { action = (Action) Class.forName(type).newInstance();// 反射实例化 //具体业务代码执行后的返回值————add/upd/del/list的返回值-》list/toList String res = action.execute(request, response);// 通过实例化的返回值action拿到execute反射代码块 //要通过返回值拿到,该方法结果是重定向还是转发,还是跳转哪个页面 ForwardModel forwardModel = actionModel.pop(res); if(forwardModel != null) { boolean redirect = forwardModel.isRedirect(); String path = forwardModel.getPath(); if(redirect) { response.sendRedirect(request.getContextPath() + "/" + path); }else { request.getRequestDispatcher(path).forward(request, response); } } } catch (Exception e) { e.printStackTrace(); } } }
index.jsp:
<p>版本5</p> 常识:查询必然转发,增删改使用重定向 弊端:中央控制器的action容器加载不可以灵活配置 <hr> <a href="book.action?methodName=add">增加</a> <a href="book.action?methodName=del">删除</a> <a href="book.action?methodName=upd">修改</a> <a href="book.action?methodName=list">查询</a>
mvc.xml:
<?xml version="1.0" encoding="UTF-8"?> <config> <action path="/order" type="com.Kissship.web.OrderAction"> </action> <action path="/book" type="com.Kissship.web.BookAction"> <forward name="list" path="/res.jsp" redirect="false" /> <forward name="toList" path="/res.jsp" redirect="true" /> </action> </config>
效果图:
控制台输出结果:
注意★:
在DispathServlet中的if判断时,我因为没有添加getContextPath方法,导致丢失了项目名。
如下:
效果如下:
解决问题代码:
加上之后我们丢失项目名的问题就解决啦。
4.优化参数封装
4.1优化前
优化之前我们的后台代码会有很多重复且繁琐的冗余代码:
book:bid,bname,price
req.getparameter("bid");
req.getparameter("bname");
req.getparameter("price");
book book = new book(bid,bname,price);
如下:
String bid = request.getParameter("bid"); String bname = request.getParameter("bname"); String price = request.getParameter("price"); Book book = new Book(); book.setBid(Integer.valueOf(bid)); book.setBname(bname); book.setPrice(Float.valueOf(price));
index.jsp:
<p>版本6</p> 弊端:jsp传递到后台,封装到实体类的代码过多<hr> <a href="book.action?methodName=add&bid=1&bname=Kissship&price=9.9">增加</a> <a href="book.action?methodName=del">删除</a> <a href="book.action?methodName=upd">修改</a> <a href="book.action?methodName=list">查询</a>
控制台输出如下:
那么我们该如何解决这个问题呢?
首先我们需要让优化后的代码满足以下需求:
1.要有表对应的类属性对象Book book
2.获取到所有的参数 及参数req.getParamertMap();
3.将参数值封装到表对应的对象中
4.要做到所有的子控制器都通用
4.2优化后
4.2.1模型驱动类
首先我们需要创建一个模型驱动类ModelDriver来让所有的action类都有指定对象,并且都能够通用(<泛型>)。所以在创建ModelDriver的同时我们需要用到泛型。如下:
ModerDriver:
package com.Kissship.framework; /** * 模型驱动接口 * Book book = new Book(); * @author jj * * @param <T> */ public interface ModelDriver<T> { T getModel(); }
写完模型驱动类之后去BookAction(子控制器)中实现ModerDriver类即可。
4.2.2中央控制器
通过代码完成4.1中未完成的需求。如下:
代码如下:
package com.Kissship.framework; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.management.RuntimeErrorException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.beanutils.BeanUtils; import com.Kissship.web.BookAction; import com.Kissship.web.OrderAction; /** * 对应图中的ActionServlet:中央控制器 * * @author jj * */ @WebServlet("*.action") public class DispatherServlet extends HttpServlet { // public Map<String, Action> actionmap = new HashMap<String,Action>();//通过Map集合去拿到Action类 // 以前所有的子控制器都是放到Map集合中,现在所有的子控制器在mvc.xml中,其实就是放到configModel private ConfigModel configModel; @Override public void init() throws ServletException {// 通过Map集合去拿到Action类之后,写一个初始化的方法init(),把所有的action放进去。(案例只有一个BookAction) // actionmap.put("/book",new BookAction()); // actionmap.put("/order", new OrderAction()); try { // configModel包含了所有的子控制器 configModel = ConfigModelFactory.build();// 初始化 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // book -> BookAction -> add String uri = request.getRequestURI();// 获取URL地址 uri = uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf("."));// 截取 // Action action = // actionmap.get(uri);//把所有的action放进init()方法之后,就可以通过map拿到uri,在这里返回一个action对象后续要通过它去抓取add等方法去提供服务 ActionModel actionModel = configModel.pop(uri);// 要通过uri=/book 在configModel对象中找值,而不是在Map里找 if (actionModel == null) throw new RuntimeException("action not config");//抛异常 // 返回的type值就是拿到com.Kissship.web.OrderAction路径 String type = actionModel.getType(); // BookAction bookaction = new BookAction();对象实例化 Action action; try { action = (Action) Class.forName(type).newInstance();// 反射实例化 //BookAction有没有实现ModelDriver接口 如果实现了才可向下转型 if(action instanceof ModelDriver) { ModelDriver md = (ModelDriver) action; Object bean = md.getModel();//这时候这model对象里还是没有值的 Map<String, String[]> map = request.getParameterMap();//所有参数值都在map里 BeanUtils.populate(bean, map);//将参数值封装到表对应的对象中 } //具体业务代码执行后的返回值————add/upd/del/list的返回值-》list/toList String res = action.execute(request, response);// 通过实例化的返回值action拿到execute反射代码块 //要通过返回值拿到,该方法结果是重定向还是转发,还是跳转哪个页面 ForwardModel forwardModel = actionModel.pop(res); if(forwardModel != null) { boolean redirect = forwardModel.isRedirect(); String path = forwardModel.getPath(); if(redirect) { response.sendRedirect(request.getContextPath() + "/" + path); }else { request.getRequestDispatcher(path).forward(request, response); } } } catch (Exception e) { e.printStackTrace(); } } }
代码解释:
if(action instanceof ModelDriver) {
ModelDriver md = (ModelDriver) action;
Object bean = md.getModel();//这时候这model对象里还是没有值的
Map<String, String[]> map = request.getParameterMap();//所有参数值都在map里
BeanUtils.populate(bean, map);//将参数值封装到表对应的对象中
}
这里的if判断是判断BookAction有没有实现ModelDriver接口 如果实现了才可向下转型
index.jsp:
<p>版本7</p> 解决了版本6的问题<hr> <a href="book.action?methodName=add&bid=1&bname=Kissship&price=9.9">增加</a> <a href="book.action?methodName=del">删除</a> <a href="book.action?methodName=upd">修改</a> <a href="book.action?methodName=list">查询</a>
效果图:
控制台输出结果如下:
可以看到我们在解决了冗余代码后依旧实现了原本的功能。
最后自定义MVC(下)就到这里,祝大家在敲代码的路上一路通畅!