一、环境与问题
环境
spring boot的版本是1.2.1.RELEASE、JDK版本是1.7
问题
A服务 PeopleService
调用B服务 HelloService
,其中B服务的方法 say()
是是一个事物方法,并且B服务实现一个接口 IHelloService
。实际过程中发现A服务无法使用 @autowire
把B服务注入,但是去掉接口 `IHelloService
或者去掉 @Transactional
则可注入B服务,亦或者@Autowired
注入的类型使用IHelloService
。
二、问题思考
在解决上面问题时,我们必须要知道spring容器在开启事物的过程中使用的是AOP
技术,其实底层是通过代理实现的。在spring在选择代理时默认实现了2中代理方式一种是JDK
代理、另外一种是CGLIB
代理。其中JDK
代理是基于接口,通过接口InvocationHandler
实现的,而CGLIB
代理是基于类的,通过接口MethodInterceptor
来实现。
三、验证思考
为了验证我的解决思路是否正确,去官网查看了一下文档
10.5 Using the ProxyFactoryBean to create AOP proxies
JavaBean properties
In common with most FactoryBean implementations provided with Spring, the ProxyFactoryBean
class is itself a JavaBean. Its properties are used to:
• Specify the target you want to proxy.
• Specify whether to use CGLIB.
上面这段话的大体意思:一般情况下面我们使用FactoryBean
来提供bean
,当使用AOP
时会使用ProxyFactoryBean
来提供bean
。我们可以指定目标(即需要代理的对象),也可指定是否使用CGLIB
代理。换而言之,spring默认使用的是JDK
的代理。
再看一下实际的A服务的bean
果然是如我们猜想的那样使用了JDK
代理。
四、解决问题
指定使用CGLIB代理
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
再看一下结果
当我们指定用CGLIB
代理之后,发现HelloService
可以被正常进行注入,并且HelloService
也由JDK
代理对象变成了CGLIB
代理对象。
五、问题深入与扩展
随着问题的深入,我们没有解决为什么使用JDK
的代理对象不行,而使用CGLIB
代理对象却可以?
不知道大家有没有注意我上面讲过一句话“JDK
代理是基于接口...,而CGLIB
代理是基于类的...”,问题就出现在这边。下面我把基于JDK和CGLIB的对象的类名、父类名及接口名打印出来:
HelloService
基于JDK
的代理方式:
基于JDK
代理的helloService
对象的类名$Proxy44
,父类是Proxy
,接口是IHelloService
HelloService
基于CGLIB的代理方式:
基于CGLIB
代理的helloService
对象的类名是HelloService$$EnhancerBySpringCGLIB$$80f67bbd
父类是HelloService
。
至此我们终于知道了通过@Autowired
注入HelloService
对象时,使用JDK
代理时代理对象实现了IHelloService
接口,而使用CGLIB
代理时代理对象是继承了HelloService
。
spring中的代理的问题告一段落了,孔夫子讲过"举一隅,不以三隅反,则不复也"。那么我们实际项目中还有那些耳熟能详的框架也使用了代理了呢?
在web开发中不知道大家有没有注意到mybatis
中只有接口而没有实现类,其实也是使用了JDK
的代理。有兴趣的童鞋可以研究一下。