Aop拦截Action 使得@Autowired 无法自动装配问题解决
对Struts1/2 Action应用Spring AOP问题小结
之前使用SSH三大经典框架的时候,写了一个简单的统计Action每个方法执行时间的功能类,代码如下:
import javax.servlet.http.HttpServletRequest;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.apache.commons.lang.StringUtils;import org.apache.commons.lang.time.StopWatch;import org.apache.struts.actions.DispatchAction;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;import org.springframework.aop.framework.ReflectiveMethodInvocation;/** * 统计方法执行时间的拦截器,采用Spring AOP方式实现. * * @author Kanine */@Service("runTimeHandler")public class RunTimeHandler implements MethodInterceptor {private static Logger logger = LoggerFactory.getLogger("code.coolbaby");@SuppressWarnings("unchecked")public Object invoke(MethodInvocation methodInvocation) throws Throwable {Object[] args = methodInvocation.getArguments();String method = methodInvocation.getMethod().getName();String action = methodInvocation.getMethod().getDeclaringClass().getName();/** * 由于Spring使用了Cglib代理,导致不能直接取得目标类名,需作此转换 */if (methodInvocation instanceof ReflectiveMethodInvocation) {Object proxy = ((ReflectiveMethodInvocation) methodInvocation).getProxy();action = StringUtils.substringBefore(proxy.toString(), "@");/** * 如使用了DispatchAction,将不能直接取得目标方法,需作此处理 */if (proxy instanceof DispatchAction) {for (Object arg : args) {if (arg instanceof HttpServletRequest)method = ((HttpServletRequest) arg).getParameter("method");}}}/** * 方法参数类型,转换成简单类型 */Class[] params = methodInvocation.getMethod().getParameterTypes();String[] simpleParams = new String[params.length];for (int i = 0; i < params.length; i++) {simpleParams[i] = params[i].getSimpleName();}String simpleMethod = method + "(" + StringUtils.join(simpleParams, ",") + ")";logger.info("{} 开始执行[{}]方法", action, method);StopWatch clock = new StopWatch();clock.start();Object result = methodInvocation.proceed();clock.stop();logger.info("执行[{}]方法共消耗{}毫秒", simpleMethod, clock.getTime());return result;}}在applicationcontext.xml加入以下配置:
<context:component-scan base-package="code.coolbaby"/><aop:config><aop:advisor advice-ref="runTimeHandler" pointcut="execution(* code.coolbaby.core.BaseDispatchAction.*(..))"/></aop:config>就可以正确地以AOP的方式完成原本比较繁琐的功能了。
import org.apache.commons.lang.time.StopWatch;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;/** * 统计方法执行时间的工具类,采用Spring AOP方式实现. * * @author Kanine */@Aspect@Componentpublic class RunTimeHandler {private static Logger logger = LoggerFactory.getLogger("code.coolbaby");@Pointcut("execution(public String *()) && !execution(public String toString())" + " && target(code.coolbaby.core.CRUDActionSupport)")void timer() {}@Around("timer()")public Object time(ProceedingJoinPoint joinPoint) throws Throwable {String clazz = joinPoint.getTarget().getClass().getSimpleName();String method = joinPoint.getSignature().getName();StopWatch clock = new StopWatch();clock.start();Object result = joinPoint.proceed();clock.stop();String[] params = new String[] { clazz, method, clock.getTime() + "" };logger.info("[{}]执行[{}]方法共消耗[{}]毫秒", params);return result;}}struts.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd"><struts><constant name="struts.convention.default.parent.package" value="crud-default" /><constant name="struts.convention.package.locators" value="web" /><constant name="struts.convention.result.path" value="/" /> <!-- 用于CRUD Action的parent package --><package name="crud-default" extends="convention-default"><!-- 基于paramsPrepareParamsStack,增加store interceptor保证actionMessage在redirect后不会丢失 --><interceptors><interceptor-stack name="crudStack"><interceptor-ref name="store"><param name="operationMode">AUTOMATIC</param></interceptor-ref><interceptor-ref name="paramsPrepareParamsStack" /></interceptor-stack></interceptors><default-interceptor-ref name="crudStack" /></package><!-- 使用Convention插件,实现约定大于配置的零配置文件风格.特殊的Result路径在Action类中使用@Result设定. --></struts>在applicationcontext.xml加入以下配置:
<context:component-scan base-package="code.coolbaby"/><aop:aspectj-autoproxy proxy-target-class="true"/>理论上讲,AOP的功能应该可以正确实现了,实际则不然,以UserAction举例说明,
package code.coolbaby.basal.web.security;//限于篇幅,省略import语句/** * 用户管理Action. * * 使用Struts2 convention-plugin Annotation定义Action参数. * * @author Kanine */@SuppressWarnings("serial")public class UserAction extends CRUDActionSupport<User> {@Autowiredprivate UserManager userManager;private User entity;private Long id;private Page<User> page = new Page<User>(5);//每页5条记录public User getModel() {return entity;}@Overrideprotected void prepareModel() throws Exception {if (id != null) {entity = userManager.get(id);} else {entity = new User();}}public void setId(Long id) {this.id = id;}public Page<User> getPage() {return page;}@Overridepublic String list() throws Exception {HttpServletRequest request = Struts2Utils.getRequest();List<PropertyFilter> filters = HibernateWebUtils.buildPropertyFilters(request);page = userManager.search(page, filters);return SUCCESS;}//限于篇幅,省略其他的代码}
<constant name="struts.objectFactory.spring.autoWire.alwaysRespect" value="true" />SpringObjectFactory的关键代码:
@Override public Object buildBean(Class clazz, Map<String, Object> extraContext) throws Exception { Object bean; try { // Decide to follow autowire strategy or use the legacy approach which mixes injection strategies if (alwaysRespectAutowireStrategy) { // Leave the creation up to Spring bean = autoWiringFactory.createBean(clazz, autowireStrategy, false); injectApplicationContext(bean); return injectInternalBeans(bean); } else { bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName()); // We don't need to call the init-method since one won't be registered. bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName()); return autoWireBean(bean, autoWiringFactory); } } catch (UnsatisfiedDependencyException e) { // Fall back return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory); } }若将
bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);改为
bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);,发现连alwaysRespect这个constant也可以去掉了!