自定义MVC架构【中】

目录​​​​​​​

一、前言

二、完善MVC架构

1.问题分析

2.解决子控制器初始化

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

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

4.总结


一、前言

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

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

猜你喜欢

转载自blog.csdn.net/weixin_74318097/article/details/131489086