修改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源码并打包使用。