Aop拦截到的Action 使得@Autowired 无法自动装配问题解决

对Struts1/2 Action应用Spring AOP问题小结
       之前使用SSH三大经典框架的时候,写了一个简单的统计Action每个方法执行时间的功能类,代码如下:
[java]
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; 
    } 
} 

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加入以下配置:
[xml]
<context:component-scan base-package="code.coolbaby"/> 
<aop:config> 
    <aop:advisor advice-ref="runTimeHandler" pointcut="execution(* code.coolbaby.core.BaseDispatchAction.*(..))"/> 
</aop:config> 

<context:component-scan base-package="code.coolbaby"/>
<aop:config>
 <aop:advisor advice-ref="runTimeHandler" pointcut="execution(* code.coolbaby.core.BaseDispatchAction.*(..))"/>
</aop:config>
就可以正确地以AOP的方式完成原本比较繁琐的功能了。
最近把框架升级到SS2H,顺便把Spring AOP实现由原来的Schema方式改为AspectJ方式,代码如下:
[java]
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 
@Component 
public 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; 
    } 
 
} 

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
@Component
public 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]
<?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> 

<?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加入以下配置:
[xml]
<context:component-scan base-package="code.coolbaby"/> 
<aop:aspectj-autoproxy proxy-target-class="true"/> 

<context:component-scan base-package="code.coolbaby"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
理论上讲,AOP的功能应该可以正确实现了,实际则不然,以UserAction举例说明,
[java]
package code.coolbaby.basal.web.security; 
 
//限于篇幅,省略import语句  
 
/**
 * 用户管理Action.
 * 
 * 使用Struts2 convention-plugin Annotation定义Action参数.
 * 
 * @author Kanine
 */ 
@SuppressWarnings("serial") 
public class UserAction extends CRUDActionSupport<User> { 
 
    @Autowired 
    private UserManager userManager; 
 
    private User entity; 
    private Long id; 
    private Page<User> page = new Page<User>(5);//每页5条记录  
 
    public User getModel() { 
        return entity; 
    } 
 
    @Override 
    protected 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; 
    } 
 
    @Override 
    public String list() throws Exception { 
        HttpServletRequest request = Struts2Utils.getRequest(); 
        List<PropertyFilter> filters = HibernateWebUtils.buildPropertyFilters(request); 
 
        page = userManager.search(page, filters); 
        return SUCCESS; 
    } 
    //限于篇幅,省略其他的代码  
} 

package code.coolbaby.basal.web.security;

//限于篇幅,省略import语句

/**
 * 用户管理Action.
 *
 * 使用Struts2 convention-plugin Annotation定义Action参数.
 *
 * @author Kanine
 */
@SuppressWarnings("serial")
public class UserAction extends CRUDActionSupport<User> {

 @Autowired
 private UserManager userManager;

 private User entity;
 private Long id;
 private Page<User> page = new Page<User>(5);//每页5条记录

 public User getModel() {
  return entity;
 }

 @Override
 protected 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;
 }

 @Override
 public String list() throws Exception {
  HttpServletRequest request = Struts2Utils.getRequest();
  List<PropertyFilter> filters = HibernateWebUtils.buildPropertyFilters(request);

  page = userManager.search(page, filters);
  return SUCCESS;
 }
 //限于篇幅,省略其他的代码
}

        测试的结果是,userManager注入失败,在执行list()方法的时候报错,NullPointer!
        接下来反复Debug后,发现个很奇怪的现象,在AOP执行的RunTimeHandler内部,Debug视图中methodInvocation的proxy的userManager属性是正确地注入的,而其target中的userManager却为null,当跳转到list()时,userManager亦为null,这是怎么回事呢?!
        变换了几种测试方法,发现如果是对service层的EntityManager(里面有使用了@Autowired的entityDAO)切面,不会出现NPE,Debug视图中proxy的entityDAO为null而target中的entityDAO正确注入;如果去掉AOP,UserAction运行正常,不会发生userManager注入失败的情况;但是该AOP在Struts1的环境下却执行正确,也没有发生注入失败的问题!
        尝试了几种解决方案后,发现如果加入userManager的setter方法,即便不加@Autowired也不会有NPE,功能运转正常,但是理论上置于field上的@Autowired已经无需setter了,而且如果要加入setter的话,就破坏了AOP无代码侵入性的优点,这样的解决方案并不可取。
        继续hacking source,发现了Struts2的一个特殊的constant,作用是确保Spring的自动装配策略总是被考虑的,struts.objectFactory.spring.autoWire.alwaysRespect,将其值设为true,OK了,没有setter,自动注入也毫无问题,算是完美解决!     
struts.xml这个隐藏得很深的参数:
[xml]
<constant name="struts.objectFactory.spring.autoWire.alwaysRespect" value="true" />  

<constant name="struts.objectFactory.spring.autoWire.alwaysRespect" value="true" />
SpringObjectFactory的关键代码:
[java]
@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); 
    } 
} 

    @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);
        }
    }
若将
[java]
bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); 

bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);改为[java] view plaincopyprint?bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false); 

bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);,发现连alwaysRespect这个constant也可以去掉了!
        问题虽然解决了,可是对于为什么会出现这样的情况我是百思不得其解,隐约觉得关键点是autoWiringFactory.autowire和autoWiringFactory.createBean这两个方法,可是又说不出个所以然来,希望大家能就此问题各抒己见,提出自己独到的见解来!


总结:
@Aspect作用于action,致使action中的@Autowired注入为null的解决方案,以下三种任选一种:
1、去掉@Autowired,改用set,get注入
2、将action纳入spring的ioc管理
3、修改Struts.xml文件的属性<constant name="struts.objectFactory.spring.autoWire.alwaysRespect" value="true" />,使自动注入总是有效

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值