• 自定义MVC框架实现


    目录

    一、让中央控制器动态加载存储子控制器

    二、参数传递封装优化

    三、对于方法执行结果转发重定向优化

    四、框架配置可变


    一、让中央控制器动态加载存储子控制器

    上期回顾,我们说明了自定义MVC工作原理,其中,中央控制器起到了接收浏览器请求,找到对应的处理人的一个作用,但是也存在缺陷,如:

     就像在每一次顾客访问前台时,有很多个部门,比如说料理部门,财务部门,每当访问一次,就要new一个此类,代码如下:

    1. public void init() throws ServletException {
    2.  actions.put("/book", new BookAction());
    3.  actions.put("/order", new OrderAction());
    4.  }

    解决方案:通过xml建模的知识,到config文件中进行操作。

    目的:使代码更加灵活

    所以接下来对中央控制器进一步作出优化改进:
         以前:

    1. String url = req.getRequestURI();
    2.   //拿到book
    3.   url = url.substring(url.lastIndexOf("/"), url.lastIndexOf("."));
    4.      ActionSupport action = actions.get(url);
    5.   action.excute(req, resp);

        现在改进代码:
      
       1、通过url来找到config文件中对应的action对象
       2、然后通过该对象来取到路径名servlet.BookAction
       3、然后找到对应的方法执行
      

     DispatcherServlet:

    1. package com.ycx.framework;
    2. import java.io.IOException;
    3. import java.util.HashMap;
    4. import java.util.Map;
    5. import javax.servlet.ServletException;
    6. import javax.servlet.annotation.WebServlet;
    7. import javax.servlet.http.HttpServlet;
    8. import javax.servlet.http.HttpServletRequest;
    9. import javax.servlet.http.HttpServletResponse;
    10. import com.ycx.web.BookAction;
    11. /**
    12. * 中央控制器:
    13. * 主要职能:接收浏览器请求,找到对应的处理人
    14. * @author 杨总
    15. *
    16. */
    17. @WebServlet("*.action")
    18. public class DispatcherServlet extends HttpServlet{
    19. /**
    20. * 通过建模我们可以知道,最终ConfigModel对象会包含config.xml中的所有子控制器信息
    21. * 同时为了解决中央控制器能够动态加载保存子控制器的信息,那么我们只需要引入configModel对象即可
    22. */
    23. private ConfigModel configModel;
    24. // 程序启动时,只会加载一次
    25. @Override
    26. public void init() throws ServletException {
    27. try {
    28. configModel=ConfigModelFactory.build();
    29. } catch (Exception e) {
    30. e.printStackTrace();
    31. }
    32. }
    33. @Override
    34. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    35. doPost(req, resp);
    36. }
    37. @Override
    38. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    39. //http:localhost:8080/mvc/book.action?methodName=list
    40. String uri=req.getRequestURI();
    41. // 拿到/book,就是最后一个“/”到最后一个“.”为止
    42. uri=uri.substring(uri.lastIndexOf("/"),uri.lastIndexOf("."));
    43. // 相比于上一种从map集合获取子控制器,当前需要获取config.xml中的全路径名,然后反射实例化
    44. ActionModel actionModel = configModel.pop(uri);
    45. if(actionModel==null) {
    46. throw new RuntimeException("action 配置错误");
    47. }
    48. String type = actionModel.getType();
    49. // type是Action子控制器的全路径名
    50. try {
    51. Action action= (Action) Class.forName(type).newInstance();
    52. action.execute(req, resp);
    53. } catch (Exception e) {
    54. e.printStackTrace();
    55. }
    56. }
    57. }

                      改进思路:
                                      1、通过url来找到config文件中对应的action对象
                                      2、然后通过该对象来取到路径名servlet.BookAction
                                      3、然后找到对应的方法执行
    展示效果:(当自己点击列表删除时,出现了“在同一个servlet中调用 del 方法”这效果,说明用xml建模的知识去优化中央控制器成功!) 

     

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

     比如:

     原因:

    所以:

     

    二、参数传递封装优化

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

     

     解决参数冗余问题:

    由于在做项目过程中,在servlet类中需要接受很多个参数值,所以就想到要解决当前参数冗余问题。比如:一个实体类Book当中有二十个属性,那么你在进行修改时就要接受二十个值,虽然每次接受语句中的属性值不一样,但从根本上来讲,性质是一样,要接收属性。如下所示:(当时此类没有实现Moderdriver接口)

    1. package com.ycx.web;
    2. import javax.servlet.http.HttpServletRequest;
    3. import javax.servlet.http.HttpServletResponse;
    4. import com.ycx.entity.Book;
    5. import com.ycx.framework.Action;
    6. import com.ycx.framework.ActionSupport;
    7. import com.ycx.framework.ModelDriven;
    8. public class BookAction extends ActionSupport {
    9. private Book book=new Book();
    10. private void load(HttpServletRequest req, HttpServletResponse resp) {
    11. System.out.println("在同一个servlet中调用 load 方法");
    12. }
    13. private void list(HttpServletRequest req, HttpServletResponse resp) {
    14. System.out.println("在同一个servlet中调用 list 方法");
    15. }
    16. private void edit(HttpServletRequest req, HttpServletResponse resp) {
    17. System.out.println("在同一个servlet中调用 edit 方法");
    18. }
    19. private void del(HttpServletRequest req, HttpServletResponse resp) {
    20. System.out.println("在同一个servlet中调用 del 方法");
    21. }
    22. private void add(HttpServletRequest req, HttpServletResponse resp) {
    23. String bid=req.getParameter("bid");
    24. String bname=req.getParameter("bname");
    25. String price=req.getParameter("price");
    26. Book book=new Book();
    27. book.setBid(Integer.valueOf(bid));
    28. book.setBname(bname);
    29. book.setPrice(Float.valueOf(price));
    30. bookDao.add(book);
    31. System.out.println("在同一个servlet中调用 add 方法");
    32. }
    33. }

      
    解决方案:建一个模型驱动接口,使BookAction实现该接口,在中央控制器中将所有要接收的参数封装到模型接口中,从而达到简便的效果。
                ModelDriven类(驱动接口类): 

    1. package com.ycx.framework;
    2. /**
    3. * 模型驱动接口,接收前台JSP传递的参数,并且封装到实体类中
    4. * @author 杨总
    5. *
    6. * @param <T>
    7. */
    8. public interface ModelDriven<T> {
    9. // 拿到将要封装的类实例 ModelDriven.getModel() ---> new Book();
    10. T getModel();
    11. }


                 DispatcherServlet 中央控制器类:

    1. @Override
    2. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    3. //http:localhost:8080/mvc/book.action?methodName=list
    4. String uri=req.getRequestURI();
    5. // 拿到/book,就是最后一个“/”到最后一个“.”为止
    6. uri=uri.substring(uri.lastIndexOf("/"),uri.lastIndexOf("."));
    7. // Action action = actions.get(uri);
    8. // 相比于上一种从map集合获取子控制器,当前需要获取config.xml中的全路径名,然后反射实例化
    9. ActionModel actionModel = configModel.pop(uri);
    10. if(actionModel==null) {
    11. throw new RuntimeException("action 配置错误");
    12. }
    13. String type = actionModel.getType();
    14. // type是Action子控制器的全路径名
    15. try {
    16. Action action= (Action) Class.forName(type).newInstance();
    17. // action是bookAction
    18. if(action instanceof ModelDriven) {
    19. ModelDriven md=(ModelDriven) action;
    20. // model指的是bookAction中的book实例
    21. Object model = md.getModel();
    22. // 要给model中的属性赋值,要接收前端jsp参数 req.getParameterMap()
    23. // PropertyUtils.getProperty(bean, name)
    24. // 将前端所有的参数值封装进实体类
    25. BeanUtils.populate(model, req.getParameterMap());
    26. System.out.println(model);
    27. }
    28. // 正式调用方法前,book中的属性要被赋值
    29. action.execute(req, resp);
    30. } catch (Exception e) {
    31. e.printStackTrace();
    32. }
    33. }


                 BookAction类:(注意,在上一张同一个类里,没有实现ModelDriver接口,而如下bookaction类实现了ModelDriver接口,把接受多个参数的属性值语句注释,达到了简便的效果)

    1. package com.ycx.web;
    2. import javax.servlet.http.HttpServletRequest;
    3. import javax.servlet.http.HttpServletResponse;
    4. import com.ycx.entity.Book;
    5. import com.ycx.framework.Action;
    6. import com.ycx.framework.ActionSupport;
    7. import com.ycx.framework.ModelDriven;
    8. public class BookAction extends ActionSupport implements ModelDriven<Book>{
    9. private Book book=new Book();
    10. private void load(HttpServletRequest req, HttpServletResponse resp) {
    11. System.out.println("在同一个servlet中调用 load 方法");
    12. }
    13. private void list(HttpServletRequest req, HttpServletResponse resp) {
    14. System.out.println("在同一个servlet中调用 list 方法");
    15. }
    16. private void edit(HttpServletRequest req, HttpServletResponse resp) {
    17. System.out.println("在同一个servlet中调用 edit 方法");
    18. }
    19. private void del(HttpServletRequest req, HttpServletResponse resp) {
    20. System.out.println("在同一个servlet中调用 del 方法");
    21. }
    22. private void add(HttpServletRequest req, HttpServletResponse resp) {
    23. // String bid=req.getParameter("bid");
    24. // String bname=req.getParameter("bname");
    25. // String price=req.getParameter("price");
    26. Book book=new Book();
    27. // book.setBid(Integer.valueOf(bid));
    28. // book.setBname(bname);
    29. // book.setPrice(Float.valueOf(price));
    30. // bookDao.add(book);
    31. System.out.println("在同一个servlet中调用 add 方法");
    32. }
    33. @Override
    34. public Book getModel() {
    35. return null;
    36. }
    37. }

     Debug运行效果如下:

     

        其中关于解决参数冗余问题关键代码是:
            BeanUtils.populate(bean, req.getParameterMap());

            //将该对象要接受的参数值封装到对应的对象中

    三、对于方法执行结果转发重定向优化

    解决跳转方式问题:

    在我们跳转到另一个界面时,需要许很多关于跳转方式的代码,有重定向,有转发,例如:
               重定向:resp.sendRedirect(path);
               转发: req.getRequestDispatcher(path).forward(req, resp);


    这些代码往往要写很多次,因此通过配置config文件来解决此类问题;

    例如这一次我跳转增加页面时是转发,跳转查询界面是重定向:

    主界面代码如下:

    1. <a href="${pageContext.request.contextPath }/book.action?methodName=add & bid=989898 & bname=laoliu & price=89">增加</a>
    2. <a href="${pageContext.request.contextPath }/book.action?methodName=list">查询</a>

    config.xml :

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <config>
    3. <action path="/book" type="com.ycx.web.BookAction">
    4. <forward name="success" path="/demo2.jsp" redirect="false" />
    5. <forward name="failed" path="/demo3.jsp" redirect="true" />
    6. </action>
    7. </config>

    思路:
            1、当点击增加或者编辑时,首先跳转的是中央控制器类(DispatchServlet):获取到url,url将决定要跳到哪一个实体类,
            2、之后进入ActionSupport(子控制器接口实现类)通过methodname要调用什么方法,
            3、再然后进入到BookAction中调用methodname方法,找到对应的返回值,
            4、通过返回值在进入到config文件找到path属性,之后在中央控制器中进行判断,来决定是重定向还是转发。

    中央控制器(DispatcherServlet): 

    1. String result = action.execute(req, resp);
    2. ForwardModel forwardModel = actionModel.pop(result);
    3. // if(forwardModel==null)
    4. // throw new RuntimeException("forward config error");
    5. String path = forwardModel.getPath();
    6. // 拿到是否需要转发的配置
    7. boolean redirect = forwardModel.isRedirect();
    8. if(redirect)
    9. //${pageContext.request.contextPath}
    10. resp.sendRedirect(req.getServletContext().getContextPath()+path);
    11. else
    12. req.getRequestDispatcher(path).forward(req, resp);
    13. } catch (Exception e) {
    14. e.printStackTrace();
    15. }
    16. }


    ActionSupport(子控制器接口实现类): 

    1. package com.ycx.framework;
    2. import java.lang.reflect.Method;
    3. import javax.servlet.http.HttpServletRequest;
    4. import javax.servlet.http.HttpServletResponse;
    5. public class ActionSupport implements Action{
    6. @Override
    7. public String execute(HttpServletRequest req, HttpServletResponse resp) {
    8. String methodName = req.getParameter("methodName");
    9. // methodName可能是多种方法
    10. // 前台传递什么方法就调用当前类的对应方法
    11. try {
    12. Method m=this.getClass()// BookServlet.Class
    13. .getDeclaredMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
    14. m.setAccessible(true);
    15. // 调用当前类实例的 methodName 方法
    16. return (String) m.invoke(this, req,resp);
    17. } catch (Exception e) {
    18. e.printStackTrace();
    19. }
    20. return null;
    21. }
    22. }


     BookAction(图中标记的为返回值):

     

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

     

    demo2界面:

    1. <%@ page language="java" contentType="text/html; charset=UTF-8"
    2. pageEncoding="UTF-8"%>
    3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    4. <html>
    5. <head>
    6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    7. <title>Insert title here</title>
    8. </head>
    9. <body>
    10. 转发页面
    11. </body>
    12. </html>


    demo3界面: 

    1. <%@ page language="java" contentType="text/html; charset=UTF-8"
    2. pageEncoding="UTF-8"%>
    3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    4. <html>
    5. <head>
    6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    7. <title>Insert title here</title>
    8. </head>
    9. <body>
    10. 重定向
    11. </body>
    12. </html>

    运行结果:
           1、当点击增加时,跳转到demo2界面,显示效果如下

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

     

     四、框架配置可变

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

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

     

     我们可以在DispatcherServlet类里的init初始化里设置它的配置地址:

    1. @Override
    2. public void init() throws ServletException {
    3. // actions.put("/book", new BookAction());
    4. // actions.put("/order", new BookAction());
    5. try {
    6. //配置地址
    7. // getInitParameter的作用是拿到web.xml中的servlet信息配置的参数
    8. String configLocation = this.getInitParameter("configLocation");
    9. if(configLocation==null||"".equals(configLocation))
    10. configModel=ConfigModelFactory.build();
    11. else
    12. configModel=ConfigModelFactory.build(configLocation);
    13. } catch (Exception e) {
    14. e.printStackTrace();
    15. }
    16. }

    web.xml :

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <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">
    3. <display-name>T280_mvc</display-name>
    4. <servlet>
    5. <servlet-name>mvc</servlet-name>
    6. <servlet-class>com.ycx.framework.DispatcherServlet</servlet-class>
    7. <init-param>
    8. <param-name>configLocation</param-name>
    9. <param-value>/yangzong.xml</param-value>
    10. </init-param>
    11. </servlet>
    12. <servlet-mapping>
    13. <url-pattern>*.action</url-pattern>
    14. </servlet-mapping>
    15. </web-app>

    然后把config.xml改成yangzong.xml即可改变框架配置

  • 相关阅读:
    SpringBoot2.x整合Jedis客户端详细过程
    ChatGPT在电子健康记录和医疗信息查询中的应用前景如何?
    护眼灯值不值得买?显色指数最好的护眼灯推荐
    创品牌强农精品培育消费引领 国稻种芯百团计划行动发布
    Windows电脑安装Linux(Ubuntu 22.04)系统(图文并茂)
    信息安全:网络物理隔离技术原理与应用.
    MindSpore深度概率推断算法与概率模型
    Windows系统开启webdav挂载功能,访问网络文件
    Flash-Attention
    Java容器使用:Map
  • 原文地址:https://blog.csdn.net/weixin_65808248/article/details/125491971