省流大师: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会生效吗(下), 欢迎阅读