`
distantlight1
  • 浏览: 44274 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

netty+javassist轻量级http服务&RequestMapping框架

 
阅读更多

在做一些后台服务的时候,有时候需要一些轻量级的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;
    }
}
 

 

 

@Rest注解类
package com.ximalaya.damus.protocol.rest.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Rest {
    String path() default "";
}
 
最后是Controller方法,以下是一个最简单的demo,可以看做是服务的封面。也可以自己实现其他的Controller,代码风格与spring mvc类似
package com.ximalaya.damus.actuary.rest;

import java.lang.reflect.Method;
import java.util.Map;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import com.ximalaya.damus.protocol.rest.annotation.Rest;
import com.ximalaya.damus.protocol.rest.binding.BindingMeta;

@Component
@Rest
public class IndexController implements ApplicationContextAware {

    @Autowired
    private Config config;
    @Autowired
    private BindingMeta bindingMeta;

    @Rest(path = "/")
    public String index() {
        return "My-Project v0.0.1 www.ximalaya.com";
    }

    @Rest(path = "/help")
    public Map<String, Method> apiHelp() {
        // help方法,展现所支持的http方法,也就是api信息
        return bindingMeta.getApiMap();
    }

    @Rest(path = "/config")
    public Config listConfig() {
        // 自己封装一些配置参数信息
        return config;
    }
}
 
这样子框架就搭好了,用的时候调用NettyHttpServer.start(),然后就可以在浏览器访问了,对于非web的后台服务还是挺方便的。最后上张效果图


 
  • 大小: 36.7 KB
分享到:
评论

相关推荐

    一款基于Netty+Zookeeper+Spring实现的轻量级Java RPC框架。提供服务注册,发现,负载均衡,.zip

    标题中的“一款基于Netty+Zookeeper+Spring实现的轻量级Java RPC框架”揭示了这个项目的核心技术栈,它整合了三个在分布式系统中广泛使用的开源库:Netty、Zookeeper和Spring。让我们逐一深入探讨这三个技术以及它们...

    基于netty+websocket+springboot的实时聊天系统项目源码.zip

    1、基于netty+websocket+springboot的实时聊天系统项目源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料...

    基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究

    基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 ...

    springboot-nettysocketio +netty+activeMq在线客服系统

    springboot +netty+activeMq在线客服系统springboot +netty+activeMq在线客服系统springboot +netty+activeMq在线客服系统springboot +netty+activeMq在线客服系统springboot +netty+activeMq在线客服系统springboot...

    springboot+netty+websocket+redis

    Netty是一个高性能、异步事件驱动的网络应用程序框架,特别适合用于高性能的网络服务,如WebSocket。它提供了一套强大的事件处理机制,使得开发者可以轻松地处理TCP、UDP和HTTP协议,包括WebSocket通信。Netty底层...

    Netty+Spring Boot仿微信 全栈开发高性能后台及客户端

    在IT行业中,构建高性能的后台服务以及用户友好的客户端是至关重要的。本项目"Netty+Spring Boot仿微信 全栈开发高性能后台及客户端"旨在教你如何利用Netty和Spring Boot这两个强大的技术栈来实现类似微信的应用。接...

    netty+thrift高并发高性能

    在当前互联网技术高速发展的背景下,高性能、高并发的服务框架成为众多企业和开发者追求的目标。Netty作为一款高性能、异步事件驱动的网络应用框架,结合Thrift这种高效的远程过程调用(RPC)框架,能够有效提升系统...

    毕设项目:基于netty+websocket+springboot的实时聊天系统.zip

    毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+...

    netty案例,netty4.1中级拓展篇六《SpringBoot+Netty+Es信息数据存储》源码

    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的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式...

    基于netty+mqtt3.1.1+springboot+jdk8 实现的 mqtt 服务端跟客户端.zip

    MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅消息协议,常用于IoT设备之间的通信,因其低开销和可靠性而受到青睐。在这个项目中,版本3.1.1是MQTT协议的实现,它定义了如何建立连接、发布和...

    springboot+netty+mybatis+sqlserver

    这个项目可能是一个高性能、轻量级的网络通信应用,利用Spring Boot的便利性和Netty的高效网络处理能力,结合MyBatis对数据库操作的支持,并通过SQL Server存储数据。 描述中提到的"socket"是指应用可能包含了基于...

    netty+spring服务端-omen-1.1

    omen-1.1 自己基于netty开发的服务端,支持spring配置服务器启动模式:http,tcp,websocket等,并支持NIO和OIO方式,项目已应用于生产,可以通过jar形式加入其它项目,业务类实现业务service,启动不依赖于其他应用...

    基于springcloud+Netty+MQ+mysql的分布式即时聊天系统源码+项目说明(毕业设计).zip

    基于springcloud+Netty+MQ+mysql的分布式即时聊天系统源码+项目说明(毕业设计).zip 1、该资源内项目代码经过严格调试,下载即用确保可以运行! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、...

    spring+netty+mybatis整合实例

    Netty则是一个高性能、异步事件驱动的网络应用程序框架,常用于构建高并发、低延迟的网络服务;MyBatis是一个持久层框架,它支持定制化SQL、存储过程以及高级映射,使得数据库操作更加便捷。现在,我们将详细讨论...

    netty+kafka+storm

    首先,Netty是一个高性能、异步事件驱动的网络应用框架,主要应用于开发高并发、低延迟的网络应用,如服务器和客户端的通信。Netty提供了丰富的API,简化了网络编程,尤其是TCP和UDP协议的处理。在本项目中,Netty...

    Spring Boot 整合 Netty + WebSocket 实时消息推送

    Netty则是一个高性能、异步事件驱动的网络应用程序框架,常用于构建高度可定制的网络服务器。当Spring Boot与Netty结合使用时,可以创建出高效、稳定的实时消息推送系统。 在"Spring Boot 整合 Netty + WebSocket ...

    netty+websocket实现心跳和断线重连

    Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。在本文中,我们将深入探讨如何利用 Netty 和 WebSocket 实现心跳检测和断线重连机制。 首先,我们需要理解 ...

Global site tag (gtag.js) - Google Analytics