原来项目比较古老,前台是用delphi,后台有用Ejb做……这货已经很少有人见过了……,现在公司主要项目都转到play上,所以这个项目也重构。
第一阶段是将SSE 迁移到play,尽量不改动代码,只要能运行即可。 需求就是这样,要做的工作不少,因为play是类Rails的框架,与传统的SSH2不在一条线上。
大致步骤如下:
第一步,去掉多余的注解,包括spring的,Struts2的,EJB的。
第二步,将原来用spring的注入的对象new出来。
第三步,将play的请求转发到原来action层。 前两步这里不讨论,重点在第三步。
在请求-响应的实现模式上,play属于参数-返回模式,而struts2属于Pojo模式。前者将请求参数封装在响应方法的参数列表中,后者将请求封装在响应类的类变量中。原始http请求数据在play与struts2中传输就是一个大问题。因为我们不能用play框架自动地将请求表单参数反序列化成对象。 如:
student.id=1&student.name=zhangshan&student.friends[0].id=2&student.friends[0].name=lisi
一般转换工作都是MVC框架帮我们做的,如果没有框架帮忙,会比较麻烦,我原本打算用ognl做这个工作的,但是后来发现有ognl不能初始化数组,导致数组越位问题,所以就放弃了。
针对“不能用play框架自动地将请求表单参数反序列化成对象”,这个问题,先想到有三种策略:
1、修改代码,将struts2中action的请求参数分别移到对应的play 响应方法参数列表中,这样就跳过那个问题。
2、增加一层,增加一层play的 controller层,与原来struts2的acton类一一对应,并将action类作为play controller层,这样框架就能自动封装action类对象。
3、动态代理,在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
}
1、读取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文件中。
2、保存第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!");;
}
}
3、在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
4、第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类达到目的。
来源:oschina
链接:https://my.oschina.net/u/1386633/blog/498301