要实现怎样的效果
一个SpringBoot框架搭建起来的项目发布接口服务是这样的
SpringBoot搭建教程点击这里
@Controller @RequestMapping("/v1/product") public class DocController { @RequestMapping(value = "/{id}", method = RequestMethod.GET) @ResponseBody public WebResult search(@PathVariable("id") Integer id) { logger.debug("获取指定产品接收产品id=>%d", id); if (id == null || "".equals(id)) { logger.debug("产品id不能为空"); return WebResult.error(ERRORDetail.RC_0101001); } return WebResult.success(products.get(id)); } }
我希望我使用Netty构建的Web服务器也能使用这样便捷的注解方式去发布我的接口服务
该怎么做
- 使用Netty自带的编解码、聚合器构建一个带有Http编解码功能的服务器这一点其实非常简单,Netty提供了对应的Http协议的编解码以及聚合器,我们只需要在管道初始化的时候加载它们。
public class HttpPipelineInitializer extends ChannelInitializer<Channel> { //编解码处理器名称 public final static String CODEC = "codec"; //HTTP消息聚合处理器名称 public final static String AGGEGATOR = "aggegator"; //HTTP消息压缩处理器名称 public final static String COMPRESSOR = "compressor"; @Override protected void initChannel(Channel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(CODEC, new HttpServerCodec()); pipeline.addLast(AGGEGATOR, new HttpObjectAggregator(512 * 1024)); pipeline.addLast(COMPRESSOR,new HttpContentCompressor()); pipeline.addLast(new AllocHandler()); } }
- 实现RequestMapping注解,用于标识处理器或者控制器对应匹配的接口地址。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String[] value() default {}; }
- 提供启动入口,程序启动时创建Spring容器,并基于Spring初始化必要组件
- 提供程序入口类
public class CettyBootstrap { private static final Logger logger = LoggerFactory.getLogger(CettyBootstrap.class); private static final String DEFAULT_SPRING_XMLPATH = "classpath:applicantContext.xml"; private static final String DEFAULT_HTTP_SERVER_BEAN_NAME = "defaultHttpServer"; public static void create() { create(DEFAULT_SPRING_XMLPATH); } public static void create(String springXmlpath) { if (StringUtils.isEmpty(springXmlpath)) { springXmlpath = DEFAULT_SPRING_XMLPATH; } logger.debug("spring框架配置文件地址为{}", springXmlpath); try { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(springXmlpath.split("[,\\s]+")); context.start(); logger.debug("spring框架启动成功"); try { context.getBean(DEFAULT_HTTP_SERVER_BEAN_NAME, DefaultHttpServer.class); } catch (NoSuchBeanDefinitionException ex) { logger.warn("未配置HttpServer,采用默认配置启动"); context.getAutowireCapableBeanFactory().createBean(DefaultHttpServer.class); } } catch (BeansException e) { e.printStackTrace(); } } }
- 定义默认实现的HttpServer组件,随Spring容器启动时加载基于Netty的Web容器,并使用HandlerMapping组件初始化HttpPipelineInitializer管道,其中HandlerMapping如果未有用户定义则使用默认的DefaultHandlerMapping实现
public class DefaultHttpServer extends ApplicationObjectSupport { private static final Logger logger = LoggerFactory.getLogger(DefaultHttpServer.class); private static final String DEFAULT_HTTP_PORT = "8080"; private static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping"; private String port; private HandlerMapping handlerMapping; public void setPort(String port) { this.port = port; } @Override public void initApplicationContext(ApplicationContext applicationContext) { beforeInit(applicationContext); initHandlerMapping(applicationContext); initServer(); } void initHandlerMapping(ApplicationContext context) { try { this.handlerMapping = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); } catch (NoSuchBeanDefinitionException ex) { this.handlerMapping = context.getAutowireCapableBeanFactory().createBean(DefaultHandlerMapping.class); } } void initServer() { logger.debug("初始化服务器"); if (!HttpUtils.isPort(port)) { logger.warn("端口号不合法,使用默认端口{}", DEFAULT_HTTP_PORT); port = DEFAULT_HTTP_PORT; } EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(Integer.parseInt(port))) .childHandler(new HttpPipelineInitializer(handlerMapping)); ChannelFuture f = b.bind().sync(); logger.info("服务启动成功,监听{}端口", port); f.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { try { workerGroup.shutdownGracefully().sync(); bossGroup.shutdownGracefully().sync(); } catch (InterruptedException e) { e.printStackTrace(); } } } protected void beforeInit(ApplicationContext applicationContext) { } }
- 提供默认的HandlerMapping实现类,负责匹配@RequestMapping注解下的处理函数
public class DefaultHandlerMapping extends ApplicationObjectSupport implements HandlerMapping { Logger logger = LoggerFactory.getLogger(DefaultHandlerMapping.class); private static Map<String, HttpHandler> httpHandlerMap = new HashMap<String, HttpHandler>(); @Override public void initApplicationContext(ApplicationContext context) throws BeansException { logger.debug("初始化处理匹配器"); Map<String, Object> handles = context.getBeansWithAnnotation(Controller.class); try { for (Map.Entry<String, Object> entry : handles.entrySet()) { logger.debug("加载控制器{}", entry.getKey()); loadHttpHandler(entry.getValue()); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } void loadHttpHandler(Object value) throws IllegalAccessException, InstantiationException { Class clazz = value.getClass(); Object clazzFromInstance = clazz.newInstance(); Method[] method = clazz.getDeclaredMethods(); for (Method m : method) { if (m.isAnnotationPresent(RequestMapping.class)) { RequestMapping requestMapping = m.getAnnotation(RequestMapping.class); for (String url : requestMapping.value()) { HttpHandler httpHandler = httpHandlerMap.get(url); if (httpHandler == null) { logger.info("加载url为{}的处理器{}", url, m.getName()); httpHandlerMap.put(url, new HttpHandler(clazzFromInstance, m)); } else { logger.warn("url{}存在相同的处理器", url); } } } } } @Override public HttpHandler getHadnler(FullHttpRequest request) { return httpHandlerMap.get(request.uri()); } }
- 当请求进入时通过HandlerMapping组件匹配处理器,如果匹配失败则返回404
public class AllocHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private HandlerMapping handlerMapping; public AllocHandler(HandlerMapping handlerMapping) { this.handlerMapping = handlerMapping; } /* 异常处理 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR); super.exceptionCaught(ctx, cause); } @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception { HttpHandler httpHandler = handlerMapping.getHadnler(fullHttpRequest); if (httpHandler != null) { Object obj = httpHandler.execute(fullHttpRequest); if (obj instanceof String) { sendMessage(ctx, obj.toString()); } else { sendMessage(ctx, JSONObject.toJSONString(obj)); } } else { sendError(ctx, HttpResponseStatus.NOT_FOUND); } } private void sendMessage(ChannelHandlerContext ctx, String msg) { FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)); response.headers().set("Content-Type", "text/plain"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } private void sendError(ChannelHandlerContext ctx, HttpResponseStatus httpResponseStatus) { FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, Unpooled.copiedBuffer(httpResponseStatus.toString(), CharsetUtil.UTF_8)); response.headers().set("Content-Type", "text/plain"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }
测试与使用
- 建立一个TestController
@Controller public class TestController { @RequestMapping("/test") public String testHandler(FullHttpRequest fullHttpRequest) { return "1234"; } @RequestMapping("/zx") public String zx(FullHttpRequest fullHttpRequest) { return "zhuxiong"; } @RequestMapping("/obj") public Object obj(FullHttpRequest fullHttpRequest) { System.out.println("\n\n----------"); HttpHeaders httpHeaders = fullHttpRequest.headers(); Set<String> names = httpHeaders.names(); for (String name : names) { System.out.println(name + " : " + httpHeaders.get(name)); } System.out.println(""); ByteBuf byteBuf = fullHttpRequest.content(); byte[] byteArray = new byte[byteBuf.capacity()]; byteBuf.readBytes(byteArray); System.out.println(new String(byteArray)); System.out.println("----------\n\n"); JSONObject json = new JSONObject(); json.put("errCode", "00"); json.put("errMsg", "0000000(成功)"); json.put("data", null); return json; } }
- 启动Spring容器
public class HttpServerTest { public static void main(String[] args) throws Exception { CettyBootstrap.create(); // CettyBootstrap.create("classpath:applicationContext.xml"); } }
未来要做的
- 与Spring框架集成,将核心组件托管给Spring容器统一管理
- 提供静态资源映射
- 修改映射策略将请求映射至一个流程(一个处理器多个拦截器)
- 支持使用模板语法进行视图解析
