从Spring + Ejb + Struts2 迁移到Play 1.x 框架 之一
从Spring + Ejb + Struts2 迁移到Play 1.x 框架 之一
刀狂剑痴 发表于2年前
从Spring + Ejb + Struts2 迁移到Play 1.x 框架 之一
  • 发表于 2年前
  • 阅读 129
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

原来项目比较古老,前台是用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策略。 项目框架原来的层次: s3 

需要修改的层次:

  s4 

关于反序列化对象的方法,放弃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类达到目的。

共有 人打赏支持
粉丝 18
博文 111
码字总数 82582
×
刀狂剑痴
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: