太久沒有寫點東西了,今天分享一下Java web中我們的一個簡單動態加載jar包,無需熱部署以及更新以前的class即可上線服務應用,Java的反射機制內容這裏不做科普(下面基本無代碼,僅提供思路,代碼黨繞行)。
環境:java8+tomcat(tomcat中的類加與javase的加載器不是一樣的,暫不做考慮);
數據交互:webservice(json)
思路:我們都知道,普遍情況下JavaWeb正常運行都需要編譯爲class文件才能解釋運行,很多時候系統更新都需要重啓服務或者服務自動熱部署,無論兩種模式如何,都會使得session失效以及部分任務中斷。
php之所以普及,不僅僅它是腳本語言無需編譯,同時哪個模塊需要更新只需要更新某個文件而不需要重啓服務就可以做到無縫更新了。基於此優勢,於是想到利用Java動態加載模式 + jsp腳本模式進行項目部署、更新,儘可能做到代碼的安全。
當我們創建一個web應用後,如果class內部有邏輯錯誤則需要更新class,這時web應用就需要得到重啓更新,但事實上,在生產環境中給你這樣的機會是少之又少,除非你能把控住用戶使用時間段。有時候會想如果寫jsp腳本,又會顯得代碼水平很low(只要安全、快速、優雅,jsp也是不錯的選擇),所以每天等到深夜12點後纔會中斷系統進行更新。
其實只需要動態加載jar文件,並定時更新就行了:
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package cn.skyatom.dynamicloader; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import sun.misc.ClassLoaderUtil; /** * 動態加載反射機制加載器 * * @author ZWK */ public class Loader { /** * 全局jar包加載器 */ private static java.util.Map<String, URLClassLoader> globJarLoader = new java.util.HashMap<String, URLClassLoader>(); /** * 獲取所有的加載器 * @return */ public static java.util.List<URLClassLoader> getAllClassLoader() { java.util.Iterator<URLClassLoader> lds = globJarLoader.values().iterator(); java.util.List<URLClassLoader> list = new java.util.ArrayList<URLClassLoader>(); while(lds.hasNext()){ URLClassLoader cl = lds.next(); list.add(cl); } return list; } /** * 執行帶參數構造器的方法 * * @param jarid jar包id * @param clazname 完整類名 * @param methodname 執行方法 * @param ctypes 構造器參數類型 * @param cvalues 構造器參數值 * @param types 參數類型 * @param values 參數值 * @return * @throws java.lang.Exception */ public static Object excMethod(String jarid, String clazname, String methodname, Class<?>[] ctypes, Object[] cvalues, Class<?>[] types, Object[] values) throws Exception { URLClassLoader loader = globJarLoader.get(jarid); if (loader == null) { return null; } Class<?> myclaz = loader.loadClass(clazname); Object c = myclaz.getConstructor(ctypes).newInstance(cvalues); Method m = myclaz.getMethod(methodname, types); Object rs = m.invoke(c, values); c = null; return rs; } /** * 執行空構造器的方法 * * @param jarid jar包id * @param clazname 完整類名 * @param methodname 方法名 * @param types 參數類型 * @param values 參數值 * @return */ public static Object excMethod(String jarid, String clazname, String methodname, Class<?>[] types, Object[] values) throws Exception { URLClassLoader loader = globJarLoader.get(jarid); if (loader == null) { return null; } Class<?> myclaz = loader.loadClass(clazname); Object c = myclaz.newInstance(); Method m = myclaz.getMethod(methodname, types); Object rs = m.invoke(c, values); c = null; return rs; } /** * 執行靜態方法 * * @param jarid jar包id * @param clazname 完整類名 * @param methodname 方法名 * @param types 參數類型 * @param values 參數值 * @return */ public static Object excStaticMethod(String jarid, String clazname, String methodname, Class<?>[] types, Object[] values) throws Exception { URLClassLoader loader = globJarLoader.get(jarid); if (loader == null) { return null; } Class<?> myclaz = loader.loadClass(clazname); Method m = myclaz.getMethod(methodname, types); Object rs = m.invoke(null, values); return rs; } /** * 執行帶參數構造器的方法 * * @param obj 對象 * @param methodname 執行方法 * @param ctypes 構造器參數類型 * @param cvalues 構造器參數值 * @param types 參數類型 * @param values 參數值 * @return * @throws java.lang.Exception */ public static Object excMethodByObject(BindClaz obj, String methodname, Class<?>[] ctypes, Object[] cvalues, Class<?>[] types, Object[] values) throws Exception { Class<?> myclaz = obj.cClaz; Object c = obj.cObject; Method m = myclaz.getMethod(methodname, types); Object rs = m.invoke(c, values); c = null; return rs; } /** * 執行空構造器的方法 * * @param obj * @param methodname 方法名 * @param types 參數類型 * @param values 參數值 * @return */ public static Object excMethodByObject(BindClaz obj, String methodname, Class<?>[] types, Object[] values) throws Exception { Class<?> myclaz = obj.cClaz; Object c = obj.cObject; Method m = myclaz.getMethod(methodname, types); Object rs = m.invoke(c, values); c = null; return rs; } /** * 執行靜態方法 * * @param jarid jar包id * @param clazname 完整類名 * @param methodname 方法名 * @param types 參數類型 * @param values 參數值 * @return */ public static Object excStaticMethodByObject(BindClaz obj, String methodname, Class<?>[] types, Object[] values) throws Exception { Class<?> myclaz = obj.cClaz; Method m = myclaz.getMethod(methodname, types); Object rs = m.invoke(null, values); return rs; } /** * 創建對象 * * @param jarid jar包id * @param clazname 完整類名 * @param ctypes 參數類型 * @param cvalues 參數值 * @return */ public static BindClaz createObject(String jarid, String clazname, Class<?>[] ctypes, Object[] cvalues) throws Exception { URLClassLoader loader = globJarLoader.get(jarid); if (loader == null) { return null; } Class<?> myclaz = loader.loadClass(clazname); if (ctypes != null) { Object c = myclaz.getConstructor(ctypes).newInstance(cvalues); BindClaz bc = new BindClaz(); bc.cClaz = myclaz; bc.cObject = c; return bc; } else { Object c = myclaz.newInstance(); BindClaz bc = new BindClaz(); bc.cClaz = myclaz; bc.cObject = c; return bc; } } /** * 釋放所有jar包 */ public static void releaseAllJarPck() { if (globJarLoader == null) { return; } java.util.Iterator<String> jids = globJarLoader.keySet().iterator(); while (jids.hasNext()) { String jid = jids.next(); releaseJarPck(jid); } globJarLoader.clear(); } /** * 釋放包資源 * * @param jarid */ public static void releaseJarPck(String jarid) { if (globJarLoader.get(jarid) != null) { // ClassLoaderUtil.releaseLoader(globJarLoader.get(jarid));//釋放加載器 globJarLoader.remove(jarid); } } /** * 從web協議加載 * * @param jarid * @param weburl * @throws Exception */ public static void loadJarPckByWeb(String jarid, String weburl) throws Exception { if (globJarLoader.get(jarid) != null) { ClassLoaderUtil.releaseLoader(globJarLoader.get(jarid));//釋放加載器 } URL jarurl = new java.net.URL(weburl); URLClassLoader myClassLoader = new URLClassLoader(new URL[]{jarurl}, Thread.currentThread().getContextClassLoader()); globJarLoader.put(jarid, myClassLoader); } /** * 加載jar包資源 * * @param jarid jar包id * @param jarPath jar包文件路徑 * @throws Exception */ public static void loadJarPck(String jarid, String jarPath) throws Exception { if (globJarLoader.get(jarid) != null) { ClassLoaderUtil.releaseLoader(globJarLoader.get(jarid));//釋放加載器 } URL jarurl = new java.io.File(jarPath).toURI().toURL(); URLClassLoader myClassLoader = new URLClassLoader(new URL[]{jarurl}, Thread.currentThread().getContextClassLoader()); globJarLoader.put(jarid, myClassLoader); } /** * 綁定創建的類 */ public static class BindClaz { public Object cObject = null;//反射創建的對象 public Class<?> cClaz = null;//當前類 } }
一個簡單的動態加載執行方法,然後再配套一個所需要的代碼生成器,將會非常節約開發時間以及相應的運維效率:
最後生成的代碼放進json文件(jsp映射)中,只需要填入對應的參數即可。
**對於web應用,我在應用中寫了一個定時器,定時更新最新版本的jar擴展包,一旦有更新的邏輯業務,都不用重啓應用,也無需負載(負載遲早要做,條件允許情況下)。
希望能給閱讀到這裏的同學一點點啓發。
****多餘的代碼也不粘貼了,基於以上思路估計花個幾個小時,你自己也能寫一個大約ok的動態加載模塊。 誰說Java不能像php一樣敏捷開發,換一個思路一樣能走通