修改Apache Velocity源碼並重新打包velocity1.7.jar已解決動態加載自定義函數的問題(一)

修改Apache Velocity源碼並重新打包velocity1.7.jar已解決動態加載自定義函數的問題(一)

velocity是Apache開源的模板引擎,非常的靈活好用,且支持自定義的函數編寫,具體的就不細說了,大家可以百度學習。

今天遇到一個情況,那就是我已經寫好的工具做成了軟件,如文檔生成等工具,使用的模板引擎就是velocity,自定義函數也內置了一些,但是由於每次需要的函數都不同,每次都需要拓展自定義函數,導致每次都要重新打包軟件,於是想到了java插件化開發,即單獨使用接口寫一個自定義函數項目然後打包成jar,讓工具添加加載這個插件即可實現功能,且不需要每次修改工具,而只需要修改自定義函數這個項目,之前遇到的問題解決方式如下:https://blog.csdn.net/rico_zhou/article/details/103817403

現在遇到的問題是不僅軟件需要動態的使用類加載器去加載自定義函數這個項目打包的jar,加載完成後,由於velocity也是通過類名去加載自定義函數類,這就好比velocity加載了一個插件,而這個插件又是另一個程序(軟件)加載進來的,導致velocity無法獲取。

聽起來有些繞,舉個例子:以下代碼是velocity初始化的時候傳遞已經繼承了Directive類的自定義函數類名

這個是自定義函數,繼承Directive

	//實例化模板引擎需要添加配置此實現類,每個實現類需要加入變量CustomFunctionConstant.ALL_CUS_FUN_VEL_NAME
	public class NowDate extends Directive {
	
		@Override
		public String getName() {
			// 返回函數名
			return CustomFunctionConstant.CUS_FUN_VEL_NOWDATE_NAME;
		}
	
		@Override
		public int getType() {
			// getType接口用來告訴velocity這個函數類型,可以是行也可以是塊函數
			return LINE;
		}
	
		@Override
		public boolean render(InternalContextAdapter context, Writer writer, Node node)
				throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
			// 判斷參數,如果沒有參數則默認使用yyyy-MM-dd HH:mm:ss當前時間,如果有參數則按照參數格式化,格式化不正確則輸出默認
			// 獲取第一個參數
			Node node0;
			try {
	
				// 先獲取參數數量
				int paramsNum = node.jjtGetNumChildren();
	
				if (paramsNum == 0) {
					// 0個參數
					// 輸出函數值
					writer.write(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS));
				} else if (paramsNum == 1) {
					// 一個參數
					node0 = node.jjtGetChild(0);
					String p0 = (String) node0.value(context);
					if (p0 == null || "".equals(p0)) {
						// 輸出函數值
						writer.write(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS));
					} else {
						// 輸出函數值
						writer.write(DateUtils.dateTimeNow(p0));
					}
				} else if (paramsNum == 2) {
					// 二個參數
	
				}
	
			} catch (Exception e) {
				// 輸出函數值
				writer.write(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS));
			}
	
			return false;
		}
	
	}

以下是velocity引擎初始化需要加載自定義函數類名:


			p.setProperty("userdirective", "com.soft.support.velocity.custom.NowDate");

代碼不全,諒解,有需要請聯繫。

自定義函數類和引擎是在一個項目中,一起編譯成爲軟件,當然不會出現問題,自定義函數也能加載到,但是如果自定義函數是在單獨的一個項目中,且不是一起編譯的,那麼即使軟件已經動態加載了自定義函數類,velocity依然無法加載它,多了一層出來,查看velocity源碼發現報錯在這(如何閱讀源碼下篇講,或者百度)

 /**
     *  instantiates and loads the directive with some basic checks
     *
     *  @param directiveClass classname of directive to load
     */
    public void loadDirective(String directiveClass)
    {
        try
        {
            Object o = ClassUtils.getNewInstance( directiveClass);

            if (o instanceof Directive)
            {
                Directive directive = (Directive) o;
                addDirective(directive);
            }
            else
            {
                String msg = directiveClass + " does not implement "
                    + Directive.class.getName() + "; it cannot be loaded.";
                log.error(msg);
                throw new VelocityException(msg);
            }
        }
        // The ugly threesome:  ClassNotFoundException,
        // IllegalAccessException, InstantiationException.
        // Ignore Findbugs complaint for now.
        catch (Exception e)
        {
            String msg = "Failed to load Directive: " + directiveClass;
            log.error(msg, e);
            throw new VelocityException(msg, e);
        }
    }
  public static Class getClass(String clazz) throws ClassNotFoundException
    {
        /**
         * Use the Thread context classloader if possible
         */
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader != null)
        {
            try
            {
                return Class.forName(clazz, true, loader);
            }
            catch (ClassNotFoundException E)
            {
                /**
                 * If not found with ThreadContext loader, fall thru to
                 * try System classloader below (works around bug in ant).
                 */
			}
        }
        /**
         * Thread context classloader isn't working out, so use system loader.
         */
        return Class.forName(clazz);
    }

稍微翻譯一下即可發現,首先velocity使用當前線程類加載器加載自定義函數,如果失敗則使用系統類加載器加載,如果還失敗則拋出異常。正常情況下我們是不需要其他方式加載的,默認即可使用,但基於以上特殊的需求我們需要另闢蹊徑。

思路:既然通過上一篇我們已經可以實現動態加載類了:https://blog.csdn.net/rico_zhou/article/details/103817403,那我們只需要讓這個velocity加載類的地方也同樣的加載(原加載方法不變)不就可以了麼。那我們只需要傳遞一個參數,一個需要加載的jar路徑,或者需要加載的jar文件夾路徑,即可實現以上功能,加載jar中的類方法如下:注意打包方式:

public static void main(String[] args)
			throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
		String jarPath = "C:\\Users\\ricozhou\\Desktop\\tt.jar";
		File jarFile = new File(jarPath);
		String className = "test3.Test1";
		URL url = jarFile.toURI().toURL();
		ClassLoader system = new URLClassLoader(new URL[] { url }, Thread.currentThread().getContextClassLoader());
		Class<?> cs = system.loadClass(className);
		Object object = cs.newInstance();
 
		System.out.println(cs.getMethod("test").invoke(object));
	}

,下一篇我們講述如何修改velocity源碼並打包使用。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章