透彻分析三大代理模式

707 阅读8分钟

1.代理模式

Java最经典的设计模式代理模式(Proxy), 在我们开发及框架源码中无处不在,其实说白了, 就是我们使用另外一种方式来访问A对象的方法, 其本质还是访问了A对象的方法, 只是对A对象方法访问的时候做了些扩展, 把A对象封装成代理对象AA, 即通过AA代理对象来访问目标对象. 这样就可以在A对象的基础上,实现其它的额外附加功能;

AA对象也叫做代理对象, 在不动A对象的前提下, 实现了对A对象的扩展, 程序开发过程中, 不能经过动别人的代码,用代理对别人的代码进行扩展.

A目标对象, AA为A的代理对象proxy, 如下图:

在这里插入图片描述

注意: 代理对象AA与目标对象A.其实就是AA对A对象的调用前后加了扩展,加了图上的第2 第4步

1.1.快速掌握静态代理

我们先要搞清什么是静态代理?

看看代码操作吧, 首先我们需要定义接口或父类,然后目标对象与代理对象需要继承相同父类或实现相同的接口.

废话不多说, 翠花, 上代码!!!

先建接口:IOrderDao.java

/**
 * 接口
 */
public interface IOrderDao {

    void createOrder();
}
1234567

目标对象:OrderDao.java

/**
 * 接口实现
 * 目标对象
 */
public class OrderDao implements IOrderDao {
    public void createOrder() {
        System.out.println("----创建订单成功!----");
    }
}
123456789

对OrderDao进行代理, 即对其进行包装(也要代理对象)

新建 OrderDaoProxy.java

/**
 * 代理对象,静态代理,与目标对象实现相同的接口或继承相同的父类
 */
public class OrderDaoProxy implements IOrderDao{
    //接收从外部传进来的目标对象
    private IOrderDao target;
    public OrderDaoProxy(IOrderDao target){
        this.target=target;
    }

    public void createOrder() {
        System.out.println("James下订单前,记录日志...");
        target.createOrder();//真正的执行从外部传进来的目标方法
        System.out.println("James下订单后,记录日志...");
    }
}
12345678910111213141516

我们测试一下,新建类:JamesApp.java

/**
 * test
 */
public class JamesApp {
    public static void main(String[] args) {
        //一定要创建目标对象
        OrderDao target = new OrderDao();

        //看到没?我执行的是proxy的createOrder,加了些日志扩展,但真正还是调了目标对象的createOrder
        OrderDaoProxy proxy = new OrderDaoProxy(target);

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

执行结果如下

James下订单前,记录日志...
----创建订单成功!----
James下订单后,记录日志...
123

看到没, 订单创建成功, 但在前后都被记录了日志,这就是代理对象对目标对象进行增强了.

静态代理总结下:
1.优点: 不修改目标对象的任何一行代码, 完成了对目标对象的扩展.
2.缺点: 代理对象会随着目标对象的修改, 也要做相应的修改,比如目标对象加了个订单查询 orderQuery方法,OrderDaoProxy代理类也要做代码开发, 才能完成这个方法的扩展, 很明显, 不方便 .

那么如何解决这个问题呢? 很明显啦 ,动态代理嘛

1.2.JDK动态代理

那么动态代理有哪些优点呢?
1.首先代理对象肯定是不需要实现接口或继承同一父类, 不然又回到了静态代理模式
2.代理对象它内部调用JDK的接口,可以动态在我们内存中生成代理对象, 这个代理对象和上面讲代理对象的作用是一样的, 对目标对象进行增强, 进行扩展, 只是实现方式更灵活更方便

OK, 那么如何调用JDK API来生成代理对象呢?
代理类所在包:java.lang.reflect.Proxy
很简单, 调用JDK的newProxyInstance方法即可对目标对象生成对应的代理对象, 语法格式如下:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
1

newProxyInstance静态方法,需要专稿三个参数:

  • ClassLoader loader,:目标对象使用的类加载器, target.getClass().getClassLoader()
  • Class<?>[] interfaces,:目标对象实现接口类型,使用泛型方式类型,target.getClass().getInterfaces()
  • InvocationHandler h: 当执行目标对象的方法时,会先调用InvocationHandler的invoke方法, 同时把当前执行目标对象的方法作为参数传入

废话不多说, 翠花, 上代码, 一看就懂:

新建IOrderDao接口:

package com.enjoy.james;

public interface IOrderDao {

    void createOrder();

    void queryOrder();

    void cancelOrder();
}
12345678910

目标对象OrderDao实现类:

package com.enjoy.james;

public class OrderDao implements IOrderDao {

    public void createOrder() {
        System.out.println("订单创建成功.......");
    }

    public void queryOrder() {
        System.out.println("订单查询成功.......");
    }

    public void cancelOrder() {
        System.out.println("订单取消成功.......");
    }
}
12345678910111213141516

一定要有代理工厂类:ProxyFactory.java

后面使用的时候, 直接向工厂申请,通过ProxyFactory类传入目标对象, 生成代理对象

package com.enjoy.james;

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

/**
 * 创建动态代理对象的步骤
 * 注:动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory {

    //维护一个目标对象
    private Object target;

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

    //对目标对象包装成代理对象
    public Object genProxyBean() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("James订单操作前,记录日志...");
                        //运用反射执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("James订单操作前,记录日志...");
                        return returnValue;  //返回已经扩展后的代理对象
                    }
                }
        );
    }

}
1234567891011121314151617181920212223242526272829303132333435363738

来,测试一下:

package com.enjoy.james;


public class TestJDKProxy {

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

        // 给目标target对象,创建代理对象(这个对象对目标对象进行了增强)
        IUserDao proxy = (IUserDao) new ProxyFactory(target).genProxyBean();

        proxy.createOrder();

        System.out.println("===1创建订单完成===");

        proxy.queryOrder();

        System.out.println("===2查询订单完成===");

        proxy.cancelOrder();
        System.out.println("===3取消订单完成===");
    }
}
123456789101112131415161718192021222324

运行结果:

James订单操作前,记录日志...
订单创建成功.......
James订单操作前,记录日志...
===1创建订单完成===
James订单操作前,记录日志...
订单查询成功.......
James订单操作前,记录日志...
===2查询订单完成===
James订单操作前,记录日志...
订单取消成功.......
James订单操作前,记录日志...
===3取消订单完成===
123456789101112

内容总结:
JDK的方式生成代理对象, 如果我们把代理对象生成p r o x y 0. c l a s s 文 件 ( 怎 么 生 成 , 请 百 度 ) , 保 存 到 本 地 磁 盘 , 通 过 对 proxy0.class文件(怎么生成, 请百度),保存到本地磁盘, 通过对proxy0.class文件(怎么生成,请百度),保存到本地磁盘,通过对proxy0.class类反编译查看, 最终是这个样子

public class $proxy0 extends Proxy implements IOrderDao
1

$proxy0代理对象已经继承了jdk的Proxy, 所以JDK动态代理只能以实现IOrderDao接口的方式完成

1.3.Cglib动态代理

我们不难发现, 刚以上写的静态代理和动态代理模式它们都要求: 目标对象是一定要实现一个接口,但在我们写业务代码有时候只是一个对象, 没有实现接口,那我们可以Cglib代理方式来完成代理对象

首先我们的pom.xml要引入cglib:

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.12</version>
        </dependency> 
12345

业务代码略:OrderDao和IOrderDao与上面完全一样。

首先建立Cglib代理工厂类

package com.enjoy.james;

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

import java.lang.reflect.Method;


public class ProxyFactoryByCglib implements MethodInterceptor {

    public ProxyFactoryByCglib() {
    }

    /**
     * 1、代理对象;
     * 2、委托类方法;
     * 3、方法参数;
     * 4、代理方法的MethodProxy对象。
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(method.getName()+"方法开始调用");
        Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println(method.getName()+"方法结束调用");
        return obj;
    }
}
123456789101112131415161718192021222324252627

开始测试:

package com.enjoy.james;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;


public class TestCglib {

    public static void main(String[] args) {
        //将动态代理类保存到磁盘D:/cglib下,方便我们反编译查看它的结构
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/cglib");
		
        ProxyFactoryByCglib cglibProxy = new ProxyFactoryByCglib();
        
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(OrderDao.class);
        //设置回调对象
        enhancer.setCallback(cglibProxy);
		
        OrderyDao proxy = (OrderyDao) enhancer.create();
        
        proxy.createOrder();
        System.out.println("===1订单创建===");
        
        proxy.queryOrder();
        System.out.println("===2订单查询===");
        
        proxy.cancelOrder();
        System.out.println("===3订单取消===");
    }
}
1234567891011121314151617181920212223242526272829303132

**测试结果: **

createOrder()方法开始调用

订单创建成功…

createOrder()方法开始调用

=

1订单创建

=

queryOrder()方法开始调用

订单查询成功…

queryOrder()方法开始调用

=

2订单查询

=

cancelOrder()方法开始调用

订单取消成功…

cancelOrder()方法开始调用

=

3订单取消

=

使用总结:
若目标对象有实现接口,我们可以用JDK代理; 若目标对象没有实现接口,则用Cglib代理;若目标对象实现了接口,并且强制用cglib, 最终还是会使用cglib代理。