这篇文章算是对之前阅读、学习、实践的一个总结。
本文主要是讲讲在Spring中,通过@Autowired
之类注解,注入的对象到底是什么?
同时引申出一些关于:AOP使用的一些细节。
先上代码:
WhatService
,其中就一个方法original
,作用是输出当前类的名称。@Service
public class WhatService {
@Async
public void original() {
System.out.println("Current is:" + this.getClass().toString());
}
}
WhatService
的类中有一个方法,输出注入的WhatService
的类名称。 private void auto() {
System.out.println("AutoWired Class:" + whatService.getClass());
}
SpringBoot
,同时在启动类傻上多加了注解@EnableAsync
@Test
public void testWhatIs() throws InterruptedException {
whatService.original();
TimeUnit.SECONDS.sleep(15);
this.auto();
TimeUnit.SECONDS.sleep(15);
}
Current is:class club.hicode.service.WhatService
AutoWired Class:class club.hicode.service.WhatService$$EnhancerBySpringCGLIB$$57db94f9
看到这里你是否有点感觉或者有点疑问:为什么2者输出的类不一样。
第一个输出,通过@Autowired
注入的类,其输出结果是:class club.hicode.service.WhatService$$EnhancerBySpringCGLIB$$57db94f9
,请看最后的一个部分:WhatService$$EnhancerBySpringCGLIB$$57db94f9
,其中有一个关键字:CGLIB
。
分析到现在,你应该知道了,这个对象是通过AOP
代理过的对象。
那么为什么AOP要代理这个对象,或者说什么时候AOP要代理对象。
其实一切的根源在于代码中做了2个事情:
SpringBoot
启动上添加了EnableAsync
WhatService
的方法上添加了@Async
注解。其中1表示的是代码中需要启用Aync
注解,而WhatService
中确定有方法使用了@Async
。
Spring通过扫描注解获取需要进行的某种操作,然后通过AOP
动态代理来进行操作。既然是动态代理
比如对象就是代理对象
不会是原来的对象。
第二个输出就是自己当前的this
,这是代理内部的真实对象,所以结果是:class club.hicode.service.WhatService
用一个图来表示:
假如将类中的注解去掉:
@Service
public class WhatService {
public void original() {
System.out.println("Current is:" + this.getClass().toString());
}
}
然后在单元测试中注入并调用
public class WhatTest extends BaseJunit {
@Autowired
private WhatService whatService;
@Test
public void testWhatIs() {
System.out.println("AutoWired Class:" + whatService.getClass());
}
结果如下所示:
AutoWired Class:class club.hicode.service.WhatService
经过对比是不是发现了一些什么?我们现在尝试回答下这个问题:什么时候AOP要代理对象
当需要通过AOP进行某项操作的时候,Spring才会进行代理,如果不需要,默认情况下,Spring是不会多此一举把对象进行代理的。
比如在第二例子中,去掉了@Async
,此时注入输出的就就是原始的对象。一旦加上,因为需要AOP进行增强,所以AOP就进行代理了。
我们继续看如下例子
@Service
public class WhatService {
@Async
public void original() {
System.out.println("Original Thread is:" + Thread.currentThread().getName());
}
public void originalWrapper() {
System.out.println("Wrapper Thread is:" + Thread.currentThread().getName());
original();
}
}
然后通过单元测试进行测试
@Test
public void wrapperTest() throws InterruptedException {
System.out.println("AutoWired Class:" + whatService.getClass());
System.out.println("==============");
whatService.original();
TimeUnit.SECONDS.sleep(3);
System.out.println("==============");
whatService.originalWrapper();
TimeUnit.SECONDS.sleep(15);
}
结果是什么?
AutoWired Class:class club.hicode.service.WhatService$$EnhancerBySpringCGLIB$$9b0b36a9
==============
Original Thread is:mic-core-bus-%d1
==============
Wrapper Thread is:main
Original Thread is:main
通过现象2我们发现了?
Aysnc
注解的方法,输出的是线程池中某一个线程的名称。originalWrapper
来调用original
输出却是主线程的名称main
。思考下2是为什么?
虽然注入的WhatService
是经过代理的,但是其直接调用的是originalWrapper
,在内部通过this
调用了original
。请注意此时的this
可不是代理对象
,而就是最本真
的自己。
此处可以参见现象1中。
这一点是初学者非常容易搞错的地方:调用者到底是谁?
如果我就是在先调用originalWrapper
,但是又想使用@Async
的作用了?
我知道3个方法,这里说2个,另外一个,留给你自己去查。
方法1:
public void originalWrapper() {
System.out.println("Wrapper Thread is:" + Thread.currentThread().getName());
//从Spring上下文中获取
WhatService whatService=SpringContext.getBean(WhatService.class);
whatService.original();
}
方法2:将originalWrapper
放到另外一个方法中,然后注入WhatService
进行调用。
方法3:自己查... 0.0
当时在学习的时候,遇到这样一个问题,
@Async
public String whatIs() throws InterruptedException {
return this.getClass().toString();
}
我通过异步的方式获取值,但是打印的时候一直都是null
,这是为什么了?
在Async
的代码注释中说的很清楚 ,其返回值只可以是void
和Future
However, the return type is constrained to either void or Future. I
对于现象1和现象2,别忽视他,很多时候我们在代码中会又不少错误用法,特别是用到事务的时候,在一个不需要代理的方法中,调用事务方法,发现事务不生效,这种问题肯定不少,如果你看懂了这个文章,应该不会犯错了。
归根代理,当使用Spring的时候,注意调用者是谁?这个调用者是否经过了代理。
多想想为什么,多思考下。