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

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

AsyncExecutionInterceptor

终于,从此处开始。可议看出它是一个MethodInterceptor,是一个增强器了。但是从命名中也可以看出,它已经能够处理异步的执行了(比如基于XML方式的),但是还和注解无关


// 他继承自AsyncExecutionAspectSupport 来处理异步方法的处理,同时是个MethodInterceptor,来增强复合条件的方法
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered {
  ...
  // 显然这个方法它直接返回null,因为XML配置嘛~~~~
  @Override
  @Nullable
  protected String getExecutorQualifier(Method method) {
    return null;
  }
  // 这个厉害了。如果父类返回的defaultExecutor 为null,那就new一个SimpleAsyncTaskExecutor作为默认的执行器
  @Override
  @Nullable
  protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
    Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
    return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
  }
  // 最高优先级  希望的是最优先执行(XML配置就是这种优先级)
  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE;
  }
// 最重要的当然是这个invoke方法
  @Override
  @Nullable
  public Object invoke(final MethodInvocation invocation) throws Throwable {
    Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
    // 注意:此处是getMostSpecificMethod  拿到最终要执行的那个方法
    Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
    // 桥接方法~~~~~~~~~~~~~~
    final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
    // determine一个用于执行此方法的异步执行器
    AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
    if (executor == null) {
      throw new IllegalStateException(
          "No executor specified and no default executor set on AsyncExecutionInterceptor either");
    }
    // 构造一个任务,Callable(此处不采用Runable,因为需要返回值)
    Callable<Object> task = () -> {
      try {
        // result就是返回值
        Object result = invocation.proceed();
        // 注意此处的处理~~~~  相当于如果不是Future类型,就返回null了
        if (result instanceof Future) {
          return ((Future<?>) result).get();
        }
      }
      // 处理执行时可能产生的异常~~~~~~
      catch (ExecutionException ex) {
        handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
      }
      catch (Throwable ex) {
        handleError(ex, userDeclaredMethod, invocation.getArguments());
      }
      return null;
    };
    // 提交任务~~~~invocation.getMethod().getReturnType()为返回值类型
    return doSubmit(task, executor, invocation.getMethod().getReturnType());
  }
}


SimpleAsyncTaskExecutor:异步执行用户任务的SimpleAsyncTaskExecutor。每次执行客户提交给它的任务时,它会启动新的线程,并允许开发者控制并发线程的上限(concurrencyLimit),从而起到一定的资源节流作用。默认时,concurrencyLimit取值为-1,即**不启用**资源节流

所以它不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程(因此建议我们在使用@Aysnc的时候,自己配置一个线程池,节约资源)


AnnotationAsyncExecutionInterceptor


很显然,他是在AsyncExecutionInterceptor的基础上,提供了对@Async注解的支持。所以它是继承它的。


// 它继承自AsyncExecutionInterceptor ,只复写了一个方法
public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionInterceptor {
  ...
  // 由此可以见它就是去拿到@Async的value值。以方法的为准,其次是类上面的
  // 备注:发现这里是不支持EJB的@Asynchronous注解的,它是不能指定执行器的
  @Override
  @Nullable
  protected String getExecutorQualifier(Method method) {
    // Maintainer's note: changes made here should also be made in
    // AnnotationAsyncExecutionAspect#getExecutorQualifier
    Async async = AnnotatedElementUtils.findMergedAnnotation(method, Async.class);
    if (async == null) {
      async = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Async.class);
    }
    return (async != null ? async.value() : null);
  }
}


@Async注解失效得原因


如下:若我是这么写的


@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public Object hello() {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        helloPrivate(); // 同类内部方法调用
        return "service hello";
    }
    @Async
    @Override
    public Object hello2() {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        return null;
    }
}

最终输出为;


当前线程:http-nio-8080-exec-3
当前线程:http-nio-8080-exec-3


发现都是tomcat的线程。what?@Async异步线程木有生效????

这个也不是什么新问题了。在之前一篇博文:【小家java】Spring事务不生效的原因大解读

原因也是一样的,都是所谓的:代理失效问题。

如何解决?这里就说一种方案吧:


@Service
public class HelloServiceImpl implements HelloService {
    @Autowired
    private ApplicationContext applicationContext;
    @Override
    public Object hello() {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        // 从容器里拿到该类型的实例~~~~(注意:若是JDK代理,这里的类型只能传接口,而不能是实现类,否则NoSuchBean...)
        HelloService helloService = applicationContext.getBean(HelloService.class);
        // 然后用本实例去调用  而不是用默认的this去调用
        helloService.hello2();
        return "service hello";
    }
    @Async
    @Override
    public Object hello2() {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        return null;
    }
}


输出:


当前线程:http-nio-8080-exec-4
当前线程:SimpleAsyncTaskExecutor-2


显然这才是我们想要的结果。因此在同类调用的时候,一定要特别的小心,很有可能是不生效的。


各位可以看看你们同事写的代码,据我推测,肯定有同事写了这种不生效的代码~~


使用推荐配置


@EnableAsync //对应的@Enable注解,最好写在属于自己的配置文件上,保持内聚性
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); //核心线程数
        executor.setMaxPoolSize(20);  //最大线程数
        executor.setQueueCapacity(1000); //队列大小
        executor.setKeepAliveSeconds(300); //线程最大空闲时间
        executor.setThreadNamePrefix("fsx-Executor-"); 指定用于新创建的线程名称的前缀。
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略(一共四种,此处省略)
      // 这一步千万不能忘了,否则报错: java.lang.IllegalStateException: ThreadPoolTaskExecutor not initialized
        executor.initialize();
        return executor;
    }
    // 异常处理器:当然你也可以自定义的,这里我就这么简单写了~~~
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

使用Spring的异步,我个人十分建议配置上自己自定义的配置器(如上配置仅供参考),这样能够更好的掌握(比如异常处理AsyncUncaughtExceptionHandler~~~)


总结


有了Spring AOP整体运行原理作为基础,再看这些基于AOP的应用,简直行云流水。因此还是应正了那句话:你能走多远,就看你的根基有多稳。


最后,使用的有两个建议:


  1. 异步方法建议尽量处理耗时的任务,或者是处理不希望阻断主流程的任务(比如异步记录操作日志)
  2. 尽量为@Async准备一个专门的线程池来提高效率减少开销,而不要用Spring默认的SimpleAsyncTaskExecutor,它不是一个真正的线程池~

相关文章
|
2月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
70 1
|
2月前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
41 0
|
15天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
22天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
77 14
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
42 1
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
38 1
|
3月前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
38 0
|
1天前
|
Java 测试技术 应用服务中间件
Spring Boot 如何测试打包部署
本文介绍了 Spring Boot 项目的开发、调试、打包及投产上线的全流程。主要内容包括: 1. **单元测试**:通过添加 `spring-boot-starter-test` 包,使用 `@RunWith(SpringRunner.class)` 和 `@SpringBootTest` 注解进行测试类开发。 2. **集成测试**:支持热部署,通过添加 `spring-boot-devtools` 实现代码修改后自动重启。 3. **投产上线**:提供两种部署方案,一是打包成 jar 包直接运行,二是打包成 war 包部署到 Tomcat 服务器。
23 10
下一篇
开通oss服务