aop学习笔记之代理模式(三)

1 代理概念

织入的时机分三种:
1.编译期织入(Aspect)
2.类加载时织入(AspectJ 5+)
3.运行时织入(Spring AOP)

那如何实现运行时织入呢?通过代理。
这里写图片描述

上面这张图描述了调用方、代理对象、目标对象之间的关系,目标对象将自己托管给代理对象。比如当调用方发起请求执行目标对象中的方法,代理对象会响应并决定自己在其之前或之后执行相关动作,而原本目标对象该执行的方法代理对象并不去管它,依然抛给目标对象自己执行。代理对象只处理目标对象方法中没有的动作。

2 代理模式

代理又分为哪些呢?
这里写图片描述
这里列出了两种动态代理的代表,Aop在创建代理时就是从他们之中选择。

3 静态代理

接下来我们用代码模拟一下静态代理的实现

这里写图片描述
通过我上面画的这张图我们可以看到,Proxy和RealSubject都继承了一个叫做Subject的接口,当请求从Client发起时,会被代理对象进行处理,执行一些额外的方法,然后又委托目标对象执行目标对象的方法

我们按照这个思路来写代码

3.1 Subject

首先我们创建一个接口叫做Subject,里面只有一个叫做request的方法

package com.example.proxy.pattern;

/**
 * Subject class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public interface Subject {
    void request();
}

3.2 RealSubject

创建目标对象继承上面的Subject接口,重写一下request方法

package com.example.proxy.pattern;

/**
 * RealSubject class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class RealSubject implements Subject{
    @Override
    public void request() {
        System.out.println("real subject execute request");
    }
}

3.3 Proxy

写完了目标对象接下来就是代理对象了,同样继承Subject接口

package com.example.proxy.pattern;

/**
 * Proxy class
 *
 * aspect
 * @author TransientBa
 * @date 2018/3/10
 */
public class Proxy implements Subject{
    /**私有一个目标对象的属性**/
    private RealSubject realSubject;

    public Proxy(RealSubject realSubject){
        this.realSubject = realSubject;
    }

    /**
     * 静态代理 通过重写request 将目标方法委托给目标对象realSubject执行,自己执行其他额外的操作
     */
    @Override
    public void request() {
        System.out.println("before do something");
        try{
            realSubject.request();
        }catch (Exception e){
            System.out.println("exception:" + e.getMessage());
            /** 代理对象不会真正改变方法  还是要把异常抛出来**/
            throw e;
        }finally {
            System.out.println("after do something");
        }
    }
}

3.4 Client测试

接下来我们测试一下整个流程

package com.example.proxy.pattern;

/**
 * Client class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class Client {
    /**
     * 静态代理
     * 缺点:
     * 代理对象要对目标对象中的方法进行委托  代理的逻辑越多 重复的逻辑越多
     * @param args
     */
    public static void main(String[] args) {
        /**客户端通过接口引用目标对象  引入的实现类是代理的实现类  其本身又委托给了目标对象去执行  自己只执行边缘的操作**/
        Subject subject = new Proxy(new RealSubject());
        subject.request();
    }

运行结果:
这里写图片描述

这样我们就完成一个简单的静态代理,那这种代理模式有哪些问题呢?我们回到代理类Proxy中,每当我们在Subject中新增一个方法,我们都需要手动的在Proxy中将新增的方法重写。这样实现起来很麻烦,我们可不可以用一种更通用的办法去解决呢?

4 jdk动态代理

为了解决上面提到的问题,我们重写代理类

4.1 JdkProxySubject

抛弃了原来的Proxy类,重写JdkProxySubject去替代它,在jdk动态代理中需要继承InvocationHandler接口,重写invoke方法

package com.example.proxy.dynamic;

import com.example.proxy.pattern.RealSubject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * JdkProxySubject class
 *
 * aspect
 * @author TransientBa
 * @date 2018/3/10
 */
public class JdkProxySubject implements InvocationHandler{
    private RealSubject realSubject;

    public JdkProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    /***
     * 通过method 利用反射去动态的代理目标类方法  无需像静态代理一样去写每一个要代理的方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before do something");
        Object result = null;
        try{
            result = method.invoke(realSubject,args);
        }catch (Exception e){
            //依然不能对异常进行捕获 还是要抛出去
            System.out.println("exception:"+e.getMessage());
            throw e;
        }finally {
            System.out.println("after do something");
        }
        return result;
    }
}

4.2 Client测试

在新建一个Client测试类

package com.example.proxy.dynamic;


import com.example.proxy.pattern.RealSubject;
import com.example.proxy.pattern.Subject;

import java.lang.reflect.Proxy;


/**
 * Client class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class Client {
    /**
     * 基于接口的Jdk动态代理
     * 优点:
     * 当Subject中新增一个方法时,静态代理必须要重新实现新增的方法,而动态代理则根据反射无需再次添加
     * @param args
     */
    public static void main(String[] args) {
        //通过设置系统变量  把生成的字节码保存下来
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

        /**
         * newProxyInstance传入三个参数
         * ClassLoader:当前类的类加载器
         * Subject.Class:目标接口
         * JdkProxySubject:InvocationHandler以及创建时引用的目标对象RealSubject
         *
         * */
        Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(),
                new Class[]{Subject.class},
                new JdkProxySubject(new RealSubject()));

        subject.request();
    }
}

运行结果如下:
这里写图片描述
为了验证jdk动态代理是否解决了静态代理的问题,我们在添加一个方法dynamicRequest
在Subject中添加

 void dynamicRequest();

在RealSubject中添加

@Override
    public void dynamicRequest() {
        System.out.println("real subject execute dynamicRequest");
    }

回到client中,调用刚才添加的方法

    subject.request();
    System.out.println();
    subject.dynamicRequest();

运行结果如下:
这里写图片描述

可以看到通过反射动态的代理了目标接口的方法,无需像静态代理一样重写方法。
为了弄清楚在这个过程中代理类到底做了什么,我们在Client中添加这样一句话

 //通过设置系统变量  把生成的字节码保存下来
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

正常运行程序后会在项目的目录下找到
这里写图片描述
打开该文件后可以看到

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.example.proxy.pattern.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 m2;
    private static Method m3;
    private static Method m4;
    private static Method m0;

    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})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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 void dynamicRequest() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void request() throws  {
        try {
            super.h.invoke(this, m4, (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)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.example.proxy.pattern.Subject").getMethod("dynamicRequest", new Class[0]);
            m4 = Class.forName("com.example.proxy.pattern.Subject").getMethod("request", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我们可以看到该类中编译了5个方法
这里写图片描述
其中dynamicRequest和request是我们要代理的方法

5 Cglib动态代理

上面的jdk动态代理基于接口,而Cglib基于继承

5.1 CglibProxySubject

不同的是jdk动态代理继承了InvocationHandler接口 ,而Cglib继承了MethodInterceptor

package com.example.proxy.cglib;


import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CglibProxySubject class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class CglibProxySubject implements MethodInterceptor {
    /**
     * 同样类似于jdkProxy ,调用methodProxy.invoke传入要反射的对象和参数
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before in chlib");
        Object result = null;
        try{
            result = methodProxy.invokeSuper(o,objects);
        }catch (Exception e){
            System.out.println("get exception:" + e.getMessage());
            throw e;
        }finally {
            System.out.println("after in cglib");
        }
        return result;
    }
}

5.2 Client测试

package com.example.proxy.cglib;

import com.example.proxy.pattern.RealSubject;
import com.example.proxy.pattern.Subject;
import org.springframework.aop.framework.DefaultAopProxyFactory;
import org.springframework.cglib.proxy.Enhancer;

/**
 * Client class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class Client {
    /**
     * 基于继承的cglib动态代理
     * 优点:
     * 同jdkProxy一样为动态代理,但其基于继承所以无接口的类同样可以进行代理
     * @param args
     */
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //传入目标对象
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new CglibProxySubject());
        Subject subject = (Subject) enhancer.create();
        subject.dynamicRequest();
    }
}

运行结果如下:
这里写图片描述

6 Aop代理的选择

上面我们了解了Aop代理的模式,那Spring在创建Aop代理时是如何选择应用哪一种模式的呢?
首先我们要了解Aop在创建代理时都调用了哪些类的哪些方法
这里写图片描述

我们可以看到在最后DefaultAopProxyFactory中调用了createAopProxy方法

  public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if(!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            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.");
            } else {
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass)?new ObjenesisCglibAopProxy(config):new JdkDynamicAopProxy(config));
            }
        }
    }

其中

return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass)?new ObjenesisCglibAopProxy(config):new JdkDynamicAopProxy(config));

反过来说,当代理对象原本就是一个接口时,或本身就是jdk代理,spring选择Jdk代理,不然选择Cglib
if(!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config))都不满足时也选择应用Jdk动态代理

6.1 强制使用Cglib代理

当proxyTargetClass为True时,Spring强制使用Cglib代理,例如
这里写图片描述

7 总结

  1. JDK动态代理方法只能针对有接口的类的接口方法进行动态代理 因接口中不允许private方法 所以同样不能对private方法进行代理
  2. cglib基于继承来实现代理,无法对static、final这种类进行代理,也无法对private、static方法进行代理
  3. 目标对象实现了接口,则采用JDK代理
  4. 目标对象没有实现接口,采用Cglib代理
  5. 目标对象实现了接口,但强制使用Cglib代理,也是采用Cglib代理
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值