精讲23种设计模式-003:站在SpringMVC源码角度分析@async失效之谜

1 异步注解失效之谜源码分析课程安排

课程内容

  1. 基于Jdk动态代理纯手写@async实现异步操作
  2. 基于SpringAop纯手写@async实现异步操作
  3. Spring中如何综合使用@Cglib与Jdk动态代理
  4. 站在代理分析为什么this不能经过代理类拦截
  5. 错用@Async会导致SpringMVC控制类无法注入,有了解过吗?
  6. 站在SpringMVC源码角度分析,为什么@Async异步注解会失效

2 简单回顾自定义注解实现方式

自定义注解
Java注解是Jdk1.5推出一个重大特性 可以标记在类、方法、属性上面

内置注解:
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。

元注解:
@Retention - 标识这个注解怎 么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 成员。
@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

@Target({ElementType.TYPE, ElementType.METHOD}) // Target注解的作用范围  TYPE用于类上的注解 METHOD用于方法上的注解
@Retention(RetentionPolicy.RUNTIME) // 注解生命周期,可以通过反射获取注解信息
@Documented
public @interface ExtAsync {
}

3 构建Jdk动态代理实现拦截目标方法

自定义注解如何能够生效?
代理模式(Aop)+反射技术

public class MayiktInvocationHandler implements InvocationHandler {

    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 使用反射技术执行目标方法
        System.out.println("目标方法执行开始>>>");
        Object result = method.invoke(target, args);
        System.out.println("目标方法执行结束>>>");
        return result;
    }

    /**
     * 生成代理类
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}
public interface OrderService {

    String addOrder();

    void addOrderLog();
}
public class OrderServiceImpl implements OrderService {

    private OrderService orderServiceProxy;

    @Override
    public String addOrder() {
        System.out.println(Thread.currentThread().getName() + ">>>流程1");
        orderServiceProxy.addOrderLog();
        System.out.println(Thread.currentThread().getName() + ">>>流程3");
        return "addOrder";
    }

    @Override
	@ExtAsync 
    public void addOrderLog() {
        System.out.println(Thread.currentThread().getName() + ">>>流程2");
    }

    public void setOrderServiceProxy(OrderService orderServiceProxy){
        this.orderServiceProxy = orderServiceProxy;
    }
}
public class Test001 {
    public static void main(String[] args) {
        OrderServiceImpl orderServiceImpl = new OrderServiceImpl();
        MayiktInvocationHandler mayiktInvocationHandler = new MayiktInvocationHandler(orderServiceImpl);
        // 使用javaJdk动态代理生成代理对象
        OrderService orderServiceProxy = mayiktInvocationHandler.getProxy();
        // 传递代理对象给目标对象
        orderServiceImpl.setOrderServiceProxy(orderServiceProxy);
        // 代理对象.addOrder()
        orderServiceProxy.addOrder();
    }
}

运行结果:
请添加图片描述
注意:OrderServiceImpl类中代理对象.addOrderLog()走两次invoke方法(代理对象需要在实现类中set赋值);this.addOrderLog()不走invoke方法

4 Jdk动态代理纯手写@async实现异步操作

重写InvocationHandler实现类的invoke方法

public class MayiktInvocationHandler implements InvocationHandler {

    private Object target;

    /**
     * 定义线程池
     */
    private ExecutorService executorService;

    public MayiktInvocationHandler(Object target) {
        this.target = target;
        executorService = Executors.newFixedThreadPool(10);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 使用反射技术执行目标方法
        // 以下直接判断接口无效,而应该判断目标对象
//        ExtAsync extAsync = method.getDeclaredAnnotation(ExtAsync.class);
        // 根据接口信息查找目标对象的方法
        Method methodImpl = target.getClass().getMethod(method.getName(), method.getParameterTypes());
        ExtAsync extAsync = methodImpl.getDeclaredAnnotation(ExtAsync.class);
        if (extAsync == null) {
            // 该方法上没有加上异步注解,直接调用目标方法
            return method.invoke(target, args);
        }
        // 单独开启一个线程异步处理目标方法
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    method.invoke(target, args);
                } catch (Exception e) {

                }
            }
        });
        return null;
    }

    /**
     * 生成代理类
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}

运行结果:
请添加图片描述

5 基于SpringAop手写@async实现异步操作

@Async实现异步操作原理:Aop机制环绕代理机制
使用Aop的机制判断方法上是否有加上异步注解,如果有加上则单独开启一个线程进行处理。

public interface MemberService {

    String addUser();
}
@RestController
@Slf4j
public class MemberServiceImpl implements MemberService {

    @Autowired
    private MemberServiceManage memberServiceManage;

    @Override
    @GetMapping("/addUser")
    public String addUser() {
        log.info(">>>流程1");
        memberServiceManage.addUserLog();
        log.info(">>>流程3");
        return "success";
    }

}
@Component
@Slf4j
public class MemberServiceManage {

    @ExtAsync
    public String addUserLog(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info(">>>流程2");
        return "success";
    }
}
@Component
@Aspect
@Slf4j
public class ExtAsyncAop {

    private ExecutorService executorService;

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

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

运行结果:
请添加图片描述

6 @async异步注解失效之谜效果演示

@RestController
@Slf4j
public class MemberServiceImpl implements MemberService {

    @Autowired
    private MemberServiceManage memberServiceManage;

    @Override
    @GetMapping("/addUser")
    public String addUser() {
        log.info(">>>流程1");
//        memberServiceManage.addUserLog();
        this.addUserLog();
        log.info(">>>流程3");
        return "success";
    }

    @Async()
    public String addUserLog() {
        try {
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        log.info(">>>流程2");
        return "success";
    }

}

运行结果:
请添加图片描述
注解底层原理 ==> Aop技术 ==> 动态代理

动态代理分为
1 Jdk动态代理,基于接口实现
2 Cglib动态代理,采用继承目标对象实现

如果控制类中有加上异步注解,并且有实现接口的情况下,则采用Jdk动态代理,控制类没有注册到SpirngMVC容器中,报404错误;
如果控制类中有加上异步注解,但是没有实现接口的情况下,则采用Cglib动态代理,控制类可以注册到SpringMVC容器中,但是异步注解会失效。

7 源码角度分析为什么加上@async注解会404

为什么控制类实现了接口无法注册到SpringMVC容器中?
站在SpringMVC源码角度分析,源码入口AbstractHandlerMethodMapping(判断哪些对象为控制类并且将对象注册到SpringMVC容器中)

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

8 @async注解失效之谜源码分析

异步注解@Async()为什么失效?

  1. 在当前类直接使用@Async失效是因为没有经过代理类,没有走拦截。如果要生效,要把生成好的代理类对象传给目标对象,再通过目标对象.addOrderLog()方法,这时候才会经过代理对象走invoke方法做拦截。
  2. 官方建议最好单独新建一类,专门处理异步操作

修改MemberServiceImpl类进行测试,手动获取代理类走代理类.addUserLog()方法

@RestController
@Slf4j
public class MemberServiceImpl {

    @Autowired
    private MemberServiceManage memberServiceManage;

    //    @Override
    @GetMapping("/addUser")
    public String addUser() {
        log.info(">>>流程1");
//        memberServiceManage.addUserLog();
        MemberServiceImpl memberServiceProxy = SpringUtils.getBean("memberServiceImpl");
//        this.addUserLog();
        memberServiceProxy.addUserLog();
        log.info(">>>流程3");
        return "success";
    }

    @Async()
    public String addUserLog() {
        try {
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        log.info(">>>流程2");
        return "success";
    }

}

运行结果:
请添加图片描述
源码下载地址(mayikt_designPattern_3.rar):
链接:https://pan.baidu.com/s/1wWKZN1MbXICZVW1Vxtwe6A
提取码:fire

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值