Java SpringMVC Netty
首先,必须要了解netty,说简单点:客户端通过TCP链接和服务器建立长连接,client和server都是通过管道(ChannelPipeline)的addLast方法的添加顺序来处理接收或者发送的数据。
这个和strutsfilterdoFilter原理类似,处理完一个filter,如果后面还有其他的filter,就将数据chain.doFilter来继续处理。
然后,说说netty怎么来整合springMVC:当client和server建立连接后,在addLast的某个类中将client发来的请求,让DispatcherServlet来处理,然后将处理后的结果通过ChannelHandlerContext或者Channel将,结果writeAndFlush到client。

1、写一个netty sever的java代码

  1. package com.fcant.netty.server;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.web.servlet.DispatcherServlet;
  5. import io.netty.bootstrap.ServerBootstrap;
  6. import io.netty.channel.ChannelFuture;
  7. import io.netty.channel.ChannelOption;
  8. import io.netty.channel.EventLoopGroup;
  9. import io.netty.channel.nio.NioEventLoopGroup;
  10. import io.netty.channel.socket.nio.NioServerSocketChannel;
  11. import com.fcant.netty.HttpServerInitializer;
  12. public class NettyHttpServer {
  13. private int port;
  14. private static Logger log = LoggerFactory.getLogger(NettyHttpServer.class);
  15. private DispatcherServlet servlet;
  16. public NettyHttpServer(Integer port) {
  17. this.port = port;
  18. }
  19. public NettyHttpServer(Integer port, DispatcherServlet servlet) {
  20. this.port = port;
  21. this.servlet = servlet;
  22. }
  23. public void start(){
  24. EventLoopGroup bossGroup = new NioEventLoopGroup();
  25. EventLoopGroup workerGroup = new NioEventLoopGroup();
  26. try {
  27. ServerBootstrap b = new ServerBootstrap();
  28. b.group(bossGroup, workerGroup)
  29. .channel(NioServerSocketChannel.class)
  30. .childHandler(new HttpServerInitializer(servlet))
  31. .option(ChannelOption.SO_BACKLOG, 128)
  32. .childOption(ChannelOption.SO_KEEPALIVE, true);
  33. System.out.println("NettyHttpServer Run successfully");
  34. // 绑定端口,开始接收进来的连接
  35. ChannelFuture f = b.bind(port).sync();
  36. // 等待服务器 socket 关闭 。在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
  37. f.channel().closeFuture().sync();
  38. } catch (Exception e) {
  39. log.error("NettySever start fail",e);
  40. } finally {
  41. workerGroup.shutdownGracefully();
  42. bossGroup.shutdownGracefully();
  43. }
  44. }
  45. }

2、初始化netty的channel管道

  1. package com.fcant.netty;
  2. import org.springframework.web.servlet.DispatcherServlet;
  3. import io.netty.channel.ChannelInitializer;
  4. import io.netty.channel.ChannelPipeline;
  5. import io.netty.channel.socket.SocketChannel;
  6. import io.netty.handler.codec.http.HttpContentCompressor;
  7. import io.netty.handler.codec.http.HttpObjectAggregator;
  8. import io.netty.handler.codec.http.HttpRequestDecoder;
  9. import io.netty.handler.codec.http.HttpResponseEncoder;
  10. import io.netty.handler.stream.ChunkedWriteHandler;
  11. public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
  12. private DispatcherServlet servlet;
  13. public HttpServerInitializer(DispatcherServlet servlet) {
  14. this.servlet = servlet;
  15. }
  16. public HttpServerInitializer() {
  17. }
  18. @Override
  19. protected void initChannel(SocketChannel ch) throws Exception {
  20. ChannelPipeline pipeline = ch.pipeline();
  21. pipeline.addLast("decoder", new HttpRequestDecoder());
  22. pipeline.addLast("encoder", new HttpResponseEncoder());
  23. pipeline.addLast("aggregator", new HttpObjectAggregator(2147483647));
  24. pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
  25. pipeline.addLast("deflater", new HttpContentCompressor());
  26. pipeline.addLast("handler", new HttpRequestHandler(servlet));
  27. }
  28. }

3、在handler里面处理client发来的请求

  1. package com.fcant.netty;
  2. import java.io.UnsupportedEncodingException;
  3. import java.net.URLDecoder;
  4. import java.util.HashMap;
  5. import java.util.Iterator;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Map.Entry;
  9. import javax.servlet.ServletContext;
  10. import io.netty.buffer.ByteBuf;
  11. import io.netty.buffer.Unpooled;
  12. import io.netty.channel.ChannelFuture;
  13. import io.netty.channel.ChannelFutureListener;
  14. import io.netty.channel.ChannelHandlerContext;
  15. import io.netty.channel.SimpleChannelInboundHandler;
  16. import io.netty.handler.codec.http.*;
  17. import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
  18. import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
  19. import io.netty.handler.codec.http.multipart.InterfaceHttpData;
  20. import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
  21. import io.netty.handler.codec.http.multipart.MemoryAttribute;
  22. import io.netty.util.CharsetUtil;
  23. import org.apache.commons.lang3.StringUtils;
  24. import org.slf4j.Logger;
  25. import org.slf4j.LoggerFactory;
  26. import org.springframework.mock.web.MockHttpServletRequest;
  27. import org.springframework.mock.web.MockHttpServletResponse;
  28. import org.springframework.web.servlet.DispatcherServlet;
  29. import org.springframework.web.util.UriComponents;
  30. import org.springframework.web.util.UriComponentsBuilder;
  31. import org.springframework.web.util.UriUtils;
  32. public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
  33. private static final Logger logger = LoggerFactory.getLogger(HttpRequestHandler.class);
  34. private final DispatcherServlet servlet;
  35. private final ServletContext servletContext;
  36. public HttpRequestHandler(DispatcherServlet servlet) {
  37. this.servlet = servlet;
  38. this.servletContext = servlet.getServletConfig().getServletContext();
  39. }
  40. @Override
  41. public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
  42. logger.error(e.getMessage(),e);
  43. ctx.close();
  44. }
  45. protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
  46. boolean flag = HttpMethod.POST.equals(fullHttpRequest.getMethod())
  47. || HttpMethod.GET.equals(fullHttpRequest.getMethod());
  48. Map<String, String> parammap = getRequestParams(ctx,fullHttpRequest);
  49. if(flag && ctx.channel().isActive()){
  50. //HTTP请求、GET/POST
  51. MockHttpServletResponse servletResponse = new MockHttpServletResponse();
  52. MockHttpServletRequest servletRequest =new MockHttpServletRequest(servletContext);
  53. // headers
  54. for (String name : fullHttpRequest.headers().names()) {
  55. for (String value : fullHttpRequest.headers().getAll(name)) {
  56. servletRequest.addHeader(name, value);
  57. }
  58. }
  59. String uri = fullHttpRequest.getUri();
  60. uri = new String(uri.getBytes("ISO8859-1"), "UTF-8");
  61. uri = URLDecoder.decode(uri, "UTF-8");
  62. UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).build();
  63. String path = uriComponents.getPath();
  64. path = URLDecoder.decode(path, "UTF-8");
  65. servletRequest.setRequestURI(path);
  66. servletRequest.setServletPath(path);
  67. servletRequest.setMethod(fullHttpRequest.getMethod().name());
  68. if (uriComponents.getScheme() != null) {
  69. servletRequest.setScheme(uriComponents.getScheme());
  70. }
  71. if (uriComponents.getHost() != null) {
  72. servletRequest.setServerName(uriComponents.getHost());
  73. }
  74. if (uriComponents.getPort() != -1) {
  75. servletRequest.setServerPort(uriComponents.getPort());
  76. }
  77. ByteBuf content = fullHttpRequest.content();
  78. content.readerIndex(0);
  79. byte[] data = new byte[content.readableBytes()];
  80. content.readBytes(data);
  81. servletRequest.setContent(data);
  82. try {
  83. if (uriComponents.getQuery() != null) {
  84. String query = UriUtils.decode(uriComponents.getQuery(),"UTF-8");
  85. servletRequest.setQueryString(query);
  86. }
  87. if(parammap!=null&&parammap.size()>0){
  88. for (String key : parammap.keySet()) {
  89. servletRequest.addParameter(UriUtils.decode(key,"UTF-8"), UriUtils.decode(parammap.get(key) == null ? "": parammap.get(key), "UTF-8"));
  90. }
  91. }
  92. } catch (UnsupportedEncodingException ex) {
  93. ex.printStackTrace();
  94. }
  95. this.servlet.service(servletRequest,servletResponse);
  96. HttpResponseStatus status = HttpResponseStatus.valueOf(servletResponse.getStatus());
  97. String result = servletResponse.getContentAsString();
  98. result = StringUtils.isEmpty(result)?"":result;
  99. FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,Unpooled.copiedBuffer(result,CharsetUtil.UTF_8));
  100. response.headers().set("Content-Type", "text/json;charset=UTF-8");
  101. response.headers().set("Access-Control-Allow-Origin", "*");
  102. response.headers().set("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,X-File-Name");
  103. response.headers().set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  104. response.headers().set("Content-Length", Integer.valueOf(response.content().readableBytes()));
  105. response.headers().set("Connection", "keep-alive");
  106. ChannelFuture writeFuture = ctx.writeAndFlush(response);
  107. writeFuture.addListener(ChannelFutureListener.CLOSE);
  108. }
  109. }
  110. /**
  111. * 获取post请求、get请求的参数保存到map中
  112. */
  113. private Map<String, String> getRequestParams(ChannelHandlerContext ctx, HttpRequest req){
  114. Map<String, String>requestParams=new HashMap<String, String>();
  115. // 处理get请求
  116. if (req.getMethod() == HttpMethod.GET) {
  117. QueryStringDecoder decoder = new QueryStringDecoder(req.getUri());
  118. Map<String, List<String>> parame = decoder.parameters();
  119. Iterator<Entry<String, List<String>>> iterator = parame.entrySet().iterator();
  120. while(iterator.hasNext()){
  121. Entry<String, List<String>> next = iterator.next();
  122. requestParams.put(next.getKey(), next.getValue().get(0));
  123. }
  124. }
  125. // 处理POST请求
  126. if (req.getMethod() == HttpMethod.POST) {
  127. HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(
  128. new DefaultHttpDataFactory(false), req);
  129. List<InterfaceHttpData> postData = decoder.getBodyHttpDatas(); //
  130. for(InterfaceHttpData data:postData){
  131. if (data.getHttpDataType() == HttpDataType.Attribute) {
  132. MemoryAttribute attribute = (MemoryAttribute) data;
  133. requestParams.put(attribute.getName(), attribute.getValue());
  134. }
  135. }
  136. }
  137. return requestParams;
  138. }
  139. }

4、初始化servlet并启动netty server

  1. package com.fcant;
  2. import javax.servlet.ServletException;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.context.ApplicationContext;
  6. import org.springframework.context.support.ClassPathXmlApplicationContext;
  7. import org.springframework.mock.web.MockServletConfig;
  8. import org.springframework.web.context.support.XmlWebApplicationContext;
  9. import org.springframework.web.servlet.DispatcherServlet;
  10. import com.fcant.common.config.PropConfig;
  11. import com.fcant.netty.server.NettyHttpServer;
  12. public class MagicWebServer {
  13. private static Logger logger = LoggerFactory.getLogger(MagicWebServer.class);
  14. public static void main(String[] args) {
  15. ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  16. Integer port = 6001;
  17. DispatcherServlet servlet = getDispatcherServlet(ctx);
  18. NettyHttpServer server = new NettyHttpServer(port,servlet);
  19. server.start();
  20. }
  21. public static DispatcherServlet getDispatcherServlet(ApplicationContext ctx){
  22. XmlWebApplicationContext mvcContext = new XmlWebApplicationContext();
  23. mvcContext.setConfigLocation("classpath:spring-servlet.xml");
  24. mvcContext.setParent(ctx);
  25. MockServletConfig servletConfig = new MockServletConfig(mvcContext.getServletContext(), "dispatcherServlet");
  26. DispatcherServlet dispatcherServlet = new DispatcherServlet(mvcContext);
  27. try {
  28. dispatcherServlet.init(servletConfig);
  29. } catch (ServletException e) {
  30. e.printStackTrace();
  31. }
  32. return dispatcherServlet;
  33. }
  34. }

5、写一个controller,并测试一下http://127.0.0.1:6001/user/login

  1. package com.fcant.controller;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.ResponseBody;
  7. import com.alibaba.fastjson.JSONObject;
  8. @Controller
  9. @RequestMapping(value="/user",produces = "text/json;charset=utf-8")
  10. public class UserController extends BaseController{
  11. @RequestMapping("/login")
  12. @ResponseBody
  13. public String login(String username,String pwd){
  14. JSONObject resultJson = new JSONObject();
  15. Map<String, String> loginResult = new HashMap<String, String>();
  16. loginResult.put("username", username);
  17. loginResult.put("age", "20");
  18. loginResult.put("sex", "boy");
  19. resultJson.put("code", 200);
  20. resultJson.put("msg", "登录成功");
  21. resultJson.put("result", loginResult);
  22. return JSONObject.toJSONString(resultJson);
  23. }
  24. }

在coding的时候遇到的问题

  • 代码中的applicationContext.xmlspring-servlet.xml的按照SpringMVC的正常配置就行
  • 如果返回到client端的代码有中文乱码,那么在requestMapping里面添加produces = "text/json;charset=utf-8"