看前请参阅上一篇《新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 调用的。
原 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 调用的。