文档章节

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

奋斗到天明
 奋斗到天明
发布于 2015/08/27 18:17
字数 1709
阅读 135
收藏 1
点赞 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
博文 112
码字总数 82707
作品 0
昌平
程序员
聊聊Servlet、Struts1、Struts2以及SpringMvc中的线程安全

前言 很多初学者,甚至是工作1-3年的小伙伴们都可能弄不明白?servlet Struts1 Struts2 springmvc 哪些是单例,哪些是多例,哪些是线程安全? 在谈这个话题之前,我们先了解一下Java中相关的...

小柒2012 ⋅ 04/13 ⋅ 0

spring data jpa 详解

本篇进行Spring-data-jpa的介绍,几乎涵盖该框架的所有方面,在日常的开发当中,基本上能满足所有需求。这里不讲解JPA和Spring-data-jpa单独使用,所有的内容都是在和Spring整合的环境中实现...

无敌小学僧 ⋅ 05/30 ⋅ 0

Spring mvc 创建完项目测试的时候前台404,后台没有错误信息这是咋了

前天遇到一个问题,springmvc创建的项目测试的时候前台404,后台没有错误输出,奇了怪了...... 这是WEB-INF下的xml文件

好吧我输了 ⋅ 06/19 ⋅ 0

jpa----spring-data-jpa 复杂查询,包括多表关联,分页,排序等

转载:由于此转载文章的出处也是转载的,所以原作者不详 本篇进行Spring-data-jpa的介绍,几乎涵盖该框架的所有方面,在日常的开发当中,基本上能满足所有需求。这里不讲解JPA和Spring-data...

u010775025 ⋅ 05/29 ⋅ 0

微服务架构之深入浅出SpringMVC

为开发团队选择一款优秀的MVC框架是件难事儿,在众多可行的方案中决择需要很高的经验和水平。你的一个决定会影响团队未来的几年。要考虑方面太多: 1、简单易用,以提高开发效率。使小部分的...

qq5acb179de594e ⋅ 04/14 ⋅ 0

Rocketmq支持用JmsTemplate发送消息吗

在Spring框架中使用JMS的JMS template同步发送消息,非常简单和强大,Activemq是标准的消息队列,其支持JMS template,现在我们用Rocketmq,据说很多没有遵循JMS规范,不知道能不能用JMS tem...

PMP4561705 ⋅ 05/14 ⋅ 0

缅甸银河国际开户13170533331

解决这个问题涉及到两个方面的问题: 配置问题 、引入静态文件问题 1、配置问题 web.xml配置的DispatchServlet如下: [html] view plain copy springmvc org.springframework.web.servlet.D...

银河国际 ⋅ 05/26 ⋅ 0

(一)SpringMVC之配置DispatcherServlet的一些坑

DispatcherServlet是SpringMVC的核心控制器,就像是SpringMVC的心脏,几乎所有的请求都会经过这个控制器,通过它,大大的降低了模块之间的耦合度。所有学SpringMVC的同学们第一步肯定都是先配...

Java攻城玩家 ⋅ 06/03 ⋅ 0

深入 Spring Boot :实现对 Fat Jar jsp 的支持

原文出处:Hengyunabc spring boot 对于jsp支持的限制 对于jsp的支持,Spring Boot官方只支持了war的打包方式,不支持fat jar。参考官方文档: https://docs.spring.io/spring-boot/docs/cu...

Hengyunabc ⋅ 06/04 ⋅ 0

后端发送数据到前端出现溢出问题

用户编辑某个页面时,后台会把一个大的实体(包含所有模块数据)返回到前端。经常出现以下报错,但是重新请求下可能就没有了。是不是因为数据量很大的缘故呢?信息如下: 09:55:04.448 [qtp6...

琴麻岛 ⋅ 04/27 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JavaScript零基础入门——(十一)JavaScript的DOM操作

JavaScript零基础入门——(十一)JavaScript的DOM操作 大家好,欢迎回到我们的JavaScript零基础入门。最近有些同学问我说,我讲的的比书上的精简不少。其实呢,我主要讲的是我在开发中经常会...

JandenMa ⋅ 22分钟前 ⋅ 0

volatile和synchronized的区别

volatile和synchronized的区别 在讲这个之前需要先了解下JMM(Java memory Model :java内存模型):并发过程中如何处理可见性、原子性、有序性的问题--建立JMM模型 详情请看:https://baike.b...

MarinJ_Shao ⋅ 49分钟前 ⋅ 0

深入分析Kubernetes Critical Pod(一)

Author: xidianwangtao@gmail.com 摘要:大家在部署Kubernetes集群AddOn组件的时候,经常会看到Annotation scheduler.alpha.kubernetes.io/critical-pod"="",以表示这是一个关键服务,那你知...

WaltonWang ⋅ 57分钟前 ⋅ 0

原子性 - synchronized关键词

原子性概念 原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。 原子性的实现方式 在jdk中,原子性的实现方式主要分为: synchronized:关键词,它依赖于JVM,保证了同...

dotleo ⋅ 今天 ⋅ 0

【2018.06.22学习笔记】【linux高级知识 14.4-15.3】

14.4 exportfs命令 14.5 NFS客户端问题 15.1 FTP介绍 15.2/15.3 使用vsftpd搭建ftp

lgsxp ⋅ 今天 ⋅ 0

JeeSite 4.0 功能权限管理基础(Shiro)

Shiro是Apache的一个开源框架,是一个权限管理的框架,实现用户认证、用户授权等。 只要有用户参与一般都要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户...

ThinkGem ⋅ 昨天 ⋅ 0

python f-string 字符串格式化

主要内容 从Python 3.6开始,f-string是格式化字符串的一种很好的新方法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在本文的最后,您将了解如何以及为什么今...

阿豪boy ⋅ 昨天 ⋅ 0

Python实现自动登录站点

如果我们想要实现自动登录,那么我们就需要能够驱动浏览器(比如谷歌浏览器)来实现操作,ChromeDriver 刚好能够帮助我们这一点(非谷歌浏览器的驱动有所不同)。 一、确认软件版本 首先我们...

blackfoxya ⋅ 昨天 ⋅ 0

线性回归原理和实现基本认识

一:介绍 定义:线性回归在假设特证满足线性关系,根据给定的训练数据训练一个模型,并用此模型进行预测。为了了解这个定义,我们先举个简单的例子;我们假设一个线性方程 Y=2x+1, x变量为商...

wangxuwei ⋅ 昨天 ⋅ 0

容器之查看minikue的environment——minikube的环境信息

执行如下命令 mjduan@mjduandeMacBook-Pro:~/Docker % minikube docker-envexport DOCKER_TLS_VERIFY="1"export DOCKER_HOST="tcp://192.168.99.100:2376"export DOCKER_CERT_PATH="/U......

汉斯-冯-拉特 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部