Integrating Netty with Spring MVC: Server, Pipeline, Handler, and Controller Example
This article explains how to combine Netty and Spring MVC by creating a Netty server, configuring the channel pipeline, implementing a request handler that forwards to DispatcherServlet, initializing the servlet, and providing a sample controller with troubleshooting tips for character encoding and configuration files.
First, understand Netty: the client and server establish a TCP long‑lived connection, and data is processed through a ChannelPipeline where handlers are added with addLast, similar to Struts filters using chain.doFilter.
Integration with Spring MVC is achieved by routing incoming requests to a DispatcherServlet within a pipeline handler, and sending the processed result back to the client using ChannelHandlerContext or
Channel writeAndFlush.
1. Netty server Java code
package com.magic.netty.server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.DispatcherServlet;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import com.magic.netty.HttpServerInitializer;
public class NettyHttpServer {
public NettyHttpServer(Integer port) {
this.port = port;
}
public NettyHttpServer(Integer port, DispatcherServlet servlet) {
this.port = port;
this.servlet = servlet;
}
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerInitializer(servlet))
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println("NettyHttpServer Run successfully");
// Bind port and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} catch (Exception e) {
log.error("NettySever start fail", e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
private int port;
private static Logger log = LoggerFactory.getLogger(NettyHttpServer.class);
private DispatcherServlet servlet;
}2. Initialize Netty channel pipeline
package com.magic.netty;
import org.springframework.web.servlet.DispatcherServlet;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
public HttpServerInitializer(DispatcherServlet servlet) {
this.servlet = servlet;
}
public HttpServerInitializer() {
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("aggregator", new HttpObjectAggregator(2147483647));
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("deflater", new HttpContentCompressor());
pipeline.addLast("handler", new HttpRequestHandler(servlet));
}
private DispatcherServlet servlet;
}3. Handler to process client requests
package com.magic.netty;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletContext;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.handler.codec.http.multipart.MemoryAttribute;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
public HttpRequestHandler(DispatcherServlet servlet) {
this.servlet = servlet;
this.servletContext = servlet.getServletConfig().getServletContext();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
logger.error(e.getMessage(), e);
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
boolean flag = HttpMethod.POST.equals(fullHttpRequest.getMethod()) || HttpMethod.GET.equals(fullHttpRequest.getMethod());
Map<String, String> parammap = getRequestParams(ctx, fullHttpRequest);
if (flag && ctx.channel().isActive()) {
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext);
// headers
for (String name : fullHttpRequest.headers().names()) {
for (String value : fullHttpRequest.headers().getAll(name)) {
servletRequest.addHeader(name, value);
}
}
String uri = fullHttpRequest.getUri();
uri = new String(uri.getBytes("ISO8859-1"), "UTF-8");
uri = URLDecoder.decode(uri, "UTF-8");
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).build();
String path = uriComponents.getPath();
path = URLDecoder.decode(path, "UTF-8");
servletRequest.setRequestURI(path);
servletRequest.setServletPath(path);
servletRequest.setMethod(fullHttpRequest.getMethod().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());
}
ByteBuf content = fullHttpRequest.content();
content.readerIndex(0);
byte[] data = new byte[content.readableBytes()];
content.readBytes(data);
servletRequest.setContent(data);
try {
if (uriComponents.getQuery() != null) {
String query = UriUtils.decode(uriComponents.getQuery(), "UTF-8");
servletRequest.setQueryString(query);
}
if (parammap != null && parammap.size() > 0) {
for (String key : parammap.keySet()) {
servletRequest.addParameter(UriUtils.decode(key, "UTF-8"), UriUtils.decode(parammap.get(key) == null ? "" : parammap.get(key), "UTF-8"));
}
}
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
this.servlet.service(servletRequest, servletResponse);
HttpResponseStatus status = HttpResponseStatus.valueOf(servletResponse.getStatus());
String result = servletResponse.getContentAsString();
result = StringUtils.isEmpty(result) ? "" : result;
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(result, CharsetUtil.UTF_8));
response.headers().set("Content-Type", "text/json;charset=UTF-8");
response.headers().set("Access-Control-Allow-Origin", "*");
response.headers().set("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,X-File-Name");
response.headers().set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
response.headers().set("Content-Length", Integer.valueOf(response.content().readableBytes()));
response.headers().set("Connection", "keep-alive");
ChannelFuture writeFuture = ctx.writeAndFlush(response);
writeFuture.addListener(ChannelFutureListener.CLOSE);
}
}
private Map<String, String> getRequestParams(ChannelHandlerContext ctx, HttpRequest req) {
Map<String, String> requestParams = new HashMap<>();
// GET parameters
if (req.getMethod() == HttpMethod.GET) {
QueryStringDecoder decoder = new QueryStringDecoder(req.getUri());
Map<String, List<String>> paramMap = decoder.parameters();
for (Map.Entry<String, List<String>> entry : paramMap.entrySet()) {
requestParams.put(entry.getKey(), entry.getValue().get(0));
}
}
// POST parameters
if (req.getMethod() == HttpMethod.POST) {
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), req);
List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
for (InterfaceHttpData data : postData) {
if (data.getHttpDataType() == HttpDataType.Attribute) {
MemoryAttribute attribute = (MemoryAttribute) data;
requestParams.put(attribute.getName(), attribute.getValue());
}
}
}
return requestParams;
}
private static final Logger logger = LoggerFactory.getLogger(HttpRequestHandler.class);
private final DispatcherServlet servlet;
private final ServletContext servletContext;
}4. Initialize servlet and start Netty server
package com.magic;
import javax.servlet.ServletException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import com.magic.common.config.PropConfig;
import com.magic.netty.server.NettyHttpServer;
public class MagicWebServer {
private static Logger logger = LoggerFactory.getLogger(MagicWebServer.class);
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Integer port = 6001;
DispatcherServlet servlet = getDispatcherServlet(ctx);
NettyHttpServer server = new NettyHttpServer(port, servlet);
server.start();
}
public static DispatcherServlet getDispatcherServlet(ApplicationContext ctx) {
XmlWebApplicationContext mvcContext = new XmlWebApplicationContext();
mvcContext.setConfigLocation("classpath:spring-servlet.xml");
mvcContext.setParent(ctx);
MockServletConfig servletConfig = new MockServletConfig(mvcContext.getServletContext(), "dispatcherServlet");
DispatcherServlet dispatcherServlet = new DispatcherServlet(mvcContext);
try {
dispatcherServlet.init(servletConfig);
} catch (ServletException e) {
e.printStackTrace();
}
return dispatcherServlet;
}
}5. Example controller and test URL
package com.magic.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
@Controller
@RequestMapping(value="/user", produces = "text/json;charset=utf-8")
public class UserController extends BaseController {
@RequestMapping("/login")
@ResponseBody
public String login(String username, String pwd) {
JSONObject resultJson = new JSONObject();
Map<String, String> loginResult = new HashMap<>();
loginResult.put("username", username);
loginResult.put("age", "20");
loginResult.put("sex", "boy");
resultJson.put("code", 200);
resultJson.put("msg", "登录成功");
resultJson.put("result", loginResult);
return JSONObject.toJSONString(resultJson);
}
}Notes on coding issues
Configure applicationContext.xml and spring-servlet.xml according to normal Spring MVC configuration.
If the response returned to the client contains Chinese characters and appears garbled, add produces = "text/json;charset=utf-8" to the @RequestMapping annotation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
