昨天在编写代码生成器时遇到一个问题:用JET2模板引擎不能够动态编译代码模板文件,也就意味着我不能都动态的添加自定义代码模板,这和我希望的开放式式的代码生成器不符合。我最看中的就是jet2模板里能由模板编写者自己控制生成哪些代码文件,很开发灵活。如果放弃jet2的话我还没找到更好的代替者,所以我只能想方设法使代码生成器能动态加载用户添加的模板。想到的办法是:让用户编写好代码模板后,将模板文件和编译好的模板类一起打包成jar,然后由代码生成运行时动态加载。
插件动态加载jar方法:自定义类加载器继承URLClassLoader,重写addURl方法使之变为public,方便多次添加jar。
代码:
public class DynamicClassLoader extends URLClassLoader { /** * 类加载器实例化后,再添加单个jar */ @Override public void addURL(URL url) { super.addURL(url); } static final URL[] EMPTY_URLS = new URL[]{}; public DynamicClassLoader(){ super(EMPTY_URLS); } public DynamicClassLoader(URL[] urls){ super(urls ,(Thread.currentThread().getContextClassLoader() == null || Thread.currentThread().getContextClassLoader() == ClassLoader.getSystemClassLoader())? Compiler.class.getClassLoader():Thread.currentThread().getContextClassLoader()); } public DynamicClassLoader(ClassLoader parent){ super(EMPTY_URLS,parent); } /** * 创建类加载器 * @param urls 待加载的jarUrl集合 * @param parent 父类加载器 */ public DynamicClassLoader(URL[] urls, ClassLoader parent){ super(urls,parent); } }
在需要的地方实例化调用(如插件启动时实例化),调用代码如下:
代码:
/** * 动态加载指定jar * @param jarURL */ public void loadJar(File jar){ if(jar != null){ try { dynamicClassLoader.addURL(jar.toURI().toURL()); } catch (MalformedURLException e) { LogUtil.logError(e.getMessage(),e); } setPathToTemplateClassMapByJar(jar); } } /** * 根据模板jar文件设置代码模板路径和类映射集合 * @param jar 模板jar文件,必须包含模板源码和编译后的模板类 */ public void setPathToTemplateClassMapByJar(File jar){ if(jar == null){ return; } try { JarFile jf = new JarFile(jar); final Enumerationentries = jf.entries(); //保存所有模板源文件 List templatePathList = new ArrayList (); //保存编译的模板类名和类全路径映射集合 Map classMap = new HashMap (); while (entries.hasMoreElements()) { final JarEntry entry = entries.nextElement(); final String entryName = entry.getName(); if (entryName.startsWith("templates")) { templatePathList.add(entryName); }else if(entryName.endsWith(".class")){ int lastspindex = entryName.lastIndexOf("/"); String key = entryName.substring(lastspindex + 1, entryName.length() - 6); String className = entryName.substring(0, entryName.length() - 6).replace("/", "."); classMap.put(key, className); } } for(String templatePath : templatePathList){ int lastspindex = templatePath.lastIndexOf("/"); int lastpoindex = templatePath.lastIndexOf("."); String key = "_jet_"+templatePath.substring(lastspindex + 1, lastpoindex).replace(".", ""); String className = classMap.get(key); if(className != null){ pathToTemplateClassMap.put(templatePath, className); } } } catch (IOException e) { LogUtil.logError(e.getMessage(),e); } }
还要修改代码生成器插件的jet2扩展中的模板加载器类,使之能根据模板路径用自定义类加载器来实例化模板对象。
代码如下:
/** * 自定义模板加载器 * CreatorTemplateLoader */ public class CreatorTemplateLoader implements JET2TemplateLoader, JET2TemplateLoaderExtension { private JET2TemplateLoader delegate = null; /* * (non-Javadoc) * * @see org.eclipse.jet.JET2TemplateLoader#getTemplate(java.lang.String) */ public JET2Template getTemplate(final String templatePath) { final String ordinal = CodeGenActivator.pathToTemplateClassMap.get(templatePath); if(ordinal != null) { try { return (JET2Template)CodeGenActivator.getDefault().getDynamicClassLoader().loadClass(ordinal).newInstance(); } catch (InstantiationException e) { LogUtil.logError(e.getMessage(), e); } catch (IllegalAccessException e) { LogUtil.logError(e.getMessage(), e); } catch (ClassNotFoundException e) { LogUtil.logError(e.getMessage(), e); } } return this.delegate != null ? this.delegate.getTemplate(templatePath) : null; } /* * (non-Javadoc) * * @see org.eclipse.jet.JET2TemplateLoaderExtension#getDelegateLoader() */ public JET2TemplateLoader getDelegateLoader() { return this.delegate; } /* * (non-Javadoc) * * @see * org.eclipse.jet.JET2TemplateLoaderExtension#setDelegateLoader(org.eclipse * .jet.JET2TemplateLoader) */ public void setDelegateLoader(final JET2TemplateLoader loader) { this.delegate = loader; } }
隔了好久才记起这个文章没写完,今天记起后赶紧补充完整了。