自定义mvc原理和框架实现

2023-11-06

目录

1.什么是MVC

2.自定义MVC工作原理图

3.自定义mvc的简单实现

1.中央控制器

2.Action接口定义

3.实现子控制器

4.完善中央控制器

1.请求分发功能

2.使用配置文件配置action

3.请求参数处理

4.完善Action

4.打包jar包


1.什么是MVC

      MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写。 它是一种软件设计典范。

      用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

提高了程序的可维护性、可移植性、可扩展性与可重用性,降低了程序的开发难度。它主要分模型、视图、控制器三层。

  • 模型(model): 它是应用程序的主体部分,主要包括业务逻辑模块(web项目中的dao类)和数据模块(pojo类)。pojo一般可以叫做实体域模型,dao和service称为过程域模型。
  • 视图(view): 用户与之交互的界面、在web中视图一般由jsp,html组成,其他的还包括android,ios等等。
  • 控制器(controller): 接收来自界面的请求 并交给模型进行处理 在这个过程中控制器不做任何处理只是起到了一个连接的做用。

不足的地方:

  • 增加系统结构和实现的复杂性。对于简单的界面,严格遵守MVC,需要使模型、视图与控制器分离,增加系统复杂性
  • 视图和控制器之间的关系太过紧密了

2.自定义MVC工作原理图

 

核心组件说明:

  • 中央控制器(ActionServlet): 复杂接收所有的请求,并分别给控制器具体处理。
  • 自控制器(Action):负责处理中央处理器分配的请求
  • 视图(view): jsp页面,负责显示
  • 模型(Model): 负责业务处理逻辑

3.自定义mvc的简单实现

1.中央控制器

通过servlet来实现一个中央控制器,负责所有请求的接收。(后续中央控制器在再将请求转发给各个子控制器,此处可以先把请求接进来,转发功能后面再加)

/**
 * 中央控制器,负责接收所有的请求并分别给控制器具体处理
 * @author Administrator
 */
@WebServlet("*.action")
public class ActionDispatchServlet extends HttpServlet {
	
	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse resp) {
		doPost(req, resp);
	}
	
	@Override
	public void doPost(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("dopost ..... ");
	}

}

2.Action接口定义

Action接口定义了每个子控制器需要遵循的行为,使得所有的子控制器都有一个同一的抽象类型,所以我们可以在中央控制器中使用Action接口类型来引用所有的子控制器。这样就为用户扩展自定义的子控制器提供了条件。

/**
 * 每个子控制器必须实现该接口,负责处理中央处理器分配的请求
 * @author Administrator
 */
public interface Action {
	
	/**
	 * 处理请求
	 * @param req  请求
	 * @param resp 响应
	 * @return String 返回转发或重定向的jsp页面名称
	 */
	String exeute(HttpServletRequest req, HttpServletResponse resp);

}

3.实现子控制器

为了方便调试,我这里实现了两个子控制器(= ̄ω ̄=)

public class BookAction implements Action {

	@Override
	public String getBooks(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("BookAction...");
		return "books";
	}

}
public class StudentAction implements Action{

	@Override
	public String execute(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("StudentAction");
		return "students";
	}

4.完善中央控制器

为了便于理解,我们可以分步骤的,循序渐进的完善中央控制器:

  • 编写简单的请求分发实现功能
  • 实现通过配置文件来配置子控制器的功能
  • 完善请求参数处理功能

1.请求分发功能

@WebServlet("*.action")
public class ActionDispatchServlet extends HttpServlet{

	//用于保存path与action子控制器的映射
	public static Map<String, Action> actionMap = new HashMap<>();
	
	static {
		actionMap.put("/studentAction", new StudentAction());
		actionMap.put("/bookAction", new BookAction());
	}
	
	
	@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 {
		
		String spath = req.getServletPath();
		String path = spath.split("\\.")[0];
		
		Action action = getActionByPath(path);
		
		String name = action.execute(req, resp);
		
		System.out.println(name);
		
	}
	

	
}

2.使用配置文件配置action

在上面的示例中,在中央控制器中直接创建action子控制器,如果新增一个子控制器需要在中央控制器中添加,这样并不实用。 为了增加灵活性,可以将action转移到配置文件中配置,中央控制器通过配置来初始化action子控制器。

(1)此时需要将config.xml文件的解析建模项目的功能集成进来。
(ConfigModel,ActionModel,ForwardModel,ConfigModelFactory)

(2)在项目的src目录下加入如下配置文件(config.xml)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE config[
	<!ELEMENT config (action*)>
	<!ELEMENT action (forward*)>
	<!ELEMENT forward EMPTY>
	<!ATTLIST action
	  path CDATA #REQUIRED
	  type CDATA #REQUIRED
	>
	<!ATTLIST forward
	  name CDATA #REQUIRED
	  path CDATA #REQUIRED
	  redirect (true|false) "false"
	>
]>
<config>
	<action path="/studentAction" type="com.zking.action.StudentAction">
		<forward name="students" path="/students.jsp" redirect="false"/>
	</action>
	<action path="/bookAction" type="com.zking.action.BookAction">
		<forward name="books" path="/books.jsp" redirect="false"/>
	</action>
</config>

(3) 完善中央处理器,通过配置文件来获取子控制器配置

@WebServlet("*.action")
public class ActionDispatchServlet extends HttpServlet{

	private static ConfigModel configModel = ConfigModelFactory.getConfig();
	
	
	@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 {
		
		String spath = req.getServletPath();
		String path = spath.split("\\.")[0];
		
		Action action = getActionByPath(path);
		
		
		//请求转发
		String name = action.execute(req, resp);
		
		ForwardModel forward = configModel.find(path).find(name);
		if(forward.isRedirect()) {
			resp.sendRedirect(req.getContextPath()+forward.getPath());
		} else {
			req.getRequestDispatcher(forward.getPath()).forward(req, resp);
		}	
	}
	
	/**
	 * 通过path获取Action的实例
	 * @param path
	 * @return
	 */
	private Action getActionByPath(String path) {
		ActionModel am = configModel.find(path);
		 
		try {
			Class<? extends Action> ac = (Class<? extends Action>)Class.forName(am.getType());
			return ac.newInstance();
		} catch (ClassNotFoundException 
				| InstantiationException 
				| IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	
}

注: 本例的实现中Action子控制器是多例模式的,及每个请求对应一个Action实例

3.请求参数处理

(1)定义接口

/**
 * 对于需要处理请求参数的Action可以通过实现该接口获取请求参数的
 * 处理能力,中央控制器将会使用该接口来获取Model对象,并统一处理
 * 参数
 * @author Administrator
 */
public interface ModelDrive {
	
	Object getModel();

}

(2)在中央处理器中加入请求参数的处理能力

@WebServlet("*.action")
public class ActionDispatchServlet extends HttpServlet{

	private static ConfigModel configModel = ConfigModelFactory.getConfig();
	
	@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 {
		
		String spath = req.getServletPath();
		String path = spath.split("\\.")[0];
		
		Action action = getActionByPath(path);
		
		//设置查询参数
		if(action instanceof ModelDrive) {
			ModelDrive m = (ModelDrive)action;
			Object bean = m.getModel();
		
			try {
				BeanUtils.populate(bean,req.getParameterMap());
			} catch (IllegalAccessException | InvocationTargetException e) {
				throw new ActionDispatchSetParameterException("在中央控制器中处理参数的时候发生异常");
			}
			
		}
		
		//请求转发
		String name = action.execute(req, resp);
		
		ForwardModel forward = configModel.find(path).find(name);
		if(forward.isRedirect()) {
			resp.sendRedirect(req.getContextPath()+forward.getPath());
		} else {
			req.getRequestDispatcher(forward.getPath()).forward(req, resp);
		}	
	}
	
	/**
	 * 通过path获取Action的实例
	 * @param path
	 * @return
	 */
	private Action getActionByPath(String path) {
		ActionModel am = configModel.find(path);
		 
		try {
			Class<? extends Action> ac = (Class<? extends Action>)Class.forName(am.getType());
			return ac.newInstance();
		} catch (ClassNotFoundException 
				| InstantiationException 
				| IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	
}

(3) 处理请求参数中的null及空字符串转换问题

方法一:在中央控制器中加入处理请求参数中的null及空字符串转换的代码

@WebServlet("*.action")
public class ActionDispatchServlet extends HttpServlet{

	private static ConfigModel .....
	
	/**
	 * 当Servlet 容器启动Web应用时调用该方法。
	 * 在调用完该方法之后,容器再对Filter 初始化,
	 * 并且对那些在Web应用启动时就需要被初始化的Servlet进行初始化。
	 */
	static {
		ConvertUtils.register(new IntegerConverter(null), Integer.class);
		ConvertUtils.register(new FloatConverter(null), Float.class);
		ConvertUtils.register(new DoubleConverter(null), Double.class);
		ConvertUtils.register(new LongConverter(null), Long.class);
		ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);		
	}
	
	@Override
	protected void doGet......
	
	
	
}

注:省略的代码在前几个中央控制器中有写,这里我就不重复写了。

方法二:加入一个监听器,在应用启动时注册转换器。

/**
 * ServletContextListener接口为Servlet API中的接口,用于监听ServletContext对象的生命周期。
 * 当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent事件,该事件由
 * ServletContextListener来处理。
 * @author Administrator
 */
@WebListener
public class BeanUtilsListener implements ServletContextListener{

	/**
	 * 当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,
	 * 容器会先销毁所有的Servlet和Filter 过滤器。
	 */
	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		// TODO Auto-generated method stub
	}

	/**
	 * 当Servlet 容器启动Web应用时调用该方法。
	 * 在调用完该方法之后,容器再对Filter 初始化,
	 * 并且对那些在Web应用启动时就需要被初始化的Servlet进行初始化。
	 */
	@Override
	public void contextInitialized(ServletContextEvent arg0) {
		ConvertUtils.register(new IntegerConverter(null), Integer.class);
		ConvertUtils.register(new FloatConverter(null), Float.class);
		ConvertUtils.register(new DoubleConverter(null), Double.class);
		ConvertUtils.register(new LongConverter(null), Long.class);
		ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);
	}

	
}

4.完善Action

每个Action只能有一个execute方法,如果处理一个模块的增删改查则需要单独编写多个Action,这样会比较麻烦。如果在一个Action实例中可以处理多个请求方法,则框架会更加灵活。

  • 规定请求参数中必须包含一个“methodName”参数,用于指定处理请求的Action中的方法
  • 构建一个抽象类,该类实现Action子控制器接口,通过反射机制调用其子类中的方法,方法名有请求参数“methodName”指定。
  • 需要开发的Action子控制器,集成上一步构建的抽象类,编写的用于处理请求的方法名要与请求参数“methodName”指定的方法名匹配,同时需要HttpServletRequest和HttpServletResponse两个参数(保留该参数主要为了方面对请求的处理)

(1) 构建抽象类

public abstract class AbstractAction implements Action{

	//获取methodName所指定的方法,然后通过反射机制方法调用
	@Override
	public String execute(HttpServletRequest req, HttpServletResponse resp) {
		String methodName = req.getParameter("methodName");
		Class<? extends Action> clazz = (Class<? extends Action>)this.getClass();
		try {
			Method method = clazz.getDeclaredMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
			return (String)method.invoke(this, req,resp);
		} catch (NoSuchMethodException 
				| SecurityException 
				| IllegalAccessException 
				| IllegalArgumentException 
				| InvocationTargetException e) {
			throw new RuntimeException(e);
		}
		
	}

	
}

(2) 自定义的Action子控制器示例

public class BookAction extends AbstractAction implements ModelDrive{

	private Book book = new Book();
	
	@Override
	public Object getModel() {
		return book;
	}
	
	public String getBooks(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("BookAction getBooks..."+book);
		return "books";
	}

	
}

注意:在请求中需要添加一个methodName的固定参数,该参数指定了需要调用的Action中的方法的名称。

示例:http://localhost:8080/项目名称/bookAction.action?methodName=getBooks&id=1000

getBooks是子控制器的方法名

4.打包jar包

将自定义mvc框架打成jar包,以便于在其他项目中使用。

项目 --(右击)-->Export

选择JAR 文件 

 

 

 这篇文章就到这里结束了(づ ̄ 3 ̄)づ,拜拜。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自定义mvc原理和框架实现 的相关文章

随机推荐

  • 中台战略-第九章、数字营销的技术架构与路径

    文章目录 第九章 数字营销的技术架构与路径 9 1基于中台架构 构建立体数字营销云 9 2 数字营销技术架构和设计理念 9 2 1 数字营销云应用介绍 1 全域会员i CDP 2 智能营销i Marketing 3 全渠道销售i Comme
  • 在线沙箱网站 在线恶意文件监测网站 病毒在线监测网站 apk分析在线网站

    沙箱 https www joesandbox com windows 沙箱 VirSCAN https www virscan org language de 只能传20M以内的文件 VirusTotal https www virust
  • 【注释模板】IDEA中JAVA类、方法注释模板教程

    文章目录 TOC 1 引言 2 JAVA创建类时注释模板配置 2 1 打开IDEA 依次点击File gt Setting 2 2 在Settings界面中依次点击Editor gt File and Code Templates 并在Fi
  • 关于示波器产生奇特波形的解释

    转发 https blog csdn net y511374875 article details 80583585
  • 让机器“看山是山”:脑启发的视觉计算

    编者按 人生之三境界的第一层 看山是山 看水是水 本质上展示了人 看见 的过程 以及思绪与理解在这一过程中所起的作用 看见 对于人类而言 似乎是一个很简单自然的事情 其实则不然 从地球上第一个长出眼睛的生物三叶虫 走到今天的人类视觉 经历了
  • office365 无法登录_office365、office2019微软账号无法登录如何解决?

    我相信很多人肯定被这个问题折磨得头大 因为微软服务器在国外的原因 所以部分设备很难登入 但是OneNote Office365 Ofice等软件如果是绑定了微软账号的 需要登入微软账号才可以激活和保存数据 日常帮助很多订阅客户处理过这个问题
  • 【数据结构】循环队列的实现(附带详细注释)

    前言 数据结构系列首页 是数据结构系列文章的首页 其中会逐步更新各种数据结构的实现 有兴趣的选手可以一看 首页中不仅有各种数据结构的实现 还有学习数据结构必备的基础知识 如果有选手觉得自己的基础不太牢固 可以先将搞定基础知识 之后再攻克数据
  • 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 -- 递归

    0 题目描述 leetcode原题链接 剑指 Offer 68 I 二叉搜索树的最近公共祖先 1 递归解法 终止条件 当 root 为空时 返回 None 当 p q 都在 root 的右子树中 则开启递归 root right 并返回 否
  • 企业大数据CDH集群安全----sentry

    Sentry安装 1 cm中选择添加服务 选择sentry 2 选择要安装sentry的主机 3 配置数据库 HDFS配置 开启启动访问控制列表 选中打勾 开启检查HDFS权限服务 开启sentry服务 hive配置 修改hive配置参数
  • 获取IronSource的广告源Pod和Maven版本

    接入IronSource库后 现在可以选择Maven或者Pod的形式导入相关广告源 Ironsource官网上有一个入口 可以方便的获取接入广告源的Maven和Pod Android https developers ironsrc com
  • Object.keys()、Object.values()、Object.entries()的用法

    一 Object keys obj 参数 要返回其枚举自身属性的对象 返回值 一个表示给定对象的所有可枚举属性的字符串数组 处理对象 返回可枚举的属性数组 let person name 张三 age 25 address 深圳 getNa
  • java参数校验常见注解介绍

    一 NotEmpty NotBlank NotNull区别介绍 NotEmpty 常用于集合 字符串等 不能为空 且长度必须大于0 NotBlank 用于字符串上 不能为空 且长度必须大于0 NotNull 字面意思 一般用于基本类型 不为
  • 聊聊编程是什么

    前言 前言不看没关系 不影响 半夜睡不着 想写点啥 浅聊下我理解的编程的 我认为编程就是解决问题 就像互联网是依附于实体业 是处理解决实际问题的 刚学编程的时候总是很恐慌的 天赋不够 我这么认为的原因 一是当时流行一种说法叫不是热爱编程的是
  • Mongodb数据库的安装部署及基本使用

    Mongodb数据库的安装部署及基本使用 一 Mongodb数据库介绍 1 Mongodb简介 2 Mongodb适用场景 3 MongoDB特性 二 检查本地系统环境 1 检查系统版本 2 检查yum仓库 三 Mongodb的安装 1 配
  • 【python开发】1. __init__.py与导包

    python开发 开始拿着github上的python代码狂啃时 发现很多知道干嘛又不知道为啥这样的代码 开始疯狂补漏 package 导包 用处1 导入包 比如这样的架构 package1 subPack1 init py module
  • 爬虫豆瓣top250

    爬虫豆瓣top250 前言 一 爬虫是什么 二 爬取豆瓣的原因 三 爬虫项目步骤 1 准备工具 2 学习python的相关知识 3 爬虫过程讲析 四 成果展示 五 代码展示 前言 随着网络的迅速发展 万维网成为大量信息的载体 如何有效地提取
  • 深度学习理论_卷积神经网络

    1 要点 激活函数一般用于卷积层和全连接层之后 激活函数是深度网络非线性的主要来源 常见的激活函数Sigmoid 双曲正切 ReLU 生物启发 克服了梯度消失问题 PReLU alpha可学习 ELU和maxout 其中PReLU和ELU都
  • MQClientException: CODE: 208  DESC: query message by key finished, but no message.

    2019 05 15 10 19 31 401 INFO closeChannel close the connection to remote address 127 0 0 1 10911 result true 2019 05 15
  • lua的for循环

    lua的三种for循环介绍 本文的lua代码编辑于luaforwindows 1 数值for循环 如图 举例如下 2 ipairs迭代器 举例如下 说明 ipairs按照索引值顺序 打印出了table中有索引值的数据 没有索引值的不管 3
  • 自定义mvc原理和框架实现

    目录 1 什么是MVC 2 自定义MVC工作原理图 3 自定义mvc的简单实现 1 中央控制器 2 Action接口定义 3 实现子控制器 4 完善中央控制器 1 请求分发功能 2 使用配置文件配置action 3 请求参数处理 4 完善A