@Async 实现原理

@Async 是 Spring 提供的一个用于实现异步方法调用的注解,它可以帮助开发者在不阻塞主线程的情况下执行某些耗时操作。其底层原理主要依赖于 Spring AOP(面向切面编程)和动态代理技术


一、@Async 的作用

通过 @Async 注解的方法会被 Spring 自动封装成一个异步任务,在指定的线程池中异步执行,而不是在调用线程中同步执行。

@Service
public class MyService {

    @Async
    public void asyncMethod() {
        System.out.println("异步方法执行,线程:" + Thread.currentThread().getName());
    }
}

二、使用前提条件

  1. 需要启用异步支持:在配置类或启动类上添加 @EnableAsync
  2. 方法必须是 public 的(因为 AOP 只能拦截 public 方法)
  3. 方法不能被 privatestaticfinal 修饰
  4. 异步方法不能与调用者在同一个类中(否则不会触发代理)

三、@Async 的核心原理

1. 启用异步:@EnableAsync

@Configuration
@EnableAsync
public class AsyncConfig {
}
  • @EnableAsync 注解会导入 AsyncConfigurationSelector 类。
  • 最终会注册一个 AsyncAnnotationBeanPostProcessor 到 Spring 容器中。

2. 动态代理机制

Spring 使用 JDK 动态代理CGLIB 代理 来为标注了 @Async 的 Bean 创建代理对象。

当调用带有 @Async 注解的方法时,实际调用的是代理对象中的增强逻辑。

3. 代理逻辑:提交到任务执行器

当调用 @Async 方法时,代理对象并不会直接执行该方法,而是:

  • 将方法封装成一个任务(Runnable/Callable)
  • 提交给配置好的 TaskExecutor(默认是 SimpleAsyncTaskExecutor
  • 在新的线程中执行目标方法

四、源码

1. @EnableAsync 导入配置

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
}

2. AsyncConfigurationSelector 注册处理器

/**
 * AsyncConfigurationSelector 是 @EnableAsync 注解的核心处理器。
 * 它的作用是根据 @EnableAsync 的配置,决定导入哪些异步支持的配置类。
 */
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

    private static final String ASYNC_ANNOTATION_CONFIGURER_CLASS_NAME =
        "org.springframework.scheduling.annotation.ProxyAsyncConfiguration";

    private static final String JTA_ASYNC_CONFIGURER_CLASS_NAME =
        "org.springframework.transaction.aspectj.JtaAsyncConfiguration";

    /**
     * 根据 @EnableAsync 注解的 mode 属性选择不同的配置类:
     *
     * - 如果 mode = AdviceMode.PROXY(默认) → 导入 ProxyAsyncConfiguration(基于代理)
     * - 如果 mode = AdviceMode.ASPECTJ → 导入 JtaAsyncConfiguration(基于 AspectJ 编织)
     *
     * @param importingClassMetadata 注解元数据
     * @return 要导入的配置类名称数组
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 获取 @EnableAsync 注解的属性值
        Class<?> annoType = EnableAsync.class;
        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);

        // 获取 mode 属性,默认是 AdviceMode.PROXY
        AdviceMode adviceMode = attributes.getEnum("mode");

        // 获取 proxyTargetClass 属性,决定是否使用 CGLIB 代理(默认 false)
        boolean proxyTargetClass = attributes.getBoolean("proxyTargetClass");

        // 存放要导入的类名
        List<String> candidateNames = new ArrayList<>();

        // 添加 ProxyAsyncConfiguration,这是标准的基于代理的异步支持类
        candidateNames.add(ASYNC_ANNOTATION_CONFIGURER_CLASS_NAME);

        // 如果启用了 AspectJ 模式,则额外添加 JTA 异步支持类
        if (adviceMode == AdviceMode.ASPECTJ) {
            candidateNames.add(JTA_ASYNC_CONFIGURER_CLASS_NAME);
        }

        // 验证目标类是否存在(比如项目是否引入了 AspectJ 或 JTA 相关依赖)
        for (String candidate : candidateNames) {
            if (!ClassUtils.isPresent(candidate, getClass().getClassLoader())) {
                throw new IllegalArgumentException("Class not found: " + candidate);
            }
        }

        // 返回要导入的类名数组
        return candidateNames.toArray(new String[0]);
    }

    /**
     * 返回要注册的 ImportSelector 的顺序,用于控制导入顺序。
     */
    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }
}

3. ProxyAsyncConfiguration 配置类

/**
 * ProxyAsyncConfiguration 是 @EnableAsync 注解导入的配置类之一。
 * 它负责注册 AsyncAnnotationBeanPostProcessor,并从容器中获取用户自定义的异步配置(如线程池和异常处理器)。
 */
@Configuration
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {

    /**
     * 自动注入容器中所有的 AsyncConfigurer Bean。
     * AsyncConfigurer 接口允许开发者自定义线程池和异常处理器。
     *
     * 这个方法会在容器启动时被调用。
     *
     * @param configurers 容器中所有实现了 AsyncConfigurer 接口的 Bean
     */
    @Autowired(required = false)
    void setConfigurers(Collection<AsyncConfigurer> configurers) {
        if (CollectionUtils.isEmpty(configurers)) {
            // 如果没有找到任何 AsyncConfigurer,使用默认配置(即 null)
            this.setAsyncExecutor(null);
            this.setExceptionHandler(null);
        } else if (configurers.size() == 1) {
            // 如果只有一个 AsyncConfigurer,则使用它提供的配置
            AsyncConfigurer configurer = configurers.iterator().next();
            this.setAsyncExecutor(configurer.getAsyncExecutor());
            this.setExceptionHandler(configurer.getAsyncUncaughtExceptionHandler());
        } else {
            // 如果有多个 AsyncConfigurer,抛出异常,避免歧义
            throw new IllegalStateException("...");
        }
    }

    /**
     * 注册 AsyncAnnotationBeanPostProcessor Bean。
     * 这个后置处理器会为标注了 @Async 的方法创建代理对象,从而实现异步调用。
     *
     * @return AsyncAnnotationBeanPostProcessor 实例
     */
    @Bean(name = AsyncAnnotationBeanPostProcessor.BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public AsyncAnnotationBeanPostProcessor asyncAnnotationBeanPostProcessor() {
        AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();

        // 设置线程池,优先使用用户自定义的 Executor,否则使用默认的
        bpp.setExecutor(this.asyncExecutor);

        // 设置异常处理器,优先使用用户自定义的 AsyncUncaughtExceptionHandler
        bpp.setExceptionHandler(this.asyncUncaughtExceptionHandler);

        return bpp;
    }
}

4. AsyncAnnotationBeanPostProcessor 创建代理

该处理器会在 Bean 初始化阶段判断是否包含 @Async 注解,并创建代理对象。

/**
 * BeanPostProcessor 实现类,用于为标注了 @Async 注解的方法创建代理对象。
 * 它会在 Bean 初始化阶段对符合条件的 Bean 进行 AOP 增强,从而实现异步调用。
 */
public class AsyncAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, BeanFactoryAware {

    // 切面对象,用于匹配带有 @Async 注解的方法
    private final Advisor advisor;

    // 线程池执行器,默认使用 SimpleAsyncTaskExecutor
    @Nullable
    private Executor executor;

    // 异常处理器,用于处理异步方法中的未捕获异常
    @Nullable
    private AsyncUncaughtExceptionHandler exceptionHandler;

    /**
     * 默认构造函数,创建一个支持标准 @Async 注解的切面
     */
    public AsyncAnnotationBeanPostProcessor() {
        this(null, null);
    }

    /**
     * 构造函数,允许传入自定义线程池和异常处理器
     */
    public AsyncAnnotationBeanPostProcessor(@Nullable Executor executor,
                                            @Nullable AsyncUncaughtExceptionHandler exceptionHandler) {

        this.executor = executor;
        this.exceptionHandler = exceptionHandler;

        // 设置优先级,确保异步增强在其他 AOP 拦截之前生效
        setBeforeExistingAdvisors(true);

        // 创建 AsyncAnnotationAdvisor,这是真正负责匹配 @Async 方法并织入异步逻辑的核心组件
        this.advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
    }

    /**
     * 设置 BeanFactory,注入容器上下文
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        // 调用父类设置 BeanFactory(如果有的话)
        if (this.advisor instanceof BeanFactoryAware) {
            ((BeanFactoryAware) this.advisor).setBeanFactory(beanFactory);
        }
    }

    /**
     * 在 Bean 初始化完成后进行处理:
     * 如果该 Bean 匹配 AsyncAnnotationAdvisor 的 Pointcut(即包含 @Async 方法),
     * 则为其创建代理对象(JDK 动态代理或 CGLIB 代理),织入异步逻辑。
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 如果没有配置 Advisor 或者是 AopInfrastructureBean 类型(Spring 内部使用的 Bean),则跳过
        if (this.advisor == null || bean instanceof AopInfrastructureBean) {
            return bean;
        }

        // 获取目标类(防止被代理影响类型判断)
        Class<?> targetClass = ProxyFactory.getProxyTargetClass(bean);

        // 判断当前类是否符合 Async 的 Pointcut 条件(即是否有 @Async 方法)
        if (AopUtils.canApply(this.advisor.getPointcut(), targetClass)) {
            // 创建代理工厂
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            // 添加 Async 的切面逻辑
            proxyFactory.addAdvisor(this.advisor);
            // 根据是否是 CGLIB 代理来决定是否代理目标类
            proxyFactory.setProxyTargetClass(AopUtils.isCglibProxyClassName(bean.getClass().getName()));
            // 返回代理后的对象
            return proxyFactory.getProxy();
        }

        // 否则返回原始对象
        return bean;
    }

    /**
     * 设置是否在已有 Advisor 之前应用此增强器
     */
    protected void setBeforeExistingAdvisors(boolean beforeExistingAdvisors) {
        if (this.advisor instanceof AbstractPointcutAdvisor) {
            ((AbstractPointcutAdvisor) this.advisor).setOrder(
                beforeExistingAdvisors ? Ordered.HIGHEST_PRECEDENCE : Ordered.LOWEST_PRECEDENCE);
        }
    }

    /**
     * 设置自定义线程池
     */
    public void setExecutor(Executor executor) {
        this.executor = executor;
        if (this.advisor instanceof AsyncAnnotationAdvisor) {
            ((AsyncAnnotationAdvisor) this.advisor).setExecutor(executor);
        }
    }

    /**
     * 设置自定义异常处理器
     */
    public void setExceptionHandler(AsyncUncaughtExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
        if (this.advisor instanceof AsyncAnnotationAdvisor) {
            ((AsyncAnnotationAdvisor) this.advisor).setExceptionHandler(exceptionHandler);
        }
    }

    /**
     * 返回当前排序顺序,保证 Async 增强优先于其他 Advisor
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

5. AsyncAnnotationAdvisor 创建AsyncExecutionInterceptor

/**
 * AsyncAnnotationAdvisor 是一个切面(Advisor),用于识别标注了 @Async 注解的方法,
 * 并为其织入异步执行逻辑(由 AsyncExecutionInterceptor 实现)。
 */
@SuppressWarnings("serial")
class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {

    // 切点(Pointcut),用于匹配标注了 @Async 注解的方法
    private final Pointcut pointcut;

    // 增强逻辑(Advice),真正负责异步方法的拦截与执行
    private final Advice advice;

    // 当前 BeanFactory,用于获取容器中的线程池等配置
    @Nullable
    private BeanFactory beanFactory;

    /**
     * 构造函数,创建 AsyncAnnotationAdvisor 实例。
     *
     * @param executor 可选的线程池,如果不传,默认使用 SimpleAsyncTaskExecutor
     * @param exceptionHandler 可选的异常处理器
     */
    public AsyncAnnotationAdvisor(@Nullable Executor executor,
                                  @Nullable AsyncUncaughtExceptionHandler exceptionHandler) {

        // 创建 Pointcut:匹配所有标注了 @Async 注解的方法
        this.pointcut = buildPointcut();

        // 创建 Advice:实际增强逻辑,负责将方法调用提交到线程池中异步执行
        this.advice = buildAdvice(executor, exceptionHandler);
    }

    /**
     * 构建切点(Pointcut),用于匹配带有 @Async 注解的方法。
     *
     * 默认实现是使用 AnnotationMatchingPointcut 匹配 @Async 注解
     */
    protected Pointcut buildPointcut() {
        return new AnnotationMatchingPointcut(Async.class);
    }

    /**
     * 构建增强逻辑(Advice),即 AsyncExecutionInterceptor。
     * 它会在调用目标方法时将其封装为任务并提交到线程池中执行。
     *
     * @param executor 线程池
     * @param exceptionHandler 异常处理器
     */
    protected Advice buildAdvice(@Nullable Executor executor,
                                 @Nullable AsyncUncaughtExceptionHandler exceptionHandler) {

        // 创建 AsyncExecutionInterceptor 实例
        AsyncExecutionInterceptor interceptor = new AsyncExecutionInterceptor();

        // 设置默认使用的线程池
        interceptor.setDefaultExecutor(executor);

        // 设置未捕获异常的处理方式
        interceptor.setExceptionLogger(exceptionHandler);

        return interceptor;
    }

    /**
     * 获取切点(Pointcut)
     */
    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    /**
     * 获取增强逻辑(Advice)
     */
    @Override
    public Advice getAdvice() {
        return this.advice;
    }

    /**
     * 设置 BeanFactory,注入容器上下文。
     * 供内部使用的 Advice 可以通过它获取容器中的 Bean(如自定义线程池等)
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;

        if (this.advice instanceof BeanFactoryAware) {
            ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
        }
    }

    /**
     * AsyncAnnotationAdvisor 使用的 MethodMatcher,用于判断方法是否被 @Async 注解标注。
     */
    private static class AsyncClassOrMethodRuleMethodMatcher extends StaticMethodMatcher {

        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            // 判断当前方法或类上是否有 @Async 注解
            return AnnotatedElementUtils.hasAnnotation(method, Async.class) ||
                   AnnotatedElementUtils.hasAnnotation(targetClass, Async.class);
        }
    }
}

6. AsyncExecutionInterceptor 拦截方法调用

当调用 @Async 方法时,由 AsyncExecutionInterceptor 拦截并处理异步逻辑:

/**
 * AsyncExecutionInterceptor 是 Spring 中负责拦截 @Async 注解方法的核心类。
 * 它将目标方法封装为异步任务,并提交到线程池中执行。
 */
@SuppressWarnings("serial")
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor {

    /**
     * 构造函数,创建一个 AsyncExecutionInterceptor 实例。
     *
     * @param defaultExecutor 默认的线程池(Executor)
     * @param exceptionHandler 异常处理器,用于处理异步方法中未捕获的异常
     */
    public AsyncExecutionInterceptor(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) {
        super(defaultExecutor);
        this.exceptionHandler = exceptionHandler;
    }

    /**
     * 这是核心的拦截方法。
     * 当调用标注了 @Async 注解的方法时,会进入这个方法。
     *
     * @param invocation MethodInvocation 对象,包含目标方法信息
     * @return 方法执行结果(通常是 Future 或 CompletableFuture 类型)
     * @throws Throwable 如果目标方法抛出异常
     */
    @Override
    @Nullable
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        // 获取目标方法对象
        Method method = invocation.getMethod();

        // 确定当前方法使用的线程池(优先使用方法级别的配置,否则使用默认)
        Executor executor = determineAsyncExecutor(method);

        if (executor == null) {
            throw new IllegalStateException(
                "No executor specified for async method '" + method.toGenericString() + "'");
        }

        // 将目标方法封装成 Callable 任务
        Callable<Object> task = () -> {
            try {
                // 执行目标方法
                Object result = invocation.proceed();
                if (result instanceof Future) {
                    // 如果返回值是 Future 类型,则等待其完成以确保异常传播
                    Future<?> futureResult = (Future<?>) result;
                    return futureResult.get();
                }
                return result;
            } catch (Throwable ex) {
                // 处理目标方法抛出的异常
                handleUnexpectedAsyncException(ex, method, invocation.getArguments());
                return null;
            }
        };

        // 提交任务到线程池执行,并根据方法返回类型决定如何包装结果
        return doSubmit(task, executor, method.getReturnType());
    }

    /**
     * 根据目标方法的返回类型,将 Callable 任务提交到线程池中执行。
     *
     * @param task 要执行的任务(Callable)
     * @param executor 使用的线程池
     * @param returnType 目标方法的返回类型
     * @return 返回值可能是 Future、CompletableFuture 或 void
     */
    protected Object doSubmit(Callable<Object> task, final Executor executor, Class<?> returnType) {
        if (CompletableFuture.class.isAssignableFrom(returnType)) {
            // 如果方法返回 CompletableFuture 类型,则使用 supplyAsync 包装任务
            return CompletableFuture.supplyAsync(() -> {
                try {
                    return task.call();
                } catch (Throwable ex) {
                    throw new AsyncExecutionException(ex);
                }
            }, executor);
        } else if (ListenableFuture.class.isAssignableFrom(returnType)) {
            // 如果返回 ListenableFuture(Spring 自定义扩展),则使用 TaskExecutorAdapter 包装
            return new TaskExecutorAdapter(executor).submitListenable(task);
        } else if (Future.class.isAssignableFrom(returnType)) {
            // 如果返回 Future 类型,则使用线程池 submit 提交任务
            return executor.submit(task);
        } else {
            // 如果返回 void 或其他类型,直接提交任务并返回 null
            executor.execute(task);
            return null;
        }
    }

    /**
     * 处理异步方法中发生的未预期异常。
     * 默认情况下会调用 AsyncUncaughtExceptionHandler 来处理。
     *
     * @param ex 异常对象
     * @param method 发生异常的方法
     * @param params 方法参数
     */
    protected void handleUnexpectedAsyncException(Throwable ex, Method method, Object[] params) {
        if (this.exceptionHandler != null) {
            // 使用用户自定义的异常处理器
            this.exceptionHandler.handleUncaughtException(ex, method, params);
        } else {
            // 否则抛出运行时异常
            throw new AsyncExecutionException("Unexpected exception occurred invoking async method: " + method, ex);
        }
    }

    /**
     * 设置默认线程池。
     */
    public void setDefaultExecutor(@Nullable Executor defaultExecutor) {
        super.setDefaultExecutor(defaultExecutor);
    }

    /**
     * 设置异常处理器。
     */
    public void setExceptionLogger(@Nullable AsyncUncaughtExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
    }

    /**
     * 内部异常类,用于封装异步执行中的异常。
     */
    static final class AsyncExecutionException extends RuntimeException {
        AsyncExecutionException(Throwable cause) {
            super(cause);
        }
    }
}

五、自定义线程池配置

可以通过自定义 TaskExecutor 来控制异步方法使用的线程池:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-pool-");
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            System.err.println("异步方法异常: " + ex.getMessage());
        };
    }
}

六、注意事项 & 常见问题

问题原因
@Async 不生效方法不是 public / 被 final/static 修饰 / 同一个类中调用
默认线程池性能差使用 SimpleAsyncTaskExecutor,每个任务都新建线程
异常无法捕获异步方法抛出的异常需要自定义 AsyncUncaughtExceptionHandler 处理

七、完整代码

1. 启动类

@SpringBootApplication
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2. Service 层

@Service
public class DemoService {

    @Async
    public void sayHelloAsync(String name) {
        System.out.println("Hello " + name + " from thread: " + Thread.currentThread().getName());
    }
}

3. Controller 层

@RestController
@RequestMapping("/api")
public class DemoController {

    @Autowired
    private DemoService demoService;

    @GetMapping("/hello")
    public String hello(@RequestParam String name) {
        demoService.sayHelloAsync(name);
        return "请求已异步发送";
    }
}

八、总结

组件作用
@EnableAsync开启异步功能
AsyncAnnotationBeanPostProcessor创建带有异步能力的代理对象
AsyncExecutionInterceptor拦截方法调用,提交到线程池执行
TaskExecutor执行异步任务的线程池
@Async标记某个方法为异步方法

如需进一步深入源码,可以查看以下类:

  • org.springframework.aop.interceptor.AsyncExecutionInterceptor
  • org.springframework.context.annotation.EnableAsync
  • org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor
@Async注解在Spring框架中用于标识一个方法是异步执行的。然而,使用@Async注解可能会带来一些潜在的危害。 首先,使用@Async注解需要在配置类上添加@EnableAsync注解,以激活异步功能。这就意味着在使用@Async注解之前,需要对配置进行一些修改,可能会增加代码复杂性和维护成本。 其次,@Async注解是通过AsyncAnnotationBeanPostProcessor这个类实现的。这个类会处理@Async注解并生成代理对象。然而,使用@Async注解后,生成的代理对象与早期暴露出去的对象可能不一样。这可能导致一些问题,比如AOP切面无法正确拦截异步方法的调用。 另外,使用@Async注解需要使用线程池来实现异步执行。虽然可以自己通过线程池来实现异步,但是需要手动管理线程池的创建和销毁。如果线程池的配置不当,可能会导致系统的资源消耗过大或线程池满载而导致请求阻塞。 综上所述,使用@Async注解可能会引入一些潜在的危害,需要在配置和线程池管理方面进行额外的注意和处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [@Async注解的坑,小心](https://blog.csdn.net/YYniannian/article/details/125737301)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Spring中异步注解@Async的使用、原理及使用时可能导致的问题](https://blog.csdn.net/qq_41907991/article/details/107500036)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值