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.响应客户端

最终响应给客户端

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

猜你喜欢

转载自blog.csdn.net/a909301740/article/details/78717138
今日推荐