内容简介:最近在用Netty做开发,需要提供一个http web server,供调用方调用。采用Netty本身提供的最开始是通过对Http method及uri 采用多层if else 嵌套判断的方法路由到真正的controller类:在只需提供
最近在用Netty做开发,需要提供一个http web server,供调用方调用。采用Netty本身提供的 HttpServerCodec
handler进行Http协议的解析,但是需要自己提供路由。
最开始是通过对Http method及uri 采用多层if else 嵌套判断的方法路由到真正的controller类:
String uri = request.uri(); HttpMethod method = request.method(); if (method == HttpMethod.POST) { if (uri.startsWith("/login")) { //url参数解析,调用controller的方法 } else if (uri.startsWith("/logout")) { //同上 } } else if (method == HttpMethod.GET) { if (uri.startsWith("/")) { } else if (uri.startsWith("/status")) { } } 复制代码
在只需提供 login
及 logout
API时,代码可以完成功能,可是随着API的数量越来越多,需要支持的方法及uri越来越多, else if
越来越多,代码越来越复杂。
在阿里开发手册中也提到过:
因此首先考虑采用状态设计模式及策略 设计模式 重构。
状态模式
状态模式的角色:
- state状态 表示状态,定义了根据不同状态进行不同处理的接口,该接口是那些处理内容依赖于状态的方法集合,对应实例的state类
- 具体的状态 实现了state接口,对应daystate和nightstate
- context context持有当前状态的具体状态的实例,此外,他还定义了供外部调用者使用的状态模式的接口。
首先我们知道每个http请求都是由method及uri来唯一标识的,所谓路由就是通过这个唯一标识定位到controller类的中的某个方法。
因此把HttpLabel作为状态
@Data @AllArgsConstructor public class HttpLabel { private String uri; private HttpMethod method; } 复制代码
状态接口:
public interface Route { /** * 路由 * * @param request * @return */ GeneralResponse call(FullHttpRequest request); } 复制代码
为每个状态添加状态实现:
public void route() { //单例controller类 final DemoController demoController = DemoController.getInstance(); Map<HttpLabel, Route> map = new HashMap<>(); map.put(new HttpLabel("/login", HttpMethod.POST), demoController::login); map.put(new HttpLabel("/logout", HttpMethod.POST), demoController::login); } 复制代码
接到请求,判断状态,调用不同接口:
public class ServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { @Override public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { String uri = request.uri(); GeneralResponse generalResponse; if (uri.contains("?")) { uri = uri.substring(0, uri.indexOf("?")); } Route route = map.get(new HttpLabel(uri, request.method())); if (route != null) { ResponseUtil.response(ctx, request, route.call(request)); } else { generalResponse = new GeneralResponse(HttpResponseStatus.BAD_REQUEST, "请检查你的请求方法及url", null); ResponseUtil.response(ctx, request, generalResponse); } } } 复制代码
使用状态设计模式重构代码,在增加url时只需要网map里面put一个值就行了。
类似SpringMVC路由功能
后来看了 JAVA反射+运行时注解实现URL路由 发现反射+注解的方式很优雅,代码也不复杂。
下面介绍Netty使用反射实现URL路由。
路由注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { /** * 路由的uri * * @return */ String uri(); /** * 路由的方法 * * @return */ String method(); } 复制代码
扫描classpath下带有 @RequestMapping
注解的方法,将这个方法放进一个路由Map: Map<HttpLabel, Action<GeneralResponse>> httpRouterAction
,key为上面提到过的Http唯一标识 HttpLabel
,value为通过反射调用的方法:
@Slf4j public class HttpRouter extends ClassLoader { private Map<HttpLabel, Action<GeneralResponse>> httpRouterAction = new HashMap<>(); private String classpath = this.getClass().getResource("").getPath(); private Map<String, Object> controllerBeans = new HashMap<>(); @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String path = classpath + name.replaceAll("\\.", "/"); byte[] bytes; try (InputStream ins = new FileInputStream(path)) { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024 * 5]; int b = 0; while ((b = ins.read(buffer)) != -1) { out.write(buffer, 0, b); } bytes = out.toByteArray(); } } catch (Exception e) { throw new ClassNotFoundException(); } return defineClass(name, bytes, 0, bytes.length); } public void addRouter(String controllerClass) { try { Class<?> cls = loadClass(controllerClass); Method[] methods = cls.getDeclaredMethods(); for (Method invokeMethod : methods) { Annotation[] annotations = invokeMethod.getAnnotations(); for (Annotation annotation : annotations) { if (annotation.annotationType() == RequestMapping.class) { RequestMapping requestMapping = (RequestMapping) annotation; String uri = requestMapping.uri(); String httpMethod = requestMapping.method().toUpperCase(); // 保存Bean单例 if (!controllerBeans.containsKey(cls.getName())) { controllerBeans.put(cls.getName(), cls.newInstance()); } Action action = new Action(controllerBeans.get(cls.getName()), invokeMethod); //如果需要FullHttpRequest,就注入FullHttpRequest对象 Class[] params = invokeMethod.getParameterTypes(); if (params.length == 1 && params[0] == FullHttpRequest.class) { action.setInjectionFullhttprequest(true); } // 保存映射关系 httpRouterAction.put(new HttpLabel(uri, new HttpMethod(httpMethod)), action); } } } } catch (Exception e) { log.warn("{}", e); } } public Action getRoute(HttpLabel httpLabel) { return httpRouterAction.get(httpLabel); } } 复制代码
通过反射调用 controller
类中的方法:
@Data @RequiredArgsConstructor @Slf4j public class Action<T> { @NonNull private Object object; @NonNull private Method method; private boolean injectionFullhttprequest; public T call(Object... args) { try { return (T) method.invoke(object, args); } catch (IllegalAccessException | InvocationTargetException e) { log.warn("{}", e); } return null; } 复制代码
ServerHandler.java
处理如下:
//根据不同的请求API做不同的处理(路由分发) Action<GeneralResponse> action = httpRouter.getRoute(new HttpLabel(uri, request.method())); if (action != null) { if (action.isInjectionFullhttprequest()) { ResponseUtil.response(ctx, request, action.call(request)); } else { ResponseUtil.response(ctx, request, action.call()); } } else { //错误处理 generalResponse = new GeneralResponse(HttpResponseStatus.BAD_REQUEST, "请检查你的请求方法及url", null); ResponseUtil.response(ctx, request, generalResponse); } 复制代码
DemoController
方法配置:
@RequestMapping(uri = "/login", method = "POST") public GeneralResponse login(FullHttpRequest request) { User user = JsonUtil.fromJson(request, User.class); log.info("/login called,user: {}", user); return new GeneralResponse(null); } 复制代码
测试结果如下:
完整代码在 github.com/morethink/N…
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。