【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)(上)

简介: 【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)(上)

前言


在开发过程中,我们会遇到很多使用线程池的业务场景,例如异步短信通知、异步记录操作日志。大多数使用线程池的场景,就是会将一些可以进行异步操作的业务放在线程池中去完成。


例如在生成订单的时候给用户发送短信,生成订单的结果不应该被发送短信的成功与否所左右,也就是说生成订单这个主操作是不依赖于发送短信这个操作,所以我们就可以把发送短信这个操作置为异步操作。


那么本文就是来看看Spring中提供的优雅的异步处理方案:在Spring3中,Spring中引入了一个新的注解@Async,这个注解让我们在使用Spring完成异步操作变得非常方便


需要注意的是这些功能都是Spring Framework提供的,而非SpringBoot。因此下文的讲解都是基于Spring Framework的工程


Spring中用@Async注解标记的方法,称为异步方法,它会在调用方的当前线程之外的独立的线程中执行,其实就相当于我们自己new Thread(()-> System.out.println("hello world !"))这样在另一个线程中去执行相应的业务逻辑。


Demo


// @Async 若把注解放在类上或者接口上,那么他所有的方法都会异步执行了~~~~(包括私有方法)
public interface HelloService {
    Object hello();
}
@Service
public class HelloServiceImpl implements HelloService {
    @Async // 注意此处加上了此注解
    @Override
    public Object hello() {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        return "service hello";
    }
}


然后只需要在配置里,开启对异步的支持即可:


@Configuration
@EnableAsync // 开启异步注解的支持
public class RootConfig {
}


输出如下:(当前线程名)


当前线程:SimpleAsyncTaskExecutor-1


可以很明显的发现,它使用的是线程池SimpleAsyncTaskExecutor,这也是Spring默认给我们提供的线程池(其实它不是一个真正的线程池,后面会有讲述)。下面原理部分讲解后,你就能知道怎么让它使用我们自定义的线程池了~~~

@Async注解使用细节


  1. @Async注解一般用在方法上,如果用在类上,那么这个类所有的方法都是异步执行的;
  2. @Async可以放在任何方法上,哪怕你是private的(若是同类调用,请务必注意注解失效的情况~~~)
  3. 所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象
  4. @Async可以放在接口处(或者接口方法上)。但是只有使用的是JDK的动态代理时才有效,CGLIB会失效。因此建议:统一写在实现类的方法上
  5. 需要注解@EnableAsync来开启异步注解的支持
  6. 若你希望得到异步调用的返回值,请你的返回值用Futrue变量包装起来


需要额外导入哪些Jar包?


它的依赖包非常简单,只依赖一些Spring的核心包外加spring-aop,但是如果你已经导入了spring-webmvc这个jar,那就什么不需要额外导入了,因为都有了:


image.png


备注:它虽然依赖于Spring AOP,但是它并不需要导入aspectjweaver,因为它和AspectJ没有半毛钱关系


原理、源码解析


@EnableAsync


它位于的包名为org.springframework.scheduling.annotation,jar名为:spring-context


@EnableXXX这种设计模式之前有分析过多次,这个注解就是它的入口,因此本文也一样,从入口处一层一层的剖析:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
   //默认情况下,要开启异步操作,要在相应的方法或者类上加上@Async注解或者EJB3.1规范下的@Asynchronous注解。
   //这个属性使得开发人员可以自己设置开启异步操作的注解(可谓非常的人性化了,但是大多情况下用Spring的就足够了)
  Class<? extends Annotation> annotation() default Annotation.class;
  // true表示启用CGLIB代理
  boolean proxyTargetClass() default false;
  // 代理方式:默认是PROXY  采用Spring的动态代理(含JDK动态代理和CGLIB)
  // 若改为:AdviceMode.ASPECTJ表示使用AspectJ静态代理方式。
  // 它能够解决同类内方法调用不走代理对象的问题,但是一般情况下都不建议这么去做,不要修改这个参数值
  AdviceMode mode() default AdviceMode.PROXY;
  // 直接定义:它的执行顺序(因为可能有多个@EnableXXX)
  int order() default Ordered.LOWEST_PRECEDENCE;
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
  // May be used to determine the target executor to be used when executing this method
  // 意思是这个value值是用来指定执行器的(写入执行器BeanName即可采用特定的执行器去执行此方法)
  String value() default "";
}


最重要的,还是上面的@Import注解导入的类:AsyncConfigurationSelector


AsyncConfigurationSelector

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
  // 这类 我也不知道在哪?是用于支持AspectJ这种静态代理Mode的,忽略吧~~~~
  private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
      "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
  @Override
  @Nullable
  public String[] selectImports(AdviceMode adviceMode) {
    // 这里AdviceMode 进行不同的处理,从而向Spring容器注入了不同的Bean~~~
    switch (adviceMode) {
      // 大多数情况下都走这里,ProxyAsyncConfiguration会被注入到Bean容器里面~~~
      case PROXY:
        return new String[] { ProxyAsyncConfiguration.class.getName() };
      case ASPECTJ:
        return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
      default:
        return null;
    }
  }
}


AdviceModeImportSelector父类的这个抽象更加的重要。它的实现类至少有如下两个:


image.png


备注:TransactionManagementConfigurationSelector需要额外导入jar包:spring-tx

这个父类抽象得非常的好,它的作用:抽象实现支持了AdviceMode,并且支持通用的@EnableXXX模式。


//@since 3.1  它是一个`ImportSelector`
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
  // 默认都叫mode
  public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";
  // 显然也允许子类覆盖此方法
  protected String getAdviceModeAttributeName() {
    return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
  }
  // importingClassMetadata:注解的信息
  @Override
  public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
    // 这里泛型,拿到泛型类型~~~
    Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
    Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");
    // 根据类型,拿到该类型的这个注解,然后转换为AnnotationAttributes
    AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
    if (attributes == null) {
      throw new IllegalArgumentException(String.format( "@%s is not present annType.getSimpleName(), importingClassMetadata.getClassName()));
    }
    // 拿到AdviceMode,最终交给子类,让她自己去实现  决定导入哪个Bean吧
    AdviceMode adviceMode = attributes.getEnum(this.getAdviceModeAttributeName());
    String[] imports = selectImports(adviceMode);
    if (imports == null) {
      throw new IllegalArgumentException(String.format("Unknown AdviceMode: '%s'", adviceMode));
    }
    return imports;
  }
  // 子类去实现  具体导入哪个Bean
  @Nullable
  protected abstract String[] selectImports(AdviceMode adviceMode);
}


改抽象提供了支持AdviceMode的较为通用的实现,若我们自己想自定义,可以考虑实现此类。


由此可议看出,@EnableAsync最终是向容器内注入了ProxyAsyncConfiguration这个Bean。由名字可议看出,它是一个配置类。


ProxyAsyncConfiguration

// 它是一个配置类,角色为ROLE_INFRASTRUCTURE  框架自用的Bean类型
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
  // 它的作用就是诸如了一个AsyncAnnotationBeanPostProcessor,它是个BeanPostProcessor
  @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
    Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
    AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
    // customAsyncAnnotation:自定义的注解类型
    // AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation") 为拿到该注解该字段的默认值
    Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
    // 相当于如果你指定了AsyncAnnotationType,那就set进去吧
    if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
      bpp.setAsyncAnnotationType(customAsyncAnnotation);
    }
    // 只有自定义了AsyncConfigurer的实现类,自定义了一个线程执行器,这里才会有值
    if (this.executor != null) {
      bpp.setExecutor(this.executor);
    }
    // 同上,异步线程异常的处理器~~~~~
    if (this.exceptionHandler != null) {
      bpp.setExceptionHandler(this.exceptionHandler);
    }
    // 这两个参数,就不多说了。
    // 可以看到,order属性值,最终决定的是BeanProcessor的执行顺序的
    bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
    bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
    return bpp;
  }
}
// 它的父类:
@Configuration
public abstract class AbstractAsyncConfiguration implements ImportAware {
  // 此注解@EnableAsync的元信息
  protected AnnotationAttributes enableAsync;
  // 异步线程池
  protected Executor executor;
  // 异步异常的处理器
  protected AsyncUncaughtExceptionHandler exceptionHandler;
  @Override
  public void setImportMetadata(AnnotationMetadata importMetadata) {
    // 拿到@EnableAsync注解的元数据信息~~~
    this.enableAsync = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false));
    if (this.enableAsync == null) {
      throw new IllegalArgumentException("@EnableAsync is not present on importing class " + importMetadata.getClassName());
    }
  }
  /**
   * Collect any {@link AsyncConfigurer} beans through autowiring.
   */
   // doc说得很明白。它会把所有的`AsyncConfigurer`的实现类都搜集进来,然后进行类似属性的合并
   // 备注  虽然这里用的是Collection 但是AsyncConfigurer的实现类只允许有一个
  @Autowired(required = false)
  void setConfigurers(Collection<AsyncConfigurer> configurers) {
    if (CollectionUtils.isEmpty(configurers)) {
      return;
    }
        //AsyncConfigurer用来配置线程池配置以及异常处理器,而且在Spring环境中最多只能有一个
        //在这里我们知道了,如果想要自己去配置线程池,只需要实现AsyncConfigurer接口,并且不可以在Spring环境中有多个实现AsyncConfigurer的类。
    if (configurers.size() > 1) {
      throw new IllegalStateException("Only one AsyncConfigurer may exist");
    }
    // 拿到唯一的AsyncConfigurer ,然后赋值~~~~   默认的请参照这个类:AsyncConfigurerSupport(它并不会被加入进Spring容器里)
    AsyncConfigurer configurer = configurers.iterator().next();
    this.executor = configurer.getAsyncExecutor();
    this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler();
  }
}


从上可知,真正做文章的最终还是AsyncAnnotationBeanPostProcessor这个后置处理器,下面我们来重点看看它


AsyncAnnotationBeanPostProcessor


AsyncAnnotationBeanPostProcessor这个BeanPostBeanPostProcessor很显然会对带有能够引发异步操作的注解(比如@Async)的Bean进行处理

image.png


从该类的继承体系可以看出,大部分功能都是在抽象类里完成的,它不关乎于@Async,而是这一类技术都是这样子处理的。


首先,ProxyProcessorSupport这里就不用多说了,在讲解自动代理创建器的时候有说过。

小家Spring】Spring AOP的核心类:AbstractAdvisorAutoProxy自动代理创建器深度剖析(AnnotationAwareAspectJAutoProxyCreator)


按照我一贯的下关,我还是喜欢从底部往上进行分析,这样能够更无阻碍些。

相关文章
|
1月前
|
安全 Java Spring
Spring之Aop的底层原理
Spring之Aop的底层原理
|
2天前
|
监控 安全 Java
Spring cloud原理详解
Spring cloud原理详解
14 0
|
8天前
|
Java 开发者 微服务
Spring Cloud原理详解
【5月更文挑战第4天】Spring Cloud是Spring生态系统中的微服务框架,包含配置管理、服务发现、断路器、API网关等工具,简化分布式系统开发。核心组件如Eureka(服务发现)、Config Server(配置中心)、Ribbon(负载均衡)、Hystrix(断路器)、Zuul(API网关)等。本文讨论了Spring Cloud的基本概念、核心组件、常见问题及解决策略,并提供代码示例,帮助开发者更好地理解和实践微服务架构。此外,还涵盖了服务通信方式、安全性、性能优化、自动化部署、服务网格和无服务器架构的融合等话题,揭示了微服务架构的未来趋势。
32 6
|
13天前
|
负载均衡 Java 开发者
Spring Cloud:一文读懂其原理与架构
Spring Cloud 是一套微服务解决方案,它整合了Netflix公司的多个开源框架,简化了分布式系统开发。Spring Cloud 提供了服务注册与发现、配置中心、消息总线、负载均衡、熔断机制等工具,让开发者可以快速地构建一些常见的微服务架构。
|
18天前
|
安全 Java API
Spring工厂API与原理
Spring工厂API与原理
35 10
|
2月前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
54 0
|
2月前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
138 0
|
1月前
|
存储 JSON Java
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
45 2
|
2月前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
|
1月前
|
前端开发 Java 应用服务中间件
Springboot对MVC、tomcat扩展配置
Springboot对MVC、tomcat扩展配置