首页 > 代码库 > 【SSH进阶之路】一步步重构MVC实现Struts框架——彻底去掉逻辑判断(五)

【SSH进阶之路】一步步重构MVC实现Struts框架——彻底去掉逻辑判断(五)

目录:

      【SSH进阶之路】Struts基本原理 + 实现简单登录(二)

      【SSH进阶之路】一步步重构MVC实现Struts框架——从一个简单MVC开始(三)

      【SSH进阶之路】一步步重构MVC实现Struts框架——封装业务逻辑和跳转路径(四)

      【SSH进阶之路】一步步重构MVC实现Struts框架——彻底去掉逻辑判断(五)

      【SSH进阶之路】一步步重构MVC实现Struts框架——完善转向页面,大功告成(六)

 

      Struts的第二篇博客【SSH进阶之路】Struts基本原理 + 实现简单登录(二),我们介绍了MVC模型和Struts的基本理论,对比学习了他们之间的联系和区别。从第三篇博客【SSH进阶之路】一步步重构MVC实现Struts框架——从一个简单MVC开始(三)开始,我们实现了一个简单MVC模型,提出了三个重构的问题。

       上篇博客【SSH进阶之路】一步步重构MVC实现Struts框架——封装业务逻辑和跳转路径(四),我们解决了第一个问题:封装业务逻辑和跳转路径。这篇博客我们解决第二个问题:彻底去掉Servlet中的逻辑判断。


我们先回顾一下上篇博客中的TestServlet的代码:

package com.liang.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.liang.action.Action;
import com.liang.action.AddUserAction;
import com.liang.action.DelUserAction;
import com.liang.action.ModifyUserAction;
import com.liang.action.QueryUserAction;
/**
 * 使用servlet做相关的控制,转向多个(V)视图
 * @author liang
 *
 */
public class TestServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//取得访问的URI
		String reqeuestURI = request.getRequestURI();
		System.out.println(reqeuestURI);
		//截取URI,获得路径
		String path = reqeuestURI.substring(reqeuestURI.indexOf("/",1), reqeuestURI.indexOf("."));
		System.out.println(path);
		
		Action action = null;
		//等于添加,调用AddUserAction
		if ("/servlet/addUser".equals(path)) {
			action = new AddUserAction();
		//等于删除,调用DelUserAction
		}else if ("/servlet/delUser".equals(path)) {
			action = new DelUserAction();
		//等于修改,调用ModifyUserAction
		}else if ("/servlet/modifyUser".equals(path)) {
			action = new ModifyUserAction();
		//等于查询,调用QueryUserAction
		}else if ("/servlet/queryUser".equals(path)) {
			action = new QueryUserAction();
		}else {
			throw new RuntimeException("请求失败");
		}
		String forward = null;
		//返回不同的转向页面
		try {
			forward = action.execute(request, response);
		} catch (Exception e) {
			e.printStackTrace();
		}
		//根据路径,统一完成转向
		request.getRequestDispatcher(forward).forward(request, response);
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request,response);
	}

}
 

      我们来分析一下上面TestServlet中的代码,我们发现了多个逻辑判断IF..ELSE,系统不灵活,需求改变时,违背了开发封闭原则,不能满足需求。我们知道我们配置连接数据库信息时,使数据库更加灵活,使用了配置文件。

      下面我们也打算把所有的字符串和业务逻辑的实现类都配置到配置文件中,最后通过实现类的方法来返回跳转路径,最终跳转到相应的页面。 需求改变时,修改配置文件即可。

      所以配置文件不仅要配上Servlet中出现的字符串,还要配置相应的Action接口的实现类(我们可以利用反射来动态的实例化该类的对象,进而使用多态的机制动态这个类的所有属性和方法),另外返回跳转路径字符串。其实此时和Struts的配置文件的道理是一致,我们也只需要自己实现一个配置文件。


web.xml,我们将struts-config.xml配置成tomacat启动时就初始化

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  <!-- 配置TestServlet类 -->
  <servlet>
		<servlet-name>TestServlet</servlet-name>
		<!-- 配置成自己写的TestServlet的路径 -->
		<servlet-class>com.liang.servlet.TestServlet</servlet-class>
		<!-- tomacat启动时,初始化struts-config.xml的配置 -->
		<init-param>
			<param-name>config</param-name>
			<param-value>/WEB-INF/struts-config.xml</param-value>
		</init-param>
		<load-on-startup>0</load-on-startup>
	</servlet>

  <!-- 怎么访问与jsp的action的路径一致 -->
  <servlet-mapping>
  	<servlet-name>TestServlet</servlet-name>
  	<!-- 只要是.do请求都会到达servlet -->
  	<url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
</web-app>

com.liang.servlet.TestServlet,就是我们的核心Servlet。


struts-config.xml,将path路径和Action的实现类,配置成map结构

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

<action-config>
	<action-mappings>
		<!--根据不同的path路径,访问各自的Action  -->
		<action path="/servlet/delUser" type="com.liang.action.DelUserAction"></action>
		<action path="/servlet/addUser" type="com.liang.action.AddUserAction"></action>
		<action path="/servlet/modifyUser" type="com.liang.action.ModifyUserAction"></action>
		<action path="/servlet/queryUser" type="com.liang.action.QueryUserAction"></action>
	</action-mappings>
</action-config>

      下面我们只需要读取配置文件,实现相应的功能。读取xml,非常流行的一种方式就是使用dom4j,我们曾经也实现过了dom4j读取xml的例子,但是使用dom4j读取配置文件之后,我们需要将信息放置到哪,毋庸置疑,我们需要一个map结构的类,用于存放路径信息和action实现类(type)的信息,里面最少需要两个参数,如下:

 

ActionMapping

package com.liang.action;

public class ActionMapping {

	//路径信息
	private String path;
	//action实现类信息
	private String type;
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getPath() {
		return path;
	}
	public void setPath(String path) {
		this.path = path;
	}

}

      配置信息,我们已经放到了一个map中,此时一个请求过来,我们需要跟上面TestServlet中的IF语句一样,根据截取的URL请求,到Map中取得本次请求对应的Action拿出来,但是Action有多个, 我们还需要一个map结构,用来区分此时的URL请求对应哪一个Action。所以此时我们还需要一个存储Action的map结构。(如果没有看懂,没事,下面的代码中有注释,可以帮助你)

 

Mappings

package com.liang.action;

import java.util.HashMap;
import java.util.Map;

public class Mappings {
	public static Map actions = new HashMap();  
}

此时,我们的准备工作已经做完了,我们需要使用dom4j读取配置文件,存储到相应的map结构中。


ConfigInit

struts-config.xml解析器。把解析后的结点保存到ActionMapping里,MappingAction是我们上面封装的一个类。如何解析看代码:

package com.liang.servlet;

import java.io.File;
import java.util.Iterator;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.liang.action.*;

public class ConfigInit {

	public static void init(String config) {
		// 创建saxReader对象
		SAXReader reader = new SAXReader();
		File f = new File(config);
		try {
			// 通过read方法读取xml文件, 转换成Document对象
			Document doc = reader.read(f);
			// 得到配置文件的根结点
			Element root = doc.getRootElement();
			Element actionmappings = (Element) root.element("action-mappings");
			// 解析action结点的所有参数
			for (Iterator j = actionmappings.elementIterator("action"); j
					.hasNext();) {
				Element am = (Element) j.next();
				ActionMapping actionMapping = new ActionMapping();

				// 设置actionMapping的path和type
				actionMapping.setPath(am.attributeValue("path"));
				actionMapping.setType(am.attributeValue("type"));
				/*
				 * 下面Mappings.actions的存储结构相当于将配置文件struts-config.xml与映射一一对应
				 * map.put("/servlet/delUser", actionMapping);
				 * map.put("/servlet/addUser", actionMapping);
				 * map.put("/servlet/modifyUser", actionMapping);
				 * map.put("/servlet/queryUser", actionMapping);
				 */
				Mappings.actions.put((String) am.attributeValue("path"),
						actionMapping);
				
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


最后,万事具备就差东风了,我们看一下最关键的类TestServlet。

      其实它就是普通的Servlet。通过上面web.xml的映射,只要是.do的访问都会先能过这个Servlet过滤到想要访问的Action里。因为这个Servlet的load-on-startup设置为0,所以Tomcat启动时,会执行init方法。ConfigInit类就是我的struts-config.xml解析器。

TestServlet

package com.liang.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.liang.action.Action;
import com.liang.action.ActionMapping;
import com.liang.action.Mappings;

/**
 * 使用servlet做相关的控制,转向多个(V)视图
 * @author liang
 *
 */
public class TestServlet extends HttpServlet {

	//需要读取的文件名
	protected static String config = "/WEB-INF/struts-config.xml";

	public void init() throws ServletException {
		//获得文件的路径
		//initialize();
		//根据web.xml中映射的目录获得文件在对应服务器中的真实路径
		config = getServletContext().getRealPath("/")+ getInitParameter("config");
		//解析struts-config.xml配置文件
		ConfigInit.init(config);
	}

	//根据web.xml中映射的目录获得文件在对应服务器中的真实路径
//	private void initialize() {
//		try {
//			config = getServletContext().getRealPath("/")
//					+ getInitParameter("config");
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//	}
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		//取得访问的URI
		String reqeuestURI = request.getRequestURI();
		//截取URI,获得路径
		String path = reqeuestURI.substring(reqeuestURI.indexOf("/",1), reqeuestURI.indexOf("."));
		
		Mappings mapings = new Mappings();
		// 根据截取的URL请求,到Map中取得本次请求对应的Action类
		ActionMapping actionMapping = (ActionMapping)mapings.actions.get(path);  
		//取得本请求对应的Action类的完整路径
		String type = actionMapping.getType(); //com.liang.action.DelUserAction
		//采用反射,动态实例化Action
		try {
			Action action = (Action)Class.forName(type).newInstance();
			// 采用多态的机制,动态调用Action中的execute方法,返回转向路径
			String forward = action.execute(request, response);
			
			//根据转向路径完成转向
			request.getRequestDispatcher(forward).forward(request, response);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		   
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request,response);
	}

}


Action实现类的代码,我只贴一个添加用户的,其他类都没有改变,代码就不再重复了。

 

AddUserAction

package com.liang.action;

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

import com.liang.servlet.UserManager;

public class AddUserAction implements Action {

	@Override
	public String execute(HttpServletRequest req, HttpServletResponse resp)
			throws Exception {
		//获取参数
		String username = req.getParameter("username");
		
		UserManager userManager = new UserManager();
		//调用业务逻辑
		userManager.add(username);
		//返回跳转页面
		return "/add_success.jsp";//转向路径可以通过配置文件读取
	}
}

      现在我们发现TestServlet中所有的IF...ELSE语句都消失了,而且没有了业务逻辑的实现类,更加面向接口开发,此时我们的系统已经非常灵活了,但是我们为了更好的实现Struts框架的雏形,我们提出了第三个问题,如上面代码中显示的一样,所有的转向页面都写死了,我们需要显示和控制分离,如果想换一个视图,要修改Action实现类的代码。

       到现在为止,我们已经实现了一个Struts框架的雏形,只是还不够完美,下篇博客,我们继续完善所有的转向页面,将显示和控制分离。下篇博客【SSH进阶之路】一步步重构MVC实现Struts框架——完善转向页面,显示和控制分离(六)。


源码下载



【SSH进阶之路】一步步重构MVC实现Struts框架——彻底去掉逻辑判断(五)