天天看点

Spring AOP---基于Xml配置应用demo

        首先,需要加载依赖的Spring相关类库,如下所示,这里创建的是普通java工程,web工程的配置,这里不再多说:

Spring AOP---基于Xml配置应用demo

        其次,配置 .xml 配置文件,具体配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  <!-- 配置aop命名空间 -->
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
 <!-- 基于注解的aop动态代理配置 -->
 <aop:aspectj-autoproxy/>
  
<bean id="duke" class = "com.springaction.springidol.Juggler">
	<constructor-arg value="15">
	</constructor-arg>
</bean>

<!-- 切面 -->
<bean id="audience" class="com.springaction.aop.domain.Audience" />

<aop:config>
	<aop:aspect ref="audience">
		<!--定义切点,只在audienct切面中使用,如果想被多个切面使用,可以放到<aop:config>元素中 -->
		<aop:pointcut id="performance" expression="execution(* com.springaction.springidol.Performer.perform(..))"/>
		<!--应用切点,前置通知-->
		<aop:before pointcut-ref="performance"
			method="takeSeats" />
		<aop:before pointcut-ref="performance"
			method="turnOffCellPhones" />
		<!-- 后置通知,方法成功执行之后调用通知 -->
		<aop:after-returning pointcut-ref="performance"
			method="applaud" />
		<!--后置通知, 方法抛出异常之后调用通知 -->
		<aop:after-throwing pointcut-ref="performance"
			method="demandRefund" />			
		<!-- 声明环绕通知 -->
		<aop:around pointcut-ref="performance"
			method="watchPerformance" />				
	</aop:aspect>
</aop:config>
</beans>
           

        上面的配置中,定义了切面、切点以及各种通知方式,其中比较特殊的是环绕通知。

       然后,看看切面:

public class Audience
{
	//表演之前:前置通知
	public void takeSeats()
	{
		System.out.println("The audience is taking their seats.");
	}
	
	//表演之前:前置通知
	public void turnOffCellPhones()
	{
		System.out.println("The audience is turning off their cellPhones");
	}
	
	//表演之后:后置通知,成功返回之后执行
	public void applaud()
	{
		System.out.println("CLAP CLAP CLAP CLAP");
	}
	
	//表演失败之后:后置通知,抛异常之后执行
	public void demandRefund()
	{
		System.out.println("Boo! We want out money back!");
	}
	
	/**
	 * 环绕通知:
	 * Around advice可以通过一个在joinpoint执行前后做一些事情的机会,
	 * 可以决定什么时候,怎么样去执行joinpoint,甚至可以决定是否真的执行joinpoint的方法调用。
	 * Around advice通常是用在下面这样的情况:
	 * 在多线程环境下,在joinpoint方法调用前后的处理中需要共享一些数据。
	 * 如果使用Before advice和After advice也可以达到目的,
	 * 但是就需要在aspect里面创建一个存储共享信息的field,但这种做法并不是线程安全的。
	 * &&
	 * ProceedingJoinPoint作为入参,可以让我们在通知里调用被通知方法。
	 * @param joinpoint
	 */
	public void watchPerformance(ProceedingJoinPoint joinpoint)
	{		
		try
		{
			//执行前的操作
			System.out.println("Around-before:The audience is taking their seats.");
			System.out.println("Around-before:The audience is turning off their cellPhones");
			long start = System.currentTimeMillis();	
			
			//执行被通知的方法
			//如果没有执行这个方法,则通知会阻止被通知方法的调用。
			joinpoint.proceed();
			
			//执行后的操作
			long end  = System.currentTimeMillis();
			System.out.println("Around-after:CLAP CLAP CLAP CLAP CLAP");
			System.out.println("Around-after:The performance took " + (end - start) 
					+ " milliseconds.");
		}
		catch (Throwable e)
		{
			System.out.println("Boo!We want our money back!");
		}
	}

}
           
上面代码对该切面中关键点都做了注释,其余的不用多说,其中环绕通知必须包含入参org.aspectj.lang.ProceedingJoinPoint
           

通过它的方法proceed()执行被通知的方法。另外,任何通知方法可以将第一个参数定义为

org.aspectj.lang.JoinPoint

  类型。

JoinPoint

  接口提供了一系列有用的方法, 比如

getArgs()

(返回方法参数)、

getThis()

(返回代理对象)、

getTarget()

(返回目标)、

getSignature()

(返回正在被通知的方法相关信息)和

toString()

(打印出正在被通知的方法的有用信息)。

        然后,目标对象:

public class Juggler implements Performer
{
	private int beanBags = 3;
	
	public Juggler()
	{
		
	}
	
	public Juggler(int beanBags)
	{
		this.beanBags = beanBags;
	}

	public void perform() throws Exception
	{
		//抛异常则触发after-throwing通知
		//throw new Exception("");
		//执行成功则触发after-returning通知
		System.out.println("JUGGLING " + beanBags + " BeanBags");		
		//无论成功与否,则触发after通知
	}
}
           

       最后是测试代码:

public class Test
{
	public static void main(String[] args)
	{
		ApplicationContext ctx = new ClassPathXmlApplicationContext("file:../../src/spring.xml");
		
		Performer performer = (Performer) ctx.getBean("duke");
		
		try
		{
			performer.perform();
		}
		catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
           

       测试代码中,使用应用上下文ClassPathXmlApplicationContext加载类路径下的配置文件,关于加载配置文件的方式,后面需要具体具体分析下。

       执行结果如下:

The audience is taking their seats.
The audience is turning off their cellPhones
Around-before:The audience is taking their seats.
Around-before:The audience is turning off their cellPhones
JUGGLING 15 BeanBags
Around-after:CLAP CLAP CLAP CLAP CLAP
Around-after:The performance took 2 milliseconds.
CLAP CLAP CLAP CLAP
           

     上面的这个Demo是基于XML配置切面的应用,基于注解的应用以及动态代理等后面会给出具体解释和Demo。

     在这个例子调试中,也遇到了一些问题,主要包括如下几个:

     >.1.spring 的依赖注入是面向接口编程的,在测试代码中Performer performer = (Performer) ctx.getBean("duke");必须转换为接口,而不是具体实现,否则报错:

Exception in thread "main" java.lang.ClassCastException: $Proxy2 cannot be cast to com.springaction.springidol.Juggler

 at com.springaction.springidol.Test.main(Test.java:14)

ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2

JDWP exit error AGENT_ERROR_NO_JNI_ENV(183):  [../../../src/share/back/util.c:820]  

     >.2.主要是缺少一些jar包,比如commons-logging.jar, spring-expression.jar等,这个根据具体报错信息添加具体jar包就可以,关键是理解这些jar包的具体作用。