Spring AOP调用本类的事务方法失效

文章目录

  • 一、JDK动态代理
    • 1、实现方式
    • 2、反编译代理类的文件
    • 3、Proxy部分源码
    • 小结
    • 问题一:本类中方法调用同类中方法会不会走代理?
    • 问题二:为什么必须要实现接口才能使用JDK动态代理?
  • 二、cglib动态代理
    • 1、实现方式
    • 2、代理类字节码编译
  • 核心问题:本类中调用本类自己的方法事务失效?
    • 1、对于JDK动态代理
    • 2、对于cglib动态代理

首先要说明失效问题,必须要了解JDK动态代理和cglib动态代理的原理和调用方式。

一、JDK动态代理

1、实现方式

理解JDK动态代理的实现原理:

  1. 拿到被代理类的引用,并且获取它的所有的接口(反射获取)。
  2. JDK Proxy类要重新生成一个新的类,实现了被代理类所有接口中的方法
  3. 动态生成Java代码,把我们增强的逻辑加入到新生成的代码中。
  4. 编译生成新的Java代码的Class文件
  5. 加载并重新运行新的Class,得到的类就是全新的类。
    实现jdk动态代理代码
// 1、需要动态代理的接口
package jiankunking;
 
/**
 * 需要动态代理的接口
 */
public interface Subject
{

    public String SayHello(String name);

    public String SayGoodBye();
}

//2、实际对象
package jiankunking;
 
/**
 * 实际对象
 */
public class RealSubject implements Subject
{
 
    public String SayHello(String name)
    {
        return "hello " + name;
    }
 
 
    public String SayGoodBye()
    {
        return " good bye ";
    }
}


//3、调用处理器实现类
package jiankunking;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
 
/**
 * 调用处理器实现类
 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
 */
public class InvocationHandlerImpl implements InvocationHandler
{
 
    /**
     * 这个就是我们要代理的真实对象
     */
    private Object subject;
 
    /**
     * 构造方法,给我们要代理的真实对象赋初值
     *
     * @param subject
     */
    public InvocationHandlerImpl(Object subject)
    {
        this.subject = subject;
    }
 
    /**
     * 该方法负责集中处理动态代理类上的所有方法调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
     *
     * @param proxy  代理类实例
     * @param method 被调用的方法对象
     * @param args   调用参数
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("在调用之前,我要干点啥呢?");
 
        System.out.println("Method:" + method);
 
        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object returnValue = method.invoke(subject, args);
 
        //在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("在调用之后,我要干点啥呢?");
 
        return returnValue;
    }
}

//4、测试
package jiankunking;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
 
/**
 * 动态代理演示
 */
public class DynamicProxyDemonstration
{
    public static void main(String[] args)
    {
        //将生成的代理类文件存到项目根目录中
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        //代理的真实对象
        Subject realSubject = new RealSubject();
        
        /**
         * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
         * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
         * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
         */
        InvocationHandler handler = new InvocationHandlerImpl(realSubject);
 
        ClassLoader loader = realSubject.getClass().getClassLoader();
        Class[] interfaces = realSubject.getClass().getInterfaces();
        /**
         * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
         */
        Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
 
        System.out.println("动态代理对象的类型:"+subject.getClass().getName());
 
        String hello = subject.SayHello("jiankunking");
        System.out.println(hello);
//        String goodbye = subject.SayGoodBye();
//        System.out.println(goodbye);
    }
 
}

2、反编译代理类的文件

1、可以看到重写了接口的所有方法

package com.sun.proxy;

import com.zhaoxiang.jdkProxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final String SayGoodBye() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    //继承了接口 实现了接口的方法
    //调用父类的InvocationHandler对象 并且调用了其invoke方法
    //此时Proxy类中的InvocationHandler对象就是InvocationHandlerImpl
    //然后调用InvocationHandlerImpl中的invoke方法
    //invoke方法中做增强处理,method.invoke()同时调用目标方法
    public final String SayHello(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
}

3、Proxy部分源码

public class Proxy implements java.io.Serializable {

    private static final long serialVersionUID = -2222568056686623797L;

    private static final Class[] constructorParams =
        { InvocationHandler.class };


    private static final WeakCache[], Class>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());


    protected InvocationHandler h;

    private Proxy() {
    }


    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    

    //测试类中调用此方法就是将InvocationHandler传入到Proxy的构造方法中,通过反射的方式创建Proxy对象
	public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
       
            final Constructor cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            return cons.newInstance(new Object[]{h});
    }

小结

  1. 分析代理类源码得知JDK代理就是继承Proxy和实现代理类的接口,同时在代理类中实现了接口的所有方法;

  2. 实现的方法中使用了Proxy,传入InvocationHandlerImpl类,调用的是InvocationHandlerImpl类的invoke方法。

  3. 增强是在InvocationHandlerImpl类的invoke方法中做增强,同时会使用反射调用目标类的方法。

问题一:本类中方法调用同类中方法会不会走代理?

​ 一个方法调用本类的其他方法是不会走代理,原因是在InvocationHandlerImpl#invoke中method.invoke(subject, args);这里调用的是目标类subject的方法,直接执行目标类方法,不会执行代理类的方法

问题二:为什么必须要实现接口才能使用JDK动态代理?

​ 因为代理类必须继承自Proxy类,同时又必须实现代理类的所有方法,所以只有实现接口然后重写接口的所有方法才能实现。java单继承的原因。

二、cglib动态代理

1、实现方式

​ JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术(ASM),其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

核心:从始至终都是在代理类中执行方法,调用当前类的this一直都是指向代理类

调用顺序:
1、调用HelloService#sayHello
2、HelloService$$EnhancerByCGLIB$$437ed335#sayHello
3、var10000#intercept(var10000是MyMethodInterceptor类)
4、methodProxy#invokeSuper
5、var10000#sayHello(var10000为代理类HelloService$$Proxy)
6、在代理类中调用super#sayHello,即目标类的方法
7、super#sayHello如果还在调用this.sayBye(),此时的this为代理类,接着会走代理类的sayBye方法

扩展:父类 子类的调用关系

public class Father {

    public void sayHello(){
        System.out.println("say hello");
        this.sayHello1();
    }

    public void sayHello1(){
        System.out.println("father say helloa");
    }
}

public class Son extends Father{

    public void sayBye(){
        super.sayHello();
    }

    @Override
    public void sayHello1(){
        System.out.println("son say hello1");
    }

    public static void main(String[] args) {
        Son son = new Son();
        son.sayBye();
    }

}

//打印结果:
//say hello
//son say hello1
//1、实现一个业务类,注意,这个业务类并没有实现任何接
package com.jpeony.spring.proxy.cglib;
 
public class HelloService {
 
    public HelloService() {
        System.out.println("HelloService构造");
    }
 
    /**
     * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
     */
    final public String sayOthers(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }
 
    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}

//2、自定义MethodInterctptor
package com.jpeony.spring.proxy.cglib;
 
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
 
/**
 * 自定义MethodInterceptor
 */
public class MyMethodInterceptor implements MethodInterceptor{
 
    /**
     * sub:表示增强的对象,即动态代理的这个对象;
     * method:表示要被拦截的方法;
     * objects:表示要被拦截方法的参数;
     * methodProxy: 表示要触发父类的方法对象;
     */
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(sub, objects);//见下面的源码
        System.out.println("======插入后者通知======");
        return object;
    }
}

//3、生成CGLIB代理对象调用目标方法:
package com.jpeony.spring.proxy.cglib;
 
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
 
public class Client {
    public static void main(String[] args) {
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置需要代理的目标类
        enhancer.setSuperclass(HelloService.class);
        // 设置拦截对象 回调的实现类
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        HelloService proxy= (HelloService)enhancer.create();
        // 通过代理对象调用目标方法
        proxy.sayHello();
    }
}

2、代理类字节码编译

​ 有三个字节码类 这里只是摘取部分代码

/**
	1.继承目标类,重写目标类的所有方法
	2.传入自己的MyInterceptor类
	3.调用MyInterceptor中的interceptor方法
	4.在interceptor中做增强处理
	5.注意此时传过来的对象是代理类对象
	6.调用invokeSuper方法时拿到fastClass
	7.再次来到动态代理类的目标方法 然后执行父类的目标方法
	8.如果动态代理类的目标方法中还调用本类的方法,那么又会走代理类的目标方法
*/
public class HelloService$$EnhancerByCGLIB$$437ed335 extends HelloService implements Factory {
    
        public final void sayHello() {
            //此时的
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (var10000 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            if (var10000 != null) {
                //调用MyInterceptor中的interceptor方法,然后调用fastClass#invoke
                //fastClass#invoke中又会来到代理类的目标方法,然后执行父类的目标方法
                var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
            } else {
                //调用父类的方法,此时在this.sayBey的时候其实调用的是代理类的sayBey 还会走代理
                super.sayHello();
            }
    }

}

	
	//此时的obj为代理类
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

//fastClass的invoke方法 此时的var2为代理类对象
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        HelloService var10000 = (HelloService)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                //调用代理类的sayHello方法 又会走到代理类的sayHello方法中
                var10000.sayHello();
                return null;
            case 1:
                return var10000.sayOthers((String)var3[0]);
            case 2:
                return new Boolean(var10000.equals(var3[0]));
            case 3:
                return var10000.toString();
            case 4:
                return new Integer(var10000.hashCode());
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

核心问题:本类中调用本类自己的方法事务失效?

1、对于JDK动态代理

​ 因为JDK动态代理采用的是接口实现的方式,通过反射调用目标类的方法,此时如果调用本类的方法,this指的是目标类,并不是代理类所以不会走代理。不走代理,事务自然会失效。

2、对于cglib动态代理

​ 上文中分析jdk动态代理和cglib动态代理中发现,本类调用本类的方法,如果是cglib代理也会走代理,为什么spring aop中这里不会走代理,事务继续无效呢?

​ 原因是cglib的代理中我们使用的proxy.invokeSuper(obj,args)方法,他就会走代理,而我们的spring AOP中他是采用责任链的方式调用,相当于先将匹配到的Advisor执行完后,直接执行目标类方法,实际走的是类似与MethodBeforeAdviceInterceptor他的invoke方法,没有走invokeSuper方法就没有走cglib动态代理机制。直接执行的目标方法,没有通过代理。

参考:
https://blog.csdn.net/qq_39597872/article/details/122509933

你可能感兴趣的:(Spring,面试,spring,事务)