This website requires JavaScript.

Service调用其他Service的private方法, @Transactional会生效吗(下)

前情提要:

Service调用其他Service的private方法, @Transactional会生效吗(上)中证明了动态代理不会代理private方法的, 并通过阅读源码证实了.

但是我们可以自己实现一个动态代理功能替代Spring Boot中原有的, 达到动态代理private方法的目的.

主要流程为:

  1. 重新实现一个ProxyGenerator.generateClassFile()方法, 输出带有private方法的代理类字节码数据
  2. 把字节码数据加载到JVM中, 生成Class
  3. 替代Spring Boot中默认的动态代理功能, 换成我们自己的动态代理.

前置代码

首先, 要实现代理目标类的private方法的目标, 必须要能拿到被代理类的实例, 所以先改装切面InvocationHandler, 把要被代理的类保存下来. .

@Getter
public abstract class PrivateProxyInvocationHandler implements InvocationHandler {

    private final Object subject;

    public PrivateProxyInvocationHandler(Object subject) {
        this.subject = subject;
    }
}

前文的切面TransactionalAopSpring BootJdkDynamicAopProxy中扫描被@Aspect注解的类, 然后解析类里面的方法以及切点等. 为了简便实现, 就不实现扫描解析的功能了, 这里直接模仿前文的TransactionalAop的功能实现切面TransactionalHandler.

@Slf4j
public class TransactionalHandler extends PrivateProxyInvocationHandler {

    public TransactionalHandler(Object subject) {
        super(subject);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] 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<Method> 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<String> argClassNames = new ArrayList<>();
    // 参数名
    List<String> args = new ArrayList<>();
    for (int i = 0; i < parameterTypes.length; i++) {
        Class<?> parameterType = parameterTypes[i];
        argClassNames.add(parameterType.getTypeName());
        args.add("var" + i);
    }
    // 写入参数的声明
    for (int i = 0; i < args.size(); 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方法动态代理的"字节码"生成功能. 现在写个单元测试看一下效果

@Slf4j
public 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(null, null, null);
            Iterable<? extends JavaFileObject> iterable = manage.getJavaFileObjects(f);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, 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中

接着写个单元测试看看效果:

@Slf4j
public 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 method, Object[] 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);
    }
}

PrivateProxyTests_IMG

从单元测试结果看到PrivateProxy.newProxyInstance()方法成功生成了HelloServiceImpl的代理类cn.zzzzbw.primary.proxy.reflect.$Proxy0, 并且把public和private方法都代理了.

以上功能我们通过实现PrivateProxyGeneratorPrivateProxy两个类, 实现了JDK的动态代理功能, 并且还能代理private方法. 接下来就要考虑如何把Spring Boot里的动态代理功能替换成我们自己的.

替代Spring Boot默认动态代理

上面通过模仿JDK的动态代理, 自己实现了一个能代理private方法的动态代理功能. 现在为了让@Transactional注解能对private方法生效, 就要把自定义的动态代理方法嵌入到Spring Boot的代理流程中

AopProxy

Spring Boot中自带的两种动态代理方式为JDK和Cglib, 对应的实现类是JdkDynamicAopProxyObjenesisCglibAopProxy, 这两个类都是实现AopProxy接口, 实现其中的getProxy()方法返回代理后的对象. 上文也分析了JdkDynamicAopProxy.getProxy()方法是如何返回代理对象的, 这里我们就模仿来实现一个自己的AopProxy.

public class PrivateAopProxy implements AopProxy {
    private final AdvisedSupport advised;
    
    /**
     * 构造方法
     * <p>
     * 直接复制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中的JdkDynamicAopProxyObjenesisCglibAopProxy. 这两种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;
	}
}

既然AopProxyFactoryProxyFactory的一个变量, 那么现在看一下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是一个抽象类并且继承的类和实现的接口比较多, 所以这边我先查看了一下其整个的类结构图(只显示了重要的接口).

AbstractAutoProxyCreator类图_IMG

首先, 看一下其父类和父接口.

其实现了org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor. SmartInstantiationAwareBeanPostProcessor继承InstantiationAwareBeanPostProcessor,而InstantiationAwareBeanPostProcessor继承BeanPostProcessor.

这三个接口是Spring用于创建Bean时的增强功能, 是Spring的IOC和AOP实现的核心思想, 建议大家都去了解一下, 这里就不详细讲解了, 只要知道AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor的接口, 所以能在创建Bean的时候对其进行代理.

接着, 看一下其子类.

其直接子类有AbstractAdvisorAutoProxyCreatorBeanNameAutoProxyCreator. 这两个类的主要区别为切点的不同, 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.aopProxyFactoryPrimaryAopProxyFactory的代码: 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<Class<?>> 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 < requiredPriority) {
					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 < APC_PRIORITY_LIST.size(); 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<Class<?>> o = (List<Class<?>>) apc_priority_list.get(AopConfigUtils.class);
        o.add(PrivateProxyAdvisorAutoProxyCreator.class);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

现在, 再跑一下最开头的单元测试!

结果图_IMG

从单元测试的结果看到, 切面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


原文地址:Service调用其他Service的private方法, @Transactional会生效吗(下)

0条评论
avatar