省流大師:java
- 一個Service調用其餘Service的private方法, @Transactional會生效嗎
- 正常流程不能生效
- 通過一番操做, 達到理論上能夠
本文基於Spring Boot 2.3.3.RELEASE、JDK1.8 版本, 使用Lombok插件git
有一天, 個人小夥伴問我, github
"一個Service調用其餘Service的private方法, @Transactional的事務會生效嗎?" spring
我當場直接就回答: "這還用想, 那確定不能生效啊!". 因而他問, "爲何不能生效?"緩存
"這不是很明顯的事情, 你怎麼在一個Service調用另外一個Service的私有方法?". 他接着說到: "能夠用反射啊". app
"就算用反射, @Transactional的原理是基於AOP的動態代理實現的, 動態代理不會代理private方法的!". ide
他接着問道: "真的不會代理private方法嗎?". spring-boot
"額...應該不會吧..."post
這下我回答的比較遲疑了. 由於平時只是大概知道動態代理會在字節碼的層面生成java類, 可是裏面具體怎麼實現, 會不會處理private方法, 還真的不肯定測試
雖然內心知道告終果, 但仍是要實踐一下, Service調用其餘Service的private方法, @Transactional
的事務到底能不能生效, 看看會不會被打臉.
因爲@Transactional
的事務效果測試的時候不方便直白的看到, 不過其事務是經過AOP的切面實現的, 因此這裏自定義一個切面來表示事務效果, 方便測試, 只要這個切面生效, 那事務生效確定也不是事.
@Slf4j @Aspect @Component public class TransactionalAop { @Around("@within(org.springframework.transaction.annotation.Transactional)") public Object recordLog(ProceedingJoinPoint p) throws Throwable { log.info("Transaction start!"); Object result; try { result = p.proceed(); } catch (Exception e) { log.info("Transaction rollback!"); throw new Throwable(e); } log.info("Transaction commit!"); return result; } }
而後寫測試的類和Test方法, Test方法中經過反射調用HelloServiceImpl
的private方法primaryHello()
.
public interface HelloService { void hello(String name); } @Slf4j @Transactional @Service public class HelloServiceImpl implements HelloService { @Override public void hello(String name) { log.info("hello {}!", name); } private long privateHello(Integer time) { log.info("private hello! time: {}", time); return System.currentTimeMillis(); } } @Slf4j @SpringBootTest public class HelloTests { @Autowired private HelloService helloService; @Test public void helloService() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { helloService.hello("hello"); Method privateHello = helloService.getClass().getDeclaredMethod("privateHello", Integer.class); privateHello.setAccessible(true); Object invoke = privateHello.invoke(helloService, 10); log.info("privateHello result: {}", invoke); } }
從結果看到, public方法hello()
成功被代理了, 可是private方法不只沒有被代理到, 甚至也沒法經過反射調用.
這其實也不難理解, 從拋出的異常信息中也能夠看到:
java.lang.NoSuchMethodException: cn.zzzzbw.primary.proxy.service.impl.HelloServiceImpl$$EnhancerBySpringCGLIB$$679d418b.privateHello(java.lang.Integer)
helloService
注入的不是實現類HelloServiceImpl
, 而是代理類生成的HelloServiceImpl$$EnhancerBySpringCGLIB$$6f6c17b4
. 假如生成代理類的時候沒有把private方法也寫上, 那麼天然是無法調用的.
一個Service調用其餘Service的private方法, @Transactional的事務是不會生效的
從上面的驗證結果能夠獲得這個結果. 可是這只是現象, 還須要最終看具體的代碼來肯定一下, 是否是真的在代理的時候把private方法丟掉了, 是怎麼丟掉的.
Spring Boot
生成代理類的大體流程以下:
[生成Bean實例] -> [Bean後置處理器(如BeanPostProcessor
)] -> [調用ProxyFactory.getProxy
方法(若是須要被代理)] -> [調用DefaultAopProxyFactory.createAopProxy.getProxy
方法獲取代理後的對象]
其中重點關注一下DefaultAopProxyFactory.createAopProxy
方法.
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // 被代理類有接口, 使用JDK代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 被代理類沒有實現接口, 使用Cglib代理 return new ObjenesisCglibAopProxy(config); } else { // 默認JDK代理 return new JdkDynamicAopProxy(config); } } }
這段代碼就是Spring Boot
經典的兩種動態代理方式選擇過程, 若是目標類有實現接口(targetClass.isInterface() || Proxy.isProxyClass(targetClass)
),
則用JDK代理(JdkDynamicAopProxy
), 不然用CGlib代理(ObjenesisCglibAopProxy
).
不過在Spring Boot 2.x版本之後, 默認會用CGlib代理模式, 但實際上Spring 5.x中AOP默認代理模式仍是JDK, 是Spring Boot特地修改的, 具體緣由這裏不詳細講解了, 感興趣的能夠去看一下 issue #5423
假如想要強制使用JDK代理模式, 能夠設置配置spring.aop.proxy-target-class=false
上面的HelloServiceImpl
實現了HelloService
接口, 用的就是JdkDynamicAopProxy
(爲了防止Spring Boot2.x
修改的影響, 這裏設置配置強制開啓JDK代理). 因而看一下JdkDynamicAopProxy.getProxy
方法
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { @Override public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); } Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } }
能夠看到JdkDynamicAopProxy
實現了InvocationHandler
接口, 而後在getProxy
方法中先是作了一系列操做(AOP的execution表達式解析、代理鏈式調用等, 裏面邏輯複雜且和咱們代理主流程關係不大, 就不研究了),
最後返回的是由JDK提供的生成代理類的方法Proxy.newProxyInstance
的結果.
既然Spring
把代理的流程託付給JDK了, 那咱們也跟着流程看看JDK究竟是怎麼生成代理類的.
先來看一下Proxy.newProxyInstance()
方法
public class Proxy implements java.io.Serializable { public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { /* * 1. 各類校驗 */ Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * 2. 獲取生成的代理類Class */ Class<?> cl = getProxyClass0(loader, intfs); /* * 3. 反射獲取構造方法生成代理對象實例 */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch ... } }
Proxy.newProxyInstance()
方法實際上作了3件事, 在上面流程代碼註釋了. 最重要的就是步驟2, 生成代理類的Class, Class<?> cl = getProxyClass0(loader, intfs);
, 這就是生成動態代理類的核心方法.
那就再看一下getProxyClass0()
方法
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } /* * 若是代理類已經生成則直接返回, 不然經過ProxyClassFactory建立新的代理類 */ return proxyClassCache.get(loader, interfaces); }
getProxyClass0()
方法從緩存proxyClassCache
中獲取對應的代理類. proxyClassCache
是一個WeakCache
對象, 他是一個相似於Map形式的緩存, 裏面邏輯比較複雜就不細看了.
不過咱們只要知道, 這個緩存在get時若是存在值, 則返回這個值, 若是不存在, 則調用ProxyClassFactory
的apply()
方法.
因此如今看一下ProxyClassFactory.apply()
方法
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { ... // 上面是不少校驗, 這裏先不看 /* * 爲新生成的代理類起名:proxyPkg(包名) + proxyClassNamePrefix(固定字符串"$Proxy") + num(當前代理類生成量) */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * 生成定義的代理類的字節碼 byte數據 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { /* * 把生成的字節碼數據加載到JVM中, 返回對應的Class */ return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch ... }
ProxyClassFactory.apply()
方法中主要就是作兩件事:1. 調用ProxyGenerator.generateProxyClass()
方法生成代理類的字節碼數據 2. 把數據加載到JVM中生成Class.
通過一連串的源碼查看, 終於到最關鍵的生成字節碼環節了. 如今一塊兒來看代理類字節碼是到底怎麼生成的, 對待private方法是怎麼處理的.
public static byte[] generateProxyClass(final String name, Class[] interfaces) { ProxyGenerator gen = new ProxyGenerator(name, interfaces); // 實際生成字節碼 final byte[] classFile = gen.generateClassFile(); // 訪問權限操做, 這裏省略 ... return classFile; } private byte[] generateClassFile() { /* ============================================================ * 步驟一: 添加全部須要代理的方法 */ // 添加equal、hashcode、toString方法 addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); // 添加目標代理類的全部接口中的全部方法 for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); } } // 校驗是否有重複的方法 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } /* ============================================================ * 步驟二:組裝須要生成的代理類字段信息(FieldInfo)和方法信息(MethodInfo) */ try { // 添加構造方法 methods.add(generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { // 因爲代理類內部會用反射調用目標類實例的方法, 必須有反射依賴, 因此這裏固定引入Method方法 fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); // 添加代理方法的信息 methods.add(pm.generateMethod()); } } methods.add(generateStaticInitializer()); } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } if (methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } if (fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } /* ============================================================ * 步驟三: 輸出最終要生成的class文件 */ // 這部分就是根據上面組裝的信息編寫字節碼 ... return bout.toByteArray(); }
這個sun.misc.ProxyGenerator.generateClassFile()
方法就是真正的實現生成代理類字節碼數據的地方, 主要爲三個步驟:
組裝須要生成的代理類的字段信息和方法信息. 這裏會根據步驟一添加的方法, 生成實際的代理類的方法的實現. 好比:
若是目標代理類實現了一個HelloService
接口, 且實現其中的方法hello
, 那麼生成的代理類就會生成以下形式方法:
public Object hello(Object... args){ try{ return (InvocationHandler)h.invoke(this, this.getMethod("hello"), args); } catch ... }
**看了這段代碼, 如今咱們能夠真正肯定代理類是不會代理private方法了. 在步驟一中知道代理類只會代理equal、hashcode、toString方法和接口中聲明的方法, 因此目標類的private方法是不會被代理到的.
不過想一下也知道, 私有方法在正常狀況下外部也沒法調用, 即便代理了也無法使用, 因此也不必去代理.**
上文經過閱讀Spring Boot
動態代理流程以及JDK動態代理功能實現的源碼, 得出結論動態代理不會代理private方法, 因此@Transactional
註解的事務也不會對其生效.
可是看完成整個代理流程以後感受動態代理也不過如此嘛, JDK提供的動態代理功能太菜了, 咱們徹底能夠本身來實現動態代理的功能, 讓@Transactional
註解的private方法也能生效, 我上我也行!
根據上面看源碼流程, 若是要實現代理private方法並使@Transactional
註解生效的效果, 那麼只要倒敘剛纔看源碼的流程, 以下:
ProxyGenerator.generateClassFile()
方法, 輸出帶有private方法的代理類字節碼數據Spring Boot
中默認的動態代理功能, 換成咱們本身的動態代理.這部份內容在Service調用其餘Service的private方法, @Transactional會生效嗎(下), 歡迎閱讀