才接触U3D和Netty,我的理解是netty是对TCP/IP协议的封装,用来对客户端的请求进行处理,而对客户端请求的处理方法只有一个,那么如果客户端请求类型非常之多,这个方法的代码就肯定非常臃肿了,这个时候想到JAVAWEB前端框架和Servlet的设计。
首先Servlet同样涉及到一个分发请求的操作。因为一个Servlet只能处理一个请求,那么想要将一个模型的请求都封装一个Servlet中,就需要在Servlet的service方法或者doGet/doPost方法中做分发处理。此时涉及到一种设计就是,在service/doGet/doPost方法中不做业务的处理,只做请求的分发,将请求分发到同类的其他方法做对应处理。
再谈谈框架,Struts/Springmvc类似,通过类和方法来定义资源名,不同的请求到不同的类对应的方法中,这样给程序员一目了然的映射关系,具体如下:
/user/list:/user可以看成是对应控制器UserController的资源名,/list可以看成是这个UserController类中对应的list方法
这个请求的目的是获取所有的用户对象,做到了一个请求一个处理方法的设计,这才是我想要的。
而在netty中,所有请求都在一个方法channelRead中,无法做到不同的请求有专门的处理方法。所以就有了如下改进:
这里只留处理方法channelRead的代码:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//数据格式如:{type:"/user/login",data:{username:"admin",password:"admin"}}
ByteBuf in = (ByteBuf) msg;
String content = in.toString(CharsetUtil.UTF_8);
//解密
content = DataCodeor.deCodeor(content);
//统一处理请求的方法
String result = MyAnnotationUtil.requestService(content);
//加密
result = DataCodeor.enCodeor(result);
// ctx.writeAndFlush(result);
}
上述代码中的核心代码是
//统一处理请求的方法
String result = MyAnnotationUtil.requestService(content);
这是利用反射机制模拟SpringMVC的请求分发机制:
使用到的知识点包含:
1.自定义注解
2.反射
3.properties资源文件解析
4.JSON格式数据类型
项目结构图先放上
然后先看资源文件中的内容:
#指定控制器所在的包 然后交给注解工具类扫描
my.controller.package=com.yuanmaxinxi.controller
#控制器是单例还是多例 默认true单例
my.single=true
上述配置跟Spring的注解扫描配置类似,指定一个控制器所在的基础包,自动扫描这个包下的子包和子包中的控制器类
接下来看自定义注解类:这个注解类可以理解为SpringMVC中的@RequestMapping注解
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
@Target 不指定Target代表可以在任何地方可以添加此注解 但是使用时只在类上和方法上使用 模拟Springmvc的RequeMapping
*/
/**
* 自定义注解类 用来标记控制器类
* @author 微笑ぃ一刀
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestAnnotation {
/**
* 资源名
* @return
*/
String value();//只有一个字段 用来接收用户请求的资源 资源类似 /user/login 格式
}
再看看StringUtil工具类,这个类很简单,只是自己写的一个验证字符串非空的方法:
public class StringUtil {
public static boolean isNotNullOrEmpty(String str) {
return str!=null&&!"".equals(str.trim());
}
}
接下来就是核心类:
package com.yuanmaxinxi.my.util;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yuanmaxinxi.my.annotation.RequestAnnotation;
import com.yuanmaxinxi.util.StringUtil;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
/**
* 注释工具类 用来解析注解类
* @author 微笑ぃ一刀
* 此类拥有如下功能
* 1.解析properties文件 得到Controller控制器类全限定名(跟Spring类似配置,指定扫描的包)
* 2.得到该包下所有的添加了自定义注解的控制器类(可能有子包) 装进集合
* 3.得到添加了自定义注解的方法 装进集合
* 4.利用反射创建控制器对象,并反射执行方法(这个功能不应该这个类有,但还是装在这个类)
*/
public class MyAnnotationUtil {
//装类+方法资源名-->类+方法的缓存
private static Map<String,ClassAndMethodDTO> map = new HashMap<>();
//控制器是否单例
private static boolean isSingle = true;//默认为true
//控制器类对象
private static Map<Class,Object> controllerClasses =new HashMap<>();
/**
* 获取方法的参数名对应的值
* @param method 指定
* @return
*/
private static List<Object> getMethodParameters(Class clz,Method method,JSONObject json){
List<Object> values = new ArrayList<>();
ClassPool pool = ClassPool.getDefault();
try {
CtClass ctClass = pool.get(clz.getName());
CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName());
// 使用javassist的反射方法的参数名
MethodInfo methodInfo = ctMethod.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
.getAttribute(LocalVariableAttribute.tag);
if (attr != null) {
CtClass[] types = ctMethod.getParameterTypes();
// 非静态的成员函数的第一个参数是this
int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
for (int i = 0; i < types.length; i++) {
//参数名
String name = attr.variableName(i + pos);
//参数类型
String type = types[i].getName();
//解析值
if (type.contains("String")) {
values.add(json.getString(name));
}
if (type.contains("Boolean")) {
values.add(json.getBoolean(name));
}
if (type.contains("boolean")) {
values.add(json.getBooleanValue(name));
}
if (type.contains("Integer")) {
values.add(json.getInteger(name));
}
if (type.contains("int")) {
values.add(json.getIntValue(name));
}
if (type.contains("Long")) {
values.add(json.getLong(name));
}
if (type.contains("long")) {
values.add(json.getLongValue(name));
}
if (type.contains("Short")) {
values.add(json.getShort(name));
}
if (type.contains("short")) {
values.add(json.getShortValue(name));
}
if (type.contains("Byte")) {
values.add(json.getByte(name));
}
if (type.contains("byte")) {
values.add(json.getByteValue(name));
}
if (type.contains("Char")) {
values.add(json.getString(name).charAt(0));
}
if (type.contains("Character")) {
values.add(new Character(json.getString(name).charAt(0)));
}
if (type.contains("Float")) {
values.add(json.getFloat(name));
}
if (type.contains("float")) {
values.add(json.getFloatValue(name));
}
if (type.contains("Double")) {
values.add(json.getDouble(name));
}
if (type.contains("double")) {
values.add(json.getDoubleValue(name));
}
}
}
} catch (NotFoundException e) {
e.printStackTrace();
}
return values;
}
/**
* 供外部调用的方法,具备如下功能
* A.传进来一个加密的字符串,解析出需要的如下内容:
* 1.请求资源 找到那个控制器类的那个方法
* 2.参数 对应类的对应方法需要的参数
* B.通过反射创建对应控制器对象
* C.调用控制器类的方法
*
* @return
*/
public static String requestService(String content) {
//转换JSON对象
JSONObject json = (JSONObject)JSON.parse(content);
//获取 资源名
String type= (String)json.get("type");
//将类的资源名和方法的进行拼接
ClassAndMethodDTO dto = map.get(type);
if (dto==null) {
return JSON.toJSONString(ResultDTO.newInstrance("404", "找不到资源:"+type));
}
try {
Class clz = dto.getClz();
//创建类的对象
Object obj = null;
//如果是单例 就去缓存拿一次
if (isSingle) {
//缓存中取一次
obj = controllerClasses.get(clz);
if (obj==null) {
//如果缓存中没有就创建对象
obj = clz.newInstance();
//放进缓存
controllerClasses.put(clz, obj);
}
}else {
//如果是多例 每次都创建新的对象
obj = clz.newInstance();
}
Method method = dto.getMethod();
//https://blog.csdn.net/wwwwenl/article/details/53427039
//获取参数名称参考上述链接博客的javassist方式
List<Object> parameters = getMethodParameters(clz,method,json);
//执行方法
Object result = method.invoke(obj,parameters.toArray());
JSON.toJSONString(result);
} catch (Exception e) {
return JSON.toJSONString(ResultDTO.newInstrance("500",e.getMessage()));
}
return null;
}
static {
//解析资源文件得到资源对象
Properties prop = parsePackAgeByProperties("resource/my.properties");
//得到是否单例
String single = prop.getProperty("my.single");
if (StringUtil.isNotNullOrEmpty(single)) {
isSingle = Boolean.parseBoolean(single);
}
//得到基础控制器包
String basePackage=prop.getProperty("my.controller.package");
//解析失败
if (!StringUtil.isNotNullOrEmpty(basePackage)) {
throw new RuntimeException("解析properties资源文件失败,请检查是否是properties文件路径有错或资源文件中属性名是否是my.controller.package");
}
//拿到基础包之后,去得到所有Controller类
List<Class> clzes = ClassUtil.parseAllController(basePackage);
//创建一个临时变量用来装类的资源,只在当前静态代码块验证类资源是否唯一
List<String> classTypes = new ArrayList<>();
//迭代所有全限定名
for (Class clz : clzes) {
//判断是否有自定义注解
if(clz.isAnnotationPresent(RequestAnnotation.class)) {
//获取自定义注解
RequestAnnotation annotation = (RequestAnnotation)clz.getAnnotation(RequestAnnotation.class);
//获取value值
String value = annotation.value();
//先获取map中是否已经存在了此资源名
if (classTypes.contains(value)) {
throw new RuntimeException("控制器类资源"+value+"已经存在,不能重复哦!");
}
//将类资源装进临时变量,提供下次循环判断
classTypes.add(value);
//获取类的所有方法
Method[] methods = clz.getMethods();
//迭代方法
for (Method method : methods) {
//判断方法是否有注解
if(method.isAnnotationPresent(RequestAnnotation.class)) {
//获取自定义注解
RequestAnnotation methodAnnotation = (RequestAnnotation)method.getAnnotation(RequestAnnotation.class);
//获取value值
String methodValue = methodAnnotation.value();
//判断资源名是否重复
String type = value+methodValue;
ClassAndMethodDTO sysDto = map.get(type);
if (sysDto!=null) {
throw new RuntimeException("方法资源"+type+"已经存在,不能重复哦!");
}
//添加类名资源+方法资源--类+方法DTO
ClassAndMethodDTO camdto = new ClassAndMethodDTO();
camdto.setClz(clz);
camdto.setMethod(method);
map.put(type, camdto);
}
}
}
}
}
/**
* 根据资源文件的url获取资源值
* @param url
* @return
*/
private static Properties parsePackAgeByProperties(String url) {
try {
Properties prop = new Properties();
prop.load(new FileInputStream(url));
return prop;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String s = MyAnnotationUtil.requestService("{type:\"/user/login\",username:\"123\",password:\"123\"}");
System.err.println(s);
}
}
这个类有个主方法对这个机制进行测试,测试的类是UserController中的登录功能:
public static void main(String[] args) {
//数据格式定义好之后就不能改变了,客户端必须通过这个数据格式进行数据传输
String s = MyAnnotationUtil.requestService("{type:\"/user/login\",username:\"123\",password:\"123\"}");
System.err.println(s);
}
import com.yuanmaxinxi.my.annotation.RequestAnnotation;
@RequestAnnotation("/user")
public class UserController {
@RequestAnnotation("/login")
public void login(int username,int password) {
System.err.println(username+"----"+password);
}
}
得到如下结果:在分发类中自动创建控制器对象,自动调用业务处理方法,并且从请求内容中解析出参数,调用方法是自动传参,这样做完之后,我们也能像使用SpringMVC一样专注于业务的处理就行了,而不再去纠结协议本身的事情。
接来下是一些小类,在核心类中需要用到这些小类:
import java.lang.reflect.Method;
/**
这个类只是一个包装类
*/
@SuppressWarnings("rawtypes")
public class ClassAndMethodDTO {
private Class clz;
private Method method;
public Class getClz() {
return clz;
}
public void setClz(Class clz) {
this.clz = clz;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
}
package com.yuanmaxinxi.my.util;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* 这个类是根据一个公共父级包得到所有子级包下所有控制器类的全限定名的工具类
* @author 微笑ぃ一刀
*功能:
*1.获取一个父级包下所有类的全限定名封装的Class对象
*/
@SuppressWarnings("rawtypes")//去掉泛型警告
public class ClassUtil {
public static List<Class> parseAllController(String basePackage){
List<Class> clzes = new ArrayList<>();
String path = basePackage.replace(".", "/");
//获取此包在磁盘的位置
URL url = Thread.currentThread().getContextClassLoader().getResource(path);
File file = new File(url.getPath());
getClass(file,clzes,basePackage);
return clzes;
}
private static void getClass(File file, List<Class> clzes,String packAgeName) {
//文件存在
if (file.exists()) {
//是文件
if (file.isFile()) {
try {
String className = null;
if (packAgeName.contains(".class")) {
className = packAgeName.replace(".class", "");
}else {
className = (packAgeName+"."+file.getName()).replace(".class", "");
}
clzes.add(Class.forName(className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//是目录
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String packAge=packAgeName+"."+f.getName();
getClass(f, clzes, packAge);
}
}
}
}
}
package com.yuanmaxinxi.my.util;
/**
* 对数据包加密 解密的类
* @author 微笑ぃ一刀
*/
public class DataCodeor {
/**
* 加密 具体实现后面再写
* @param str
* @return
*/
public static String enCodeor(String str) {
return str;
}
/**
* 解密
* @param str
* @return
*/
public static String deCodeor(String str) {
return str;
}
}
package com.yuanmaxinxi.my.util;
/**
* 结果的封装类 单例 饿汉
* @author 微笑ぃ一刀
*
*/
public class ResultDTO {
private String code;//访问代码 200 成功 404 找不到资源 500 服务器异常
private String msg;
private static ResultDTO dto = new ResultDTO();
private ResultDTO() {
}
public static ResultDTO newInstrance(String code,String msg) {
dto.setMsg(msg);
dto.setCode(code);
return dto;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public static ResultDTO getDto() {
return dto;
}
public static void setDto(ResultDTO dto) {
ResultDTO.dto = dto;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
下面是大致的访问的流程:只要从channelRead方法的下面这句代码开始读就能看懂,注释应该非常详细了。需要注意的是:MyAnnotationUtil这个类的静态代码块中做了一些核心的基础的事情。
String result = MyAnnotationUtil.requestService(content);