struts1属于MVC开发模式中的控制层框架,这种控制层框架的主要作用是将模型与视图分离(就是用户发送一个请求的时候,后台并不是直接在jsp页面里进行业务逻辑操作,把数据直接渲染到页面上返回给用户。而是先获取数据,再解析页面,再把数据和页面进行组合,最后返回给用户响应,达到一个解耦的作用。),而MVC这种开发模式的作用也是实现这个作用。
ps:在多年以前的开发过程中是没有像struts这样的控制层框架在模型和视图之间进行解耦的,而是直接在jsp页面上写业务逻辑,连接数据库,进行增删改查(这也就是为什么jstl标签库会提供那么多类似连接数据库等功能强大的标签的原因),显然这种开发方式不利于维护,不利于扩展,不利于复用,所以就出现了MVC这种设计模式,就有struts1,sruts2,springMVC这种控制层框架在模型和视图之间进行解耦。
下面进入正题:
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配置文件中的配置信息主要有两个:
- “请求名称” 与 “对应的处理该请求的业务控制类” 之间的映射关系(就是你发送的这个请求将由哪个业务控制类来处理)
- 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.响应客户端
最终响应给客户端注:上述代码并非源码,仅供理解原理。