Netty 整合 SpringMVC

1.导入 maven 依赖

  <properties>

    ......

    <!-- spring -->
    <spring.version>5.1.1.RELEASE</spring.version>
    <!-- jackson-json -->
    <jackson.version>2.9.4</jackson.version>
    <!-- log4j -->
    <slf4j.version>1.7.18</slf4j.version>
    <log4j.version>1.2.17</log4j.version>
  </properties>

  <dependencies>
    <!-- spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!-- Jackson -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>${jackson.version}</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <!-- AOP -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.6</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.6</version>
    </dependency>

    <!-- 日志相关 -->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>${log4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>${slf4j.version}</version>
    </dependency>

    <!-- servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>

    <!-- netty -->
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.44.Final</version>
    </dependency>
  </dependencies>

2.创建 spring.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 自动扫描的包名 -->
    <context:component-scan base-package="com.wode" />

    <!-- 开启AOP代理 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <!--开启注解处理器 -->
    <context:annotation-config />

</beans>

3.创建 spring-mvc.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
    <!-- 开启SpringMVC注解模式 -->
    <mvc:annotation-driven />

    <!-- 扫描web相关的bean -->
    <context:component-scan base-package="com.wode.controller" />

    <!-- 静态资源默认servlet配置 -->
    <mvc:default-servlet-handler/>

</beans>

4.创建 Spring 管理器

public class SpringManager {
    //单例
    private static SpringManager instance = new SpringManager();

    private ApplicationContext ctx;
    private XmlWebApplicationContext mvcContext;
    private DispatcherServlet dispatcherServlet;

    private SpringManager() {
        ctx = new ClassPathXmlApplicationContext("spring.xml");
        mvcContext = new XmlWebApplicationContext();
        mvcContext.setConfigLocation("classpath:spring-mvc.xml");
        mvcContext.setParent(ctx);
        MockServletConfig servletConfig = new MockServletConfig(mvcContext.getServletContext(), "dispatcherServlet");
        dispatcherServlet = new DispatcherServlet(mvcContext);
        try {
            dispatcherServlet.init(servletConfig);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static SpringManager getInstance(){
        return instance;
    }

    public ApplicationContext getSpringContext(){
        return ctx;
    }

    public XmlWebApplicationContext getMvcContext(){
        return mvcContext;
    }

    public DispatcherServlet getDispatcherServlet(){
        return dispatcherServlet;
    }
}

5.创建 Netty 启动类

public class NettyServer {
    //单例
    private static NettyServer instance = new NettyServer();
    private NettyServer() {}
    private static NettyServer getInstance(){
        return instance;
    }

    public void start(int port) throws Exception {
        //负责接收客户端的连接的线程。线程数设置为1即可,netty处理链接事件默认为单线程,过度设置反而浪费cpu资源
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //负责处理数据传输的工作线程。线程数默认为CPU核心数乘以2
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup);
            bootstrap.channel(NioServerSocketChannel.class);
            //在ServerChannelInitializer中初始化ChannelPipeline责任链,并添加到serverBootstrap中
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel channel) {
                    //添加HTTP编解码
                    channel.pipeline().addLast("decoder", new HttpRequestDecoder());
                    channel.pipeline().addLast("encoder", new HttpResponseEncoder());
                    //消息聚合器,将消息聚合成FullHttpRequest
                    channel.pipeline().addLast("aggregator", new HttpObjectAggregator(1024*1024*5));
                    //支持大文件传输
                    channel.pipeline().addLast("chunked", new ChunkedWriteHandler());
                    //自定义Handler
                    channel.pipeline().addLast("dispatchHandler", new DispatchHandler());
                    channel.pipeline().addLast("httpHandler", new HttpHandler());
                }
            });
            //标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //Netty4使用对象池,重用缓冲区
            bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            //是否启用心跳保活机制
            bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
            //禁止使用Nagle算法,便于小数据即时传输
            bootstrap.childOption(ChannelOption.TCP_NODELAY, true);

            //绑定端口后,开启监听
            ChannelFuture future = bootstrap.bind(port).sync();
            future.addListener(f -> {
                if (f.isSuccess()) {
                    System.out.println("服务启动成功");
                } else {
                    System.out.println("服务启动失败");
                }
            });
            //等待服务监听端口关闭
            future.channel().closeFuture().sync();
        } finally {
            //释放资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }


    public static void main(String[] args) {
        try {
            SpringManager.getInstance();
            NettyServer.getInstance().start(8080);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

6.创建请求分发处理器

public class DispatchHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;
            //判断是否为websocket握手请求
            if(isWebSocketHandShake(request)) {
                // TODO websocket握手逻辑

                //Http请求
            }else{
                ctx.fireChannelRead(new HttpRequestVo(request));
            }
            //websocket请求
        } else if (msg instanceof WebSocketFrame) {
            // TODO websocket处理逻辑

        }
    }

    //判断是否为websocket握手请求
    private boolean isWebSocketHandShake(FullHttpRequest request){
        //1、判断是否为get 2、判断Upgrade头是否包含websocket 3、Connection头是否包含upgrade
        return request.method().equals(HttpMethod.GET)
                && request.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true)
                && request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true);
    }

}

7.创建 Http 请求处理器

public class HttpHandler extends SimpleChannelInboundHandler<HttpRequestVo> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpRequestVo requestVo) throws Exception {
        FullHttpRequest nettyRequest = requestVo.getRequest();
        MockHttpServletRequest servletRequest = this.transRequest2Spring(nettyRequest);
        MockHttpServletResponse servletResponse = new MockHttpServletResponse();
        SpringManager.getInstance().getDispatcherServlet().service(servletRequest, servletResponse);
        FullHttpResponse nettyResponse = this.transResponse2Netty(servletResponse);
        ResponseUtil.sendHttpResponse(ctx, nettyRequest, nettyResponse);
    }

    //Netty转Spring请求
    private MockHttpServletRequest transRequest2Spring(FullHttpRequest nettyRequest){
        UriComponents uriComponents = UriComponentsBuilder.fromUriString(nettyRequest.uri()).build();
        ServletContext servletContext = SpringManager.getInstance().getDispatcherServlet().getServletConfig().getServletContext();

        MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext);
        servletRequest.setRequestURI(uriComponents.getPath());
        servletRequest.setPathInfo(uriComponents.getPath());
        servletRequest.setMethod(nettyRequest.method().name());

        if (uriComponents.getScheme() != null) {
            servletRequest.setScheme(uriComponents.getScheme());
        }
        if (uriComponents.getHost() != null) {
            servletRequest.setServerName(uriComponents.getHost());
        }
        if (uriComponents.getPort() != -1) {
            servletRequest.setServerPort(uriComponents.getPort());
        }

        for (String name : nettyRequest.headers().names()) {
            servletRequest.addHeader(name, nettyRequest.headers().get(name));
        }

        ByteBuf bbContent = nettyRequest.content();
        if(bbContent.hasArray()) {
            byte[] baContent = bbContent.array();
            servletRequest.setContent(baContent);
        }

        if (uriComponents.getQuery() != null) {
            String query = UriUtils.decode(uriComponents.getQuery(), "UTF-8");
            servletRequest.setQueryString(query);
        }

        Map<String, String> paramMap = ParamUtil.getRequestParams(nettyRequest);
        if(! CollectionUtils.isEmpty(paramMap)){
            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                servletRequest.addParameter(entry.getKey(), entry.getValue());
            }
        }
        return servletRequest;
    }

    //Spring转Netty响应
    private FullHttpResponse transResponse2Netty(MockHttpServletResponse servletResponse){
        HttpResponseStatus status = HttpResponseStatus.valueOf(servletResponse.getStatus());
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.wrappedBuffer(servletResponse.getContentAsByteArray()));

        for (String name : servletResponse.getHeaderNames()) {
            for (Object value : servletResponse.getHeaderValues(name)) {
                response.headers().add(name, value);
            }
        }
        return response;
    }

}

8.创建 Http 请求 VO 类

public class HttpRequestVo {

    private FullHttpRequest request;

    public HttpRequestVo(FullHttpRequest request) {
        this.request = request;
    }

    public FullHttpRequest getRequest() {
        return request;
    }

    public void setRequest(FullHttpRequest request) {
        this.request = request;
    }
}

9.创建测试 Controller

@RestController
public class TestController {

    @RequestMapping("/add")
    public int add(int p1, int p2){
        return p1 + p2;
    }

}

10.创建工具类

  a.创建请求参数工具类

public class ParamUtil {

    /**
     * 获取请求参数
     */
    public static Map<String, String> getRequestParams(HttpRequest request){
        Map<String, String>requestParams=new HashMap<>();
        // 处理get请求
        if (request.method() == HttpMethod.GET) {
            QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
            Map<String, List<String>> params = decoder.parameters();
            for(Map.Entry<String, List<String>> entry : params.entrySet()){
                requestParams.put(entry.getKey(), entry.getValue().get(0));
            }
        }
        // 处理POST请求
        if (request.method() == HttpMethod.POST) {
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), request);
            List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
            for(InterfaceHttpData data : postData){
                if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                    MemoryAttribute attribute = (MemoryAttribute) data;
                    requestParams.put(attribute.getName(), attribute.getValue());
                }
            }
        }
        return requestParams;
    }

}

  b.创建响应工具类

public class ResponseUtil {

    /**
     * 获取400响应
     */
    public static FullHttpResponse get400Response(){
        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
    }

    /**
     * 获取200响应
     */
    public static FullHttpResponse get200Response(String content){
        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes()));
    }

    /**
     * 获取500响应
     */
    public static FullHttpResponse get500Response(){
        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR, Unpooled.wrappedBuffer("服务器异常".getBytes()));
    }

    /**
     * 发送HTTP响应
     */
    public static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
        // 返回应答给客户端
        if (response.status().code() != 200) {
            ByteBufUtil.writeUtf8(response.content(), response.status().toString());
        }

        //添加header描述length,避免客户端接收不到数据
        if(StringUtils.isEmpty(response.headers().get(HttpHeaderNames.CONTENT_TYPE))){
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
        }
        if(StringUtils.isEmpty(response.headers().get(HttpHeaderNames.CONTENT_LENGTH))){
            response.headers().add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        }

        //解决跨域的问题
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN,"*");
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS,"*");//允许headers自定义
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS,"GET, POST, PUT,DELETE");
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS,"true");

        // 如果是非Keep-Alive,关闭连接
        if (! HttpUtil.isKeepAlive(request) || response.status().code() != 200) {
            response.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
            ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }else{
            response.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            ctx.channel().writeAndFlush(response);
        }
    }

}

11.访问 http://localhost:8080/add?p1=2&p2=3 测试

猜你喜欢

转载自www.cnblogs.com/vettel0329/p/12502737.html