大数跨境
0
0

Spring AOP之通知Advice API详细介绍及使用

Spring AOP之通知Advice API详细介绍及使用 Spring全家桶实战案例
2023-02-05
0
导读:Spring AOP之通知Advice API详细介绍及使用

环境:Spring5.3.23


回顾:《Spring AOP之通知Pointcut API详细介绍及使用

Advice生命周期

每个Advice都是一个Bean。Advice实例可以在所有Advisor之间共享,也可以对每个Advisor对象都是唯一的。这对应于每个类或每个实例的Advice。

最常使用的是每类Advice。它适用于一般的Advice,例如事务Advisors。这些不依赖于代理对象的状态或添加新状态。它们只是对方法和参数起作用。

每个实例Advice适用于引入,以支持mixin。在这种情况下,通知将状态添加到代理对象。

你可以在同一个AOP代理中混合使用共享通知和每个实例通知。

Advice类型

Spring提供了几种通知类型,并且可以扩展以支持任意通知类型。

  • 拦截环绕通知

Spring中最基本的通知类型是围绕通知的拦截。

Spring与AOP Alliance接口兼容,支持使用方法拦截的环绕通知。实现MethodInterceptor和around advice的类还应该实现以下接口:

public interface MethodInterceptor extends  org.aopalliance.intercept.Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;}

invoke()方法的MethodInvocation参数公开了被调用的方法、目标连接点、AOP代理和方法的参数。invoke()方法应该返回调用的结果:连接点的返回值。

面的例子展示了一个简单的MethodInterceptor实现:

public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("Before: invocation=[" + invocation + "]"); Object rval = invocation.proceed(); System.out.println("Invocation returned"); return rval; }}

注意:MethodInvocation中对proceed()方法的调用。这将沿着拦截器链向下直至连接点。大多数拦截器调用此方法并返回其返回值。然而,MethodInterceptor和任何around通知一样,可以返回不同的值或抛出异常,而不是调用proceed方法。然而,如果没有充分的理由,你不会想要这样做。

MethodInterceptor实现提供了与其他遵循AOP联盟的AOP实现的互操作性。虽然使用最具体的通知类型有好处,但如果你可能想在另一个AOP框架中运行方面,请坚持使用MethodInterceptor。注意,切入点目前不能在框架之间互操作,而且AOP联盟目前不定义切入点接口。

  • 前置通知

一个简单的Advice类型是事前Adivce。它不需要MethodInvocation对象,因为它只在进入方法之前被调用。

before通知的主要优点是不需要调用proceed()方法,因此不可能在无意中无法继续执行拦截器链。

public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;}

注意:返回类型是void。Before通知可以在连接点运行之前插入自定义行为,但不能更改返回值。如果before通知抛出异常,它将停止拦截器链的进一步执行。异常在拦截器链中向上传播。如果未检查或在被调用方法的签名上,它将直接传递给客户端。否则,它将被AOP代理包装在未检异常中。

下面的例子展示了Spring中的before通知,它统计了所有的方法调用:

public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable { ++count; }
public int getCount() { return count; }}
  • 异常通知

如果连接点抛出异常,则在连接点返回后调用Throws通知。Spring提供了类型化异常通知。注意,这意味着
org.springframework.aop.ThrowsAdvice
接口不包含任何方法。它是一个标记接口,标识给定对象实现了一个或多个类型化throws通知方法。格式如下:

afterThrowing([Method, args, target], subclassOfThrowable)

Method,args,target3个参数是可选的。

public class BusinessThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(BusinessException ex) throws Throwable { // ... }}

下一个示例声明了4个参数,因此它可以访问被调用的方法、方法参数和目标对象。如果抛出ServletException,将调用以下Advice:

public class ControllerAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, MethodArgumentNotValidException ex) { // ... }}

在一个异常通知类中定义多个不同异常的处理

public static class CombinedThrowsAdvice implements ThrowsAdvice {  public void afterThrowing(BusinessException ex) throws Throwable {    // ...  }  public void afterThrowing(Method m, Object[] args, Object target, MethodArgumentNotValidException ex) {    // ...  }}
  • 后置通知

Spring中的后置通知必须实现
org.springframework.aop.AfterReturningAdvice
接口,如下:

public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable;}

返回通知可以访问返回值(它不能修改)、被调用的方法、方法的参数和目标。

public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable { ++count; }
public int getCount() { return count; }}

如果它抛出异常,它将被抛出拦截器链,而不是返回值。

  • 引介通知

Spring将引介通知视为一种特殊的拦截通知。

Introduction需要一个IntroductionAdvisor和一个IntroductionInterceptor实现以下接口:

public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);}

从AOP Alliance方法拦截器接口继承的invoke()方法必须实现引入。也就是说,如果被调用的方法在引入的接口上,则引入拦截器负责处理方法调用—它不能调用proceed()。

引介通知不能与任何切入点一起使用,因为它只适用于类级别,而不是方法级别。你只能在 IntroductionAdvisor中使用介绍建议,它有以下方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;}
public interface IntroductionInfo {
Class<?>[] getInterfaces();}

没有MethodMatcher,因此也没有与引介通知相关联的切入点。只有类过滤。

getInterfaces()方法返回这个Advisor引入的接口。

validateInterfaces()方法在内部使用,以查看引入的接口是否可以由配置的IntroductionInterceptor实现。下面直接给出示例,该示例的作用就是使某个类不具备某个接口能力时动态给予该接口的能力:

接口

public interface CountDAO {    public void count() ;}

这里的引介拦截器必须实现我们期望的一个接口

public class CustomIntroductionInterceptor implements IntroductionInterceptor, CountDAO {
@Override public void count() { System.out.println("订单统计...") ; }
@Override public boolean implementsInterface(Class<?> intf) { return CountDAO.class.isAssignableFrom(intf) ; }
@Override public Object invoke(MethodInvocation invocation) throws Throwable { if (implementsInterface(invocation.getMethod().getDeclaringClass())) { System.out.println("我是Introduction增强..." + "Class: " + invocation.getMethod().getDeclaringClass() + ", method: " + invocation.getMethod().getName()) ; // 实际调用的就是当前Advice实现的CountDAO#count方法。 return invocation.getMethod().invoke(this, invocation.getArguments()) ; } return invocation.proceed() ; }
}

创建代理处理器

@Componentpublic class OrderProxyCreater extends AbstractAutoProxyCreator {    @Override  protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,     TargetSource customTargetSource) throws BeansException {    return new Object[] {new DefaultIntroductionAdvisor(new CustomIntroductionInterceptor(), CountDAO.class)} ;  }
// 判断只要不是OrderDAO类型的都进行跳过(这里只代理是OrderDAO类型的Bean) @Override protected boolean shouldSkip(Class<?> beanClass, String beanName) { return !OrderDAO.class.isAssignableFrom(beanClass) ; }}

OrderDAO实现,该DAO并没有实现CountDAO

@Servicepublic class OrderDAOImpl implements OrderDAO {
@Override public void save() { System.out.println("保存订单...") ; }
@Override public void query() { System.out.println("查询订单...") ; }}

测试

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.pack.aop") ;ctx.registerShutdownHook();OrderDAO persondao = ctx.getBean(OrderDAO.class) ;persondao.save() ;Object obj = ctx.getBean("orderDAOImpl") ;if (obj instanceof CountDAO) {  CountDAO cdao = (CountDAO) obj ;  cdao.count() ;}

运行结果:

保存订单...我是Introduction增强...Class: interface com.pack.aop.CountDAO, method: count订单统计...

从运行结果看到OrderDAO具备了CountDAO接口能力,而具体实现CountDAO是我们的引介拦截器上实现的。

完毕!!!

关注我长期更新


【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读382
粉丝0
内容832