struts1原理

struts1属于MVC开发模式中的控制层框架,这种控制层框架的主要作用是将模型与视图分离(就是用户发送一个请求的时候,后台并不是直接在jsp页面里进行业务逻辑操作,把数据直接渲染到页面上返回给用户。而是先获取数据,再解析页面,再把数据和页面进行组合,最后返回给用户响应,达到一个解耦的作用。),而MVC这种开发模式的作用也是实现这个作用。

ps:在多年以前的开发过程中是没有像struts这样的控制层框架在模型和视图之间进行解耦的,而是直接在jsp页面上写业务逻辑,连接数据库,进行增删改查(这也就是为什么jstl标签库会提供那么多类似连接数据库等功能强大的标签的原因),显然这种开发方式不利于维护,不利于扩展,不利于复用,所以就出现了MVC这种设计模式,就有struts1,sruts2,springMVC这种控制层框架在模型和视图之间进行解耦。

下面进入正题:

struts1

1.初始化ActionServlet

struts1通过一个前端控制器ActionServlet来匹配所有后缀为".do"的请求,这个servlet在web容器(Tomcat)中配置成一启动就加载并初始化的servlet。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="3.0">
	<display-name>struts1Login</display-name>
	<welcome-file-list>
		<welcome-file>login.jsp</welcome-file>
	</welcome-file-list>

	<!-- 配置struts1的ActionServlet匹配所有.do请求,在系统启动时就初始化 -->
	<servlet>
		<description>ActionServlet</description>
		<servlet-name>ActionServlet</servlet-name>
		<servlet-class>com.tz.jspstudy.framework.struts.servlet.ActionServlet</servlet-class>
		<!-- 
			值设为大于等于0的正数,web容器(Tomcat)在启动的时候就加载这个servlet 
			值小于0或者没有配置该选项的时候,用户在请求该servlet的时候才会加载初始化这个servlet
			值必须为整数,并且正数的值越小优先级越高,这里把他的优先级设为0,表示最高的优先级
			如果两个servlet的值相同,容器会自行选择哪一个servlet被先加载。
		-->
		<load-on-startup>0</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>ActionServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

</web-app>

ActionServlet在初始化的时候调用init方法,使用DOM4J解析struts1的配置文件struts-config.xml,并将配置信息存储到ActionMapping中然后将ActionMapping放到分发器DispatchProcessor中,这个分发器的作用是将ActionServlet所匹配到的请求分发给对应的业务控制类Action来处理

package com.tz.jspstudy.framework.struts.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.tz.jspstudy.framework.struts.bean.ActionMapping;

public class ActionServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;
	
	private ActionMapping mapping = null;
	private DispatchProcessor dispatchProcessor = new DispatchProcessor();
	
	public ActionServlet() {
		super();
	}
	
	public void destroy() {
	}
	
	//处理请求
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {		
		//请求传递给RquestProcessor处理
		System.out.println("已经进入ActionServlet sevice方法-准备分发请求");
		dispatchProcessor.processor(request, response);	
	}

	//系统启动的时候 就会初始化ActionSevelet 
	public void init() throws ServletException {
		System.out.println("初始化ActionServlet(所有的请求入口-准备加载struts-config配置文件)");
		//获得配置文件所在的文件路径(这里将它放在WEB-INF目录下)
		String realPath = this.getServletContext().getRealPath("/WEB-INF");		
		//使用DOM4J解析struts-config.xml配置文件中的配置信息,封装成一个ActionMapping对象
		mapping = new XMLParser().parseXML(realPath);
		dispatchProcessor.setMapping(mapping);
		System.out.println("初始化ActionServlet(所有的请求入口-加载struts-config配置文件完毕)");
	}
}

struts-config.xml配置文件中的配置信息主要有两个:

  1. “请求名称” 与 “对应的处理该请求的业务控制类” 之间的映射关系(就是你发送的这个请求将由哪个业务控制类来处理)
  2. form-name与所对应的表单类之间的映射关系(就是与你这个请求相关联的表单对象的名称,在这个表单对象里存储着你请求里面的参数信息)
<?xml version="1.0" encoding="UTF-8"?>
<struts-config>
	<!-- 用来配置form-name与所对应的表单类之间的映射关系 -->
	<form-beans>
		<!-- form-bean标签有多个 -->
		<form-bean name="loginForm" type="com.tz.jspstudy.sysmanage.formbean.LoginForm"/>
	</form-beans>
	
	<!-- 用来配置  "请求名path" 与   "对应的处理该请求的业务控制类type" 之间的映射关系 -->
	<action-mappings>
		<!-- 
			action元素:配置业务Action类
			path : 请求的路径
			type : 请求对应的业务Action类的类路径	
		 -->
		<!-- action标签有多个 -->
		<action path="/login" type="com.tz.jspstudy.sysmanage.action.LoginAction" name="loginForm"/>
	</action-mappings>
	
</struts-config>

使用DOM4J解析xml文件的代码如下:

package com.tz.jspstudy.framework.struts.servlet;

import java.util.List;

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

import com.tz.jspstudy.framework.struts.bean.ActionConfig;
import com.tz.jspstudy.framework.struts.bean.ActionMapping;
import com.tz.jspstudy.framework.struts.bean.FormBeanConfig;

public class XMLParser {
	
	public ActionMapping parseXML(String realPath) {
		ActionMapping mapping = new ActionMapping();
		
		//构造SAX解析器
		SAXReader reader = new SAXReader();
		
		try {
			//获得文档对象和根元素
			Document doc = reader.read(realPath+"/struts-config.xml");
			Element root = doc.getRootElement();
			
			//读取form-beans标签
			Element formBeans = root.element("form-beans");
			List<Element> formBeanList = formBeans.elements();
			
			//循环封装对象
			for (Element formBean : formBeanList) {
				String name = formBean.attributeValue("name");
				String type = formBean.attributeValue("type");				
				FormBeanConfig config = new FormBeanConfig();
				config.setName(name);
				config.setType(type);				
				//数据存入ActionMapping
				mapping.setFormBeanConfig(config);
			}
			
			//读取action-mappings
			Element actions = root.element("action-mappings");
			List<Element> actionList = actions.elements();
			for (Element action : actionList) {
				String name = action.attributeValue("name");
				String path = action.attributeValue("path");
				String type = action.attributeValue("type");
				
				ActionConfig config = new ActionConfig();
				config.setName(name);
				config.setPath(path);
				config.setType(type);
				
				//数据封装到mapping中
				mapping.setActionConfig(config);
			}
			
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		return mapping;
	}
}

ActionMapping类的成员如下:

package com.tz.jspstudy.framework.struts.bean;

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

public class ActionMapping {
	//key是name 存储表单名与表单对象之间的映射关系
	private Map<String,FormBeanConfig> formMap = new HashMap<String,FormBeanConfig>();
	
	//key是path 存储请求名称与处理该请求的业务控制类之间的映射关系
	private Map<String,ActionConfig> actionMap = new HashMap<String,ActionConfig>();
	
	public void setFormBeanConfig(FormBeanConfig config) {
		formMap.put(config.getName(), config);
	}
	
	public void setActionConfig(ActionConfig config) {
		actionMap.put(config.getPath(), config);
	}
	
	public FormBeanConfig getFormBeanConfig(String name) {
		return formMap.get(name);
	}
	
	public ActionConfig getActionConfig(String path) {
		return actionMap.get(path);
	}
	
}

2.客户端发送请求

用户发送一个请求,该请求会被ActionServlet拦截然后进入service方法里

3.分发器分发请求

在该方法里,ActionServlet会调用分发器(DispatchProcessor)的processor方法来分发request请求。
protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {		
		//请求传递给RquestProcessor处理
		System.out.println("已经进入ActionServlet sevice方法-准备分发请求");
		dispatchProcessor.processor(request, response);	
	}

在分发器内部持有上文提到的ActionMapping对象的引用,以及一个actionMap(存储Action的缓存,就是一个map集合)

进入processor方法,根据url地址截取请求名称path

String path = null;
String uri = request.getRequestURI(); // 得到/工程名/xxx.do
String contenPath = request.getContextPath(); // 得到 /工程名
uri = uri.substring(contenPath.length()); // 去掉工程名得到 /xxx.do
path = uri.substring(0, uri.length() - 3); // 去掉.do 获得/xxx
System.out.println("进入请求分发器DispatchProcessor--解析完请求方法,请求的路径为"+path);

创建ActionConfig对象

然后在ActionMapping中根据请求的名称查找该请求的相关信息,然后创建一个ActionConfig对象(该对象其实是对struts-config.xml文件中action配置信息的封装) 如果创建的ActionConfig对象为空说明ActionMpping中没有与该请求名对应的配置信息,就会抛出异常
// 根据路径获取ActionConfig对象
ActionConfig config = mapping.getActionConfig(path);

// config为空则说明没有配置这个路径
if (config == null) {
	throw new ServletException("struts-config.xml没有配置" + path + "路径");
}

ActionConfig的类信息如下:

package com.tz.jspstudy.framework.struts.bean;

public class ActionConfig {
	// 请求的名称
	private String path;
	// 请求对应的业务控制类
	private String type;
	// 与该请求相关的表单名form-name
	private String name;
	
	public String getPath() {
		return path;
	}
	public void setPath(String path) {
		this.path = path;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
}

查找Action

如果ActionConfig不为空,则会到ActionMap缓存中根据path(键)去查找对应的Action,如果没有查找到该Action说明客户端第一次发送该请求,那么分发器就会通过反射创建一个该Action的实例,并且放入缓存里,查找到了就直接赋值给引用。

Action action = actionMap.get(path);
// 用路径在缓存中找action的实例,没找到说明是第一次用户请求这个Action,则反射创建缓存
if (action == null) {
	// 反射的代码
	action = this.createAction(config);
	// 缓存
	actionMap.put(path, action);
}

填充Form

接下来会到刚才封装的ActionConfig对象中取出form的name值到FormBeanConfig(ActionServlet在初始化的时候创建的一个form-name到form之间的映射)中查找是否有相对应的表单对象,如果没有就说明配置文件中action配置中的form的name属性配置错误,抛出异常;如果有就会根据FormBeanConfig对象创建一个ActionForm对象并从request中获取参数赋值给ActionForm。

// 获得ActionForm设置参数
ActionForm form = null;
// 获得ActionConfig的name,如果有就设置参数
if (config.getName() != null) {
	// 用name查找出对应的form-bean标签
	FormBeanConfig cfg = mapping.getFormBeanConfig(config.getName());
	if (cfg == null) {
		throw new ServletException(
				"action标签的name必须匹配一个form-bean标签的name");
	}

	// 反射创建ActionForm的实例
	form = this.createActionForm(cfg);
	// 反射设置好参数
	this.setParameter(form, request);
}

FormBeanConfig类的信息:

package com.tz.jspstudy.framework.struts.bean;

public class FormBeanConfig {
	// form-name
	private String name;
	// 对应的form类
	private String type;
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
}

6.执行Action的execute方法

在执行Action的execute方法的时候会把上一步封装的ActionForm对象一并传入方法内,处理完请求后返回一个ActionForward对象,ActionForward对象中其实就是封装了一个标记位(用来判断是转发还是重定向)和一个路径(用于转发或重定向的路径)
// 调用Action的execute获得ActionForward转发/重定向
try {
	System.out.println("进入请求分发器--准备调用对应的业务处理Action");
	ActionForward forward = action.execute(form, request, response);
			
	if (forward.isForward()) {		
		request.getRequestDispatcher(forward.getPath()).forward(request, response);
	} else {
		response.sendRedirect(forward.getPath());
	}
			
} catch (Exception e) {
	e.printStackTrace();
}

ActionForward类的代码如下:

package com.tz.jspstudy.framework.struts.servlet;

public class ActionForward {
	private boolean isForward = true;	//默认是转发
	private String path;
	
	public ActionForward(String path) {
		this.path = path;
	}
	
	public ActionForward(String path,boolean isForward) {
		this.path = path;
		this.isForward = isForward;	//传递false,则是做重定向
	}

	public boolean isForward() {
		return isForward;
	}

	public void setForward(boolean isForward) {
		this.isForward = isForward;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}
	
}

Action的代码如下:

package com.tz.jspstudy.framework.struts.servlet;

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

public class Action {
	
	public ActionForward execute(ActionForm form,HttpServletRequest request,HttpServletResponse response) throws Exception {
		return null;
	}	
}

完整的分发器类以及它的processor方法如下:

package com.tz.jspstudy.framework.struts.servlet;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

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

import com.tz.jspstudy.framework.struts.bean.ActionConfig;
import com.tz.jspstudy.framework.struts.bean.ActionMapping;
import com.tz.jspstudy.framework.struts.bean.FormBeanConfig;

public class DispatchProcessor {
	private ActionMapping mapping = null;

	// 这个MAP是缓存所有的Action的实例的,因为Action写成单例模式
	private Map<String, Action> actionMap = new HashMap<String, Action>();

	public DispatchProcessor() {
	}
	
	public void setMapping(ActionMapping mapping) {
		this.mapping = mapping;
	}

	public void processor(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		//http://localhost:8080/studyJsp/login.do
		//login
		// 获得Action
		// 获得请求的路径地址
		System.out.println("进入请求分发器DispatchProcessor--解析请求方法,去ActionConfig对象匹配");

		String path = null;
		String uri = request.getRequestURI(); // 得到/工程名/xxx.do
		String contenPath = request.getContextPath(); // 得到 /工程名
		uri = uri.substring(contenPath.length()); // 去掉工程名得到 /xxx.do
		path = uri.substring(0, uri.length() - 3); // 去掉.do 获得/xxx
		System.out.println("进入请求分发器DispatchProcessor--解析完请求方法,请求的路径为"+path);

		// 根据路径获取ActionConfig对象
		ActionConfig config = mapping.getActionConfig(path);

		// config为空则说明没有配置这个路径
		if (config == null) {
			throw new ServletException("struts-config.xml没有配置" + path + "路径");
		}

		Action action = actionMap.get(path);
		// 用路径在缓存中找action的实例,没找到说明是第一次用户请求这个Action,则反射创建缓存
		if (action == null) {
			// 反射的代码
			action = this.createAction(config);
			// 缓存
			actionMap.put(path, action);
		}

		// 获得ActionForm设置参数
		ActionForm form = null;
		// 获得ActionConfig的name,如果有就设置参数
		if (config.getName() != null) {
			// 用name查找出对应的form-bean标签
			FormBeanConfig cfg = mapping.getFormBeanConfig(config.getName());
			if (cfg == null) {
				throw new ServletException(
						"action标签的name必须匹配一个form-bean标签的name");
			}

			// 反射创建ActionForm的实例
			form = this.createActionForm(cfg);
			// 反射设置好参数
			this.setParameter(form, request);
		}

		// 调用Action的execute获得ActionForward转发/重定向
		try {
			System.out.println("进入请求分发器--准备调用对应的业务处理Action");
			ActionForward forward = action.execute(form, request, response);
			
			if (forward.isForward()) {
				request.getRequestDispatcher(forward.getPath()).forward(request, response);
			} else {
				response.sendRedirect(forward.getPath());
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void setParameter(ActionForm form, HttpServletRequest request) {
		// 得到所有的参数名称
		Enumeration<String> enu = request.getParameterNames();
		// 迭代所有名称
		while (enu.hasMoreElements()) {
			String name = enu.nextElement();
			
			//过滤掉method参数
			if ("method".equals(name)) {
				continue;
			}
			
			// 根据名称拼接set方法
			String setMethodName = "set" + name.substring(0, 1).toUpperCase()
					+ name.substring(1);

			// 得到参数的值,反射调用form的set方法传递值
			String[] array = request.getParameterValues(name);
			Class clz = form.getClass();
			Method method = null;
			try {
				if (array.length > 1) {
					method = clz.getMethod(setMethodName, String[].class);
					method.invoke(form, array);
				} else {
					method = clz.getMethod(setMethodName, String.class);
					method.invoke(form, array[0]);
				}
			} 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();
			}

		}

	}

	private ActionForm createActionForm(FormBeanConfig config) {
		String type = config.getType();
		ActionForm form = null;
		try {
			form = (ActionForm) Class.forName(type).newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return form;
	}

	private Action createAction(ActionConfig config) {
		String type = config.getType();
		Action action = null;
		try {
			action = (Action) Class.forName(type).newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return action;
	}

}

8.响应客户端

最终响应给客户端

注:上述代码并非源码,仅供理解原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值