`
brunoplum
  • 浏览: 14453 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

对Struts1/2 Action应用Spring AOP问题小结

    博客分类:
  • SSH
阅读更多
       之前使用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这两个方法,可是又说不出个所以然来,希望大家能就此问题各抒己见,提出自己独到的见解来!
分享到:
评论
1 楼 san586 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" />,使自动注入总是有效

相关推荐

    struts2+spring aop demo

    4. **连接Struts2和Spring**:在Struts2的配置文件(如`struts.xml`)中,我们需要配置Action类以使用Spring的依赖注入,这样Spring才能识别并应用AOP代理。 在实际的项目中,我们还需要考虑如何处理自定义参数。...

    spring和struts的整合-aop的使用

    总之,Spring与Struts2的整合能够提升应用的灵活性和可扩展性,而AOP的使用则进一步增强了代码的可维护性和透明度。通过深入理解和实践这些技术,开发者可以构建出更强大、更健壮的Java Web应用程序。

    struts2+hirbenate+SpringAOP

    Struts2、Hibernate和Spring AOP是Java Web开发中三个重要的框架,它们分别负责不同的职责。Struts2作为MVC(Model-View-Controller)框架,主要处理HTTP请求,实现业务逻辑与视图的解耦;Hibernate是一个持久化框架...

    struts1,struts2,spring,hibernate

    Struts1、Struts2、Spring和Hibernate是Java Web开发中的四大框架,它们各自承担着不同的职责,共同构建了一个强大的企业级应用体系。 Struts1是Apache组织开发的一个开源MVC框架,它使得Java Web应用程序的开发...

    三大框架整合(Spring+Hibernate+Struts1/Struts2)

    对于Struts2,其核心组件Action、Interceptor等与Spring的集成方式类似,主要是通过Spring的依赖注入(DI)和AOP实现控制层与业务层的解耦。在实际项目中,开发者可以根据具体需求调整配置,以实现最佳的框架整合...

    集成 Struts2/Spring/Hibernate

    1. **Struts2**:Struts2是一个基于MVC设计模式的Web应用程序框架,提供强大的请求处理和结果映射功能。在集成中,它的核心是`struts2-core`库,同时依赖于XWork、OGNL、FreeMarker和日志组件。`struts.xml`是配置...

    struts2_mybatis_spring_框架实例整合_数据库 文档

    Struts2、MyBatis和Spring是Java Web开发中常用的三大框架,它们分别负责MVC模式中的Action层、数据持久层和应用上下文管理。这篇文档将深入探讨如何将这三个框架整合在一起,以及如何结合数据库进行实际应用。 ...

    struts2 spring hibernate integration

    Struts2、Spring和Hibernate是Java开发中三大重要的开源框架,它们各自负责Web应用的不同层面。Struts2作为MVC框架处理HTTP请求和视图展示,Spring提供了依赖注入(DI)和面向切面编程(AOP),以及服务层管理,而...

    Struts2+Hibernate+Spring整合开发深入剖析与范例应用03

    Struts2、Hibernate和Spring是Java企业级应用中三大核心框架,它们的整合使用能够构建出高效、可维护性高的Web应用程序。本篇将深入剖析这三者如何协同工作,并提供实际范例应用。 首先,Struts2作为MVC(模型-视图...

    struts2+spring集成bug——使用AOP时可能遇到的问题分析

    解决这些问题通常需要对Spring AOP的原理有深入理解,同时也需要熟悉Struts2的工作方式。查看日志,使用调试工具,以及查阅官方文档都是定位和解决问题的有效方法。 最后,对于给出的文件名"test",可能是测试代码...

    Struts2,Spring与Hibernate整合应用,学生成绩管理系统

    ### Struts2、Spring与Hibernate整合应用:学生成绩管理系统 #### 一、知识点概览 本项目聚焦于Struts2、Spring与Hibernate三大框架的整合应用,旨在构建一个高效、稳定、易于维护的学生成绩管理系统。通过整合这...

    struts2-spring-plugin-2.3.4.jar

    Struts2-Spring-Plugin-2.3.4.jar 是一个专门为 Struts 2 框架和 Spring 框架整合而设计的插件,主要用于处理 Struts 2 和 Spring 之间的集成问题。在Java Web开发中,这两个框架经常一起使用,Spring 提供了依赖...

    Struts2整合Spring、JPA

    Struts2整合Spring和JPA是企业级Java应用开发中常见的技术组合,它们分别负责不同的职责:Struts2作为一款成熟的MVC框架,主要用于处理Web层的请求与响应;Spring作为一个全面的轻量级框架,提供了依赖注入(DI)和...

    struts2与spring2的整合

    4. **Spring插件**:在Struts2中使用Spring插件(struts2-spring-plugin),这个插件能够帮助Struts2与Spring进行交互,自动将Action类实例化并注入依赖。 5. **Action类**:Action类需要实现Spring的`org.spring...

    Struts2+Spring+Hibernate和Struts2+Spring+Ibatis

    Struts2+Spring+Hibernate和Struts2+Spring+Ibatis是两种常见的Java Web应用程序集成框架,它们分别基于ORM框架Hibernate和轻量级数据访问框架Ibatis。这两种框架结合Spring,旨在提供一个强大的、可扩展的、易于...

    struts2+spring2.5+hibernate3基础包(包括AOP)

    Struts2、Spring 2.5 和 Hibernate 3 是经典的 Java Web 开发三大框架,它们的整合使用(SSH 整合)在过去的许多年里一直是企业级应用开发的主流技术栈。这个压缩包包含了这三个框架的基础组件,以及AOP(面向切面...

    struts2+spring+mybatis框架

    Struts2、Spring和MyBatis是Java Web开发中经典的三大框架,它们组合起来可以构建出高效、可维护的Web应用程序。以下是对这三个框架及其整合的详细解释。 **Struts2框架** Struts2是一个基于MVC(Model-View-...

    struts2+spring练习

    Struts2提供了MVC(模型-视图-控制器)架构,帮助开发者更好地组织和管理应用程序逻辑,而Spring则是一个全面的后端框架,提供依赖注入、AOP(面向切面编程)、事务管理等特性。 在"Struts2+Spring练习"项目中,...

    struts2-spring-plugin-2.2.1.jar

    这个插件的主要目的是简化在基于Struts2的应用程序中整合Spring的功能,如依赖注入(DI)、AOP(面向切面编程)以及事务管理等。下面我们将深入探讨Struts2、Spring框架以及它们之间的集成。 **Struts2框架** ...

    struts1和spring整合

    Struts1 和 Spring 整合是 Java Web 开发中常见的一种技术组合,它们分别作为 MVC 框架和依赖注入框架,共同提升了应用的可维护性和可扩展性。Struts1 提供了强大的控制器层,而 Spring 提供了业务逻辑处理和依赖...

Global site tag (gtag.js) - Google Analytics