1. 本章需要完成的內容:
- 完成ControllerRequestProcessor類的編寫: Controller請求處理器
- 完成JspRequestProcessor類的編寫:jsp資源請求處理
- 完成PreRequestProcessor類的編寫: 請求預處理,包括編碼以及路徑處理
- 完成StaticResourceRequestProcessor類的編寫: 靜態資源請求處理,包括但不限於圖片,css,以及js檔案等, 轉發到DefaultServlet
2. PreRequestProcessor類的編寫
2.1 PreRequestProcessor類需要完成的程式碼
package org.myframework.mvc.processor.impl;
/**
* @author wuyiccc
* @date 2020/6/16 18:54
* 豈曰無衣,與子同袍~
*/
import lombok.extern.slf4j.Slf4j;
import org.myframework.mvc.RequestProcessorChain;
import org.myframework.mvc.processor.RequestProcessor;
/**
* 請求預處理,包括編碼以及路徑處理
*/
@Slf4j
public class PreRequestProcessor implements RequestProcessor {
@Override
public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {
// 1.設定請求編碼,將其統一設定成UTF-8
requestProcessorChain.getRequest().setCharacterEncoding("UTF-8");
// 2.將請求路徑末尾的/剔除,為後續匹配Controller請求路徑做準備
// (一般Controller的處理路徑是/aaa/bbb,所以如果傳入的路徑結尾是/aaa/bbb/,
// 就需要處理成/aaa/bbb)
String requestPath = requestProcessorChain.getRequestPath();
//http://localhost:8080/myframework requestPath="/"
if (requestPath.length() > 1 && requestPath.endsWith("/")) {
requestProcessorChain.setRequestPath(requestPath.substring(0, requestPath.length() - 1));
}
log.info("preprocess request {} {}", requestProcessorChain.getRequestMethod(), requestProcessorChain.getRequestPath());
return true;
}
}
2.2 PreRequestProcessor類相關程式碼講解:
3. StaticResourceRequestProcessor類的編寫
3.1 StaticResourceRequestProcessor類需要完成的程式碼:
package com.wuyiccc.helloframework.mvc.processor.impl;
import com.wuyiccc.helloframework.mvc.RequestProcessorChain;
import com.wuyiccc.helloframework.mvc.processor.RequestProcessor;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
/**
* @author wuyiccc
* @date 2020/7/14 22:40
* 豈曰無衣,與子同袍~
*/
@Slf4j
public class StaticResourceRequestProcessor implements RequestProcessor {
public static final String DEFAULT_TOMCAT_SERVLET = "default";
public static final String STATIC_RESOURCE_PREFIX = "/static/";
//tomcat預設請求派發器RequestDispatcher的名稱
RequestDispatcher defaultDispatcher;
public StaticResourceRequestProcessor(ServletContext servletContext) {
this.defaultDispatcher = servletContext.getNamedDispatcher(DEFAULT_TOMCAT_SERVLET);
if (this.defaultDispatcher == null) {
throw new RuntimeException("There is no default tomcat servlet");
}
log.info("The default servlet for static resource is {}", DEFAULT_TOMCAT_SERVLET);
}
@Override
public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {
//1.通過請求路徑判斷是否是請求的靜態資源 webapp/static
if (isStaticResource(requestProcessorChain.getRequestPath())) {
//2.如果是靜態資源,則將請求轉發給default servlet處理
defaultDispatcher.forward(requestProcessorChain.getRequest(), requestProcessorChain.getResponse());
return false;
}
return true;
}
//通過請求路徑字首(目錄)是否為靜態資源 /static/
private boolean isStaticResource(String path) {
return path.startsWith(STATIC_RESOURCE_PREFIX);
}
}
3.2 StaticResourceRequestProcessor類相關程式碼講解:
4. JspRequestProcessor
4.1 JspRequestProcessor需要完成的程式碼:
package org.myframework.mvc.processor.impl;
import org.myframework.mvc.RequestProcessorChain;
import org.myframework.mvc.processor.RequestProcessor;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
/**
* @author wuyiccc
* @date 2020/6/16 18:59
* 豈曰無衣,與子同袍~
*/
/**
* jsp資源請求處理
*/
public class JspRequestProcessor implements RequestProcessor {
//jsp請求的RequestDispatcher的名稱
private static final String JSP_SERVLET = "jsp";
//Jsp請求資源路徑字首
private static final String JSP_RESOURCE_PREFIX = "/templates/";
/**
* jsp的RequestDispatcher,處理jsp資源
*/
private RequestDispatcher jspServlet;
public JspRequestProcessor(ServletContext servletContext) {
jspServlet = servletContext.getNamedDispatcher(JSP_SERVLET);
if (null == jspServlet) {
throw new RuntimeException("there is no jsp servlet");
}
}
@Override
public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {
if (isJspResource(requestProcessorChain.getRequestPath())) {
jspServlet.forward(requestProcessorChain.getRequest(), requestProcessorChain.getResponse());
return false;
}
return true;
}
/**
* 是否請求的是jsp資源
*/
private boolean isJspResource(String url) {
return url.startsWith(JSP_RESOURCE_PREFIX);
}
}
4.2 JspRequestProcessor相關程式碼講解:
5. ControllerRequestProcessor類的編寫
5.1 ControllerRequestProcessor需要完成的程式碼:
package org.myframework.mvc.processor.impl;
import lombok.extern.slf4j.Slf4j;
import org.myframework.core.BeanContainer;
import org.myframework.mvc.RequestProcessorChain;
import org.myframework.mvc.annotation.RequestMapping;
import org.myframework.mvc.annotation.RequestParam;
import org.myframework.mvc.annotation.ResponseBody;
import org.myframework.mvc.processor.RequestProcessor;
import org.myframework.mvc.render.JsonResultRender;
import org.myframework.mvc.render.ResourceNotFoundResultRender;
import org.myframework.mvc.render.ResultRender;
import org.myframework.mvc.render.ViewResultRender;
import org.myframework.mvc.type.ControllerMethod;
import org.myframework.mvc.type.RequestPathInfo;
import org.myframework.util.ConverterUtil;
import org.myframework.util.ValidationUtil;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author wuyiccc
* @date 2020/6/16 19:14
* 豈曰無衣,與子同袍~
*/
/**
* Controller請求處理器
*/
@Slf4j
public class ControllerRequestProcessor implements RequestProcessor {
//IOC容器
private BeanContainer beanContainer;
//請求和controller方法的對映集合
private Map<RequestPathInfo, ControllerMethod> pathControllerMethodMap = new ConcurrentHashMap<>();
/**
* 依靠容器的能力,建立起請求路徑、請求方法與Controller方法例項的對映
*/
public ControllerRequestProcessor() {
this.beanContainer = BeanContainer.getInstance();
Set<Class<?>> requestMappingSet = beanContainer.getClassesByAnnotation(RequestMapping.class);
initPathControllerMethodMap(requestMappingSet);
}
private void initPathControllerMethodMap(Set<Class<?>> requestMappingSet) {
if (ValidationUtil.isEmpty(requestMappingSet)) {
return;
}
//1.遍歷所有被@RequestMapping標記的類,獲取類上面該註解的屬性值作為一級路徑
for (Class<?> requestMappingClass : requestMappingSet) {
RequestMapping requestMapping = requestMappingClass.getAnnotation(RequestMapping.class);
String basePath = requestMapping.value();
if (!basePath.startsWith("/")) {
basePath = "/" + basePath;
}
//2.遍歷類裡所有被@RequestMapping標記的方法,獲取方法上面該註解的屬性值,作為二級路徑
Method[] methods = requestMappingClass.getDeclaredMethods();
if (ValidationUtil.isEmpty(methods)) {
continue;
}
for (Method method : methods) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping methodRequest = method.getAnnotation(RequestMapping.class);
String methodPath = methodRequest.value();
if (!methodPath.startsWith("/")) {
methodPath = "/" + basePath;
}
String url = basePath + methodPath;
//3.解析方法裡被@RequestParam標記的引數,
// 獲取該註解的屬性值,作為引數名,
// 獲取被標記的引數的資料型別,建立引數名和引數型別的對映
Map<String, Class<?>> methodParams = new HashMap<>();
Parameter[] parameters = method.getParameters();
if (!ValidationUtil.isEmpty(parameters)) {
for (Parameter parameter : parameters) {
RequestParam param = parameter.getAnnotation(RequestParam.class);
//目前暫定為Controller方法裡面所有的引數都需要@RequestParam註解
if (param == null) {
throw new RuntimeException("The parameter must have @RequestParam");
}
methodParams.put(param.value(), parameter.getType());
}
}
//4.將獲取到的資訊封裝成RequestPathInfo例項和ControllerMethod例項,放置到對映表裡
String httpMethod = String.valueOf(methodRequest.method());
RequestPathInfo requestPathInfo = new RequestPathInfo(httpMethod, url);
if (this.pathControllerMethodMap.containsKey(requestPathInfo)) {
log.warn("duplicate url:{} registration,current class {} method{} will override the former one",
requestPathInfo.getHttpPath(), requestMappingClass.getName(), method.getName());
}
ControllerMethod controllerMethod = new ControllerMethod(requestMappingClass, method, methodParams);
this.pathControllerMethodMap.put(requestPathInfo, controllerMethod);
}
}
}
}
@Override
public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {
//1.解析HttpServletRequest的請求方法,請求路徑,獲取對應的ControllerMethod例項
String method = requestProcessorChain.getRequestMethod();
String path = requestProcessorChain.getRequestPath();
ControllerMethod controllerMethod = this.pathControllerMethodMap.get(new RequestPathInfo(method, path));
if (controllerMethod == null) {
requestProcessorChain.setResultRender(new ResourceNotFoundResultRender(method, path));
return false;
}
//2.解析請求引數,並傳遞給獲取到的ControllerMethod例項去執行
Object result = invokeControllerMethod(controllerMethod, requestProcessorChain.getRequest());
//3.根據處理的結果,選擇對應的render進行渲染
setResultRender(result, controllerMethod, requestProcessorChain);
return true;
}
/**
* 根據不同情況設定不同的渲染器
*/
private void setResultRender(Object result, ControllerMethod controllerMethod, RequestProcessorChain requestProcessorChain) {
if (result == null) {
return;
}
ResultRender resultRender;
boolean isJson = controllerMethod.getInvokeMethod().isAnnotationPresent(ResponseBody.class);
if (isJson) {
resultRender = new JsonResultRender(result);
} else {
resultRender = new ViewResultRender(result);
}
requestProcessorChain.setResultRender(resultRender);
}
private Object invokeControllerMethod(ControllerMethod controllerMethod, HttpServletRequest request) {
//1.從請求裡獲取GET或者POST的引數名及其對應的值
Map<String, String> requestParamMap = new HashMap<>();
//GET,POST方法的請求引數獲取方式
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> parameter : parameterMap.entrySet()) {
if (!ValidationUtil.isEmpty(parameter.getValue())) {
//只支援一個引數對應一個值的形式
requestParamMap.put(parameter.getKey(), parameter.getValue()[0]);
}
}
//2.根據獲取到的請求引數名及其對應的值,以及controllerMethod裡面的引數和型別的對映關係,去例項化出方法對應的引數
List<Object> methodParams = new ArrayList<>();
Map<String, Class<?>> methodParamMap = controllerMethod.getMethodParameters();
for (String paramName : methodParamMap.keySet()) {
Class<?> type = methodParamMap.get(paramName);
String requestValue = requestParamMap.get(paramName);
Object value;
//只支援String 以及基礎型別char,int,short,byte,double,long,float,boolean,及它們的包裝型別
if (requestValue == null) {
//將請求裡的引數值轉成適配於引數型別的空值
value = ConverterUtil.primitiveNull(type);
} else {
value = ConverterUtil.convert(type, requestValue);
}
methodParams.add(value);
}
//3.執行Controller裡面對應的方法並返回結果
Object controller = beanContainer.getBean(controllerMethod.getControllerClass());
Method invokeMethod = controllerMethod.getInvokeMethod();
invokeMethod.setAccessible(true);
Object result;
try {
if (methodParams.size() == 0) {
result = invokeMethod.invoke(controller);
} else {
result = invokeMethod.invoke(controller, methodParams.toArray());
}
} catch (InvocationTargetException e) {
//如果是呼叫異常的話,需要通過e.getTargetException()
// 去獲取執行方法丟擲的異常
throw new RuntimeException(e.getTargetException());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return result;
}
}
5.2 ControllerRequestProcessor相關程式碼講解
6. ConverterUtil值轉換工具類的補充
相關程式碼如下:
package com.wuyiccc.helloframework.util;
/**
* @author wuyiccc
* @date 2020/7/15 8:02
* 豈曰無衣,與子同袍~
*/
public class ConverterUtil {
/**
* 返回基本資料型別的空值
* 需要特殊處理的基本型別即int\double\short\long\byte\float\boolean
*
* @param type 引數型別
* @return 對應的空值
*/
public static Object primitiveNull(Class<?> type) {
if (type == int.class || type == double.class || type == short.class || type == long.class || type == byte.class || type == float.class) {
return 0;
} else if (type == boolean.class) {
return false;
}
return null;
}
/**
* String型別轉換成對應的引數型別
*
* @param type 引數型別
* @param requestValue 值
* @return 轉換後的Object
*/
public static Object convert(Class<?> type, String requestValue) {
if (isPrimitive(type)) {
if (ValidationUtil.isEmpty(requestValue)) {
return primitiveNull(type);
}
if (type.equals(int.class) || type.equals(Integer.class)) {
return Integer.parseInt(requestValue);
} else if (type.equals(String.class)) {
return requestValue;
} else if (type.equals(Double.class) || type.equals(double.class)) {
return Double.parseDouble(requestValue);
} else if (type.equals(Float.class) || type.equals(float.class)) {
return Float.parseFloat(requestValue);
} else if (type.equals(Long.class) || type.equals(long.class)) {
return Long.parseLong(requestValue);
} else if (type.equals(Boolean.class) || type.equals(boolean.class)) {
return Boolean.parseBoolean(requestValue);
} else if (type.equals(Short.class) || type.equals(short.class)) {
return Short.parseShort(requestValue);
} else if (type.equals(Byte.class) || type.equals(byte.class)) {
return Byte.parseByte(requestValue);
}
return requestValue;
} else {
throw new RuntimeException("count not support non primitive type conversion yet");
}
}
/**
* 判定是否基本資料型別(包括包裝類以及String)
*
* @param type 引數型別
* @return 是否為基本資料型別
*/
private static boolean isPrimitive(Class<?> type) {
return type == boolean.class
|| type == Boolean.class
|| type == double.class
|| type == Double.class
|| type == float.class
|| type == Float.class
|| type == short.class
|| type == Short.class
|| type == int.class
|| type == Integer.class
|| type == long.class
|| type == Long.class
|| type == String.class
|| type == byte.class
|| type == Byte.class
|| type == char.class
|| type == Character.class;
}
}
github地址:https://github.com/wuyiccc/he...