1.基于Schema的AOP
基于schema的AOP从spring2.0之后通过命名空间定义切面,切入点及声明通知。
在Spring配置文件中,所以AOP相关定义必须放在<aop:config>标签下,该标签下可以有<aop:pointcut>、<aop:advisor>、<aop:aspect>标签,配置顺序不可变。
- <aop:pointcut>:用来定义切入点,该切入点可以重用;
- <aop:advisor>:用来定义只有一个通知和一个切入点的切面;
- <aop:aspect>:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。
2.声明切面
切面就是包含切入点和通知的对象,在Spring容器中将被定义为一个Bean,Schema方式的切面需要一个切面支持Bean,该支持Bean的字段和方法提供了切面的状态和行为信息,并通过配置方式来指定切入点和通知实现。
切面使用<aop:aspect>标签指定,ref属性用来引用切面支持Bean。
切面支持Bean“aspectSupportBean”跟普通Bean完全一样使用,切面使用“ref”属性引用它。
3.声明切入点
切入点在Spring中也是一个Bean,Bean定义方式可以有很三种方式:
1)在<aop:config>标签下使用<aop:pointcut>声明一个切入点Bean,该切入点可以被多个切面使用,对于需要共享使用的切入点最好使用该方式,该切入点使用id属性指定Bean名字,在通知定义时使用pointcut-ref属性通过该id引用切入点,expression属性指定切入点表达式:
2)在<aop:aspect>标签下使用<aop:pointcut>声明一个切入点Bean,该切入点可以被多个切面使用,但一般该切入点只被该切面使用,当然也可以被其他切面使用,但最好不要那样使用,该切入点使用id属性指定Bean名字,在通知定义时使用pointcut-ref属性通过该id引用切入点,expression属性指定切入点表达式:
3)匿名切入点Bean,可以在声明通知时通过pointcut属性指定切入点表达式,该切入点是匿名切入点,只被该通知使用:
4.声明通知
基于Schema方式支持前边介绍的5中通知类型:
一、前置通知(before):在切入点选择的方法之前执行,通过<aop:aspect>标签下的<aop:before>标签声明:
pointcut和pointcut-ref:二者选一,指定切入点;
method:指定前置通知实现方法名,如果是多态需要加上参数类型,多个用“,”隔开,如beforeAdvice(java.lang.String);
arg-names:指定通知实现方法的参数名字,多个用“,”分隔,可选,类似于【3.1.2 构造器注入】中的参数名注入限制:在class文件中没生成变量调试信息是获取不到方法参数名字的,因此只有在类没生成变量调试信息时才需要使用arg-names属性来指定参数名,如arg-names="param"表示通知实现方法的参数列表的第一个参数名字为“param”。
public interface HelloWorldService {
void whoSayHello(String name);
}
public class HelloWorldServiceImpl implements HelloWorldService {
@Override
public void whoSayHello(String name) {
System.out.println(name + "hello world!");
}
}
<aop:config>
<aop:aspect ref="aspectHello">
<aop:before arg-names="param" method="beforeAdvice(java.lang.String)" pointcut="execution(* com.anjunshuang.aop..*.whoSayHello(..)) and args(param)" />
</aop:aspect>
</aop:config>
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring/aspect/helloworld.xml");
HelloWorldService helloWorldService = context.getBean("helloWorldService", HelloWorldService.class);
helloWorldService.whoSayHello("aj ");
}
1)切入点匹配:在配置中使用“execution(* cn.javass..*.whoSayHello(..)) ”匹配目标方法whoSayHello,且使用“args(param)”匹配目标方法只有一个参数且传入的参数类型为通知实现方法中同名的参数类型;
2)目标方法定义:使用method=" beforeAdvice(java.lang.String) "指定前置通知实现方法,且该通知有一个参数类型为java.lang.String参数;
3)目标方法参数命名:其中使用arg-names=" param "指定通知实现方法参数名为“param”,切入点中使用“args(param)”匹配的目标方法参数将自动传递给通知实现方法同名参数。
二、后置返回通知(after-returning):在切入点选择的方法正常返回时执行,通过<aop:aspect>标签下的<aop:after-returning>标签声明:
pointcut和pointcut-ref:同前置通知同义;
method:同前置通知同义;
arg-names:同前置通知同义;
returning:定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法执行正常返回后,将把目标方法返回值传给通知方法;returning限定了只有目标方法返回值匹配与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值。
boolean sayAfterReturning();
@Override
public boolean sayAfterReturning() {
System.out.println("============after returning");
return true;
}
public void afterAdviceReturn(Object retVal) {
System.out.println("【" + retVal + "】===========afterAdviceReturn advice");
}
<aop:after-returning method="afterAdviceReturn" pointcut="execution(* com.anjunshuang.aop..*.sayAfterReturning(..))" arg-names="retVal" returning="retVal"/>
@Test
public void testSayGoodAfter() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring/aspect/sayGood.xml");
SayGoodService sayGood = context.getBean("sayGood", SayGoodService.class);
sayGood.sayAfterReturning();
}
1)切入点匹配:在配置中使用“execution(* com.anjunshuang..*.sayAfterReturning(..)) ”匹配目标方法sayAfterReturning,该方法返回true;
2)目标方法定义:使用method="afterReturningAdvice"指定后置返回通知实现方法;
3)目标方法参数命名:其中使用arg-names="retVal"指定通知实现方法参数名为“retVal”;
4)返回值命名:returning="retVal"用于将目标返回值赋值给通知实现方法参数名为“retVal”的参数上。
三、后置异常通知(after-throwing):在切入点选择的方法抛出异常时执行,通过<aop:aspect>标签下的<aop:after-throwing>标签声明
throwing:定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;throwing限定了只有目标方法抛出的异常匹配与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
void sayAfterThrowing();
@Override
public void sayAfterThrowing() {
System.out.println("============before throwing");
throw new RuntimeException();
}
public void afterThrowingAdvice(Exception exception) {
System.out.println("===========after throwing advice exception:" + exception);
}
<aop:after-throwing method="afterThrowingAdvice" pointcut="execution(* com.anjunshuang.aop..*.sayAfterThrowing(..))" arg-names="exception" throwing="exception"/>
@Test
public void testSayAfterThrowing() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring/aspect/sayGood.xml");
SayGoodService sayGood = context.getBean("sayGood", SayGoodService.class);
sayGood.sayAfterThrowing();
}
四、后置最终通知:在切入点选择的方法返回时执行,不管是正常返回还是抛出异常都执行,通过<aop:aspect>标签下的<aop:after >标签声明
例子省略,和开始的列子一样:https://blog.csdn.net/m0_37438942/article/details/100098523
五、环绕通知:环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值,可通过<aop:aspect>标签下的<aop:around >标签声明:
环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型,在通知实现方法内部使用ProceedingJoinPoint的proceed()方法使目标方法执行,proceed 方法可以传入可选的Object[]数组,该数组的值将被作为目标方法执行时的参数。
void sayAround(String param);
@Override
public void sayAround(String param) {
System.out.println("============around param:" + param);
}
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("===========around before advice");
Object retVal = pjp.proceed(new Object[] {"replace"});
System.out.println("===========around after advice");
return retVal;
}
<aop:around pointcut="execution(* com.anjunshuang.aop.service.impl..*.sayAround(..))" method="aroundAdvice"/>
@Test
public void testSchemaAroundAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/aspect/sayGood.xml");
SayGoodService sayGoodService =
ctx.getBean("sayGood", SayGoodService.class);
sayGoodService.sayAround("haha");
System.out.println("======================================");
}
5.引入
Spring引入允许为目标对象引入新的接口,通过在< aop:aspect>标签内使用< aop:declare-parents>标签进行引入。
types-matching:匹配需要引入接口的目标对象的AspectJ语法类型表达式;
implement-interface:定义需要引入的接口;
default-impl和delegate-ref:定义引入接口的默认实现,二者选一,default-impl是接口的默认实现类全限定名,而delegate-ref是默认的实现的委托Bean名;
public interface IntroductionService {
void induct();
}
public class IntroductionServiceImpl implements IntroductionService{
@Override
public void induct() {
System.out.println("=====induct");
}
}
<aop:declare-parents types-matching="com.anjunshuang.aop..*.SayGoodService+" implement-interface="com.anjunshuang.aop.service.IntroductionService" default-impl="com.anjunshuang.aop.service.impl.IntroductionServiceImpl"/>
6.Advisor
Advisor表示只有一个通知和一个切入点的切面,由于Spring AOP都是基于AOP联盟的拦截器模型的环绕通知的,所以引入Advisor来支持各种通知类型(如前置通知等5种),Advisor概念来自于Spring1.2对AOP的支持,在AspectJ中没有相应的概念对应。
Advisor可以使用<aop:config>标签下的<aop:advisor>标签定
例如:<aop:advisor pointcut="切入点表达式" pointcut-ref="切入点Bean引用" advice-ref="通知API实现引用"/>
void sayAdvisorBefore(String param);
@Override
public void sayAdvisorBefore(String param) {
System.out.println("============say " + param);
}
/**
* @ClassName BeforeAdviceImpl
* @Description 定义前置通知api
* @Author anjunshuang
* @Date 2019/8/29 17:14
* @Version 1.0
*/
public class BeforeAdviceImpl implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("===========before advice");
}
}
<bean id="beforeAdvice" class="com.anjunshuang.aop.service.impl.BeforeAdviceImpl"/>
<aop:config>
<aop:advisor pointcut="execution(* com.anjunshuang..*.sayAdvisorBefore(..))" advice-ref="beforeAdvice"/>
</aop:config>
@Test
public void testSchemaAdvisor() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/aspect/sayGood.xml");
SayGoodService sayGoodService = ctx.getBean("sayGood", SayGoodService.class);
sayGoodService.sayAdvisorBefore("haha");
System.out.println("======================================");
}
不推荐使用Advisor,除了在进行事务控制的情况下,其他情况一般不推荐使用该方式,该方式属于侵入式设计,必须实现通知API。