【從零寫javaweb框架】(八)請求轉發器

上一篇我們實現了把ClassHelper/BeanHelper/IocHelper/ControllerHelper在項目啓動時加載進來。【從零寫javaweb框架】(七)初始化框架

現在開始寫請求轉發器,請求轉發器是MVC的核心:需要編寫一個servlet,讓它來處理所有的請求。

從HttpServletRequest對象中獲取請求方法與請求路徑,通過ControllerHelper.getHandler方法回去Handler對象。

當拿到Handler對象後,我們可以方便地獲取Controller的類,進而通過BeanHelper.getBean方法獲取Controller的實例對象。

隨後可以從HttpServletRequest對象中獲取所有請求參數,並將其初始化到一個名爲Param的對象中,Param類代碼如下:

package org.smart4j.framework.bean;

import org.smart4j.framework.util.CastUtil;

import java.util.Map;

/**
 * desc : 請求參數對象
 * Created by Lon on 2018/1/28.
 */
public class Param {

    private Map<String, Object> paramMap;

    public Param(Map<String, Object> paramMap) {
        this.paramMap = paramMap;
    }

    /**
     * 根據參數名獲取long型參數值
     */
    public long getLong(String name){
        return CastUtil.castLong(paramMap.get(name));
    }

    //此處省略獲取各個類型參數值的方法...

    /**
     * 獲取所有字段信息
     */
    public Map<String, Object> getMap(){
        return paramMap;
    }

}

還可從Handler對象中獲取Action的方法返回值,返回值有兩種可能情況:

1.若返回值是View類型對象,則返回一個JSP頁面。

2.若返回值是Data類型對象,則返回一個JSON數據。


下面先看View類:

package org.smart4j.framework.bean;

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

/**
 * desc : 用於返回的視圖對象
 * Created by Lon on 2018/1/29.
 */
public class View {

    /**
     * 視圖路徑
     */
    private String path;

    /**
     * 模型數據
     */
    private Map<String, Object> model;

    public View(String path) {
        this.path = path;
        this.model = new HashMap<String, Object>();
    }

    public String getPath(){
        return path;
    }

    public Map<String, Object> getModel() {
        return model;
    }
}

再看Data類:

package org.smart4j.framework.bean;

/**
 * desc : 返回數據對象
 * Created by Lon on 2018/1/29.
 */
public class Data {

    /**
     * 模型數據
     */
    private Object model;

    public Data(Object model) {
        this.model = model;
    }

    public Object getModel() {
        return model;
    }
}

接下來就是最核心的DispatcherServlet類:

package org.smart4j.framework;

import org.smart4j.framework.bean.Data;
import org.smart4j.framework.bean.Handler;
import org.smart4j.framework.bean.Param;
import org.smart4j.framework.bean.View;
import org.smart4j.framework.helper.BeanHelper;
import org.smart4j.framework.helper.ConfigHelper;
import org.smart4j.framework.helper.ControllerHelper;
import org.smart4j.framework.util.*;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * desc : 請求轉發器
 * Created by Lon on 2018/1/29.
 */
@WebServlet(urlPatterns = "/*", loadOnStartup = 0)
public class DispatcherServlet extends HttpServlet{

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        // 初始化相關Helper類
        HelperLoader.init();
        // 獲取ServletContext對象(用於註冊Servlet)
        ServletContext servletContext = servletConfig.getServletContext();
        // 註冊處理JSP的Servlet
        ServletRegistration jspServlet = servletContext.getServletRegistration("jsp");
        jspServlet.addMapping(ConfigHelper.getAppJspPath() + "*");
        // 註冊處理靜態資源的默認Servlet
        ServletRegistration defaultServlet = servletContext.getServletRegistration("default");
        defaultServlet.addMapping(ConfigHelper.getAppAssetPath() + "*");
    }

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 獲取請求方法與請求路徑
        String requestMethod = request.getMethod().toLowerCase();
        String requestPath = request.getPathInfo();
        // 獲取Action處理器
        Handler handler = ControllerHelper.getHandler(requestMethod, requestPath);
        if (handler != null){
            // 獲取Controller類及其Bean實例
            Class<?> controllerClass = handler.getControllerClass();
            Object controllerBean = BeanHelper.getBean(controllerClass);
            // 創建請求參數對象
            Map<String, Object> paramMap = new HashMap<String, Object>();
            Enumeration<String> paramNames = request.getParameterNames();
            while (paramNames.hasMoreElements()){
                String paramName = paramNames.nextElement();
                String paramValue = request.getParameter(paramName);
                paramMap.put(paramName, paramValue);
            }
            String body = CodecUtil.decodeURL(StreamUtil.getString(request.getInputStream()));
            if (StringUtil.isNotEmpty(body)){
                String[] params = StringUtil.splitString(body, "&");
                if (ArrayUtil.isNotEmpty(params)){
                    for (String param : params){
                        String[] array = StringUtil.splitString(param, "=");
                        if (ArrayUtil.isNotEmpty(array) && array.length == 2){
                            String paramName = array[0];
                            String paramValue = array[1];
                            paramMap.put(paramName, paramValue);
                        }
                    }
                }
            }
            Param param = new Param(paramMap);
            // 調用Action方法
            Method actionMethod = handler.getActionMethod();
            Object result = ReflectionUtil.invokeMethod(controllerBean, actionMethod, param);
            // 處理方法返回值
            if (result instanceof View){
                View view = (View) result;
                String path = view.getPath();
                if (StringUtil.isNotEmpty(path)){
                    if (path.startsWith("/")){
                        response.sendRedirect(request.getContextPath() + path);
                    } else {
                        Map<String, Object> model = view.getModel();
                        for (Map.Entry<String, Object> entry : model.entrySet()){
                            request.setAttribute(entry.getKey(), entry.getValue());
                        }
                        request.getRequestDispatcher(ConfigHelper.getAppJspPath() + path).forward(request, response);
                    }
                }
            } else if (result instanceof Data){
                // 返回JSON數據
                Data data = (Data) result;
                Object model = data.getModel();
                if (model != null){
                    response.setContentType("application/json");
                    response.setCharacterEncoding("UTF-8");
                    PrintWriter writer = response.getWriter();
                    String json = JsonUtil.toJson(model);
                    writer.write(json);
                    writer.flush();
                    writer.close();
                }
            }
        }
    }

}

在DispatcherServlet類中用到了幾個新的工具類

StreamUtil用於常用的流操作:

package org.smart4j.framework.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * desc : 流操作工具類
 * Created by Lon on 2018/1/29.
 */
public final class StreamUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(StreamUtil.class);

    /**
     * 從輸入流中獲取字符串
     */
    public static String getString(InputStream is){
        StringBuilder sb = new StringBuilder();
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = reader.readLine()) != null){
                sb.append(line);
            }
        } catch (Exception e){
            LOGGER.error("get string failure", e);
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

}

CodecUtil用於編碼與解碼操作:

package org.smart4j.framework.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * desc : 編碼與解碼操作工具類
 * 作用 : 轉義特殊字符、解決中文亂碼問題
 * Created by Lon on 2018/1/29.
 */
public final class CodecUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(CodecUtil.class);

    /**
     * 將URL編碼
     */
    public static String encodeURL(String source){
        String target;
        try {
            target = URLEncoder.encode(source, "UTF-8");
        } catch (Exception e){
            LOGGER.error("encode url failure");
            throw new RuntimeException(e);
        }
        return target;
    }

    /**
     * 將URL解碼
     */
    public static String decodeURL(String source){
        String target;
        try {
            target = URLDecoder.decode(source, "UTF-8");
        } catch (Exception e){
            LOGGER.error("decode url failure");
            throw new RuntimeException(e);
        }
        return target;

    }

}

JsonUtil類用於處理JSON與POJO之間的轉換,基於Jackson實現:

package org.smart4j.framework.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * desc : JSON工具類
 * Created by Lon on 2018/1/29.
 */
public final class JsonUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtil.class);

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    /**
     * 將POJO轉爲JSON
     */
    public static <T> String toJson(T obj){
        String json;
        try {
            json = OBJECT_MAPPER.writeValueAsString(obj);
        } catch (Exception e){
            LOGGER.error("convert POJO to JSON failure", e);
            throw new RuntimeException(e);
        }
        return json;
    }

    /**
     * 將JSON轉爲POJO
     */
    public static <T> T from(String json, Class<T> type){
        T pojo;
        try {
            pojo = OBJECT_MAPPER.readValue(json, type);
        } catch (Exception e){
            LOGGER.error("convert JSON to POJO failure", e);
            throw new RuntimeException(e);
        }
        return pojo;
    }

}

另外還有,我們修改了之前寫的StringUtil,新增了一個splitString方法:

    /**
     * 分開字符
     */
    public static String[] splitString(String str, String regex){
        return str.split(regex);
    }


總結:

到這裏,已經成功的搭建了一個簡單的MVC框架,定義了一系列註解,通過Controller/Service註解來定義Controller和Service類;通過Action註解來定義Action方法;通過Inject註解來實現依賴注入;通過一系列Helper類來初始化MVC框架;通過DispatchServlet來處理所有的請求;根據請求方法與請求路徑來調用具體的Action方法,根據Action方法的返回值,若爲View類型,則跳轉到JSP頁面,若爲Data類型,則返回JSON數據。

框架現在基本能跑起來了,但還缺少如AOP(Aspect Oriented Programming面向方面編程)。下一篇開始實現這個特性






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章