原來項目比較古老,前臺是用delphi,後臺有用Ejb作……這貨已經不多有人見過了……,如今公司主要項目都轉到play上,因此這個項目也重構。java
第一階段是將SSE 遷移到play,儘可能不改動代碼,只要能運行便可。 需求就是這樣,要作的工做很多,由於play是類Rails的框架,與傳統的SSH2不在一條線上。python
大體步驟以下: spring
第一步,去掉多餘的註解,包括spring的,Struts2的,EJB的。 apache
第二步,將原來用spring的注入的對象new出來。 json
第三步,將play的請求轉發到原來action層。 前兩步這裏不討論,重點在第三步。 數組
在請求-響應的實現模式上,play屬於參數-返回模式,而struts2屬於Pojo模式。前者將請求參數封裝在響應方法的參數列表中,後者將請求封裝在響應類的類變量中。原始http請求數據在play與struts2中傳輸就是一個大問題。由於咱們不能用play框架自動地將請求表單參數反序列化成對象。 如:mvc
student.id=1&student.name=zhangshan&student.friends[0].id=2&student.friends[0].name=lisi
通常轉換工做都是MVC框架幫咱們作的,若是沒有框架幫忙,會比較麻煩,我本來打算用ognl作這個工做的,可是後來發現有ognl不能初始化數組,致使數組越位問題,因此就放棄了。app
針對「不能用play框架自動地將請求表單參數反序列化成對象」,這個問題,先想到有三種策略: 框架
一、修改代碼,將struts2中action的請求參數分別移到對應的play 響應方法參數列表中,這樣就跳過那個問題。 函數
二、增長一層,增長一層play的 controller層,與原來struts2的acton類一一對應,並將action類做爲play controller層,這樣框架就能自動封裝action類對象。
三、動態代理,在appliaction中,增長一個方法,接收全部請求,經過其餘方法將請求表單參數反序列化成對象。
三個策略中,1的工做量太多,不適合需求,2的工做量不比1少多少,3的方法最適合現階段使用。因此最後採起了第3策略。 項目框架原來的層次:
須要修改的層次:
關於反序列化對象的方法,放棄ognl以後,終於在play框架中找到了相關方法,並且很簡單,略微修改就可拿來用。具體的方法步驟以下: 首先來一個簡單的action文件,掐頭去尾,只是爲了給你們展現用:
package com.lizhi.action; import org.apache.struts2.convention.annotation.Action; import org.apache.struts2.convention.annotation.Namespace; import org.apache.struts2.convention.annotation.ParentPackage; import org.apache.struts2.convention.annotation.Result; import org.apache.struts2.convention.annotation.Results; @ParentPackage(value="default") @Namespace(value = "/user") @Results(@Result(type = "json", name = "json")) public class UserAction { @InjectEJB private UserService userService; private User user; private String msg; private boolean flag; @Action(value = "/modify") public String modifyUser() { boolean result = userService.modify(user); if (result) { msg = "修改爲功!"; flag = true; } else { msg = "修改失敗!"; flag = false; } return "json"; } // ... 省略setter getter }
一、讀取struts2中的url 與 action類方法的映射關係。可採用java編寫,也可採用腳本。這是給出python腳本,適用於struts2的url是用註解配置,如是xml配置的則需自行編寫提取腳本。
#!/usr/bin/python # coding=utf-8 __author__ = 'Mark' import os import re # walk to each file def main(): rootDir = raw_input("please input a folder path:\n") if rootDir == "" or rootDir == '': rootDir = os.path.abspath('.') temp = "" + walk(rootDir); routePath = os.path.join(rootDir,"switch") if os.path.exists(routePath): os.remove(routePath) routeFile = open(routePath,"w") routeFile.write(temp) print "Converter Complete!" def walk(rootDir): temp = "" for lists in os.listdir(rootDir): srcPath = os.path.join(rootDir,lists) if os.path.isdir(srcPath): temp += walk(srcPath) elif srcPath.endswith('.java'): temp += process(srcPath) return temp #process one file def process(srcPath): actionName = os.path.split(srcPath)[1][0:-5] namespace = "" package = "" method = "" action = "" temp = "" f = open(srcPath, "r") isReadAtAction = False for line in f: #match the word if line.strip().lower().startswith("@namespace"): params = re.compile(r"\"").split(line) namespace = params[1] elif line.strip().lower().startswith("package"): package = line.strip()[7:-1].strip() elif line.strip().lower().startswith("@action"): params = re.compile(r"\"").split(line) action = params[1] isReadAtAction = True elif line.strip().lower().startswith("public") and isReadAtAction: p = re.compile(r'\s\S+\(') result = p.findall(line) if len(result) != 0: method = result[0][1:-1] temp += namespace + "/" + action + " " + package + "." + actionName + " "+ method + "\n" isReadAtAction = False f.close() return temp main()
腳本運行結果,就是掃描全部的文件,將action註解的url + 類的全名(報名)+ 方法 三個參數用空格隔開,整合到switch文件中。
二、保存第1步中的switch文件到conf下,經過play的job,在應用啓動的時候,構造映射對象ActionNode的Map,預加載到application中 儲存原來action與method的對象,module.ActionNode.java:
package module; public class ActionNode { public String controller; public String action; public ActionNode(String controller, String action) { super(); this.controller = controller; this.action = action; } }
job文件,job.RouteBuilder.java:
package job; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.HashMap; import java.util.Map; import module.ActionNode; import play.Logger; import play.jobs.Job; import play.jobs.OnApplicationStart; import play.vfs.VirtualFile; import controllers.Application; @OnApplicationStart public class RouteBuilder extends Job { public void doJob() throws Exception{ super.doJob(); Map<String, ActionNode> routes = new HashMap<String, ActionNode>(); VirtualFile vf = VirtualFile.fromRelativePath("/conf/switch"); File realFile = vf.getRealFile(); if(realFile == null){ throw new Exception("haven't switch file, cann't do builder!"); } BufferedReader br = new BufferedReader( new FileReader(realFile)); String line = null; while ((line = br.readLine()) != null) { String[] params = line.trim().split("\\s+"); routes.put(params[0], new ActionNode(params[1],params[2])); } br.close(); Application.routes = routes; Logger.log4j.info("load switch file successed!");; } }
三、在Application中添Swith方法,並配置url。在Swith方法中,利用play封裝函數參數的方法,封裝請求參數,並利用反射,調用action層的方法。 contollers.Application.java:
package controllers; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import module.ActionNode; import play.data.binding.Binder; import play.mvc.Controller; import play.mvc.Scope; import com.alibaba.fastjson.JSON; public class Application extends Controller { public static Map<String, ActionNode> routes; /** * 轉發play的請求到原sturts2的action層 * @param controllername action的類名 * @param actionname action的方法名 * @throws Exception */ public static void Switch(String controllername, String actionname) throws Exception { ActionNode actionNode = routes.get("/" + controllername + "/" + actionname); if(actionNode == null){ throw new Exception("no such url"); } Class controllerClass = Class.forName(actionNode.controller); HashMap<String, String[]> requstMap = rebuildParams(); //調用play包,反序列化requstMap成對象 Object result = Binder.bindInternal("action", controllerClass, controllerClass, new Annotation[]{} , requstMap, "", null); //反射調用 action類的方法 Method methodName = controllerClass.getMethod(actionNode.action,new Class[] {}); methodName.invoke(result, new Object[] {}); String jsonString = JSON.toJSONString(result); renderText(jsonString); } /** * 重構請求參數,在key中加入action.前綴,用來反序列化requstMap成對象(由於sturts2與play的請求響應模式的不對,致使傳入參數名不一樣) * @return */ private static HashMap<String, String[]> rebuildParams() { Map<String, String[]> all = Scope.Params.current().all(); HashMap<String, String[]> requstMap = new HashMap<String, String[]>(); Iterator<Entry<String, String[]>> it = all.entrySet().iterator(); while(it.hasNext()){ Entry<String, String[]> next = it.next(); requstMap.put("action." + next.getKey(), next.getValue()); } return requstMap; } public static void index() { render(); } }
conf文件: conf/switch
* /{controllername}/{actionname} Application.Switch
四、第3步,完成以後你會發現有報錯,由於Binder.bindInternal方法的修飾符是默認級別,即包級別。因此須要修改,這個地方能夠修改修飾符,也可重寫一個方法,引用bindInternal。完成以後,從新編譯play的jar包。 play.data.binding.Binder.java:
public static Object bindInternal(String name, Class clazz, Type type, Annotation[] annotations, Map<String, String[]> params, String suffix, String[] profiles)
這4步以後,就完成了SSE到Play第一階段簡單的遷移,程序可以跑起來了。固然這個項目前臺是delphi,沒有視圖層的選擇,全是json數據傳輸,若是是有視圖層,則能夠考慮擴充ActionNode類達到目的。