由动态代理浅析SpringAOP和声明式事务

     **

由动态代理浅析SpringAOP和声明式事务

**

动态代理顾名思义就是代理呗!,有人说,那你不跟没讲一样,其实就类似于中介一样,比如,你要卖房子,给中介代理,也就是说它要怎么宣传怎么吹牛逼,什么添油加醋都是中介干的事,但是你的房子是不变的。同理,动态代理就是不改变原有的(房子)代码的基础上,进行扩展,增强。

java动态代理的方式主要由两种

1.基于jdk环境的Proxy生成一个动态代理对象(代理对象只能代理实现接口的方式)

2.第三方cglib生成代理对象(代理对象可以实现基于类的)

下面我们来浅析下jdk实现代理对象

下面有个接口,实现类,非常简单

package com.lzh.staticProxy;

public interface Vehicle {
    void run();
}

public class Car implements Vehicle{
    @Override
    public void run() {
        System.out.println("小汽车开始运行了");
    }
}

再写一个能代理Vehicle接口实现类的工具类,提供一个方法去返回被代理类的代理对象,写个非常简单的demo

public class VehicleProxyProvided implements InvocationHandler{
    
    private Vehicle vehicle;
    
    
    public Vehicle bind(Vehicle vehicle){
        this.vehicle = vehicle;
        //这里就是重点通过反射包的Proxy,返回一个代理对象
        //这里就是要传三个参数,直接要什么参数就提供什么参数
        
        return (Vehicle)Proxy.newProxyInstance(vehicle.getClass().getClassLoader(),
                vehicle.getClass().getInterfaces()
                ,this);
        
    }
    
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("交通工具开始运行");
        Object invoke = method.invoke(vehicle, args);
        System.out.println("交通工具停止运行");
        return invoke;
    }
    
}

有小伙伴可能一脸懵逼,它是怎么获得的代理对象,直接进入源码,就晓得了

提示一下上面的Proxy的第三个参数是一个接口类型InvocationHandler,直接打开该接口,定义了一个invoke方法,由于是接口就没办法new了,那就想肯定传进去的是该接口的实现类,如上代码 VehicleProxyProvided implements InvocationHandler 说明对该接口的方法进行实现,this传进去就完事。

进入源码:直接看重点

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        
        
        //源码翻译的很明白查找或生成指定的代理的Class类,是由asm技术动态的生成Class类。这里怎么动态
        //生成的Class并加载到内存,装载到JVM有点复杂,有兴趣可以自己往下追
        //那拿到Class创建个对象不是轻轻松松嘛,直接底层反射创建对象
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
             /*通过反射
             拿到参数构造器,追进去源码看他的构造器的参数类型是啥
             private static final Class<?>[] constructorParams ={ InvocationHandler.class };
             这参数类型不就是第三个传的参数类型嘛,说明它生成的代理类一定有个构造器是带这种类型
             
             */
            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 (IllegalAccessException|InstantiationException e) {
           
       

可能还有小伙伴懵逼怎么就生成了代理对象,由于代理类直接加载到内存里,但是我们可以通过流输入该class文件,

当我们看到该Class类的源文件就明白通透了

如下:

public class Test {
    public static void main(String[] args) {
        //只要加上这句话就能输出代理类对象的Class文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        
        VehicleProxyProvided vehicleProxyProvided = new VehicleProxyProvided();
        Vehicle bind = vehicleProxyProvided.bind(new Car());
        bind.run();
    }
}

输出结果如下:

交通工具开始运行
小汽车开始运行了
交通工具停止运行

输出代理类Class文件如下:

你们看看这代理了干了啥事,直接在源码的疑惑都明白了

public final class $Proxy0 extends Proxy implements Vehicle {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
    
    
    //这不是静态代码块嘛?也就是初始化的时候先通过反射自动给这些属性进行初始化
     static {
        try {
            m1=Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object")); 
            m3 = Class.forName("com.lzh.staticProxy.Vehicle").getMethod("run");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }

    //反射的时候它拿的是这个构造器,它居然调用了它的父类的那个构造器,追进去看父类干了啥
    /*
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    */
    
    //这就是它的父类构造器,它居然把第三个参数赋值给一个h的属性?
    
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    
    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    
         
    //这里是重点重点!!!!
    //你看它干了啥事,这不就是上面说的h属性,居然去调用父类h属性的.invoke()方法,
    //并把三个参数传过去了那就通透了
    //也就是说,根据OOP的动态绑定,会绑定到第三个参数的invoke方法
    //上面我们不是重写了invoke方法也就是会动态绑定到我们这个invoke方法,把上面的方法拿下来如下所示进行分析
    /*
       @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("交通工具开始运行");也就是说我们可以在这里进行扩展增强
        Object invoke = method.invoke(vehicle, args);这里调用了被代理的对象的run
        System.out.println("交通工具停止运行");我们在这里也可以进行扩展增强
        return invoke;
    }   
    
    你们开头看静态代码块的m3初始化的是什么
    
    m3 = Class.forName("com.lzh.staticProxy.Vehicle").getMethod("run");
    不就是通过反射,拿到我们被代理类的run方法嘛
    在进行调用被代理类的方法逻辑,实现不改变原有的代码的基础上增强和扩展的目的
    */     
         
    public final void run() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

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

由上面分析大家大概知道为啥代理对象,能调动被代理对象的方法,

其实底层都是通过反射+动态绑定,在由初始化静态代码块的Method方法的不同,再通过Mothod对象反射调用被代理对象的不同方法,底层还重写了Object类的hashCode(),toString()…等等方法,在重写的方法一样里调用invoke(),再去调用原来的对象的方法

不得不说,这个底层自动生成Class的代理类是真的牛逼

有了一定的基础我们再去简析SpringAOP机制,有一定的IOC基础可以往下浅析

为什么我们通过注解就能把方法切入各种类各个面

为了理解我们可以配置如下如示的后置处理器

public class PostProcessedHandler implements BeanPostProcessor {
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("after bean type = "+bean.getClass());
        return null;
    }
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("before bean type = "+bean.getClass());
        return null;
    }
}

由于SpringIOC容器会初始化,看你是否是单例,是否懒加载,等等,底层会创建好来放入IOC容器中,底层是通过SetXX对某些类进行初始化的,后它会执行我们上面配置的后置处理器

先调用postProcessBeforeInitialization, 在调用(如果你配置了初始化方法的话)初始化方法,再调用后置处理器,最后放入你的IOC容器中

Spring会直接上来就扫描我们的配置的文件,或者是注解有没有配置切面类

举个例子:如下切面类

@Aspect
@Component
public class AspectClass {
    
    
    @Before(value = "execution(* Pig.getSub(..))")
    public void showBeforeLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        System.out.println("beforeLog...代码执行成功 methodName = "+methodName);
    }
    
    
    public void successEndLog(JoinPoint joinPoint, Object res){
        System.out.println("afterReturning ... result = "+res);
    }

以before为例子:其他同理

它会扫描到这个注解类,底层通过反射看你的方法上面有没有注解有注解扫描到你的value值,

底层通过自己的一套匹配机制哪个类需要切入,是Pig类需要切入,对这个value字符串进行解析

并把解析的结果存起来,再回到我们IOC初始化的时候去进行匹配,当对我们Pig类初始化的时候

setXX完成后调用我们后置处理器的postProcessBeforeInitialization()方法

在这里插入图片描述

打印出来这个对象运行类型还是原来的Pig实例对象

但是postProcessAfterInitialization()后确是代理类型,牛逼,,这也就是为啥我们能切入到Pig类了

举个简单的例子更容易理解

由学到的动态代理,代理对象底层不是都会调用到传进去第三参数的invoke()方法里

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       if(method.getName.equal("getSub")){//匹配到getSub
        Spring底层肯定能拿到切面类实例    用切面对象实例.showBeforeLog()//这不就相当于前置通知try{
             Object invoke = method.invoke(pig, args);// 这里相当于调用原对象的方法
          ....后置通知等等也如此,spring肯定不肯能如此简单,只是简单举例子理解
           }catch(Exception e){

            } finnaly{

               }      

         }else{
           //如果不是getSub方法就不进行调用
             Object invoke = method.invoke(pig, args);//
          }
        return invoke;
    }

上面只是简单理解,Spring底层进行了大量的封装,才有我们看到的注解配置一下就能切入想切入的方法

也就是说IOC容器最后存放进去的是代理对象,通过代理对象切入需要的方法后,再去调用真正的对象,

而且这种机制也就是能有四个切面的出现,调用方法前,前置通知, 后 返回通知, 出现异常 异常通知进行切入

finally中的最终通知。

基于这几个切面,这不就是为事务管理进行的量身定制,真的牛逼

//传统的事务管理不就是把start Transaction set false
try{
    //进行crud操作数据库(这个必须是原子性,要嘛全部成功,要嘛全部失败)
    //所有操作成功后提交
  }   catch(Exception e){
        //这里进行回滚
    }

这不就一一对应上AOP的切面,只是Spring底层进行了封装,只需要加个注解就行,只要有这个注解就是IOC容器里放的是代理对象,这不就又是AOP了

为什么说不要在声明事务的类内部方法去调用声明事务的方法,这会导致声明事务失效?我想由上面的讲解大家都明白了吧

没有走代理对象,不就没走切面

异常通知进行切入

finally中的最终通知。

基于这几个切面,这不就是为事务管理进行的量身定制,真的牛逼

//传统的事务管理不就是把start Transaction set false
try{
    //进行crud操作数据库(这个必须是原子性,要嘛全部成功,要嘛全部失败)
    //所有操作成功后提交
  }   catch(Exception e){
        //这里进行回滚
    }

这不就一一对应上AOP的切面,只是Spring底层进行了封装,只需要加个注解就行,只要有这个注解就是IOC容器里放的是代理对象,这不就又是AOP了

为什么说不要在声明事务的类内部方法去调用声明事务的方法,这会导致声明事务失效?我想由上面的讲解大家都明白了吧

没有走代理对象,不就没走切面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值