aop:aspectj-autoproxy 致使autowired失效

来源:互联网 发布:小牛学堂大数据就业班 编辑:程序博客网 时间:2024/04/27 23:55
对Struts1/2 Action应用Spring AOP问题小结
       之前使用SSH三大经典框架的时候,写了一个简单的统计Action每个方法执行时间的功能类,代码如下:
[java] view plain copy
  1. import javax.servlet.http.HttpServletRequest;  
  2.   
  3. import org.aopalliance.intercept.MethodInterceptor;  
  4. import org.aopalliance.intercept.MethodInvocation;  
  5. import org.apache.commons.lang.StringUtils;  
  6. import org.apache.commons.lang.time.StopWatch;  
  7. import org.apache.struts.actions.DispatchAction;  
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10. import org.springframework.stereotype.Service;  
  11. import org.springframework.aop.framework.ReflectiveMethodInvocation;  
  12.   
  13. /** 
  14.  * 统计方法执行时间的拦截器,采用Spring AOP方式实现. 
  15.  *  
  16.  * @author Kanine 
  17.  */  
  18. @Service("runTimeHandler")  
  19. public class RunTimeHandler implements MethodInterceptor {  
  20.   
  21.     private static Logger logger = LoggerFactory.getLogger("code.coolbaby");  
  22.   
  23.     @SuppressWarnings("unchecked")  
  24.     public Object invoke(MethodInvocation methodInvocation) throws Throwable {  
  25.   
  26.         Object[] args = methodInvocation.getArguments();  
  27.         String method = methodInvocation.getMethod().getName();  
  28.         String action = methodInvocation.getMethod().getDeclaringClass().getName();  
  29.   
  30.         /** 
  31.          * 由于Spring使用了Cglib代理,导致不能直接取得目标类名,需作此转换 
  32.          */  
  33.         if (methodInvocation instanceof ReflectiveMethodInvocation) {  
  34.             Object proxy = ((ReflectiveMethodInvocation) methodInvocation).getProxy();  
  35.             action = StringUtils.substringBefore(proxy.toString(), "@");  
  36.             /** 
  37.              * 如使用了DispatchAction,将不能直接取得目标方法,需作此处理 
  38.              */  
  39.             if (proxy instanceof DispatchAction) {  
  40.                 for (Object arg : args) {  
  41.                     if (arg instanceof HttpServletRequest)  
  42.                         method = ((HttpServletRequest) arg).getParameter("method");  
  43.                 }  
  44.             }  
  45.         }  
  46.   
  47.         /** 
  48.          * 方法参数类型,转换成简单类型  
  49.          */  
  50.         Class[] params = methodInvocation.getMethod().getParameterTypes();  
  51.         String[] simpleParams = new String[params.length];  
  52.         for (int i = 0; i < params.length; i++) {  
  53.             simpleParams[i] = params[i].getSimpleName();  
  54.         }  
  55.   
  56.         String simpleMethod = method + "(" + StringUtils.join(simpleParams, ",") + ")";  
  57.           
  58.         logger.info("{} 开始执行[{}]方法", action, method);  
  59.   
  60.         StopWatch clock = new StopWatch();  
  61.         clock.start();  
  62.         Object result = methodInvocation.proceed();  
  63.         clock.stop();  
  64.   
  65.         logger.info("执行[{}]方法共消耗{}毫秒", simpleMethod, clock.getTime());  
  66.   
  67.         return result;  
  68.     }  
  69. }  
在applicationcontext.xml加入以下配置:
[xml] view plain copy
  1. <context:component-scan base-package="code.coolbaby"/>  
  2. <aop:config>  
  3.     <aop:advisor advice-ref="runTimeHandler" pointcut="execution(* code.coolbaby.core.BaseDispatchAction.*(..))"/>  
  4. </aop:config>  
就可以正确地以AOP的方式完成原本比较繁琐的功能了。
最近把框架升级到SS2H,顺便把Spring AOP实现由原来的Schema方式改为AspectJ方式,代码如下:
[java] view plain copy
  1. import org.apache.commons.lang.time.StopWatch;  
  2. import org.aspectj.lang.ProceedingJoinPoint;  
  3. import org.aspectj.lang.annotation.Around;  
  4. import org.aspectj.lang.annotation.Aspect;  
  5. import org.aspectj.lang.annotation.Pointcut;  
  6. import org.slf4j.Logger;  
  7. import org.slf4j.LoggerFactory;  
  8. import org.springframework.stereotype.Component;  
  9.   
  10. /** 
  11.  * 统计方法执行时间的工具类,采用Spring AOP方式实现. 
  12.  *  
  13.  * @author Kanine 
  14.  */  
  15. @Aspect  
  16. @Component  
  17. public class RunTimeHandler {  
  18.       
  19.     private static Logger logger = LoggerFactory.getLogger("code.coolbaby");  
  20.   
  21.     @Pointcut("execution(public String *()) && !execution(public String toString())" + " && target(code.coolbaby.core.CRUDActionSupport)")  
  22.     void timer() {  
  23.     }  
  24.   
  25.     @Around("timer()")  
  26.     public Object time(ProceedingJoinPoint joinPoint) throws Throwable {  
  27.           
  28.         String clazz = joinPoint.getTarget().getClass().getSimpleName();  
  29.         String method = joinPoint.getSignature().getName();  
  30.   
  31.         StopWatch clock = new StopWatch();  
  32.         clock.start();  
  33.         Object result = joinPoint.proceed();  
  34.         clock.stop();  
  35.           
  36.         String[] params = new String[] { clazz, method, clock.getTime() + "" };  
  37.         logger.info("[{}]执行[{}]方法共消耗[{}]毫秒", params);  
  38.           
  39.         return result;  
  40.     }  
  41.   
  42. }  
struts.xml内容如下:
[xml] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"  
  3.         "http://struts.apache.org/dtds/struts-2.1.dtd">  
  4. <struts>  
  5.     <constant name="struts.convention.default.parent.package" value="crud-default" />  
  6.     <constant name="struts.convention.package.locators" value="web" />  
  7.     <constant name="struts.convention.result.path" value="/" />    
  8.       
  9.     <!-- 用于CRUD Action的parent package -->  
  10.     <package name="crud-default" extends="convention-default">  
  11.         <!-- 基于paramsPrepareParamsStack,  
  12.             增加store interceptor保证actionMessage在redirect后不会丢失 -->  
  13.         <interceptors>  
  14.             <interceptor-stack name="crudStack">  
  15.                 <interceptor-ref name="store">  
  16.                     <param name="operationMode">AUTOMATIC</param>  
  17.                 </interceptor-ref>  
  18.                 <interceptor-ref name="paramsPrepareParamsStack" />  
  19.             </interceptor-stack>  
  20.         </interceptors>  
  21.   
  22.         <default-interceptor-ref name="crudStack" />  
  23.     </package>  
  24.   
  25.     <!--   
  26.         使用Convention插件,实现约定大于配置的零配置文件风格.  
  27.         特殊的Result路径在Action类中使用@Result设定.   
  28.     -->  
  29. </struts>  
在applicationcontext.xml加入以下配置:
[xml] view plain copy
  1. <context:component-scan base-package="code.coolbaby"/>  
  2. <aop:aspectj-autoproxy proxy-target-class="true"/>  
理论上讲,AOP的功能应该可以正确实现了,实际则不然,以UserAction举例说明,
[java] view plain copy
  1. package code.coolbaby.basal.web.security;  
  2.   
  3. //限于篇幅,省略import语句  
  4.   
  5. /** 
  6.  * 用户管理Action. 
  7.  *  
  8.  * 使用Struts2 convention-plugin Annotation定义Action参数. 
  9.  *  
  10.  * @author Kanine 
  11.  */  
  12. @SuppressWarnings("serial")  
  13. public class UserAction extends CRUDActionSupport<User> {  
  14.   
  15.     @Autowired  
  16.     private UserManager userManager;  
  17.   
  18.     private User entity;  
  19.     private Long id;  
  20.     private Page<User> page = new Page<User>(5);//每页5条记录  
  21.   
  22.     public User getModel() {  
  23.         return entity;  
  24.     }  
  25.   
  26.     @Override  
  27.     protected void prepareModel() throws Exception {  
  28.         if (id != null) {  
  29.             entity = userManager.get(id);  
  30.         } else {  
  31.             entity = new User();  
  32.         }  
  33.     }  
  34.   
  35.     public void setId(Long id) {  
  36.         this.id = id;  
  37.     }  
  38.   
  39.     public Page<User> getPage() {  
  40.         return page;  
  41.     }  
  42.   
  43.     @Override  
  44.     public String list() throws Exception {  
  45.         HttpServletRequest request = Struts2Utils.getRequest();  
  46.         List<PropertyFilter> filters = HibernateWebUtils.buildPropertyFilters(request);  
  47.   
  48.         page = userManager.search(page, filters);  
  49.         return SUCCESS;  
  50.     }  
  51.     //限于篇幅,省略其他的代码  
  52. }  

        测试的结果是,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] view plain copy
  1. <constant name="struts.objectFactory.spring.autoWire.alwaysRespect" value="true" />   
SpringObjectFactory的关键代码:
[java] view plain copy
  1. @Override  
  2. public Object buildBean(Class clazz, Map<String, Object> extraContext) throws Exception {  
  3.     Object bean;  
  4.   
  5.     try {  
  6.         // Decide to follow autowire strategy or use the legacy approach which mixes injection strategies  
  7.         if (alwaysRespectAutowireStrategy) {  
  8.             // Leave the creation up to Spring  
  9.             bean = autoWiringFactory.createBean(clazz, autowireStrategy, false);  
  10.             injectApplicationContext(bean);  
  11.             return injectInternalBeans(bean);  
  12.         } else {  
  13.             bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);  
  14.             bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName());  
  15.             // We don't need to call the init-method since one won't be registered.  
  16.             bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName());  
  17.             return autoWireBean(bean, autoWiringFactory);  
  18.         }  
  19.     } catch (UnsatisfiedDependencyException e) {  
  20.         // Fall back  
  21.         return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory);  
  22.     }  

若将
[java] view plain copy
  1. bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);  
改为
[java] view plain copy
  1. 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