课程标题《站在动态代理模式角度分析@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容器中)
[
](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.官方建议最好单独新建一类,专门处理异步操作
this.addAsyncUser();----- 没有经过异步注解回调
走cglib回调 异步 开启线程处理