阿里-马云的学习笔记

导航

静态代理、动态代理

  代理模式最大的优势就是能够解耦,在spring中也是广泛使用。spring中一个重要的特性就是aop,aop是个啥东西呢?其实很简单,比如现在有个业务方法,那这个业务方法很重要,涉及到非常重要的业务数据,那对于广大企业应用来说,为了以后能够及时的定位问题,需要记录相关入参以及出参到日志表。

但是对于企业应用来说,需要记录日志的地方应该是蛮多的,如果每个方法中都手动的去写这些记录日志的东西,就会特别的冗余,那使用代理模式就可以解决。

 

一、静态代理

  1、User接口

package com.ty.staticProxy;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下没有难敲的代码!
 */
public interface User {

    /*
     * 业务逻辑接口
     */
    public void work(String workName);
}

  

  2、UserImpl实现类

package com.ty.staticProxy;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下没有难敲的代码!
 */
public class UserImpl implements User {

    /*
     * 实际业务逻辑实现方法
     */
    @Override
    public void work(String workName) {
        System.out.println("我是做" + workName + "的");
    }

}

 

  3、代理类

package com.ty.staticProxy;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下没有难敲的代码!
 * 此类为代理类,并且实现业务逻辑类接口,接收一个实际业务处理对象
 */
public class ProxyUser implements User {

    private User user;
    
    public ProxyUser(User user) {
        this.user = user;
    }
    
    @Override
    public void work(String workName) {
        /*
         * 调用实际业务逻辑处理前可以定制化一些功能
         */
        System.out.println("工作前先放松放松=============");
        /*
         * 调用实际业务逻辑处理方法
         */
        user.work(workName);
        /*
         * 调用实际业务逻辑处理后也可以定制一些功能
         */
        System.out.println("工作后还是要放松放松=============");
    }

}

 

  4、StaticProxyDemo

package com.ty.staticProxy;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下没有难敲的代码!
 */
public class StaticProxyDemo {

    public static void main(String[] args) {
        User proxyUser = new ProxyUser(new UserImpl());
        proxyUser.work("java开发");
    }
}

运行结果:

工作前先放松放松=============
我是做java开发的
工作后还是要放松放松=============

优点:

  • 1、 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 2、 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的

缺点:

 

  • 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  • 2、 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。使用静态代理模式需要程序员手写很多代码。

 

二、动态代理

在java中,实现动态代理主要有两种方式,一种是jdk动态代理,一种是cglib

1、jdk动态代理

User以及UserImpl跟上面一致

a、UserDynamicProxy

package com.ty.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author Taoyong
 * @date 2018年7月2日
 * 天下没有难敲的代码!
 */
public class UserDynamicProxy implements InvocationHandler {

    private User user;
    
    public UserDynamicProxy(User user) {
        this.user = user;
    }

    /*
     * jdk动态代理基于接口,其中proxy好像没啥卵用、method代表当前被代理对象的实际调用方法、args则代表方法参数
     * 由invoke方法对被代理对象进行相关的增强
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("工作前放松放松========");
        method.invoke(user, args);
        System.out.println("工作后也要放松放松===========");
        return null;
    }

}

 

b、DynamicProxyDemo

package com.ty.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @author Taoyong
 * @date 2018年7月2日
 * 天下没有难敲的代码!
 */
public class DynamicProxyDemo {

    public static void main(String[] args) {
        User user = new UserImpl();
        InvocationHandler h = new UserDynamicProxy(user);
        /*
         * 使用Proxy的静态方法是生成代理类的核心。
         * 一共有三个参数:
         * 1、第一个参数是被代理类的类加载器,通过此类加载器将代理类加载入jvm中;
         * 2、第二个参数则是被代理类所实现的所有接口,需要所有的接口的目的是创建新的代理类实现被代理类的所有接口,保证被代理类所有方法都能够
         * 被代理。其实代理的核心就是新创建一个类并实例化对象,去集成被代理对象所有功能的同时,再加入某些特性化的功能;
         * 3、第三个参数则是真正的扩展,使用动态代理的主要目的就是能够对原方法进行扩展,尤其是对于大部分方法都具有的重复方法(例如记录日志),
         * 可以理解为面向切面编程中的增强.
         */
        User proxy = (User) Proxy.newProxyInstance(User.class.getClassLoader(), user.getClass().getInterfaces(), h);
        /*
         * 在调用生成的代理类对象后,调用原方法后,该method对象以及参数等会被传入到InvocationHandler的invoke方法中,由InvocationHandler的
         * invoke方法对被代理类对象进行增强。
         */
        proxy.work("敲代码");
        proxy.eat("吃大餐");
        
    }
}

 

 2、cglib代理

jdk动态代理的缺点就是必须基于接口,没有接口就无法实现代理,而cglib则是使用继承的方式去生成代理类,使用范围更广

a、添加cglib.jar、asm.jar(注意jar包版本)

使用cglib前必须进行导包,并且版本如果过低会导致报错

 

 

b、UserImpl(使不使用接口都可以)

package com.ty.dynamic.cglib;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下没有难敲的代码!
 */
public class UserImpl {
    
    /*
     * 实际业务逻辑实现方法
     */
    public void work(String workName) {
        System.out.println("我是做" + workName + "的");
    }

    
}

 

c、CglibDynamicProxy

package com.ty.dynamic.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * @author Taoyong
 * @date 2018年7月2日
 * 天下没有难敲的代码!
 */
public class CglibDynamicProxy implements MethodInterceptor {

    /*
     * 实际的增强
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("这是前处理==============");
        methodProxy.invokeSuper(obj, objects);        
        System.out.println("这是前处理==============");
        return null;
    }

}

 

d、CglibProxyDemo

package com.ty.dynamic.cglib;

import net.sf.cglib.proxy.Enhancer;

/**
 * @author Taoyong
 * @date 2018年7月2日
 * 天下没有难敲的代码!
 */
public class CglibProxyDemo {

    public static void main(String[] args) {
        /*
         * 创建字节码增强器
         */
        Enhancer enhancer = new Enhancer();
        /*
         * 被代理类设置为字节码增强器父类,cglib使用的是继承方式去创建代理类
         */
        enhancer.setSuperclass(UserImpl.class);
        /*
         * 设置字节码增强器回调方法。对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截
         */ 
enhancer.setCallback(
new CglibDynamicProxy());

/*
* 创建代理实例

*/
UserImpl userImpl
= (UserImpl) enhancer.create(); userImpl.work("敲代码"); } }

3、原理-----基于jdk动态代理

上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。上述代码里,唯一的“黑厘子”就是Proxy.newProxyInstance()方法,除此之外再没有任何特殊之处。

在JDK动态代理中涉及如下角色:

业务接口Interface、业务实现类target、业务处理类Handler、JVM在内存中生成的动态代理类$Proxy0

动态代理原理图:

 

说白了,动态代理的过程是这样的:

  1. Proxy通过传递给它的参数(interfaces/invocationHandler)生成代理类$Proxy0;

  2. Proxy通过传递给它的参数(ClassLoader)来加载生成的代理类$Proxy0的字节码文件;

动态代理的关键代码就是Proxy.newProxyInstance(classLoader, interfaces, handler),我们跟进源代码看看

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
  // handler不能为空
    if (h == null) {
        throw new NullPointerException();
    }

    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.
     */
  // 通过loader和接口,得到代理的Class对象
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
            // create proxy instance with doPrivilege as the proxy class may
            // implement non-public interfaces that requires a special permission
            return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    return newInstance(cons, ih);
                }
            });
        } else {
       // 创建代理对象的实例
            return newInstance(cons, ih);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    }
}

我们看一下newInstance方法的源代码:

private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
    try {
        return cons.newInstance(new Object[] {h} );
    } catch (IllegalAccessException | InstantiationException e) {
        throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString());
        }
    }
}

讲解完了代理类的生成源码,我们一定想要看看代理类的代码是什么样的,下面提供一个生成代理类的方法供大家使用: 

/**
 * 代理类的生成工具 
 * @author ChenHao
 * @since 2019-4-2 
 */
public class ProxyGeneratorUtils {

    /**
     * 把代理类的字节码写到硬盘上 
     * @param path 保存路径 
     */
    public static void writeProxyClassToHardDisk(String path) {
        // 第一种方法
        // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);  

        // 第二种方法  

        // 获取代理类的字节码  
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces());

        FileOutputStream out = null;

        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class");
    }

}

此时就会在指定的C盘x文件夹下生成代理类的.class文件,我们看下反编译后的结果:

package org.fenixsoft.bytecode;

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 DynamicProxyTest.IHello
{
  private static Method m3;
  private static Method m1;
  private static Method m0;
  private static Method m2;
  /**
  *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
  *super(paramInvocationHandler),是调用父类Proxy的构造方法。
  *父类持有:protected InvocationHandler h;
  *Proxy构造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  /**
  * 
  *这里调用代理对象的sayHello方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
  *this.h.invoke(this, m3, null);  this.h就是父类Proxy中保存的InvocationHandler实例变量
  *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
  *再联系到InvacationHandler中的invoke方法。嗯,就是这样。
  */
  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);
    }
  }

  // 此处由于版面原因,省略equals()、hashCode()、toString()三个方法的代码
  // 这3个方法的内容与sayHello()非常相似。

  static
  {
    try
    {
      m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

java.lang.reflect.Proxy

public class Proxy implements java.io.Serializable {

    protected InvocationHandler h;

    private Proxy() {
    }

    protected Proxy(InvocationHandler h) {
        doNewInstanceCheck();
        this.h = h;
    }
    //
}

这个代理类的实现代码也很简单,它为传入接口中的每一个方法,以及从 java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实现 ,并且统一调用了InvocationHandler对象的invoke()方法(代码中的“this.h”就是父类Proxy中保存的InvocationHandler实例变量)来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用动态代理的哪一个方法,实际上都是在执行InvocationHandler.invoke()中的代理逻辑。

 

posted on 2018-07-02 23:07  阿里-马云的学习笔记  阅读(1444)  评论(0编辑  收藏  举报