您的位置:首页 > 运维架构

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

2013-03-28 09:57 447 查看
对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的方式完成原本比较繁琐的功能了。
最近把框架升级到SS2H,顺便把Spring AOP实现由原来的Schema方式改为AspectJ方式,代码如下:
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 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> {

@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这个隐藏得很深的参数:
<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也可以去掉了!
        问题虽然解决了,可是对于为什么会出现这样的情况我是百思不得其解,隐约觉得关键点是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" />,使自动注入总是有效
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐