浅谈静态代理与动态代理

图片来源:https://www.cnblogs.com/bigmonkeys/p/7823268.html

首先,理解一下什么是代理,所谓代理,字面意思就是“代替”、“处理”,就是说一定会有一个代理的对象和一个被代理的对象,代理的对象代替被代理对象来处理一些事情,就好像我们现实生活中的代理商,他来代替厂家来销售产品给消费者,另外代理商会提供其他的服务,并收取额外的费用。所以,可以看出,代理一般有三个必不可少的元素:代理对象、被代理对象以及代理对象提供的其他服务。

静态代理:代理类和目标对象的类在程序编译期间就已经确定。他们都实现相同的接口或继承相同的抽象类。

动态代理:代理类和目标对象的类是在程序运行期间由JVM根据反射等机制动态生成的。

下面来说一下未使用代理之前和使用静态代理、使用动态代理之后的代码有什么区别:

1、未使用代理之前:

(1)创建目标对象

public class RealObject {

    private String name;

    public RealObject(String name) {
        this.name = name;
    }

    public void doSomething() {
        //实现主要逻辑之前的做一些处理
        System.out.println("before process...");
        //要实现的主要(核心)逻辑
        System.out.println(name + " doSomething()...");
        //实现主要逻辑之后的做一些处理
        System.out.println("after process...");
    }

}

(3)测试类:

public class TestNonProxy {

    public static void main(String[] args) {
        RealObject realObject = new RealObject("RealObject");
        realObject.doSomething();
    }
}

测试结果:
before process...
RealObject doSomething()...
after process...

存在问题:非核心逻辑与原有核心逻辑紧耦合,不利于程序的扩展。

2、静态代理

方式一:使用聚合方式(推荐)

(懒得画UML图了,用IDEA自动生成的继承关系代替下,下同)

 (1)创建代理类和被代理(目标)类都需要实现的公共接口:

/**
 * 代理类和被代理类都需要实现的公共接口
 */
public interface CommonInterface {

    void doSomething();

}

(2)被代理(目标)类:

/**
 * 被代理(目标)类
 */
public class RealObject implements CommonInterface {

    private String name;

    public RealObject(String name) {
        this.name = name;
    }

    /**
     * 被代理(目标)对象实现的方法
     */
    @Override
    public void doSomething() {
        System.out.println(name + " doSomething()...");
    }

}

(3)代理类:

/**
 * (静态)代理类
 */
public class Proxy implements CommonInterface {

    /**
     * 被代理对象作为属性(聚合)的方式引入
     */
    private RealObject realObject;

    /**
     * 通过构造器方法给属性(被代理对象)赋值
     *
     * @param realObject 被代理对象
     */
    public Proxy(RealObject realObject) {
        this.realObject = realObject;
    }

    /**
     * 代理对象和被代理对象都实现同一接口的同一个方法,
     * 代理对象可以在调用被代理对象前后添加其他处理逻辑
     */
    @Override
    public void doSomething() {
        System.out.println("before process...");
        realObject.doSomething();
        System.out.println("after process...");
    }

}

(4)测试静态代理(聚合方式):

public class TestProxy {

    public static void main(String[] args) {
        RealObject realObject = new RealObject("RealObject");
        CommonInterface proxy = new Proxy(realObject);
        proxy.doSomething();
    }

}

测试结果:
before process...
RealObject doSomething()...
after process...

 方式二:通过继承方式(不推荐)

就是写一个 RealObject 的子类,然后通过多态的方式在调用子类的方法当中来调用父类的方法,这里不再赘述。

静态代理的优缺点:

(1)优点:扩展原功能的同时,不会侵入原有代码的核心逻辑,实现起来比较简单。

(2)缺点:代理类和被代理类都是在编译期间就确定下来,不利于程序的扩展。每一个代理类只能为一个接口服务,这样在开发过程中要实现不同的功能就会产生很多的代理类。

3、动态代理

Java中实现动态代理的方式常见的有两种:JDK 动态代理和 Cglib 动态代理。

方式一:JDK 动态代理

实现步骤:

(1)创建被代理类要实现的接口

/**
 * 被代理类实现接口
 */
public interface CommonInterface {

    void doSomething();

}

(2)创建被代理类

/**
 * 被代理(目标)类
 */
public class RealObject implements CommonInterface {

    private String name;

    public RealObject(String name) {
        this.name = name;
    }

    /**
     * 被代理(真正目标)对象实现的方法
     */
    @Override
    public void doSomething() {
        System.out.println(name + " doSomething()...");
    }

}

 (3)创建一个实现了 InvocationHandler 接口的实现类

/**
 * 创建一个实现了 java.lang.reflect.InvocationHandler 接口的类
 */
public class MyInvocationHandler implements InvocationHandler {

    /**
     * 被代理对象
     */
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 实现 Invocation 的 invoke()方法,
     * 所有调用被代理对象方法都会转换成调用此方法来间接调用目标方法
     *
     * @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 process...");
        Object returnValue = method.invoke(target, args);
        System.out.println("after process...");
        return returnValue;
    }

}

(4)测试JDK动态代理:

/**
 * 测试 JDK 动态代理
 */
public class TestDynamicProxy {

    public static void main(String[] args) {
        //创建被代理对象
        RealObject realObject = new RealObject("RealObject");
        //创建 InvocationHandler 的实现类,并将被代理对象传入
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(realObject);
        //通过 Proxy 的 newInstance() 方法创建动态代理对象
        CommonInterface ci = (CommonInterface) Proxy.newProxyInstance(
                CommonInterface.class.getClassLoader(),
                new Class<?>[]{CommonInterface.class},
                myInvocationHandler);
        ci.doSomething();
    }

}

测试结果:
before process...
RealObject doSomething()...
after process...

核心API:

Proxy:专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。

核心方法:

  • static Class<?> getProxyClass(ClassLoader loader, Class<?>...interfaces) :创建一个动态代理类所对应的 Class 对象。
  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):直接创建一个动态代理对象。

参数说明:

  • loader:类加载器
  • interfaces:被代理类实现的全部接口
  • h:InvocationHandler 接口的实现类实例

方式二:Cglib 动态代理

注:使用 Cglib 动态代理需要导入相关的jar包:cglib-nodep-2.2.2.jar(可以通过 maven 方式下载再导入到工程中)

实现步骤:

(1)创建被代理(目标)类

/**
 * 被代理(目标)类
 * 不需要实现任何接口
 */
public class RealObject {

    private String name;

    public RealObject(String name) {
        this.name = name;
    }

    /**
     * 被代理(真正目标)对象实现的方法
     */
    public void doSomething() {
        System.out.println(name + " doSomeThing()...");
    }

}

 (2)创建一个实现 net.sf.cglib.proxy.MethodInterceptor 拦截器的类

/**
 * 实现 net.sf.cglib.proxy.MethodInterceptor 拦截器
 */
public class CglibProxy implements MethodInterceptor {

    /**
     * 用来创建被代理类的子类(即:代理类)
     */
    private Enhancer enhancer = new Enhancer();

    /**
     * 创建 Cglib 代理对象
     *
     * @param clazz         被代理类的 Class 实例
     * @param argumentTypes 被代理类有参构造器的参数类型数组
     * @param arguments     被代理类有参构造器的参数值数组
     * @return Cglib 代理对象
     */
    public Object getProxy(Class clazz,
                           Class[] argumentTypes,
                           Object[] arguments) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //如果不需要调用有参构造器,则直接使用 enhancer.create() 
        //方法创建对象就行,这里为了通用性,
        //使用create(Class[] argumentTypes, Object[] arguments) 方法
        return enhancer.create(argumentTypes, arguments);
    }

    /**
     * 代理类拦截方法
     *
     * @param obj    Cglib 生成的代理对象
     * @param method 被代理(目标)对象方法的反射对象
     * @param args   被代理(目标)对象方法的参数
     * @param proxy  Cglib代理对象方法
     * @return 返回值
     * @throws Throwable 异常
     */
    @Override
    public Object intercept(Object obj, Method method,
                            Object[] args, MethodProxy proxy)
            throws Throwable {
        //代理类通过 invokeSuper 调用父类的方法
        System.out.println("before process...");
        Object returnValue = proxy.invokeSuper(obj, args);
        System.out.println("after process...");
        return returnValue;
    }

}

(3)创建 Cglib 测试类

/**
 * Cglib 动态代理测试类
 */
public class TestCglibDynamicProxy {

    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        RealObject realObject = (RealObject) cglibProxy.getProxy(
                RealObject.class,
                new Class[]{String.class},
                new Object[]{"RealObject"}
        );
        realObject.doSomething();
    }

}


测试结果:
before process...
RealObject doSomeThing()...
after process...

 对比 JDK 动态代理和 Cglib 动态代理:

JDK 动态代理:只能对实现了接口的类生成代理,没有实现接口的类不能实现JDK的动态代理;

Cglib 动态代理:是针对类实现代理,主要是对指定的目标类生成一个子类,覆盖其中的方法来拦截对目标类的调用(继承)。

 

总结:无论是静态代理还是动态代理(JDK动态代理和Cglib动态代理),其本质都是一样的,就是使用一个代理对象将原始(目标)对象包装起来,然后通过该代理对象来取代原始(目标)对象,之后任何对原始(目标)对象的调用都要通过代理对象,从而控制外部对原始对象的访问和操作(决定是否以及何时调用原始对象的方法)。

后话:本文只是暂时简单的介绍了一下静态代理和动态代理的实现方式及优缺点,后续会接着介绍动态代理和AOP的关系以及动态代理的实现原理,并会自己手动实现一个简单的动态代理类。

 

本文参考地址:

https://www.cnblogs.com/gonjan-blog/p/6685611.html

https://blog.csdn.net/briblue/article/details/73928350

https://www.cnblogs.com/bigmonkeys/p/7823268.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值