Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 an

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

Copyright reserved by Rebel Inchtml

 

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.java

本章節咱們會對服務器, 容器, 框架如何動態使用動態classloader reload java類和應用程序作一個回顧.咱們也會說起如何使用優化的方法更快的reload和redeploy. web

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).這在生產環境中是頗有意義的, 由於他基於你一種很簡單的裝配和部署應用的方式, 可是當開發這樣的應用的時候, 你一般都會想在編輯完程序文件後立刻在瀏覽器看到編輯完的改變.apache

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).windows

在 Reloading Java Classes 101, 咱們解釋了動態classloaders如何用來reload java 類和應用. 在這篇文章中咱們探討服務器和框架如何使用動態的classloader來加速開發.咱們會使用Apache Tomcat做爲主要的例子, 並對它與其餘容器的不懂行爲進行比較(Tomcat也和JBoss和GlassFish直接相關, 由於他們是鍵入Tomcat做爲servlet容器的)api

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的層級關係以下:

classloaders-jee

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()方法被調用

tomcat-cl-reload

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同樣的結構部署一個目錄

exploded

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參數能夠被寫入數據庫或文件以便之後使用), 這在不少狀況下都沒法收到制約.

hot-deploy-session

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的包裝集合, 這是能夠任意丟棄和從新建立的.

osgi

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再次載入該類), 舊的狀態不是被反序列化就是基於配置被建立.

component

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和重部署過程做解釋, 這兩個工具用來減小重載和重部署的時間.請繼續收看!

相關文章
相關標籤/搜索