快速理解三大代理模式


代理模式

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

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

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

静态代理

定义接口或者父类,然后目标对象与代理对象需要继承相同父类或者实现相同的接口.

  1. 建立接口:IOrderDao.java
public interface IOrderDao {
    void createOrder();
}
  1. 目标对象:OrderDao.java
public class OrderDao implements IOrderDao {
    public void  createOrder() {
        System.out.println("----订单创建成功!-----");
    }
}
  1. 代理对象:OrderDaoProxy.java
public class OrderDaoProxy implements IOrderDao {
    //接受从外部传过来的目标对象
    private IOrderDao target;
    
    public OrderDaoProxy(IOrderDao target) {
        this.target = target;
    }
}

public void createOrder() {
    System.out.println("------------------下单前-------------------------");
    target.createOrder(); //目标方法
    System.out.println("------------------下单后-------------------------");
}
  1. 测试
public class JamesApp {
    public static void main(String[] args) {
        //一定要创建目标对象
        OrderDao target = new OrderDao();

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

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

创建订单的前后都加上了日志打印,这就是代理对象对目标对象进行增强。

静态代理总结:

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

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

JDK动态代理

那么动态代理有哪些优点呢

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

OK,那么如何调用JDK API来生成代理对象呢?
代理类所在包:java.lang.reflect.Proxy
调用JDK的newProxyInstance方法即可对目标对象生成对应的代理对象,语法格式如下:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)

newProxyInstance静态方法,需要传3个参数:

  • ClassLoader loader: 目标对象使用的类加载器,target.getClass().getClassLoader()

  • Class<?> interfaces: 目标对象实现接口类型,使用泛型方式类

  • InvocationHandler h:当执行目标对象的方法时,会先调用InvocationHandler的invoke方法,同时把当前执行目标对象的方法作为参数传入

  1. 新建IOrderDao接口:
package com.enjoy.james;

public interface IOrderDao {

    void createOrder();

    void queryOrder();

    void cancelOrder();
}
  1. 目标对象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("订单取消成功.......");
    }
}

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

/**
 *  动态代理不需要实现接口,但是需要指定接口类型
 */
 public class ProxyFactory {
    
    //维护一个目标对象
    private Object target;
    
    public ProxyFactory(Object object) {
        this.target = object;
    }
    
    //对目标对象包装成代理对象,返回已经扩展后的代理对象
    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("-----------------创建订单前--------------------");
                    //运用反射执行目标对象方法
                    Object returnValue = method.invoke(target, args);
                    System.out.println("-----------------创建订单后--------------------");
                    return returnValue;
                }
            }
        );
    }   
 }

来测试一下

public class TestJDKProxy {
    public static void main(String[] args) {
        //目标对象
        OrderDao target = new OrderDao();
        //给目标target对象,创建代理对象
        IOrderDao proxy = new ProxyFactory(target).genProxyBean();
        
        proxy.createOrder();
    }
}

内容总结:
JDK的方式生成代理对象,如果我们把代理对象生成 P r o x y 0. c l a s s 文 件 , 保 存 至 本 地 磁 盘 , 通 过 对 Proxy0.class文件,保存至本地磁盘,通过对 Proxy0.classProxy0.class类反编译查看,最终是这个样子
public class $Proxy0 extents Proxy implements IOrderDao

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

因为代理类是运行时生成的,我们把它的字节码从内存中获取出来,用下面的方式:

public static void createProxyClassFile() {
	byte[] $Proxy0s = ProxyGenerator.generateProxyClass(
			"$Proxy0", new Class[]{IOrderDao.class});

	try {
		FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
		fileOutputStream.write($Proxy0s);
		fileOutputStream.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

CGLIB动态代理

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

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

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

业务代理省略,OrderDao和IOrderDao与上面完全一样

首先建立cglib代理工厂类

public class ProxyFactoryByCglib implements MethodInterceptor {
    
    public ProxyFactoryByCglib() {
        
    }
    
    @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;
    }
}

开始测试:

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订单取消==="); 
    }
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值