前几天有人在群里提了个问题:


怎么样通过程序向Tomcat内部署应用?


工作比较忙,也没细问具体的使用场景,提供了一种使用JMX接口的思路。后来提问者说不太了解JMX,网上搜了一些看了看,比较蒙。


本次不打算详细描述JMX怎么使用,而是梳理下可供Tomcat远程部署应用的几种方式,方便有类似需求的朋友。



说到应用部署,熟悉Tomcat的都知道,他默认包含了一个manager应用,功能不少,其中就包含应用部署,不论是目录部署,还是文件部署。


一开始写公众号的时候介绍过一点manager应用:深入Tomcat的Manager


这里注意对于manager应用的使用,默认做了访问限制,只能在本机访问,所以如果你想远程使用manager部署应用到目标服务器,需要在content.xml中做修改,可以参考前面的一篇旧文:为什么你的Manager登录不成功?


配置之后就和本地使用manager一样,部署功能直接使用即可,不再赘述。这里我们来说下使用接口的形式远程部署。


在manager应用内,我们页面上看到的,一般称为HTML接口,还有一个text接口,可以根据在URL中指定的command和参数,执行相应的动作。

格式类似这样:


http://{host}:{port}/manager/text/{command}?{parameters}



host和port换成你的目标主机和端口,command代表你执行的操作,parameter是命令对应需要的参数。

支持的命令很多,如下图是managerServlet里部分代码截图




当然这里面没截取我们要说的deploy命令,这个命令我们单独说一下。对应我们前面说的要部署应用,在url类似这样:


http://localhost:8080/manager/text/deploy?path=/hello&war=d:/abc.war


这里指定应用在磁盘上存放路径,以及应用名称,即可进行应用部署。


部署结果类似这样:


OK - Deployed application at context path /hello


这里也支持应用的多版本部署,只需要在参数中增加version即可。


我们通过源码来看下,这种部署形式背后是如何实现的。


如何远程部署应用到Tomcat_java


我们来看红框标注的三个地方,是整个部署逻辑的重点。


  1. 首先将应用添加到service内,代表已注册的服务。后面再部署的时候都会先检查,不在此列表内的才被允许。


  2. 将远程的应用包拷贝到本地目录内。


  3. 触发部署的逻辑,真正进行部署。



我们主要注意一下,第一步和第三步,其实都是通过JMX接口来进行的。例如check方法的内容是这样的:

如何远程部署应用到Tomcat_java_02


这里的mBeanServer就是JMX里所有MBean对象注册的服务点,连接到MBeanServer上之后,后面的逻辑和反射有些类似,指定ObjectName,再指定方法名和参数即可。

如何远程部署应用到Tomcat_java_03


我们这里的ObjectName是"Catalina:host=locahost,type=Deployer"。


调用check之后,最终会调用到HostConfig类的check方法,从而触发部署流程,进行应用的部署。完整的部署过程请参考前面的文章:


如何在Tomcat中部署应用的多个版本

WEB应用是怎么被部署的?

Tomcat集群应用部署功能实现分析


所以,如果你想自己造个轮子来实现远程部署的时候,也可以参考这种使用JMX的方式。


另一种方式


之前介绍过IDE内Tomcat工作方式时描述过IDEA里在向Tomcat部署应用时是怎么样通过JMX进行的(你一定不知道IDE里的Tomcat是怎么工作的!)。


在IDEA里,向tomcat部署一个应用,启动时,其实并不会在本地的tomcat中找到该应用的目录,或者实际运行的目录下有该应用。仔细观察发现,IDEA是通过Tomcat的MBean,动态的向tomcat增加了一个Context,即一个应用。这样直接指定了应用的路径,访问路径等

例如下面的调用链:

TCP Connection(2)-127.0.0.1@1379 daemon, prio=5, in group 'RMI Runtime', status: 'RUNNING'
at org.springframework.web.context.ContextLoaderListener.<init>(ContextLoaderListener.java:98)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)
at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1585)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:463)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:413)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1466)
at java.lang.Thread.run(Thread.java:745)


也就是根据实际路径path,docBase这些构造一个StandardContext,并添加到Host中,对外提供服务。


以上,是几种通过JMX可以动态远程部署的方式,供参考。当然如果想用更直接一些的方式,在代码里控制应用的copy,然后控制Tomcat进程自动重启,也可以啦。


马上到新年了,提前祝各位朋友新年快乐,2017心想事成,万事如意!

2017愿我们都能更上一层楼。