??xml version="1.0" encoding="utf-8" standalone="yes"?> q样做的最l效果就是修改了目的运行方式。原先的q行方式是以tomcatZ心,由tomcat来启动和l止目Q现在是由我们的启动E序 Z心,由启动程序来负责启动和终止项目。就相当于现在流行的csE序一P有单独的启动脚本Q在启动时进行环境预初始化,更新E序以及其它操作Q待完成 之后再进行最l的目启动?/p> q篇主要讲解如何使用embeded tomcat在代码中q行启动和终止。网上的一般文章均为tomca5.x来做Q这里用了最新的tomcat7Q因为tomcat7为embeded开 发,单独发布了org.apache.tomcat.embed包,以进行独立的embed开发。以下是相应的maven?/p> 使用了embed包中的core包,以及用于~译jsp的jasper包,然后是工L以及q行上场记录的logging-juli包。开始写代码Q?/p> 上面使用了TomcatcLq行启动c,在tomcat7以前均是使用一个叫EmbedcLq行启动Q在tomcat7之后Qembedc被不徏 议用,而徏议用新的TomcatcLq行启动了。然后设|主机名Q端口,再设|一个工作目录。这个工作目录可以是L目录Q主要是tomcat需要这 个目录来记录一些东西,比如记录word信息Q日志信息(如果配置了日志的话)Q以及时文件存储等?/p> 上面的代码,首先讄我们的项目程序所在的appbaseQ即N目代码的地方。在通常的tomcat配置中,q个目录一般是webapps。接 着讄一个listenerQ这个listener主要是负责启动一些比如html native支持E序以及ipv6{信息配|(可以忽略Q。接着是配|一个关闭的注册端口Q当向这个端口发送信息时Q就可以辑ֈ关闭tomcat的目?Q后面会Ԍ?/p> 我们单独使用了一个Context来ؓq个hostd上下文,tomcat本n提供一个方法tomcat.addWebҎ来添加项目包Q不q?׃q里需要单独设|一个tomcat的sessionNameQ所以用与与tomcat.addWeb实现cM的方法来d一个项目包?br /> 以上代码中有两个需要注意的listenerQ一个是DefaultWebXmlListenerQ这个是由tomcat加蝲一些默认的配置?息,比如jspServletQ以及一些繁复的mime/type信息Q加上这个,׃需要我们自己去写这么多的配|,因ؓ每个目都需要这些。这个配|?与tomcat目录下的conf/web.xml中的配置一P只不q这里是代码化了。第二个是FixContextListenerQ这个主要是在项?部v完后Q将q个上下文设|ؓconfiguredQ表C已l配|好了(不然Qtomcat启动时会报错Q即相应上下文还未配|好Q?br /> 配置OK了之后,是启动tomcat了: 启动tomcatQƈ让tomcat在关闭端口上监听。如果没有最后一句,E序直接结束,保证监听之后Qtomcat一直监听关闭事Ӟ待有关闭事g之后才结束当前程序。所以如果想要关闭当前的tomcatQ只需要向关闭端口发送一些信息即可: q样卛_辑ֈ关闭tomcat的目的?/p> 实际上看整个目代码Q项目代码的q行Q就是一个配|一个基的server.xmlQ即tomcat目录下的 conf/server.xml)Q先配置q行端口Q关闭监听端口;然后配置q行的host以及d一个上下文contextQ最后就开始运行ƈ开始监 听。对照这个程序,再看一下server.xml中的配置信息Q就很容易明白以上这D代码了?/p> Z么写q篇文档Q?/p> ?用过hibernate, spring或其他大型组Ӟ写过50个类以上的网l应用程?web application)的开发者应该知道,当系l中有很多类Ӟ如果开启了Tomcat的reloadable=true,那么每当相关文g改变 ӞTomcat会停止web appq攑ֆ?然后重新加蝲web app.q实在是个浩大的工程?br /> 所以我L在想如果能有只重载某几个cȝ功能Q将极大的满xq个x调试狂?/p> d我在论坛上发帖,才发现已l有一些应用服务器h了这个功能,比如WebLogic, WebSphere, {等。好像还有一个很L名字Q叫开发模式。看来我q是孤陋寡闻了点?/p> 当然很多人都是在Tomcat上开发,包括我。我很喜Ƣ它的轻,那些大内存和高CPU消耗的应用服务器不愧ؓg杀手,没理׃改进Tomcat :)?/p> 最l实现功?/p> 我没有时间去研究Tomcat的文件监听机Ӟ也没旉L他写?#8221;开发模?#8221;q么完整的功能,我最l实现的是,实现重蝲功能的测试jspQ-很抱歉我q是没办法写得更完整。当Ӟ你可以在q个基础上进行改q?/p> 阅读ȝ 阅读本文Q你应该具备以下知识 jvm 规范有关cd载器的章?/p> http://java.sun.com/docs/books/vmspec/2nd-edition/html/VMSpecTOC.doc.html Tomcat cd载机?/p> java 反射机制 ant (好象该网址被不定时锁Q有时能上,有时不能) 最好在你的电脑上安装ant,因ؓTomcat源码包用ant从互联网获得依赖包。不q我也是修改了一个错误才使它完全~译通过?/p> 当然Q你也可以用其他IDE工具查ƈd依赖包,在IDE中,其实你只需要添加jar直到使org.apache.catalina.loader.WebappClassLoader无错卛_?/p> 修改q程 说明 新添加的代码h加到java文g的末,因ؓ我在说明行数的时候,量W合原始行数 web appcd载器 在Tomcat中,org.apache.catalina.loader.WebappClassLoader是web app的类加蝲器,所以需要修改它实现重蝲功能?/p> 资源列表 在WebappClassLoader中,有一个Mapcd属性resourceEntriesQ它记蝲了web app中WEB-INF/classes目录下所加蝲的类Q因此当我们需要重载一个类Ӟ我们需要先它在resourceEntries里删除,我编写了一个方法方便调用: public boolean removeResourceEntry(String name) { if (resourceEntries.containsKey(name)) { resourceEntries.remove(name); return true; } return false; } 是否重蝲标志 让WebappClassLoader需要知道加载一个类是否使用重蝲的方式。所以我建立一个boolean cd的属性和实现它的getter/setterҎQ?/p> private boolean isReload = false; public boolean isReload() { return isReload; } public void setReload(boolean isReload) { this.isReload = isReload; } 动态类加蝲?/p> Ҏjvmcd载器规范Q一个类加蝲器对象只能加载一个类1ơ,所以重载实际上是创建出另一个类加蝲器对象来加蝲同一个类。当Ӟ我们不需要再创徏一个WebappClassLoaderQ他太大而且加蝲规则很复杂,不是我们惌的,所以我们创Z个简单的cd载器corg.apache.catalina.loader.DynamicClassLoaderQ?/p> package org.apache.catalina.loader; import java.net.URL; import java.net.URLClassLoader; import java.security.CodeSource; import java.util.*; /** * 动态类加蝲?/p> * * @author peter * */ public class DynamicClassLoader extends URLClassLoader { /* 父类加蝲?nbsp;*/ private ClassLoader parent = null; /* 已加载类名列?nbsp;*/ private List classNames = null; /** * 构造器 * * @param parent * 父类加蝲器,q里传入的是WebappClassLoader */ public DynamicClassLoader(ClassLoader parent) { super(new URL[0]); classNames = new ArrayList(); this.parent = parent; } /** * 从类的二q制数据中加载类. * * @param name * cd * @param classData * cȝ二进制数?/p> * @param codeSource * 数据来源 * @return 成功加蝲的类 * @throws ClassNotFoundException * 加蝲p|抛出未找到此cd?/p> */ public Class loadClass(String name, byte[] classData, CodeSource codeSource) throws ClassNotFoundException { if (classNames.contains(name)) { // System.out.println("此类已存在,调用 loadClass Ҏ加蝲."); return loadClass(name); } else { // System.out.println("新类, 记录到类名列表,q用cd义方法加载类"); classNames.add(name); return defineClass(name, classData, 0, classData.length, codeSource); } } /* * * 重蝲此方法,当要加蝲的类不在cd列表中时Q调用父cd载器Ҏ加蝲. * @see java.lang.ClassLoader#loadClass(java.lang.String) */ public Class loadClass(String name) throws ClassNotFoundException { if (!classNames.contains(name)) { //System.out.println("不在cd列表中,调用父类加蝲器方法加?); return parent.loadClass(name); } return super.loadClass(name); } } 在webappClassLoader中添加DynamicClassLoader d属?/p> private DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this); d重徏ҎQ以侉K要再ơ重载时替换掉上ơ的cd载器对象 public void reCreateDynamicClassLoader() { dynamicClassLoader = new DynamicClassLoader(this); } 修改调用?/p> W?32行,公开findClassҎ public Class findClass(String name) throws ClassNotFoundException { W?569行,d如下一行代码?/p> if (isReload) removeResourceEntry(name); W?577行,q里好像是一个bugQ具体原因我忘了-_-|| if ((entry == null) || (entry.binaryContent == null)) 改ؓ if ((entry == null) || (entry.loadedClass == null && entry.binaryContent == null)) W?633~1636?/p> if (entry.loadedClass == null) { clazz = defineClass(name, entry.binaryContent, 0, entry.binaryContent.length, codeSource); 改ؓ byte[] classData = new byte[entry.binaryContent.length]; System.arraycopy(entry.binaryContent, 0, classData, 0, classData.length); if (entry.loadedClass == null) { clazz = isReload ? dynamicClassLoader.loadClass(name, classData, codeSource) : defineClass(name, classData, 0, classData.length, codeSource); 试代码 test.jsp 我测试用的jsp?CATALINA_HOME/webapps/ROOT/test.jspQ由于webapp里面q不会显式加载tomcat的核心类Q所以我们需要用反射代码调用WebappClassLoader的方法。代码如下: <% ClassLoader loader = (Thread.currentThread().getContextClassLoader()); Class clazz = loader.getClass(); java.lang.reflect.Method setReload = clazz.getMethod("setReload", new Class[]{boolean.class}); java.lang.reflect.Method reCreate = clazz.getMethod("reCreateDynamicClassLoader", null); java.lang.reflect.Method findClass = clazz.getMethod("findClass", new Class[]{String.class}); reCreate.invoke(loader, null); setReload.invoke(loader, new Object[]{true}); Class A = (Class)findClass.invoke(loader, new Object[]{"org.AClass"}); setReload.invoke(loader, new Object[]{false}); A.newInstance(); // 如果你用下面这行代码,当重~译cLQ请E微修改一下调用它的jspQ让jsp也重新编?/p> //org.AClass a = (org.AClass)A.newInstance(); // 下面q些代码是测试当一个类不在DynamicClassLoadercd列表时的反应 //a.test(); //java.lang.reflect.Method test = a.getClass().getMethod("test", null); //test.invoke(a, null); %> org.AClass package org; public class AClass { public AClass() { // 修改输出内容认Tomcat重新加蝲了类 System.out.println("AClass v3"); } public void createBClass() { new BClass(); } } org.BClass package org; public class BClass { public BClass() { //修改输出内容认Tomcat重新加蝲了类 System.out.println("BClass v1"); } } 试步骤 按照上述步骤修改Tomcat源码q编译?/p> 用winzip/winrar/file-roller打开$CATALINA_HOME/server/lib/catalina.jar。把前面~译完成后的org.apache.catalina.loader目录下的class文g覆盖jar中同名文件?/p> ~译org.AClass和org.BClass 启动Tomcatq在览器中打开试http://localhost:8080/test.jsp 修改org.AClass中的System.out.println();语句q~译cR?/p> 按下F5按键h览器?/p> 查看Tomcat控制台是否输Z不同的语句? Good Luck! :))) ׃E序中用了jtds驱动来连接数据库?br />
一D|间后Q我发现tomcat的temp文g夹内jtds*.tmp文g(形如jtds424647.tmp)来多Q容量也来大。有一ơ清理时Q据然有几个G多,严重的媄响了pȝ的运行速度?/p>
解决措施: 如果是linuxpȝ 如果是windowsQ需要定期清理tomcat的tmp文g 可以用windows自带的定时Q务器建立如下delTmp.bat的文?/p>
@echo off :start ::启动q程Q切换目?/p>
set pwd=%cd% cd %1 echo 工作目录是:& chdir :clean ::d理过E,执行清理工作 @echo on rem @for /r %%c in (.log) do @if exist %%c ( rd /s /q %%c & echo 删除目录%%c) @echo off @del logs\*.log @del temp\*.tmp del /f /s /q E:\osaplatform\WEB-INF\logs\*.log.*-* echo "当前目录下的log信息已清? goto end :noclean ::分支q程Q取消清理工?/p>
echo "log信息清楚操作已取? goto end :end ::退出程?/p>
cd "%pwd%" REM pause Tomcat׃q行的时间过镉K成内存不宜释放Q导致运行性能的降低,定时重启tomcat有利于提升系l的性能Q?/p>
首先建立如下的bat文g set JAVA_HOME=C:\Program Files\Java\jdk1.6.0_06 E:\tomcat6_hb\bin\service.bat install Tomcat604 注册为windows服务 然后在徏立tomcat.batQƈ此文g攑ֈwindows定期d?/p>
@echo off net stop tomcat5 rem ping 20个包Q实现g时功?/p>
ping 127.0.0.1 -n 20 net start tomcat5 如果需要解除服务,则徏立如下文Ӟ直接执行卛_ set JAVA_HOME=C:\Program Files\Java\jdk1.6.0_06 E:\tomcat6_hb\bin\service.bat remove Tomcat604<
dependency
>
<
groupId
>org.apache.tomcat.embed</
groupId
>
<
artifactId
>tomcat-embed-core</
artifactId
>
<
version
>7.0.2</
version
>
</
dependency
>
<
dependency
>
<
groupId
>org.apache.tomcat</
groupId
>
<
artifactId
>tomcat-util</
artifactId
>
<
version
>7.0.2</
version
>
</
dependency
>
<
dependency
>
<
groupId
>org.apache.tomcat.embed</
groupId
>
<
artifactId
>tomcat-embed-jasper</
artifactId
>
<
version
>7.0.2</
version
>
</
dependency
>
<
dependency
>
<
groupId
>org.apache.tomcat.embed</
groupId
>
<
artifactId
>tomcat-embed-logging-juli</
artifactId
>
<
version
>7.0.2</
version
>
</
dependency
>
//讄工作目录
String catalina_home =
"d:/"
;
Tomcat tomcat =
new
Tomcat();
tomcat.setHostname(
"localhost"
);
tomcat.setPort(startPort);
//讄工作目录,其实没什么用,tomcat需要用这个目录进行写一些东?/code>
tomcat.setBaseDir(catalina_home);
//讄E序的目录信?/code>
tomcat.getHost().setAppBase(
"e:/"
);
// Add AprLifecycleListener
StandardServer server = (StandardServer) tomcat.getServer();
AprLifecycleListener listener =
new
AprLifecycleListener();
server.addLifecycleListener(listener);
//注册关闭端口以进行关?/code>
tomcat.getServer().setPort(shutdownPort);
//加蝲上下?/code>
StandardContext standardContext =
new
StandardContext();
standardContext.setPath(
"/aa"
);
//contextPath
standardContext.setDocBase(
"aa"
);
//文g目录位置
standardContext.addLifecycleListener(
new
Tomcat.DefaultWebXmlListener());
//保证已经配置好了?/code>
standardContext.addLifecycleListener(
new
Tomcat.FixContextListener());
standardContext.setSessionCookieName(
"t-session"
);
tomcat.getHost().addChild(standardContext);
tomcat.start();
tomcat.getServer().await();
private
static
void
shutdown()
throws
Exception {
Socket socket =
new
Socket(
"localhost"
, shutdownPort);
OutputStream stream = socket.getOutputStream();
for
(
int
i =
0
;i < shutdown.length();i++)
stream.write(shutdown.charAt(i));
stream.flush();
stream.close();
socket.close();
}
]]>
目前发现能解决的办法有两U?br />Ҏ一?br />server.xml文gConnectorl点d URIEncoding="iso-8859-1" 属?br />
Ҏ二?br />
#http://tomcat.apache.org/tomcat-8.0-doc/config/http.html URIEncoding
属性部?br />#http://tomcat.apache.org/tomcat-8.0-doc/config/systemprops.html org.apache.catalina. STRICT_SERVLET_COMPLIANCE
属性部?br />org.apache.catalina.STRICT_SERVLET_COMPLIANCE=true
#Cookie的path限制修改Qfalse允许使用/ 参考Tomcat文
#http://tomcat.apache.org/tomcat-8.0-doc/config/systemprops.html
]]>
另外|上有一文章是关于在Tomcatq行动态重载类Q下面是该文章的内容
]]>
@echo off
net stop tomcat5
rem ping 20个包Q实现g时功?br />
ping 127.0.0.1 -n 20
net start tomcat5
注意q里的tomcat5是tomcat在windows的服务名Q注册服务的Ҏ如下Q?br />
扑ֈtomcat安装目录bin子目录下的service.bat
在命令行里执行:
service.bat install
则自动安装服务?br />
定时重启SQL Server
net stop mssqlserver
net start mssqlserver
tomcat产生垃圾~存文g的处理与tomcat的定旉?/p>
可以指定启动?Djava.io.tmpdir=/tmp
q个目录pȝ会用cron脚本自动清理文g
]]>
从Tomcat的log上看到该d所在的Context有三ơ启动的q程Q计划Q务也被创Z三次
问题原来出在Host的appBase讄上,原来的设|是q样?/p>
Ҏ不设|appBaseQ只Ҏ定的Context讄docBaseok?/p>
<Host name="www.xxx.com" appBase=""
unpackWARs="false" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<Context docBase="/usr/local/projects/xxx/web" path="" reloadable="true" workDir="work">
<!-- Others -->
</Context>
</Host>
<Host name="admin.xxx.com" appBase=""
unpackWARs="false" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<Context docBase="/usr/local/projects/xxx/admin" path="" reloadable="true" workDir="work">
<!-- Others -->
</Context>
Z么不讄appBaseOK来呢Q?/p>
我们采取的是集中理的办法。主要技术:
1.讄Context 的crossContext="true"Q得各个web应用的servletcontext是可以互访的
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<Context path="/SessionManager" reloadable="true" crossContext="true"></Context>
<Context path="/SessionManagerTest1" reloadable="true" crossContext="true"></Context>
<Context path="/SessionManagerTest2" reloadable="true" crossContext="true"></Context>
2.d讄cookiesQ设|jsessionid׃n的session的idQ统一利用requestsessionid在指定的一? servletcontext里的一个map查找对于的sessionQ需要存取attribute都对取得的session操作
3.用监听器监听属性的失效
所有Context的实现支持如下属性:
属?/font> | 描述 |
---|---|
|
g表在context及其子容器(包括所有的wrappersQ上调用backgroundProcessҎ的gӞ以秒为单位。如果g时值非负,子容器不会被调用Q也是说子容器使用自己的处理线E。如果该gؓ正,会创Z个新的线E。在{待指定的时间以后,该线E在L及其 子容器上调用backgroundProcessҎ。context利用后台处理sessionq期Q监类的变化用于重新蝲入。如果没有指定,该属性的~省值是Q?Q说明context依赖其所属的Host的后台处理?/td>
|
|
实现的Javacd。该cdd?code>接口。如果没有指定,使用标准实现Q在下面定义Q?/td> |
|
如果惛_用cookies来传递session identifierQ需要客L支持cookiesQ,设ؓture。否则ؓfalseQ这U情况下只能依靠URL Rewriting传递session identifier?/p>
|
|
如果惛_应用内调用ServletContext.getContext()来返回在该虚拟主Zq行的其他web application的request dispatcher,设ؓtrue。在安全性很重要的环境中Q设为falseQ得getContext()Lq回null。缺省gؓfalse?/p> |
|
该web应用的文档基准目录(Document BaseQ也UCؓContext RootQ,或者是WAR文g的\径。可以用绝对\径,也可以用相对于context所属的Host的appBase路径?/p>
|
|
如果惛_用该Context元素中的讄覆盖DefaultContext中相应的讄Q设为true。缺省情况下使用DefaultContext中的讄?/p> |
|
设ؓtrueQ允许context使用container servletsQ比如manager servlet?/p>
|
|
web应用的context路径。catalina每个URL的v始和context pathq行比较Q选择合适的web应用处理该请求。特定Host下的context path必须是惟一的。如果context path为空字符Ԍ""Q,q个context是所属Host的缺省web应用,用来处理不能匚wMcontext path的请求?/p>
|
|
如果希望Catalina监视/WEB-INF/classes/?WEB-INF/lib下面的类是否发生变化Q在发生变化的时候自动重载web applicationQ设为true。这个特征在开发阶D很有用Q但也大大增加了服务器的开销。因此,在发布以后,不推荐用。但是,你可以用Manager应用在必要的时候触发应用的重蝲?/td> |
|
org.apache.catalina.Wrapper实现cȝ名称Q用于该Context理的servlets。如果没有指定,使用标准的缺省倹{?/p> |