Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.litongjava.tio.boot.http.handler.controller.TioBootHttpControllerRouter Maven / Gradle / Ivy
package com.litongjava.tio.boot.http.handler.controller;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.esotericsoftware.reflectasm.MethodAccess;
import com.litongjava.annotation.Delete;
import com.litongjava.annotation.Get;
import com.litongjava.annotation.Post;
import com.litongjava.annotation.Put;
import com.litongjava.annotation.RequestPath;
import com.litongjava.constatns.ServerConfigKeys;
import com.litongjava.controller.ControllerFactory;
import com.litongjava.controller.DefaultControllerFactory;
import com.litongjava.controller.PathUnitVo;
import com.litongjava.controller.VariablePathVo;
import com.litongjava.tio.boot.utils.ParameterNameUtil;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.hutool.ArrayUtil;
import com.litongjava.tio.utils.hutool.ClassScanAnnotationHandler;
import com.litongjava.tio.utils.hutool.ClassUtil;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.hutool.StrUtil;
import com.litongjava.tio.utils.json.MapJsonUtils;
/**
* @author tanyaowu 2017年7月1日 上午9:05:30
*/
public class TioBootHttpControllerRouter {
private static Logger log = LoggerFactory.getLogger(TioBootHttpControllerRouter.class);
public static final String META_PATH_KEY = "TIO_HTTP_META_PATH";
/**
* 路径和对象映射
* key: /user
* value: object
*/
public final Map PATH_BEAN_MAP = new TreeMap<>();
/**
* class和对象映射
* key: XxxController.class
* value: XxxController.class对应的实例对象
*/
public static final Map, Object> CLASS_BEAN_MAP = new HashMap<>();
/**
* bean和MethodAccess映射
* key: XxxController.class对应的实例对象
* value: MethodAccess
*/
public static final Map BEAN_METHODACCESS_MAP = new HashMap<>();
/**
* 路径和class映射
* 只是用来打印的
* key: /user
* value: Class
*/
public static final Map> PATH_CLASS_MAP = new TreeMap<>();
/**
* 路径和class映射
* key: class
* value: /user
*/
public static final Map, String> CLASS_PATH_MAP = new HashMap<>();
/**
* Method路径映射
* key: /user/update,包含forward的路径
* value: method
*/
public final Map PATH_METHOD_MAP = new TreeMap<>();
/**
* 方法参数名映射
* key: method
* value: ["id", "name", "scanPackages"]
*/
public final Map METHOD_PARAM_NAME_MAP = new HashMap<>();
/**
* 方法和参数类型映射
* key: method
* value: [String.class, int.class]
*/
public final Map[]> METHOD_PARAM_TYPE_MAP = new HashMap<>();
/**
* path跟forward映射
* key: 原访问路径
* value: forward后的路径
* 譬如:原来的访问路径是/user/123,forward是/user/getById,这个相当于是一个rewrite的功能,对外路径要相对友好,对内路径一般用于业务更便捷地处理
*/
public final Map PATH_FORWARD_MAP = new HashMap<>();
/**
* 方法和对象映射
* key: method
* value: bean
*/
public final Map METHOD_BEAN_MAP = new HashMap<>();
/**
* Method路径映射
* 只是用于打印日志
* key: /user/update
* value: method string
*/
public final Map PATH_METHOD_STR_MAP = new TreeMap<>();
/**
* 含有路径变量的请求
* value: VariablePathVo
*/
public final Map VARIABLE_PATH_MAP = new TreeMap<>();
/**
* 含有路径变量的请求
* 只是用于打印日志
* key: 配置的路径/user/{userid}
* value: method string
*/
public final Map VARIABLE_PATH_METHOD_STR_MAP = new TreeMap<>();
private final StringBuilder errorStr = new StringBuilder();
private List> scannedClasses = new ArrayList<>();
public TioBootHttpControllerRouter() {
}
/**
*
* @param scanPackages
*/
public TioBootHttpControllerRouter(String[] scanPackages) {
this(scanPackages, null);
}
public TioBootHttpControllerRouter(String scanPackage) {
this(scanPackage, null);
}
public TioBootHttpControllerRouter(String[] scanPackages, ControllerFactory controllerFactory) {
addRoutes(scanPackages, controllerFactory);
}
public TioBootHttpControllerRouter(String scanPackage, ControllerFactory controllerFactory) {
this(new String[] { scanPackage }, controllerFactory);
}
//
public TioBootHttpControllerRouter(Class>[] scanRootClasses) {
this(toPackages(scanRootClasses), null);
}
public TioBootHttpControllerRouter(Class> scanRootClasse) {
this(scanRootClasse.getPackage().getName(), null);
}
public TioBootHttpControllerRouter(Class>[] scanRootClasses, ControllerFactory controllerFactory) {
addRoutes(toPackages(scanRootClasses), controllerFactory);
}
public TioBootHttpControllerRouter(Class> scanRootClasse, ControllerFactory controllerFactory) {
this(new String[] { scanRootClasse.getPackage().getName() }, controllerFactory);
}
/**
* 根据 扫描的到的scannedClasses 添加路由
*
* @param scannedClasses
* @param controllerFactory
*/
public void addControllers(List> scannedClasses) {
if (scannedClasses == null || scannedClasses.size() < 1) {
return;
} else {
this.scannedClasses.addAll(scannedClasses);
}
}
public void scan(ControllerFactory controllerFactory) {
for (Class> clazz : scannedClasses) {
this.processClazz(clazz, controllerFactory);
}
scannedClasses.clear();
this.afterProcessClazz();
}
public static String[] toPackages(Class>[] scanRootClasses) {
String[] scanPackages = new String[scanRootClasses.length];
int i = 0;
for (Class> clazz : scanRootClasses) {
scanPackages[i++] = clazz.getPackage().getName();
}
return scanPackages;
}
/**
* 添加路由
*
* @param scanPackages
* @author tanyaowu
*/
public void addRoutes(String[] scanPackages) {
addRoutes(scanPackages, null);
}
/**
* 添加路由
*
* @param scanPackages
* @param controllerFactory
* @author tanyaowu
*/
public void addRoutes(String[] scanPackages, ControllerFactory controllerFactory) {
if (controllerFactory == null) {
controllerFactory = DefaultControllerFactory.me;
}
ControllerFactory controllerFactory1 = controllerFactory;
if (scanPackages != null) {
for (String pkg : scanPackages) {
try {
ClassUtil.scanPackage(pkg, new ClassScanAnnotationHandler(RequestPath.class) {
@Override
public void handlerAnnotation(Class> clazz) {
processClazz(clazz, controllerFactory1);
}
});
} catch (Exception e) {
log.error(e.toString(), e);
}
}
afterProcessClazz();
}
}
public void processClazz(Class> clazz, ControllerFactory controllerFactory) {
String beanPath = null;
RequestPath requestPath = clazz.getAnnotation(RequestPath.class);
if (requestPath != null) {
beanPath = requestPath.value();
} else {
Get get = clazz.getAnnotation(Get.class);
if (get != null) {
beanPath = get.value();
} else {
Post post = clazz.getAnnotation(Post.class);
if (post != null) {
beanPath = post.value();
} else {
Put put = clazz.getAnnotation(Put.class);
if (put != null) {
beanPath = put.value();
} else {
Delete delete = clazz.getAnnotation(Delete.class);
if (delete != null) {
beanPath = delete.value();
} else {
return;
}
}
}
}
}
try {
Object bean = controllerFactory.getInstance(clazz);
if (bean != null) {
MethodAccess access = MethodAccess.get(clazz);
BEAN_METHODACCESS_MAP.put(bean, access);
}
Object obj = PATH_BEAN_MAP.get(beanPath);
if (obj != null) {
if (!"".equals(beanPath)) {
log.error("mapping[{}] already exists in class [{}]", beanPath, obj.getClass().getName());
errorStr.append("mapping[" + beanPath + "] already exists in class [" + obj.getClass().getName() + "]\r\n\r\n");
} else {
PATH_BEAN_MAP.put(beanPath, bean);
CLASS_BEAN_MAP.put(clazz, bean);
PATH_CLASS_MAP.put(beanPath, clazz);
CLASS_PATH_MAP.put(clazz, beanPath);
}
}
Method[] methods = clazz.getDeclaredMethods();
this.processClazzMethods(bean, beanPath, methods);
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* 处理Controller的所有Method
*
* @param bean
* @param beanPath
* @param methods
*/
private void processClazzMethods(Object bean, String beanPath, Method[] methods) {
c: for (Method method : methods) {
int modifiers = method.getModifiers();
if (Modifier.isPrivate(modifiers)) {
continue c;
}
Class> returnType = method.getReturnType();
if (returnType == Void.TYPE) {
continue c;
}
// 检查方法上的注解
String methodPath = null;
String httpMethodType = null;
String forwardPath = null;
// 处理 @RequestPath
RequestPath requestPath = method.getAnnotation(RequestPath.class);
if (requestPath != null) {
methodPath = requestPath.value();
forwardPath = requestPath.forward();
}
// 处理 @Get
Get get = method.getAnnotation(Get.class);
if (get != null) {
methodPath = get.value();
forwardPath = get.forward();
httpMethodType = "GET";
}
// 处理 @Post
Post post = method.getAnnotation(Post.class);
if (post != null) {
methodPath = post.value();
forwardPath = post.forward();
httpMethodType = "POST";
}
// 处理 @Put
Put put = method.getAnnotation(Put.class);
if (put != null) {
methodPath = put.value();
forwardPath = put.forward();
httpMethodType = "PUT";
}
// 处理 @Delete
Delete delete = method.getAnnotation(Delete.class);
if (delete != null) {
methodPath = delete.value();
forwardPath = delete.forward();
httpMethodType = "DELETE";
}
// 如果没有任何相关注解,则跳过
if (methodPath == null) {
// 默认使用方法名作为路径
methodPath = "/" + method.getName();
}
String completePath = beanPath + methodPath;
Class>[] parameterTypes = method.getParameterTypes();
try {
String[] parameterNames = ParameterNameUtil.getParameterNames(method);
// 检查路径是否已存在
String key = null;
String existingMethodStr = null;
if (httpMethodType == null) {
key = completePath;
existingMethodStr = PATH_METHOD_STR_MAP.get(key);
} else {
key = httpMethodType + " " + completePath;
existingMethodStr = PATH_METHOD_STR_MAP.get(key);
}
if (existingMethodStr != null) {
log.error("mapping[{}] already exists in method [{}]", completePath, existingMethodStr);
errorStr.append("mapping[" + completePath + "] already exists in method [" + existingMethodStr + "]\r\n\r\n");
continue c;
}
// 构建方法字符串
String methodStr = methodToStr(method, parameterNames);
// 根据 HTTP 方法类型存储不同的映射
PATH_METHOD_MAP.put(key, method);
PATH_METHOD_STR_MAP.put(key, methodStr);
METHOD_PARAM_NAME_MAP.put(method, parameterNames);
METHOD_PARAM_TYPE_MAP.put(method, parameterTypes);
if (forwardPath != null && !forwardPath.trim().isEmpty()) {
String forwardKey = httpMethodType + " " + forwardPath;
PATH_FORWARD_MAP.put(key, forwardPath);
PATH_METHOD_STR_MAP.put(forwardKey, methodStr);
PATH_METHOD_MAP.put(forwardKey, method);
}
METHOD_BEAN_MAP.put(method, bean);
} catch (Throwable e) {
log.error(e.toString(), e);
}
}
}
public void afterProcessClazz() {
processVariablePath();
printMapping();
}
private void printMapping() {
String pathClassMapStr = MapJsonUtils.toPrettyJson(PATH_CLASS_MAP);
String pathMethodstrMapStr = MapJsonUtils.toPrettyJson(PATH_METHOD_STR_MAP);
String variablePathMethodstrMapStr = MapJsonUtils.toPrettyJson(VARIABLE_PATH_METHOD_STR_MAP);
if (EnvUtils.getBoolean(ServerConfigKeys.SERVER_HTTP_CONTROLLER_PRINTMAPPING, true)) {
if (PATH_CLASS_MAP.size() > 0) {
log.info("class mapping\r\n{}", pathClassMapStr);
}
if (PATH_METHOD_STR_MAP.size() > 0) {
log.info("method mapping\r\n{}", pathMethodstrMapStr);
}
if (VARIABLE_PATH_METHOD_STR_MAP.size() > 0) {
log.info("variable path mapping\r\n{}", variablePathMethodstrMapStr);
}
}
if (EnvUtils.getBoolean(ServerConfigKeys.SERVER_HTTP_CONTROLLER_WRITEMAPPING, false)) {
try {
FileUtil.writeString(pathClassMapStr, "tio_boot_path_class.json", "utf-8");
FileUtil.writeString(pathMethodstrMapStr, "tio_boot_path_method.json", "utf-8");
FileUtil.writeString(variablePathMethodstrMapStr, "tio_boot_variablepath_method.json", "utf-8");
if (errorStr.length() > 0) {
FileUtil.writeString(errorStr.toString(), "tio_boot_error.txt", "utf-8");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 处理有变量的路径
*
* @param PATH_METHOD_MAP
*/
private void processVariablePath() {
Set> set = PATH_METHOD_MAP.entrySet();
for (Entry entry : set) {
String key = entry.getKey(); // e.g., "PUT /users/{id}"
Method method = entry.getValue();
if (StrUtil.contains(key, '{') && StrUtil.contains(key, '}')) {
// Extract HTTP method and path
String httpMethod = null;
String path = null;
int spaceIndex = key.indexOf(' ');
if (spaceIndex == -1) {
path = key;
} else {
httpMethod = key.substring(0, spaceIndex).toUpperCase();
path = key.substring(spaceIndex + 1);
}
String[] pathUnits = StrUtil.split(path, "/");
PathUnitVo[] pathUnitVos = new PathUnitVo[pathUnits.length];
boolean isVarPath = false;
for (int i = 0; i < pathUnits.length; i++) {
PathUnitVo pathUnitVo = new PathUnitVo();
String pathUnit = pathUnits[i];
if (StrUtil.contains(pathUnit, '{') || StrUtil.contains(pathUnit, '}')) {
if (StrUtil.startWith(pathUnit, "{") && StrUtil.endWith(pathUnit, "}")) {
String varName = StrUtil.subBetween(pathUnit, "{", "}");
if (ArrayUtil.contains(METHOD_PARAM_NAME_MAP.get(method), varName)) {
isVarPath = true;
pathUnitVo.setVar(true);
pathUnitVo.setPath(varName);
} else {
log.error("path:{}, method [{}] does not contain parameter named {}", path, method, varName);
errorStr.append("path:{" + path + "}, method [" + method + "] does not contain parameter named " + varName + "\r\n\r\n");
}
} else {
pathUnitVo.setVar(false);
pathUnitVo.setPath(pathUnit);
}
} else {
pathUnitVo.setVar(false);
pathUnitVo.setPath(pathUnit);
}
pathUnitVos[i] = pathUnitVo;
}
if (isVarPath) {
VariablePathVo variablePathVo = new VariablePathVo(path, method, pathUnitVos);
addVariablePathVo(httpMethod, pathUnits.length, variablePathVo);
}
}
}
}
/**
* 根据class获取class对应的bean
*
* @param
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public static T getController(Class clazz) {
return (T) CLASS_BEAN_MAP.get(clazz);
}
public static String getRequestPath(Class> clazz) {
return CLASS_PATH_MAP.get(clazz);
}
/**
* @param httpMethod
* @param pathUnitCount
* @param variablePathVo
*/
private void addVariablePathVo(String httpMethod, Integer pathUnitCount, VariablePathVo variablePathVo) {
String key = null;
if (httpMethod != null) {
key = httpMethod.toUpperCase() + " " + pathUnitCount;
} else {
key = pathUnitCount + "";
}
VariablePathVo[] existValue = VARIABLE_PATH_MAP.get(key);
if (existValue == null) {
existValue = new VariablePathVo[] { variablePathVo };
VARIABLE_PATH_MAP.put(key, existValue);
} else {
VariablePathVo[] newExistValue = new VariablePathVo[existValue.length + 1];
System.arraycopy(existValue, 0, newExistValue, 0, existValue.length);
newExistValue[newExistValue.length - 1] = variablePathVo;
VARIABLE_PATH_MAP.put(key, newExistValue);
}
String methodStr = methodToStr(variablePathVo.getMethod(), METHOD_PARAM_NAME_MAP.get(variablePathVo.getMethod()));
if (httpMethod != null) {
VARIABLE_PATH_METHOD_STR_MAP.put(httpMethod + " " + variablePathVo.getPath(), methodStr);
} else {
VARIABLE_PATH_METHOD_STR_MAP.put(variablePathVo.getPath(), methodStr);
}
}
private String methodToStr(Method method, String[] parameterNames) {
return method.getDeclaringClass().getName() + "." + method.getName() + "(" + ArrayUtil.join(parameterNames, ",") + ")";
}
public Method getActionByPath(String path, String httpMethod, HttpRequest request) {
String key = httpMethod.toUpperCase() + " " + path;
Method method = PATH_METHOD_MAP.get(key);
if (method != null) {
return method;
}
method = PATH_METHOD_MAP.get(path);
if (method != null) {
return method;
}
String[] pathUnitsOfRequest = StrUtil.split(path, "/"); // e.g., ["", "users", "1"]
String varPathKey = httpMethod.toUpperCase() + " " + pathUnitsOfRequest.length;
VariablePathVo[] variablePathVos = VARIABLE_PATH_MAP.get(varPathKey);
if (variablePathVos == null) {
variablePathVos = VARIABLE_PATH_MAP.get(pathUnitsOfRequest.length + "");
}
if (variablePathVos != null) {
TreeMap matched = new TreeMap<>();
for (VariablePathVo variablePathVo : variablePathVos) {
PathUnitVo[] pathUnitVos = variablePathVo.getPathUnits();
boolean isMatch = true;
Integer fixedSegments = 0;
for (int i = 0; i < pathUnitVos.length; i++) {
PathUnitVo pathUnitVo = pathUnitVos[i];
String pathOfVo = pathUnitVo.getPath();
String pathUnitOfRequest = pathUnitsOfRequest[i];
if (pathUnitVo.isVar()) {
request.addParam(pathOfVo, pathUnitOfRequest);
} else {
if (!StrUtil.equals(pathOfVo, pathUnitOfRequest)) {
isMatch = false;
break;
} else {
fixedSegments++;
}
}
}
if (isMatch) {
matched.put(fixedSegments, variablePathVo);
}
}
if (matched.size() > 0) {
Integer maxKey = matched.lastKey();
VariablePathVo variablePathVo = matched.get(maxKey);
String metapath = variablePathVo.getPath();
String forward = PATH_FORWARD_MAP.get(httpMethod.toUpperCase() + " " + metapath);
if (StrUtil.isNotBlank(forward)) {
request.requestLine.path = forward;
}
method = variablePathVo.getMethod();
return method;
}
}
return null;
}
}