要實現怎樣的效果
一個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");
}
}
複製程式碼
未來要做的
- [x] 與Spring框架整合,將核心元件託管給Spring容器統一管理
- [ ] 提供靜態資源對映
- [ ] 修改對映策略將請求對映至一個流程(一個處理器多個攔截器)
- [ ] 支援使用模板語法進行檢視解析