原来项目比较古老,前台是用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类达到目的。