其实大家在工作的时候,一般很少涉及框架大改的情况,除非是一个值得重视与长期发展的项目,碰巧我遇到了一个比较重要的项目,由于项目的初次搭建的时候欠缺很多方法的考虑,使用了比较旧的架构:Spring+Struts2+Hibernate+MyBatis,这个框架也有一些优点,比如Hibernate+MyBatis可以同时使用,而且还共用了数据源,也不影响事务的切面。由于Spring的发展比Struts2的发展快以及各项优点(此处不多说),加上SpringBoot、SpringCloud的出现,大多数程序员都倾向于使用SpringMVC而不是Struts2MVC,因此,考虑到公司大项目的发展,项目计划第一步由Struts2MVC转换成SpringMVC(Spring版本不变,还是3版本),第二步升级Spring版本(由版本3升级版本4,尽量做到无配置文件),第三步转为SpringBoot项目或者SpringCloud微服务(模块分割),此处由有多年架构方面经验的我来完成项目计划的第一步(由Struts2MVC转换成SpringMVC)
1、修改pom文件:
去掉struts2的相关依赖:比如:struts2-core、struts2-convention-plugin、struts2-junit-plugin、struts2-json-plugin、struts2-spring-plugin等等包含struts2的依赖包
由于我的项目有appfuse-web的依赖,所以不需要添加spring-webmvc的依赖,如果你的项目没有SpringMVC相关依赖,请添加
2、修改web.xml文件:
(1)去掉Struts2的filter和filter-mapping以及删除struts.xml文件(建议备份)
<filter>
<filter-name>struts</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>config</param-name>
<!-- 配置装载struts.xml路径 -->
<param-value>struts-default.xml,struts-plugin.xml,conf/struts/struts.xml</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>struts</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
(2)加上springMVC的servlet和servlet-mapping(需要注意url-pattern的值,文章下面有其他说明)
<!-- Spring MVC 核心控制器 DispatcherServlet 配置 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3、在classpath:conf/spring/下面添加spring-mvc.xml(Json转换器暂时使用Jackson,而MyExceptionMappingInterceptor是本项目的全局异常类,PrivilegeInterceptor是本项目的拦截器)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描controller(controller层注入) -->
<context:component-scan base-package="com.test.cn.**.action"/>
<!-- 解决使用@ResponseBody乱码,Spring版本问题 -->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!--避免IE执行AJAX时,返回JSON出现下载文件 -->
<bean id="mappingJacksonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
<!-- 启动SpringMVC的注解功能,完成请求和注解POJO的映射 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="mappingJacksonHttpMessageConverter" /> <!-- JSON转换器 -->
</list>
</property>
</bean>
<!--对静态资源文件的访问-->
<mvc:resources mapping="/lib/**" location="/lib/" />
<!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->
<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置springMVC处理上传文件的信息 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"/>
<property name="maxUploadSize" value="1073741824"/><!-- 最大上传1G -->
<property name="maxInMemorySize" value="40960"/>
</bean>
<bean class="com.test.cn.core.web.interceptor.MyExceptionMappingInterceptor" />
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.test.cn.core.web.interceptor.PrivilegeInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.test.cn.core.web.interceptor.LogInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
</beans>
4、由于Controller注解在上面的xml已扫描,所以在applicationContext-service.xml去掉Controller注解的扫描(这个xml文件原有事务管理器与aop切面等配置,根据自己项目寻找自己的xml文件)【注:至于Controller注解扫描为什么与其他注解分开,这个问题请自行去了解一下SpringMVC的启动原理】
修改前:
<!--开启包注解扫描-->
<context:component-scan base-package="com.test">
</context:component-scan>
修改后:
<!--开启包注解扫描-->
<context:component-scan base-package="com.test">
<!--结合springmvc -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
5、拦截器调整
(1)调整全局异常拦截器MyExceptionMappingInterceptor:由继承Struts2的ExceptionMappingInterceptor换成继承SpringMVC的ExceptionHandlerExceptionResolver,由于继承类不同,方法也不同,返回类型不同,稍微理解一下进行了更改如下:
public class MyExceptionMappingInterceptor extends ExceptionMappingInterceptor {
private static final long serialVersionUID = -1L;
private static final Logger logger = LoggerFactory.getLogger(MyExceptionMappingInterceptor.class);
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String result = null;
try {
result = invocation.invoke();
} catch (GeneralException ex) {
result = handleException(ex, invocation);
} catch (UnauthorizedAccessException ex) {
result = handleException(ex, invocation);
} catch (BusinessException ex) {
result = handleException(ex, invocation);
} catch (IncorrectResultSizeDataAccessException ex) {
result = handleException(new BusinessException(ex, "数据库查询唯一记录时返回了2条及以上记录!"), invocation);
} catch (DataAccessException ex) {
result = handleException(new BusinessException(ex, "数据库操作失败!"), invocation);
} catch (NullPointerException ex) {
result = handleException(new BusinessException(ex, "调用了未经初始化的对象或者是不存在的对象!"), invocation);
} catch (IOException ex) {
result = handleException(new BusinessException(ex, "IO异常!"), invocation);
} catch (ClassNotFoundException ex) {
result = handleException(new BusinessException(ex, "指定的类不存在!"), invocation);
} catch (ArithmeticException ex) {
result = handleException(new BusinessException(ex, "数学运算异常!"), invocation);
} catch (ArrayIndexOutOfBoundsException ex) {
result = handleException(new BusinessException(ex, "数组下标越界!"), invocation);
} catch (IllegalArgumentException ex) {
result = handleException(new BusinessException(ex, "方法的参数错误!"), invocation);
} catch (ClassCastException ex) {
result = handleException(new BusinessException(ex, "类型强制转换错误!"), invocation);
} catch (SecurityException ex) {
result = handleException(new BusinessException(ex, "违背安全原则异常!"), invocation);
} catch (SQLException ex) {
result = handleException(new BusinessException(ex, "SQL语句出错!"), invocation);
// } catch (QuerySyntaxException ex) {
// result = handleException(new BusinessException(ex, "HQL语句出错!"), invocation);
} catch (NoSuchMethodError ex) {
result = handleException(new BusinessException(ex, "请求的方法末找到!"), invocation);
} catch (NoSuchMethodException ex) {
result = handleException(new BusinessException(ex, "请求的方法末找到!"), invocation);
} catch (InternalError ex) {
result = handleException(new BusinessException(ex, "Java虚拟机发生了内部错误!"), invocation);
} catch (Exception ex) {
result = handleException(new BusinessException(ex, "程序内部错误,操作失败!"), invocation);
}
return result;
}
/**
* 处理异常并路由到需要的页面
* 与struts.xml中的global-exception-mappings配置匹配
* @param e
* 抛出的异常
* @param invocation
* 上下文环境
* @return
*/
private String handleException(RuntimeException e, ActionInvocation invocation) {
ActionContext ctx = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest)ctx.get(ServletActionContext.HTTP_REQUEST);
HttpServletResponse response = (HttpServletResponse)ctx.get(ServletActionContext.HTTP_RESPONSE);
// 记录错误日志
writeErrorLog(e, invocation);
// 统一处理下载请求的异常
String contentType = response.getContentType();
if(StringUtils.equalsIgnoreCase(contentType, "application/x-download")){
response.setCharacterEncoding("utf-8");
PrintWriter out = null;
try {
out = response.getWriter();
} catch (IOException e1) {
logger.error(e1.getMessage(), e1);
}
out.print("<script language='javascript'>alert('" + e.getMessage() + "');</script>");
out.close();
}
// ajax请求异常,则返回友好提示
String type = request.getHeader("X-Requested-With");
if ("XMLHttpRequest".equalsIgnoreCase(type)) {
ActionResult actionResult = new ActionResult(true);
actionResult.setErrorMsg(e.getMessage(), e);
AjaxUtil.renderJSON(response, JsonUtil.bean2json(actionResult));
return null;
}else{
handleLogging(e);
List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
// 根据异常的类型查找对应的路由
ExceptionMappingConfig mappedResult = this.findMappingFromExceptions(exceptionMappings, e);
if (mappedResult != null) {
String result = mappedResult.getResult();
publishException(invocation, new ExceptionHolder(e));
return result;
} else {
throw e;
}
}
}
/**
* 记录错误日志
* @param e
* @param invocation
*/
private void writeErrorLog(RuntimeException e, ActionInvocation invocation) {
try {
ActionProxy proxy = invocation.getProxy();
String module = StringUtils.replace(proxy.getNamespace(), "/", "");
String subModule = proxy.getActionName();
// 请求参数
Map<String, Object> map = invocation.getInvocationContext().getParameters();
String message = "提交的参数:" + AppUtils.getRequestParameterStr(map);
SysLoginUser loginUser = AppUtils.getSysLoginUser();
String userId = loginUser != null ? loginUser.getSurId() : "";
ErrorLogWriter.doLogging(message, e, module, subModule, userId);
}catch (Exception ex){
logger.error("写错误日志异常:" + e.getMessage(), ex);
}
}
}
修改后:
public class MyExceptionMappingInterceptor extends ExceptionHandlerExceptionResolver {
private static final long serialVersionUID = -1L;
private static final Logger logger = LoggerFactory.getLogger(MyExceptionMappingInterceptor.class);
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o, Exception e) {
RuntimeException runtimeException = null;
if (e instanceof GeneralException) {
runtimeException = (RuntimeException) e;
} else if (e instanceof UnauthorizedAccessException) {
runtimeException = (RuntimeException) e;
} else if (e instanceof BusinessException) {
runtimeException = (RuntimeException) e;
} else if (e instanceof IncorrectResultSizeDataAccessException) {
runtimeException = new BusinessException(e, "数据库查询唯一记录时返回了2条及以上记录!");
} else if (e instanceof DataAccessException) {
runtimeException = new BusinessException(e, "数据库操作失败!");
} else if (e instanceof NullPointerException) {
runtimeException = new BusinessException(e, "调用了未经初始化的对象或者是不存在的对象!");
} else if (e instanceof IOException) {
runtimeException = new BusinessException(e, "IO异常!");
} else if (e instanceof ClassNotFoundException) {
runtimeException = new BusinessException(e, "指定的类不存在!");
} else if (e instanceof ArithmeticException) {
runtimeException = new BusinessException(e, "数学运算异常!");
} else if (e instanceof ArrayIndexOutOfBoundsException) {
runtimeException = new BusinessException(e, "数组下标越界!");
} else if (e instanceof IllegalArgumentException) {
runtimeException = new BusinessException(e, "方法的参数错误!");
} else if (e instanceof ClassCastException) {
runtimeException = new BusinessException(e, "类型强制转换错误!");
} else if (e instanceof SecurityException) {
runtimeException = new BusinessException(e, "违背安全原则异常!");
} else if (e instanceof SQLException) {
runtimeException = new BusinessException(e, "SQL语句出错!");
// } else if (e instanceof NoSuchMethodError) {
} else if (e instanceof NoSuchMethodException) {
runtimeException = new BusinessException(e, "请求的方法末找到!");
// } else if (e instanceof InternalError) {
} else if (e instanceof InternalException) {
runtimeException = new BusinessException(e, "Java虚拟机发生了内部错误!");
} else if (e instanceof Exception) {
runtimeException = new BusinessException(e, "程序内部错误,操作失败!");
}
return handleException(runtimeException, httpServletRequest, httpServletResponse, o);
}
/**
* 处理异常并路由到需要的页面
*
* @param e 抛出的异常
* @param httpServletRequest
* @param httpServletResponse @return
* @param o
*/
private ModelAndView handleException(RuntimeException e, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {
// 记录错误日志
writeErrorLog(e, httpServletRequest, httpServletResponse, o);
// 统一处理下载请求的异常
String contentType = httpServletResponse.getContentType();
if (StringUtils.equalsIgnoreCase(contentType, "application/x-download")) {
httpServletResponse.setCharacterEncoding("utf-8");
PrintWriter out = null;
try {
out = httpServletResponse.getWriter();
} catch (IOException e1) {
logger.error(e1.getMessage(), e1);
}
out.print("<script language='javascript'>alert('" + e.getMessage() + "');</script>");
out.close();
}
ModelAndView modelAndView = null;
// ajax请求异常,则返回友好提示
String type = httpServletRequest.getHeader("X-Requested-With");
if ("XMLHttpRequest".equalsIgnoreCase(type)) {
ActionResult actionResult = new ActionResult(true);
actionResult.setErrorMsg(e.getMessage(), e);
AjaxUtil.renderJSON(httpServletResponse, JsonUtil.bean2json(actionResult));
} else {
if (e instanceof GeneralException) {
modelAndView = new ModelAndView("/error.jsp");
} else if (e instanceof UnauthorizedAccessException) {
modelAndView = new ModelAndView("/unauth.jsp");
} else if (e instanceof BusinessException) {
modelAndView = new ModelAndView("/error.jsp");
}
}
return modelAndView;
}
/**
* 记录错误日志
*
* @param e
* @param httpServletRequest
* @param httpServletResponse
* @param o
*/
private void writeErrorLog(RuntimeException e, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {
try {
Object handler = null;
if (o instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) o;
handler = handlerMethod.getBean();
}
// String module = StringUtils.replace(proxy.getNamespace(), "/", "");
StringBuffer url = httpServletRequest.getRequestURL();
String module = url.delete(url.length() - httpServletRequest.getRequestURI().length(), url.length()).append("/").toString();
String subModule = handler.getClass().getName();
// 请求参数
Map<String, Object> map = httpServletRequest.getParameterMap();
String message = "提交的参数:" + AppUtils.getRequestParameterStr(map);
SysLoginUser loginUser = AppUtils.getSysLoginUser();
String userId = loginUser != null ? loginUser.getSurId() : "";
ErrorLogWriter.doLogging(message, e, module, subModule, userId);
} catch (Exception ex) {
logger.error("写错误日志异常:" + e.getMessage(), ex);
}
}
}
(2)调整日志拦截器LogInterceptor:由继承AbstractInterceptor改为实现HandlerInterceptor接口
需要注意的是: AbstractInterceptor的intercept方法里面的result = invocation.invoke();这句代码为分割点,分割点前的代码放进HandlerInterceptor的preHandle方法里面,而分割点后的代码放进postHandle方法里面,至于try catch分割点的代码只能迁移到全局异常类里操作(此处省略掉),至于为什么要这样改,我也不多说了,请了解一下Struts2的拦截器和Spring的拦截器
修改前:
public class LogInterceptor extends ExceptionMappingInterceptor {
private static final long serialVersionUID = -5260452584263751123L;
private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
private static final String ENTER = "->>>开始调用: ";
private static final String EXIT = "<<<-结束调用: ";
private final static String USER_NAME = "userName";
private final static String THREADINFO = "ThreadInfo";
private ThreadLocal<Integer> depth = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public String intercept(ActionInvocation invocation) throws Exception {
long startTime = System.currentTimeMillis();
Map<String, Object> session = invocation.getInvocationContext().getSession();
if (session != null) {
SysLoginUser loginUser = (SysLoginUser) session.get(Constants.SYSLOGINUSER_IN_SESSION);
if (loginUser != null) {
Thread current = Thread.currentThread();
MDC.put(USER_NAME, loginUser.getSurName());
MDC.put(THREADINFO, "ThreadID:" + current.getId());
}
}
//构建StringBuffer
ActionProxy proxy = invocation.getProxy();
String method = proxy.getMethod();
StringBuilder sb = new StringBuilder();
int d = depth.get();
for (int i = 0; i < d; i++) {
sb.append(' ');
}
sb.append(ENTER);
sb.append(proxy.getActionName());
//可以打印请求参数(登陆请求不打印参数)
Map<String, Object> map = invocation.getInvocationContext().getParameters();
//参数非空则打印参数
if (map.size() != 0) {
sb.append('(');
for (String key : map.keySet()) {
if (!"sfUser.sfuNameSpelling".equals(key)) { // 不打印协同的token
sb.append('[');
sb.append(key);
sb.append('=');
sb.append(((String[]) map.get(key))[0].toString());
sb.append("]");
}
}
sb.append(')');
}
//在方法调用之前打印方法名
logger.info(sb.toString());
depth.set(++d);
String result = "";
try {
//继续调用拦截器
result = invocation.invoke();
} catch (Throwable t) {
depth.set(--d);
sb.replace(d, d + ENTER.length(), EXIT);
logger.info(sb.toString());
throw t;
}
depth.set(--d);
//替换-> 为 <-
sb.replace(d, d + ENTER.length(), EXIT);
//在方法调用之后打印方法名
logger.info(sb.toString());
//在方法调用之后打印该请求的花费时间
logger.info("[方法:" + proxy.getActionName() + "() 调用时间] = " + (System.currentTimeMillis() - startTime) + " 毫秒!");
//清除用户名
MDC.remove(USER_NAME);
return result;
}
}
修改后:
public class LogInterceptor implements HandlerInterceptor {
private static final long serialVersionUID = -5260452584263751123L;
private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
private static final String ENTER = "->>>开始调用: ";
private static final String EXIT = "<<<-结束调用: ";
private final static String USER_NAME = "userName";
private final static String THREADINFO = "ThreadInfo";
private ThreadLocal<Long> startTime = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return System.currentTimeMillis();
}
};
private ThreadLocal<Integer> depth = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
private ThreadLocal<String> actionName = new ThreadLocal<String>();
private ThreadLocal<StringBuilder> stringBuilder = new ThreadLocal<StringBuilder>(){
@Override
protected StringBuilder initialValue() {
return new StringBuilder();
}
};
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
if (o instanceof HandlerMethod) {
HandlerMethod h = (HandlerMethod) o;
actionName.set(h.getBean().getClass().getName());
}
HttpSession session = httpServletRequest.getSession();
if (session != null) {
SysLoginUser loginUser = (SysLoginUser) session.getAttribute(Constants.SYSLOGINUSER_IN_SESSION);
if (loginUser != null) {
Thread current = Thread.currentThread();
MDC.put(USER_NAME, loginUser.getSurName());
MDC.put(THREADINFO, "ThreadID:" + current.getId());
}
}
//构建StringBuffer
StringBuilder sb = stringBuilder.get();
int d = depth.get();
for (int i = 0; i < d; i++) {
sb.append(' ');
}
sb.append(ENTER);
sb.append(actionName.get());
//可以打印请求参数(登陆请求不打印参数)
Map<String, Object> map = httpServletRequest.getParameterMap();
//参数非空则打印参数
if (map.size() != 0) {
sb.append('(');
for (String key : map.keySet()) {
if (!"sfUser.sfuNameSpelling".equals(key)) { // 不打印协同的token
sb.append('[');
sb.append(key);
sb.append('=');
sb.append(((String[]) map.get(key))[0].toString());
sb.append("]");
}
}
sb.append(')');
}
//在方法调用之前打印方法名
logger.info(sb.toString());
depth.set(++d);
stringBuilder.set(sb);
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
int d = depth.get();
depth.set(--d);
StringBuilder sb = stringBuilder.get();
//替换-> 为 <-
sb.replace(d, d + ENTER.length(), EXIT);
stringBuilder.set(sb);
//在方法调用之后打印方法名
logger.info(sb.toString());
//在方法调用之后打印该请求的花费时间
logger.info("[方法:" + actionName.get() + "() 调用时间] = " + (System.currentTimeMillis() - startTime.get()) + " 毫秒!");
//清除用户名
MDC.remove(USER_NAME);
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
(3)同理调整权限拦截器PrivilegeInterceptor(这个代码比较简单,可不看):由继承AbstractInterceptor改为实现HandlerInterceptor接口
修改前:(只显示部分代码)
public class PrivilegeInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 8887312034961459123L;
private static final Logger logger = LoggerFactory.getLogger(PrivilegeInterceptor.class);
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String result = "";
try {
ActionContext ctx = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);
SysLoginUser loginUser = (SysLoginUser) request.getSession().getAttribute(Constants.SYSLOGINUSER_IN_SESSION);
if (loginUser != null) {
// 检查权限,没有权限就抛异常
checkPrivilege(request, loginUser);
}
// 继续调用拦截器
result = invocation.invoke();
} catch (Throwable t) {
throw t;
}
return result;
}
}
修改后:
public class PrivilegeInterceptor implements HandlerInterceptor {
private static final long serialVersionUID = 8887312034961459123L;
private static final Logger logger = LoggerFactory.getLogger(PrivilegeInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
try {
SysLoginUser loginUser = (SysLoginUser) httpServletRequest.getSession().getAttribute(Constants.SYSLOGINUSER_IN_SESSION);
if (loginUser != null) {
// 检查权限,没有权限就抛异常
checkPrivilege(httpServletRequest, loginUser);
}
} catch (Throwable t) {
throw t;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
6、由于本项目的过滤器都是使用javax.servlet.Filter,所以不需要更改filter代码
7、全局搜索ServletActionContext(这是Struts的上下文)
本项目把ServletActionContext.getResponse()替换成
((ServletWebRequest) RequestContextHolder.getRequestAttributes()).getResponse()
把ServletActionContext.getRequest()替换成
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
8、全局搜索sessionMap.get替换成sessionMap.getAttribute
9、把Struts2接收附件的MultiPartRequestWrapper替换成Spring的MultipartHttpServletRequest,替换后使用的语法不同,此处不粘贴详细代码了(上传附件建议在controller的方法里面加上参数@RequestParam("upfile") MultipartFile[] files来接收文件,本项目用ajaxfileupload.js插件上传,框架换了后这个js也有小调整)
10、页面jsp调整:去掉<%@ taglib prefix="s" uri="/struts-tags"%>以及调整s开头的标签如下:
(1)<s:form替换成<form(</s:form>同理)
(2)<s:hidden name="aaa"/> 替换成<input type="hidden" name="aaa" value="${aaa}"/>
(3)<s:textarea替换成<textarea(</s:textarea>同理)
(4)<s:property value="aaa"/>替换成${aaa}
(5)<s:property value="#session.aaa"/>替换成${sessionScope.aaa}
(6)cssStyle替换成style
11、那么重点来了,最痛苦的就是所有的url请求地址的更换,对于一般的小项目来说直接替换url,但是本项目的代码太多了,于是,找到了一个很好的办法,便是使用了urlrewrite实现url重写,把.do为后缀的url进行重写,这样就不用更换所有url了
(1)在web.xml添加filter和filter-mapping,注意filter的顺序,在该filter之前的filter处理的url还是有.do的,之后的filter则是被转换后的url
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<init-param>
<param-name>logLevel</param-name>
<param-value>WARN</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>*.do</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
(2)在WEB-INF下添加urlrewrite.xml(请根据自身项目调整from和to的匹配)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.2//EN" "\\urlrewrite3.2.dtd">
<urlrewrite>
<rule>
<note>forward(转发模式)传参</note>
<note>example:/a/b_c.do->/a/b/c</note>
<from>/([A-Za-z0-9]+)/([A-Za-z0-9]+)_([A-Za-z0-9]+).do</from>
<to type="forward">/$1/$2/$3</to>
</rule>
</urlrewrite>
12、url调整好了,就准备开始改另一个重点,Controller层(或Action层)的代码调整
为了方便代码的更改,我自己写了一个工具类,获取获取controller类以及方法的RequestMapping注解的值
public class BaseActionUtil {
/**
* 获取controller的RequestMapping注解的值
*
* @return
*/
public static String getActionRequestMapping(Class clazz) {
String result = "";
RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
if (requestMapping != null) {
String[] requestMappingValue = requestMapping.value();
if (requestMappingValue.length > 0) {
result = requestMappingValue[0];
}
}
return result;
}
/**
* 获取controller下的方法的RequestMapping注解的值
*
* @return
*/
public static Map<String, String> getMethodRequestMapping(Class clazz) {
Map<String, String> methodRequestMappingValue = new HashMap<String, String>();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
if (requestMapping != null) {
String[] requestMappingValue = requestMapping.value();
if (requestMappingValue.length > 0) {
methodRequestMappingValue.put(method.getName(), requestMappingValue[0]);
}
}
}
return methodRequestMappingValue;
}
}
本项目Controller层(或Action层)的代码调整步骤:
(1)类的调整
a、去掉@Scope注解
b、加上@RequestMapping注解(值:根据请求url来写)
c、去掉BaseAction继承(本项目有抽取过父类,所以不需要继承了)
d、加上2个私有变量:
private String actionRequestMappingValue;
private Map<String, String> methodRequestMappingValue;
e、加上初始化方法(注意修改:aaa.class为当前类)
@PostConstruct
public void init() {
this.actionRequestMappingValue = BaseActionUtil.getActionRequestMapping(aaa.class);
this.methodRequestMappingValue = BaseActionUtil.getMethodRequestMapping(aaa.class);
}
(2)方法调整:
a、普通方法(返回jsp路径的,非ajax)
(a)加上@RequestMapping注解(值:根据请求url来写, 这里一般都是跟方法名一致,除了strust.xml中的某些特殊url])
(b)返回类型都是String
(c)除了strust.xml特殊url,返回路径都是return actionRequestMappingValue + methodRequestMappingValue.get("aaa");(注意修改:aaa为当前方法的名),因此这里就可以直接返回controller类RequestMapping注解的值+方法的RequestMapping注解的值,刚刚好就是jsp在项目中的路径。
b、ajax方法
(a)加上注解 @RequestMapping、@ResponseBody
(b)返回类型基本为ActionResult(本项目统一结果返回的类),特殊例外
(3)注意事项:
(a)action里面的方法如果存在有不需要加注解的方法,都应该改为私有方法
(b)若存在action的方法调用action里另一个方法,则确认2个方法是否都有url请求相对应,若都有,则把调用方法的代码改为return转发的url
(c)若action有局部变量,由于Struts2是多例,Spring是单例,所有局部变量都得加到方法里面获取
13、Json转换器更换:由于本项目原来的Json转换器是用Gson进行转换而不是Jackson来转换json,两者在转换json的时候有差异(比如boolean类型转换的时候一个有is前缀一个则没有),因此,本项目则写一个新的Gson转换器来替换Jackson转换器
(1)把spring-mvc.xml调整为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描controller(controller层注入) -->
<context:component-scan base-package="com.test.cn.**.action"/>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.test.cn.core.web.converter.GsonHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<!--对静态资源文件的访问-->
<mvc:resources mapping="/lib/**" location="/lib/" />
<!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->
<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置springMVC处理上传文件的信息 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"/>
<property name="maxUploadSize" value="1073741824"/><!-- 最大上传1G -->
<property name="maxInMemorySize" value="40960"/>
</bean>
<bean class="com.test.cn.core.web.interceptor.MyExceptionMappingInterceptor" />
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.test.cn.core.web.interceptor.PrivilegeInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.test.cn.core.web.interceptor.LogInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
</beans>
(2)新建json转化器:GsonHttpMessageConverter
public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").disableHtmlEscaping().
registerTypeAdapter(Double.class, new JsonSerializer<Double>() {
@Override
public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
if (src == src.longValue())
return new JsonPrimitive(src.longValue());
return new JsonPrimitive(src);
}
}).create(); //serializeNulls(). 不对空值序列化;
private String jsonPrefix;
public GsonHttpMessageConverter() {
super(new MediaType[]{MediaType.APPLICATION_JSON, new MediaType("application", "*+json")});
}
public void setGson(Gson gson) {
Assert.notNull(gson, "‘gson‘ is required");
this.gson = gson;
}
public Gson getGson() {
return this.gson;
}
public void setJsonPrefix(String jsonPrefix) {
this.jsonPrefix = jsonPrefix;
}
public void setPrefixJson(boolean prefixJson) {
this.jsonPrefix = prefixJson?")]}‘, ":null;
}
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return this.canRead(mediaType);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return this.canWrite(mediaType);
}
protected boolean supports(Class<?> clazz) {
throw new UnsupportedOperationException();
}
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
TypeToken<?> token = this.getTypeToken(clazz);
return this.readTypeToken(token, inputMessage);
}
/**
* Abstract template method that writes the actual body. Invoked from {@link #write}.
*
* @param o the object to write to the output message
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
Charset charset = this.getCharset(outputMessage.getHeaders());
OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), charset);
try {
if(this.jsonPrefix != null) {
writer.append(this.jsonPrefix);
}
TypeToken<?> token = this.getTypeToken(o.getClass());
Type type = token.getType();
if(type != null) {
this.gson.toJson(o, type, writer);
} else {
this.gson.toJson(o, writer);
}
writer.close();
} catch (JsonIOException var7) {
throw new HttpMessageNotWritableException("Could not write JSON: " + var7.getMessage(), var7);
}
}
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
TypeToken<?> token = this.getTypeToken(type);
return this.readTypeToken(token, inputMessage);
}
protected TypeToken<?> getTypeToken(Type type) {
return TypeToken.get(type);
}
private Object readTypeToken(TypeToken<?> token, HttpInputMessage inputMessage) throws IOException {
InputStreamReader json = new InputStreamReader(inputMessage.getBody(), this.getCharset(inputMessage.getHeaders()));
try {
return this.gson.fromJson(json, token.getType());
} catch (JsonParseException var5) {
throw new HttpMessageNotReadableException("Could not read JSON: " + var5.getMessage(), var5);
}
}
private Charset getCharset(HttpHeaders headers) {
return headers != null && headers.getContentType() != null && headers.getContentType().getCharSet() != null?headers.getContentType().getCharSet():DEFAULT_CHARSET;
}
}
更换框架是一个很大的工作量,如果项目的代码量越大,要改的代码则会更多,本项目花了5天把除了Controller层的代码外的都换好了,然后再花费2天分配给2个人,根据我写的规则,来更改Controller层和jsp页面的代码(注意:一定要选比较认真严格的年轻小伙子,因为一不小心就会眼花改乱了,本人比较恐怖,完美主义者之一,相信世界上还有很多比我恐怖的coder,感谢大家阅读)