Java中的代理


关于代理:
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

1.静态代理

静态代理就是自己定义一个代理类,在代理类中调用目标对象。然后,使用时,使用你的代理类。

示例:
(1)接口(目标对象与代理对象都要实现该接口)

public interface IUserDao {

    void save();
}

(2)目标对象

public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已经保存数据!----");
    }
}

(3)代理对象

public class UserDaoProxy implements IUserDao{
    //接收保存目标对象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }

    public void save() {
        System.out.println("开始事务...");
        target.save();//执行目标对象的方法
        System.out.println("提交事务...");
    }
}

(4)使用目标对象功能时,使用代理对象

public class App {
    public static void main(String[] args) {
        //目标对象
        UserDao target = new UserDao();

        //代理对象,把目标对象传给代理对象,建立代理关系
        UserDaoProxy proxy = new UserDaoProxy(target);

        proxy.save();//执行的是代理的方法
    }
}

缺点

静态代理由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。这种模式的缺点就是:

  1. 不够灵活,对于某类或是某个目标对象,一般要实现一个代理对象。

  2. 另外,当目标对象接口改变时,代理对象也需要改变,难以做到动态感知变化。

2.动态代理

代理类在程序运行时创建的代理方式被成为动态代理。静态代理中,代理类是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的

2.1 代理代理实现方式一:jdk 动态代理框架

https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

2.1.1 Proxy类

https://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html

该类是工具类,用于动态生成代理类、代理类的类实例对象(代理对象)

在这里插入图片描述

Proxy提供了静态方法来创建动态代理类和实例。同时,所有通过Proxy创建的动态代理类都属于Proxy的子类(因此,该种模式只能代理接口,不能代理类。因为代理类没有办法多继承目标类)。

使用Proxy类创建代理对象的方式有两种:
(1)先创建代理类,通过反射调用代理类的构造方法创建代理实例

     InvocationHandler handler = new MyInvocationHandler(...);
     Class proxyClass = Proxy.getProxyClass(
         Foo.class.getClassLoader(), new Class[] { Foo.class });
    
     Foo f = (Foo) proxyClass.
         getConstructor(new Class[] { InvocationHandler.class }).
         newInstance(new Object[] { handler });

(2)直接获取代理实例

     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                          new Class[] { Foo.class },
                                          handler);

在运行过程中生成的动态代理对象实现了一系列的接口,具有如下特点

  • 实现了代理接口;
  • 代理对象都是代理类的实例。每个代理对象都会关联一个invocation handler对象;
  • 在代理对象上,调用代理接口中的相关方法时,都会转到invocation handler对象的invoke方法中,同时会将本代理对象实例作为invoke方法的参数传递过去(多个代理对象可以绑定到一个invocation handler上,这样可以多不同的代理对象进行区分),另外,会invoke方法会接收目标方法的参数(但是类型已经擦除了,为了通用性,内部需要做类型转化)。invoke方法的返回值会作为代理接口方法的返回值。InvocationHandler接口的invoke方法的完整签名如下:
Object  invoke(Object proxy, Method method, Object[] args) 
          Processes a method invocation on a proxy instance and returns the result.

动态生成的代理类具有如下特点:

  • 代理类是public,final, not abstract;
  • 动态类的类名没有具体的规范(不要对类名做任何假设)。但是,以$Proxy 开头类名空间应该保留给动态代理类;
  • 动态代理类继承了java.lang.reflect.Proxy类;
  • 如果代理类实现了non-public的接口,则代理类的应该在和该接口相同的包中定义;
  • 代理类实现了生成时声明的所有接口。在该类class 对象上调用getInterfaces 方法会返回所有的声明的接口;
  • 通过Proxy.isProxyClass方法可判定一个类是不是java 动态代理类(java动态代理通过Proxy.getProxyClass方法或是Proxy.newProxyInstance方法创建。通过其他方式创建的类都不是java 动态代理);
  • 动态代理类的java.security.ProtectionDomain和由bootstrap类加载器加载的系统类(如java.lang.Object)是一样的。因为,代理类的代码是由可信的系统代码生成。动态代理类一般被授予java.security.AllPermission权限;
  • 所有的java动态代理类都有一个公共的构造方法,该方法接收一个参数: 实现了InvocationHandler接口的实例;

动态代理对象具有如下特点:

  • 加入目标对象接口为Foo, 动态代理对象为proxy,则proxy对象是Foo接口实例,即proxy instanceof Foo,也就是说转型(Foo) proxy代码不会抛出异常;
  • 所有的动态代理对象都会和一个invocation handler实例关联,可通过Proxy.getInvocationHandler方法获取某个代理对象关联的handler;
  • 通过动态代理对象调用代理接口上的方法,都会分派转成成对InvocationHandler.invoke方法的调用;

多个代理接口中相同的方法处理

当某个代理类的多个接口具有相同的方法(相同的方法名字与参数签名)时,则在invoke方法中传入的Method是代理类中第一个包含该方法的接口的方法对象。

如果调用的方法是java.lang.Object的方法,则传入的Method方法是代理接口方法。

proxy类 成员变量、方法

成员或方法 描述
protected InvocationHandler h类实例变量,标识代理对象绑定的invocation handler实例
protected Proxy(InvocationHandler h)构造方法:Constructs a new Proxy instance from a subclass (typically, a dynamic proxy class) with the specified value for its invocation handler.
static InvocationHandler getInvocationHandler(Object proxy)返回代理对象所绑定的handler
static Class<?> **getProxyClass**(ClassLoader loader, Class<?>… interfaces)f获取给定类加载器与代理接口,返回对应的代理类
static boolean isProxyClass(Class<?> cl)判定某个类是否是java动态代理类
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成代理interfaces接口的代理对象

在这里插入图片描述

2.1.2 InvacationHandler

https://docs.oracle.com/javase/6/docs/api/java/lang/reflect/InvocationHandler.html

在这里插入图片描述

InvacationHandler接口方法

  • 只有一个方法,方法签名: Object invoke(Object proxy, Method method, Object[] args) :对代理对象proxy调用method方法,方法参数为args。

2.1.3 使用java代理的一般步骤与示例

(1)一般步骤:

  • 实现InvacationHandler,在invoke方法中实现对方法的过滤、特殊处理逻辑;
  • 通过Proxy类生成代理对象,传入自定义的invocation handler;
  • 调用代理对象方法。

(2)示例

  • 代理的接口
public interface Calculator {
    public Integer add(Integer num1, Integer num2);
    public Integer minus(Integer num1, Integer num2);
}
  • 目标对象
public class CalculatorImpl implements Calculator {
 
    @Override
    public Integer add(Integer num1, Integer num2) {
        int ret = num1 + num2;
        System.out.println("in calculatorImpl, res: " + ret);
        return ret;
    }
     
    @Override
    public Integer minus(Integer num1, Integer num2) {
        int ret = num1 - num2;
        System.out.println("int calculatorImpl, res: " + ret);
        return ret;
    }
 
  • 实现InvocationHandler接口
public class CalculatorHandler implements InvocationHandler {
     
    private Object obj; //被代理类
     
    public CalculatorHandler(Object obj) {
        this.obj = obj;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("in calculatorhandler, before invocation");
         
        Object ret = method.invoke(obj, args);  //执行被代理类方法
         
        System.out.println("in calculationhandler, after invocation");
        return ret;
    }
 
}
  • 使用动态代理

CalculatorHandler calculatorHandler = new CalculatorHandler(new CalculatorImpl() );

Calculator calculator = (Calculator) Proxy.newProxyInstance(calculatorImpl.getClass().getClassLoader(), calculatorImpl.getClass().getInterfaces(), calculatorHandler);

System.out.println(calculator.add(1,2));
System.out.println(calculator.minus(1, 2));

2.2 代理代理实现方式二:CGLIB 动态代理

https://github.com/cglib/cglib

http://www.cnblogs.com/monkey0307/p/8328821.html

官方说明:Byte Code Generation Library is high level API to generate and transform JAVA byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access(主要用于生成动态代理对象、访问拦截)

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问
在介绍CGLib之前,可先看看CGlib的基础:ASM。

2.2.1 asm

https://blog.csdn.net/youyou1543724847/article/details/85248855
ASM是一个多功能的java 字节码分析操控框架,可用于分析现有的类、或是动态的生成二进制的类。ASM提供了一些通用的字节码转化器和分析算法,复杂的自定义的转化器和分析工具可在此基础上进行二次开发。

2.2.2 Enhancer

http://cglib.sourceforge.net/apidocs/net/sf/cglib/proxy/Enhancer.html

在这里插入图片描述

用处生成代理对象(作用类似与JDK中的Proxy类),但是功能比Proxy类强大,可以生成类的代理(Proxy只能生成接口的代理对象)。动态生成的代理子类覆盖重写了父类中的非final方法,并且,对将对这些方法的调用转发给注册的interceptor的回调函数。

最常使用的回调类型是MethodInterceptor,该种回调可以在目标方法调用前、后添加新的aop代码(用aop的术语,就是实现了“around advice”)。同时也可以在调用目标方法前,修改目标方法的参数,或是干脆不调用目标方法。

2.2.3 MethodInterceptor

在这里插入图片描述

提供 “around advice” 功能的Enhancer回调。

MethodInterceptor接口只有一个方法intercept,方法签名如下:

java.lang.Object intercept(java.lang.Object obj, java.lang.reflect.Method method, java.lang.Object[] args, MethodProxy proxy) :所有被代理的方法都会通过这个方法进入,而不是直接调用目标方法。

2.2.4 使用cglib 动态代理示例

(1)代理目标对象

public class PersonService {
    public PersonService() {
        System.out.println("PersonService构造");
    }
    //该方法不能被子类覆盖
    final public Person getPerson(String code) {
        System.out.println("PersonService:getPerson>>"+code);
        return null;
    }

    public void setPerson() {
        System.out.println("PersonService:setPerson");
    }
}

(2)实现方法代理拦截器

public class CglibProxyIntercepter implements MethodInterceptor {
//sub   : cglib生成的代理对象
//method:   被代理的方法()目标方法
//objects:  被代理的方法的方法参数
// methodProxy:  代理方法
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("执行前...");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("执行后...");
        return object;
    }
}

(3)测试类:

public class Test {
    public static void main(String[] args) {
        //代理类class文件存入本地磁盘
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PersonService.class);
        enhancer.setCallback(new CglibProxyIntercepter());


        PersonService proxy= (PersonService)  enhancer.create();
        proxy.setPerson();
        proxy.getPerson("1"); 
    } }

执行结果:

执行前...
PersonService:setPerson
执行后...
PersonService:getPerson>>1

2.2.5 cglib实现

原文地址:https://www.cnblogs.com/chinajava/p/5880887.html

使用cglib[Code Generation Library]实现动态代理,并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码,下面通过一个例子看看使用CGLib如何实现动态代理。

(1)定义业务逻辑

public class UserServiceImpl {  
    public void add() {  
        System.out.println("This is add service");  
    }  
    public void delete(int id) {  
        System.out.println("This is delete service:delete " + id );  
    }  
}

(2)实现MethodInterceptor接口,定义方法拦截器

public class MyMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
        System.out.println("Before:" + method);  
        Object object = proxy.invokeSuper(obj, arg);
        System.out.println("After:" + method); 
        return object;
    }
}

(3)利用Enhancer类生成代理类

Enhancer enhancer = new Enhancer();  
enhancer.setSuperclass(UserServiceImpl.class);  
enhancer.setCallback(new MyMethodInterceptor());  
UserServiceImpl userService = (UserServiceImpl)enhancer.create();

(4)执行结果

Before: add
This is add service
After: add

Enhance类生成代理对象的大概过程如下:

  • 生成代理对象类class的二进制字节码;
  • 通过Class.forName加载二进制字节码,生成Class对象;
  • 通过反射机制获取实例构造,并初始化代理对象。

** cglib 字节码生成**:

Enhancer是CGLib的字节码增强器,可以方便的对类进行扩展,内部调用GeneratorStrategy.generate方法生成代理类的字节码,通过以下方式可以生成class文件。

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\\\Code\\\\whywhy\\\\target\\\\classes\\\\zzzzzz")

生成的代理类代码大致样子如下:

import net.sf.cglib.core.Signature;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;

// 
// Decompiled by Procyon v0.5.30
// 

public class UserService$$EnhancerByCGLIB$$394dddeb extends UserService implements Factory
{
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$add$0$Method;
    private static final MethodProxy CGLIB$add$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;


    static void CGLIB$STATICHOOK2() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        final Class<?> forName = Class.forName("UserService$$EnhancerByCGLIB$$394dddeb");
        final Class<?> forName3;
        CGLIB$add$0$Method = ReflectUtils.findMethods(new String[] { "add", "()V" }, (forName3 = Class.forName("UserService")).getDeclaredMethods())[0];
        CGLIB$add$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "add", "CGLIB$add$0");
    }

    final void CGLIB$add$0() {
        super.add();
    }

    public final void add() {
        MethodInterceptor cglib$CALLBACK_2;
        MethodInterceptor cglib$CALLBACK_0;
        if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
            CGLIB$BIND_CALLBACKS(this);
            cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
        }
        if (cglib$CALLBACK_0 != null) {
            cglib$CALLBACK_2.intercept((Object)this, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Method, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$emptyArgs, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Proxy);
            return;
        }
        super.add();
    }

    static {
        CGLIB$STATICHOOK2();
    }

通过cglib生成的字节码相比jdk实现来说显得更加复杂。

  1. 代理类UserService E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB394dddeb继承了委托类UserSevice,且委托类的final方法不能被代理;
  2. 代理类为每个委托方法都生成两个方法,以add方法为例,一个是重写的add方法,一个是CGLIB$add$0方法,该方法直接调用委托类的add方法;
  3. 当执行代理对象的add方法时,会先判断是否存在实现了MethodInterceptor接口的对象cglib$CALLBACK_0,如果存在,则调用MethodInterceptor对象的intercept方法:
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) {
    System.out.println("Before:" + method);  
    Object object = proxy.invokeSuper(obj, arg);
    System.out.println("After:" + method); 
    return object;
}
参数分别为:1、代理对象;2、委托类方法;3、方法参数;4、代理方法的MethodProxy对象。
  1. 每个被代理的方法都对应一个MethodProxy对象,methodProxy.invokeSuper方法最终调用委托类的add方法,实现如下:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

单看invokeSuper方法的实现,似乎看不出委托类add方法调用,在MethodProxy实现中,通过FastClassInfo维护了委托类和代理类的FastClass。FastClsss主要思想就是在为了避免方法调用时,过度使用反射造成调用慢的问题。给每一个方法一个签名,遇到这个签名时,直接显示调用实现类的实现方法,
可以参考下面的fastClass文件

private static class FastClassInfo {
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;
}

** 关于cglib的疑问**:

  1. MethodProxy是什么?什么时候创建的?
  2. cglib代理类时,生成的代理直接继承目标类,但是代理接口时,怎么处理,如何生成代理对象?

3 总结

https://www.jianshu.com/p/9a61af393e41
三种方式的区别

代理方式实现优点缺点特点
JDK静态代理代理类与委托类实现同一接口,并且在代理类中需要硬编码接口实现简单,容易理解代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率很低好像没啥特点
JDK动态代理代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理不需要硬编码接口,代码复用率高只能够代理实现了接口的委托类底层使用反射机制进行方法的调用
CGLIB动态代理代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口不能对final类以及final方法进行代理底层将方法全部存入一个数组中,通过数组索引直接进行方法调用

最后我们总结一下JDK动态代理和Gglib动态代理的区别:
1.JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
2.JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制,效率更高

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值