003:站在SpringMVC源码角度分析@async失效之谜
1 异步注解失效之谜源码分析课程安排
课程内容
- 基于Jdk动态代理纯手写@async实现异步操作
- 基于SpringAop纯手写@async实现异步操作
- Spring中如何综合使用@Cglib与Jdk动态代理
- 站在代理分析为什么this不能经过代理类拦截
- 错用@Async会导致SpringMVC控制类无法注入,有了解过吗?
- 站在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()为什么失效?
- 在当前类直接使用@Async失效是因为没有经过代理类,没有走拦截。如果要生效,要把生成好的代理类对象传给目标对象,再通过目标对象.addOrderLog()方法,这时候才会经过代理对象走invoke方法做拦截。
- 官方建议最好单独新建一类,专门处理异步操作
修改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