目录
上期回顾,我们说明了自定义MVC工作原理,其中,中央控制器起到了接收浏览器请求,找到对应的处理人的一个作用,但是也存在缺陷,如:
就像在每一次顾客访问前台时,有很多个部门,比如说料理部门,财务部门,每当访问一次,就要new一个此类,代码如下:
- public void init() throws ServletException {
- actions.put("/book", new BookAction());
- actions.put("/order", new OrderAction());
- }
解决方案:通过xml建模的知识,到config文件中进行操作。
目的:使代码更加灵活
所以接下来对中央控制器进一步作出优化改进:
以前:
- String url = req.getRequestURI();
- //拿到book
- url = url.substring(url.lastIndexOf("/"), url.lastIndexOf("."));
- ActionSupport action = actions.get(url);
- action.excute(req, resp);
现在改进代码:
1、通过url来找到config文件中对应的action对象
2、然后通过该对象来取到路径名servlet.BookAction
3、然后找到对应的方法执行
DispatcherServlet:
- package com.ycx.framework;
-
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
-
- 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.ycx.web.BookAction;
-
- /**
- * 中央控制器:
- * 主要职能:接收浏览器请求,找到对应的处理人
- * @author 杨总
- *
- */
- @WebServlet("*.action")
- public class DispatcherServlet extends HttpServlet{
-
- /**
- * 通过建模我们可以知道,最终ConfigModel对象会包含config.xml中的所有子控制器信息
- * 同时为了解决中央控制器能够动态加载保存子控制器的信息,那么我们只需要引入configModel对象即可
- */
- private ConfigModel configModel;
-
- // 程序启动时,只会加载一次
- @Override
- public void init() throws ServletException {
-
- try {
- configModel=ConfigModelFactory.build();
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- }
-
- @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 {
- //http:localhost:8080/mvc/book.action?methodName=list
- String uri=req.getRequestURI();
- // 拿到/book,就是最后一个“/”到最后一个“.”为止
- uri=uri.substring(uri.lastIndexOf("/"),uri.lastIndexOf("."));
-
- // 相比于上一种从map集合获取子控制器,当前需要获取config.xml中的全路径名,然后反射实例化
- ActionModel actionModel = configModel.pop(uri);
- if(actionModel==null) {
- throw new RuntimeException("action 配置错误");
- }
- String type = actionModel.getType();
- // type是Action子控制器的全路径名
- try {
- Action action= (Action) Class.forName(type).newInstance();
- action.execute(req, resp);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
改进思路:
1、通过url来找到config文件中对应的action对象
2、然后通过该对象来取到路径名servlet.BookAction
3、然后找到对应的方法执行
展示效果:(当自己点击列表删除时,出现了“在同一个servlet中调用 del 方法”这效果,说明用xml建模的知识去优化中央控制器成功!)


但是注意,如果我们的路径名不对的话,就会相应的报错
比如:

原因:

所以:

- <h3>参数传递封装优化</h3>
- <a href="${pageContext.request.contextPath }/book.action?methodName=add & bid=989898 & bname=laoliu & price=89">增加</a>
- <a href="${pageContext.request.contextPath }/book.action?methodName=del">删除</a>
- <a href="${pageContext.request.contextPath }/book.action?methodName=edit">修改</a>
- <a href="${pageContext.request.contextPath }/book.action?methodName=list">查询</a>
- <a href="${pageContext.request.contextPath }/book.action?methodName=load">回显</a>

解决参数冗余问题:
由于在做项目过程中,在servlet类中需要接受很多个参数值,所以就想到要解决当前参数冗余问题。比如:一个实体类Book当中有二十个属性,那么你在进行修改时就要接受二十个值,虽然每次接受语句中的属性值不一样,但从根本上来讲,性质是一样,要接收属性。如下所示:(当时此类没有实现Moderdriver接口)
- package com.ycx.web;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- import com.ycx.entity.Book;
- import com.ycx.framework.Action;
- import com.ycx.framework.ActionSupport;
- import com.ycx.framework.ModelDriven;
-
- public class BookAction extends ActionSupport {
-
- private Book book=new Book();
-
- private void load(HttpServletRequest req, HttpServletResponse resp) {
- System.out.println("在同一个servlet中调用 load 方法");
- }
-
- private void list(HttpServletRequest req, HttpServletResponse resp) {
- System.out.println("在同一个servlet中调用 list 方法");
- }
-
- private void edit(HttpServletRequest req, HttpServletResponse resp) {
- System.out.println("在同一个servlet中调用 edit 方法");
- }
-
- private void del(HttpServletRequest req, HttpServletResponse resp) {
- System.out.println("在同一个servlet中调用 del 方法");
- }
-
- private void add(HttpServletRequest req, HttpServletResponse resp) {
- String bid=req.getParameter("bid");
- String bname=req.getParameter("bname");
- String price=req.getParameter("price");
- Book book=new Book();
- book.setBid(Integer.valueOf(bid));
- book.setBname(bname);
- book.setPrice(Float.valueOf(price));
- bookDao.add(book);
- System.out.println("在同一个servlet中调用 add 方法");
- }
-
-
-
- }
-
- package com.ycx.framework;
-
- /**
- * 模型驱动接口,接收前台JSP传递的参数,并且封装到实体类中
- * @author 杨总
- *
- * @param <T>
- */
- public interface ModelDriven<T> {
- // 拿到将要封装的类实例 ModelDriven.getModel() ---> new Book();
- T getModel();
- }
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- //http:localhost:8080/mvc/book.action?methodName=list
- String uri=req.getRequestURI();
- // 拿到/book,就是最后一个“/”到最后一个“.”为止
- uri=uri.substring(uri.lastIndexOf("/"),uri.lastIndexOf("."));
- // Action action = actions.get(uri);
- // 相比于上一种从map集合获取子控制器,当前需要获取config.xml中的全路径名,然后反射实例化
- ActionModel actionModel = configModel.pop(uri);
- if(actionModel==null) {
- throw new RuntimeException("action 配置错误");
- }
- String type = actionModel.getType();
- // type是Action子控制器的全路径名
- try {
- Action action= (Action) Class.forName(type).newInstance();
- // action是bookAction
- if(action instanceof ModelDriven) {
- ModelDriven md=(ModelDriven) action;
- // model指的是bookAction中的book实例
- Object model = md.getModel();
- // 要给model中的属性赋值,要接收前端jsp参数 req.getParameterMap()
- // PropertyUtils.getProperty(bean, name)
- // 将前端所有的参数值封装进实体类
- BeanUtils.populate(model, req.getParameterMap());
- System.out.println(model);
- }
-
- // 正式调用方法前,book中的属性要被赋值
- action.execute(req, resp);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- package com.ycx.web;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- import com.ycx.entity.Book;
- import com.ycx.framework.Action;
- import com.ycx.framework.ActionSupport;
- import com.ycx.framework.ModelDriven;
-
- public class BookAction extends ActionSupport implements ModelDriven<Book>{
-
- private Book book=new Book();
-
- private void load(HttpServletRequest req, HttpServletResponse resp) {
- System.out.println("在同一个servlet中调用 load 方法");
- }
-
- private void list(HttpServletRequest req, HttpServletResponse resp) {
- System.out.println("在同一个servlet中调用 list 方法");
- }
-
- private void edit(HttpServletRequest req, HttpServletResponse resp) {
- System.out.println("在同一个servlet中调用 edit 方法");
- }
-
- private void del(HttpServletRequest req, HttpServletResponse resp) {
- System.out.println("在同一个servlet中调用 del 方法");
- }
-
- private void add(HttpServletRequest req, HttpServletResponse resp) {
- // String bid=req.getParameter("bid");
- // String bname=req.getParameter("bname");
- // String price=req.getParameter("price");
- Book book=new Book();
- // book.setBid(Integer.valueOf(bid));
- // book.setBname(bname);
- // book.setPrice(Float.valueOf(price));
- // bookDao.add(book);
- System.out.println("在同一个servlet中调用 add 方法");
- }
-
- @Override
- public Book getModel() {
- return null;
- }
-
- }
-
Debug运行效果如下:

其中关于解决参数冗余问题关键代码是:
BeanUtils.populate(bean, req.getParameterMap());//将该对象要接受的参数值封装到对应的对象中
解决跳转方式问题:
在我们跳转到另一个界面时,需要许很多关于跳转方式的代码,有重定向,有转发,例如:
重定向:resp.sendRedirect(path);
转发: req.getRequestDispatcher(path).forward(req, resp);
这些代码往往要写很多次,因此通过配置config文件来解决此类问题;
例如这一次我跳转增加页面时是转发,跳转查询界面是重定向:
主界面代码如下:
- <a href="${pageContext.request.contextPath }/book.action?methodName=add & bid=989898 & bname=laoliu & price=89">增加</a>
- <a href="${pageContext.request.contextPath }/book.action?methodName=list">查询</a>
-
config.xml :
- <?xml version="1.0" encoding="UTF-8"?>
- <config>
-
- <action path="/book" type="com.ycx.web.BookAction">
- <forward name="success" path="/demo2.jsp" redirect="false" />
- <forward name="failed" path="/demo3.jsp" redirect="true" />
- </action>
-
- </config>
思路:
1、当点击增加或者编辑时,首先跳转的是中央控制器类(DispatchServlet):获取到url,url将决定要跳到哪一个实体类,
2、之后进入ActionSupport(子控制器接口实现类)通过methodname要调用什么方法,
3、再然后进入到BookAction中调用methodname方法,找到对应的返回值,
4、通过返回值在进入到config文件找到path属性,之后在中央控制器中进行判断,来决定是重定向还是转发。
- String result = action.execute(req, resp);
- ForwardModel forwardModel = actionModel.pop(result);
- // if(forwardModel==null)
- // throw new RuntimeException("forward config error");
- String path = forwardModel.getPath();
- // 拿到是否需要转发的配置
- boolean redirect = forwardModel.isRedirect();
- if(redirect)
- //${pageContext.request.contextPath}
- resp.sendRedirect(req.getServletContext().getContextPath()+path);
- else
- req.getRequestDispatcher(path).forward(req, resp);
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- package com.ycx.framework;
-
- import java.lang.reflect.Method;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- public class ActionSupport implements Action{
-
- @Override
- public String execute(HttpServletRequest req, HttpServletResponse resp) {
- String methodName = req.getParameter("methodName");
- // methodName可能是多种方法
- // 前台传递什么方法就调用当前类的对应方法
- try {
- Method m=this.getClass()// BookServlet.Class
- .getDeclaredMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
- m.setAccessible(true);
- // 调用当前类实例的 methodName 方法
- return (String) m.invoke(this, req,resp);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- }


config文件(图中name为返回值,path为要跳转的界面路径名,redirect为跳转方式):

demo2界面:
- <%@ page language="java" contentType="text/html; charset=UTF-8"
- pageEncoding="UTF-8"%>
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Insert title here</title>
- </head>
- <body>
- 转发页面
- </body>
- </html>
demo3界面:
- <%@ page language="java" contentType="text/html; charset=UTF-8"
- pageEncoding="UTF-8"%>
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Insert title here</title>
- </head>
- <body>
- 重定向
- </body>
- </html>
运行结果:
1、当点击增加时,跳转到demo2界面,显示效果如下

2.当点击查询时,跳转到demo3界面,显示效果如下:

如果config.xml文件名改成mvc.xml,该程序是否还可因运行呢?答案肯定是不行的。

因为 ConfigModelFactory 类里就定义了它的默认路径名如下:

我们可以在DispatcherServlet类里的init初始化里设置它的配置地址:
- @Override
- public void init() throws ServletException {
- // actions.put("/book", new BookAction());
- // actions.put("/order", new BookAction());
- try {
- //配置地址
- // getInitParameter的作用是拿到web.xml中的servlet信息配置的参数
- String configLocation = this.getInitParameter("configLocation");
- if(configLocation==null||"".equals(configLocation))
- configModel=ConfigModelFactory.build();
- else
- configModel=ConfigModelFactory.build(configLocation);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- }
web.xml :
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
- <display-name>T280_mvc</display-name>
- <servlet>
- <servlet-name>mvc</servlet-name>
- <servlet-class>com.ycx.framework.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>configLocation</param-name>
- <param-value>/yangzong.xml</param-value>
- </init-param>
- </servlet>
- <servlet-mapping>
- <url-pattern>*.action</url-pattern>
- </servlet-mapping>
- </web-app>
然后把config.xml改成yangzong.xml即可改变框架配置