how2j.cn

下载区
文件名 文件大小
请先登录 11m
增值内容 11m
11m

14分22秒
本视频采用html5方式播放,如无法正常播放,请将浏览器升级至最新版本,推荐火狐,chrome,360浏览器。 如果装有迅雷,播放视频呈现直接下载状态,请调整 迅雷系统设置-基本设置-启动-监视全部浏览器 (去掉这个选项)。 chrome 的 视频下载插件会影响播放,如 IDM 等,请关闭或者切换其他浏览器

步骤 1 : 拓扑图点亮   
步骤 2 : 热加载概念   
步骤 3 : 热加载流程图   
步骤 4 : server.xml   
步骤 5 : ServerXMLUtil   
步骤 6 : ContextFileChangeWatcher   
步骤 7 : Context   
步骤 8 : Host   
步骤 9 : 测试   
步骤 10 : TestTomcat   
步骤 11 : 比较可运行项目,快速定位问题   

增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
拓扑图点亮
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
步骤 3 :

热加载流程图

edit
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
热加载流程图
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
<?xml version="1.0" encoding="UTF-8"?> <Server> <Service name="Catalina"> <Connector port="18080"/> <Connector port="18081"/> <Connector port="18082"/> <Engine defaultHost="localhost"> <Host name = "localhost"> <Context path="/b" docBase="d:/project/diytomcat/b" /> <Context path="/javaweb" docBase="d:/project/javaweb/web" reloadable = "true" /> </Host> </Engine> </Service> </Server>
<?xml version="1.0" encoding="UTF-8"?>
<Server>
    <Service name="Catalina">
        <Connector port="18080"/>
        <Connector port="18081"/>
        <Connector port="18082"/>
        <Engine defaultHost="localhost">
            <Host name = "localhost">
                <Context path="/b" docBase="d:/project/diytomcat/b"  />
                <Context path="/javaweb" docBase="d:/project/javaweb/web" reloadable = "true" />
            </Host>
        </Engine>
    </Service>
</Server>
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
package cn.how2j.diytomcat.util; import cn.how2j.diytomcat.catalina.*; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.List; public class ServerXMLUtil { public static List<Connector> getConnectors(Service service) { List<Connector> result = new ArrayList<>(); String xml = FileUtil.readUtf8String(Constant.serverXmlFile); Document d = Jsoup.parse(xml); Elements es = d.select("Connector"); for (Element e : es) { int port = Convert.toInt(e.attr("port")); Connector c = new Connector(service); c.setPort(port); result.add(c); } return result; } public static List<Context> getContexts(Host host) { List<Context> result = new ArrayList<>(); String xml = FileUtil.readUtf8String(Constant.serverXmlFile); Document d = Jsoup.parse(xml); Elements es = d.select("Context"); for (Element e : es) { String path = e.attr("path"); String docBase = e.attr("docBase"); boolean reloadable = Convert.toBool(e.attr("reloadable"), true); Context context = new Context(path, docBase, host, reloadable); result.add(context); } return result; } public static String getEngineDefaultHost() { String xml = FileUtil.readUtf8String(Constant.serverXmlFile); Document d = Jsoup.parse(xml); Element host = d.select("Engine").first(); return host.attr("defaultHost"); } public static String getServiceName() { String xml = FileUtil.readUtf8String(Constant.serverXmlFile); Document d = Jsoup.parse(xml); Element host = d.select("Service").first(); return host.attr("name"); } public static List<Host> getHosts(Engine engine) { List<Host> result = new ArrayList<>(); String xml = FileUtil.readUtf8String(Constant.serverXmlFile); Document d = Jsoup.parse(xml); Elements es = d.select("Host"); for (Element e : es) { String name = e.attr("name"); Host host = new Host(name,engine); result.add(host); } return result; } }
步骤 6 :

ContextFileChangeWatcher

edit
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
package cn.how2j.diytomcat.watcher; import java.nio.file.Path; import java.nio.file.WatchEvent; import cn.how2j.diytomcat.catalina.Context; import cn.how2j.diytomcat.catalina.Host; import cn.hutool.core.io.watch.WatchMonitor; import cn.hutool.core.io.watch.WatchUtil; import cn.hutool.core.io.watch.Watcher; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.log.LogFactory; public class ContextFileChangeWatcher { private WatchMonitor monitor; private boolean stop = false; public ContextFileChangeWatcher(Context context) { this.monitor = WatchUtil.createAll(context.getDocBase(), Integer.MAX_VALUE, new Watcher() { private void dealWith(WatchEvent<?> event) { synchronized (ContextFileChangeWatcher.class) { String fileName = event.context().toString(); if (stop) return; if (fileName.endsWith(".jar") || fileName.endsWith(".class") || fileName.endsWith(".xml")) { stop = true; LogFactory.get().info(ContextFileChangeWatcher.this + " 检测到了Web应用下的重要文件变化 {} " , fileName); context.reload(); } } } @Override public void onCreate(WatchEvent<?> event, Path currentPath) { dealWith(event); } @Override public void onModify(WatchEvent<?> event, Path currentPath) { dealWith(event); } @Override public void onDelete(WatchEvent<?> event, Path currentPath) { dealWith(event); } @Override public void onOverflow(WatchEvent<?> event, Path currentPath) { dealWith(event); } }); this.monitor.setDaemon(true); } public void start() { monitor.start(); } public void stop() { monitor.close(); } }
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
package cn.how2j.diytomcat.catalina; import cn.how2j.diytomcat.classloader.WebappClassLoader; import cn.how2j.diytomcat.exception.WebConfigDuplicatedException; import cn.how2j.diytomcat.util.ContextXMLUtil; import cn.how2j.diytomcat.watcher.ContextFileChangeWatcher; import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.LogFactory; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.File; import java.util.*; public class Context { private String path; private String docBase; private File contextWebXmlFile; private Map<String, String> url_servletClassName; private Map<String, String> url_ServletName; private Map<String, String> servletName_className; private Map<String, String> className_servletName; private WebappClassLoader webappClassLoader; private Host host; private boolean reloadable; private ContextFileChangeWatcher contextFileChangeWatcher; public Context(String path, String docBase, Host host, boolean reloadable) { TimeInterval timeInterval = DateUtil.timer(); this.host = host; this.reloadable = reloadable; this.path = path; this.docBase = docBase; this.contextWebXmlFile = new File(docBase, ContextXMLUtil.getWatchedResource()); this.url_servletClassName = new HashMap<>(); this.url_ServletName = new HashMap<>(); this.servletName_className = new HashMap<>(); this.className_servletName = new HashMap<>(); ClassLoader commonClassLoader = Thread.currentThread().getContextClassLoader(); this.webappClassLoader = new WebappClassLoader(docBase, commonClassLoader); LogFactory.get().info("Deploying web application directory {}", this.docBase); deploy(); LogFactory.get().info("Deployment of web application directory {} has finished in {} ms", this.docBase,timeInterval.intervalMs()); } public void reload() { host.reload(this); } private void deploy() { TimeInterval timeInterval = DateUtil.timer(); init(); if(reloadable){ contextFileChangeWatcher = new ContextFileChangeWatcher(this); contextFileChangeWatcher.start(); } } private void init() { if (!contextWebXmlFile.exists()) return; try { checkDuplicated(); } catch (WebConfigDuplicatedException e) { // TODO Auto-generated catch block e.printStackTrace(); return; } String xml = FileUtil.readUtf8String(contextWebXmlFile); Document d = Jsoup.parse(xml); parseServletMapping(d); } private void parseServletMapping(Document d) { // url_ServletName Elements mappingurlElements = d.select("servlet-mapping url-pattern"); for (Element mappingurlElement : mappingurlElements) { String urlPattern = mappingurlElement.text(); String servletName = mappingurlElement.parent().select("servlet-name").first().text(); url_ServletName.put(urlPattern, servletName); } // servletName_className / className_servletName Elements servletNameElements = d.select("servlet servlet-name"); for (Element servletNameElement : servletNameElements) { String servletName = servletNameElement.text(); String servletClass = servletNameElement.parent().select("servlet-class").first().text(); servletName_className.put(servletName, servletClass); className_servletName.put(servletClass, servletName); } // url_servletClassName Set<String> urls = url_ServletName.keySet(); for (String url : urls) { String servletName = url_ServletName.get(url); String servletClassName = servletName_className.get(servletName); url_servletClassName.put(url, servletClassName); } } private void checkDuplicated(Document d, String mapping, String desc) throws WebConfigDuplicatedException { Elements elements = d.select(mapping); // 判断逻辑是放入一个集合,然后把集合排序之后看两临两个元素是否相同 List<String> contents = new ArrayList<>(); for (Element e : elements) { contents.add(e.text()); } Collections.sort(contents); for (int i = 0; i < contents.size() - 1; i++) { String contentPre = contents.get(i); String contentNext = contents.get(i + 1); if (contentPre.equals(contentNext)) { throw new WebConfigDuplicatedException(StrUtil.format(desc, contentPre)); } } } private void checkDuplicated() throws WebConfigDuplicatedException { String xml = FileUtil.readUtf8String(contextWebXmlFile); Document d = Jsoup.parse(xml); checkDuplicated(d, "servlet-mapping url-pattern", "servlet url 重复,请保持其唯一性:{} "); checkDuplicated(d, "servlet servlet-name", "servlet 名称重复,请保持其唯一性:{} "); checkDuplicated(d, "servlet servlet-class", "servlet 类名重复,请保持其唯一性:{} "); } public String getServletClassName(String uri) { return url_servletClassName.get(uri); } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getDocBase() { return docBase; } public void setDocBase(String docBase) { this.docBase = docBase; } public WebappClassLoader getWebappClassLoader() { return webappClassLoader; } public void stop() { webappClassLoader.stop(); contextFileChangeWatcher.stop(); } public boolean isReloadable() { return reloadable; } public void setReloadable(boolean reloadable) { this.reloadable = reloadable; } }
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
package cn.how2j.diytomcat.catalina; import cn.how2j.diytomcat.util.Constant; import cn.how2j.diytomcat.util.ServerXMLUtil; import cn.hutool.log.LogFactory; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; public class Host { private String name; private Map<String, Context> contextMap; private Engine engine; public Host(String name, Engine engine){ this.contextMap = new HashMap<>(); this.name = name; this.engine = engine; scanContextsOnWebAppsFolder(); scanContextsInServerXML(); } public String getName() { return name; } public void setName(String name) { this.name = name; } private void scanContextsInServerXML() { List<Context> contexts = ServerXMLUtil.getContexts(this); for (Context context : contexts) { contextMap.put(context.getPath(), context); } } private void scanContextsOnWebAppsFolder() { File[] folders = Constant.webappsFolder.listFiles(); for (File folder : folders) { if (!folder.isDirectory()) continue; loadContext(folder); } } private void loadContext(File folder) { String path = folder.getName(); if ("ROOT".equals(path)) path = "/"; else path = "/" + path; String docBase = folder.getAbsolutePath(); Context context = new Context(path,docBase,this, true); contextMap.put(context.getPath(), context); } public Context getContext(String path) { return contextMap.get(path); } public void reload(Context context) { LogFactory.get().info("Reloading Context with name [{}] has started", context.getPath()); String path = context.getPath(); String docBase = context.getDocBase(); boolean reloadable = context.isReloadable(); // stop context.stop(); // remove contextMap.remove(path); // allocate new context Context newContext = new Context(path, docBase, this, reloadable); // assign it to map contextMap.put(newContext.getPath(), newContext); LogFactory.get().info("Reloading Context with name [{}] has completed", context.getPath()); } }
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
测试
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢
步骤 11 :

比较可运行项目,快速定位问题

edit
增值内容,请先登录
自己写一个Tomcat, 几乎使用到了除开框架外的所有Java 技术,如多线程,Socket, J2EE, 反射,Log4j, JSoup, JUnit, Html 等一整套技术栈, 从无到有,循序渐进涵盖全部74个知识点,549个开发步骤, 为竞争高薪资职位加上一个有吸引力的砝码.
增值内容,点击购买
使用爬虫已经被系统记录,请勿使用爬虫,增大封号风险。 如果是误封 ,请联系站长,谢谢


HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。


问答区域    
2023-04-06 第二次修改不更新的原因
慕羽1314




第二次变化后监听器收到的变化文件名永远是cn
加载中

							

							


1 个答案

慕羽1314
答案时间:2023-04-06
感觉是工具的问题 可以把这个条件判断去掉来解决
if (fileName.endsWith(".jar") || fileName.endsWith(".class") || fileName.endsWith(".xml"))



回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到





2022-01-08 上述问题的分析
asdasdadazz




上述问题的分析
	public void watch(Watcher watcher) throws WatchException{
		if(isClosed){
			throw new WatchException("Watch Monitor is closed !");
		}
		registerPath();
//		log.debug("Start watching path: [{}]", this.path);
		
		while (false == isClosed) {
			WatchKey wk;
			try {
				wk = watchService.take();
			} catch (InterruptedException e) {
//				log.warn(e);
				return;
			}
			
			final Path currentPath = watchKeyPathMap.get(wk);
			WatchEvent.Kind<?> kind;
			for (WatchEvent<?> event : wk.pollEvents()) {
				kind = event.kind();
				if(null != this.filePath && false == this.filePath.endsWith(event.context().toString())){
//					log.debug("[{}] is not fit for [{}], pass it.", event.context(), this.filePath.getFileName());
					continue;
				}
				
				if(kind == StandardWatchEventKinds.ENTRY_CREATE){
					watcher.onCreate(event, currentPath);
				}else if(kind == StandardWatchEventKinds.ENTRY_MODIFY){
					watcher.onModify(event, currentPath);
				}else if(kind == StandardWatchEventKinds.ENTRY_DELETE){
					watcher.onDelete(event, currentPath);
				}else if(kind == StandardWatchEventKinds.OVERFLOW){
					watcher.onOverflow(event, currentPath);
				}
			}
			wk.reset();

			// 如果第一个获取的事件没有触发reload,那么不会生成新的watchmonitor registerPath不会调用
			// 因为idea rebuild 会重新生成目录导致之前注册都路径失效,因为registerPath只会调用在最初调用一次
			// 所以导致无法监听事件
			// 在此处加入registerPath方法可以解决
			if (!isClosed) {
				registerPath();
			}

		}
	}

							


4 个答案

黄文新
答案时间:2022-11-01
这个更新问题怎么解决啊

我爱露琪亚
答案时间:2022-07-21
已经在我的脑子里了

huanghf
答案时间:2022-03-01
请问上述代码加在哪儿呢?

how2j
答案时间:2022-02-02
nice and good



回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到





2020-07-13 Intellij修改HelloServlet,热更新不生效
2020-06-19 ContextFileChangeWatcher检测文件变化第二次及以上不好使




提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
关于 实践项目-DiyTomcat-热加载 的提问

尽量提供截图代码异常信息,有助于分析和解决问题。 也可进本站QQ群交流: 578362961
提问尽量提供完整的代码,环境描述,越是有利于问题的重现,您的问题越能更快得到解答。
对教程中代码有疑问,请提供是哪个步骤,哪一行有疑问,这样便于快速定位问题,提高问题得到解答的速度
在已经存在的几千个提问里,有相当大的比例,是因为使用了和站长不同版本的开发环境导致的,比如 jdk, eclpise, idea, mysql,tomcat 等等软件的版本不一致。
请使用和站长一样的版本,可以节约自己大量的学习时间。 站长把教学中用的软件版本整理了,都统一放在了这里, 方便大家下载: https://how2j.cn/k/helloworld/helloworld-version/1718.html

上传截图