驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
谁在执行这个方法的思考
/    

谁在执行这个方法的思考

开篇

这篇文章算是对之前阅读、学习、实践的一个总结。

本文主要是讲讲在Spring中,通过@Autowired之类注解,注入的对象到底是什么?

同时引申出一些关于:AOP使用的一些细节。

现象1

样例

先上代码:

  1. 有一个类WhatService,其中就一个方法original,作用是输出当前类的名称。
@Service
public class WhatService {
    @Async
    public void original() {
        System.out.println("Current is:" + this.getClass().toString());
    }
}
  1. 然后在调用WhatService的类中有一个方法,输出注入的WhatService的类名称。
    private void auto() {
        System.out.println("AutoWired Class:" + whatService.getClass());
    }
  1. 然后分别调用输出,我用的是SpringBoot,同时在启动类傻上多加了注解@EnableAsync
    @Test
    public void testWhatIs() throws InterruptedException {
        whatService.original();
        TimeUnit.SECONDS.sleep(15);
        this.auto();
        TimeUnit.SECONDS.sleep(15);
    }
  1. 输出结果是什么?
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个事情:

  1. SpringBoot启动上添加了EnableAsync
  2. WhatService 的方法上添加了@Async注解。

其中1表示的是代码中需要启用Aync注解,而WhatService中确定有方法使用了@Async

Spring通过扫描注解获取需要进行的某种操作,然后通过AOP动态代理来进行操作。既然是动态代理比如对象就是代理对象不会是原来的对象。

第二个输出就是自己当前的this,这是代理内部的真实对象,所以结果是:class club.hicode.service.WhatService

用一个图来表示:

image-20180813195047654

假如将类中的注解去掉:

@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就进行代理了。

现象2

样例

我们继续看如下例子

@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我们发现了?

  1. 直接调用Aysnc注解的方法,输出的是线程池中某一个线程的名称。
  2. 通过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的代码注释中说的很清楚 ,其返回值只可以是voidFuture

However, the return type is constrained to either void or Future. I

总结

对于现象1和现象2,别忽视他,很多时候我们在代码中会又不少错误用法,特别是用到事务的时候,在一个不需要代理的方法中,调用事务方法,发现事务不生效,这种问题肯定不少,如果你看懂了这个文章,应该不会犯错了。

归根代理,当使用Spring的时候,注意调用者是谁?这个调用者是否经过了代理。

多想想为什么,多思考下。

骐骥一跃,不能十步。驽马十驾,功在不舍。