文档章节

从Spring + Ejb + Struts2 迁移到Play 1.x 框架 之一

奋斗到天明
 奋斗到天明
发布于 2015/08/27 18:17
字数 1709
阅读 159
收藏 1

「深度学习福利」大神带你进阶工程师,立即查看>>>

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

奋斗到天明
粉丝 19
博文 112
码字总数 82707
作品 0
昌平
程序员
私信 提问
加载中
请先登录后再评论。
访问安全控制解决方案

本文是《轻量级 Java Web 框架架构设计》的系列博文。 今天想和大家简单的分享一下,在 Smart 中是如何做到访问安全控制的。也就是说,当没有登录或 Session 过期时所做的操作,会自动退回到...

黄勇
2013/11/03
3.6K
8
我的架构演化笔记 功能1: 基本的用户注册

“咚咚”,一阵急促的敲门声, 我从睡梦中惊醒,我靠,这才几点,谁这么早, 开门一看,原来我的小表弟放暑假了,来南京玩,顺便说跟我后面学习一个网站是怎么做出来的。 于是有了下面的一段...

强子哥哥
2014/05/31
976
3
极速博客引擎--Gor

gor 是使用 golang 实现的类Ruhoh静态博客引擎(Ruhoh like),基本兼容ruhoh 1.x规范. 相当于与ruhoh的官方实现(ruby实现), 有以下优点: 速度完胜 -- 编译wendal.net近200篇博客,仅需要1秒 安装...

wendal
2013/01/20
3.9K
0
TDD的测试框架--Machine.Specification

Machine.Specification 是一个 TDD 测试驱动开发的测试框架,简化了测试,无需关心语言本身特性。 Machine.Specifications 带来的好处是不需要在代码里有注释,但同时阅读代码的人可以一目了...

匿名
2013/01/22
1.2K
0
mvc框架--Razor

Razor 是一个轻巧而优雅的servlet mvc框架 # 又一个轮子? no,写就她是为了证实我个人的某些想法,并在这个过程中练练手,这两种冲动碰撞在一起,自然而然地产生了Razor # Razor的现在和未来...

dtubest
2013/01/25
3.1K
0

没有更多内容

加载失败,请刷新页面

加载更多

使用旁路输出(side output)来拆分和复制流

  我们在处理数据的时候,有时候想对不同情况的数据进行不同的处理,那么就需要把流进行拆分或者复制。 如果是使用filter来进行拆分,也能满足我们的需求,但每次筛选都要保留整个流,然后...

osc_ct0tt1cu
32分钟前
0
0
Azure AD 与 AWS IAM 集成实现SSO—上(Azure部分)

整体的架构和流程是下面这个样子: Azure部分: 登录Azure的portal通过Azure Active Directory创建一个测试用户: 返回Azure Active Directory 创建新的应用程序 Amazon Web Service 搜索 Am...

osc_bgs3qxk5
34分钟前
0
0
Azure AD 与 AWS IAM 集成实现SSO—下(AWS部分)

再回顾一下架构,我们都要做什么,别乱: 看来在AWS上做的工作要多一些。 登录AWS控制台: 进入到IAM这个服务: 在配置提供程序中,选择SAML,提供商名称自定义,比如WAAD,将刚才下载的元数...

osc_ibuoui1c
35分钟前
0
0
AWS DevOps 通过Config自动审计Security Group配置——上篇

这个实验的一个场景是,运维同事设计安全组Security Group的时候,打开了除了HTTP和HTTPS的入口访问权限。其他协议或端口如果打开,除了审计不通过的同时,会自动触发一个函数将它修改成我们...

osc_l330x9u1
36分钟前
3
0
AWS DevOps 通过Config自动审计Security Group配置——下篇

再讲一下背景, 这个实验的一个场景是,运维同事设计安全组Security Group的时候,打开了除了HTTP和HTTPS的入口访问权限。其他协议或端口如果打开,除了审计不通过的同时,会自动触发一个函数...

osc_cudh2wh2
37分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部