自定义MVC原理

目录

1. 什么是MVC

2. 自定义MVC工作原理图

3. 自定义mvc实现

3.1 创建web工程

 3.2 中央处理器

3.3 Action接口定义

3.4 实现子控制器

3.5 完善中央控制器

3.5.1 请求分发功能

3.5.2 使用配置文件配置action

3.5.3 请求参数处理

3.6 完善Action

4. 其他公用组件的集成

5. 打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实现

3.1 创建web工程

创建一个web工程,需要加入必要的依赖。

 3.2 中央处理器

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

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

}

3.3 Action接口定义

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

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

}

3.4 实现子控制器

为方便调试,实现两个子控制器

public class BookAction implements Action {

	@Override
	public String exeute(HttpServletRequest request, HttpServletResponse response) {
		
		return "bookList";
	}

}
public class StudentAction implements Action {

	@Override
	public String exeute(HttpServletRequest request, HttpServletResponse response) {
		// TODO Auto-generated method stub
		return "students";
	}

}

3.5 完善中央控制器

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

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

3.5.1 请求分发功能

为了在中央控制器中完成请求的分发,需要在中央控制器中维护所有子控制器的实例,并且能够依据请求路径,将请求转发给与其关联的子控制器。

/**
 * 中央控制器,负责接收所有的请求并分别给控制器具体处理
 * @author Administrator
 */
@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
	public void doGet(HttpServletRequest request, HttpServletResponse response) {
		doPost(request, response);
	}
	
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) {
		String servletPath = request.getServletPath();
		String path = servletPath.split("\\.")[0];
		
		Action action = actionMap.get(path);
		
		String rpath = action.exeute(request, response);
		
		System.out.println(rpath);
	}

}

3.5.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="org.lisen.mvc.action.StudentAction">
		<forward name="students" path="/students/studentList.jsp" redirect="false"/>
	</action>
</config>

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

@WebServlet("*.action")
public class ActionDispatchServlet extends HttpServlet {
	
	//用于保存path与action子控制器的映射
	//public static Map<String, Action> actionMap = new HashMap<>();
	
	private static ConfigModel configModel;
	
	static {
		//actionMap.put("/students", new StudentAction());
		//actionMap.put("/books", new BookAction());
		configModel  = ConfigModelFactory.getConfigModel();
	}
	
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) 
			throws IOException, ServletException {
		doPost(request, response);
	}
	
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) 
			throws IOException, ServletException {
		
		String servletPath = request.getServletPath();
		String path = servletPath.split("\\.")[0];
		
		Action action = getActionByPath(path);
		String name = action.exeute(request, response);
		
		ForwardModel forwardModel = getForwardModel(path, name);
		if (forwardModel.isRedirect()) {
			response.sendRedirect(request.getContextPath() + "/" + forwardModel.getPath());
		} else {
			request.getRequestDispatcher(forwardModel.getPath()).forward(request, response);
		}
	}
	
	//通过请求路径获取对应的action实例
	private Action getActionByPath(final String path) {
		ActionModel action = configModel.find(path);
		try {
			Class<?> clazz = Class.forName(action.getType());
			return (Action)clazz.newInstance();
		} catch (Exception e) {
			throw new RuntimeException("创建Action实例异常"+e.getMessage(), e);
		}
	}
	
	public ForwardModel getForwardModel(String path, String name) {
		return configModel.find(path).find(name);
	}

}

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

3.5.3 请求参数处理

1) 定义接口

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

}

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

@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) 
		throws IOException, ServletException {
	
	......
	Action action = getActionByPath(path);
	
	//处理请求参数
	if(action instanceof ModelDrive) {
		Object model = ((ModelDrive) action).getModel();
		if(model != null) {
			try {
				BeanUtils.populate(model, request.getParameterMap());
			} catch (Exception e) {
				throw new RuntimeException("在中央处理器中处理请求参数时发生异常", e);
			}
		}
	}
	
	String name = action.exeute(request, response);
	......
}

3) 处理请求参数中的null及空字符串转换问题
为了更方面的处理请求参数中的null及空字符串,转换为数值型数据的问题,加入一个监听器,在应用启动时注册转换器。

/**
 * 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);
	}

}

3.6 完善Action

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

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

1) 构建抽象类

public abstract class AbstractDispatchAction implements Action {

	@Override
	public String exeute(HttpServletRequest request, HttpServletResponse response) {
		
		String methodName = request.getParameter("methodName");
		Class<? extends AbstractDispatchAction> clazz = this.getClass();
		try {
			Method method = clazz.getDeclaredMethod(
					methodName, 
					HttpServletRequest.class,
					HttpServletResponse.class);
			return (String)method.invoke(this, request,response);
		} catch (Exception e) {
			throw new RuntimeException("在调用Action中的["+methodName+"]方法是异常", e);
		} 
	}

}

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

public class StudentAction extends AbstractDispatchAction implements ModelDrive {
	
	private Student student = new Student();
	
	@Override
	public Object getModel() {
		return student;
	}

	/*@Override
	public String exeute(HttpServletRequest request, HttpServletResponse response) {		
		System.out.println("StudentAction = " + student);	
		return "students";
	}*/
	
	public String getStudents(HttpServletRequest request, HttpServletResponse response) {	
		System.out.println("getStudents");
		System.out.println("StudentAction = " + student);
		return "students";
	}
	
	public String addStudent(HttpServletRequest request, HttpServletResponse response) {
		System.out.println("addStudent");
		System.out.println("add student = " + student);
		return "students";
	}

}

3)发送请求,示例
http://localhost:8080/mvc/studentAction.action?methodName=addStudent&sid=100&age=23&addr=aabbcc

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

4. 其他公用组件的集成

可以将通用分页,字符编码过滤器等组件一集成到mvc框架中便于复用。

5. 打jar包

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值