Spring中AOP相关的API及源码解析,原来AOP是这样子的(1)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring中AOP相关的API及源码解析,原来AOP是这样子的(1)

前言


之所以写这么一篇文章主要是因为下篇文章将结束Spring启动整个流程的分析,从解析配置到创建对象再到属性注入最后再将创建好的对象初始化成为一个真正意义上的Bean。因为下篇文章会设计到AOP,所以提前单独将AOP的相关API及源码做一次解读,这样可以降低阅读源码的障碍,话不多说,我们进入正文!


一个使用API创建代理的例子


在进入API分析前,我们先通过两个例子体会下如何使用API的方式来创建一个代理对象,对应示例如下:


1.定义通知

public class DmzAfterReturnAdvice implements AfterReturningAdvice {
  @Override
  public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
    System.out.println("after invoke method [" + method.getName() + "],aop afterReturning logic invoked");
  }
}
public class DmzAroundAdvice implements MethodInterceptor {
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    System.out.println("aroundAdvice invoked");
    return invocation.proceed();
  }
}
public class DmzBeforeAdvice implements MethodBeforeAdvice {
  @Override
  public void before(Method method, Object[] args, Object target) throws Throwable {
    System.out.println("before invoke method [" + method.getName() + "],aop before logic invoked");
  }
}
public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
  @Override
  public void run() {
    System.out.println("running!!!!");
  }
}

2.切点

public class DmzPointcut implements Pointcut {
  @Override
  @NonNull
  public ClassFilter getClassFilter() {
    // 在类级别上不进行拦截
    return ClassFilter.TRUE;
  }
  @Override
  @NonNull
  public MethodMatcher getMethodMatcher() {
    return new StaticMethodMatcherPointcut() {
      @Override
      public boolean matches(@NonNull Method method, Class<?> targetClass) {
        // 对于toString方法不进行拦截
        return !method.getName().equals("toString");
      }
    };
  }
}

3.目标类

public class DmzService {
  @Override
  public String toString() {
    System.out.println("dmzService toString invoke");
    return "dmzService";
  }
  public void testAop(){
    System.out.println("testAop invoke");
  }
}

4.测试代码

public class Main {
  public static void main(String[] args) {
    ProxyFactory proxyFactory = new ProxyFactory();
    // 一个Advisor代表的是一个已经跟指定切点绑定了的通知
        // 在这个例子中意味着环绕通知不会作用到toString方法上
    Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice());
    // 添加一个绑定了指定切点的环绕通知
    proxyFactory.addAdvisor(advisor);
    // 添加一个返回后的通知
    proxyFactory.addAdvice(new DmzAfterReturnAdvice());
    // 添加一个方法执行前的通知
    proxyFactory.addAdvice(new DmzBeforeAdvice());
    // 为代理类引入一个新的需要实现的接口--Runnable
    proxyFactory.addAdvice(new DmzIntroductionAdvice());
    // 设置目标类
    proxyFactory.setTarget(new DmzService());
    // 因为要测试代理对象自己定义的方法,所以这里启用cglib代理
    proxyFactory.setProxyTargetClass(true);
    // 创建代理对象
    Object proxy = proxyFactory.getProxy();
    // 调用代理类的toString方法,通过控制台查看代理逻辑的执行情况
    proxy.toString();
    if (proxy instanceof DmzService) {
      ((DmzService) proxy).testAop();
    }
    // 判断引入是否成功,并执行引入的逻辑
    if (proxy instanceof Runnable) {
      ((Runnable) proxy).run();
    }
  }
}

这里我就不将测试结果放出来了,大家可以先自行思考这段程序将输出什么。接下来我们就来分析上面这段程序中所涉及到的API,通过这些API的学习相信大家可以彻底理解上面这段代码。


API介绍


Pointcut(切点)


对应接口定义如下:

public interface Pointcut {
    // ClassFilter,在类级别进行过滤
  ClassFilter getClassFilter();
  // MethodMatcher,在方法级别进行过滤
  MethodMatcher getMethodMatcher();
    // 一个单例对象,默认匹配所有
  Pointcut TRUE = TruePointcut.INSTANCE;
}

切点的主要作用是定义通知所要应用到的类跟方法,上面的接口定义也很明显的体现了这一点,我们可以将其拆分成为两个部分

  • ClassFilter,接口定义如下:
public interface ClassFilter {
  boolean matches(Class<?> clazz);
  ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

ClassFilter的主要作用是在类级别上对通知的应用进行一次过滤,如果它的match方法对任意的类都返回true的话,说明在类级别上我们不需要过滤,这种情况下,通知的应用,就完全依赖MethodMatcher的匹配结果。

  • MethodMatcher,接口定义如下:
public interface MethodMatcher {
  boolean matches(Method method, @Nullable Class<?> targetClass);
  boolean isRuntime();
  boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);
  MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

MethodMatcher中一共有三个核心方法


  • matches(Method method, @Nullable Class<?> targetClass),这个方法用来判断当前定义的切点跟目标类中的指定方法是否匹配,它可以在创建代理的时候就被调用,从而决定是否需要进行代理,这样就可以避免每次方法执行的时候再去做判断
  • isRuntime(),如果这个方法返回true的话,意味着每次执行方法时还需要做一次匹配
  • matches(Method method, @Nullable Class<?> targetClass, Object... args),当之前的isRuntime方法返回true时,会调用这个方法再次进行一次判断,返回false的话,意味这个不对这个方法应用通知


Advice(通知)


环绕通知(Interception Around Advice)


接口定义如下:

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

在上面接口定义的invoke方法中,MethodInvocation就是当前执行的方法,当我们调用invocation.proceed就是在执行当前的这个方法,基于此,我们可以在方法的执行前后去插入我们自定义的逻辑,比如下面这样

// 执行前的逻辑
doSomeThingBefore();
Object var = invocation.proceed;
doSomeThingAfter();
// 执行后的逻辑
retrun var;

前置通知(Before Advice)


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

跟环绕通知不同的是,这个接口中定义的方法的返回值是void,所以前置通知是无法修改方法的返回值的。

如果在前置通知中发生了异常,那么会直接终止目标方法的执行以及打断整个拦截器链的执行


后置通知(After Returning Advice)

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

后置通知相比较于前置通知,主要有以下几点不同

  • 后置通知可以访问目标方法的返回值,但是不能修改
  • 后置通知是在方法执行完成后执行


异常通知(Throws Advice)

public interface ThrowsAdvice extends AfterAdvice {
}

异常通知中没有定义任何方法,它更像一个标记接口。我们在定义异常通知时需要实现这个接口,同时方法的签名也有要求


1.方法名称必须是afterThrowing

2.方法的参数个数必须是1个或者4个,如下

public class OneParamThrowsAdvice implements ThrowsAdvice {
    // 如果只有一个参数,那么这个参数必须是要进行处理的异常
    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}
public class FourParamThrowsAdvice implements ThrowsAdvice {
    // 如果定义了四个参数,那么这四个参数分别是
    // 1.m:目标方法
    // 2.args:执行目标方法所需要的参数
    // 3.target:目标对象
    // 4.ex:具体要处理的异常
    // 并且参数类型必须按照这个顺序定义
    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

我们可以在一个异常通知中定义多个方法,在后续的源码分析中我们会发现,这些方法最终会被注册成对应的异常的handler,像下面这样

public static class CombinedThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

引入通知(Introduction Advice)


引入通知的主要作用是可以让生成的代理类实现额外的接口。例如在上面的例子中,我们为DmzService创建一个代理对象,同时为其定义了一个引入通知

public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
  @Override
  public void run() {
    System.out.println("running!!!!");
  }
}

在这个引入通知中,我们为其引入了一个新的需要实现的接口Runnable,同时通知本身作为这个接口的实现类。


通过这个引入通知,我们可以将生成的代理类强转成Runnable类型然后执行其run方法,同时,run方法也会被前面定义的前置通知,后置通知等拦截。


为了更好的了解引入通知,我们来需要了解下DelegatingIntroductionInterceptor这个类。见名知意,这个类就是一个委托引入拦截器,因为我们要为代理类引入新的接口,因为着我们要提供具体的实现的逻辑,而具体的实现的逻辑就可以被委托给这个DelegatingIntroductionInterceptor。


我们可以看看它的源码

public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
    implements IntroductionInterceptor {
    // 实际实现了引入逻辑的类
  @Nullable
  private Object delegate;
    // 对外提供了一个带参的构造函数,通过这个构造函数我们可以传入一个
    // 具体的实现类
  public DelegatingIntroductionInterceptor(Object delegate) {
    init(delegate);
  }
    // 对子类暴露了一个空参的构造函数,默认将自身作为实现了引入逻辑的委托类
    // 我们上面的例子中就是使用的这种方法
  protected DelegatingIntroductionInterceptor() {
    init(this);
  }
    // 对这个类进行初始化,要通过实际的实现类来找到具体要实现的接口
  private void init(Object delegate) {
    Assert.notNull(delegate, "Delegate must not be null");
    this.delegate = delegate;
        // 找到delegate所有实现的接口
    implementInterfacesOnObject(delegate);
        // 因为我们可能会将DelegatingIntroductionInterceptor本身作为委托者
        // Spring的设计就是不对外暴露这两个接口
        // 如果将其暴露,意味着我们可以将代理类强转成这种类型
    suppressInterface(IntroductionInterceptor.class);
    suppressInterface(DynamicIntroductionAdvice.class);
  }
  // 引入通知本身也是基于拦截器实现的,当执行一个方法时需要判断这个方法
    // 是不是被引入的接口中定义的方法,如果是的话,那么不能调用目标类的方法
    // 而要调用委托类的方法
  public Object invoke(MethodInvocation mi) throws Throwable {
    if (isMethodOnIntroducedInterface(mi)) {
      Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());
      // 这里是处理一种特殊情况,方法的返回值是this的时候
            // 这里应该返回代理类
      if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
        Object proxy = ((ProxyMethodInvocation) mi).getProxy();
        if (mi.getMethod().getReturnType().isInstance(proxy)) {
          retVal = proxy;
        }
      }
            // 其余情况下直接将委托类的执行结果返回
      return retVal;
    }
        // 执行到这里说明不是引入的方法,这是Spring提供了一个扩展逻辑
        // 正常来说这个类只会处理引入的逻辑,通过这个方法可以对目标类中的方法做拦截
        // 不常用
    return doProceed(mi);
  }
  protected Object doProceed(MethodInvocation mi) throws Throwable {
    return mi.proceed();
  }
}

通过查看这个类的源码我们可以发现,所谓的引入其实就是在方法执行的时候加了一层拦截,当判断这个方法是被引入的接口提供的方法的时候,那么就执行委托类中的逻辑而不是目标类中的方法

相关文章
|
8天前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
24天前
|
机器学习/深度学习 人工智能 自然语言处理
企业级API集成方案:基于阿里云函数计算调用DeepSeek全解析
DeepSeek R1 是一款先进的大规模深度学习模型,专为自然语言处理等复杂任务设计。它具备高效的架构、强大的泛化能力和优化的参数管理,适用于文本生成、智能问答、代码生成和数据分析等领域。阿里云平台提供了高性能计算资源、合规与数据安全、低延迟覆盖和成本效益等优势,支持用户便捷部署和调用 DeepSeek R1 模型,确保快速响应和稳定服务。通过阿里云百炼模型服务,用户可以轻松体验满血版 DeepSeek R1,并享受免费试用和灵活的API调用方式。
155 12
|
1月前
|
数据采集 监控 搜索推荐
深度解析淘宝商品详情API接口:解锁电商数据新维度,驱动业务增长
淘宝商品详情API接口,是淘宝开放平台为第三方开发者提供的一套用于获取淘宝、天猫等电商平台商品详细信息的应用程序接口。该接口涵盖了商品的基本信息(如标题、价格、图片)、属性参数、库存状况、销量评价、物流信息等,是电商企业实现商品管理、市场分析、营销策略制定等功能的得力助手。
|
6天前
|
存储 缓存 监控
如何高效爬取天猫商品数据?官方API与非官方接口全解析
本文介绍两种天猫商品数据爬取方案:官方API和非官方接口。官方API合法合规,适合企业长期使用,需申请企业资质;非官方接口适合快速验证需求,但需应对反爬机制。详细内容涵盖开发步骤、Python实现示例、反爬策略、数据解析与存储、注意事项及扩展应用场景。推荐工具链包括Playwright、aiohttp、lxml等。如需进一步帮助,请联系作者。
|
6天前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
7天前
|
JSON API 数据格式
淘宝商品评论API接口系列的应用与数据解析
在电商平台中,用户评论是了解商品质量、服务水平和用户满意度的重要数据来源。淘宝作为中国最大的电商平台,提供了商品评论API接口,帮助开发者获取和分析用户评价数据。本文将介绍淘宝商品评论API接口系列的作用、使用方法,并通过示例展示如何调用API并解析返回的JSON数据。
|
8天前
|
存储 自然语言处理 监控
深度解析淘宝商品评论API接口:技术实现与应用实践
淘宝商品评论API接口是电商数据驱动的核心工具,帮助开发者高效获取用户评价、画像及市场趋势。其核心功能包括多维度信息采集、筛选排序、动态更新、OAuth 2.0认证和兼容多种请求方式。通过该接口,开发者可进行商品优化、竞品分析、舆情监控等。本文详细解析其技术原理、实战应用及挑战应对策略,助力开启数据驱动的电商运营新篇章。
|
1月前
|
XML API 开发者
使用 API 接口获取京东商品详情全解析
京东作为头部电商平台,其商品数据极具价值。开发者可通过API接口获取商品详情、订单数据等信息,满足各种业务需求。使用前需注册账号并创建应用获取App Key和App Secret。调用流程包括认证授权、构建请求、发送请求及处理响应。注意事项包括遵守平台规则、控制调用频率和确保数据时效性。通过这些步骤,可为电商数据分析提供有力支持。
|
14天前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
114 0
|
2月前
|
数据挖掘 API 数据安全/隐私保护
深度解析:获取亚马逊畅销榜API接口及实战应用
Lazada 淘宝详情 API 是连接 Lazada 和淘宝商品数据的桥梁,帮助电商从业者获取淘宝商品的详细信息(如标题、描述、价格等),并应用于 Lazada 平台。它在市场调研、产品选品、价格策略和数据分析等方面为商家提供支持,助力优化运营策略。通过 Python 示例代码展示了 API 的实际应用,并强调了数据准确性、API 使用限制及数据安全的重要性。
64 10

热门文章

最新文章

推荐镜像

更多