本篇主要对Web的实现做说明,在参考文章的同事,可以把code clone下来,看,代码中有很多需要优化的地址,我已经用TODO标记处理啊,小编会不断的进行优化和分析,演示SmileBootDemo也可以git clone,debug学习
目录
- 核心描述类介绍
- Smile启动核心实现
- Http请求多线程异步实现
- 下一篇主要介绍内容
-
- 扩展
1. 核心描述类,主要保存处理方法及参数类型
其实所有方法的执行,都离不开ioc的实现,IOC主要将组件(被@SmileComponent标记过的都为组件)保存为BeanDefinition的形式,而组件中的method,主要保存为WebDefinition的形式,而method的参数名称,参数类型,参数位置索引,主要保存在ParamterDefinition,对于请求的处理,就是根据url找到对应的method,然后根据ParamterDefinition将请求参数,转换成参数原本类型,然后处理
描述类 | 说明 | 存放位置 |
---|---|---|
BeanDefinition | 保存组件Class字节码及实例化对象 | Map<String(beanName), BeanDefinition> registeredBeans |
WebDefinition | 保存Url及对应的处理方法,及实例化对象,及参数类型 | Map<String(url), WebDefinition> webHandlerMaps = new ConcurrentHashMap<>(); |
ParamterDefinition | 保存参数位置索引及参数名称,参数类型,参数注解 | WebDefinition.ParamterDefinition |
2. Smile启动核心
-
2.1 SmileApplicationContext 扫描所有组件,并check 是否需要代理,最终生成IOC容器
Map<String, BeanDefinition> registeredBeans = new ConcurrentHashMap<>();
-
2.2 SmileApplicationContext生成IOC容器之后,加载是否有实现ExtApplicationContext扩展类
scanExtContext()
方法WebApplicationContext此时会执行,并扫描被@ResController注解修饰的路由类,然后
扫描其Methods,获取方法中有@GetMapping和@PostMapping 的方法生成
WebDefinition
和ParamterDefinition
Consumer<Map.Entry<String, BeanDefinition>> entryConsumer = entry -> { BeanDefinition beanDefinition = entry.getValue(); Class<?> controllerClass = beanDefinition.getClazz(); RestController annotation = controllerClass.getAnnotation(RestController.class); String oneUrl = annotation.value(); Method[] methods = controllerClass.getMethods(); for (Method method : methods) { boolean isGet = method.isAnnotationPresent(GetMapping.class); if (isGet) { bindGetMethod(oneUrl, method, beanDefinition); } boolean isPost = method.isAnnotationPresent(PostMapping.class); if (isPost) { bindPostMethod(oneUrl, method, beanDefinition); } } }; definitionMap.entrySet().forEach(entryConsumer);
/** * 绑定get请求 * * @param oneUrl 一级url * @param method 方法 * @param beanDefinition bean描述 */ public void bindGetMethod(String oneUrl, Method method, BeanDefinition beanDefinition) { Object controllerInstance = beanDefinition.getInstance(); Package aPackage = beanDefinition.getClazz().getPackage(); GetMapping getMapping = method.getAnnotation(GetMapping.class); String twoUrl = getMapping.value(); String[] parameterNames = WebTools.getParameterNames(method); if (StringTools.isEmpty(twoUrl)) { throw new BindUrlHanderException("[ " + aPackage.getName() + " ]:绑定url异常,请检查,请填写需要绑定的url地址"); } String realUrl = WebTools.checkUrl(oneUrl, twoUrl); String methodPath = method.toGenericString(); logger.info("Mapped url:[{}],produces:[{}],consumes:[{}],paramter:{},onto:{}", realUrl, getMapping.produces(), getMapping.consumes(), parameterNames, methodPath); webHandlerMaps.put(realUrl, new WebDefinition(realUrl, RequestMethod.GET, getMapping.consumes(), getMapping.produces(), controllerInstance, method, parameterNames)); }
3. HTTP处理实现核心
-
3.1将每个请求生成一个MessageRequest及MessageResponse,作为一个任务交给线程池去异步执行
MessageRequest messageRequest = new MessageRequest(randomUUID, requestMethod, requestParams, webDefinition, headerMaps); MessageResponse messageResponse = new MessageResponse(); SmileTaskChoice smileTaskChoice = new DefaultTaskProcessChoice(messageRequest, messageResponse, false); SmileMessageExecutor.submit(smileTaskChoice.choice(), ctx, req, messageRequest, messageResponse);
-
3.2 SmileTaskChoice 是执行策略,当为ture时候,为rpc默认,可以定义自己的rpc远程调用的方式实现结果返回
public class DefaultTaskProcessChoice implements SmileTaskChoice { private boolean isRpc = false; private MessageRequest messageRequest; private MessageResponse messageResponse; /** * * @param request * @param response * @param isRpc 本地方法:false rpc调用:true */ public DefaultTaskProcessChoice(final MessageRequest request, final MessageResponse response, boolean isRpc) { this.messageRequest = request; this.messageResponse = response; this.isRpc = isRpc; } @Override public Callable choice() { Callable callTask =null; if (!isRpc) { callTask = new LocalMessageTask(messageRequest, messageResponse); }else { callTask=new RpcProcessTask(messageRequest,messageResponse); } return callTask; } }
-
3.3 SmileTask中
主要利用反射,将请求 URL
获取到 WebDefinition
,拿到执行方法,将请求参数,绑定到方法,作为实参,传递
Object invokeResult = method.invoke(controller, args);
并通过Netty 连接通道Channel把处理结果返回给客户端
-
3.4 SmileMessageExecutor.submit 方法中监听任务是否成功处理,成功并通过Netty 连接通道Channel把处理结果返回给客户端
public static void submit(Callable<Boolean> task, final ChannelHandlerContext ctx, HttpRequest metaRequest, final MessageRequest request, final MessageResponse response) { /** * SmileThreadFactory 目的构建自己的线程名,并通过线程组进行统一管理 * SmileThreadPoolExecutor 构建自己的线程池,对任务进行,细微管理 */ if (threadPoolExecutor == null) { SmileThreadPoolExecutor smileThreadPoolExecutor = new SmileThreadPoolExecutor(new SmileThreadFactory("Smile")); ThreadPoolExecutor executorService = (ThreadPoolExecutor) smileThreadPoolExecutor.getExecutory(); threadPoolExecutor = MoreExecutors.listeningDecorator(executorService); } /** * 处理完成任务如果任务完成就,渲染出去 */ ListenableFuture<Boolean> listenableFuture = threadPoolExecutor.submit(task); Futures.addCallback(listenableFuture, new FutureCallback<Boolean>() { @Override public void onSuccess(Boolean result) { if (result){ NettyResponse.writeResponseAndListener(ctx.channel(), request, response, new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { channelFuture.channel().close(); logger.info("Smile Server Send message-id:{}" , request.getMessageId()); } }); } } @Override public void onFailure(Throwable t) { t.printStackTrace(); } }, threadPoolExecutor); }
4. 下一篇主要介绍内容
框架的实现方案属于传统的MVC机构,只不过吧视图层V取消掉了,这也是趋势,前后分离,后端只做关心数据处理,
而传统的MVC架构,核心为Servlet,SpringMVC核心为DispatchServlet,是对原始Java Servlet的一个封装,关于这点可以看小编的另一篇文章手写一个轻量级的网关API,当然使用Netty也是如此,我们的入口就是HttpDispatchServerHandler
核心方法就是messageReceivedDispatch
而只知道,这些是远远不够的,Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,我会新开二篇,专门介绍IO模型重点介绍IO multiplexing(IO多路复用)
和Netty
如何工作 ! 包括如何实现,心跳检测
主要会介绍下面写模块
- Bootstrap or ServerBootstrap
- EventLoop
- EventLoopGroup
- ChannelPipeline
- Future or ChannelFuture
- ChannelInitializer
- ChannelHandler
- ByteToMessageDecoder
- MessageToByteEncoder
public void messageReceivedDispatch(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
String dispatchUrl = "";
Map<String, Object> headerMaps = new ConcurrentHashMap<>();
if (msg instanceof HttpRequest) {
HttpRequest req = this.request = (HttpRequest) msg;
HttpHeaders headers = req.headers();
headers.entries().stream().forEach(x -> {
headerMaps.put(x.getKey(), x.getValue());
});
String contentType = request.headers().get("Content-Type");
String methodName = request.getMethod().name();
dispatchUrl = req.getUri();
String randomUUID = UUID.randomUUID().toString().replaceAll("-", "");
Map<String, Object> requestParams = new ConcurrentHashMap<>();
// 处理get请求
if (methodName.equalsIgnoreCase("GET")) {
boolean contains = dispatchUrl.contains("?");
if (contains){
String queryContent = dispatchUrl.substring(dispatchUrl.indexOf("?") + 1);
Map<String, Object> queryParameterFromContent = URLTools.getQueryParameterFromContent(queryContent);
queryParameterFromContent.entrySet().forEach(entry -> {
requestParams.put(entry.getKey(), entry.getValue());
});
}
}
// 处理POST请求
if (methodName.equalsIgnoreCase("POST")) {
if (StringTools.endsWithIgnoreCase(contentType, "application/json")) {
FullHttpRequest request1 = (FullHttpRequest) msg;
ByteBuf jsonBuf = request1.content();
String jsonStr = jsonBuf.toString(CharsetUtil.UTF_8).replaceAll("\\\\s*|\\t|\\r|\\n", "");
if (!StringTools.isEmpty(jsonStr)) {
requestParams.put("BODY", jsonStr);
}
} else {
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(
new DefaultHttpDataFactory(false), req);
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());
}
}
}
}
if (StringTools.contains(dispatchUrl,"?")){
dispatchUrl = dispatchUrl.substring(0, dispatchUrl.indexOf("?"));
}
RequestMethod requestMethod = WebTools.getRequestMethod(methodName);
WebDefinition webDefinition = WebContextTools.getWebDefinitionByUrl(dispatchUrl, requestMethod);
if (webDefinition instanceof Web404Definition) {
NettyResponse.writeResponse(ctx.channel(), "Not Found", HttpResponseStatus.NOT_FOUND);
return;
}
if (webDefinition instanceof Web405Definition) {
NettyResponse.writeResponse(ctx.channel(), "Method Not Allowed", HttpResponseStatus.METHOD_NOT_ALLOWED);
return;
}
String consumes = webDefinition.getConsumes();
if (StringTools.isNotEmpty(contentType)){
if (StringTools.isNotEmpty(consumes)&(!contentType.equalsIgnoreCase(consumes))){
NettyResponse.writeResponse(ctx.channel(), "Bad Request (The content-type don't match)", HttpResponseStatus.BAD_REQUEST);
return;
}
}
/**
* //TODO 异步处理url获取处理的 bean
*/
MessageRequest messageRequest = new MessageRequest(randomUUID, requestMethod, requestParams, webDefinition, headerMaps);
MessageResponse messageResponse = new MessageResponse();
/**
* //TODO 根据启动配置,当如果是rpc服务就要使用MessageProcessTask
* 如果是本地服务使用LocalMessageTask
*
* 此时MessageRequest和MessageResponse都是final 修饰,目的是保证始终是对当前的MessageResponse
*/
SmileTaskChoice smileTaskChoice = new DefaultTaskProcessChoice(messageRequest, messageResponse, false);
/**
* //TODO 交给线程处理异步处理响应
*/
SmileMessageExecutor.submit(smileTaskChoice.choice(), ctx, req, messageRequest, messageResponse);
}
}
扩展
再次声明小编也是一个菜鸟,是一只具有学习精神,并热爱编程的菜鸟, 所有的文章都是经过参考很多优秀博文,给我带来的进步,小编,希望将学习到的所有知识点,也分享给大家 ! 小编会在这里列出,参考到的优秀博文,尊重每位知识传播者的劳动果实.
如果您发现小编文章中,有错误,请及时指出,并通知小编改正,小编在此谢过.
欢迎继续关注小编~ 小编努力coding…
参考
Smart Framework 设计动力来源
segmentfault-Netty 源码分析 Netty强化学习
SpringIOC源码分析 描述类灵感来源
网友评论