10.spring @async失效之谜

课程标题《站在动态代理模式角度分析@async注解失效之谜》
课程内容:
1.Jdk动态代理纯手写@async实现异步操作
2.基于SpringAop手写@async实现异步操作
3@async异步注解失效之谜效果演示
4.源码角度分析为什么加上@async注解会404
5.@async注解失效之谜源码分析

@async aop技术 底层基于代理模式封装

spring框架底层使用jdk动态代理呢?还是cglib代理呢?

被代理类 实现接口的情况下 jdk动态代理呢

被代理类没有实现接口的情况下cglib代理

@async注解失效之谜

基于JDK动态代理简单手写异步注解

package com.mayikt.proxy04;

import com.mayikt.proxy04.ext.MayiktAsync;

/**
 * @author 余胜军
 * @ClassName MayiktService
 * @qq 644064779
 * @addres www.mayikt.com
 * 微信:yushengjun644
 */
public interface MayiktService {
    @MayiktAsync
    void addMayikt();
}
package com.mayikt.proxy04.impl;

import com.mayikt.proxy04.MayiktService;
import com.mayikt.proxy04.ext.MayiktAsync;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;

/**
 * @author 余胜军
 * @ClassName MayiktServiceImpl
 * @qq 644064779
 * @addres www.mayikt.com
 * 微信:yushengjun644
 */
@Slf4j
public class MayiktServiceImpl implements MayiktService {
    @Override
    @MayiktAsync
    public void addMayikt() {
        log.info("<addMayikt>");
    }
}

package com.mayikt.proxy04;

import com.mayikt.proxy04.ext.MayiktAsync;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author 余胜军
 * @ClassName MayiktInvocationHandler
 * @qq 644064779
 * @addres www.mayikt.com
 * 微信:yushengjun644
 */
@Slf4j
public class ThreadInvocationHandler implements InvocationHandler {
    /**
     * 目标对象
     */
    private Object target;

    public ThreadInvocationHandler(Object target) {
        this.target = target;
    }

    private ExecutorService executorService = Executors.newCachedThreadPool();

    /**
     * 参数1:Object proxy JDK自动生成代理类
     * 参数2:目标对象的接口
     * 参数3:目标对象的接口 需要参数值
     * OrderService.addOrder();
     * -----MayiktInvocationHandler.invoke();
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //参数 那个对象中 该方法 该方法的时候传递参数值
        log.info("<目标方法之前执行....args:{}>", args);
        MayiktAsync mayiktAsync = method.getDeclaredAnnotation(MayiktAsync.class);
        if (mayiktAsync == null) {
            return method.invoke(target, args);
        }
        executorService.execute(() -> {
            try {
                method.invoke(target, args);// 底层基于java反射机制调用 该对象中 该方法
            } catch (Exception e) {
                log.error("e:{}", e);
            }
        });
//        Object result = method.invoke(target, args);// 底层基于java反射机制调用 该对象中 该方法
        log.info("<目标方法之后执行....args:{}>", args);
        return null;
    }
    // 底层 帮助自动生成代理类

    /**
     * 参数1:类加载器 传递 目标对象的类加载器
     * 参数2:目标对象实现的接口
     * JDK动态代理底层 生成的代理类  实现该接口
     *
     * @param <T> 传递 目标对象
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                this);
    }
}

基于aop的方式 封装异步注解

maven依赖

       <!--aop相关的依赖引入-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
package com.mayikt.ext;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
@Aspect
@Slf4j
public class ExtAsyncAop {

    private ExecutorService executorService;

    public ExtAsyncAop() {
        executorService = Executors.newFixedThreadPool(10);
    }

    // 只要方法上用注解,可以直接拿到方法,不需要再判断,减少代码冗余
    // 获取到方法上有加上ExtAsync直接进入环绕通知
    @Around(value = "@annotation(com.mayikt.ext.MayiktAsync)")
    public void doBefore(ProceedingJoinPoint joinPoint) throws Exception {
        log.info(">>>拦截到方法上有加上异步注解ExtAsync");
        executorService.execute(() -> {
            //执行目标方法
            try {
                joinPoint.proceed();
            } catch (Throwable throwable) {
                log.error("e:{}", throwable);
            }
        });
    }
}

为什么控制类实现了接口无法注册到SpringMVC容器中?

async注解失效之谜 接口无法注册到springmvc种 最后变成接口404呢

** $Proxy1 implements MayiktService
*** $Proxy1 注册到springmvc容器中?*

1.springmvc会从ioc容器中查找到所有的bean对象

2.需要判断每个类上 是否有加上@Controller

3.如果我们的类上有加上@Controller 则将bean对象 注册到 springmvc容器。

springmvc容器 分类

*$Proxy1 implements MayiktService{ —注册到 springmvc容器 不会的

}

判断代理类上是否有加上@Controller 没有的 则不会注册到 springmvc容器中

$Proxy1.class

1 Jdk动态代理,基于接口实现;

2 Cglib动态代理,采用继承目标对象实现;

3.如果控制类中有加上异步注解,并且有实现接口的情况下,则采用Jdk动态代理,生成的动态代理 控制类没有注册到SpirngMVC容器中,报404错误;

4.如果控制类中有加上异步注解,但是没有实现接口的情况下,则采用Cglib动态代理,控制类可以注册到SpringMVC容器中,但是异步注解会失效,原因是没有经过代理。

springboot启动时加上该参数:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
站在SpringMVC源码角度分析,源码入口AbstractHandlerMethodMapping(判断哪些对象为控制类并且将对象注册到SpringMVC容器中)

img

img

[

](https://blog.csdn.net/u012425860/article/details/120538924)

原理:采用Jdk动态代理技术,代理基于接口实现,而接口上没有加上@RestController注解,所以代理对象无法注册到SpringMVC容器中;而采用Cglib生成的代理对象继承了目标对象@RestController注解,这时候Cglib生成的代理对象是可以注入到SpringMVC容器中。

cglib 生成 代理类 继承 目标类MayiktServiceImpl 而

我们目标类上是加上@RestController

async注解失效情况

1.注解@Async的方法不是public方法

2.注解@Async的返回值只能为void或Future

3.注解@Async方法使用static修饰也会失效

4.spring无法扫描到异步类,没加注解@Async或@EnableAsync注解

5.调用方与被调用方不能在同一个类

6.类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象

7.在Async方法上标注@Transactional是没用的.但在Async方法调用的方法上标注@Transcational是有效的

1.在当前类直接使用@Async失效是因为没有经过代理类,没有走拦截。如果要生效,要把生成好的代理类对象传给目标对象,再通过目标对象.addOrderLog()方法,这时候才会经过代理对象走invoke方法做拦截。

2.官方建议最好单独新建一类,专门处理异步操作

img

this.addAsyncUser();-----img 没有经过异步注解回调

img 走cglib回调 异步 开启线程处理

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值