├── README.md ├── doc └── wechat.jpg ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── netty │ │ └── http │ │ └── server │ │ ├── NettyHttpServerApplication.java │ │ ├── annotation │ │ ├── RequestMapping.java │ │ ├── RequestParam.java │ │ └── RestController.java │ │ ├── bootstrap │ │ ├── NettyServer.java │ │ └── NettyServerHandler.java │ │ ├── common │ │ ├── Constant.java │ │ ├── response │ │ │ └── GeneralResponse.java │ │ └── utils │ │ │ ├── JsonUtil.java │ │ │ ├── RequestUtil.java │ │ │ ├── ResponseUtil.java │ │ │ └── SpringContextHolder.java │ │ ├── configuration │ │ └── MyNettyServerConfiguration.java │ │ ├── order │ │ ├── controller │ │ │ └── OrderController.java │ │ ├── entity │ │ │ └── Order.java │ │ ├── mapper │ │ │ └── OrderMapper.java │ │ └── service │ │ │ ├── OrderService.java │ │ │ └── impl │ │ │ └── OrderServiceImpl.java │ │ └── router │ │ ├── HttpRouter.java │ │ ├── HttpRouterDispatch.java │ │ └── HttpRouterTally.java └── resources │ ├── META-INF │ └── spring.factories │ ├── application.yml │ └── mybatis │ └── mybatis-config.xml └── test └── java └── com └── netty └── http └── server └── NettyHttpServerApplicationTests.java /README.md: -------------------------------------------------------------------------------- 1 | # netty-http-server 2 | # 采用Netty4.x开发Http Server服务,以此提高性能和吞吐量(统一处理请求URL路由,多种数据类型参数映射等) # 3 | 4 | ## Prerequisite ## 5 | 1. JDK 1.8+ 6 | 2. Maven 3.5.x 7 | 3. Git版本控制 8 | 9 | ## Quick Start ## 10 | - Clone & Build 11 | > git clone https://github.com/fengjingwei/netty-http-server.git 12 | > 13 | > cd netty-http-server 14 | > 15 | > mvn clean install -DskipTests -U 16 | 17 | ## Support ## 18 | - 如有任何问题欢迎微我 19 | - ![](https://raw.githubusercontent.com/fengjingwei/netty-http-server/master/doc/wechat.jpg) 20 | -------------------------------------------------------------------------------- /doc/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengjingwei/netty-http-server/4aa16c964e4e750da72ae8f984d3a408be2377da/doc/wechat.jpg -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.netty.http 6 | netty-http-server 7 | 0.0.1-RELEASE 8 | jar 9 | Netty Http Server 10 | Netty Http Server project for Spring Boot 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 2.2.7.RELEASE 16 | 17 | 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 1.8 23 | 2.1.1 24 | 8.0.33 25 | 4.1.90.Final 26 | 32.0.0-jre 27 | 2.10.1 28 | 3.12.0 29 | 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | 43 | org.mybatis.spring.boot 44 | mybatis-spring-boot-starter 45 | ${mybatis.spring.boot.version} 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-devtools 51 | true 52 | runtime 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-test 58 | test 59 | 60 | 61 | 62 | mysql 63 | mysql-connector-java 64 | ${mysql.version} 65 | 66 | 67 | 68 | io.netty 69 | netty-all 70 | ${netty.version} 71 | 72 | 73 | 74 | org.projectlombok 75 | lombok 76 | true 77 | 78 | 79 | 80 | com.google.guava 81 | guava 82 | ${guava.version} 83 | 84 | 85 | 86 | com.google.code.gson 87 | gson 88 | ${gson.version} 89 | 90 | 91 | 92 | org.apache.commons 93 | commons-lang3 94 | ${commons.lang3.version} 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.springframework.boot 102 | spring-boot-maven-plugin 103 | 104 | true 105 | true 106 | com.netty.http.server.NettyHttpServerApplication 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/NettyHttpServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.transaction.annotation.EnableTransactionManagement; 7 | 8 | @SpringBootApplication 9 | @EnableTransactionManagement 10 | @MapperScan(basePackages = {"com.netty.http.server.order.mapper"}) 11 | public class NettyHttpServerApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(NettyHttpServerApplication.class, args); 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/annotation/RequestMapping.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.annotation; 2 | 3 | import org.springframework.web.bind.annotation.RequestMethod; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(value = {ElementType.TYPE, ElementType.METHOD}) 8 | @Retention(value = RetentionPolicy.RUNTIME) 9 | @Inherited 10 | @Documented 11 | public @interface RequestMapping { 12 | 13 | String uri() default ""; 14 | 15 | RequestMethod method() default RequestMethod.GET; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/annotation/RequestParam.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.annotation; 2 | 3 | import com.netty.http.server.common.Constant; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(value = ElementType.PARAMETER) 8 | @Retention(value = RetentionPolicy.RUNTIME) 9 | @Inherited 10 | @Documented 11 | public @interface RequestParam { 12 | 13 | String name(); 14 | 15 | boolean required() default true; 16 | 17 | String defaultValue() default Constant.EMPTY; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/annotation/RestController.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.annotation; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(value = ElementType.TYPE) 8 | @Retention(value = RetentionPolicy.RUNTIME) 9 | @Inherited 10 | @Documented 11 | @Component 12 | public @interface RestController { 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/bootstrap/NettyServer.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.bootstrap; 2 | 3 | import com.netty.http.server.common.utils.SpringContextHolder; 4 | import com.netty.http.server.router.HttpRouter; 5 | import io.netty.bootstrap.ServerBootstrap; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelOption; 9 | import io.netty.channel.ChannelPipeline; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.handler.codec.http.HttpContentCompressor; 14 | import io.netty.handler.codec.http.HttpObjectAggregator; 15 | import io.netty.handler.codec.http.HttpServerCodec; 16 | import io.netty.handler.logging.LogLevel; 17 | import io.netty.handler.logging.LoggingHandler; 18 | import io.netty.handler.stream.ChunkedWriteHandler; 19 | import lombok.extern.log4j.Log4j2; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | 22 | import javax.servlet.ServletContextListener; 23 | import java.net.InetSocketAddress; 24 | 25 | @Log4j2 26 | public class NettyServer implements ServletContextListener { 27 | 28 | private static final Integer PORT = 9999; 29 | private final HttpRouter httpRouter = new HttpRouter(); 30 | 31 | @Autowired 32 | private SpringContextHolder springContextHolder; 33 | 34 | public void start() { 35 | final NioEventLoopGroup bossGroup = new NioEventLoopGroup(); 36 | final NioEventLoopGroup workerGroup = new NioEventLoopGroup(); 37 | try { 38 | springContextHolder.loadControllerClass().forEach(httpRouter::addRouter); 39 | final ServerBootstrap bootstrap = new ServerBootstrap(); 40 | bootstrap.group(bossGroup, workerGroup) 41 | .channel(NioServerSocketChannel.class) 42 | .handler(new LoggingHandler(LogLevel.INFO)) 43 | .option(ChannelOption.SO_REUSEADDR, true) 44 | .childHandler(new ChannelInitializer() { 45 | 46 | @Override 47 | protected void initChannel(SocketChannel ch) { 48 | final ChannelPipeline pipeline = ch.pipeline(); 49 | pipeline.addLast("httpServerCodec", new HttpServerCodec()); 50 | pipeline.addLast("httpObjectAggregator", new HttpObjectAggregator(65536)); 51 | pipeline.addLast("httpContentCompressor", new HttpContentCompressor()); 52 | pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler()); 53 | pipeline.addLast("nettyServerHandler", new NettyServerHandler(httpRouter)); 54 | } 55 | }); 56 | 57 | final Channel serverChannel = bootstrap.bind(new InetSocketAddress(PORT)).sync().channel(); 58 | log.info("http://127.0.0.1:{}/ Start-up success", PORT); 59 | serverChannel.closeFuture().sync(); 60 | } catch (InterruptedException e) { 61 | e.printStackTrace(); 62 | } finally { 63 | workerGroup.shutdownGracefully(); 64 | bossGroup.shutdownGracefully(); 65 | } 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/bootstrap/NettyServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.bootstrap; 2 | 3 | import com.netty.http.server.common.Constant; 4 | import com.netty.http.server.common.response.GeneralResponse; 5 | import com.netty.http.server.common.utils.ResponseUtil; 6 | import com.netty.http.server.router.HttpRouter; 7 | import com.netty.http.server.router.HttpRouterDispatch; 8 | import com.netty.http.server.router.HttpRouterTally; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.ChannelInboundHandlerAdapter; 11 | import io.netty.handler.codec.http.FullHttpRequest; 12 | 13 | public class NettyServerHandler extends ChannelInboundHandlerAdapter { 14 | 15 | private final HttpRouter httpRouter; 16 | 17 | NettyServerHandler(HttpRouter httpRouter) { 18 | this.httpRouter = httpRouter; 19 | } 20 | 21 | @Override 22 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 23 | // System.out.println("NettyServerHandler.handlerAdded"); 24 | } 25 | 26 | @Override 27 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 28 | // System.out.println("NettyServerHandler.channelRegistered"); 29 | } 30 | 31 | 32 | @Override 33 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 34 | // System.out.println("NettyServerHandler.channelActive"); 35 | } 36 | 37 | @Override 38 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 39 | if (msg instanceof FullHttpRequest) { 40 | final FullHttpRequest request = (FullHttpRequest) msg; 41 | String uri = request.uri(); 42 | if (Constant.FAVICON.equals(uri)) { 43 | ctx.close(); 44 | return; 45 | } 46 | if (uri.contains(Constant.QUESTION)) { 47 | uri = uri.substring(0, uri.indexOf(Constant.QUESTION)); 48 | } 49 | final HttpRouterDispatch> httpRouterDispatch = httpRouter.getRoute(new HttpRouterTally(uri, request.method())); 50 | if (httpRouterDispatch != null) { 51 | ResponseUtil.response(ctx, request, httpRouterDispatch.call(request)); 52 | } else { 53 | ResponseUtil.notFound(ctx, request); 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 60 | // System.out.println("NettyServerHandler.channelReadComplete"); 61 | } 62 | 63 | @Override 64 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 65 | // System.out.println("NettyServerHandler.channelInactive"); 66 | } 67 | 68 | @Override 69 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 70 | // System.out.println("NettyServerHandler.channelUnregistered"); 71 | } 72 | 73 | @Override 74 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 75 | // System.out.println("NettyServerHandler.handlerRemoved"); 76 | } 77 | 78 | @Override 79 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 80 | cause.printStackTrace(); 81 | ctx.close(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/common/Constant.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.common; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | public class Constant { 6 | 7 | public static final String EMPTY = StringUtils.EMPTY; 8 | 9 | public static final String QUESTION = "?"; 10 | 11 | public static final String SLASH = "/"; 12 | 13 | public static final String FAVICON = "/favicon.ico"; 14 | } -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/common/response/GeneralResponse.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.common.response; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | public class GeneralResponse { 10 | 11 | public static final transient GeneralResponse NOT_FOUND = new GeneralResponse<>(HttpResponseStatus.NOT_FOUND, HttpResponseStatus.NOT_FOUND.reasonPhrase(), null); 12 | 13 | private static final transient HttpResponseStatus OK = HttpResponseStatus.OK; 14 | 15 | private static final transient HttpResponseStatus SERVER_ERROR = HttpResponseStatus.INTERNAL_SERVER_ERROR; 16 | 17 | private int status; 18 | 19 | private String message = "SUCCESS"; 20 | 21 | private T data; 22 | 23 | private GeneralResponse(final HttpResponseStatus responseStatus, final T data) { 24 | this.status = responseStatus.code(); 25 | this.data = data; 26 | } 27 | 28 | private GeneralResponse(final HttpResponseStatus responseStatus, final String message, final T data) { 29 | this.status = responseStatus.code(); 30 | this.message = message; 31 | this.data = data; 32 | } 33 | 34 | public static GeneralResponse success(final T data) { 35 | return new GeneralResponse<>(OK, data); 36 | } 37 | 38 | public static GeneralResponse success() { 39 | return success(null); 40 | } 41 | 42 | public static GeneralResponse error(final String message) { 43 | return new GeneralResponse<>(SERVER_ERROR, message, null); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/common/utils/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.common.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | public class JsonUtil { 7 | 8 | private static final GsonBuilder GSON_BUILDER = new GsonBuilder(); 9 | private static final Gson GSON = GSON_BUILDER.disableHtmlEscaping().setDateFormat("yyyy-MM-dd HH:mm:ss").create(); 10 | 11 | private JsonUtil() { 12 | 13 | } 14 | 15 | public static String toJson(Object o) { 16 | return GSON.toJson(o); 17 | } 18 | 19 | public static T fromJson(String json, Class clazz) { 20 | return GSON.fromJson(json, clazz); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/common/utils/RequestUtil.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.common.utils; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.netty.http.server.common.Constant; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.QueryStringDecoder; 8 | import io.netty.util.CharsetUtil; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class RequestUtil { 15 | 16 | private RequestUtil() { 17 | } 18 | 19 | public static Map> getParameterMap(final FullHttpRequest request) { 20 | final String uri = request.uri(); 21 | if (StringUtils.isNotBlank(uri)) { 22 | if (uri.contains(Constant.QUESTION)) { 23 | return new QueryStringDecoder(uri).parameters(); 24 | } 25 | } 26 | return Maps.newTreeMap(); 27 | } 28 | 29 | public static List getParameterValue(final FullHttpRequest request, final String name) { 30 | return getParameterMap(request).get(name); 31 | } 32 | 33 | public static String getParameterFirstValue(final FullHttpRequest request, final String name) { 34 | return getParameterValue(request, name).iterator().next(); 35 | } 36 | 37 | public static T postEntity(final FullHttpRequest request, final Class clazz) { 38 | final ByteBuf jsonBuf = request.content(); 39 | final String json = jsonBuf.toString(CharsetUtil.UTF_8); 40 | return JsonUtil.fromJson(json, clazz); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/common/utils/ResponseUtil.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.common.utils; 2 | 3 | import com.netty.http.server.common.response.GeneralResponse; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.http.*; 8 | 9 | public class ResponseUtil { 10 | 11 | private ResponseUtil() { 12 | 13 | } 14 | 15 | public static void notFound(ChannelHandlerContext ctx, FullHttpRequest request) { 16 | response(ctx, request, GeneralResponse.NOT_FOUND); 17 | } 18 | 19 | public static void response(ChannelHandlerContext ctx, FullHttpRequest request, GeneralResponse generalResponse) { 20 | final byte[] jsonBytes = JsonUtil.toJson(generalResponse).getBytes(); 21 | final FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(jsonBytes)); 22 | final HttpHeaders headers = response.headers(); 23 | headers.set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON); 24 | headers.setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); 25 | final boolean keepAlive = HttpUtil.isKeepAlive(request); 26 | if (!keepAlive) { 27 | ctx.write(response).addListener(ChannelFutureListener.CLOSE); 28 | } else { 29 | headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 30 | ctx.write(response); 31 | } 32 | ctx.flush(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/common/utils/SpringContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.common.utils; 2 | 3 | import com.netty.http.server.annotation.RestController; 4 | import org.springframework.aop.support.AopUtils; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.DisposableBean; 7 | import org.springframework.beans.factory.InitializingBean; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.ApplicationContextAware; 10 | import org.springframework.context.annotation.Lazy; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.lang.annotation.Annotation; 14 | import java.util.List; 15 | import java.util.Objects; 16 | import java.util.stream.Collectors; 17 | 18 | @Component 19 | @Lazy(value = false) 20 | public class SpringContextHolder implements ApplicationContextAware, InitializingBean, DisposableBean { 21 | 22 | private static ApplicationContext applicationContext; 23 | private List> controllerClass; 24 | 25 | public static T getBean(Class requiredType) { 26 | return applicationContext.getBean(requiredType); 27 | } 28 | 29 | public static Object getBean(String name) { 30 | return applicationContext.getBean(name); 31 | } 32 | 33 | public ApplicationContext getApplicationContext() { 34 | return applicationContext; 35 | } 36 | 37 | @Override 38 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 39 | SpringContextHolder.applicationContext = applicationContext; 40 | this.controllerClass = loadControllerClass(applicationContext); 41 | } 42 | 43 | @Override 44 | public void destroy() { 45 | SpringContextHolder.applicationContext = null; 46 | } 47 | 48 | private List> loadControllerClass(ApplicationContext ctx) { 49 | final Class clazz = RestController.class; 50 | return ctx.getBeansWithAnnotation(clazz) 51 | .values().stream() 52 | .map(AopUtils::getTargetClass) 53 | .filter(cls -> Objects.nonNull(cls.getAnnotation(clazz))) 54 | .collect(Collectors.toList()); 55 | } 56 | 57 | public List> loadControllerClass() { 58 | return controllerClass; 59 | } 60 | 61 | @Override 62 | public void afterPropertiesSet() { 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/configuration/MyNettyServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.configuration; 2 | 3 | import com.netty.http.server.bootstrap.NettyServer; 4 | import org.springframework.boot.SpringBootConfiguration; 5 | import org.springframework.context.annotation.Bean; 6 | 7 | @SpringBootConfiguration 8 | public class MyNettyServerConfiguration { 9 | 10 | @Bean(initMethod = "start") 11 | public NettyServer nettyServer() { 12 | return new NettyServer(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/order/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.order.controller; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.netty.http.server.annotation.RequestMapping; 5 | import com.netty.http.server.annotation.RequestParam; 6 | import com.netty.http.server.annotation.RestController; 7 | import com.netty.http.server.common.response.GeneralResponse; 8 | import com.netty.http.server.common.utils.SpringContextHolder; 9 | import com.netty.http.server.order.entity.Order; 10 | import com.netty.http.server.order.service.OrderService; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | 14 | import java.math.BigDecimal; 15 | import java.util.List; 16 | 17 | @RestController 18 | @RequestMapping(uri = "v1/order") 19 | public class OrderController { 20 | 21 | private static final OrderService orderService = SpringContextHolder.getBean(OrderService.class); 22 | 23 | private static List buildOrderList() { 24 | List data = Lists.newArrayList(); 25 | data.add(new Order("1", 1, "1", new BigDecimal("99.98"), 1, "1")); 26 | data.add(new Order("2", 2, "2", new BigDecimal("199.99"), 2, "1")); 27 | data.add(new Order("3", 1, "1", new BigDecimal("99.88"), 1, "2")); 28 | return data; 29 | } 30 | 31 | @RequestMapping(uri = "findAll") 32 | public GeneralResponse> findAll() { 33 | return GeneralResponse.success(buildOrderList()); 34 | } 35 | 36 | @RequestMapping(uri = "findByCondition") 37 | public GeneralResponse> findByCondition(@RequestParam(name = "orderNo") String orderNo, 38 | @RequestParam(name = "status", required = false) Byte status, 39 | @RequestParam(name = "userIds") String[] userIds) { 40 | List data = Lists.newArrayList(); 41 | buildOrderList().stream() 42 | .filter(order -> StringUtils.equals(order.getOrderNo(), orderNo)) 43 | .forEach(data::add); 44 | return GeneralResponse.success(data); 45 | } 46 | 47 | @RequestMapping(uri = "create", method = RequestMethod.POST) 48 | public GeneralResponse create(Order order) { 49 | return GeneralResponse.success(order); 50 | } 51 | 52 | @RequestMapping(uri = "update", method = RequestMethod.PUT) 53 | public GeneralResponse update(Order order) { 54 | return GeneralResponse.success(order); 55 | } 56 | 57 | @RequestMapping(uri = "delete", method = RequestMethod.DELETE) 58 | public GeneralResponse delete(@RequestParam(name = "orderNo") String orderNo) { 59 | return GeneralResponse.success(orderNo); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/order/entity/Order.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.order.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.math.BigDecimal; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class Order { 13 | 14 | /** 15 | * 订单编号 16 | */ 17 | private String orderNo; 18 | 19 | /** 20 | * 订单状态 21 | */ 22 | private Integer status; 23 | 24 | /** 25 | * 商品id 26 | */ 27 | private String productId; 28 | 29 | /** 30 | * 付款金额 31 | */ 32 | private BigDecimal totalAmount; 33 | 34 | /** 35 | * 购买数量 36 | */ 37 | private Integer count; 38 | 39 | /** 40 | * 购买人 41 | */ 42 | private String userId; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/order/mapper/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.order.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | 5 | @Mapper 6 | public interface OrderMapper { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/order/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.order.service; 2 | 3 | public interface OrderService { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/order/service/impl/OrderServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.order.service.impl; 2 | 3 | import com.netty.http.server.order.service.OrderService; 4 | import org.springframework.stereotype.Service; 5 | 6 | @Service 7 | public class OrderServiceImpl implements OrderService { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/router/HttpRouter.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.router; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.netty.http.server.annotation.RequestMapping; 5 | import com.netty.http.server.common.Constant; 6 | import com.netty.http.server.common.response.GeneralResponse; 7 | import io.netty.handler.codec.http.HttpMethod; 8 | import lombok.extern.log4j.Log4j2; 9 | 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.FileInputStream; 12 | import java.io.InputStream; 13 | import java.lang.annotation.Annotation; 14 | import java.lang.reflect.Method; 15 | import java.util.Map; 16 | 17 | @Log4j2 18 | public class HttpRouter extends ClassLoader { 19 | 20 | private static final int BUFFER_SIZE = 1024 * 8; 21 | private final Map>> httpRouterMapper = Maps.newConcurrentMap(); 22 | private final String classpath = this.getClass().getResource(Constant.EMPTY).getPath(); 23 | private final Map controllerBeans = Maps.newConcurrentMap(); 24 | 25 | @Override 26 | protected Class findClass(final String name) throws ClassNotFoundException { 27 | final String path = classpath + name.replaceAll("\\.", Constant.SLASH); 28 | byte[] bytes; 29 | try (InputStream ins = new FileInputStream(path)) { 30 | try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { 31 | final byte[] buffer = new byte[BUFFER_SIZE]; 32 | int temp; 33 | while ((temp = ins.read(buffer)) != -1) { 34 | out.write(buffer, 0, temp); 35 | } 36 | bytes = out.toByteArray(); 37 | } 38 | } catch (Exception e) { 39 | throw new ClassNotFoundException(name); 40 | } 41 | return super.defineClass(name, bytes, 0, bytes.length); 42 | } 43 | 44 | public void addRouter(final Class clazz) { 45 | try { 46 | final RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class); 47 | String clazzUri = Constant.EMPTY; 48 | if (classRequestMapping != null) { 49 | final String uri = classRequestMapping.uri(); 50 | clazzUri = uri.startsWith(Constant.SLASH) ? uri : Constant.SLASH + uri; 51 | } 52 | final Method[] methods = clazz.getDeclaredMethods(); 53 | for (Method invokeMethod : methods) { 54 | final Annotation[] annotations = invokeMethod.getAnnotations(); 55 | for (Annotation annotation : annotations) { 56 | final Class annotationType = annotation.annotationType(); 57 | if (annotationType == RequestMapping.class) { 58 | final RequestMapping methodRequestMapping = (RequestMapping) annotation; 59 | final String methodUri = methodRequestMapping.uri(); 60 | final String httpMethod = methodRequestMapping.method().toString(); 61 | if (!controllerBeans.containsKey(clazz.getName())) { 62 | controllerBeans.put(clazz.getName(), clazz.newInstance()); 63 | } 64 | final HttpRouterDispatch> httpRouterDispatch = new HttpRouterDispatch<>(controllerBeans.get(clazz.getName()), invokeMethod); 65 | final String requestUri = clazzUri + (methodUri.startsWith(Constant.SLASH) ? methodUri : Constant.SLASH + methodUri); 66 | httpRouterMapper.put(new HttpRouterTally(requestUri, HttpMethod.valueOf(httpMethod)), httpRouterDispatch); 67 | } 68 | } 69 | } 70 | 71 | if (log.isDebugEnabled()) { 72 | httpRouterMapper.forEach((key, value) -> log.info("加载控制层 => [{}], 请求路径 => [{}], 请求方法 => [{}]", value.getObject(), key.getUri(), key.getMethod())); 73 | } 74 | } catch (Exception e) { 75 | log.error(e); 76 | } 77 | } 78 | 79 | public HttpRouterDispatch> getRoute(final HttpRouterTally httpRouterTally) { 80 | return httpRouterMapper.get(httpRouterTally); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/router/HttpRouterDispatch.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.router; 2 | 3 | import com.netty.http.server.annotation.RequestParam; 4 | import com.netty.http.server.common.response.GeneralResponse; 5 | import com.netty.http.server.common.utils.RequestUtil; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.HttpMethod; 8 | import lombok.Data; 9 | import lombok.NonNull; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.log4j.Log4j2; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.springframework.util.CollectionUtils; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.lang.reflect.Array; 17 | import java.lang.reflect.Method; 18 | import java.lang.reflect.Parameter; 19 | import java.util.*; 20 | 21 | @Data 22 | @RequiredArgsConstructor 23 | @Log4j2 24 | public class HttpRouterDispatch { 25 | 26 | @NonNull 27 | private Object object; 28 | 29 | @NonNull 30 | private Method method; 31 | 32 | // private boolean injectionFullHttpRequest; 33 | 34 | private static void verifyParameterAnnotations(Map> parameterMap, Method method) { 35 | Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 36 | if (parameterAnnotations.length == 0) { 37 | return; 38 | } 39 | for (Annotation[] parameterAnnotation : parameterAnnotations) { 40 | for (Annotation annotation : parameterAnnotation) { 41 | if (annotation instanceof RequestParam) { 42 | RequestParam requestParam = (RequestParam) annotation; 43 | if (requestParam.required()) { 44 | String name = requestParam.name(); 45 | List values = parameterMap.get(name); 46 | if (CollectionUtils.isEmpty(values)) { 47 | String defaultValue = requestParam.defaultValue(); 48 | if (StringUtils.isBlank(defaultValue)) { 49 | throw new RuntimeException(String.format("Required parameter '%s' is not present.", name)); 50 | } else { 51 | parameterMap.put(name, Collections.singletonList(defaultValue)); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | private static void handleParameterTypes(Class parameterType, List values, List args) { 61 | if (CollectionUtils.isEmpty(values)) { 62 | args.add(null); 63 | return; 64 | } 65 | String value = values.iterator().next(); 66 | if (parameterType.equals(String.class)) { 67 | args.add(value); 68 | } else if (parameterType.equals(Integer.class) || parameterType.equals(int.class)) { 69 | args.add(Integer.valueOf(value)); 70 | } else if (parameterType.equals(Long.class) || parameterType.equals(long.class)) { 71 | args.add(Long.valueOf(value)); 72 | } else if (parameterType.equals(Byte.class) || parameterType.equals(byte.class)) { 73 | args.add(Byte.valueOf(value)); 74 | } else if (parameterType.equals(Float.class) || parameterType.equals(float.class)) { 75 | args.add(Float.valueOf(value)); 76 | } else if (parameterType.equals(Double.class) || parameterType.equals(double.class)) { 77 | args.add(Double.valueOf(value)); 78 | } else if (parameterType.equals(Boolean.class) || parameterType.equals(boolean.class)) { 79 | args.add(Boolean.valueOf(value)); 80 | } else if (parameterType.equals(Short.class) || parameterType.equals(short.class)) { 81 | args.add(Short.valueOf(value)); 82 | } else if (parameterType.isArray()) { 83 | Class componentType = parameterType.getComponentType(); 84 | if (componentType.equals(String.class)) { 85 | args.add(values.toArray(new String[0])); 86 | } else if (componentType.equals(Integer.class) || componentType.equals(int.class)) { 87 | Object array = Array.newInstance(componentType, values.size()); 88 | for (int i = 0; i < values.size(); i++) { 89 | Array.set(array, i, Integer.valueOf(values.get(i))); 90 | } 91 | args.add(array); 92 | } else if (componentType.equals(Long.class) || componentType.equals(long.class)) { 93 | Object array = Array.newInstance(componentType, values.size()); 94 | for (int i = 0; i < values.size(); i++) { 95 | Array.set(array, i, Long.valueOf(values.get(i))); 96 | } 97 | args.add(array); 98 | } else if (componentType.equals(Byte.class) || componentType.equals(byte.class)) { 99 | Object array = Array.newInstance(componentType, values.size()); 100 | for (int i = 0; i < values.size(); i++) { 101 | Array.set(array, i, Byte.valueOf(values.get(i))); 102 | } 103 | args.add(array); 104 | } else if (componentType.equals(Float.class) || componentType.equals(float.class)) { 105 | Object array = Array.newInstance(componentType, values.size()); 106 | for (int i = 0; i < values.size(); i++) { 107 | Array.set(array, i, Float.valueOf(values.get(i))); 108 | } 109 | args.add(array); 110 | } else if (componentType.equals(Double.class) || componentType.equals(double.class)) { 111 | Object array = Array.newInstance(componentType, values.size()); 112 | for (int i = 0; i < values.size(); i++) { 113 | Array.set(array, i, Double.valueOf(values.get(i))); 114 | } 115 | args.add(array); 116 | } else if (componentType.equals(Boolean.class) || componentType.equals(boolean.class)) { 117 | Object array = Array.newInstance(componentType, values.size()); 118 | for (int i = 0; i < values.size(); i++) { 119 | Array.set(array, i, Boolean.valueOf(values.get(i))); 120 | } 121 | args.add(array); 122 | } else if (componentType.equals(Short.class) || componentType.equals(short.class)) { 123 | Object array = Array.newInstance(componentType, values.size()); 124 | for (int i = 0; i < values.size(); i++) { 125 | Array.set(array, i, Short.valueOf(values.get(i))); 126 | } 127 | args.add(array); 128 | } 129 | } 130 | } 131 | 132 | public T call(FullHttpRequest request) { 133 | try { 134 | return (T) method.invoke(object, handleRequest(request)); 135 | } catch (Exception e) { 136 | log.error("Reasons for failure", e); 137 | return (T) GeneralResponse.error(e.getMessage()); 138 | } 139 | } 140 | 141 | private Object[] handleRequest(FullHttpRequest request) { 142 | HttpMethod method = request.method(); 143 | if (method == HttpMethod.GET) { 144 | return handleGetRequest(request); 145 | } else if (method == HttpMethod.POST) { 146 | return handlePostRequest(request); 147 | } else if (method == HttpMethod.PUT) { 148 | return handlePutRequest(request); 149 | } else if (method == HttpMethod.DELETE) { 150 | return handleDeleteRequest(request); 151 | } 152 | throw new RuntimeException(String.format("Unsupported '%s' request methods.", method)); 153 | } 154 | 155 | private Object[] handleGetRequest(FullHttpRequest request) { 156 | Parameter[] parameters = method.getParameters(); 157 | Class[] parameterTypes = method.getParameterTypes(); 158 | List args = new ArrayList<>(parameters.length); 159 | Map> parameterMap = RequestUtil.getParameterMap(request); 160 | verifyParameterAnnotations(parameterMap, method); 161 | for (int i = 0; i < parameters.length; i++) { 162 | List values = parameterMap.get(parameters[i].getName()); 163 | handleParameterTypes(parameterTypes[i], values, args); 164 | } 165 | return args.toArray(); 166 | } 167 | 168 | private Object[] handlePostRequest(FullHttpRequest request) { 169 | Class[] parameterTypes = method.getParameterTypes(); 170 | List args = Collections.singletonList(RequestUtil.postEntity(request, parameterTypes[0])); 171 | return args.toArray(); 172 | } 173 | 174 | private Object[] handlePutRequest(FullHttpRequest request) { 175 | return handlePostRequest(request); 176 | } 177 | 178 | private Object[] handleDeleteRequest(FullHttpRequest request) { 179 | return handleGetRequest(request); 180 | } 181 | } -------------------------------------------------------------------------------- /src/main/java/com/netty/http/server/router/HttpRouterTally.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server.router; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class HttpRouterTally { 12 | 13 | private String uri; 14 | 15 | private HttpMethod method; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | #org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.netty.http.server.order.controller.OrderController -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev 4 | application: 5 | name: netty-http-server 6 | datasource: 7 | driver-class-name: com.mysql.jdbc.Driver 8 | url: jdbc:mysql://192.168.1.139:3306/mall_order?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false 9 | username: root 10 | password: 123456 11 | dbcp2: 12 | initial-size: 20 13 | min-idle: 20 14 | max-wait-millis: 60000 15 | time-between-eviction-runs-millis: 60000 16 | min-evictable-idle-time-millis: 30000 17 | validation-query: select 'x' 18 | test-while-idle: true 19 | test-on-borrow: false 20 | test-on-return: false 21 | pool-prepared-statements: true 22 | 23 | mybatis: 24 | config-location: classpath:mybatis/mybatis-config.xml 25 | mapper-locations: classpath*:mapper/**/*.xml 26 | 27 | logging: 28 | level: 29 | com.netty.http.server: debug -------------------------------------------------------------------------------- /src/main/resources/mybatis/mybatis-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/java/com/netty/http/server/NettyHttpServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.netty.http.server; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.junit4.SpringRunner; 6 | 7 | @RunWith(SpringRunner.class) 8 | public class NettyHttpServerApplicationTests { 9 | 10 | @Test 11 | public void contextLoads() { 12 | } 13 | 14 | } 15 | --------------------------------------------------------------------------------