spring系列之——Aop中的动态代理

什么是代理?

代理就是代理,字面意思,就像明星的经纪人一样,让明星A在晚会上唱一首歌,你找的是经纪人,完成唱歌的是明星A,但是经纪人在唱歌的之前还会完成很多的事情。这就是代理。

代理与Aop:

我们已经了解了Aop是干什么的、怎么干,两者放到一起我们就会发现,代理的特点和Aop的有一些东西是契合的,当然这是因为Aop本身就是由动态代理实现的,但是我们做对比,有助于理解其原理。代理如同经纪人,而我们作为一个大型娱乐公司,为了明星专注于自己的工作(唱歌、跳舞、演戏),我们把大家都需要的谈合同,安排行程交给经纪人,这不就是我们的Aop吗?其中关节还需仔细推敲,见下文。

静态代理

实例:
我们就以歌手唱歌这样一个场景写一个实例,一个业务类,一个代理类,共同实现一个接口,用户找代理类。
实现如下:

接口
public interface ISinger {
    //主要业务逻辑唱歌
    void sing();
}

业务类
public class SingerImpl implements ISinger {

    @Override
    public void sing() {
        System.out.println("歌手唱了一首青藏高原");
    }
}

代理类
public class SingerProxy implements ISinger {
    //目标对象
    private SingerImpl target;

    public SingerProxy(SingerImpl target) {
        this.target = target;
    }

    @Override
    public void sing() {

        before();
        target.sing();
    }

    public void before(){
        System.out.println("歌手与客户签订演出合同");
    }
}


客户类
public class Client {
    public static void main(String[] args) {
        //创建目标对象,客户告诉经纪人要那个歌手
        SingerImpl target=new SingerImpl();
        //创建代理对象,经纪人收到客户要求
        SingerProxy proxy=new SingerProxy(target);
        //歌手怎么唱与客户无关,只是告诉经纪人要让歌手唱歌,内部业务逻辑是经纪人与歌手的事情
        proxy.sing();
    }
}

以上就是一个静态代理的全过程,我们来看一下这种代理的特点。
特点:
一个业务类对应一个代理类,程序一开始就设计好了代理关系,这样就出现一个问题,正如我们的例子一个公司有多个歌手,我们的代理类应该为多个歌手去代理才是我们想要的结果,对比aop我们发现aop就是将我们的非核心业务(也就是公共业务)单独写出来成为一个切面,对多个方法服务,这就是我们代理类的功能,但是显然和静态代理不同,那它用的是什么代理呢?

动态代理:

动态代理顾名思义是动态的,也就是说我们在编译阶段并不知道被代理的对象是谁,而是在需要的时候动态的生成,这样一来我们就可以用一个类,对一系列的有相同增强需求的类实现代理。
Java的代理方式分为两种:JDK动态代理、CGILB动态代理。
JDK动态代理:
学习JDK动态代理之前我们需要先了解一个类、一个接口、我们为了了解这两个东西先做一个动态代理的例子,我们没必要搞懂这个例子,只需要看一下它与静态代理的不同以及InovationHandler和Proxy在其中的作用即可,它们的原理在慢慢分析。
InovationHandler和Proxy实现动态代理的实例:
我们还是使用歌手的例子接口略去:

业务类:
public class SingerImpl implements ISinger {
    @Override
    public void sing() {
        System.out.println("长亭外古道边,芳草碧连天");
    }
}
代理工具类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class SingerProxy implements InvocationHandler {
    private Object target;

    //这是方法就是用来生成代理对象的方法,当然也可以不用写直接在,客户调用时使用Proxy直接生成,这里相当于一个集成。
    public <T> T newProxyInstance(T target) {
        this.target = target;
        Class<?> clazz = target.getClass();
        // a proxy instance 的 invocation handler 实现 InvocationHandler 接口
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy start");
        Object result = null;
                try {
            result = method.invoke(target, args);
        } catch (Exception e) {
            // do something
        }
        System.out.println("proxy end");
        return result;
    }
}
客户类:

public class Client {
    public static void main(String[] args) {
        ISinger singer=new SingerImpl();
        SingerProxy singerProxy=new SingerProxy();
        ISinger proxy=singerProxy.newProxyInstance(singer);
        proxy.sing();
    }
}

运行结果:
在这里插入图片描述
与静态代理的对比:
我们直接去观察客户类:步骤一,都一样,就是生成一个业务类对象;步骤二:SingerProxy proxy=new SingerProxy(target);直接创建代理对象,而动态代理是创建代理类生成工具类用工具类去生成代理对象,这就是他们的不同,为什么静态代理只需要一步呢?因为我们的代理类以经在编译阶段确定了代理对象,而动态代理是没有确定的,也就是这样付出多一步操作完成了动态二字。
InovationHandler与Proxy在其中的作用:
*InovationHandler:*我们可以直观的看到运行结果中包含了,proxy start 和proxy end也就是我们在实现的InovationHandler接口方法中写入的输出语句,再看我们的client类,我们调用的是代理类的sing()方法,我们是否可以做如下推断:
生成的代理类包含了我们的invoke方法在我们调用代理类的业务方法时会结果invoke方法,这里的InovationHandler接口就是拦截了我们的调用方法进行了相应的增强处理。
*Proxy:*proxy的作用就体现在我们的代理工具类中,它用newProxyInstance 方法通过class反射生成了一个代理对象。
Method:我们可以看到在invoke方法中出现了三个参数,第二个参数是method它调运了这个类中的invoke方法,具体作用不清楚,不急慢慢来。
清楚了这些之后我们来具体的看一下这两个类一个接口到底是什么?能干什么?
InvocationHandler(接口):
它就是一个调用管理的接口,其中包含一个方法invoke(Object proxy, Method method, Object[] args)
我们看一下这个类的说明(直接上中文的英语好的可以自己去看源码):
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用
我们来具体看一下这个invoke方法:
/

* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* args:指代代理对象方法传递的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
Proxy(类)实现动态代理的难点:
这个类承担了很大的作用我们使用的创建代理对象的方法是:

(T) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);

这段代码相当于:

InvocationHandler handler = new SingerImpl(...);
Class proxyClass = Proxy.getProxyClass(ISinger.class.getClassLoader(), new Class[] { ISinger.class });
ISinger iSinger = (ISinger) proxyClass.getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] { handler });

由此可见:
(T)它表示的是我们的业务实现的哪个接口;
clazz.getClassLoader表示的是我们接口的加载器(接口和实现类是同一个加载器,当然类的加载器这一部分涉及颇多笔者后面的jvm系列会讲到);
clazz.getInterfaces返回的就是我们类所实现的接口的数组。
我们知道JDK动态代理的业务类是需要实现接口,我们在创建代理类的时候传入的参数是三个:
业务类的类加载器,业务类实现的接口,代理工具类;
Proxy会进行一系列的验证,验证类是否实现了接口、等验证,然后就会动态的生成代理对象,这个类的源码内容非常多,事实上也没有必要学习所有,下面是一段创建代理对象的简单方法的源码(有兴趣可以自己去了解全部源代码):

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   类的加载器
     * @param   类实现的接口的链表
     * @param   实现了InovationHandler接口的代理工具类
     * @return  具有代理类指定调用处理程序的代理实例,该代理类由指定的类加载器定义并实现指定接口
     */
 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    	// null检查,h为null就抛出NullPointerException
        Objects.requireNonNull(h);
		// 将接口类对象数组clone一份。
        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.
         */
          // 查找或者是生成一个特定的代理类对象,具体方法下面分析应该注意的是这里没有传入h参数
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
         //使用指定的调用处理程序调用它的构造函数
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
			// 是static final 修饰的,源码: private static final Class<?>[] constructorParams ={ InvocationHandler.class };
            // 从代理类对象中查找参数为InvocationHandler的构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            
            final InvocationHandler ih = h;
            // 检测构造器是否是Public修饰,如果不是则强行转换为可以访问的。
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 通过反射,将h作为参数,实例化代理类,返回代理类实例。开始没有用到的h参数在这里出现了
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

看到这里还是一脸懵逼,那就对了,因为我们并不清楚,获取的代理类对象cl是怎么来的,我们就需要看下一个方法getProxyClass0:

/**
	 *生成一个代理类对象
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     * 参数有两个也就是我们上一个方法的前两个参数
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //接口数不能超过65535个,正常人写的都超不过
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        // 从代理类对象缓存中,根据类加载器和接口类对象数组查找代理类对象,
        return proxyClassCache.get(loader, interfaces);
    }

这里的proxyClassCache缓存中获取的,我们来看一下这个实例的源码声明:

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

通过·这个声明我们可以看出,首先它是WeakCache的实例,调用的构造函数传入了两个参数一个KeyFactory对象,ProxyClassFactory对象。这里的代码原理相对过于复杂,就不详细赘述,我们只需要知道通过这个从代理类对象缓存中,根据类加载器和接口类对象数组查找代理类对象,如果没有那么它会根据我们的参数去创建。
JDK动态代理生成的代理对象字节码反编译

import com.jpeony.spring.proxy.jdk.IHello;
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 IHello // 继承了Proxy类和实现IHello接口
{
  // 变量,都是private static Method  XXX
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
 
  // 代理类的构造函数,其参数正是是InvocationHandler实例,Proxy.newInstance方法就是通过通过这个构造函数来创建代理实例的
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
 
  // 以下Object中的三个方法
  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  // 接口代理方法
  public final void sayHello()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 
  public final String toString()
    throws 
  {
    try
    {
      return ((String)this.h.invoke(this, m2, null));
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 
  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 
  // 静态代码块对变量进行一些初始化工作
  static
  {
    try
    {
	  // 这里每个方法对象 和类的实际方法绑定
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.jpeony.spring.proxy.jdk.IHello").getMethod("sayHello", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

JDK动态代理总结
使用方法:
1)创建一个接口,
2)业务类实现接口,
3)创建代理工具类实现java.lang.reflect.InvocationHandler接口
4)通过Proxy类的(ISinger)Proxy.newProxyInstance(ISinger.class.getClassLoader(),SingerImpl.class.getInterfaces(), singerProxy);方法创建代理对象,这里可以集成到代理工具类中,不集成的话我们要先通过代理工具类的构造函数,将业务类的实例对象传入到代理工具类中。集成的话可将二者合二为一。
5)客户端测试
Proxy的内部过程:
1)得到三个参数(加载器,实现接口,代理工具类对象)
2)验证三个参数的正确性,
3)通过getProxyClass0方法查找或者是生成一个特定的代理类对象(未传入代理工具类实例参数)
4)获取其构造器
5)通过反射,以代理工具类实例为参数返回代理类实例。
串联关系:
在经过一系列的工作之后我们拿到了一个代理对象,它里面到底有什么,让我们实现了那样一个调用效果,其实代理对象中有一个InvocationHandler对象,而InvocationHandler对象中又有一个被代理对象,使用就有了这样的调用效果。

CGLIB动态代理:

实例:

//业务类
public class SingerImpl {
    public SingerImpl() {
        System.out.println("SingerImpl的构造函数被执行");
    }

    public void sing(){
        System.out.println("长亭外古道边");
    }
}
//自定义MethodInterceptor
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}
//调用测试
public class Client {
    public static void main(String[] args) {
        // 通过CGLIB动态代理获取代理对象的过程,先获取一个空的代理工厂
        System.out.println("步骤1创建空代理工厂");
        Enhancer enhancer=new Enhancer();
        // 设置enhancer对象的父类,相当于传入了被代理对象,这里的方式是继承而不是实现接口
        System.out.println("步骤2传入被代理类");
        enhancer.setSuperclass(SingerImpl.class);
        // 设置enhancer的回调对象
        System.out.println("步骤3传入回调对象也就是我们的增强类");
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        System.out.println("步骤4创建代理类");
        SingerImpl proxy= (SingerImpl) enhancer.create();
        // 通过代理对象调用目标方法
        System.out.println("步骤5执行方法");
        proxy.sing();

    }
}

运行结果:

在这里插入图片描述
我们可以看到,CGLIB动态代理的方式,使用分为如下几步:
1)创建业务类,无需实现接口,当然有接口也可以实现。
2)创建自定义回调类
3)调运测试,使用Enhancer类创建代理对象并使用
CGLIB动态代理原理分析
实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口,源码如下:

package net.sf.cglib.proxy;
 
/**
 * General-purpose {@link Enhancer} callback which provides for "around advice".
 * @author Juozas Baliuka <a href="mailto:baliuka@mwm.lt">baliuka@mwm.lt</a>
 * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
 */
public interface MethodInterceptor
extends Callback
{
    /**
     * All generated proxied methods call this method instead of the original method.
     * The original method may either be invoked by normal reflection using the Method object,
     * or by using the MethodProxy (faster).
     * @param obj "this", the enhanced object
     * @param method intercepted Method
     * @param args argument array; primitive types are wrapped
     * @param proxy used to invoke super (non-intercepted method); may be called
     * as many times as needed
     * @throws Throwable any exception may be thrown; if so, super method will not be invoked
     * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
     * @see MethodProxy
     */    
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
 
}

这个接口只有一个intercept()方法,这个方法有4个参数:

1)obj表示增强的对象,即实现这个接口类的一个对象;

2)method表示要被拦截的方法;

3)args表示要被拦截方法的参数;

4)proxy表示要触发父类的方法对象;
在上面的Client代码中,通过Enhancer.create()方法创建代理对象,create()方法的源码:

public Object create() {
        classOnly = false;
        argumentTypes = null;
        return createHelper();
    }

JDK动态代理与CGLIB动态代理的对比
使用要求:
JDK动态代理需要实现接口,而CGLIB不需要,但是CGLIB方式被代理对象不能是final类
实现方式:
JDK动态代理:通过Proxy类创建一个实现了指定接口的代理类,需要一个实现类InvocationHandler的代理工具类作为参数,我们需要实现的增强也写在这个类中。在代理类的方法调用机制上是反射
CGLIB动态代理:通过Enhancer类创建代理类,它其实是我们业务类的子类是通过ASM的字节码编译框架编译的class文件,它的调用机制不是反射。
性能对比:
CGLIB由于使用了ASM框架所有创建速度低于JDK代理而调用的效率要高于JDK因为JDK动态代理调用机制是反射而CGLIB会生成一个index参数,用这个参数实现调用。
注:部分较为难的原理没有一一讲解,如果有兴趣可以自己去研究源码。

最后一句话:

看到此处便是缘,请您再听我一言;
新生博主甚是难,还请原谅把你烦;
博文写了没人看,博主这活没法干;
看了不评就走人,他人怎知行不行;
看了评论又推荐,博主心里笑开颜。
ps:就想皮一下,但还是希望你能指出错误,如果觉得可以夸奖一下就更好了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值