前言
前面幾篇文章講了下如果編寫簡單的eclipse插件,如創建插件項目、編寫右鍵彈出菜單等功能,接下來主要寫一下如何生成代碼的功能,這一片的功能跟插件本身的編寫關聯不太大,主要處理插件之后的業務內容。即彈出菜單后,點擊后 執行生成代碼的功能,由於這一塊的功能相對獨立,所以單獨建一個項目用來管理,以跟插件項目進行解耦,方便后期的維護,由於這一塊內容相對較多且引用了其他項目的一些內容,所以簡單列舉一下內容講解一下,就不在進行從頭進行演示了。后面會附加項目源碼,可以自己運行下看看效果。
對了,這里着重提示一下,本項目中引用且參考了其他的項目就是以前一個開源的koala項目,現在應該有一段時間不更新了,不過里面的思想還是很不錯的,大家有時間可以去看看。本項目中的一些目錄結構及項目代碼都參考或引用了該項目的內容。
本片生成代碼的核心生成領域對象的Form、Assembler,Form是平時業務中所屬的VO,Assembler是領域對象到Form的一個轉換的裝配器。
引用jar包
這里引用的jar包,有一些commons的工具jar包及log4j的日志jar包,其他的還有兩個jar包,分別是:
koala-plugin-util-core:這個jar包內容主要是一些基礎工具操作,如:文件復制、xml文件解析,maven項目編譯等,這個jar包就是原koala項目的jar包,不過這里只用到了其中的一小部分功能
velocity:這是一個模板引擎工具,跟freemark相似,具體內容可以上網搜索一下。
項目目錄
項目的目錄主要包括一下幾大塊:
analysis:該jar包下面主要包括java類的編譯、加載以及類、字段的分析
module:此jar包下的內容主要是一些對領域建模的實體類,領域對象中屬性的實體類,以及文件類型的實體類等等,如果不太理解,下面會詳細講解
utils:該jar包下面主要包括了一些工具類,如Velocity的引擎工具類,classloader清理的工具類,以及代碼生成的入口等…
templates:該目錄下的內容主要是一些Velocity的模板,描述要生成的代碼的模板
module包
module包下,包含兩個子包,一個是core,一個是pojo,其中core下面的內容是關於領域建模的實體類與領域對象中屬性的兩個實體類。類圖如下:
EntityModel:
是領域建模的實體類,一個EntityModel代表一個領域類,也就是我們要分析生成代碼的一個對象,
成員變量:
name:是領域類的簡單的類名稱,不包含包名
className:領域類的全類名,包含包名。
方法:
getLastPackageName():用來獲取改類的包名的最后一個包的名字
如:com.yunzaipiao.user.UserModel ,則name為:UserModel,className: com.yunzaipiao.user.UserModel。
getLastPackageName()返回:user
FieldModel:
該類為領域對象屬性的實體類,一個FieldModel代表領域對象的一個屬性,后面會介紹怎么分析領域對象生成FieldModel實體類
成員變量:
name:字段的名稱
type:字段的類型,如果為包裝類,則是有包名的,如:java.lang.String
方法:
getNameToFirstLetterUpper:返回將字段名的第一個字母轉為大寫后的字符串,如:name=”age”則返回:Age
getSimpleType:返回不包含報名的類型名稱,如:類型為java.lang.String 則該方法返回:String
pojo包下面的類主要是跟文件有關的實體類,類圖如下:
NewFileType:
文件類型枚舉類,包裝了要創建的文件類型,如:form文件、assembler文件。這里創建的兩個文件為我自己定義的,可以隨便定義要創建的文件
NewFile:
該類為所有生成文件實體的父類,包含要創建文件的名稱、項目路徑、包名,以及文件類型
方法:
process:該方法為一個抽象方法,由子類實現,實現后的方法用來根據子類的類型生成新文件
getPath:獲取要生成的文件的路徑,如下代碼,根據項目路徑、包名、文件名合並成一個新的文件名稱。
protected String getPath() {
return MessageFormat.format("{0}/src/{1}/{2}.java", projectPath, //getPackageName().replaceAll("\\.", "/"), getName());}
FormNewFile:
該類為NewFile的子類,用來生成Form文件,在項目開發過程中有領域類,如各種Model,也有一些數據傳輸類(VO),這里要生成的Form文件就是VO。該類中包含一些成員變量,存儲要生成Form類的名稱、字段信息、模板路徑以及要導入的包。
成員變量:
fieldMap:存儲要生成的Form類字段信息,key為字段名,value為類型,改字段在構造方法中初始化,如下代碼:
imports:用來獲取要導入的包
entityModel:存儲要生成Form類的名稱等信息
TEMPLATE_PATH:靜態變量,存儲要生成代碼的模板文件
方法:
process:該方法實現了父類的process方法,處理生成新文件的功能,代碼如下,如有不明白的地方可以上網搜索Velocity的使用方法
AssemblerNewFile:
該類的功能就是生成Assembler文件的一個實體類,Assembler類是一個包裝器類,主要用來轉換po與vo的一個工具類
WrapperType:
用來包裝基本數據類型類,暫時好像沒有用到,是從原koala項目摘過來的。
Analysis包
此包下面的類為編譯項目、加載項目以及分析類等工具,其中編譯項目的工具類並沒有使用,因為編譯項目涉及到加載jar包等需要更多的代碼且eclipse可以幫助我們編譯,所以暫時沒有做編譯項目的工作。
主要方式為尋找到eclipse編譯后的目錄,然后加載所有的類,然后通過指定的java文件名獲取類,再然后通過反射分析相關的字段等信息。類圖如下:
CURDClassCompiler:
該類為編譯java源文件類,由於此項目暫未使用就不在詳細講解了,有興趣可以查看源碼看看怎么實現的,暫時只能編譯沒有引用任何自定義類的源文件,因為編譯時沒有指定jar包
CURDClassLoader:
該工具類主要用來加載項目的所有jar包與類
成員變量:
classloader : URLClassLoader,這是一個通過URL來加載class的工具類,使用該工具類可以直接指定一個class的文件路徑來加載類
webRootPath: web項目的跟路徑
webType: 項目類型,這里這種說明一下,由於編寫倉促,沒有太多細節的優化,這里的webType直接寫死了為“WEB”,其實可以設置為普通的java項目,項目類型不同,主要是加載class的路徑不同,其他都一樣
urlList:存儲class及jar包的路徑
方法:
initLoader:初始化class路徑及jar包路徑,由於平時開發項目時,class編譯后的路徑統一為項目跟路徑下的/web/WEB-INF/classes路徑下,而jar包的路徑為項目跟路徑下的/web/WEB-INF/lib/路徑下,所以這里初始化class及jar包路徑后,調用loadWeb方法,加載項目
private void initLoader() throws MalformedURLException{if ("WEB".equals(webType)) {String dir = webRootPath + "/web/WEB-INF/classes/";
String libs = webRootPath + "/web/WEB-INF/lib/";
loadeWeb(dir, libs);} else {
String dir = webRootPath + "/bin";
loadJar(dir);}}loadWeb:該方法將class路徑以及所有的jar包存儲到成員變量urlList中去,然后構造方法會new一個URLClassLoader,將urlList轉換成URL數組后作為參數。然后通過URLClassLoader.loadClass(name)方法可以獲取指定的class
private void loadeWeb(String dir, String libs) throws MalformedURLException {urlList.add(new URL("file:" + dir));File lib = new File(libs);
File[] jarFiles = lib.listFiles();for (File jarFile : jarFiles) {
logger.info("addJar:" + jarFile.getAbsolutePath());
urlList.add(new URL("file:" + jarFile.getAbsolutePath()));}}forName:通過URLClassLoader.loadClass(name),獲取加載后的指定class信息
CURDClassLoader:構造方法,整合了以上方法的功能,首先調用initLoader初始化class、及jar包路徑,然后調用loadWeb獲取class路徑及所有的jar包路徑,最后生成一個URLClassLoader。發布一個公有方法forName通過URLClassLoader.loadClass獲取指定class的信息。
private CURDClassLoader(String webRootPath) throws MalformedURLException{this.webRootPath = webRootPath;
initLoader();URL[] urls = new URL[] {};
classloader = new URLClassLoader(urlList.toArray(urls));
}
CURDCoreAnalysis:
這個類是領域建模核心,分析領域對象的類名、屬性信息等,
成員變量:
classloader:這個是剛才創建的CURDClassLoader 類的一個實例,用來加載要分析的類
方法:
analysis:此方法為該工具類的入口方法,傳入一個項目路徑、要分析的java文件,然后進行分析類的屬性與相關的字段,分析類及屬性信息是通過java的反射機制完成的,以下為完整代碼,如果有不明白的地方請百度下或留言一起學習吧。
/**
* 傳入選中的JAVA文件的完整路徑,對此源文件進行領域建模分析* @param srcJava java文件完整的路徑,從包名開始,如:com.yunzaipiao.user.User* @throws Exception*/public EntityModel analysis(String projectPath, String srcJava) throws Exception{// String binPath = srcPath.replace("/src", "/bin");
// binPath = binPath.substring(0, binPath.lastIndexOf("/"));
// 編譯
// new CURDClassCompilerAndLoad().compiler(srcPath, binPath);
classloader = CURDClassLoader.getInstance(projectPath);//實體本身進行分析
EntityModel entity = analysisField(srcJava);classloader.close();return entity;
}/**
* 分析用戶選中的ENTITY類* @param srcJava*/private EntityModel analysisField(String srcJava){
if(entityMap.containsKey(srcJava))return entityMap.get(srcJava);logger.info("==============對"+srcJava+"進行建模工作============");EntityModel entity = new EntityModel();
entityMap.put(srcJava, entity);Class classEntity = null;
try {
classEntity = classloader.forName(srcJava);} catch (ClassNotFoundException e) {
e.printStackTrace();}entity.setName(classEntity.getSimpleName());entity.setClassName(classEntity.getName());analysisField(classEntity,entity);logger.info("entity.name: " + classEntity.getSimpleName());
logger.info("entity.className: " + classEntity.getName());
logger.info("entity.packageName: " + classEntity.getPackage().getName());
logger.info("==============對"+srcJava+"進行建模完成=============");return entity;
}/**
* 傳入一個類,解析這個類所擁有的屬性* @param classEntity*/@SuppressWarnings("rawtypes")
private void analysisField(Class classEntity,EntityModel entity){List<Class> classesForAnalysis = new ArrayList<Class>();
classesForAnalysis.add(classEntity);Class supperClass = classEntity.getSuperclass();while (supperClass != null) {classesForAnalysis.add(supperClass);supperClass = supperClass.getSuperclass();}for (int i = classesForAnalysis.size() -1; i >=0; i--) {Class classForAnalysis = classesForAnalysis.get(i);Field[] fields = classForAnalysis.getDeclaredFields();Method[] methods = classForAnalysis.getDeclaredMethods();Map<String,Method> methodMap = new HashMap<String,Method>();
for(Method method:methods){
methodMap.put(method.getName().toLowerCase(), method);}for(Field field:fields){
boolean isStatic = Modifier.isStatic(field.getModifiers());
if(isStatic)continue;logger.info("分析到屬性:"+field.getName()+";類型是:【"+field.getType().getName()+"】");//分析FIELD
createModel(field,entity);}}}private void createModel(Field field,EntityModel entity){FieldModel fieldModel = new FieldModel(field.getName(),field.getType().getName());
entity.getFields().add(fieldModel);}
utils
該包下面有三個類,ClassLoaderClear、CodeGenerator、VelocityContextUtils三個工具類,其中ClassLoaderClear為清除與釋放加載項目時的一些資源,具體細節請查看源碼吧;VelocityContextUtils就是生成一個單例模式的VelocityContext的實例,也沒有什么好講解的,下面主要講解一下CodeGenerator類。
CodeGenerator:
首先請看一下我們平時做項目的目錄結構,
可以看到,我們的項目目錄是在src目錄下面就直接是包名了,所以,給我一個java源文件的全路徑,我直接截取src前面的字符串,即為項目的路徑,src后面的字符串並去除.java字符串即為要分析的java全類名,各位也可以根據自己的項目目錄結構進行處理一下,當然這樣可能也不太嚴謹,如果有更好的處理方法請留言出來一塊學習吧。
先看一下最關鍵的一個方法的代碼:
public static void generateCode(String javaPath) throws Exception {try {
String projectPath = javaPath.substring(0,javaPath.indexOf("/src") + 1);
projectPath = projectPath.substring(0, projectPath.lastIndexOf("/"));
logger.info("項目路徑是:" + projectPath);
String srcPath = javaPath.substring(javaPath.indexOf("/src") + 4);
String srcJava = srcPath.substring(1, srcPath.lastIndexOf(".java"));
logger.info("要分析的實體類是:" + srcJava);
srcJava = srcJava.replaceAll("/", ".");EntityModel entityModel = CURDCoreAnalysis.getInstance().analysis(projectPath, srcJava);VelocityContext context = VelocityContextUtils.getVelocityContext();context.put("entity", entityModel);
FormNewFile formNewFile = createFormFile(projectPath, entityModel);formNewFile.process();AssemblerNewFile assemblerNewFile = createAssemblerFile(projectPath, entityModel);assemblerNewFile.process();} catch (Exception e) {
throw e;
}}可以看到該方法,首先通過傳入的java源文件的全路徑,分析項目路徑及java類的全名稱,然后通過上面講過的CURDCoreAnalysis分析類的名稱及屬性信息,然后通過VelocityContext獲取一個實例,將分析到的信息放入velocity屬性中,然后創建一個FormNewFile,通過process方法創建Form文件,最后創建Assembler文件。
在上面的generateCode方法中調用了createFormFile、createAssemblerFile兩個方法用來創建對應的FormNewFile與AssemblerNewFile,首先看一下createFormFile方法:
private static FormNewFile createFormFile(String projectPath,EntityModel entityModel) {String className = entityModel.getClassName();
String formName = entityModel.getName();
if (formName.endsWith("Model")) {formName = formName.substring(0, formName.indexOf("Model"));
}formName += "Form";
String packageName = className.substring(0, className.lastIndexOf("."))+ ".form";
packageName = packageName.replace(".model.", ".web.");FormNewFile formNewFile = new FormNewFile(formName, projectPath,
packageName, NewFileType.Form, entityModel);return formNewFile;
}
由代碼出可以看到,如果領域對象的結尾包含Model則去掉,然后在后面加一個后綴Form,然后分析Form文件要存儲的路徑為.form包下面,這里由於項目特殊的原因,我們的項目中的路徑領域對象一般在model包下面,而form對象一般在web包下面,所以后面進行了替換,將model替換為web.然后創建一個FormNewFile對象。
createAssemblerFile與createFormFile類似,也就不在具體講述了。
集成:
好了,對於領域對象的Form、Assembler文件的生成基本已經完成了,可以調用CodeGenerator.generateCode(javaPath)方法,傳入一個java文件的全路徑名進行測試一下,如果不出意外會生成對應的兩個文件,但是這樣肯定不是我們的最終目的,且前面特別講述了插件的創建方式,所以我們要把該項目集成到前面幾節創建的插件項目中去。
第一步,首先將本次創建的項目打包成jar包,導出。項目右鍵->Export->JAR file,
按照圖中的選好,且指定好存儲位置即可,然后一路確認下去,即可導出jar文件。
第二部:將導出的jar文件以及CodeGeneratorCURD項目依賴的jar包全部拷入插件項目,並添加到編譯路徑,然后在插件項目的配置文件中,在配置一下,如圖:
將所有的jar包添加進來
第三部,在彈出菜單的的操作方法中獲取當前選中文件的全路徑,並且調用生成代碼的方法。代碼如下:
public Object execute(ExecutionEvent event) throws ExecutionException {String path = getSelectPath();
if (null!=path) {
try {
CodeGenerator.generateCode(path);MessageDialog.openInformation(null,"生成代碼",
"生成代碼成功!!");
} catch (Exception e) {
e.printStackTrace();}}return null;
}private String getSelectPath(){IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();ISelection selection = page.getSelection();IStructuredSelection sel = (IStructuredSelection)selection;Object element = sel.getFirstElement();
IResource resources = null;if (element instanceof IResource) {
resources = (IResource) element;}if (!(element instanceof IAdaptable)) {
return null;
}IAdaptable adaptable = (IAdaptable) element;Object adapter = adaptable.getAdapter(IResource.class);resources = (IResource) adapter;String path = resources.getLocationURI().getPath();
if (null!=path) {
if (path.startsWith("/") && path.substring(2, 3).equals(":")) {path = path.substring(1);}}System.out.println(path);return path;
}
Example:
在插件項目中右鍵,run as Eclipse Application,運行成功后,新建一個web項目,並更改項目的編譯class文件的輸出路徑為:web/WEB-INF/classes路徑中,如下圖:
所有完成后,新建一個類,點擊生成代碼,生成后的目錄及代碼如下圖:
好了,寫的有點倉促,如果有不明白或不正確的地方歡迎指正。。。
附件鏈接地址:http://files.cnblogs.com/files/haifeng1990/TestPlugin.rar