在做一些后台服务的时候,有时候需要一些轻量级的Http入口,以便通过浏览器就能实现便捷的后台功能,例如
1.监控服务运行状态,如服务存在性、版本、配置、性能等
2.手动触发一些功能入口(特别适合测试环境的冒烟测试)
3.支持一些紧急操作,例如手动清缓存,有时候排查问题有用
这些操作通常数量不多,也没什么并发,专门搭一套web框架(如tomcat+spring mvc)有点浪费,一点不封装又不方便。以下用netty+javassist实现一个简单的http服务框架,使得在使用上达到接近spring mvc的体验。这里还使用了spring-bean,但只是为了托管实例方便,如果愿意也可以完全不依赖spring
以下为主要代码实现。首先是server主类,标准的netty server端,没什么特殊的
package com.ximalaya.damus.protocol.rest; import java.io.IOException; import java.net.InetSocketAddress; import org.apache.log4j.Logger; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.handler.codec.http.HttpContentCompressor; import org.jboss.netty.handler.codec.http.HttpRequestDecoder; import org.jboss.netty.handler.codec.http.HttpResponseEncoder; import org.springframework.beans.factory.annotation.Autowired; import com.ximalaya.damus.protocol.exception.RestException; import com.ximalaya.damus.protocol.rest.binding.BindingMeta; import com.ximalaya.damus.protocol.rest.netty.SimpleHttpRequestHandler; public class NettyHttpServer { private ServerBootstrap bootstrap; private final InetSocketAddress host; @Autowired private BindingMeta meta; private Logger logger = Logger.getLogger(getClass()); public NettyHttpServer(String hostname, int port) { host = new InetSocketAddress(hostname, port); } public void start() throws RestException, IOException { bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory()); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("decoder", new HttpRequestDecoder()); pipeline.addLast("encoder", new HttpResponseEncoder()); pipeline.addLast("deflater", new HttpContentCompressor()); pipeline.addLast("handler", new SimpleHttpRequestHandler(meta)); return pipeline; } }); bootstrap.bind(host); logger.info("Start NettyHttpServer at " + host); } public void close() { logger.info("Close NettyHttpServer at " + host); if (bootstrap != null) { bootstrap.shutdown(); bootstrap.releaseExternalResources(); } } }
下面是Handler,主要逻辑就是messageReceived()
package com.ximalaya.damus.protocol.rest.netty; import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK; import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; import org.apache.log4j.Logger; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import com.ximalaya.damus.common.util.JsonUtils; import com.ximalaya.damus.protocol.rest.binding.BindingMeta; public class SimpleHttpRequestHandler extends SimpleChannelUpstreamHandler { private Logger logger = Logger.getLogger(getClass()); private final BindingMeta meta; public SimpleHttpRequestHandler(BindingMeta meta) { this.meta = meta; } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { HttpRequest request = (HttpRequest) e.getMessage(); String uri = request.getUri(); logger.info("Rest Request:" + uri); if (!"/favicon.ico".equals(uri)) { // 过滤静态资源,因为不需要前端页面 Object ret = meta.invoke(request); // 触发controller逻辑 writeResponse(e.getChannel(), ret != null ? ret : "done"); // 返回 } } // 这个方法将返回值以Json方式返回,类似实现Spring的@ResponseBody注解 private void writeResponse(Channel channel, Object obj) { HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); response.setStatus(HttpResponseStatus.OK); response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/json"); String resMsg = JsonUtils.toJsonString(obj); // 序列化方法,不用展开了吧 response.setContent(ChannelBuffers.wrappedBuffer(resMsg.getBytes())); final ChannelFuture future = channel.write(response); future.addListener(ChannelFutureListener.CLOSE); } @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { logger.info("channel closed:" + e.getChannel()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { logger.error("rest error", e.getCause()); // 如果抛异常,就把异常信息返回出去 writeResponse(e.getChannel(), "ERROR: " + e.getCause().getMessage()); } }
接下来就是BindingMeta,这是主要逻辑的实现类,包括Controller信息的收集,RequestMapping字段信息解析,已经Controller触发逻辑。这个类用spring托管只是方便起见
package com.ximalaya.damus.protocol.rest.binding;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javassist.ClassPool;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.LocalVariableAttribute;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import com.ximalaya.damus.common.util.JsonUtils;
import com.ximalaya.damus.protocol.exception.RestException;
import com.ximalaya.damus.protocol.rest.annotation.Rest;
@Component
public class BindingMeta implements ApplicationContextAware {
// Controller方法元数据,每个元素对应一个http请求
private Map<String, Method> apiMap;
// Controller实例,一些单例的集合
private Map<Class<?>, Object> beanMap;
// 各Controller方法参数名集合
private Map<String, String[]> paramNamesMap;
private Logger logger = Logger.getLogger(getClass());
/**
* 初始化以上各属性
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
logger.info("binging REST meta");
apiMap = new HashMap<String, Method>();
beanMap = new HashMap<Class<?>, Object>();
paramNamesMap = new HashMap<String, String[]>();
// Rest是自定义的一个注解类,用于标识http api方法,类似spring的@RequestMapping注解
Map<String, Object> allBeans = applicationContext.getBeansWithAnnotation(Rest.class);
for (Object instance : allBeans.values()) {
// 生成Controller类的实例
Class<?> clz = instance.getClass();
beanMap.put(clz, instance);
// 解析成员方法,也就是各http api
parseMethods(clz);
}
}
private void parseMethods(Class<?> clz) {
for (Method method : clz.getDeclaredMethods()) {
Rest rest = method.getAnnotation(Rest.class);
// 遍历所有带@Rest注解的方法
if (rest != null) {
logger.info("Binding url " + rest.path() + " to " + method);
apiMap.put(rest.path(), method);
// 解析参数名,也就是http请求所带的参数名
String[] paramNames = parseParamNames(method);
paramNamesMap.put(rest.path(), paramNames);
}
}
}
// 这个方法要解析某个方法所有参数的声明名称,这里需要用到javassist的高级反射特性(普通的反射机制是拿不到方法参数声明名称的,只能拿到类型)
private String[] parseParamNames(Method method) {
try {
CtMethod cm = ClassPool.getDefault().get(method.getDeclaringClass().getName())
.getDeclaredMethod(method.getName());
LocalVariableAttribute attr = (LocalVariableAttribute) cm.getMethodInfo()
.getCodeAttribute().getAttribute(LocalVariableAttribute.tag);
String[] paramNames = new String[cm.getParameterTypes().length];
int offset = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < cm.getParameterTypes().length; i++) {
paramNames[i] = attr.variableName(i + offset);
}
return paramNames;
} catch (NotFoundException e) {
logger.error("parseParamNames Error", e);
return new String[] {};
}
}
// 这个就是Handler调用的入口,也就是将HttpRequest映射到对应的方法并映射各参数
public Object invoke(HttpRequest request) throws RestException {
String uri = request.getUri();
int index = uri.indexOf('?');
// 获取请求路径,映射到Controller的方法
String path = index >= 0 ? uri.substring(0, index) : uri;
Method method = apiMap.get(path);
if (method == null) { // 没有注册该方法,直接抛异常
throw new RestException("No method binded for request " + path);
}
try {
Class<?>[] argClzs = method.getParameterTypes(); // 参数类型
Object[] args = new Object[argClzs.length]; // 这里放实际的参数值
String[] paramNames = paramNamesMap.get(path); // 参数名
// 这个是netty封装的url参数解析工具,当然也可以自己split(",")
Map<String, List<String>> requestParams = new QueryStringDecoder(uri).getParameters();
// 逐个把请求参数解析成对应方法参数的类型
for (int i = 0; i < argClzs.length; i++) {
Class<?> argClz = argClzs[i];
String paramName = paramNames[i];
if (!requestParams.containsKey(paramName)
|| CollectionUtils.isEmpty(requestParams.get(paramName))) {
// 没有找到对应参数,则默认取null。愿意的话也可以定义类似@Required或者@DefaultValue之类的注解来设置自定义默认值
args[i] = null;
continue;
}
// 如果带了这个参数,则根据不同的目标类型来解析,先是一些基础类型,这里列的不全,有需要的话可以自己加其他,例如Date
String param = requestParams.get(paramNames[i]).get(0);
if (param == null) {
args[i] = null;
} else if (argClz == HttpRequest.class) {
args[i] = request;
} else if (argClz == long.class || argClz == Long.class) {
args[i] = Long.valueOf(param);
} else if (argClz == int.class || argClz == Integer.class) {
args[i] = Integer.valueOf(param);
} else if (argClz == boolean.class || argClz == Boolean.class) {
args[i] = Boolean.valueOf(param);
} else if (argClz == String.class) {
args[i] = param;
} else {
// 复合类型的话,默认按照Json方式解析。不过这种场景一般也不需要特别复杂的参数
try {
args[i] = JsonUtils.fromJsonString(argClz, param);
} catch (Exception e) {
args[i] = null;
}
}
}
// 最后反射调用方法
Object instance = beanMap.get(method.getDeclaringClass());
return method.invoke(instance, args);
} catch (Exception e) {
throw new RestException(e);
}
}
public Map<String, Method> getApiMap() {
return apiMap;
}
}
相关推荐
标题中的“一款基于Netty+Zookeeper+Spring实现的轻量级Java RPC框架”揭示了这个项目的核心技术栈,它整合了三个在分布式系统中广泛使用的开源库:Netty、Zookeeper和Spring。让我们逐一深入探讨这三个技术以及它们...
1、基于netty+websocket+springboot的实时聊天系统项目源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料...
基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 ...
springboot +netty+activeMq在线客服系统springboot +netty+activeMq在线客服系统springboot +netty+activeMq在线客服系统springboot +netty+activeMq在线客服系统springboot +netty+activeMq在线客服系统springboot...
Netty是一个高性能、异步事件驱动的网络应用程序框架,特别适合用于高性能的网络服务,如WebSocket。它提供了一套强大的事件处理机制,使得开发者可以轻松地处理TCP、UDP和HTTP协议,包括WebSocket通信。Netty底层...
在IT行业中,构建高性能的后台服务以及用户友好的客户端是至关重要的。本项目"Netty+Spring Boot仿微信 全栈开发高性能后台及客户端"旨在教你如何利用Netty和Spring Boot这两个强大的技术栈来实现类似微信的应用。接...
在当前互联网技术高速发展的背景下,高性能、高并发的服务框架成为众多企业和开发者追求的目标。Netty作为一款高性能、异步事件驱动的网络应用框架,结合Thrift这种高效的远程过程调用(RPC)框架,能够有效提升系统...
毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+...
netty案例,netty4.1中级拓展篇六《SpringBoot+Netty+Elasticsearch收集日志信息数据存储》源码 ...
基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式...
MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅消息协议,常用于IoT设备之间的通信,因其低开销和可靠性而受到青睐。在这个项目中,版本3.1.1是MQTT协议的实现,它定义了如何建立连接、发布和...
这个项目可能是一个高性能、轻量级的网络通信应用,利用Spring Boot的便利性和Netty的高效网络处理能力,结合MyBatis对数据库操作的支持,并通过SQL Server存储数据。 描述中提到的"socket"是指应用可能包含了基于...
omen-1.1 自己基于netty开发的服务端,支持spring配置服务器启动模式:http,tcp,websocket等,并支持NIO和OIO方式,项目已应用于生产,可以通过jar形式加入其它项目,业务类实现业务service,启动不依赖于其他应用...
基于springcloud+Netty+MQ+mysql的分布式即时聊天系统源码+项目说明(毕业设计).zip 1、该资源内项目代码经过严格调试,下载即用确保可以运行! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、...
Netty则是一个高性能、异步事件驱动的网络应用程序框架,常用于构建高并发、低延迟的网络服务;MyBatis是一个持久层框架,它支持定制化SQL、存储过程以及高级映射,使得数据库操作更加便捷。现在,我们将详细讨论...
首先,Netty是一个高性能、异步事件驱动的网络应用框架,主要应用于开发高并发、低延迟的网络应用,如服务器和客户端的通信。Netty提供了丰富的API,简化了网络编程,尤其是TCP和UDP协议的处理。在本项目中,Netty...
Netty则是一个高性能、异步事件驱动的网络应用程序框架,常用于构建高度可定制的网络服务器。当Spring Boot与Netty结合使用时,可以创建出高效、稳定的实时消息推送系统。 在"Spring Boot 整合 Netty + WebSocket ...
Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。在本文中,我们将深入探讨如何利用 Netty 和 WebSocket 实现心跳检测和断线重连机制。 首先,我们需要理解 ...