Github源码下载地址:https://github.com/chenxingxing6/sourcecode/tree/master/code-netty-tomcat


一、前言

Tomcat是一个轻量级的应用服务器,或者说是一个Servlet容器。Tomcat默认的Http实现是采用阻塞的Socket通信,每个请求都需要创建一个线程处理,当线程>500,性能非常低。Tomcat默认最大请求数为150,具体需要看硬件配置。此篇博客,需要通过netty实现tomcat通信,提高tomcat的并发量,同时加深对netty的学习。除此之外,我们还将了解一下热部署的原理,自己简单实现一个热部署。


二、Netty实现简易Tomcat

2.1 依赖配置文件
 <dependencies><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.31.Final</version></dependency>
</dependencies>
2.2项目结构目录

2.3 配置文件
application.name=web## 这里为了解析方便,我们用properties进行配置
servlet.test=testServlet
servlet.test.className=com.catalina.servlet.TestServlet
servlet.test.urlPattern=/test*
2.4 核心代码Tomcat
package com.catalina.server;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;/*** User: lanxinghua* Date: 2019/10/9 09:30* Desc:*/
public class Tomcat {/*** 开启服务* @param port* @throws Exception*/public void start(int port) {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workGroup = new NioEventLoopGroup();try {// 配置ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workGroup)// 主线程处理类.channel(NioServerSocketChannel.class)// 子线程处理类.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) {// response编码socketChannel.pipeline().addLast(new HttpResponseEncoder());// request解码socketChannel.pipeline().addLast(new HttpRequestDecoder());// 业务逻辑处理socketChannel.pipeline().addLast(new TomcatHandler());}})// 对主线程,最大分配128个线程.option(ChannelOption.SO_BACKLOG, 128)// 子线程保存长连接.childOption(ChannelOption.SO_KEEPALIVE, true);// 启动服务器ChannelFuture f = bootstrap.bind(port).sync();System.out.println("Netty Tomcat Server Is Start...http://localhost:" + port);// 主线程waitf.channel().closeFuture().sync();}catch (Exception e){e.printStackTrace();}finally {bossGroup.shutdownGracefully();workGroup.shutdownGracefully();}}
}
2.5 核心代码TomcatHandler
package com.catalina.server;import com.catalina.config.Config;
import com.catalina.http.Request;
import com.catalina.http.Response;
import com.catalina.http.Servlet;
import com.hot.MyClassLoader;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpRequest;import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;/*** User: lanxinghua* Date: 2019/10/9 09:41* Desc:*/
public class TomcatHandler extends ChannelInboundHandlerAdapter {private static final Map<Pattern,String> servletMapping = new HashMap<Pattern, String>();private static String applicationName;static {Config.load("web.properties");applicationName = Config.getValue("application.name");for (String key : Config.getKeys()) {if (key.startsWith("servlet")){String name = key.replaceFirst("servlet.", "");if (name.indexOf(".") != -1){name = name.substring(0, name.indexOf("."));}String pattern = Config.getValue("servlet." + name + ".urlPattern").replaceAll("\\*", ".*");if (applicationName != null){pattern = "/"+ applicationName + pattern;}String className = Config.getValue("servlet." + name + ".className");if (!servletMapping.containsKey(pattern)){try {servletMapping.put(Pattern.compile(pattern), className);}catch (Exception e){e.printStackTrace();}}}}}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if (msg instanceof HttpRequest){HttpRequest httpRequest = (HttpRequest) msg;Request request = new Request(ctx, httpRequest);Response response = new Response(ctx, httpRequest);doServlet(request, response);}}public void doServlet(Request request, Response response){String uri = request.getUri();String requestType = request.getRequestType();System.out.println("请求:" + uri + "type:" + requestType);if (applicationName != null && !uri.contains(applicationName)){String out = String.format("404 Not Found");response.write(out);return;}try {boolean hasPattern = false;for (Map.Entry<Pattern, String> entry : servletMapping.entrySet()) {if (entry.getKey().matcher(uri).matches()){// 自定义类加载器,实现热部署Class<?> clz = null;try {// 用自定义类加载器,是为了实现热部署MyClassLoader myLoader = new MyClassLoader();clz = myLoader.findClass(entry.getValue());}catch (Exception e){throw new RuntimeException("类加载异常," + e.getMessage());}Servlet servlet = (Servlet) clz.newInstance();if ("get".equalsIgnoreCase(requestType)){servlet.doGet(request, response);}else {servlet.doPost(request, response);}hasPattern = true;}}if (!hasPattern){String out = String.format("404 Not Found");response.write(out);return;}}catch (Exception e){String out = String.format("500 Error msg:%s", e.getStackTrace());response.write(out);}}
}
2.6 服务启动BootStrap
package com.catalina.server;
/*** User: lanxinghua* Date: 2019/10/9 09:31* Desc: 服务启动*/
public class BootStrap {public static void main(String[] args) {new Tomcat().start(8080);}
}
2.7 测试结果


到这里一个基于netty简易版的Tomcat就实现了。全部代码,可以看github哦,亲…


三、热部署

3.1 类加载器

Java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被Java虚拟机直接使用的Java类型, 这就是虚拟机的类加载机制.JVM中用来完成上述功能的具体实现就是类加载器.类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个 实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象.

3.2 热部署

对于Java应用程序来说,热部署就是在运行时更新Java类文件。也就是不重启服务器的情况下实现java类文件的替换修改等.举个例子,就像电脑可以在不重启 的情况下,更换U盘。简单一句话让JVM重新加载新的class文件!

3.3 实现思路

1.监听修改文件,生成class文件,并进行替换
2.创建新的类加载器,加载更新的class文件

3.4 核心代码FileMonitor
package com.hot;import sun.nio.ch.IOUtil;import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.*;
import java.util.concurrent.TimeUnit;/*** User: lanxinghua* Date: 2019/10/9 15:00* Desc: 开启热部署,com.catalina.servlet下的文件* 修改servlet文件,保存,会自动实现热更新部署*/
public class FileMonitor {private static final String projectName = "code-netty-tomcat";private static final String packagePath = "com/catalina/servlet/";public static void main(String[] args) {new FileMonitor().start();}public void start(){System.out.println("开启热部署.....");try {Path path = Paths.get(projectName + "/src/main/java/" + packagePath);WatchService watcher = FileSystems.getDefault().newWatchService();path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);new Thread(() -> {while (true) {try {WatchKey key = watcher.take();for (WatchEvent<?> event : key.pollEvents()) {if (event.kind() == StandardWatchEventKinds.OVERFLOW){// 事件可能是lost or discardedcontinue;}Path p = (Path) event.context();System.out.println("------------ start 热部署 --------------");hotDeploy(p);System.out.println("------------- end 热部署 --------------");}if (!key.reset()){break;}}catch (Exception e){e.printStackTrace();}}}).start();}catch (Exception e){e.printStackTrace();}try {TimeUnit.SECONDS.sleep(60*10);}catch (Exception e){e.printStackTrace();}}/*** 热部署* @param path*/public static void hotDeploy(Path path){String fileName = path.toFile().getName();// java源码路径String prefixPath = projectName + "/src/main/java/" + packagePath;String sourceCodePath = prefixPath+fileName;fileName = fileName.replace(".java", ".class");try {System.gc();String p = projectName + "/target/classes/"+ packagePath +fileName;File oldFile = new File(p);oldFile.delete();// 对源码进行编译doCompile(sourceCodePath);// 编译后端class文件移动到target对应的目录中去moveFile(prefixPath + fileName, p);}catch (Exception e){e.printStackTrace();}}private static void doCompile(String sourceCodePath){try {System.out.println("源码文件进行编译:"+sourceCodePath);File file = new File(sourceCodePath);JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);Iterable iterable = manager.getJavaFileObjects(file);JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);task.call();manager.close();}catch (Exception e){e.printStackTrace();}}private static void moveFile(String srcPathStr, String desPathStr) {try{// 创建输入输出流对象File file = new File(desPathStr);if (!file.exists()){file.createNewFile();}FileInputStream fis = new FileInputStream(srcPathStr);FileOutputStream fos = new FileOutputStream(desPathStr);//创建搬运工具byte datas[] = new byte[1024*8];//创建长度int len = 0;//循环读取数据while((len = fis.read(datas))!=-1){fos.write(datas,0,len);}//释放资源fis.close();fis.close();File srcFile = new File(srcPathStr);srcFile.delete();}catch (Exception e){e.printStackTrace();}}
}

这里我们默认监听servlet目录下的文件,修改servlet文件后,可以实现自动热部署。

3.5 修改servlet文件TestServlet
public class TestServlet extends Servlet {public void doGet(Request req, Response resp) throws Exception {this.doPost(req, resp);}public void doPost(Request req, Response resp) throws Exception {String name = req.getParamter("name");resp.setCharSet("UTF-8");resp.setContentType("text/html; charset=utf-8");String html = "<h2>自己手写Netty实现Tomcat&热部署</h2><hr>";html += "<div>name:"+ name +"<div>";// 修改的东西html += "update.....";resp.write(html);}
}
3.6 文件修改监听日志
------------ start 热部署 --------------
源码文件进行编译:code-netty-tomcat/src/main/java/com/catalina/servlet/TestServlet.java
------------- end 热部署 --------------
3.7 测试结果


希望给路过的朋友们可以解决一些疑惑,走过路过,不用错过,感兴趣就关注一波吧,感谢…

第五篇 - 手写Tomcat(基于Netty)热部署相关推荐

  1. 第二篇 - 手写Tomcat服务器

    Github源码下载地址:https://github.com/chenxingxing6/Mytomcat CSDN源码下载地址:https://download.csdn.net/download ...

  2. 手写一个基于NIO的迷你版Tomcat

    笔者也建立的自己的公众号啦,平时会分享一些编程知识,欢迎各位大佬支持~ 扫码或微信搜索北风IT之路关注 本文公众号地址:手写一个基于NIO的迷你版Tomcat 在很久之前看到了一篇文章写一个迷你版的T ...

  3. tomcat配置url跳转_架构成长之路:还不清楚原理就晚了,Java技术大咖带你手写Tomcat...

    推荐阅读 手把手教你手写SpringMVC,剑指优秀开源框架灵魂 纯手写实现高可用的RPC框架,Java架构师必备技能 前言 Tomcat,这只3脚猫,大学的时候就认识了,直到现在工作中,也常会和它打 ...

  4. 纯jsp实现评论功能_自己实现的java手写tomcat

    这是一个java写的模拟tomcat工作原理的demo,是一个极简的tomcat服务器,也是我们培训班(邦邦IT教育)的讲义,是整个j2ee培训的精髓,理解了这个demo其实后面的很多东西都是可以自学 ...

  5. Web开发如何实现Tomcat等服务器热部署不用重启

    Web开发如何实现Tomcat等服务器热部署不用重启 听语音 | 浏览:354 | 更新:2016-05-28 11:18 在进行java web开发的时候,对类改动一些代码后,通常就需要对服务器比如 ...

  6. Linux安装tomcat并使用+热部署

    安装tomcat 1.解压缩 apache-tomcat-7.0.47.tar.gz:     tar -zxvf apache-tomcat-7.0.47.tar.gz 2.创建 /usr/loca ...

  7. 第六篇 - 手写基于接口实现动态代理

    Github源码下载地址:https://github.com/chenxingxing6/sourcecode/tree/master/code-proxy 一.前言 我们知道常见的动态代理有两种实 ...

  8. 第四篇 - 手写RPC框架

    Github源码下载地址:https://github.com/chenxingxing6/myrpc 一.前言 RPC(Remote Procedure Call)-远程过程调用,它是一种通过网络从 ...

  9. 第一篇 - 手写SpringMvc框架

    Github源码下载地址:https://github.com/chenxingxing6/springmvc CSDN源码下载地址:https://download.csdn.net/downloa ...

最新文章

  1. 基于全局场景背景图和关系优化的全景3D场景理解(ICCV 2021)
  2. 从Image Caption Generation理解深度学习
  3. 递归学习五--折半查找--java
  4. python 如何在linux进行调试
  5. CodeForces 340C
  6. JVM:JVM内存划分、主内存、工作内存
  7. Python 内建函数 - sorted(iterable[, key][, reverse])
  8. 23-26 Python File方法、OS文件/目录方法、异常处理、内置函数
  9. 转载 2012年游戏行业人才需求预测
  10. 渗透测试工具——BurpSuite
  11. Uipath文档教程
  12. 艾宾浩斯英语单词记忆表格生成器
  13. ubuntu 910 下安装万能五笔
  14. Python使用scrapy爬取阳光热线问政平台过程解析
  15. 如何破解linux密码
  16. BZOJ 1513 [POI2006]Tet-Tetris 3D 二维线段树
  17. 中外法律文献查找下载常用数据库大盘点
  18. 增加路由表地址实现双网卡域名同时解析
  19. Tekton系列之实践篇-如何用Jenkins来管理Tekton
  20. dw怎么用css做图片轮播(收藏)

热门文章

  1. Unity3dRPG 相机跟随player旋转_人物头部和眼睛实现跟随目标转动的轻量级IK实践...
  2. Flask-socketio服务器端与js客户端socket.io版本不匹配问题解决
  3. 【计算机网络】—— 概念、组成、功能和分类
  4. 用python计算复利计算器_Python 常用笔记
  5. Win10+VS2019+Nvidia_Geforce_GTX_1080Ti编译YOLOv4
  6. android 查看gpio状态_android gpio 调试
  7. Nginx在windows下使用为什么死掉
  8. 现代微型计算机的主要技术,《现代微型计算机原理与接口技术》
  9. 解读集成墙板有刺鼻的气味道是怎么回事
  10. 百度清华联合发布《中国人工智能社会认知报告》【附全文下载】