spring11----基于Schema的AOP
基于Schema的AOP从Spring2.0之后通过“aop”命名空间来定义切面、切入点及声明通知。
在Spring配置文件中,所以AOP相关定义必须放在<aop:config>标签下,该标签下可以有<aop:pointcut>、 <aop:advisor>、<aop:aspect>标签,配置顺序不可变。
一. 声明切面
切面就是包含切入点和通知的对象,在Spring容器中将被定义为一个Bean,Schema方式的切面需要一个切面支持 Bean,该支持Bean的字段和方法提供了切面的状态和行为信息,并通过配置方式来指定切入点和通知实现。
<bean id="aspect" class="com.test.spring.aop.HelloWorldAspect" /> <aop:config> <aop:pointcut id="pointcut" expression="execution(* com.test.spring.service.impl..*.*(..))" /> <aop:aspect ref="aspect"> <aop:before pointcut-ref="pointcut" method="beforeAdvice" /> <!-- <aop:after pointcut="execution(* com.test.spring.service.impl..*.*(..))" method="afterFinallyAdvice"/> --> <aop:after pointcut-ref="pointcut" method="afterFinallyAdvice" /> </aop:aspect> </aop:config>
切面使用<aop:aspect>标签指定,ref属性用来引用切面支持Bean。
切面支持Bean“aspectSupportBean”跟普通Bean完全一样使用,切面使用“ref”属性引用它。
二. 声明切入点
切入点在Spring中也是一个Bean,Bean定义方式可以有以下3种:
1. 在<aop:config>标签下使用<aop:pointcut>声明一个切入点Bean,该切入点可以被多个切面使用,对于需要 共享使用的切入点最好使用该方式,该切入点使用id属性指定Bean名字,在通知定义时使用pointcut-ref属性通过该id 引用切入点,expression属性指定切入点表达式:
<aop:config> <aop:pointcut id="pointcut" expression="execution(* cn.javass..*.*(..))"/> <aop:aspect ref="aspectSupportBean"> <aop:before pointcut-ref="pointcut" method="before"/> </aop:aspect> </aop:config>
2. 在<aop:aspect>标签下使用<aop:pointcut>声明一个切入点Bean,该切入点可以被多个切面使用,但一般该 切入点只被该切面使用,当然也可以被其他切面使用,但最好不要那样使用,该切入点使用id属性指定Bean名字,在通 知定义时使用pointcut-ref属性通过该id引用切入点,expression属性指定切入点表达式:
<aop:config> <aop:aspect ref="aspectSupportBean"> <aop:pointcut id=" pointcut" expression="execution(* cn.javass..*.*(..))"/> <aop:before pointcut-ref="pointcut" method="before"/> </aop:aspect> </aop:config>
3. 匿名切入点Bean,可以在声明通知时通过pointcut属性指定切入点表达式,该切入点是匿名切入点,只被该通知 使用:
<aop:config> <aop:aspect ref="aspectSupportBean"> <aop:after pointcut="execution(* cn.javass..*.*(..))" method="afterFinallyAdvice"/> </aop:aspect> </aop:config>
三. 声明通知
基于Schema方式支持前边介绍的5种通知类型:
1. 前置通知
在切入点选择的方法之前执行,通过<aop:aspect>标签下的<aop:before>标签声明:
<aop:before pointcut="切入点表达式" pointcut-ref="切入点Bean引用" method="前置通知实现方法名" arg-names="前置通知实现方法参数列表参数名字"/>
pointcut和pointcut-ref:二者选一,指定切入点;
method:指定前置通知实现方法名,如果是多态需要加上参数类型,多个用“,”隔开,如 beforeAdvice(java.lang.String);
arg-names:指定通知实现方法的参数名字,多个用“,”分隔,可选,类似于构造器注入中的参数 名注入限制:在class文件中没生成变量调试信息是获取不到方法参数名字的,因此只有在类没生成变量调试信息时才需 要使用arg-names属性来指定参数名,如arg-names="param"表示通知实现方法的参数列表的第一个参数名字为 “param”。
举例:
1 package com.test.spring.service; 2 3 public interface IHelloWorldService { 4 public void sayHello(String param); 5 }
1 package com.test.spring.service.impl; 2 3 import com.test.spring.service.IHelloWorldService; 4 5 public class HelloWorldService implements IHelloWorldService{ 6 7 public void sayHello(String param) { 8 System.out.println("======Hello "+param); 9 } 10 }
1 package com.test.spring.aop; 2 3 public class HelloWorldAspect { 4 //前置通知 5 public void beforeAdvice(String param) { 6 System.out.println("=====before advice param "+param); 7 } 8 }
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="helloWorldService" class="com.test.spring.service.impl.HelloWorldService" /> <bean id="aspect" class="com.test.spring.aop.HelloWorldAspect" /> <aop:config> <aop:aspect ref="aspect"> <aop:before pointcut="execution(* com.test.spring.service.impl..*.sayHello(..)) and args(param)" //这里的parm指的是通知种的参数 method="beforeAdvice(java.lang.String)" arg-names="param"/> //这里的param指的是通知的param </aop:aspect> </aop:config> </beans>
1 public class AopTest { 2 @Test 3 public void test() { 4 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 5 IHelloWorldService helloWorldService=(IHelloWorldService) context.getBean("helloWorldService"); 6 helloWorldService.sayHello("hermioner"); 7 } 8 }
说明:
1)切入点匹配:在配置中使用“execution(* cn.javass..*.sayBefore(..)) ”匹配目标方法sayHello,且使用 “args(param)”匹配目标方法只有一个参数且传入的参数类型为通知实现方法中同名的参数类型;
2)目标方法定义:使用method=" beforeAdvice(java.lang.String) "指定前置通知实现方法,且该通知有一个参数类 型为java.lang.String参数;
3)目标方法参数命名:其中使用arg-names=" param "指定通知实现方法参数名为“param”,切入点中使用 “args(param)”匹配的目标方法参数将自动传递给通知实现方法同名参数。(比如:Service种的sayHello(String param),但是通知中是beforeAdvice(String param2),那么上面的两处arg配置都应该用param2)
2. 后置返回通知
在切入点选择的方法正常返回时执行,通过<aop:aspect>标签下的<aop:after-returning>标签声 明:
<aop:after-returning pointcut="切入点表达式" pointcut-ref="切入点Bean引用" method="后置返回通知实现方法名" arg-names="后置返回通知实现方法参数列表参数名字" returning="返回值对应的后置返回通知实现方法参数名" />
pointcut和pointcut-ref:同前置通知同义,二选一;
method:同前置通知同义;
arg-names:同前置通知同义;
returning:定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法执行正常返回后,将把目 标方法返回值传给通知方法;returning限定了只有目标方法返回值匹配与通知方法相应参数类型时才能执行后置返回 通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值。
举例:
1 package com.test.spring.service; 2 3 public interface IHelloWorldService { 4 public void sayHello(String param); 5 public boolean sayAfterReturning(); 6 }
1 package com.test.spring.service.impl; 2 3 import com.test.spring.service.IHelloWorldService; 4 5 public class HelloWorldService implements IHelloWorldService{ 6 7 public void sayHello(String param) { 8 System.out.println("======Hello "+param); 9 } 10 11 public boolean sayAfterReturning() { 12 System.out.println("--------after returning"); 13 return true; 14 } 15 }
1 package com.test.spring.aop; 2 3 public class HelloWorldAspect { 4 //前置通知 5 public void beforeAdvice(String param2) { 6 System.out.println("=====before advice param "+param2); 7 } 8 9 //后置返回通知 10 public void afterReturningAdvice(Object retVal) { 11 System.out.println("==========after returning advice retVal:"+retVal); 12 } 13 }
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:aop="http://www.springframework.org/schema/aop" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> 9 10 <bean id="helloWorldService" 11 class="com.test.spring.service.impl.HelloWorldService" /> 12 <bean id="aspect" class="com.test.spring.aop.HelloWorldAspect" /> 13 14 <aop:config> 15 <aop:aspect ref="aspect"> 16 <aop:before pointcut="execution(* com.test.spring.service.impl..*.sayHello(..)) and args(param2)" 17 method="beforeAdvice(java.lang.String)" 18 arg-names="param2"/> 19 20 <aop:after-returning pointcut="execution(* com.test.spring.service.impl..*.sayAfterReturning(..))" 21 method="afterReturningAdvice" 22 arg-names="retVal" 23 returning="retVal"/> 24 </aop:aspect> 25 </aop:config> 26 27 </beans>
1 public class AopTest { 2 @Test 3 public void test() { 4 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 5 IHelloWorldService helloWorldService=(IHelloWorldService) context.getBean("helloWorldService"); 6 helloWorldService.sayAfterReturning(); 7 } 8 }
1 --------after returning 2 ==========after returning advice retVal:true
分析:
1)切入点匹配:在配置中使用“execution(* cn.javass..*.sayAfterReturning(..)) ”匹配目标方法 sayAfterReturning,该方法返回true;
2)目标方法定义:使用method="afterReturningAdvice"指定后置返回通知实现方法;
3)目标方法参数命名:其中使用arg-names="retVal"指定通知实现方法参数名为“retVal”;
4)返回值命名:returning="retVal"用于将目标返回值赋值给通知实现方法参数名为“retVal”的参数上。
3. 后置异常通知
在切入点选择的方法抛出异常时执行,通过<aop:aspect>标签下的<aop:after-throwing>标签声 明:
<aop:after-throwing pointcut="切入点表达式" pointcut-ref="切入点Bean引用" method="后置异常通知实现方法名" arg-names="后置异常通知实现方法参数列表参数名字" throwing="将抛出的异常赋值给的通知实现方法参数名"/>
throwing:定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标 方法抛出的异常传给通知方法;throwing限定了只有目标方法抛出的异常匹配与通知方法相应参数异常类型时才能执行 后置异常通知,否则不执行,对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
1 package com.test.spring.service; 2 3 public interface IHelloWorldService { 4 public void sayHello(String param); 5 public boolean sayAfterReturning(); 6 public void sayAfterThrowing(); 7 }
1 package com.test.spring.service.impl; 2 3 import com.test.spring.service.IHelloWorldService; 4 5 public class HelloWorldService implements IHelloWorldService{ 6 7 public void sayHello(String param) { 8 System.out.println("======Hello "+param); 9 } 10 11 public boolean sayAfterReturning() { 12 System.out.println("--------after returning"); 13 return true; 14 } 15 16 public void sayAfterThrowing() { 17 System.out.println("======before thrwing"); 18 throw new RuntimeException(); 19 } 20 }
1 package com.test.spring.aop; 2 3 public class HelloWorldAspect { 4 //前置通知 5 public void beforeAdvice(String param2) { 6 System.out.println("=====before advice param "+param2); 7 } 8 9 //后置返回通知 10 public void afterReturningAdvice(Object retVal) { 11 System.out.println("==========after returning advice retVal:"+retVal); 12 } 13 14 //后置异常通知 15 public void afterThrowingAdvice(Exception exception) { 16 System.out.println("======after throwing advice exception:"+exception); 17 } 18 }
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:aop="http://www.springframework.org/schema/aop" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> 9 10 <bean id="helloWorldService" 11 class="com.test.spring.service.impl.HelloWorldService" /> 12 <bean id="aspect" class="com.test.spring.aop.HelloWorldAspect" /> 13 14 <aop:config> 15 <aop:aspect ref="aspect"> 16 <aop:before pointcut="execution(* com.test.spring.service.impl..*.sayHello(..)) and args(param2)" 17 method="beforeAdvice(java.lang.String)" 18 arg-names="param2"/> 19 20 <aop:after-returning pointcut="execution(* com.test.spring.service.impl..*.sayAfterReturning(..))" 21 method="afterReturningAdvice" 22 arg-names="retVal" 23 returning="retVal"/> 24 25 <aop:after-throwing pointcut="execution(* com.test.spring.service.impl..*.sayAfterThrowing(..))" 26 method="afterThrowingAdvice" 27 arg-names="exception" 28 throwing="exception"/> 29 </aop:aspect> 30 </aop:config> 31 32 </beans>
1 public class AopTest { 2 @Test 3 public void test() { 4 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 5 IHelloWorldService helloWorldService=(IHelloWorldService) context.getBean("helloWorldService"); 6 helloWorldService.sayAfterThrowing(); 7 } 8 }
1 ======before thrwing 2 ======after throwing advice exception:java.lang.RuntimeException
分析:
1)切入点匹配:在配置中使用“execution(* cn.javass..*.sayAfterThrowing(..))”匹配目标方法sayAfterThrowing, 该方法将抛出RuntimeException异常;
2)目标方法定义:使用method="afterThrowingAdvice"指定后置异常通知实现方法;
3)目标方法参数命名:其中使用arg-names="exception"指定通知实现方法参数名为“exception”;
4)异常命名:returning="exception"用于将目标方法抛出的异常赋值给通知实现方法参数名为“exception”的参数 上。
4. 后置最终通知
在切入点选择的方法返回时执行,不管是正常返回还是抛出异常都执行,通过<aop:aspect>标签 下的<aop:after >标签声明:
<aop:after pointcut="切入点表达式" pointcut-ref="切入点Bean引用" method="后置最终通知实现方法名" arg-names="后置最终通知实现方法参数列表参数名字"/>
举例:
1 public interface IHelloWorldService { 2 public void sayHello(String param); 3 public boolean sayAfterReturning(); 4 public void sayAfterThrowing(); 5 public boolean sayAfterFinally(); 6 }
1 package com.test.spring.service.impl; 2 3 import com.test.spring.service.IHelloWorldService; 4 5 public class HelloWorldService implements IHelloWorldService{ 6 7 public void sayHello(String param) { 8 System.out.println("======Hello "+param); 9 } 10 11 public boolean sayAfterReturning() { 12 System.out.println("--------after returning"); 13 return true; 14 } 15 16 public void sayAfterThrowing() { 17 System.out.println("======before thrwing"); 18 throw new RuntimeException(); 19 } 20 21 public boolean sayAfterFinally() { 22 System.out.println("============before finally"); 23 throw new RuntimeException(); 24 } 25 }
1 package com.test.spring.aop; 2 3 public class HelloWorldAspect { 4 //前置通知 5 public void beforeAdvice(String param2) { 6 System.out.println("=====before advice param "+param2); 7 } 8 9 //后置返回通知 10 public void afterReturningAdvice(Object retVal) { 11 System.out.println("==========after returning advice retVal:"+retVal); 12 } 13 14 //后置异常通知 15 public void afterThrowingAdvice(Exception exception) { 16 System.out.println("======after throwing advice exception:"+exception); 17 } 18 19 //后置最终通知 20 public void afterFinallyAdvice() { 21 System.out.println("========after finally advice"); 22 } 23 }
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:aop="http://www.springframework.org/schema/aop" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> 9 10 <bean id="helloWorldService" 11 class="com.test.spring.service.impl.HelloWorldService" /> 12 <bean id="aspect" class="com.test.spring.aop.HelloWorldAspect" /> 13 14 <aop:config> 15 <aop:aspect ref="aspect"> 16 <aop:before pointcut="execution(* com.test.spring.service.impl..*.sayHello(..)) and args(param2)" 17 method="beforeAdvice(java.lang.String)" 18 arg-names="param2"/> 19 20 <aop:after-returning pointcut="execution(* com.test.spring.service.impl..*.sayAfterReturning(..))" 21 method="afterReturningAdvice" 22 arg-names="retVal" 23 returning="retVal"/> 24 25 <aop:after-throwing pointcut="execution(* com.test.spring.service.impl..*.sayAfterThrowing(..))" 26 method="afterThrowingAdvice" 27 arg-names="exception" 28 throwing="exception"/> 29 <aop:after pointcut="execution(* com.test.spring.service.impl..*.sayAfterFinally(..))" 30 method="afterFinallyAdvice"/> 31 </aop:aspect> 32 </aop:config> 33 34 </beans>
1 public class AopTest { 2 @Test 3 public void test() { 4 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 5 IHelloWorldService helloWorldService=(IHelloWorldService) context.getBean("helloWorldService"); 6 helloWorldService.sayAfterFinally(); 7 } 8 }
1 ============before finally 2 ========after finally advice
分析:
1)切入点匹配:在配置中使用“execution(* cn.javass..*.sayAfterFinally(..))”匹配目标方法sayAfterFinally,该方法 将抛出RuntimeException异常;
2)目标方法定义:使用method=" afterFinallyAdvice "指定后置最终通知实现方法。
5. 环绕通知
环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知非常强大,可以决定目标方法是否执 行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值,可通过<aop:aspect>标签下的 <aop:around >标签声明
<aop:around pointcut="切入点表达式" pointcut-ref="切入点Bean引用" method="后置最终通知实现方法名" arg-names="后置最终通知实现方法参数列表参数名字"/>
环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型,在通知实现方法内部使用 ProceedingJoinPoint的proceed()方法使目标方法执行,proceed 方法可以传入可选的Object[]数组,该数组的值将 被作为目标方法执行时的参数。
举例:
1 package com.test.spring.service.impl; 2 3 import com.test.spring.service.IHelloWorldService; 4 5 public class HelloWorldService implements IHelloWorldService{ 6 public void sayAround(String param) { 7 System.out.println("=======around param:"+param); 8 } 9 }
1 package com.test.spring.aop; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 5 public class HelloWorldAspect { 6 public Object aroundAdvice(ProceedingJoinPoint pip) throws Throwable { 7 System.out.println("=======around before"); 8 Object retVal=pip.proceed(new Object[] {"replace"}); 9 System.out.println("======around after"); 10 return retVal; 11 } 12 }
1 <bean id="helloWorldService" 2 class="com.test.spring.service.impl.HelloWorldService" /> 3 <bean id="aspect" class="com.test.spring.aop.HelloWorldAspect" /> 4 5 <aop:config> 6 <aop:aspect ref="aspect"> 7 8 <aop:around pointcut="execution(* com.test.spring.service.impl..*.sayAround(..))" 9 method="aroundAdvice"/> 10 </aop:aspect> 11 </aop:config>
1 public class AopTest { 2 @Test 3 public void test() { 4 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 5 IHelloWorldService helloWorldService=(IHelloWorldService) context.getBean("helloWorldService"); 6 helloWorldService.sayAround("hahah"); 7 } 8 }
1 =======around before 2 =======around param:replace 3 ======around after
分析:
1)切入点匹配:在配置中使用“execution(* cn.javass..*.sayAround(..))”匹配目标方法sayAround;
2)目标方法定义:使用method="aroundAdvice"指定环绕通知实现方法,在该实现中,第一个方法参数为pjp,类型 为ProceedingJoinPoint,其中“Object retVal = pip.proceed(new Object[] {"replace"});”,用于执行目标方法, 且目标方法参数被“new Object[] {"replace"}”替换,最后返回“retVal ”返回值。
3)测试:我们使用“helloworldService.sayAround("haha");”传入参数为“haha”,但最终输出为“replace”,说 明参数被替换了。
note: 如果上面的pip.proceed(),不添加任何参数,输出的将会是hello.
四. Advisor
不推荐使用Advisor,除了在进行事务控制的情况下,其他情况一般不推荐使用该方式,该方式属于侵入式设计,必 须实现通知API。
(mynote:等需要的时候再回头学习补充)
五. 通知的参数自动传入
比如下面结构的代码和配置:
1.接口
1 package com.a.hm.service; 2 3 public interface IHelloApi { 4 5 public void sayHello(String param,String a); 6 7 8 9 }
2.接口实现
1 package com.a.hm.service; 2 3 public class HelloImpl implements IHelloApi{ 4 5 public void sayHello(String param,String a) { 6 System.out.println("hello world"+" "+param+","+" "+a); 7 } 8 9 10 }
3.通知类
1 package com.a.hm; 2 3 public class Advice { 4 public void beforeAdvice(String m,String n) { 5 System.out.println("before advice"+" "+m+", "+n); 6 } 7 }
4.配置
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:aop="http://www.springframework.org/schema/aop" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> 9 10 <!-- 1.配置目标类 --> 11 <bean id="helloId" class="com.a.hm.service.HelloImpl"/> 12 13 <!-- 2.配置通知类 --> 14 <bean id="adviceId" class="com.a.hm.Advice"/> 15 16 <!-- 3.配置切面 --> 17 <aop:config> 18 <aop:pointcut id="pointcut" expression="execution(* com.a.hm.service..*.*(..)) and args(m,n)"/> 19 <aop:aspect ref="adviceId"> 20 <aop:before pointcut-ref="pointcut" method="beforeAdvice"/> 21 </aop:aspect> 22 </aop:config> 23 24 </beans>
Note:上面配置的切入点表达式中的args(m,n)必须和通知中的方法变量名一样,否则报错
Note:上面配置的切入点表达式中的args中的参数个数和通知类中的方法的参数个数一致;可以不用和目标方法中的参数个数一致
5.测试
1 package com.a.hm; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 import com.a.hm.service.IHelloApi; 7 8 public class Test { 9 public static void main(String args[]) { 10 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 11 IHelloApi helloApi=context.getBean("helloId",IHelloApi.class); 12 helloApi.sayHello("hello","a"); 13 } 14 15 }
1 before advice hello, a 2 hello world hello, a
参考文献
https://jinnianshilongnian.iteye.com/blog/1418598