Java 代理机制

本文纯属搬运,参考以下文章(侵删,请联系):
Java代理机制与hook:
http://www.zengye.cc/2016/05/01/java%E4%BB%A3%E7%90%86%E6%9C%BA%E5%88%B6%E4%B8%8Ehook/
Java三种代理模式:静态代理、动态代理和cglib代理:
https://segmentfault.com/a/1190000011291179

代理模式

代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。

举个例子:我们生活中经常到火车站去买车票,但是人一多的话,就会非常拥挤,于是就有了代售点,我们能从代售点买车票了。这其中就是代理模式的体现,代售点代理了火车站对象,提供购买车票的方法。


静态代理

静态代理非常简单
这里写图片描述
就是创建一个代理类,与真实类继承自同一接口,代理类持有真实类的引用,在外部调用代理类,代理类可以在调用真实类的前后加上自己的逻辑。其中接口类可以省略,代理类直接继承自真实类。

// 抽象角色:
abstract public class Subject {
    abstract public void  request();
}

// 真实角色:实现了Subject的request()方法
public class  RealSubject  extends  Subject  {
  public  RealSubject()  { }

  public void  request()  {
     System.out.println( " From real subject. " );
    }
}

// 代理角色:
public class  ProxySubject  extends  Subject  {
  // 以真实角色作为代理角色的属性
  private  Subject realSubject;

  public  ProxySubject(Subject realSubject)  {this.realSubject = realSubject }

  // 该方法封装了真实对象的request方法
  public void  request()  {
     preRequest();
     realSubject.request();  // 此处执行真实对象的request方法
     postRequest();
  }
  ...
}

// 客户端调用:
RealSubject real = new RealSubject();
Subject sub = new  ProxySubject(real);
Sub.request();

或者接口实现

//抽象类
public interface Subject {
    public void request();
}

//真实角色:实现了的request()方法
public class RealSubject implements Subject{
    @Override
    public void request()  {
     System.out.println( " From real subject. " );
    }
}

//静态代理对象
public class ProxySubject implements Subject{
    //以真实角色作为代理角色的属性
    private  Subject realSubject;

  public  ProxySubject(Subject realSubject)  {this.realSubject = realSubject }

  // 该方法封装了真实对象的request方法
  public void  request()  {
     preRequest();
     realSubject.request();  // 此处执行真实对象的request方法
     postRequest();
  }
  ...
}

// 客户端调用:
RealSubject real = new RealSubject();
Subject sub = new  ProxySubject(real);
Sub.request();

静态代理的缺点:
1. 冗余。由于代理对象要实现与目标对象一致的接口/抽象类,会产生过多的代理类。
2. 不易维护。如果真实类中有多个方法需要代理,每个方法都要去实现,一旦接口增加方法,目标对象与代理对象都要进行修改。


基于JDK反射的动态代理

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。

基于JDK的动态代理原理:主要是利用了Java的反射机制。

静态代理与动态代理的区别主要在:

  • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
  • 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
public  interface Subject {
  abstract  public  void request();
}

// 具体角色RealSubject:
public  class RealSubject implements Subject {
  public RealSubject() {}
  public  void request() {
    System.out.println( " From real subject. " );
 }
}

// 基于反射的代理处理器:
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;

public  class Handler implements InvocationHandler {
  private Object sub;
  public Handler(Object obj) {
    sub = obj;
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println( " before calling "  + method);
    method.invoke(sub,args);
    System.out.println( " after calling "  + method);
    return null ;
  }
}
//调用
RealSubject rs = new RealSubject(); // 在这里指定被代理类
InvocationHandler hander = new Handler(rs);
Class cls = rs.getClass();
Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), hander);
subject.request();

可以通过 Proxy.newProxyInstance() 方法来动态的创建一个代理。这个方法有3个参数:

ClassLoader loader :负责加载动态代理类,将字节码文件加载进虚拟机并生成相应的class
这里得到的loader是其子类AppClassLoader(负责加载应用层字节码)的一个实例

Class<?>[] interfaces : 接口数组

InvocationHandler h :InvocationHandler接口的实例,把方法调用转到代理上

所有对 proxy 的调用都被转向到实现了 InvocationHandler 接口的 handler 上。

invoke()方法中的 proxy 参数是实现要代理接口的动态代理对象。通常你是不需要他的。
invoke()方法中的 Method 对象参数代表了被动态代理的接口中要调用的方法,从这个method对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。
invoke()方法中的 Object[]数组参数包含了被动态代理的方法需要的方法参数。注意:原生数据类型(如int,long等等)方法参数传入等价的包装对象(如Integer, Long等等)。

动态代理特点:
1. 动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。
2. 动态代理是使用反射去调用真实类,处理代理的逻辑都在 InvocationHandler 实现类的 invoke 方法中,比静态代理好维护。但是JDK的动态代理机制必须基于接口,Proxy.newProxyInstance 方法必须传入一个接口,在非接口实现类中,只能使用静态代理,或使用第三方方案,例如CGLIB库。


CGLIB动态代理

CGLIB(Code Generation Library) 是一个基于 ASM 的字节码生成库,它允许在运行时对字节码进行修改和动态生成,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。CGLIB 通过 继承方式 实现代理。

ASM 是一个JAVA字节码分析、创建和修改的开源应用框架,可以参考
http://victorzhzh.iteye.com/category/140253

cglib与反射动态代理的区别是

  • 使用动态代理的对象必须实现一个或多个接口
  • 使用cglib代理的对象则无需实现接口,达到代理类无侵入。
  • cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。
//真实类
public class Subject{
    public void request() {
        System.out.println( " From real subject. " );
    }
}

// CGLIB动态代理
// 1. 首先实现一个 MethodInterceptor,方法调用会被转发到该类的 intercept()方法。
public class ProxyFactory implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //before do something
        return proxy.invokeSuper(obj, args);
        //after do something
    }
}
// 2. 然后在需要使用真实类的时候,通过CGLIB动态代理获取代理对象。

//例子
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyFactory implements MethodInterceptor{

    private Object target;//维护一个目标对象
    public ProxyFactory(Object target) {
        this.target = target;
    }

    //为目标对象生成代理对象
    public Object getProxyInstance() {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类对象代理
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开启事务");
        // 执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("关闭事务");
        return null;
    }
}

//目标对象
Subject target = new Subject();
//代理对象
Subject proxy = (Subject) new ProxyFactory(target).getProxyInstance();
//执行代理对象方法
proxy.request();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值