Spring Boot注解的幕后运作和自我调用问题

在开发Spring Boot应用程序时,我们经常使用如@Transactional、@Cacheable、@Retryable、@Validated、@Async等注解。通过这些注解,我们为我们的bean添加了补充逻辑,如将数据库操作封装在事务中或实现缓存机制。

然而,并非每个人都会思考它们在底层是如何工作的,以及使用它们可能会出现什么问题。在这篇文章中,让我们开始一段旅程,探索最流行的注解是如何工作的,以及什么是自我调用问题。

Spring Beans和代理

当一些注解,如@Transactional,被应用到beans的方法中时。Spring创建代理对象,这些对象拦截实际的方法调用,并在此之前或之后执行额外的逻辑。

Spring通过这些代理利用AOP(面向切面编程)来处理日志记录、安全和事务等横切关注点,这些关注点与主逻辑不同。这使我们能够保持核心业务逻辑的清洁,并且只专注于它,而不会被其他事项分心。

理解事物通常在我们看一个例子时会更容易。所以,让我们考虑下面的服务:

@Service
public class CacheableService {
  
  
    @Cacheable(cacheNames = "cache")
    public String getFromCache() {
        return "This value will be moved to cache and next time used from there";
    }
  
}

为了 CacheableService 开始使用缓存,我们 getFromCache() @Cacheable注释标记了该方法。Spring通过引入补充逻辑来承担增强bean的任务:

  • 如果缓存中不存在该值,则必须检索该值并将其放入缓存中。

  • 如果该值已存在于缓存中,则必须直接从缓存中检索该值。

这个过程涉及到代理的使用。在运行时,Spring不仅创建一个新的实例CacheableService,而且还生成一个代理类来伴随它。

简化的序列如下所示:

Spring Boot注解,幕后运作,自我调用问题,代理,@Cacheable,@Retryable,@Validated,@Async

因此,Spring 生成一个代理对象,它镜像现有方法,同时合并补充逻辑。

如果类实现了接口,则代理对象也实现了该接口,这种情况下使用动态代理。否则,代理使用CGLib扩展目标类。

最流行的注释如何在幕后工作的示例

代理的类是在运行时生成的。在我们的示例中CacheableService,生成的代理如下所示:

public class CacheableService$$SpringCGLIB$$0 extends CacheableService implements SpringProxy, Advised, Factory {
    ...
    private MethodInterceptor CGLIB$CALLBACK_0;
    ...
    @Override
    public final String getFromCache() {
        //execute using method interceptor
    }
    ...
}

我们可以看到使用了 CGLib,因为它

我们可以看到使用了 CGLib,因为它CacheableService没有实现任何接口,并且生成的代理只是对其进行了扩展。如果CacheableService实现了某个接口,则会使用动态代理,并且它将实现相同的接口。

Spring 在代理中使用MethodInterceptors,这些拦截器负责在实际方法调用周围添加补充逻辑

让我们看看方法拦截器内部发生了什么,这些方法拦截器与最常见的注释一起使用:

@Cacheable

让我们从@Cacheable注释开始,如本文中的示例所示CacheableService。在这种情况下,创建的代理使用CacheInterceptor

if (cacheHit != null && !hasCachePut(contexts)) {
    // If there are no put requests, just use the cache hit
    cacheValue = cacheHit.get();
    returnValue = wrapCacheValue(method, cacheValue);
} else {
    // Invoke the method if we don't have a cache hit
    returnValue = invokeOperation(invoker);
    cacheValue = unwrapReturnValue(returnValue);
}

方法拦截器的操作如下:它检查缓存中是否有特定值,如果找到则直接返回该值。如果缓存中不存在该值,拦截器将通过invokeOperation(目标方法调用)协调其计算。计算完成后,拦截器将值存储在缓存中,然后返回它。

@Cacheable

在事务注释的情况下,代理使用另一个MethodInterceptor实现 - TransactionInterceptor

TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
    // Target method invocation
    retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
    // target invocation exception -> transaction rollback
    completeTransactionAfterThrowing(txInfo, ex); // leads to transactionManager.rollback(...);
    throw ex;
} finally {
    cleanupTransactionInfo(txInfo);
}

我们们可以看到,其实这并没有那么困难。在方法调用之前启动一个新事务,如果没有错误发生则提交;否则,会发生事务回滚。

@Retryable

当涉及到@Retryable注释时,生成的代理在幕后利用RetryOperationsInterceptor和RetryTemplate :

while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
    try {
        ...
        T result = retryCallback.doWithRetry(context);
        doOnSuccessInterceptors(retryCallback, context, result);
        return result;
    } catch (Throwable e) {
        registerThrowable(retryPolicy, state, context, e);
        ...
    }
    ...
}

实际的目标方法在循环中执行,直到成功或超出错误限制。

@Validated

@NotBlank如果我们使用 @Validated 注解来注解某些组件,并且某些方法参数也使用、@Min、 等验证注解进行标记@Email,则MethodValidationInterceptor将被添加到生成的代理中。拦截器的工作原理如下:

try {
    result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
} catch (IllegalArgumentException ex) {
    ...
    result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
}
if (!result.isEmpty()) {
    throw new ConstraintViolationException(result);
}

@Async

在此注释的情况下,代理使用AsyncExecutionInterceptor和AsyncTaskExecutor调用目标方法:

if (CompletableFuture.class.isAssignableFrom(returnType)) {
    return executor.submitCompletable(task);
} else if (... some other cases ...){
} else if (Future.class.isAssignableFrom(returnType)) {
    return executor.submit(task);
} else if (void.class == returnType) {
    executor.submit(task);
    return null;
} else {
    throw new IllegalArgumentException("Invalid return type for async method (only Future and void supported): " + returnType);
}

自调用问题

我们已经了解到,Spring 创建代理对象来镜像现有的 bean 方法,并在目标方法调用之前或之后执行补充逻辑。

但是,如果我们从同一个 bean 中的另一个方法调用标有注

@Service
public class CacheableService {
  
    public String selfInvoke() {
        return getFromCache();
    }
  
    @Cacheable(cacheNames = "cache")
    public String getFromCache() {
        return "This value will be moved to cache and next time used from there";
    }
  
}

现在,CacheableService包含一个名为 的附加方法selfInvoke(),该方法未标记任何注释。该方法只是调用,这是一个用注释标记并在代理中增强以利用缓存的getFromCache()方法。@Cacheable

那么,问题就来了:该selfInvoke()方法会从缓存中检索值吗?

许多开发人员期望这应该可行并且缓存将被利用。然而,事实上,它并没有达到预期的效果。为了理解这一点,让我们分析一个包含 update CacheableService、其代理和方法调用的序列图selfInvoke()。

Spring Boot注解,幕后运作,自我调用问题,代理,@Cacheable,@Retryable,@Validated,@Async

所以,当我们调用selfInvoke()方法时,我们只访问getFromCache()目标bean中的方法。但是,为此方法启用缓存所做的所有增强都存在于getFromCache()代理内的重写方法中,不幸的是,该方法仍未被访问。

同样的问题也适用于其他注释,例如@Transactional、@Retryable、@Validated、@Async等。

事实上,有多种选择可以解决这个问题。一种方法涉及重构代码以防止自调用标记有注释的方法并仅从其他 bean 调用它们。或者,可以考虑过渡到 AspectJ 来创建代理(编译时编织),而不是依赖 CGLib 和动态代理。此外,还可以采用一种称为自注入的技术,其中 Bean 被注入到其自身中,并在注入的 Bean 上调用目标方法。然而,值得注意的是,后一种解决方案本质上是一种解决方法。

结论

@Transactional在本文中,我们解释了 Spring 如何使用代理向标有、@Cacheable、@Retryable、@Validated、@Async等注释的方法添加额外的逻辑。

我们还通过一些代码示例深入了解了这些注释在幕后如何工作。

但是,使用代理有时会导致自调用问题等问题。我们已经讨论了它是什么以及解决它的方法。

掌握这些概念很重要,我真的希望您觉得这篇文章很有趣。 

谢谢阅读!



文章来源地址https://www.toymoban.com/diary/share/297.html

到此这篇关于Spring Boot注解的幕后运作和自我调用问题的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

原文地址:https://www.toymoban.com/diary/share/297.html

如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用
上一篇 2023年08月25日 16:30
下一篇 2023年08月26日 15:53

相关文章

  • java spring boot 注解、接口和问题解决方法(持续更新)

    @RestController         是SpringMVC框架中的一个注解,它结合了@Controller和@ResponseBody两个注解的功能,用于标记一个类或者方法,表示该类或方法用于处理HTTP请求,并将响应的结果直接返回给客户端,而不需要进行视图渲染 @Controller         是Spring Framework中的注解,用于

    2024年02月06日
    浏览(53)
  • Spring 使用注解开发、代理模式、AOP

    在Spring4之后,要使用注解开发,必须要保证AOP的包导入了 项目搭建: 在配置文件中导入约束,增加注解支持 bean 实体类 @Component 注解 xml配置 测试: 属性如何注入 衍生的注解 @Component 有几个衍生的注解,我们在web开发中,会按照MVC三层架构分层 dao层: @Repository 等价于poj

    2024年02月13日
    浏览(51)
  • Spring boot注解讲解

    人不走空                                                                            目录         🌈个人主页:人不走空       💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨   注解 注解列表如下 JPA注解 作者其他作品:   @SpringBootApplication :申

    2024年02月19日
    浏览(48)
  • Spring boot 常见注解

    Spring Boot是一个基于Spring框架的快速开发框架,它通过自动化配置和约定优于配置的原则,简化了Spring应用程序的开发过程。Spring Boot可以帮助开发者快速构建独立的、生产级别的应用程序,并且可以与其他Spring框架和第三方库无缝集成。 Spring Boot提供了很多便利的特性,比如

    2024年01月18日
    浏览(41)
  • Spring Boot常用注解

    在 Spring Boot 中,有许多注解用于简化和标识应用程序的不同方面。以下是一些常用的 Spring Boot 注解: @SpringBootApplication : 用于标识主应用程序类。通常与 @EnableAutoConfiguration 、 @ComponentScan 和 @Configuration 一起使用,它是一个复合注解,用于简化配置。 @Controller : 用于标识控

    2024年01月19日
    浏览(42)
  • Spring Boot 学习之——@SpringBootApplication注解(自动注解原理)

    springboot是基于spring的新型的轻量级框架,最厉害的地方当属**自动配置。**那我们就可以根据启动流程和相关原理来看看,如何实现传奇的自动配置 从上面代码可以看出,Annotation定义(@SpringBootApplication)和类定义(SpringApplication.run)最为耀眼,所以要揭开SpringBoot的神秘面纱

    2024年01月25日
    浏览(49)
  • Spring Boot 启动注解分析

    虽然我们在日常开发中,Spring Boot 使用非常多,算是目前 Java 开发领域一个标配了,但是小伙伴们仔细想想自己的面试经历,和 Spring Boot 相关的面试题都有哪些?个人感觉应该是比较少的,Spring Boot 本质上还是曾经 SSM 那一套,只是通过各种 starter 简化了配置而已,其他都是

    2024年02月13日
    浏览(41)
  • Spring Boot 注解解读详解

    Spring Boot提供了大量的注解来简化Spring应用的开发。下面我们将详细介绍一些最常用的Spring Boot注解。 1. @SpringBootApplication 这是一个复合注解,用于标记应用的主类。它包含了以下三个注解: @SpringBootConfiguration :等同于Spring的@Configuration,标明该类是配置类,并会把该类作为

    2024年02月06日
    浏览(45)
  • Spring boot自定义注解

    定义一个注解类 使用AOP对注解进行解析,需要定义一个切面类,包括自定义的切点方法normalPointCut(),以及连接点的处理方法normalPointAround()。连接点中的ProceedingJoinPoint可以获取被代理类的方法属性等。 2.1 定义注解 2.2 实现参数解释器 记得实现WebMvcConfigurer 接口配置LimitReque

    2023年04月27日
    浏览(39)
  • Spring Boot常用注解详细说明

    Spring Boot是一个用于构建独立的、基于Spring框架的Java应用程序的开发框架。它提供了许多注解,用于简化开发过程并提供特定功能。下面是一些常用的Spring Boot注解,按照功能进行分类说明: @RestController :将类标记为RESTful风格的控制器,自动将返回值转换为JSON响应。 @Cont

    2024年02月03日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包