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 测试