自定义MVC架构【中】

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 自定义MVC架构【中】

一、前言

自定义MVC架构【上】中我们了解到了什么是MVC架构与三层架构的区别以及MVC版本迭代的演变过程,但是在上篇中,依旧遗留一些问题,这篇文章我将带领大家从上篇的基础之上继续优化!!

二、完善MVC架构

1.问题分析

      首先我们的第一个问题就是子控制器的初始化配置问题,我在上篇中的最后一个版本“反射增强版”演示到如果需要对项目中的其他表进行操作,就必须编写子控制器并进行配置。

但是我们最后是要将我们所写的自定义MVC架构打成jar架包的因此不能进行修改,我们做不到未卜先知的能力,提前写好子控制器,不知道未来将有什么样的表以及属性字段,所以我们要将中央控制器(DispatchServlet)的初始化工作变成“自动化”。

      其次第二个问题就是我们在子控制器中无论是什么操作都是要进行页面回显的,需要进行重定向response.sendRedirect();或者转发request.getRequestDispatcher().forward();操作(ajax方式除外)这些代码是冗余的。

      最后就是部分操作需要获取请求参数进行实体封装问题,在进行新增操作和修改操作就要将获取请求参数并进行实体封装,如下图。

假如我们一张表内有十几个字段,是不是要获取请求参数,那无疑又是十几行代码,其次可能还会存在构造器填写出错的问题。如果不用构造器选用set方法依次赋值,虽避免了填写出错问题,但是也大大增加了我们的代码量。

小贴士:

什么是jar架包

JAR(Java ARchive)是一种用于存储和分发Java类文件、资源文件和其他相关文件的压缩文件格式。JAR文件以.jar作为扩展名,它是Java平台上常用的一种文件格式,用于打包和发布Java应用程序和库。

为什么架包不能进行修改

由于JAR文件中的类文件是已编译的二进制代码,所以无法直接修改源代码。对于想要修改源代码的情况,你需要访问原始的Java源代码文件,对其进行修改,并重新编译生成新的类文件。然后,你可以使用新编译的类文件来创建或更新一个新的JAR文件。

2.解决子控制器初始化

其实解决办法很简单,就是利用XML约束(XML Schema或DTD)来定义和验证MVC配置文件的结构和内容。XML文件可以描述MVC中的模型(Model)、视图(View)和控制器(Controller)之间的关系和行为。拿到配置文件中的信息后,进行反射实例化动态处理。

如果不了解XML约束以及建模的可以了解一下作者所编写的XML三部曲:

DTD约束的基本概述

Dom4j框架解析XML

XML建模看这一篇就够了

好啦,回归正题,我们先编写一个xml文件并进行建模和解析初始化数据到Model中。

①编写XML文件

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <action path="/books" type="com.xw.servlet.BookAction">
    <forward name="forward" path="/forward.jsp" redirect="false" />
    <forward name="redirect" path="/redirect.jsp" redirect="true" />
  </action>
  <action path="/goods" type="com.xw.servlet.GoodsAction">
    <forward name="forward" path="/forward.jsp" redirect="false" />
    <forward name="redirect" path="/redirect.jsp" redirect="true" />
  </action>
</config>

注意:这个xml文件存放在根目录下,先建立一个Source Folder文件夹将我们的config.xml文件放入即可。

将需要的子控制器进行配置即可,以后谁要用我们的自定义MVC也是如此。

温馨提示:

action标签中的path:是DispatchServlet类截取到的url路径。

action标签中的type:是子控制器的全路径名

forward标签中的name:是子控制器处理结果的return值用于判断是转发还是重定向(问题二的解决方案)。

forward标签中的path:是跳转的页面。

forward标签中的redirect:是说明是不是要跳转,true跳转、false不跳转。

②xml文件的建模以及解析xml初始化数据到建模

ConfigModel:config标签的建模

package com.xw.model;
import java.util.HashMap;
import java.util.Map;
/**config标签实体对象
 * @author 索隆
 *
 */
public class ConfigModel {
  private Map<String, ActionModel> ConfigMap=new HashMap<String, ActionModel>();
  public ConfigModel() {
    // TODO Auto-generated constructor stub
  }
  //将ActionModel放入ConfigModel属性中
  public void push(ActionModel a) {
    ConfigMap.put(a.getPath(), a);
  }
  //根据ActionModel的path属性查找指定ConfigMap
  public ActionModel pop(String path) {
    return ConfigMap.get(path);
  }
  @Override
  public String toString() {
    return "ConfigModel [ConfigMap=" + ConfigMap + "]";
  }
}

ActionModel:action标签的建模

package com.xw.model;
import java.util.HashMap;
import java.util.Map;
/**action标签实体对象
 * @author 索隆
 *
 */
public class ActionModel {
  private String path;
  private String type;
  private Map<String, ForwardModel> ActionMap = new HashMap<>();
  public ActionModel() {
    // TODO Auto-generated constructor stub
  }
  public String getPath() {
    return path;
  }
  public void setPath(String path) {
    this.path = path;
  }
  public String getType() {
    return type;
  }
  public void setType(String type) {
    this.type = type;
  }
  // 将ForwardModel传入ActionModle属性
  public void push(ForwardModel f) {
    ActionMap.put(f.getName(), f);
  }
  public Map<String, ForwardModel> getActionMap() {
    return ActionMap;
  }
  public void setActionMap(Map<String, ForwardModel> actionMap) {
    ActionMap = actionMap;
  }
  // 根据ForwardModel的name属性找到指定的ActionMap
  public ForwardModel pop(String name) {
    return ActionMap.get(name);
  }
  //根据ActionModel中的path属性查询全部的ForardModel
  public ForwardModel Vive(String path) {
    return null;
  }
  @Override
  public String toString() {
    return "ActionModel [path=" + path + ", type=" + type + ", ActionMap=" + ActionMap + "]";
  }
}

ForwardModel:forward标签的建模

package com.xw.model;
/**
 * Forward标签对象实体
 * 
 * @author 索隆
 *
 */
public class ForwardModel {
  private String name;
  private String path;
  private boolean redirect;
  public ForwardModel() {
    // TODO Auto-generated constructor stub
  }
  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;
  }
  @Override
  public String toString() {
    return "ForwardModel [name=" + name + ", path=" + path + ", redirect=" + redirect + "]";
  }
}

解析xml并初始化数据到建模

package com.xw.model;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.eclipse.jdt.core.BuildJarIndex;
/**
 * Config.xml的“工厂”,用于初始化数据
 * 
 * @author 索隆
 *
 */
public class ConfigFactory {
  /**
   * 初始化数据
   * 
   * @param xmlPath
   *            需要解析的xml
   * @return ConfigModel 实体
   * @throws Exception
   */
  public static ConfigModel build(String xmlPath) throws Exception {
    // 定义ConfigModel对象
    ConfigModel cm = new ConfigModel();
    // 获取配置文件并转换成流对象
    InputStream is = ConfigFactory.class.getResourceAsStream(xmlPath);
    // 利用dom4j解析流
    SAXReader sa = new SAXReader();
    // 读取流对象
    Document read = sa.read(is);
    // 获取config标签下的所有action标签
    List<Element> configNodes = read.selectNodes("/config/action");
    // 遍历所有action标签
    for (Element configNode : configNodes) {
      // 实例化ActionModel对象
      ActionModel am = new ActionModel();
      // 将解析后的内容添加到ActionModel实体
      am.setPath(configNode.attributeValue("path"));
      am.setType(configNode.attributeValue("type"));
      // 获取action标签下的所有forward标签
      List<Element> forwardNodes = configNode.selectNodes("forward");
      for (Element element : forwardNodes) {
        // 实例化ForwardModle对象
        ForwardModel fm = new ForwardModel();
        // 将解析后的内容添加到ForwardModle实体
        fm.setName(element.attributeValue("name"));
        fm.setPath(element.attributeValue("path"));
        fm.setRedirect(!"false".equals(element.attributeValue("redirect")));
        am.push(fm);
      }
      cm.push(am);
    }
    return cm;
  }
  public static void main(String[] args) throws Exception {
    //测试是否成功初始化
    ConfigModel build = ConfigFactory.build("/config.xml");
    //模拟DispatchServlet截取到的url看是否拿到指定全路径名
    ActionModel pop = build.pop("/books");
     System.out.println("/books的子控制器全路径是"+pop.getType());
  }
}

打印测试的结果:

最终的中央控制器(DispatchServlet)类优化代码放在后面。

2.解决跳转页面的代码冗余问题

同理,我们继续沿用config.xml配置文件的解决方案进行优化,我们只需将平常所写的页面回显的代码变成字符串也就是前面所解释的forward标签中的name属性

 

③将子控制器的操作方法返回类型变成“String”返回相应的字符串forward转发或redirect重定向

BookAction子控制器

package com.xw.servlet;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xw.entity.Book;
import com.xw.framework.ActionSupport;
import com.xw.framework.ModelDeivern;
public class BookAction extends ActionSupport  {
  private String list(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本五反射机制优化的查询——book");
    request.setAttribute("xw", "xw");
    return "forward";
  }
  private String del(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本五反射机制优化的删除——book");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  private String upd(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本五反射机制优化的修改——book");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  private String add(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本五反射机制优化的新增——book");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
}

GoodsAction子控制器

package com.xw.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xw.entity.Goods;
import com.xw.framework.ActionSupport;
import com.xw.framework.ModelDeivern;
public class GoodsAction extends ActionSupport {
  private String list(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本五反射机制优化的查询——goods");
    request.setAttribute("xw", "xw");
    return "forward";
  }
  private String del(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本五反射机制优化的删除——goods");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  private String upd(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本五反射机制优化的修改——goods");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  private String add(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本五反射机制优化的新增——goods");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
}

3.解决获取请求参数进行实体封装问题

想要优化请求参数进行实体封装问题必须完成以下四步:

1.要有表对应的类属性对象
2.要获取到所有的属性及参数
3.将参数值封装到表对应的对象中
4.要做到所有子控制器通用

那么怎么完成呢?简单,我们定义一个模型驱动接口,让子控制通用

package com.xw.framework;
/**
 * 模型驱动接口,让子控制通用
 * 
 * @author Java方文山
 *
 */
public interface ModelDeivern<T> {
  T getModel();
}

让我们的子控制器实现该接口,实现必须重写接口的方法,我们只需要将要操作的表(实体对象)传递即可。

BookAction子控制器实现ModelDeivern

package com.xw.servlet;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xw.entity.Book;
import com.xw.framework.ActionSupport;
import com.xw.framework.ModelDeivern;
public class BookAction extends ActionSupport  implements ModelDeivern<Book>{
  private Book book=new Book();
  private String list(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本四反射机制优化的查询——book");
    request.setAttribute("xw", "xw");
    return "forward";
  }
  private String del(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本四反射机制优化的删除——book");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  private String upd(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本四反射机制优化的修改——book");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  private String add(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("测试新增"+book);
    System.out.println("我是版本四反射机制优化的新增——book");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  /**
   * 因为实现类必须重写这个方法以及传递泛型,到时候谁用就是谁
   */
  @Override
  public Book getModel() {
    return book;
  }
}

GoodsAction 子控制器实现ModelDeivern

package com.xw.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xw.entity.Goods;
import com.xw.framework.ActionSupport;
import com.xw.framework.ModelDeivern;
public class GoodsAction extends ActionSupport implements ModelDeivern<Goods>{
  private Goods goods=new Goods();
  private String list(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本四反射机制优化的查询——goods");
    request.setAttribute("xw", "xw");
    return "forward";
  }
  private String del(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本四反射机制优化的删除——goods");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  private String upd(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("我是版本四反射机制优化的修改——goods");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  private String add(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("测试新增"+goods);
    System.out.println("我是版本四反射机制优化的新增——goods");
    request.setAttribute("xw", "xw");
    return "redirect";
  }
  @Override
  public Goods getModel() {
    return goods;
  }
}

为什么中央控制器(DispatchServlet)类优化代码要放到最后来讲解呢,如果你还记得自定义MVC的概念的话,就已经想到了,我们的中央控制器(DispatchServlet)类才是分发请求操作的类,而前面写的类(子控制器)都是接收中央控制器(DispatchServlet)类的操作请求做事的人,以上三种问题都要在中央控制器(DispatchServlet)类进行判断处理分发请求给子控制器。下面来看优化后的代码。

中央控制器(DispatchServlet)优化后

package com.xw.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 org.w3c.dom.ranges.RangeException;
import com.xw.model.ActionModel;
import com.xw.model.ConfigFactory;
import com.xw.model.ConfigModel;
import com.xw.model.ForwardModel;
import com.xw.servlet.BookAction;
import com.xw.servlet.GoodsAction;
/**
 * 中央控制器拦截请求根据请求找到子控制器
 */
@WebServlet("*.do")
public class DispatchServlet extends HttpServlet {
  // 获取配置文件中的子控制器
  private ConfigModel ConfigModel;
  @Override
  public void init() throws ServletException {
    // ConfigFactory是Config.xml的“工厂”,用于解析Config.xml文件并完成ConfigModel初始化数据
    ConfigFactory ConfigFactory = new ConfigFactory();
    try {
      ConfigModel=ConfigFactory.build("/config.xml");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    doPost(request, response);
  }
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    // 获取到url请求
    String url = request.getRequestURI();
    // 截取指定需要操作的表
    url = url.substring(url.lastIndexOf("/"), url.lastIndexOf("."));
    // 根据path也就是截取路径(url)找到配置的type(子控制器)
    ActionModel ActionModel = ConfigModel.pop(url);
    // 防止一些人配置没写完善这里做一个非空判断如果请求路径未配置就抛出一个自定义异常给他
    if (ActionModel == null)
      throw new RuntimeException("Config is not configured yet, please configure it first.");
    // 拿到ActionModel里面的type值(type值就是子控制器的全路径名)
    String type = ActionModel.getType();
    try {
      //根据全路径名获取类类并反射实例化
      Action action = (Action) Class.forName(type).newInstance();
      //查看该类是否实现ModelDeivern接口
      if(action instanceof ModelDeivern) {
        ModelDeivern md=(ModelDeivern)action;
        //获取泛型传递的实体对象
        Object model = md.getModel();
        //获取请求参数的所有的属性及参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        //使用工具类将参数值封装到表对应的对象中
        BeanUtils.populate(model, parameterMap);
      }
      //调用子控制器
      String execute = action.execute(request, response);
      //判读是重定向还是转发-根据返回值找到指定ForwardModel
      ForwardModel pop = ActionModel.pop(execute);
      //如果是ajax不需要配置xml,所以ForwardModel有值的时候才进行跳转判断
      if(pop!=null) {
        //拿到redirect进行相对应的判断
        boolean redirect = pop.isRedirect();
        //拿到path进行相对应的页面跳转
        String path = pop.getPath();
        if(redirect) {
          //true重定向
          //注意这里会丢失项目路径所以要request.getContextPath()
          response.sendRedirect(request.getContextPath()+path);
        }else {
          //false转发
          request.getRequestDispatcher(path).forward(request, response);
        }   
      }     
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

注意:

①重定向这里会丢失项目路径所以要拼接request.getContextPath()。

②初始化数据传递的xml文件由于是在根目录,所以要加“/”。

至此我们的三个问题都以得到解决下面我们操作测试来看看

 

我们的页面可以进行重定向或转发的跳转页面并且可以动态的封装实体。

4.总结

利用XML文件配置与约束的方式进行反射操作,可自动配置文件并解决编写页面跳转的时候代码冗余的问题。定义一个“模型驱动类”,将来谁要编写操作都要将实体类填写在泛型内,就可以拿到实体类,我们就可以通过实体类反射实例,动态获取属性以及属性的赋值操作,减少了我们自己封装实体的弊端。

这篇文章就到这里啦,期待我的下次更新吧!!


相关文章
|
1月前
|
存储 前端开发 调度
Flux 与传统的 MVC 架构模式区别
Flux是一种用于构建用户界面的架构模式,与传统的MVC架构不同,它采用单向数据流,通过Dispatcher统一管理数据的分发,Store负责存储数据和业务逻辑,View只负责展示数据,使得应用状态更加可预测和易于维护。
|
4月前
|
设计模式 前端开发 数据库
哇塞!Rails 的 MVC 架构也太牛了吧!快来看看这令人惊叹的编程魔法,开启新世界大门!
【8月更文挑战第31天】《Rails中的MVC架构解析》介绍了Ruby on Rails框架核心的MVC设计模式,通过模型(Model)、视图(View)和控制器(Controller)三部分分离应用逻辑,利用Active Record进行数据库操作,ERB模板渲染视图,以及控制器处理用户请求与业务逻辑,使代码更易维护和扩展,提升团队开发效率。
81 0
|
1月前
|
存储 前端开发 数据可视化
在实际项目中,如何选择使用 Flux 架构或传统的 MVC 架构
在实际项目中选择使用Flux架构或传统MVC架构时,需考虑项目复杂度、团队熟悉度和性能需求。Flux适合大型、高并发应用,MVC则适用于中小型、逻辑简单的项目。
|
2月前
|
前端开发 Java 数据库
springBoot:template engine&自定义一个mvc&后端给前端传数据&增删改查 (三)
本文介绍了如何自定义一个 MVC 框架,包括后端向前端传递数据、前后端代理配置、实现增删改查功能以及分页查询。详细展示了代码示例,从配置文件到控制器、服务层和数据访问层的实现,帮助开发者快速理解和应用。
|
3月前
|
设计模式 前端开发 数据库
理解mvc架构
mvc架构
33 4
|
4月前
|
设计模式 存储 前端开发
MVC革命:如何用一个设计模式重塑你的应用架构,让代码重构变得戏剧性地简单!
【8月更文挑战第22天】自定义MVC(Model-View-Controller)设计模式将应用分为模型、视图和控制器三个核心组件,实现关注点分离,提升代码可维护性和扩展性。模型管理数据和业务逻辑,视图负责数据显示与用户交互,控制器处理用户输入并协调模型与视图。通过示例代码展示了基本的MVC框架实现,可根据需求扩展定制。MVC模式灵活性强,支持单元测试与多人协作,但需注意避免控制器过度复杂化。
47 1
|
4月前
|
开发者 前端开发 Java
架构模式的诗与远方:如何在MVC的田野上,用Struts 2编织Web开发的新篇章
【8月更文挑战第31天】架构模式是软件开发的核心概念,MVC(Model-View-Controller)通过清晰的分层和职责分离,成为广泛采用的模式。随着业务需求的复杂化,Struts 2框架应运而生,继承MVC优点并引入更多功能。本文探讨从MVC到Struts 2的演进,强调架构模式的重要性。MVC将应用程序分为模型、视图和控制器三部分,提高模块化和可维护性。
49 0
|
4月前
|
存储 前端开发 数据库
神秘编程世界惊现强大架构!Web2py 的 MVC 究竟隐藏着怎样的神奇魔力?带你探索实际应用之谜!
【8月更文挑战第31天】在现代 Web 开发中,MVC(Model-View-Controller)架构被广泛应用,将应用程序分为模型、视图和控制器三个部分,有助于提高代码的可维护性、可扩展性和可测试性。Web2py 是一个采用 MVC 架构的 Python Web 框架,其中模型处理数据和业务逻辑,视图负责呈现数据给用户,控制器则协调模型和视图之间的交互。
41 0
|
5月前
|
存储 前端开发 算法
MVC(Model-View-Controller)架构
MVC架构帮助开发者构建清晰、可维护和可扩展的Web应用程序。
42 2
|
4月前
|
中间件 API 网络架构
Django后端架构开发:从匿名用户API节流到REST自定义认证
Django后端架构开发:从匿名用户API节流到REST自定义认证
46 0