浏览 5229 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-06-15
最后修改:2009-06-22
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这两个方法,可是又说不出个所以然来,希望大家能就此问题各抒己见,提出自己独到的见解来! 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-12-03
谢谢十二郎,
@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" />,使自动注入总是有效 |
|
返回顶楼 | |