手寫Struts,帶你深入原始碼中心解析

煙花散盡13141發表於2019-08-14

個人剖析,不喜勿噴

掃碼關注公眾號,不定期更新干活
這裡寫圖片描述

在此申明本博文並非原創,原文:http://blog.csdn.net/lenotang/article/details/3336623,本文章是在此文章基礎上進行優化。也談不上優化,只是加上了點自己的想法

jar包準備

  • 這裡寫圖片描述

  • 為什麼會用到這兩個jar包呢,因為我需要通過這個jar來解析xml配置檔案。

新建專案

  • 這裡寫圖片描述

流程梳理

  • struts配置檔案
```
<?xml version="1.0" encoding="UTF-8"?>




/index.jsp
/WEB-INF/login.jsp



```

  • 熟悉struts的朋友都清楚struts.xml配置檔案的重要性,這個配置檔名字是可以更改的,這裡簡單解釋下這個配置檔案的作用,首先我們找到action這個節點這個action的name是login,就是說前臺中請求這個login經過這個配置檔案解析就會把這個請求交給action中的class屬性,也就是上面的
```
org.zxh.action.LoginAction
```

具體的是交由這個類的login(method)這個方法。這個方法會方法一個string型別的字串,如果返回的是success就將頁面重定向到index.jsp如果是login就重定向到login.jsp。這個配置檔案就是這樣的作用。因為是自己寫的,所以這裡並不會想struts框架那樣封裝了很多東西,這裡只是為了讓讀者更加深入的理解struts的執行機制。


如何將我們寫的struts.xml檔案在程式中啟動呢?

  • 剛入門的同志可能會疑問,寫一個配置檔案就能處理前後臺互動了?答案當然是不能。這裡給大家普及一下web基礎接觸filter的,每次互動需要filter(jsp就是特殊的servlet),所以想實現互動我們就得新建一個servlet,在這個servlet裡我們去讀我們寫的struts.xml檔案,通過讀到的資訊決定下一步的操作。那麼如何啟動一個filter呢?這個不多說,直接在web專案中的web.xml配置攔截器就會執行filter。

新建filter(FilterDispatcher)

  • 這個servlet就是struts的核心過濾器,需要先繼承過濾器。
```

public class FilterDispatcher implements Filter{

@Override
public void destroy() {
    // TODO Auto-generated method stub
    
}

@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1,
        FilterChain arg2) throws IOException, ServletException {
    // TODO Auto-generated method stub
    
}

@Override
public void init(FilterConfig arg0) throws ServletException {
    // TODO Auto-generated method stub
    
}

}

```
  • Filter中我們要在初始化函式(init)中對一些引數進行初始化,對那些資料初始化呢,對!當然是拿配置檔案的資訊啦。配置檔案是.xml這裡我用dom4j讀取.xml配置檔案。 把struts.xml配置檔案放在src下,(可以放在其他地方,這裡的地址填的對應就行了)
// 獲得xml配置檔案
        String webRootPath = getClass().getClassLoader()
                .getResource("struts.xml").getPath();
  • 拿到配置檔案路徑之後開始讀取,這裡我講讀到的資料封裝到一個map裡面。在封裝在Map中我們仔細觀察一下配置檔案

這裡寫圖片描述

  • 其實我們放在Map裡面就是這四個屬性的值,有了這四個值我們就可以完成一次前後臺互動的對映了。所以為了方便這裡封裝成javabean。
package org.zxh.util;

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

/**
 * 將action屬性封裝成類
 * @author 87077
 *
 */
public class ActionConfig {
    
    //action 給別人呼叫的名字
    private String name;
    //action對應程式中的action類
    private String clazzName;
    //action中的方法
    private String method;
    //返回結果不知一條 所以用Map
    private Map<String, String> resultMap = new HashMap<String, String>();
    
    public ActionConfig(){
        
    }
    
    public ActionConfig(String name , String clazzName , String method , Map<String, String> resultMap){
        this.name=name;
        this.clazzName=clazzName;
        this.method=method;
        this.resultMap=resultMap;
    }

    public String getName() {
        return name;
    }

    public String getClazzName() {
        return clazzName;
    }

    public String getMethod() {
        return method;
    }

    public Map<String, String> getResultMap() {
        return resultMap;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setClazzName(String clazzName) {
        this.clazzName = clazzName;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public void setResultMap(Map<String, String> resultMap) {
        this.resultMap = resultMap;
    }
    
}
  • 有了javabean 我們開始解析xml檔案
package org.zxh.util;

import java.io.File;
import java.util.List;
import java.util.Map;

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

/**
 * 採用dom4j解析xml配置檔案
 * 
 * @author 87077
 * 
 */
public class ConfigUtil {

    /**
     * @param fileName
     *            待解析的檔案
     * @param map
     *            存放解析的資料
     */
    public static void parseConfigFile(String fileName,
            Map<String, ActionConfig> map) {
        SAXReader reader = new SAXReader();
        try {
            Document doc = reader.read(new File("D:\\zxh\\soft\\apache-tomcat-7.0.70\\apache-tomcat-7.0.70\\webapps\\MyStruts\\WEB-INF\\classes\\struts.xml"));
            Element root = doc.getRootElement();
            List<Element> list = root.selectNodes("package/action");
            for (Element element : list) {
                // 封裝成ActionConfig物件,儲存在map中
                ActionConfig config = new ActionConfig();
                // 獲取action中的值
                String name = element.attributeValue("name");
                String clazzName = element.attributeValue("class");
                String method = element.attributeValue("method");
                // 將值傳入javabean中
                config.setName(name);
                config.setClazzName(clazzName);
                // 如果沒有設定執行method 執行預設的
                if (method == null || "".equals(method)) {
                    method = "execute";
                }
                config.setMethod(method);
                // 繼續向下獲取action中的返回方法
                List<Element> resultList = element.selectNodes("result");
                for (Element resultElement : resultList) {
                    String resultName = resultElement.attributeValue("name");
                    String urlPath = resultElement.getTextTrim();
                    if (resultName == null || "".equals(resultName)) {
                        resultName = "success";
                    }
                    config.getResultMap().put(resultName, urlPath);
                }
                map.put(name, config);
            }
        } catch (DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
  • 現在我們在回到過濾器上,上面兩個類就是為了解析xml的。所以在Filter中的init方法裡我們就可以將解析的資料放到我們的全域性Map中
@Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub 過濾器的初始化過程
        // 獲得xml配置檔案
        String webRootPath = getClass().getClassLoader()
                .getResource("struts.xml").getPath();
        // 將xml配置檔案解析裝在到map中
        ConfigUtil.parseConfigFile(webRootPath, map);
    }

過濾器的執行

  • 過濾器真正執行是在doFilter方法開始時。
public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2)

doFilter()方法類似於Servlet介面的service()方法。當客戶端請求目標資源的時候,容器就會呼叫與這個目標資源相關聯的過濾器的 doFilter()方法。其中引數 request, response 為 web 容器或 Filter 鏈的上一個 Filter 傳遞過來的請求和相應物件;引數 chain 為代表當前 Filter 鏈的物件,在特定的操作完成後,可以呼叫 FilterChain 物件的 chain.doFilter(request,response)方法將請求交付給 Filter 鏈中的下一個 Filter 或者目標 Servlet 程式去處理,也可以直接向客戶端返回響應資訊,或者利用RequestDispatcher的forward()和include()方法,以及 HttpServletResponse的sendRedirect()方法將請求轉向到其他資源。這個方法的請求和響應引數的型別是 ServletRequest和ServletResponse,也就是說,過濾器的使用並不依賴於具體的協議。

  • 獲取請求域和響應域還有Filter鏈,並設定編碼防止亂碼
//針對http請求,將請求和響應的型別還原為HTTP型別
        HttpServletRequest request = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;
        //設定請求和響應的編碼問題
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
  • 獲取請求地址
//獲取請求路徑
String url = request.getServletPath();
  • 通過請求去判斷知否攔截過濾這個地址的請求,本文預設過濾所有以.action結尾的請求
//請求地址過濾,如果不是以.action結尾的
        if(!url.endsWith(".action")){
            //不是.action的放行
            arg2.doFilter(request, response);
            return ;
        }
  • 看我之前將xml檔案中資料放入到Map的格式可以看出我是講整個javabean放入Map中名字是action的name。所以下面我就要去那個name(就是請求中的login)
//解析request路徑
        int  start = url.indexOf("/");
        int end = url.lastIndexOf(".");
        String path=url.substring(start+1,end);
        //通過path去匹配到對應的ActionConfig類。在這裡已經解析到了所有的action的資訊
        ActionConfig config = map.get(path);
        //匹配不成功就返回找不到頁面錯誤資訊
        if(config==null){
            response.setStatus(response.SC_NOT_FOUND);
            return ;
        }
  • 獲取了ActionConfig類了,action的所有資訊都儲存在這個javabean類中了,下面的事情就好辦了。下面的只是會用到反射的知識。我們拿到真正action類的名稱後就需要根據名字獲取到這個action的實體類。
//通過ActionConfig獲取完成的類名字
        String clazzName=config.getClazzName();
        //例項化Action物件,不存在的話就提示錯誤資訊 
        Object action = getAction(clazzName);
        if(action==null){
            //說明這個action是錯誤的,在配置檔案中沒有佔到對應的action類
            response.setStatus(response.SC_NOT_FOUND);
            return ;
        }

request引數獲取並賦值給action

  • 執行action的方法前很定需要先將request中的引數獲取到,進行賦值,這部才是真正的意義上的互動。
public static void requestToAction(HttpServletRequest request , Object action )
  • 將傳進來的action物件進行class話並獲取action實體下的屬性
Class<? extends Object> clazzAction = action.getClass();
        //獲取aciton中所有屬性,從前臺獲取的值很多,只有action屬性中有的才會進行反射賦值
        Field[] fields = action.getClass().getDeclaredFields();
  • 拿到request傳過來的值並進行遍歷
//獲取請求中的名字屬性值
        Enumeration<String> names=request.getParameterNames();

String name=names.nextElement();
            boolean flag=false;
            //需要判斷action屬性中沒有的而請求中有的我們不需要進行反射處理
            for (Field field : fields) {
                if(name.equals(field.getName())){
                    flag=true;
                }
            }
            if(!flag){
                return;
            }
            String[] value=request.getParameterValues(name);
  • 通過request中的name並且在action中有這個屬性之後我們就需要獲取action這個欄位的屬性。
Class<Object> fieldType=(Class<Object>) clazzAction.getDeclaredField(name).getType();
  • 獲取action的改name欄位屬性的set方法
//通過反射呼叫該屬性的set方法
                    String setName="set"+name.substring(0,1).toUpperCase()+name.substring(1);
                    Method method=clazzAction.getMethod(setName, new Class[]{fieldType});
  • 下面我們就需要將獲取的value按型別
private static Object[] transfer(Class<Object> fieldType , String[] value){
        Object[] os = null;
        //fieldType 是[]這種型別的,需要將[]去掉
        String type=fieldType.getSimpleName().replace("[]", "");
        if("String".equals(type)){
            os=value;
        }else if("int".equals(type)||"Integer".equals(type)){
            os = new Integer[value.length];
            for (int i = 0; i < os.length; i++) {
                os[i] = Integer.parseInt(value[i]);
            }
        }else if("float".equals(type)||"Float".equals(type)){
            os=new Float[value.length];
            for (int i = 0; i < os.length; i++) {
                os[i]=Float.parseFloat(value[i]);
            }
        }else if("double".equals(type)||"Double".equals(type)){
            os=new Double[value.length];
            for (int i = 0; i < os.length; i++) {
                os[i]=Double.parseDouble(value[i]);
            }
        }
        return os;
    }
  • 獲取object資料之後就是講這個object資料通過反射付給action對應的屬性
//判斷是否是陣列屬性
                    if(fieldType.isArray()){
                        method.invoke(action, new Object[]{object});
                    }else {
                        method.invoke(action, new Object[]{object[0]});
                    }

這說一下 method.invoke是將action類中method方法這個方法需要的引數就是object<a href="http://www.oschina.net/code/snippet_216465_36771”>詳解

  • 有了這個方法我們在回到Filter就可以了
//前置攔截,獲取request裡面的引數,呼叫action的set方法給屬性設定值
        BeanUtil.requestToAction(request, action);
  • 屬性賦值完成就開始執行action中的method了
private String executeAction(ActionConfig config, Object action) {
        String method = config.getMethod();
        String result = null;
        try {
            Method callMethod = action.getClass().getMethod(method,String.class);
            result = (String) callMethod.invoke(action, new Object[] {});
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return config.getResultMap().get(result);
    }
  • 到這裡你已經獲取了配置檔案中前臺對映後應該的result了,那麼就簡單了,直接重定向就可以了,到這裡就實現了struts的前後臺互動。
request.getRequestDispatcher(result).forward(request, response);

驗證正確性

  • 下面就在前臺jsp中form表單將資料傳遞給我們的login action看看會不會去執行指定的方法
<form method="post" action="login.action" name="loginForm">
        <table width="422" border="1" bgcolor="#0080c0" height="184">
            <caption>
                <h1>使用者登陸</h1>
            </caption>
            <tbody>
                <tr>
                    <td>&nbsp;姓名:</td>
                    <td>&nbsp; <input type="text" name="username">
                    </td>
                </tr>
                <tr>
                    <td>&nbsp;密碼:</td>
                    <td>&nbsp; <input type="password" name="password">
                    </td>
                </tr>
                <tr align="center">
                    <td colspan="2">&nbsp; <input type="submit" value="登陸"
                        name="submit"> <input type="reset" value="重置" name="reset">
                    </td>
                </tr>
            </tbody>
        </table>
    </form>
  • 效果讀者自行展示吧,到這裡struts的執行機制就講完了,注意知識執行機制裡面還有很多值得我們學習的東西,就好比說這裡有很多過濾器,不同過濾器過濾資料程度不同執行效果不同。希望有機會再和大家分享一些其他關於struts的知識!

上訴原理的原始碼下載

相關文章