SSH开发新方案之Service、Action(Struts Model)层的变化

看前请参阅上一篇《新SSH开发架构之基于SSH架构的重新分层》,个人见解,欢迎各位看官交流指正。

    原
SSH 架构中 Service 层的功能是提供事务,并且在此调用 DAO 进行业务操作,并且对于完整的逻辑业务操作,另一部分逻辑代码混杂在 Action 类。对于 SSH 重新分层的方式,原 Action 的作用和 Service 层的代码将合并为一个 Service 层,并由 Spring 管理,这样便充分利用了 Spring 来进行 Hibernate 事务配置,所有的代码均在一个完整的事务内。

    可能有人又要问,你这样分层后,事务控制起来不是很不方便?我不知道是调用 save add ,还是 find 方法。对于这个问题来说,通过方法名来判断事务的方式确实会增加复杂度,但是可以采用变通的方法,改为通过类名来判断事物的方式。以前一个 Service 对应若干个业务,当前一个 Service 对应一个业务,并从命名上提供对事务的支持。看起来更加清晰,并且在团队开发中,由于各自编写各自的类,也极大的减少了版本控制冲突的发生。通过不同的命名规则,来对其 Service 来进行 AOP 操作。

以下是主要负责 Service 层的 Spring 配置文件 (service-config.xml) ,请注意红字部分
<? xml version = "1.0" encoding = "UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <aop:aspectj-autoproxy />

    <aop:config>
        <aop:advisor pointcut="execution(* ${app.aop.servicePackage}..Find*.service(..)) or execution(* ${app.aop.servicePackage}..Get*.service(..)) or execution(* ${app.aop.servicePackage}..View*.service(..)) or execution(* ${app.aop.servicePackage}..To*.service(..)) or execution(* ${app.aop.servicePackage}..List*.service(..)) or execution(* ${app.aop.servicePackage}..Read*.service(..))"
            advice-ref="rTxAdvice"/>
        <aop:advisor pointcut="execution(* ${app.aop.servicePackage}..Add*.service(..)) or execution(* ${app.aop.servicePackage}..Save*.service(..)) or execution(* ${app.aop.servicePackage}..Update*.service(..)) or execution(* ${app.aop.servicePackage}..Modify*.service(..)) or execution(* ${app.aop.servicePackage}..Del*.service(..)) or execution(* ${app.aop.servicePackage}..Do*.service(..)) or execution(* ${app.aop.servicePackage}..Set*.service(..)) or execution(* ${app.aop.servicePackage}..Write*.service(..))"
            advice-ref="txAdvice"/>
        <aop:aspect id="aroundServiceInitAdvice" ref="serviceInitAdvise">
            <aop:around method="around"
                     pointcut="execution(* ${app.aop.servicePackage}..*.service(..))"/>
        </aop:aspect>
    </aop:config>
    <bean id="serviceInitAdvise" class="com.aherp.framework.services.interceptors.ServiceInitAdvise"/>

    <bean id="businessBaseService" class="com.aherp.framework.services.BusinessBaseService" abstract="true">
        <description>business service
基类 </ description >
        <property name="messageRes" ref="messageResources"/>
        <property name="labelValueCodeDAO" ref="labelValueCodeDAOCache"/>
    </bean>

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>MessageResources</value>
                <value>SysMan</value>
                <value>Voice</value>
            </list>
        </property>
    </bean>

    <bean id="messageResources" class="com.aherp.framework.message.SpringMessageResources" scope="session">
        <description>
语言翻译类 </ description >
        <aop:scoped-proxy/>
    </bean>

</beans>

从以上配置可以看出,按照类型命名的规则,已经可以区分各,并且可在此基础上增加权限检测等的 Advise

以下是开发过程中,需要使用的业务 Service 的定义文件,以 (SysMan 系统管理系统为例,文件名为 WEB-INF/sysman/applicationContext/service-config.xml)
    < bean id = " sysman.appman.doSysLoginService " class = "com.aherp.appsys.sysman.services.appman.DoSysLoginService" parent = "businessBaseService" />

以下是 Service 层的 JAVA 代码,我的 IbusinessService 定义如下:
package com.aherp.framework.services.interfaces;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

/**
 *
基于Web应用的业务操作类接口.
 *@authorJimWu
 *
 */
publicinterface IBusinessService {

    /**
     *
基于资源码的权限管理,定义了该模块的资源码
     *@return
     */
    Integer getModuleId();

    /**
     *@paraminputDataMap
获取页面的数据传入
     *@paramrequestHttpServletRequest
     *@return
返回调用的数据
     *@throwsException
     */
    Object service(Map<String, String> inputDataMap, HttpServletRequest request) throws Exception;

    IMessageResources getMessageRes();
}

实现 IbusinessService 接口,以 DoSysLoginService.java 为例。

package com.aherp.appsys.sysman.services.appman;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.jstl.core.Config;

import com.aherp.appsys.sysman.model.sysman.SysAppRes;
import com.aherp.appsys.sysman.model.sysman.SysModuleRes;
import com.aherp.appsys.sysman.model.sysman.SysUser;
import com.aherp.appsys.sysman.services.mem.SysManService;
import com.aherp.framework.exception.BusinessException;
import com.aherp.framework.services.BusinessBaseService;
import com.aherp.framework.services.interfaces.IBusinessService;
import com.aherp.framework.util.Constants;
import com.aherp.util.OnLineCountListener;
import com.aherp.util.SessionUserInfo;
import com.aherp.util.sysman.ConstantCodes;

/**
 *
系统登陆模块.
 *
设置系统语言,检测重复登陆,单用户登陆,强制登陆
 *@authorJimWu
 *
 */
publicclass DoSysLoginService extends BusinessBaseService implements IBusinessService{

    @Override
    public Object service(Map<String, String> inputDataMap, HttpServletRequest request) throws Exception {
       
        //
设置语言
        String[] sysLangInfo = inputDataMap.get("sysLang").split(" ");
        Config.set(request.getSession(), Config.FMT_LOCALE, new Locale(sysLangInfo[0], sysLangInfo[1]));

        SysManService sysManService = (SysManService)getBean("sysManService");
        SysAppRes sysAppRes = sysManService.getSysAppRes(new Integer(inputDataMap.get("sysAppResId")));
        SysUser sysUser = sysManService.findSysUserBySysEntIdLoginNmUserPsw(new Integer(inputDataMap.get("sysEntId").toString()), inputDataMap.get("loginNm"), inputDataMap.get("userPsw"));
       
        if (sysUser == null)
            thrownew BusinessException("bizErr.login.pswError");

        //
检测重复登陆
        SessionUserInfo sessionUserInfo = OnLineCountListener.getUser(sysUser.getId());

        if(sessionUserInfo != null){ //
判断指定用户被其它人登陆
            if (!request.getSession().getId().equals(sessionUserInfo.getSessionId())){

                if("T".equals(inputDataMap.get("forceLogout"))) //
当前登陆user的session强行踢出
                    OnLineCountListener.inValidateSession(sessionUserInfo.getSessionId());
                else
                    thrownew BusinessException("bizErr.login.multiUser", new Object[]{sysUser.getUserNm()});
            }else {
                logger.info("sessionUserInfo exist: userId is" + sessionUserInfo.getSysUserId() + ",userNm is "
                        + sysUser.getUserNm() + ",SysEnt is " + sessionUserInfo.getSysEntId() + ",SysAppRes is " + sysAppRes.getAbbreviate());
            }
        }else{
            //
当用户后退返回重新登陆B用户时,保证让不使用的A用户释放掉让其它session可以登入,这样一个session才对应一个ID。
            SessionUserInfo loggedInSessionUserInfo = (SessionUserInfo) request.getSession().getAttribute(Constants.USER_KEY);
            if (loggedInSessionUserInfo != null) {
                System.out.println("logged out user .." + loggedInSessionUserInfo.getSysUserId());
                OnLineCountListener.removeUser(loggedInSessionUserInfo.getSysUserId()); //
断开用户
            }

            //
连接新帐户
            sessionUserInfo = new SessionUserInfo();
            sessionUserInfo.setSessionId(request.getSession().getId());
           
            sessionUserInfo.setSysUserId(sysUser.getId());
            sessionUserInfo.setSysAppResId(sysAppRes.getId());
            sessionUserInfo.setSysEntId(sysUser.getSysEnt().getId());

            //
设置session
            request.getSession().setAttribute(Constants.USER_KEY, sessionUserInfo);
            logger.info("user logged in: userId is " + sysUser.getId()
                    + ",sysAppResId is " + sysAppRes.getId() + ",sysEntId is "
                    + sysUser.getSysEnt().getId());
            OnLineCountListener.addUser(sessionUserInfo);
        }

        //
获取菜单树信息
        List<SysModuleRes> sysModuleResList = sysManService.findSysModuleResBySysUserIdSysAppResIdModuleProp(sessionUserInfo.getSysUserId(), sysAppRes.getId(), ConstantCodes.MODULE_PROP_CODE_MENUITEM);
        HashMap<String, Object> resultMap = new HashMap<String, Object>();

        resultMap.put("sysModuleResList", sysModuleResList);
        resultMap.put("sysFullName", sysAppRes.getFullName());

        //
设置返回地址
        request.setAttribute(Constants.USER_FORWARD_URL, "/pages/" + sysAppRes.getAbbreviate() + "/mainFrame.jsp");
        return resultMap;
    }

}


当写好并配置好一个 Service 后,接下来就可以供 Struts 调用了,实现 Struts 的支持过程如下:

所有的非 AJAX 提交的功能均通过一个 Struts Action 类调用。如下:
package com.aherp.framework.app.action;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.upload.FormFile;
import org.codehaus.xfire.util.Base64;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.aherp.framework.exception.BusinessException;
import com.aherp.framework.services.interfaces.IActionService;
import com.aherp.framework.services.interfaces.IMessageResources;
import com.aherp.framework.util.Constants;
import com.aherp.framework.util.SpringContextLoaderListener;

/**
 *
所有除publish以外的Action均继承该Action.
 *
当需要自定义返回地址时,需要在request设置Constants.USER_FORWARD_URL的String作为跳转地址
 *
 *@authorJimWu
 *
 */
publicclass BusinessAction extends Action {

    protectedstaticfinal Log logger = LogFactory.getLog(BusinessAction.class);
   
    public BusinessAction() {
        super();
    }

    @SuppressWarnings("unchecked")
    finalpublic ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        Map<String, String> paramMap = new HashMap<String, String>();

        Method[] Methods = form.getClass().getMethods();
        for(int i = 0; i < Methods.length; i ++){
            String methodName = Methods[i].getName();
            Class returnType = Methods[i].getReturnType();
            if(methodName.startsWith("get") && Methods[i].getParameterTypes().length == 0){
                String keyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
                if(returnType.equals(String.class))
                    paramMap.put(keyName, (String)Methods[i].invoke(form));
                else
                    if(returnType.equals(String[].class)){ //
处理多选框,用回车分割
                        String[] stringArray = (String[])Methods[i].invoke(form);
                        if(stringArray != null && stringArray.length > 0) {
                            StringBuilder arrayBuilder = new StringBuilder();
                                for(int n = 0; n < stringArray.length; n ++){
                                    if(n > 0)
                                        arrayBuilder.append('/n');
                                    arrayBuilder.append(stringArray[n]);
                                }
                            paramMap.put(keyName, arrayBuilder.toString());
                        }
                    }
                else
                    if (returnType.getClass().equals(FormFile.class)){
                        FormFile formFile = (FormFile)Methods[i].invoke(form);
                        paramMap.put(Constants.UPLOAD_FILE_NAME_KEY, formFile.getFileName());
                        paramMap.put(Constants.UPLOAD_FILE_SIZE_KEY, Integer.toString(formFile.getFileSize()));
                       paramMap.put(Constants.UPLOAD_FILE_DATA_KEY, Base64.encode(formFile.getFileData()));
                    }
            }
        }
        logger.debug("paramMap = " + paramMap);

        String convertStr = mapping.getPath().substring(1).replaceAll("/", ".");
        String actionBeanName = convertStr + "Service";
        IActionService actionService = (IActionService)WebApplicationContextUtils.getRequiredWebApplicationContext(servlet.getServletContext()).getBean(actionBeanName);

        if (!(Constants.TOKEN_NONE.equals(actionService.getTokenValidateFlag()) || isTokenValid(request))) {
            if (Constants.TOKEN_CHECK.equals(actionService.getTokenValidateFlag()))
                thrownew Exception("
重复提交" ); // 暂定如此
            else
                return mapping.findForward("success");
            // throw new BusinessException("error.submit.duplicate");
        }

        try{
            Object result = actionService.service((Map<String, String>)paramMap, request);
            BeanUtils.copyProperties(form, paramMap);
   
            request.setAttribute(Constants.RESULT_KEY, result);
   
            saveToken(request);
            String userForwardUrl = (String)request.getAttribute(Constants.USER_FORWARD_URL);
            if(userForwardUrl == null)
                return mapping.findForward("success");
            else
                returnnew ActionForward(userForwardUrl);
        }catch(BusinessException be){
            be.printStackTrace();
            IMessageResources iMessageResources = (IMessageResources)getBean("messageResources");
            iMessageResources.setSession(request.getSession());
            request.setAttribute(Constants.EXCEPTION_KEY, iMessageResources.getMessage(be.getCode(), be.getArgs()));
            returnnew ActionForward(mapping.getInput());
        }
    }

    protected Object getBean(String beanName) {
        return SpringContextLoaderListener.getContext().getBean(beanName);
    }
}


    至此,我想各位看官可能有人要发问了。为什么采用 HashMap 而不是 Object Bean 来传入页面表单信息呢?这不是更加违反 OO ?说起来,为 OO OO 似乎没有必要,一方便要保证开发的便捷性,另一方便个人认为只要不违反 JDK 的支持, HashMap 的使用也是可以的。持久化不也是出了个 ibatis 么?

    反过来,如果将 FormBean 转换为 HashMap ,直接使用 Bean ,在此是没有问题的。但是当前架构是要支持 Ajax WebService ,在 Bean 方式的处理上, WebService 对于非提供 java 平台的 Client 转换成 Bean 会很麻烦, Ajax 提交本身就是扁平化信息的,没必要非得组织成 Bean 。如果不考虑非 Java 平台的 WebService 使用,采用 Bean 也是另外一个选择方案,但是得注意一点:如果继承 Struts Form 导入 Service 处理, Service 层将会和 Struts 耦合,这是件很不好的做法。

    这样处理的话,还有一个难点,就是表单的 name 集合的形式,在 Struts 里对应的是字符数组,而 inputDataMap 却是 String ,因此将 name 集合转换成带回车的 String ,一般不会有人将回车放入表单 value ,当然,也可以考虑其它不可直接输入的字符例如 Tab”/t” 来分割。

所提供的 FormBean 如下,还是常见的 FormBean 格式:
package com.aherp.appsys.sysman.app.forms;

import com.aherp.framework.app.forms.BaseForm;

publicclass LoginForm extends BaseForm {

    String sysAppResId;
   
    String sysEntId;
   
    String loginNm;
   
    String userPsw;
   
    String sysLang;
   
    String forceLogout;

    public String getSysAppResId() {
        returnsysAppResId;
    }

    publicvoid setSysAppResId(String sysAppResId) {
        this.sysAppResId = sysAppResId;
    }

    public String getSysEntId() {
        returnsysEntId;
    }

    publicvoid setSysEntId(String sysEntId) {
        this.sysEntId = sysEntId;
    }

    public String getSysLang() {
        returnsysLang;
    }

    publicvoid setSysLang(String sysLang) {
        this.sysLang = sysLang;
    }

    public String getLoginNm() {
        returnloginNm;
    }

    publicvoid setLoginNm(String loginNm) {
        this.loginNm = loginNm;
    }

    public String getUserPsw() {
        returnuserPsw;
    }

    publicvoid setUserPsw(String userPsw) {
        this.userPsw = userPsw;
    }

    public String getForceLogout() {
        returnforceLogout;
    }

    publicvoid setForceLogout(String forceLogout) {
        this.forceLogout = forceLogout;
    }
   
}

然后是 struts 配置 WEB-INF/sysman/strutsConfig/forms.xml
<? xml version = "1.0" encoding = "utf-8" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
          "http://struts.apache.org/dtds/struts-config_1_3.dtd">
<struts-config>
    <form-beans>
        <form-bean name="LoginForm"
            type="com.aherp.appsys.sysman.app.forms.LoginForm" />
    </form-beans>

    <global-forwards>
    </global-forwards>

    <action-mappings>
        <action path="/sysman/appman/toSysLogin"
            type="com.aherp.framework.app.action.BusinessAction" name="BaseForm"
            scope="request" input="/pages/sysman/appman/login.jsp">
            <forward name="success" path="/pages/sysman/appman/login.jsp" />
        </action>

        <action path="/sysman/appman/doSysLogin"
            type="com.aherp.framework.app.action.BusinessAction" name="LoginForm"
            scope="request" input="/sysman/appman/toSysLogin.do">
            <forward name="success" path="/sysman/appman/toSysLogin.do" />
        </action>
    </action-mappings>
</struts-config>

可见,所有非 AJAX 方式的提交,只要是非数据流方式返回的,都采用 BusinessAction 来调用。

    估计在此又有人疑问了,只定义 path form name forward ,怎样知道调用的是哪个 Service 呢,细心的人在前面可能看到这样红色的代码:
< bean id = " sysman.appman.doSysLoginService "
......
......

        String convertStr = mapping.getPath().substring(1).replaceAll("/", ".");
        String actionBeanName = convertStr + "Service";
        IActionService actionService = (IActionService)WebApplicationContextUtils.getRequiredWebApplicationContext( servlet .getServletContext()). getBean(actionBeanName) ;

由此可见控制方式是通过对action path的解析 ,来作为 Spring getBean 的参数,拿到对应的 Service Bean 调用的。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值