当前位置:   article > 正文

bootdelay修改不生效_Service调用其他Service的private方法, @Transactional会生效吗

springboot 修改了service 但调用的时候没有改变

作者:zzzzbw

来源:SegmentFault 思否社区


省流大师:

  1. 一个Service调用其他Service的private方法, @Transactional会生效吗
  2. 正常流程不能生效
  3. 经过一番操作, 达到理论上可以
本文基于Spring Boot 2.3.3.RELEASE、JDK1.8 版本, 使用Lombok插件


疑问

有一天, 我的小伙伴问我,"一个Service调用其他Service的private方法, @Transactional的事务会生效吗?" 我当场直接就回答: "这还用想, 那肯定不能生效啊!". 于是他问, "为什么不能生效?" "这不是很明显的事情, 你怎么在一个Service调用另一个Service的私有方法?". 他接着说到: "可以用反射啊". "就算用反射, @Transactional的原理是基于AOP的动态代理实现的, 动态代理不会代理private方法的!". 他接着问道: "真的不会代理private方法吗?". "额...应该不会吧..." 这下我回答的比较迟疑了. 因为平时只是大概知道动态代理会在字节码的层面生成java类, 但是里面具体怎么实现, 会不会处理private方法, 还真的不确定

验证

虽然心里知道了结果, 但还是要实践一下, Service调用其他Service的private方法, @Transactional的事务到底能不能生效, 看看会不会被打脸. 由于@Transactional的事务效果测试的时候不方便直白的看到, 不过其事务是通过AOP的切面实现的, 所以这里自定义一个切面来表示事务效果, 方便测试, 只要这个切面生效, 那事务生效肯定也不是事.
@Slf4j@Aspect@Componentpublic 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@Servicepublic 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@SpringBootTestpublic 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);    }}
0bc8c1063e867e2494da5943a081d526.png 从结果看到, 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代理生成流程

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 AopProxyInvocationHandlerSerializable {    @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的结果.

JDK代理类生成流程

既然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() {                    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        Method[] methods = interfaces[i].getMethods();       for (int j = 0; j            addProxyMethod(methods[j], interfaces[i]);       }   }   // 校验是否有重复的方法   for (List sigmethods : proxyMethods.values()) {       checkReturnTypes(sigmethods);   }   /* ============================================================    * 步骤二:组装需要生成的代理类字段信息(FieldInfo)和方法信息(MethodInfo)    */   try {       // 添加构造方法       methods.add(generateConstructor());for (List 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()方法就是真正的实现生成代理类字节码数据的地方, 主要为三个步骤: 添加所有需要代理的方法, 把需要代理的方法(equal、hashcode、toString方法和接口中声明的方法)的一些相关信息记录下来.
  1. 组装需要生成的代理类的字段信息和方法信息. 这里会根据步骤一添加的方法, 生成实际的代理类的方法的实现. 比如:

  2. 如果目标代理类实现了一个HelloService接口, 且实现其中的方法hello, 那么生成的代理类就会生成如下形式方法:

    public Object hello(Object... args){    try{        return (InvocationHandler)h.invoke(thisthis.getMethod("hello"), args);    } catch ...  }
  3. 把上面添加和组装的信息通过流拼接出最终的java class字节码数据

**看了这段代码, 现在我们可以真正确定代理类是不会代理private方法了. 在步骤一中知道代理类只会代理equal、hashcode、toString方法和接口中声明的方法, 所以目标类的private方法是不会被代理到的.
不过想一下也知道, 私有方法在正常情况下外部也无法调用, 即使代理了也没法使用, 所以也没必要去代理.**

结论

上文通过阅读Spring Boot动态代理流程以及JDK动态代理功能实现的源码, 得出结论动态代理不会代理private方法, 所以@Transactional注解的事务也不会对其生效. 但是看完成整个代理流程之后感觉动态代理也不过如此嘛, JDK提供的动态代理功能太菜了, 我们完全可以自己来实现动态代理的功能, 让@Transactional注解的private方法也能生效, 我上我也行! 根据上面看源码流程, 如果要实现代理private方法并使@Transactional注解生效的效果, 那么只要倒叙刚才看源码的流程, 如下:
  1. 重新实现一个ProxyGenerator.generateClassFile()方法, 输出带有private方法的代理类字节码数据
  2. 把字节码数据加载到JVM中, 生成Class
  3. 替代Spring Boot中默认的动态代理功能, 换成我们自己的动态代理.
这部分内容在Service调用其他Service的private方法, @Transactional会生效吗(下), 欢迎阅读
前情提要: 在Service调用其他Service的private方法, @Transactional会生效吗(上)中证明了动态代理不会代理private方法的, 并通过阅读源码证实了. 但是我们可以自己实现一个动态代理功能替代Spring Boot中原有的, 达到动态代理private方法的目的. 主要流程为:
  1. 重新实现一个ProxyGenerator.generateClassFile()方法, 输出带有private方法的代理类字节码数据
  2. 把字节码数据加载到JVM中, 生成Class
  3. 替代Spring Boot中默认的动态代理功能, 换成我们自己的动态代理.


前置代码

首先, 要实现代理目标类的private方法的目标, 必须要能拿到被代理类的实例, 所以先改装切面InvocationHandler, 把要被代理的类保存下来. 
@Getterpublic abstract class PrivateProxyInvocationHandler implements InvocationHandler {    private final Object subject;    public PrivateProxyInvocationHandler(Object subject) {        this.subject = subject;    }}
前文的切面TransactionalAop是Spring Boot在JdkDynamicAopProxy中扫描被@Aspect注解的类, 然后解析类里面的方法以及切点等.
为了简便实现, 就不实现扫描解析的功能了, 这里直接模仿前文的TransactionalAop的功能实现切面TransactionalHandler.
@Slf4jpublic class TransactionalHandler extends PrivateProxyInvocationHandler {    public TransactionalHandler(Object subject) {        super(subject);    }    @Override    public Object invoke(Object proxy, Method methodObject[] args) throws Throwable {        log.info("Transaction start!");        Object result;        try {            result = method.invoke(getSubject(), args);        } catch (Exception e) {            log.info("Transaction rollback!");            throw new Throwable(e);        }        log.info("Transaction commit!");        return result;    }}

生成字节码数据

根据阅读ProxyGenerator.generateProxyClass()方法生成字节码的代码可以知道, 动态代理的功能实际上就是动态的生成类的字节码, 通过新生成的字节码的类替代原有的类. 那我们只要模仿generateProxyClass()方法的功能, 实现自己的动态生成代码的功能, 并且在生成的时候把被代理类的private方法也一并生成了, 就可以实现private方法的动态代理功能. 另外ProxyGenerator.generateProxyClass()方法是直接编写字节码数据的(即.class文件), 为了方便我们编写和查看生成的数据, 我们就实现动态编写java数据, 然后再编译成字节码文件. PrivateProxyGenerator是这次功能实现的核心代码, 迫于文章篇幅这里只放出重点部分, 如需完整代码可直接查看源码
public class PrivateProxyGenerator {    ...    private String generateClassSrc() {        // 1. 添加equal、hashcode、toString方法        // 这里省略        // 2. 添加interface中的方法        for (Class> interfaceClz : interfaces) {            // TODO 这里就不考虑多个interfaces含有相同method的情况了            Method[] methods = interfaceClz.getMethods();            this.proxyMethods.put(interfaceClz, Arrays.asList(methods));        }        // 3. 添加代理类中的私有方法        // TODO 这是新增的        Object subject = h.getSubject();        Method[] declaredMethods = subject.getClass().getDeclaredMethods();        List privateMethods = Arrays.stream(declaredMethods)                .filter(method -> method.getModifiers() == Modifier.PRIVATE)                .collect(Collectors.toList());        this.privateMethods.addAll(privateMethods);        // 4. 校验方法的签名等@see sun.misc.ProxyGenerator.checkReturnTypes        // 这里省略        // 5. 添加类里的字段信息和方法数据        // 如静态方法、构造方法、字段等        // TODO 这里省略, 在编写java字符串(步骤7)时直接写入        // 6. 校验一下方法长度、字段长度等        // 这里省略        // 7. 把刚才添加的数据真正写到class文件里        // TODO 这里我们根据逻辑写成java字符串return writeJavaSrc();    }    ...}
这部分代码和JDK的ProxyGenerator.generateProxyClass()方法流程类似, 主要就是保存一下被代理类及其方法的一些信息, 真正编写代码数据的功能在writeJavaSrc()方法里完成.
private String writeJavaSrc() {    StringBuffer sb = new StringBuffer();    int packageIndex = this.className.lastIndexOf(".");    String packageName = this.className.substring(0, packageIndex);    String clzName = this.className.substring(packageIndex + 1);    // package信息    sb.append("package").append(SPACE).append(packageName).append(SEMICOLON).append(WRAP);    // class 信息, interface接口    sb.append(PUBLIC).append(SPACE).append("class").append(SPACE).append(clzName).append(SPACE);    sb.append("implements").append(SPACE);    String interfaceNameList = Arrays.stream(this.interfaces).map(Class::getTypeName).collect(Collectors.joining(","));    sb.append(interfaceNameList);    sb.append(SPACE).append("{").append(WRAP);    // 必须要的属性和构造函数    /**     * private PrivateProxyInvocationHandler h;     */    sb.append(PRIVATE).append(SPACE).append(PrivateProxyInvocationHandler.class.getName()).append(SPACE).append("h;").append(WRAP);    /**     *  public $Proxy0(PrivateProxyInvocationHandler h) {     *      this.h = h;     * }     */    sb.append(PUBLIC).append(SPACE).append(clzName).append("(")            .append(PrivateProxyInvocationHandler.class.getName()).append(SPACE).append("h").append("){").append(WRAP)            .append("this.h = h;").append(WRAP)            .append("}");    // 代理public方法    this.proxyMethods.forEach((interfaceClz, methods) -> {        for (Method proxyMethod : methods) {            writeProxyMethod(sb, interfaceClz, proxyMethod, PUBLIC);        }    });    // 代理private方法    for (Method proxyMethod : this.privateMethods) {        writeProxyMethod(sb, null, proxyMethod, PRIVATE);    }    sb.append("}");    return sb.toString();}/** * 编写代理方法数据 */private void writeProxyMethod(StringBuffer sb, Class> interfaceClz, Method proxyMethod, String accessFlag) {    // 1. 编写方法的声明, 例:    // public void hello(java.lang.String var0)    sb.append(accessFlag)            .append(SPACE)            // 返回类            .append(proxyMethod.getReturnType().getTypeName()).append(SPACE)            .append(proxyMethod.getName()).append("(");    // 参数类    Class>[] parameterTypes = proxyMethod.getParameterTypes();    // 参数类名    List argClassNames = new ArrayList<>();    // 参数名    List args = new ArrayList<>();for (int i = 0; i         Class> parameterType = parameterTypes[i];        argClassNames.add(parameterType.getTypeName());        args.add("var" + i);    }    // 写入参数的声明for (int i = 0; i         sb.append(argClassNames.get(i)).append(SPACE).append(args.get(i)).append(",");    }if (parameterTypes.length > 0) {        //去掉最后一个逗号        sb.replace(sb.length() - 1, sb.length(), "");    }    sb.append(")").append("{").append(WRAP);    // 如果是public方法, 则编写的代理方法逻辑大致如下    /**     * try {     *  Method m = HelloService.class.getMethod("hello"String.class, Integer.class);     *  return this.h.invoke(this, proxyMethod, new Object[]{var0, var1...});     * } catch (Throwable e) {     *  throw new RuntimeException(e);     * }     */    // 如果是private方法, 则编写的代理方法逻辑大致如下    /**     * try {     *  Method m = h.getSubject().getClass().getDeclaredMethod("hello"String.class, Integer.class);     *  m.setAccessible(true);     *  return this.h.invoke(this, proxyMethod, new Object[]{var0, var1...});     * } catch (Throwable e) {     *  throw new RuntimeException(e);     * }     */    // 2. try    sb.append("try{").append(WRAP);    // 3. 编写获取目标代理方法的功能    sb.append(Method.class.getTypeName()).append(SPACE).append("m = ");if (PUBLIC.equals(accessFlag)) {        // 3.1 public方法的代理, 通过接口获取实例方法. 例:        // java.lang.reflect.Method m = HelloService.class.getMethod("hello"String.class, Integer.class);        sb.append(interfaceClz.getTypeName()).append(".class")                .append(".getMethod(").append("\"").append(proxyMethod.getName()).append("\"").append(",").append(SPACE);    } else {        // 3.2 private方法的代理, 通过目标代理类实例获取方法. 例:        // java.lang.reflect.Method m = h.getSubject().getClass().getDeclaredMethod("hello"String.class, Integer.class);        sb.append("h.getSubject().getClass().getDeclaredMethod(").append("\"").append(proxyMethod.getName()).append("\"").append(",").append(SPACE);    }    argClassNames.forEach(name -> sb.append(name).append(".class").append(","));if (parameterTypes.length > 0) {        //去掉最后一个逗号        sb.replace(sb.length() - 1, sb.length(), "");    }    sb.append(");").append(WRAP);if (!PUBLIC.equals(accessFlag)) {        // 3.3 不是public方法, 设置访问权限        sb.append("m.setAccessible(true);").append(WRAP);    }    // 4. InvocationHandler中调用代理方法逻辑, 例:    // return this.h.invoke(this, m, new Object[]{var0});if (!proxyMethod.getReturnType().equals(Void.class&& !proxyMethod.getReturnType().equals(void.class)) {        // 有返回值则返回且强转        sb.append("return").append(SPACE).append("(").append(proxyMethod.getReturnType().getName()).append(")");    }    String argsList = String.join(",", args);    sb.append("this.h.invoke(this, m, new Object[]{").append(argsList).append("});");    // 5. catch    sb.append("} catch (Throwable e) {").append(WRAP);    sb.append("throw new RuntimeException(e);").append(WRAP);    sb.append("}");    sb.append("}").append(WRAP);}
writeJavaSrc()大体上分为两部分, 第一部分是编写类的一些固定信息代码数据, 如包名、类声明、构造函数等, 生成大致类似于下面的代码:
package cn.zzzzbw.primary.proxy.reflect;public class $Proxy0 implements cn.zzzzbw.primary.proxy.service.HelloService {    private cn.zzzzbw.primary.proxy.reflect.PrivateProxyInvocationHandler h;    public $Proxy0(cn.zzzzbw.primary.proxy.reflect.PrivateProxyInvocationHandler h) {        this.h = h;    }}
第二部分就是writeProxyMethod()方法, 编写代理后的方法的代码数据, 生成大致类似于下面的代码:
// 代理的public方法public void hello(java.lang.String var0) {    try {        java.lang.reflect.Method m = cn.zzzzbw.primary.proxy.service.HelloService.class.getMethod("hello", java.lang.String.class);        this.h.invoke(this, m, new Object[]{var0});    } catch (Throwable e) {        throw new RuntimeException(e);    }}// 代理的private方法private long primaryHello(java.lang.Integer var0) {    try {        java.lang.reflect.Method m = h.getSubject().getClass().getDeclaredMethod("privateHello", java.lang.Integer.class);        m.setAccessible(true);        return (long) this.h.invoke(this, m, new Object[]{var0});    } catch (Throwable e) {        throw new RuntimeException(e);    }}
以上就是我们自己实现的支持private方法动态代理的"字节码"生成功能. 现在写个单元测试看一下效果
@Slf4jpublic class PrivateProxyGeneratorTests {    public static void main(String[] args) throws IOException {        // 1 生成java源碼        String packageName = "cn.zzzzbw.primary.proxy.reflect";        String clazzName = "$Proxy0";        String proxyName = packageName + "." + clazzName;        Class>[] interfaces = HelloServiceImpl.class.getInterfaces();        PrivateProxyInvocationHandler h = new TransactionalHandler(new HelloServiceImpl());        String src = PrivateProxyGenerator.generateProxyClass(proxyName, interfaces, h);        // 2 保存成java文件        String filePath = PrivateProxy.class.getResource("/").getPath();        String clzFilePath = filePath + packageName.replace(".""/"+ "/" + clazzName + ".java";        log.info("clzFilePath: {}", clzFilePath);        File f = new File(clzFilePath);        if (!f.getParentFile().exists()) {            f.getParentFile().mkdirs();        }        try (FileWriter fw = new FileWriter(f)) {            fw.write(src);            fw.flush();        }    }}
运行之后生成了一个$Proxy0.java文件, 看一下文件内容(代码格式化了一下):
package cn.zzzzbw.primary.proxy.reflect;public class $Proxy0 implements cn.zzzzbw.primary.proxy.service.HelloService {    private cn.zzzzbw.primary.proxy.reflect.PrivateProxyInvocationHandler h;    public $Proxy0(cn.zzzzbw.primary.proxy.reflect.PrivateProxyInvocationHandler h) {        this.h = h;    }    public void hello(java.lang.String var0) {        try {            java.lang.reflect.Method m = cn.zzzzbw.primary.proxy.service.HelloService.class.getMethod("hello", java.lang.String.class);            this.h.invoke(this, m, new Object[]{var0});        } catch (Throwable e) {            throw new RuntimeException(e);        }    }    private long privateHello(java.lang.Integer var0) {        try {            java.lang.reflect.Method m = h.getSubject().getClass().getDeclaredMethod("privateHello", java.lang.Integer.class);            m.setAccessible(true);            return (long) this.h.invoke(this, m, new Object[]{var0});        } catch (Throwable e) {            throw new RuntimeException(e);        }    }}
生成的$Proxy0就是被代理类HelloServiceImpl的代理类, 他实现了HelloServiceImpl的所有interface, 有个成员变量PrivateProxyInvocationHandler h,
其所有public和private方法都被$Proxy0重新实现了一遍, 通过PrivateProxyInvocationHandler.invoke()来调用代理后的方法逻辑. 看来我们自己实现的代理类字节码动态生成的功能挺成功的, 接下来就要考虑代理类生成的逻辑, 以及如何把.java文件加载到JVM里.

加载到JVM, 生成动态代理类

现在就模仿java.lang.reflect.Proxy.newProxyInstance()方法, 编写自己的编译加载生成动态代理类对象的方法.
public class PrivateProxy {    private static final String proxyClassNamePrefix = "$Proxy";    private static final AtomicLong nextUniqueNumber = new AtomicLong();    public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, PrivateProxyInvocationHandler h) {        try {            // 1 生成java源码            String packageName = PrivateProxy.class.getPackage().getName();            long number = nextUniqueNumber.getAndAdd(1);            String clazzName = proxyClassNamePrefix + number;            String proxyName = packageName + "." + clazzName;            String src = PrivateProxyGenerator.generateProxyClass(proxyName, interfaces, h);            // 2 讲源码输出到java文件中            String filePath = PrivateProxy.class.getResource("/").getPath();            String clzFilePath = filePath + packageName.replace(".""/"+ "/" + clazzName + ".java";            File f = new File(clzFilePath);            if (!f.getParentFile().exists()) {                f.getParentFile().mkdirs();            }            try (FileWriter fw = new FileWriter(f)) {                fw.write(src);                fw.flush();            }            //3、将java文件编译成class文件            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();            StandardJavaFileManager manage = compiler.getStandardFileManager(nullnullnull);            Iterable extends JavaFileObject> iterable = manage.getJavaFileObjects(f);            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, nullnullnull, iterable);            task.call();            manage.close();            f.delete();            // 4、将class加载进jvm            Class> proxyClass = loader.loadClass(proxyName);            // 通过构造方法生成代理对象            Constructor> constructor = proxyClass.getConstructor(PrivateProxyInvocationHandler.class);            return constructor.newInstance(h);        } catch (Exception e) {            e.printStackTrace();        }        return null;    }}
PrivateProxy通过调用PrivateProxyGenerator.generateProxyClass()获取到代理类的.java文件的字符串, 然后输出到java文件中, 再编译成.class文件.
接着通过ClassLoader加载到JVM中 接着写个单元测试看看效果:
@Slf4jpublic class PrivateProxyTests {    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {        PrivateProxyInvocationHandler handler = new PrivateProxyInvocationHandler(new HelloServiceImpl()) {            @Override            public Object invoke(Object proxy, Method methodObject[] args) throws Throwable {                log.info("PrivateProxyInvocationHandler!");                return method.invoke(getSubject(), args);            }        };        Object o = PrivateProxy.newProxyInstance(ClassLoader.getSystemClassLoader(), HelloServiceImpl.class.getInterfaces(), handler);        log.info("{}", o);        HelloService helloService = (HelloService) o;        helloService.hello("hello");        Method primaryHello = helloService.getClass().getDeclaredMethod("privateHello", Integer.class);        primaryHello.setAccessible(true);        Object invoke = primaryHello.invoke(helloService, 10);        log.info("privateHello result: {}"invoke);    }}
fb04c4cd491411d6f98e4c57fa035747.png 从单元测试结果看到PrivateProxy.newProxyInstance()方法成功生成了HelloServiceImpl的代理类cn.zzzzbw.primary.proxy.reflect.$Proxy0, 并且把public和private方法都代理了. 以上功能我们通过实现PrivateProxyGenerator和 PrivateProxy两个类, 实现了JDK的动态代理功能, 并且还能代理private方法. 接下来就要考虑如何把Spring Boot里的动态代理功能替换成我们自己的.

替代Spring Boot默认动态代理

上面通过模仿JDK的动态代理, 自己实现了一个能代理private方法的动态代理功能.
现在为了让@Transactional注解能对private方法生效, 就要把自定义的动态代理方法嵌入到Spring Boot的代理流程中
AopProxy
Spring Boot中自带的两种动态代理方式为JDK和Cglib, 对应的实现类是JdkDynamicAopProxy和ObjenesisCglibAopProxy, 这两个类都是实现AopProxy接口, 实现其中的getProxy()方法返回代理后的对象.
上文也分析了JdkDynamicAopProxy.getProxy()方法是如何返回代理对象的, 这里我们就模仿来实现一个自己的AopProxy.
public class PrivateAopProxy implements AopProxy {    private final AdvisedSupport advised;        /**     * 构造方法     * 


     * 直接复制JdkDynamicAopProxy构造方法逻辑
     */
    public PrivateAopProxy(AdvisedSupport config) throws AopConfigException {
        Assert.notNull(config, "AdvisedSupport must not be null");if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
            throw new AopConfigException("No advisors and no TargetSource specified");
        }
        this.advised = config;
    }
    @Override
    public Object getProxy() {return getProxy(ClassUtils.getDefaultClassLoader());
    }
    @Override
    public Object getProxy(ClassLoader classLoader) {
        // 获取目标类接口
        Class>[] interfaces = this.advised.getTargetClass().getInterfaces();
        TransactionalHandler handler;
        try {
            // 生成切面, 这里写死为TransactionalHandler
            handler = new TransactionalHandler(this.advised.getTargetSource().getTarget());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // 返回代理类对象return PrivateProxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

PrivateAopProxy.getProxy()方法先通过advised获取到目标代理类的接口, 并通过实例生成切面TransactionalHandler, 然后返回刚才实现的PrivateProxy.newProxyInstance()方法生成的代理类. JdkDynamicAopProxy的切面是通过自身实现InvocationHandler接口的invoke()方法, 实现了一个切面的链式调用的功能, 逻辑较复杂就不去模仿了.
本文的目的主要是代理私有方法, 不怎么关注切面, 所以就直接固定用new TransactionalHandler().
AbstractAdvisorAutoProxyCreator
实现了PrivateAopProxy类, 再考虑如何把他替换掉Spring Boot中的JdkDynamicAopProxy和ObjenesisCglibAopProxy.
这两种AopProxy是通过DefaultAopProxyFactory.createAopProxy()根据条件生成的, 那么现在就要替换掉DefaultAopProxyFactory, 通过实现自己的AopProxyFactory来生成PrivateAopProxy. 因为不需要DefaultAopProxyFactory里的那种判断动态代理方式, 自定义的AopProxyFactory就直接new一个PrivateAopProxy返回就行了.
class PrimaryAopProxyFactory implements AopProxyFactory {    @Override    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {        return new PrivateAopProxy(config);    }}
实现了的PrimaryAopProxyFactory, 现在要考虑怎么替换掉Spring Boot中的DefaultAopProxyFactory (是不是有点像套娃, 但是没办法, 就只能这样一步一步替换过去. 我个人觉得Spring Boot这部分设计的就不够优雅了, 使用了Factory工厂模式, 但是想要替换AopProxy的时候却要把Factory也替换了.可能是开发者认为AOP这部分没必要开放给使用者修改吧, 或者是我个人没找到更好的方式修改) 想要替换掉DefaultAopProxyFactory, 就要找出哪里生成AopProxyFactory, 那么就可以通过打断点的方式把断点打在createAopProxy()上, 然后再看一下调用链. 观察到org.springframework.aop.framework.ProxyFactory.getProxy()方法负责生成和控制AopProxyFactory.createAopProxy()的逻辑. ProxyFactory继承了ProxyCreatorSupport类,
其getProxy()方法会调用ProxyCreatorSupport中的aopProxyFactory变量, 而aopProxyFactory默认就是DefaultAopProxyFactory, 相关源码如下:
public class ProxyFactory extends ProxyCreatorSupport {        public Object getProxy() {        return createAopProxy().getProxy();    }}public class ProxyCreatorSupport extends AdvisedSupport {    private AopProxyFactory aopProxyFactory;    /**     * Create a new ProxyCreatorSupport instance.     */    public ProxyCreatorSupport() {        this.aopProxyFactory = new DefaultAopProxyFactory();    }    protected final synchronized AopProxy createAopProxy() {        if (!this.active) {            activate();        }        return getAopProxyFactory().createAopProxy(this);    }    public AopProxyFactory getAopProxyFactory() {        return this.aopProxyFactory;    }}
既然AopProxyFactory是ProxyFactory的一个变量, 那么现在看一下ProxyFactory是由谁控制的, 怎么样才能修改为PrimaryAopProxyFactory. 继续通过断点的方式, 发现在org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy()方法中会new一个ProxyFactory并且赋值一些属性, 然后调用ProxyFactory.getProxy()方法返回生成的代理对象. 看一下源码
protected Object createProxy(Class> beanClass, @Nullable String beanName,        @Nullable Object[] specificInterceptors, TargetSource targetSource) {    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);    }    // 实例化ProxyFactory    ProxyFactory proxyFactory = new ProxyFactory();        // 下面都是为proxyFactory赋值    proxyFactory.copyFrom(this);    if (!proxyFactory.isProxyTargetClass()) {        if (shouldProxyTargetClass(beanClass, beanName)) {            proxyFactory.setProxyTargetClass(true);        }        else {            evaluateProxyInterfaces(beanClass, proxyFactory);        }    }    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);    proxyFactory.addAdvisors(advisors);    proxyFactory.setTargetSource(targetSource);    customizeProxyFactory(proxyFactory);    proxyFactory.setFrozen(this.freezeProxy);    if (advisorsPreFiltered()) {        proxyFactory.setPreFiltered(true);    }        // 调用Factory工厂方法返回代理类对象    return proxyFactory.getProxy(getProxyClassLoader());}
AbstractAutoProxyCreator.createProxy()做的事情就是new一个ProxyFactory, 然后为其赋值, 最后调用ProxyFactory.getProxy()返回代理对象. 由于ProxyFactory是直接new出来的, 是一个局部变量, 所以没办法全局的修改ProxyFactory.aopProxyFactory.
所以就考虑实现一个类继承AbstractAutoProxyCreator然后重写createProxy()方法, 在自己的createProxy()方法中修改ProxyFactory.aopProxyFactory的值. AbstractAutoProxyCreator是一个抽象类并且继承的类和实现的接口比较多, 所以这边我先查看了一下其整个的类结构图(只显示了重要的接口). 02485dd695392b34a2b8c66667410a24.png 首先, 看一下其父类和父接口. 其实现了org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor.
SmartInstantiationAwareBeanPostProcessor继承InstantiationAwareBeanPostProcessor,而InstantiationAwareBeanPostProcessor继承BeanPostProcessor. 这三个接口是Spring用于创建Bean时的增强功能, 是Spring的IOC和AOP实现的核心思想, 建议大家都去了解一下, 这里就不详细讲解了,只要知道AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor的接口, 所以能在创建Bean的时候对其进行代理. 接着, 看一下其子类. 其直接子类有AbstractAdvisorAutoProxyCreator和BeanNameAutoProxyCreator.
这两个类的主要区别为切点的不同, BeanNameAutoProxyCreator是通过Bean名称等配置指定切点, AbstractAdvisorAutoProxyCreator是基于Advisor匹配机制来决定切点. AbstractAdvisorAutoProxyCreator又有三个子类, 分别为AnnotationAwareAspectJAutoProxyCreator(AspectJAwareAdvisorAutoProxyCreator), InfrastructureAdvisorAutoProxyCreator, DefaultAdvisorAutoProxyCreator.
通常使用的就是AnnotationAwareAspectJAutoProxyCreator, 从名字上看就可以知道, 它会通过注解和Aspect表达式来决定切面,
如上文实现的TransactionalAop切面里的@Around("@within(org.springframework.transaction.annotation.Transactional)")形式就是由AnnotationAwareAspectJAutoProxyCreator处理的. 那么现在直接继承抽象类AbstractAutoProxyCreator的子类AnnotationAwareAspectJAutoProxyCreator, 然后重写createProxy()方法.
public class PrivateProxyAdvisorAutoProxyCreator extends AnnotationAwareAspectJAutoProxyCreator {    @Override    protected Object createProxy(Class> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {                // 由于AutoProxyUtils.exposeTargetClass不是public方法, 且与本文功能无关, 这里就不作改造, 直接注释掉        /*        if (this.beanFactory instanceof ConfigurableListableBeanFactory) {            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);        }        */        ProxyFactory proxyFactory = new ProxyFactory();        // 设置aopProxyFactory为PrimaryAopProxyFactory        proxyFactory.setAopProxyFactory(new PrimaryAopProxyFactory());        proxyFactory.copyFrom(this);        if (!proxyFactory.isProxyTargetClass()) {            if (shouldProxyTargetClass(beanClass, beanName)) {                proxyFactory.setProxyTargetClass(true);            } else {                evaluateProxyInterfaces(beanClass, proxyFactory);            }        }        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);        proxyFactory.addAdvisors(advisors);        proxyFactory.setTargetSource(targetSource);        customizeProxyFactory(proxyFactory);        proxyFactory.setFrozen(isFrozen());        if (advisorsPreFiltered()) {            proxyFactory.setPreFiltered(true);        }        return proxyFactory.getProxy(getProxyClassLoader());    }}
直接把AbstractAutoProxyCreator.createProxy()方法里的代码拷贝过来, 然后把一些调用private变量的地方改成调用其public的getter方法,
再加上设置ProxyFactory.aopProxyFactory为PrimaryAopProxyFactory的代码: proxyFactory.setAopProxyFactory(new PrimaryAopProxyFactory());就完成了PrivateProxyAdvisorAutoProxyCreator.
引入到Bean中
接下来就是把PrivateProxyAdvisorAutoProxyCreator引入到Spring Boot组件中, 因为其实现了SmartInstantiationAwareBeanPostProcessor接口, 所以我想着直接在类上加@Component注解就好了.
但是加上之后却没有生效, 就去看一下AnnotationAwareAspectJAutoProxyCreator, 这个类上是没有加@Component注解的, 那么它是怎么引入到Spring Boot的?
为了查明原因, 我就查一下哪里调用了AnnotationAwareAspectJAutoProxyCreator类, 找到了一个AopConfigUtils这么一个工具类, 上文提到的几种AbstractAdvisorAutoProxyCreator的实现类就是这里引入的,
且设置Bean名为"org.springframework.aop.config.internalAutoProxyCreator", 看一下相关代码:
public abstract class AopConfigUtils {    public static final String AUTO_PROXY_CREATOR_BEAN_NAME =            "org.springframework.aop.config.internalAutoProxyCreator";        // AbstractAdvisorAutoProxyCreator实现类列表    private static final List> APC_PRIORITY_LIST = new ArrayList<>(3);    static {        // 添加AbstractAdvisorAutoProxyCreator实现类, 优先级有小到大, 也就是说默认为最后添加的AnnotationAwareAspectJAutoProxyCreator        APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);        APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);        APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);    }    // 引入AspectJAwareAdvisorAutoProxyCreator    @Nullable    public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {return registerAspectJAutoProxyCreatorIfNecessary(registry, null);    }    @Nullable    public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(            BeanDefinitionRegistry registry, @Nullable Object source) {return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);    }    /**     * 此方法引入AbstractAdvisorAutoProxyCreator实现类到Spring Boot中     */     @Nullable    private static BeanDefinition registerOrEscalateApcAsRequired(            Class> cls, BeanDefinitionRegistry registry, @Nullable Object source) {        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {            // 如果Spring Boot中已经有被引入的AbstractAdvisorAutoProxyCreator实现类, 则比对优先级            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);if (!cls.getName().equals(apcDefinition.getBeanClassName())) {                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());                int requiredPriority = findPriorityForClass(cls);if (currentPriority                     apcDefinition.setBeanClassName(cls.getName());                }            }return null;        }        // 引入对应的cls到Spring Boot的Bean管理中, 且命名为AUTO_PROXY_CREATOR_BEAN_NAME变量值        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);        beanDefinition.setSource(source);        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);return beanDefinition;    }}
AopConfigUtils工具类引入AbstractAdvisorAutoProxyCreator的实现类的时候指定了Bean名,
所以我们要给PrivateProxyAdvisorAutoProxyCreator的Bean名也指定为AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME才能覆盖:
@Component(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME)public class PrivateProxyAdvisorAutoProxyCreator extends AnnotationAwareAspectJAutoProxyCreator {    ...}
但是这样还不够, 如果直接这样启动项目, 会爆出Class name [cn.zzzzbw.primary.proxy.spring.PrivateProxyAdvisorAutoProxyCreator] is not a known auto-proxy creator class的错误.
这是由于AopConfigUtils在查找AbstractAdvisorAutoProxyCreator实现类的优先级的时候要求必须是在AopConfigUtils.APC_PRIORITY_LIST有的才行.
private static int findPriorityForClass(@Nullable String className) {    for (int i = 0; i         Class> clazz = APC_PRIORITY_LIST.get(i);        if (clazz.getName().equals(className)) {            return i;        }    }    throw new IllegalArgumentException(            "Class name [" + className + "] is not a known auto-proxy creator class");}
这下就比较麻烦了, APC_PRIORITY_LIST是private属性, 且也没有开放public方法去修改, 大概Spring官方也不想别人去修改这部分功能吧. 所以我只能通过反射的方式去修改了(如果是单元测试则写在单元测试里, 如果是启动项目则写在启动类里), 代码如下:
static {    try {        Field apc_priority_list = AopConfigUtils.class.getDeclaredField("APC_PRIORITY_LIST");        apc_priority_list.setAccessible(true);        List> o = (List>) apc_priority_list.get(AopConfigUtils.class);        o.add(PrivateProxyAdvisorAutoProxyCreator.class);    } catch (Exception e) {        e.printStackTrace();    }}
现在, 再跑一下最开头的单元测试! 48cb1e058a497584d81a935a8d1b60b2.png 从单元测试的结果看到, 切面TransactionalHandler不仅代理了HelloServiceImpl的public方法hello(), 也成功代理了private方法privateHello(), 并且是由Spring Boot来控制的! 经过一大长串的花里胡哨的操作, 终于实现了在private方法上使@Transactional生效的效果了. 当然, 目前这只是理论上的生效,
因为中间在模仿JdkDynamicAopProxy实现PrivateAopProxy的时候, 由于JdkDynamicAopProxy的切面实现逻辑非常复杂, 我们直接把切面写死成了TransactionalHandler.
但是本文的主要目的就是能够在Spring Boot代理private方法, 只要能够代理, 说明@Transactional事务生效也是完全能做到的.

感悟

"Service调用其他Service的private方法, @Transactional会生效吗" 如果仅仅回答问题本身是很简单的, 只要了解Spring Boot的AOP原理即可. 但是也可以深入其中, 顺着这个问题继续研究,
从前文Service调用其他Service的private方法, @Transactional会生效吗(上)阅读Spring Boot动态代理的功能源码实现, 到本文亲手实现"特殊功能"的动态代理,
不仅精通了Spring Boot动态代理的代码实现流程, 还掌握了JDK的动态代理功能, 收益非常大! 文中相关源码: private-proxy-source-code (https://github.com/zzzzbw/private-proxy-source-code)
- END -

8980ba99299238932107eb5f1dade9a2.png

c64f8e6cd3886bda5ba30155e8929dcd.gif

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/224640
推荐阅读
相关标签
  

闽ICP备14008679号