自定义MVC框架实现

目录

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

二、参数传递封装优化

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

四、框架配置可变


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

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

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

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

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

目的:使代码更加灵活

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

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

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

 DispatcherServlet:

package com.ycx.framework;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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 com.ycx.web.BookAction;

/**
 * 中央控制器:
 * 主要职能:接收浏览器请求,找到对应的处理人
 * @author 杨总
 *
 */
@WebServlet("*.action")
public class DispatcherServlet extends HttpServlet{

	/**
	 * 通过建模我们可以知道,最终ConfigModel对象会包含config.xml中的所有子控制器信息
	 * 同时为了解决中央控制器能够动态加载保存子控制器的信息,那么我们只需要引入configModel对象即可
	 */
	private ConfigModel configModel;
	
//	程序启动时,只会加载一次
	@Override
	public void init() throws ServletException {

		try {
			configModel=ConfigModelFactory.build();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

	@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 {
		//http:localhost:8080/mvc/book.action?methodName=list
		String uri=req.getRequestURI();
//		拿到/book,就是最后一个“/”到最后一个“.”为止
		uri=uri.substring(uri.lastIndexOf("/"),uri.lastIndexOf("."));

//		相比于上一种从map集合获取子控制器,当前需要获取config.xml中的全路径名,然后反射实例化
		ActionModel actionModel = configModel.pop(uri);
		if(actionModel==null) {
			throw new RuntimeException("action 配置错误");
		}
		String type = actionModel.getType();
//		type是Action子控制器的全路径名
		try {
			Action action= (Action) Class.forName(type).newInstance();
			action.execute(req, resp);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

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

 

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

 比如:

 原因:

所以:

 

二、参数传递封装优化

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

 

 解决参数冗余问题:

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

package com.ycx.web;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ycx.entity.Book;
import com.ycx.framework.Action;
import com.ycx.framework.ActionSupport;
import com.ycx.framework.ModelDriven;

public class BookAction extends ActionSupport {
	
		private Book book=new Book();

		private void load(HttpServletRequest req, HttpServletResponse resp) {
			System.out.println("在同一个servlet中调用 load 方法");
		}

		private void list(HttpServletRequest req, HttpServletResponse resp) {
			System.out.println("在同一个servlet中调用 list 方法");
		}

		private void edit(HttpServletRequest req, HttpServletResponse resp) {
			System.out.println("在同一个servlet中调用 edit 方法");
		}

		private void del(HttpServletRequest req, HttpServletResponse resp) {
			System.out.println("在同一个servlet中调用 del 方法");
		}

		private void add(HttpServletRequest req, HttpServletResponse resp) {
			String bid=req.getParameter("bid");
			String bname=req.getParameter("bname");
			String price=req.getParameter("price");
			Book book=new Book();
			book.setBid(Integer.valueOf(bid));
			book.setBname(bname);
			book.setPrice(Float.valueOf(price));
			bookDao.add(book);
			System.out.println("在同一个servlet中调用 add 方法");
		}

		

	}

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

package com.ycx.framework;

/**
 * 模型驱动接口,接收前台JSP传递的参数,并且封装到实体类中
 * @author 杨总
 *
 * @param <T>
 */
public interface ModelDriven<T> {
//	拿到将要封装的类实例   ModelDriven.getModel() ---> new Book();
	T getModel();
}


             DispatcherServlet 中央控制器类:

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


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

package com.ycx.web;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ycx.entity.Book;
import com.ycx.framework.Action;
import com.ycx.framework.ActionSupport;
import com.ycx.framework.ModelDriven;

public class BookAction extends ActionSupport implements ModelDriven<Book>{
	
		private Book book=new Book();

		private void load(HttpServletRequest req, HttpServletResponse resp) {
			System.out.println("在同一个servlet中调用 load 方法");
		}

		private void list(HttpServletRequest req, HttpServletResponse resp) {
			System.out.println("在同一个servlet中调用 list 方法");
		}

		private void edit(HttpServletRequest req, HttpServletResponse resp) {
			System.out.println("在同一个servlet中调用 edit 方法");
		}

		private void del(HttpServletRequest req, HttpServletResponse resp) {
			System.out.println("在同一个servlet中调用 del 方法");
		}

		private void add(HttpServletRequest req, HttpServletResponse resp) {
//			String bid=req.getParameter("bid");
//			String bname=req.getParameter("bname");
//			String price=req.getParameter("price");
			Book book=new Book();
//			book.setBid(Integer.valueOf(bid));
//			book.setBname(bname);
//			book.setPrice(Float.valueOf(price));
//			bookDao.add(book);
			System.out.println("在同一个servlet中调用 add 方法");
		}

		@Override
		public Book getModel() {
			return null;
		}

	}

 Debug运行效果如下:

 

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

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

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

解决跳转方式问题:

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


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

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

主界面代码如下:

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

config.xml :

<?xml version="1.0" encoding="UTF-8"?>
<config>

	<action path="/book" type="com.ycx.web.BookAction">
		<forward name="success" path="/demo2.jsp" redirect="false" />
		<forward name="failed" path="/demo3.jsp" redirect="true" />
	</action>
	
</config>

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

中央控制器(DispatcherServlet): 

String result = action.execute(req, resp);
			ForwardModel forwardModel = actionModel.pop(result);
//			if(forwardModel==null)
//				throw new RuntimeException("forward config error");
			String path = forwardModel.getPath();
//			拿到是否需要转发的配置
			boolean redirect = forwardModel.isRedirect();
			if(redirect)
				//${pageContext.request.contextPath}
				resp.sendRedirect(req.getServletContext().getContextPath()+path);
			else
				req.getRequestDispatcher(path).forward(req, resp);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}


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

package com.ycx.framework;

import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ActionSupport implements Action{

	@Override
	public String execute(HttpServletRequest req, HttpServletResponse resp) {
		String methodName = req.getParameter("methodName");
//		methodName可能是多种方法
//		前台传递什么方法就调用当前类的对应方法
		try {
			Method m=this.getClass()// BookServlet.Class
			.getDeclaredMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
			m.setAccessible(true);
//			调用当前类实例的 methodName 方法
			return (String) m.invoke(this, req,resp);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}


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

 

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

 

demo2界面:

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


demo3界面: 

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

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

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

 

 四、框架配置可变

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

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

 

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

@Override
	public void init() throws ServletException {
//		actions.put("/book", new BookAction());
//		actions.put("/order", new BookAction());
		try {
			//配置地址
//			getInitParameter的作用是拿到web.xml中的servlet信息配置的参数
			String configLocation = this.getInitParameter("configLocation");
			if(configLocation==null||"".equals(configLocation))
				configModel=ConfigModelFactory.build();
			else
				configModel=ConfigModelFactory.build(configLocation);	
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

web.xml :

<?xml version="1.0" encoding="UTF-8"?>
<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">
  <display-name>T280_mvc</display-name>
	<servlet>
		<servlet-name>mvc</servlet-name>
		<servlet-class>com.ycx.framework.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>configLocation</param-name>
			<param-value>/yangzong.xml</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
	<url-pattern>*.action</url-pattern>
	</servlet-mapping>
</web-app>

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酒醉猫(^・ェ・^)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值