背景与愿景:开发环境下,tomcat对热布署的支持还不够全面,致使开发人员浪费大量时间在重起服务上。为了提高开发效率,决定引入Jrebel,它对热布署的支持相对比较全面。虽然Jrebel官方号称使用它不存在内存泄漏问题,但是占用一定的资源是肯定的,因此不考虑在正式环境下使用热布署。Jrebel实际上支持非常多中间件,除了Tomcat还包括Jetty、Resin、Weblogic等等,从理论上来讲,他跟中间件也没什么关系,但实际配置的时候还是会根据中间件有所不同,具体可以上官网查看,本文要讲的是tomcat+ eclipse+ spring+ struts2+ maven的环境。在使用Jrebel后,我们期望看到开发人员早上开机启动一次tomcat后就够了。
使用场景:Tomcat对热布署的使用场景是Servlet+JSP+JaveBean。如果项目含有其他框架时,其热布署效果就会大大降低,在与同事一同测试观察后发现:tomcat6在spring+struts框架下的项目,对java文件修改后的成功热布署概率偏低。由于概率太低,而且有无热布署成功不能确定,大部分开发人员修改类后不管什么情况直接选择重起,长此以往,浪费的时间积累起来不在少数。下面把tomcat和jrebel对热布署测试结果对比一下:
对比项 | Jrebel | Tomcat |
Class文件 | 绝大部分能热布署 | 小部分能热布署 |
Spring支持 | 改成用注释的方式后,可支持 | 不支持 |
Struts配置文件 | 支持 | 支持 |
页面相关文件 | 支持 | 支持 |
从对比可以看到,Jrebel最大的提升是对java类修改时,热布署大大提高;而对spring的支持实际上还是有限的,需要把IoC的实现改成使用注释的方式,而不能是配置的方式。如果你的工程的Spring已经是注释的方式,那就比较顺利,装好插件后,绝大部分情况下都能使用热布署了。如果你不是使用注释方式,那就麻烦了,要么全都改成注释方式,要么Jrebel对spring作用有限,看你自己的选择了。下面把已知Jrebel不能成功的热布署的情况作一列举:
1、替换了父类。
2、增加或删除了继承的接口。
3、Spring布署文件修改(如果改成注释方式,实际上spring只剩个别固定的第三方包的beans描述,比如数据库链接等)
4、web.xml,虽然jrebel和tomcat都支持web.xml修改的热布署,但是如果项目比较复杂,初始化工作较多的话,还是直接重起吧,直接热布署意义不大,而且重复初始化对于某些业务来说是会报错,所以建议有较复杂的初始化项目来说,还是直接重起算得了。
Jrebel安装和使用
1、jrebel是商用软件,而且价格不扉,去下载个破解版吧,最新的破解版是4.0,如果网上找不到,请留下邮件。下载jrebel.jar到本地,比如放在d:\jrebel.jar
2、Eclipse window->preference->tomcat->JVM Settings,加入以下参数
-Drebel.spring_plugin=true支持spring框架
-Drebel.aspectj_plugin=true支持aspectj
-Drebel.struts2_plugin=true支持strut2
-javaagent:D:\jrebel.jar这里自行修改jrebel.jar正确的路径
-noverify
如果你要支持更多的框架,可以参考官网http://www.zeroturnaround.com/jrebel/features/frameworks/
如果你要了解更多的参数配置,可以参考官网
http://www.zeroturnaround.com/jrebel/configuration/
jrebel支持监控多个目录下的classes、配置文件、jar包是否被修改,因此建议新建并配置rebel.xml文件,如果Eclipse安装了官网的jrebel plugin,那么可以从eclipse菜单里产生rebel.xml文件。以下是rebel.xml的简单手动配置:
<?xmlversion="1.0"encoding="UTF-8"?>
<application
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.zeroturnaround.com"
xsi:schemaLocation="http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd">
<classpath>
<dirname="E:\projects\cmac\target\classes"/>
<dirname="E:\projects\cmac\target\test-classes"/>
</classpath>
<web>
<linktarget="/">
<dirname="E:\projects\cmac\src\main\webapp"/>
</link>
</web>
</application>
rebel.xml更详细配置说明参考官网(http://www.zeroturnaround.com/jrebel/configuration/)
3、此时启动tomcat,会发现如下错误信息
严重:Exception starting filter Struts2
java.lang.NoClassDefFoundError:Lorg/apache/velocity/app/VelocityEngine;
atjava.lang.Class.getDeclaredFields0(Native Method)
atjava.lang.Class.privateGetDeclaredFields(Class.java:2291)
atjava.lang.Class.getDeclaredFields(Class.java:1743)
atcom.opensymphony.xwork2.inject.ContainerImpl.addInjectors(ContainerImpl.java:102)
atcom.opensymphony.xwork2.inject.ContainerImpl$1.create(ContainerImpl.java:84)
atcom.opensymphony.xwork2.inject.ContainerImpl$1.create(ContainerImpl.java:82)
atcom.opensymphony.xwork2.inject.util.ReferenceCache$CallableCreate.call(ReferenceCache.java:155)
atjava.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
atjava.util.concurrent.FutureTask.run(FutureTask.java:138)
atcom.opensymphony.xwork2.inject.util.ReferenceCache.internalCreate(ReferenceCache.java:81)
atcom.opensymphony.xwork2.inject.util.ReferenceCache.get(ReferenceCache.java:121)
atcom.opensymphony.xwork2.inject.ContainerImpl$ConstructorInjector.<init>(ContainerImpl.java:333)
atcom.opensymphony.xwork2.inject.ContainerImpl$5.create(ContainerImpl.java:299)
atcom.opensymphony.xwork2.inject.ContainerImpl$5.create(ContainerImpl.java:298)
atcom.opensymphony.xwork2.inject.util.ReferenceCache$CallableCreate.call(ReferenceCache.java:155)
atjava.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
atjava.util.concurrent.FutureTask.run(FutureTask.java:138)
atcom.opensymphony.xwork2.inject.util.ReferenceCache.internalCreate(ReferenceCache.java:81)
atcom.opensymphony.xwork2.inject.util.ReferenceCache.get(ReferenceCache.java:121)
atcom.opensymphony.xwork2.inject.ContainerImpl.getConstructor(ContainerImpl.java:578)
atcom.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:476)
at com.opensymphony.xwork2.inject.ContainerImpl$7.call(ContainerImpl.java:517)
atcom.opensymphony.xwork2.inject.ContainerImpl.callInContext(ContainerImpl.java:565)
atcom.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:515)
at com.opensymphony.xwork2.config.impl.LocatableFactory.create(LocatableFactory.java:32)
atcom.opensymphony.xwork2.inject.ContainerBuilder$4.create(ContainerBuilder.java:135)
atcom.opensymphony.xwork2.inject.Scope$2$1.create(Scope.java:49)
atcom.opensymphony.xwork2.inject.ContainerImpl$ParameterInjector.inject(ContainerImpl.java:447)
atcom.opensymphony.xwork2.inject.ContainerImpl.getParameters(ContainerImpl.java:462)
atcom.opensymphony.xwork2.inject.ContainerImpl.access$000(ContainerImpl.java:48)
at com.opensymphony.xwork2.inject.ContainerImpl$MethodInjector.inject(ContainerImpl.java:288)
atcom.opensymphony.xwork2.inject.ContainerImpl$2.call(ContainerImpl.java:117)
atcom.opensymphony.xwork2.inject.ContainerImpl$2.call(ContainerImpl.java:115)
at com.opensymphony.xwork2.inject.ContainerImpl.callInContext(ContainerImpl.java:558)
atcom.opensymphony.xwork2.inject.ContainerImpl.injectStatics(ContainerImpl.java:114)
atcom.opensymphony.xwork2.inject.ContainerBuilder.create(ContainerBuilder.java:495)
at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:170)
atcom.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:55)
atorg.apache.struts2.dispatcher.Dispatcher.init_PreloadConfiguration(Dispatcher.java:371)
atorg.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:424)
atorg.apache.struts2.dispatcher.FilterDispatcher.init(FilterDispatcher.java:213)
atorg.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:273)
atorg.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:254)
atorg.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:372)
atorg.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:98)
atorg.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4562)
atorg.apache.catalina.core.StandardContext$2.call(StandardContext.java:5240)
atorg.apache.catalina.core.StandardContext$2.call(StandardContext.java:5235)
atjava.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
atjava.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
atjava.lang.Thread.run(Thread.java:619)
Caused by: java.lang.ClassNotFoundException: org.apache.velocity.app.VelocityEngine
atorg.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1676)
atorg.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1521)
atjava.lang.ClassLoader.loadClassInternal(ClassLoader.java:316)
... 53 more
竟然报出需要velocity相关包,那好吧,我的项目是用maven来做管理的,在pom.xml里加上相关依赖如下:
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version>2.0</version>
</dependency>
再次重起tomcat后,一切正常,可以看到jrebel关键信息在console里输出:
JRebel: Directory'E:\projects\cmac\target\classes' will be monitored for changes.
JRebel: Directory'E:\projects\cmac\target\test-classes' will be monitored for changes.
JRebel: Directory 'E:\projects\cmac\src\main\webapp' will be monitored forchanges.
4、如果你使用maven发布并启动tomcat,那么需要安装jrebel-maven-plugin。本文前面提到只是满足开发阶段而且启动tomcat方式不是使用mvn命令方式,因此不需要安装jrebel-maven-plugin。
Spring利用注释方式实现IoC
现在大部分java项目有使用Spring框架,为了能使Jrebel更好的对Spring相关资源发生热布署作用,就得充分使用注释的方式实现依赖注入。这里对Spring实现注释方式作一下最简单的介绍,首先在applicationContext.xml里配置如下两行代码:
<context:annotation-config/>
<context:component-scanbase-package="*"/>
简单的说,以上的配置让spring支持了我们将要实现的注释依赖注入。以下以登录为实例,按action层、业务层、数据库操作层、PO层分别新建四个类:
LoginAction.java//struts action
UserServiceImpl.java//business layer
UserDaoImpl.java//dao layer
User.java //pojo
那么怎样通过注释方式进行调用的呢,首先给要被调用的类加上@Component注释,Spring为了区分不同层次的类,分别定义了以下四种注释
@Reposity
@Service
@Controller
@Component
目前阶段这四个注释实际上效果是一样的,我们约定如下:PO类如有需要使用@Reposity注释;Dao和Service使用@Service注释;Action使用@Controller注释;剩余分不出层次的类使用@Component注释。
如本例,action、service、dao分别加上注释
@Scope("prototype")
@Controller("loginAction")
public classLoginAction extends BaseAction{
}
@Service("userService")
public classUserServiceImpl implements UserService{
}
@Service("userDao")
public classUserDaoImpl extends BaseDao implements UserDao{
}
Scope注释默认是singleton,可以缺省。使用这四个标签时,如果不使用参数值,那么spring会按自己规范取名,比如LoginAction,使用@Controller()注释,默认取名为loginAction。取好了名,相当于在配置文件里配置了一组bean,接下来看怎么注入依赖,比如LoginAction要调用UserService,代码片段如下:
@Scope("prototype")
@Controller("loginAction")
public classLoginAction extends BaseAction{
…
@Autowired
PrivateUserService userService
…
}
就这么简单,添加xwork.xml配置,新加跳转页面,这些操作统统不用重起服务。
弹出Continue or Terminate疑问
装上jrebel后,可以进入你的tomcat/conf/context.xml或server.xml,其中有一个参数reload=true,把它改成false。表示关闭tomcat自身的热布署,在eclipse里启动tomcat,修改了类,有时还是会弹出Continue or Terminate框,难道是个Bug?不得而知。不过有jrebel在不用担心,继续continue,会发现你的修改是有效的。只有碰到前面提到的不适合jrebel热布署的场景时,即使没弹出Continue or Terminate提示框,你也要自己重起服务。
Jrebel官方对热布署支持的场景列表(查看官网说明http://www.zeroturnaround.com/jrebel/features/)
Java EE Support | Jrebel | JVM Hot Swap |
Time to reload | < 1s | < 1s |
No memory leak | YES | YES |
Changes to method bodies | YES | YES |
Adding/removing Methods | YES | NO |
Adding/removing constructors | YES | NO |
Adding/removing fields | YES | NO |
Adding/removing classes | YES | NO |
Adding/removing annotations | YES | NO |
Changing static field value | YES JRebel 3.0+ | NO |
Adding/removing enum values | YES JRebel 3.0+ | NO |
Changing interfaces | YES | NO |
Replacing superclass | NO | NO |
Adding/removing implemented interfaces | NO | NO |
Skip builds for WAR directories | YES | YES |
Skip builds for .WAR/.EAR class updates | YES | YES |
Skip builds for .WAR/.EAR resource updates | YES | NO |
Map multiple source dirs to one .WAR/.EAR target dir | YES | NO |
Map classes and resources with include/exclude patterns | YES | NO |
Map multiple source dirs with Ant-style patterns | YES | NO |
Use system properties to make mapping machine-independent | YES | NO |
Maven plugin | YES | NO |
JSP EL changes | YES | NO |
JSP Scriptlet changes | YES Enterprise Add-on | NO |
EJB 1.x session bean interface changes | YES Enterprise Add-on | NO |
EJB 2.x session bean interface changes | YES Enterprise Add-on | NO |
EJB 3.x session bean interface changes | YES JRebel 3.0+ | NO |
JSF changes (Mojarra) | YES JRebel 3.0+ | NO |
JPA changes (Hibernate, EclipseLink, TopLink, OpenJPA) | YES JRebel 3.0+ | NO |
CDI changes (Weld) | YES JRebel 3.0+ | NO |
ResourceBundle | YES | NO |
Spring Framework 2.x or later | YES | NO |
Hibernate | YES JRebel 3.0+ | NO |
JBoss Seam 2.x or later | YES JRebel 3.0+ | NO |
Google Guice | YES | NO |
Stripes 1.x or later | YES | NO |
Apache log4j 1.2.x or later | YES | NO |
Apache Struts 1.x | YES | NO |
Apache Struts 2.x or later | YES | NO |
Apache Tapestry4 | YES | NO |
Apache Velocity | YES | NO |
Apache Wicket | YES | NO |
CgLib | YES JRebel 3.0+ | NO |
Javassist | YES JRebel 3.0+ | NO |
Atlassian Confluence plugins | YES | NO |
ClassWorlds | YES Beta | NO |
Apache Felix | YES Beta | NO |
Eclipse Equinox | YES Beta | NO |
IntelliJ IDEA 7.x, 8.x plugins | YES Beta | NO |
NetBeans plugins | YES Beta | NO |
Jrebel对第三方框架支持地应表 (查看官网说明http://www.zeroturnaround.com/jrebel/features/frameworks/)
AspectJ | aspectj_plugin |
| 1.5.3 | 1.5+ |
Click | click_plugin |
| 2.2.0 | 2.2+ |
EclipseLink | eclipselink_plugin |
| 1.0.1 | 1.x |
Facelets | facelets_plugin |
| 1.1.15.B1 | 1.x |
FreeMarker | (integrated) |
| 2.3.16 | 2.3.7+ |
Groovy | groovy_plugin |
| 1.5+ | |
Guice | guice_plugin |
| 1.0 | 1.0+ |
GWT | gwt_plugin |
| 2.0.3 | 2.0.x |
Hibernate | hibernate_plugin |
| 3.3.2.GA | 3.x. |
Hibernate Validator | hibernate_validator_plugin |
| 4.0.2.GA | 4.x.x |
iBatis | ibatis_plugin |
| 2.1.6 | 2.1.x |
Jackson | jackson_plugin |
| 1.0.0 | 1.0.0+ |
JBoss AOP | jbossaop_plugin |
| 2.0 | |
Lift | lift_plugin |
| Lift 2.1, 2.2 / Scala 2.8.0 | Lift 2.1+ / Scala 2.8+ |
Log4J | log4j-plugin |
| 1.2.12 | at least all 1.2.x versions |
LOGBack | logback_plugin |
| 0.9.12 | 0.9.12 – .. |
Mojarra | mojarra_plugin |
| 1.2_13-b01-FCS | 1.2+ |
OpenJPA | openjpa_plugin |
| 1.1 | 1.x |
RestEasy | resteasy_plugin |
| 2.0.1.GA | 2.0.x |
Seam | seam_plugin |
| 2.0.2.SP1 | 2.x |
Seam-Wicket integration | seam_wicket_plugin |
| ||
Spring (core) | spring_plugin |
| 2.0 | 2.0 |
Spring MVC | spring_plugin |
| 2.0 | 2.0 |
Spring Web Flow | spring_webflow_plugin |
| 2.1.1 | 2.x |
Stripes | stripes_plugin |
| 1.5.3 | 1.5.x |
Struts1 | struts1-plugin |
| 1.0.2 | 1.x.x 1.1.x 1.2.x 1.3.x) |
Struts2 | struts2-plugin |
| 2.0.14 | 2.0.x |
Tapestry4 | tapestry4_plugin |
| 4.1.6 | 4.x |
Tiles 1 (embedded in Struts1) | tiles1_struts_plugin |
| Struts 1.1 | Struts 1.1 |
Tiles2 | tiles2_plugin |
| 2.0.7 | 2.0.x |
TopLink | toplink_plugin |
| 10.1.3 | 10.x |
TopLink-Spring integration | toplink_spring_plugin |
| TopLink 10.1.3 / Spring 2.5.6 | TopLink 10.x / Spring 1.2.x, 2.x, 3.x |
Velocity | velocity_plugin |
| 1.6.3 | 1.6.x |
Weld | weld_plugin |
| 1.0.1 | 1.x+ |
Wicket | wicket_plugin |
| 1.2.7 | 1.2.x |
JAX-WS Metro | metro_plugin |
| 2.1.7 | 2.1.x |
Spring-WS | springws_plugin |
| 2.0.2 | 2.x |
JAXB | jaxb_plugin |
| 2.0.5 | 2.x |