自己写一个MVC框架(三)

  

   自从上一篇之后,隔了好久才写这篇真是不好意思。下面我把这个MVC框架的剩余的最后一部分分享给大家。

    MVC里面不仅需要action这样普通的控制器,还需要另外一种控制器:前端控制器 ActionServlet
    ActionServlet 继承了传统的servlet,负责从创建应用命令控制器RequestProcessor,和创建XML解析器XmlParser,它如同打仗时的先锋队,“所有的请求”首先被它获取拦截,通过它和已知的配置文件就可以把请求发给对应的action,因此它在MVC模型中扮演中央控制器的角色。(其实和struts框架中的actionservlet的功能几乎一样)代码如下:
   

public class ActionServlet extends HttpServlet {

	
	public void destroy() {
		
	}

	
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		this.doPost(request, response);
	}

	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		this.process(request, response);
	}

	/**
	 * 负责创建应用控制器RequestProcessor,并且缓存它
	 * @param request
	 * @param response
	 */
	private void process(HttpServletRequest request, HttpServletResponse response) {
		
		RequestProcessor processor = null;
		
		if (null == this.getServletContext().getAttribute("REQUERS_PROCESSOR")) {
			//创建
			processor = new RequestProcessor(this.getServletContext());
			
			this.getServletContext().setAttribute("REQUERS_PROCESSOR", processor);
			
		} else {
			processor = (RequestProcessor) this.getServletContext().getAttribute("REQUERS_PROCESSOR");
		}
		
		try {
			processor.processor(request, response);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	
	/**
	 * 在初始化的时候,解析xml返回ActionMappingConfig对象,缓存对象
	 */
	public void init() throws ServletException {
		
		String tmp = this.getServletContext().getRealPath("/");
		String path = tmp + this.getInitParameter("config");
		
		ActionMappingConfig config = XMLParser.getActionMappingConfig(path);
		this.getServletContext().setAttribute("config", config);
		
	}

}

初始化的时候,我们需要解析xml并返回之前谈到过的ActionMappingConfig,并将其缓存到公共区域中,以便之后的读取。当然要想使这个servlet起作用还是需要在web。xml里面进行配置的,由于需要一开始就加载配置文件,所以说还要加上<load-on-startup>0</load-on-startup>;

<servlet>
    <servlet-name>ActionServlet</servlet-name>
    <servlet-class>net.localer.mvc.servlet.ActionServlet</servlet-class>
    
  	<init-param>
  		<param-name>config</param-name>
  		<param-value>/WEB-INF/mvc.xml</param-value>
  	</init-param>
  	<load-on-startup>0</load-on-startup>
  </servlet>

上面涉及到RequestProcessor这个类,这个类在MVC小框架中充当命令应用控制器。                                                                                                             它负责创建和缓存Action对象,同时也根据请求path调用action对象,并创建ActionForm对象,将ActionForm传递到Action对象中。详细的RequestProcessor类的处理过程我总结如下:
1、获取请求路径。
2、根据请求路径查找缓存(可以用一个Hashtabel来当缓存)中是否有对应的action实例,如果没有将对应的action实例化出来。
3、若是刚实例的action则把Action对象缓存起来。
4.  根据actionconfig查找对应action的actionForm。
5.  反射设置参数到actionForm。
6.  传递ActionForm到Action,并调用execute方法。
7.  接受action的返回值。
8.  根据返回值决定重定向还是转发。代码如下:

public class RequestProcessor {
	
	
	private ServletContext application = null;
	
	private Map<String,Action> actionInstance = null;
	
	public RequestProcessor(ServletContext application) {
		this.application = application;
		actionInstance = new Hashtable<String, Action>();
	}


	/**

	 * @param request
	 * @param response
	 * @throws Exception
	 */
	public void processor(HttpServletRequest request, HttpServletResponse response) throws Exception {
		//1、获取请求路径
		String uri = request.getRequestURI();
		//2、去掉后缀的.do,比如请求的地址是http://127.0.0.1/mvc/index.do
		uri = uri.substring(0, uri.length() - 3);
		String path = application.getRealPath("/");
		String[] array = path.split("\\\\");
		uri = uri.substring(array[array.length - 1].length() + 1);
		
		
		//3、获取缓存ActionMappingConfig
		ActionMappingConfig mapping = (ActionMappingConfig)application.getAttribute("config");
		
		
		//4、把路径当成key获取对应的ActionConfig对象
		ActionConfig actionConfig = mapping.getActionConfig(uri);
		
		//5、判断acttionConfig对象是否存在,为空就是配置文件中没有这个路径的配置
		if (null == actionConfig) {
			throw new Exception("没有找到对应的action实例");
		}
		
		//从缓存获取action对象
		Action action = actionInstance.get(uri);
		
		//6、根据路径信息到缓存中查找Action对象
		if (null == action) {
			
			//7、反射出该ActionConfig对象对应的Action对象
			action = this.getActionInstance(actionConfig);
			
			//8、把新创建的action对象缓存
			actionInstance.put(uri, action);
		}
		
		
		//9、获取到action配置的name属性的值
		String name = actionConfig.getName();
		
		ActionForm form = null;
		
		if (name != null) {
			
			//10、根据ActionConfig的name属性找到对应的FormBeanConfig对象
			FormBeanConfig config = mapping.getFormBeanConfig(name);
			
			//11、获取对应ActionForm的实例
			form = this.getActionFormInstance(config);
			
			//12、封装界面上传递的参数到ActionForm里面
			this.setParamter(form, request);
		}
		
		
		//13、调用action的execute方法,传递form对象
		ActionForward forward = action.execute(form, request, response);
		
		//14、做转发或者重定向
		this.forward(forward, request, response);
	}
	
	/**
	 * 这个方法是决定重定向或者转发对应视图的
	 * @param forward
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	private void forward(ActionForward forward,HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException  {
		if (null == forward) {
			return;
		}
		
		if (forward.isForward()) {
			//转发
			request.getRequestDispatcher(forward.getUri()).forward(request, response);
			
		} else {
			//重定向
			response.sendRedirect(forward.getUri());
		}
		
	}
	
	/**
	 * 反射自动封装参数
	 * @param form
	 * @param request
	 */
	private void setParamter(ActionForm form, HttpServletRequest request)  {
		//1、获取界面上传递过来的所有的参数的名字
		Enumeration<String> enu = request.getParameterNames();
		
		String parameterName = null;
		Class c = form.getClass();
		Field[] fields = null;
		StringBuffer setMethodName = null;
		Method setMethod = null;
		
		Map<String,String> map = new HashMap<String,String>();
		
		//2、迭代所有名字,判断和form当中是否有名字一致的
		while (enu.hasMoreElements()) {
			
			setMethodName = new StringBuffer();
			parameterName = enu.nextElement();
			
			if (map.get(parameterName) != null) {
				continue;
			}
			
			//获取form所有的字段
			fields = c.getDeclaredFields();
			
			for (int i = 0; i < fields.length; i++) {
				
				//如果有一个参数名字和字段名字一模一样
				if (fields[i].getName().equals(parameterName)) {
					
					//拼凑出set方法
					setMethodName.append("set").append(parameterName.substring(0, 1).toUpperCase()).append(parameterName.substring(1));
					
					
					//获取对应的set方法
					try {
						setMethod = c.getMethod(setMethodName.toString(), new Class[]{fields[i].getType()});
						
						//判断form里面的字段是数组的还是String类型的
						if (fields[i].getType().getName().equals("[Ljava.lang.String;")) {
							map.put(parameterName, "");
							
							setMethod.invoke(form, new Object[]{request.getParameterValues(parameterName)});
						} else {
							setMethod.invoke(form, new Object[]{request.getParameter(parameterName)});
						}
						
					} catch (SecurityException e) {
						e.printStackTrace();
					} catch (NoSuchMethodException e) {
						e.printStackTrace();
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					} catch (InvocationTargetException e) {
						e.printStackTrace();
					}
					
				}
				
			}
			
		}
		
		
	}
	
	/**
	 * 反射从FormBeanConfig中获取ActionForm对象
	 * @param config
	 * @return
	 */
	private ActionForm getActionFormInstance(FormBeanConfig config) {
		ActionForm form = null;
		try {
			form = (ActionForm)Class.forName(config.getType()).newInstance();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		
		return form;
	}
	
	
	/**
	 * 反射出ActionConfig中Action对象
	 * @param config
	 * @return
	 */
	private Action getActionInstance(ActionConfig config) {
		Action action = null;
		try {
			
			action = (Action)Class.forName(config.getType()).newInstance();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		
		return action;
	}
	
	
}

上述代码的注释也把整个流程描述得十分清楚。有很多代码细节大家若不懂,百度一下就能知道,是一些很简单的小细节功能。

加上之前的类,一个简单的MVC框架就基本成型了。下面是简单的应用代码:

public class NewsAction extends Action {

	@Override
	public ActionForward execute(ActionForm form, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		NewsForm newsForm = (NewsForm)form;
		
		return new ActionForward("index.jsp",false);
	}

}

这个Action就继承了抽象类Action并对其中的execute方法进行重写。

public class NewsForm extends ActionForm{
	private String id;
	private String title;
	private String content;
	private String[] abc;
	
	
	
	public String[] getAbc() {
		return abc;
	}
	public void setAbc(String[] abc) {
		this.abc = abc;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	
	
}

这个form继承了actionform用来封装页面传来的参数。之前的mvc.xml配置如下:

<!-- form-beans标签只有一个 -->
	<form-beans>
		<!-- form-bean标签有多个 -->
		<form-bean name="newsForm" type="net.localer.news.form.NewsForm"/>
		
		<form-bean name="userForm" type="net.localer.news.form.UserForm"/>
		
	</form-beans>
	
	<!-- action-mappings标签只有一个 -->
	<action-mappings>
		<!-- 
			action元素:配置业务Action类
			
			path : 请求的路径
			
			type : 业务Action类的类路径
		
		 -->
		<!-- action标签有多个 -->
		<action path="/index" type="net.localer.news.action.NewsAction" name="newsForm"/>
		
		<action path="/user" type="net.localer.news.action.UserAction" name="userForm"/>

	</action-mappings>

项目运行起来之后,在页面输入http://127.0.0.1/项目名/index.do 请求就会被拦截并调用NewsAction中的execute方法。
(具体项目的具体设置不一样,struts框架原则上认为访问任何一个页面之前都需要通过一个控制器,之后再转发到对应的页面,因此我们用户在地址栏上输入的时候一般不会出现*.jsp这样的地址,而一般都是*.do或者*.action等等这样的地址)。

最后呢我们可以把之前的代码打成一个jar包(不包括NewsAction)。下次写一个新的web项目的时候我们就可以把这个jar包导入,自己建一个xml文件来写配置(和上面的配置文件一样),省去了写大量servlet配置的时间,这样的感觉就如同用一个框架,虽然这个框架很小,嘿嘿,总之算是实现了一个MVC框架的最最基本的功能。当然呢我这个框架还有很多很多的不足,其中我认为最大最大的不足呢是:
1.dispatcherAction里面的那个method参数不能自己配置,而是写死了,这样页面传递参数的时候会受到了限制。
2.没有像标准struts框架那样把转发重定向的路径 也配置到文件(如<result name=”hello”>/index.jsp</result>),这样有时候路劲变化的时候还是需要改变源代码,违背了开闭原则。
这个框架和标准的框架没得比,但是也把MVC的基本思想体现出来,在下分享出来后,希望大家多多交流,我也希望通过这个来学习更多的架构知识,当然本人的实力实在有限,文字表达功底也不大行,所有的不足,望大家包容。
                             那这样吧,嘿嘿!

    原文作者:lixinstudio
    原文地址: https://blog.csdn.net/lixinstudio/article/details/7039949
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞