Spring 源码解析 | Aop 源码实现(一)(上)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本次我们一起来聊一下 Spring Aop 的原理。本文主要是通过一个简单的 Aop 案例,然后结合 Spring 的源码进行分析, Spring Aop 介入时机; Spring Aop 初始化等。环境介绍:JDK 17Spring 6.0.0-SNAPSHOT

案例介绍


我们在业务中可能用到 Redis 作为分布式锁,如果我们手动去 lock, unLock。 这样做的缺点就是代码量比较大,结合我们当前的 Aop 原理背景, 我们可以对我们业务开发中的 Lock 做一个简单的封装。


案例代码


代码如下所示:


@Component
@Aspect
public class DistributedLockAspect {
  private static final Logger logger = LoggerFactory.getLogger(DistributedLockAspect.class);
  @Pointcut("@annotation(com.summer.test.distributedlock.DistributedLock)")
  public void distributedLockPointCut() {
    // distributedLockPointCut
  }
  @Around("distributedLockPointCut()")
  public Object distributedLockAround(ProceedingJoinPoint pjp) throws Throwable {
    Method method = ((MethodSignature) pjp.getSignature()).getMethod();
    DistributedLock ub = method.getAnnotation(DistributedLock.class);
    String lockKey = lockKey(ub.key(), pjp);
    long timeOut = ub.timeOut();
    logger.debug("Start Redis Distributed Lock, Lock key:{}", lockKey);
    try {
      // todo redis lock
      Object result = pjp.proceed();
      logger.debug("End Redis Distributed Lock, Lock key:{}", lockKey);
      return result;
    } catch (Throwable ex) {
      logger.warn("Error Redis Distributed Lock, Lock key:{}", lockKey);
      throw ex;
    }
  }
  private String lockKey(String key, ProceedingJoinPoint joinPoint) {
    SpelExpressionParser parser = new SpelExpressionParser();
    DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
    // 通过joinPoint获取被注解方法
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Method method = methodSignature.getMethod();
    // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
    String[] paramNames = nameDiscoverer.getParameterNames(method);
    // 解析过后的Spring表达式对象
    Expression expression = parser.parseExpression(key);
    // spring的表达式上下文对象
    EvaluationContext context = new StandardEvaluationContext();
    // 通过joinPoint获取被注解方法的形参
    Object[] args = joinPoint.getArgs();
    // 给上下文赋值
    if (paramNames != null) {
      for (int i = 0; i < args.length; i++) {
        context.setVariable(paramNames[i], args[i]);
      }
    }
    // 表达式从上下文中计算出实际参数值
    return Objects.requireNonNull(expression.getValue(context)).toString();
  }
}


案例总结


在上述代码中我有用到 @Aspect@Pointcut@Around 这些概念我先不展开介绍。我们先看看如何使用。具体的概念介绍可以参考 Spring 官网, 或者我之前的文章 Spring Aop 详解


@DistributedLock(key = "'user_service:user_opt:' + #userModel.name")
public void save(UserModel userModel) {
    // todo
}


通过 @DistributedLock 降低了代码的复杂度,而且这样做也利于我们公共组件的封装。


Spring Aop 介入时机


方法入口


Spring Aop 是在 Spring 依赖注入完成之后,初始化 Bean 方法AbstractAutowireCapableBeanFactory.java#initializeBean� 中执行的。我们先来看看具体的代码。


// bean 初始化方法
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    invokeAwareMethods(beanName, bean);
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 初始化之前
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    try {
        // 执行初始化方法
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
    }
    if (mbd == null || !mbd.isSynthetic()) {
        // 初始化之后,这里会判断是否需要执行 Aop 代理
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
}


核心处理类


核心处理类是 AbstractAutoProxyCreator 它是实现了 BeanPostProcessor 我们可以先看看它的 postProcessAfterInitialization 方法


public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}


到了这里我们就可以很容易知道  wrapIfNecessary 就是核心的逻辑处理。


Spring Aop 初始化


我们先来看看 wrapIfNecessary  的具体实现:


protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    // 判断是否需要进行 Aop
    // Object[] DO_NOT_PROXY = null;
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 创建代理对象
        // new SingletonTargetSource(bean) 被代理对象,就是 Bean
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}


看到上面的代码我们先梳理一下 这个代理的过程:


  1. 首先获取 advice


  1. 然后判断是否需要代理


  1. 然后执行代理


获取 Advice


获取的入口方法是 getAdvicesAndAdvisorsForBean


protected Object[] getAdvicesAndAdvisorsForBean(
    Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    // 寻找匹配的 advisor
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}


然后通过 findEligibleAdvisors 寻找匹配的 advisor


protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 找到所有的 Advisor
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 筛选
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        // 排序
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}


相关文章
|
2月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
144 6
|
8天前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
52 29
|
5天前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
23 3
|
7天前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
16天前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
1月前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
128 25
|
1月前
|
XML 安全 Java
Spring AOP—深入动态代理 万字详解(通俗易懂)
Spring 第四节 AOP——动态代理 万字详解!
95 24
|
7天前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
15天前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
2月前
|
XML Java 开发者
Spring底层架构核心概念解析
理解 Spring 框架的核心概念对于开发和维护 Spring 应用程序至关重要。IOC 和 AOP 是其两个关键特性,通过依赖注入和面向切面编程实现了高效的模块化和松耦合设计。Spring 容器管理着 Beans 的生命周期和配置,而核心模块为各种应用场景提供了丰富的功能支持。通过全面掌握这些核心概念,开发者可以更加高效地利用 Spring 框架开发企业级应用。
92 18

推荐镜像

更多