從Spring + Ejb + Struts2 遷移到Play 1.x 框架 之一

原來項目比較古老,前臺是用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策略。 項目框架原來的層次: 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
}


一、讀取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類達到目的。

相關文章
相關標籤/搜索