Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 and so on Translation

时间:2021-03-19 19:41:13

The Original link : http://zeroturnaround.com/rebellabs/rjc301/

Copyright reserved by Rebel Inc

 

In this article we’ll review how dynamic classloaders are used in real servers, containers and frameworks to reload Java classes and applications.  We’ll also touch on how to get faster reloads and redeploys by using them in optimal ways.

本章节我们会对服务器, 容器, 框架如何动态使用动态classloader reload java类和应用程序做一个回顾.我们也会提及如何使用优化的方法更快的reload和redeploy. 

Other Articles in the Reloading Java Classes Series


Java EE (web) applications

In order for a Java EE web application to run, it has to be packaged into an archive with a .WAR extension and deployed to a servlet container like Tomcat. This makes sense in production, as it gives you a simple way to assemble and deploy the application, but when developing that application you usually just want to edit the application’s files and see the changes in the browser.

要想让一个Java EE web 应用跑起来, 他必须打包到一个.WAR包中并部署到一个servlet容器中(如Tomcat).这在生产环境中是很有意义的, 因为他基于你一种很简单的装配和部署应用的方式, 但是当开发这样的应用的时候, 你通常都会想在编辑完程序文件后马上在浏览器看到编辑完的改变.

A Java EE enterprise application has to be packaged into an archive with an .EAR extension and deployed to an application container. It can contain multiple web applications and EJB modules, so it often takes a while to assemble and deploy it. Recently, 1100+ EE developers told us how much time it takes them, and we compiled the results into the Redeploy and Restart Report.
Spoiler: Avg redeploy & restart time is 2.5 minutes – which is higher than we expected.

一个Java EE企业级应用必须打包到.EAR中并部署到应用容器中. 他可以包含多个web应用和EJB模块, 所以通常他都会需要花一点时间才能装配和部署好. 最近, 1100+ 的EE 开发者告诉我们这有多慢,  我们把结果编辑到了 Redeploy and Restart Report.

In Reloading Java Classes 101, we examined how dynamic classloaders can be used to reload Java classes and applications. In this article we will take a look at how servers and frameworks use dynamic classloaders to speed up the development cycle. We’ll use Apache Tomcat as the primary example and comment when behavior differs in other containers (Tomcat is also directly relevant for JBoss and GlassFish as these containers embed Tomcat as the servlet container).

在 Reloading Java Classes 101, 我们解释了动态classloaders如何用来reload java 类和应用. 在这篇文章中我们探讨服务器和框架如何使用动态的classloader来加速开发.我们会使用Apache Tomcat作为主要的例子, 并对它与其他容器的不懂行为进行比较(Tomcat也和JBoss和GlassFish直接相关, 因为他们是键入Tomcat作为servlet容器的)

Redeployment

To make use of dynamic classloaders we must first create them. When deploying your application, the server will create one classloader for each application (and each application module in the case of an enterprise application). The classloaders form a hierarchy as illustrated:

要使用动态classloader必须先创建他们.当部署你的应用时候, 服务器会为每个应用创建一个classloader(一个企业级应用的每个应用模块也会有一个classloader).这些classloader的层级关系如下:

Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 and so on Translation

In Tomcat each .WAR application is managed by an instance of the StandardContext class that creates an instance of WebappClassLoader used to load the web application classes. When a user presses “reload” in the Tomcat Manager the following will happen:

在tomcat中每个.WAR 应用被一个StandardContext 类实例管理, 它创建了一个WebappClassLoader 实例来载入web应用的类. 当一个用户点击Tomcat Manager的"reload"实际上做了如下的事情:

  • StandardContext.reload() method is called
  • StandardContext.reload()方法被调用
  • The previous WebappClassLoader instance is replaced with a new one
  • 旧的WebappClassLoader会被新的代替
  • All reference to servlets are dropped
  • 所有对servlet的引用被丢弃
  • New servlets are created
  • 创建新的servlet
  • Servlet.init() is called on them
  • 新的servlet的Servlet.init()方法被调用

Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 and so on Translation

Calling Servlet.init() recreates the “initialized” application state with the updated classes loaded using the new classloader instance. The main problem with this approach is that to recreate the “initialized” state we run the initialization from scratch, which usually includes loading and processing metadata/configuration, warming up caches, running all kinds of checks and so on. In a sufficiently large application this can take many minutes, but in a  in small application this often takes just a few seconds and is fast enough to seem instant, as commonly demonstrated in the Glassfish v3promotional demos.

调用Servlet.init()方法会使用新的classloader实例载入的新的classes来重新创建"初始化的"应用状态. 这种方法最主要的问题是重新创建"初始化的"状态需要从头做起, 这包括加载和处理元数据/配置, 预热缓存, 运行所有类型的检查等等. 在一个足够大的程序中, 这会花上很多时间, 但在小程序中这只会花上几秒钟, 足以让你马上看到改变, 就像GlassFish v3 promotional demos 示范的那样.

If your application is deployed as an .EAR archive, many servers allow you to also redeploy each application module separately, when it is updated. This saves you the time you would otherwise spend waiting for non-updated modules to reinitialize after the redeployment.

如果你的文件是作为.EAR部署的, 许多服务器允许你在应用升级后将应用的模块分别重部署.这会节省很多你不必要的等待没有升级的模块重部署后重新初始化的时间

Hot Deployment

Web containers commonly have a special directory (e.g. “webapps” in Tomcat, “deploy” in JBoss) that is periodically scanned for new web applications or changes to the existing ones. When the scanner detects that a deployed .WAR is updated, the scanner causes a redeploy to happen (in Tomcat it calls the StandardContext.reload() method). Since this happens without any additional action on the user’s side it is commonly referred to “Hot Deployment”.

Web容器通常有一个特殊的目录(Tomcat中的webapps, JBoss中的deploy), 用来对新的web应用或已经存在的应用的修改做定期扫描. 当扫描器检测到一个已经部署的.WAR更新了, 扫描器会触发一次重部署(Tomcat中是调用StandardContext.reload()方法). 由于这些操作的执行在用户端没有任何额外的操作, 所以被称为"热部署"

Hot Deployment is supported by all wide-spread application servers under different names: autodeployment, rapid deployment, autopublishing, hot reload, and so on. In some containers, instead of moving the archive to a predefined directory you can configure the server to monitor the archive at a specific path. Often the redeployment can be triggered from the IDE (e.g. when the user saves a file) thus reloading the application without any additional user involvement. Although the application is reloaded transparently to the user, it still takes the same amount of time as when hitting the “Reload” button in the admin console, so code changes are not immediately visible in the browser, for example.

热部署已经被大部分应用服务器以不同名字支持: autodeployment, rapid depoyment, autopublishing, hot reload, 等待. 在一些容器内, 你可以指定服务器监控特定路径的archive, 而不是预定义的archive目录. 重部署通常可以通过IDE触发(例如当用户保存了一个文件是), 因此重载应用不要对用户有何额外的牵连. 虽然应用重加载对用户是透明的,但他其实在你点击"reload"按钮的时候会话大量时间来重载.所以代码的改变并不会马上在浏览器中看到.

Another problem with redeployment in general and hot deployment in particular is classloader leaks. As we reviewed in Reloading Java Classes 201, it is amazingly easy to leak a classloader and quickly run out of heap causing an OutOfMemoryError. As each deployment creates new classloaders, it is common to run out of memory in just a few redeploys on a large enough application (whether in development or in production).

另一个重部署和热部署的问题是classloader泄露.如我们在Reloading Java Classes 201所讲, 泄露一个classloader是很容易的, 并且他会很快耗尽堆资源并造成 OutOfMemoryError. 因为每次部署都会生成新的classloader, 在一个大应用几次重部署后耗尽内存资源是很正常的(不论是在开发环境还是生产环境)

Exploded Deployment

An additional feature supported by the majority of web containers is the so called “exploded deployment”, also known as “unpackaged” or “directory” deployment. Instead of deploying a .WAR archive, one can deploy a directory with exactly the same layout as the .WAR archive:

另外一个web容器支持的特点叫做"分解部署", 也叫做"不打包"或"目录"部署.不部署一个WAR, 而是使用和war一样的结构部署一个目录

Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 and so on Translation

Why bother? Well, packaging an archive is an expensive operation, so deploying the directory can save quite a bit of time during build. Moreover, it is often possible to set up the project directory with exactly the same layout as the .WAR archive. This means an added benefit of editing files in place, instead of copying them to the server. Unfortunately, as Java classes cannot be reloaded without a redeploy, changing a .java file still means waiting for the application to reinitialize.

何必这样呢? 好吧, 打包一个archive是一个成本高昂的操作, 所以部署目录可以节省很多时间. 此外, 将项目目录结构设置成war那样也是很有可能的事情. 这意味着适当的编辑文件有额外的好处, 而不是将他们复制到服务器.很不幸的是, java类不重部署是不能重加载的, 改变一个.java文件仍然代表需要等待程序重新初始化

With some servers it makes sense to find out exactly what triggers the hot redeploy in the exploded directory. Sometimes the redeploy will be triggered only when the “web.xml” timestamp changes, or as in the case of GlassFish only when a special ”.reload” file timestamp changes. In most servers any change to deployment descriptors or compiled classes will cause a hot redeploy.

对于某些服务器, 精确的找到在分解目录中是什么触发了热部署是很有意义的. 有时候重部署只会在"web.xml"的时间戳被修改的时候被处罚, 在GlassFish的例子中, 只有当一个特殊的.reload文件被修改才会进行重部署. 大多数服务器中任何对部署描述符或编译类的修改都会造成热重部署.

If your server only supports deploying by copying to a special directory (e.g. Tomcat “webapps”, JBoss “deploy” directories) you can skip the copying by creating a symlink from that special directory to your project workspace. On Linux and Mac OS X you can use the common “ln -s” command to do that, whereas on Windows you should download the Sysinternals “junction” utility.

加入你的服务器只支持通过复制到特殊目录的方式重部署(例如, tomcat "webapps", JBoss "deploy" 文件夹), 你可以通过创建一个你的workspace到那个特殊目录的符号链接来跳过复制.在Linux和Max OS X中, 你可以使用"ln -s"命令来做这个事情, 在windows中你则可以下载 Sysinternals "junction" utility.

If you use Maven, then it’s quite complicated to set up exploded development from your workspace. If you have a solo web application you can use the Maven Jetty plugin, which uses classes and resources directly from Maven source and target project directories. Unfortunately, the Maven Jetty plugin does not support deploying multiple web applications, EJB modules or EARs so in the latter case you’re stuck doing artifact builds.

如果你使用Maven, 那么在你的工作空间中设置搭建分解开发模式会相当复杂.如果你有一个单独的web项目你可以使用Maven的Jetty插件, 它直接使用Maven源码和目标项目目录的classes和resources.

Session Persistence

Since we’re on the topic of reloading classes, and redeploying involves reinitializing an application, it makes sense to talk about session state. An HTTP session usually holds information like login credentials and conversational state. Losing that session when developing a web application means spending time logging in and browsing to the changes page – something that most web containers have tried to solve by serializing all of the objects in the HttpSession map and then deserializing them in the new classloader. Essentially, they copy all of the session state. This requires that all session attributes implement Serializable (ensuring session attributes can be written to a database or a file for later use), which is not restricting in most cases.

因为我们讨论的是冲载入classes和重部署(包括重新初始化)一个应用, 那么谈一谈session的状态是很有意义的.一个HTTP session通常会保存一些登录凭证和会话状态.而在开发web应用时丢失session意味着需要花时间登录并跳转到更改的页面 - 一些web容器尝试通过序列化所有HttpSession中的对象并在新的classloader中反序列化他们. 本质上他们复制了所有的session状态. 这需要所有的session参数都实现了Serializable接口(保证session参数可以被写入数据库或文件以便以后使用), 这在很多情况下都无法收到制约.

Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 and so on Translation

Session persistence has been present in most major containers for many years (e.g. Restart Persistence in Tomcat), but was notoriously absent in Glassfish before v3.

Session持久化已经在大部分主流容器*存在很多年了(例如 Tomcat的Restart Persistence), 但众所周知GlassFish在v3之前是没有的

OSGi

There is a lot of misunderstanding surrounding what exactly OSGi does and doesn’t do. If we ignore the aspects irrelevant to the current issue, OSGi is basically a collection of modules each wrapped in its own classloader, which can be dropped and recreated at will. When it’s recreated, the modules are reinitialized exactly the same way a web application is.

对于OSGi具体做了什么和没做什么事情有很多误解. 如果我们忽略和这个问题相关的方面, OSGi基本上就是他自己的classloader的包装集合, 这是可以任意丢弃和重新创建的.

Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 and so on Translation

The difference between OSGi and a web container is that OSGi is something that is exposed to your application, that you use to split your application into arbitrarily small modules. Therefore, by design, these modules will likely be much smaller than the monolithic web applications we are used to building. And since each of these modules is smaller and we can “redeploy” them one-by-one, re-initialization takes less time. The time depends on how you design your application (and can still be significant).

 OSGi和web容器是OSGi是暴露给你的应用程序的, 你可以将你的程序切分成任意小的模块. 因此, 这些模块可能会远远小于我们用来build的整个web应用程序.而由于每个模块都很小,很显著的差异我们以将他们一个接一个的重部署, 重新初始化花费的时间也会更少. 这个时间取决于你如何设计你的程序(这可能)

Tapestry 5, RIFE & Grails

Recently, some web frameworks, such as Tapestry 5, RIFE and Grails, have taken a different approach, taking advantage of the fact that they already need to maintain application state. They’ll ensure that state will be serializable, or otherwise easily re-creatable, so that after dropping a classloader, there is no need to reinitialize anything.

最近的一些框架, 例如 Tapestry 5, RIFE 和 Grails, 使用了一种不同的方法, 这种方法已经可以用来维护程序的状态了. 他们保证状态可以被序列化, 或者其他简单的重创建, 所以在丢弃classloader后已经没有必要去重新初始化任何东西.

This means that application developers use frameworks’ components and the lifecycle of those components is handled by the framework. The framework will initialize (based on some configuration, either xml or annotation based), run and destroy the components.

这意味着程序开发者使用的框架组件和那些组件的生命周期都是被框架处理的.框架会初始化(基于某些配置, 不是xml就是注释), 运行和销毁这些组件

As the lifecycle of the components is managed by the framework, it is easy to recreate a component in a new classloader without user intervention and thus create the effect of reloading code. In the background, the old component is destroyed (classloader is dropped) and a new one created (in a new classloader where the classes are read in again) and the old state is either deserialized or created based on the configuration.

由于组件的什么周期由框架管理, 不需要用户接入就使用新的classloader重建一个组件是很简单的, 因此也能创造出重载代码的效果.在后台,旧的组件被销毁(classloader被丢弃)而新的被创建(在一个新的classloader再次载入该类), 旧的状态不是被反序列化就是基于配置被创建.

Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 and so on Translation

This has the obvious advantage of being very quick, as components are small and the classloaders are granular. Therefore the code is reloaded instantly, giving a smooth experience in developing the application. However such an approach is not always possible as it requires the component to be completely managed by the framework. It also leads to incompatibilities between the different class versions causing, among others, ClassCastExceptions.

这样的优势是很明显的, 由于组件很小并且classloader的粒度小, 他的速度可以非常快. 因此会被立刻重载入,在程序开发过程中可以提供流畅的体验.然而这样的方法并不总是有可能的, 因为他需要组件完全被框架管理. 这也会导致不同类版本的不兼容性, 在其他类中引发ClassCastException

We’ve Covered a Lot – and simplified along the way

It’s worth mentioning that using classloaders for code reloading really isn’t as smooth as we have described here – this is an introductory article series. Especially with the more granular approaches (such as frameworks that have per component classloaders, manual classloader dropping and recreating, etc), when you start getting a mixture of older and newer classes all hell can break loose. You can hold all kinds of references to old objects and classes, which will conflict with the newly loaded ones (a common problem is getting a ClassCastException), so watch what you’re doing along the way. As a side note: Groovy is actually somewhat better at handling this, as all calls through the Meta-Object Protocol are not subject to such problems.

值得一提的是使用classloader来进行代码重载并没有我们在这里提到的这么顺利 -- 这是一个介绍性的文章系列. 特别是随着一些更细粒度的方法(比如拥有每一个组件的classloader的框架, 手动的丢弃和重创建classloader), 当你开始混合使用旧的和新的类的时候, 地狱之门可能已经打开了. 你可以持有所有旧的对象和类的引用, 这回跟新载入的造成冲突(常见问题是造成ClassCastException), 所以注意你正在做的事情. 附注: Groovy在这个问题上处理的更好一些, 因为所有通过Meta-Object协议的调用都没有受到这个问题的影响

This article addressed the following questions:

  • How are dynamic classloaders used to reload Java classes and applications?
  • 动态classloader是如何用来重载java类和应用的
  • How do Tomcat, GlassFish (incl v3), and other servers reload Java classes and applications?
  • Tomcat, GlassFish(包括v3),和其他服务器是如何重载java类和应用的
  • How does OSGi improve reload and redeploy times?
  • OSGi是如何改善重载和重部署的时间的
  • How do frameworks (incl Tapestry 5, RIFE, Grails) reload Java classes and applications?
  • 框架(包括Tapestry 5, RIFE, Grails)是如何重载类和应用的

Coming up next, we continue our explanation of classloaders and the redeploy process with an investigation into HotSwap and JRebel, two tools used to reduce time spent reloading and redeploying. Stay tuned!

接下来, 我们会用HotSwap和JRebel继续对classloader和重部署过程作解释, 这两个工具用来减少重载和重部署的时间.请继续收看!