设计模式之:三种代理模式详解

什么是代理呢?

生活中处处都有代理:

  1. 明星档期安排,都会有所谓的经纪人
  2. 春运抢不到火车票,会有代售点和黄牛
  3. 专家号不好挂,随处可见的黄牛

这些经纪人、代售点、黄牛就是所谓的代理通俗点就是中介。

代理模式的定义:

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用

为什么要用代理模式,好处是什么?

中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口
开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

代理模式分类:

静态代理

思路:

  1. 创建一个接口
  2. 创建被代理的类(目标类)去实现该接口并且实现该接口中的抽象方法。
  3. 创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法

模拟火车黄牛代理买票的代码实现:
买票接口类:

public interface BuyTicketInterface {
    void buyTicket();
}

乘客买票接口(被代理类):

public class Person implements BuyTicketInterface {
    @Override
    public void buyTicket() {
        System.out.println("我要进行买票");
    }
}

黄牛代理抢票类(代理类):

public class ProxyBuyTicket implements BuyTicketInterface {

    private Person person;
    
    public ProxyBuyTicket (Person person) {
        this.person= person;
    }

    // 中介费每张票十块
    public void addMoney() {
        System.out.println("加十块钱中介费");
    }

    @Override
    public void buyTicket() {
        //可以在买票前自定义代理类的事件
        //比如电话联系用户等
        person.buyTicket();  // 给用户买票       
        addMoney();   // 中介费加钱
    }
}

特点:

  1. 需要自己写代理类,代理类需要实现与目标对象相同的接口
  2. 需要为每一个服务创建代理类,工作量大,接口修改后代理类也需要进行相应修改
  3. 目标对象的接口有很多方法的话,那我们还是得一一实现,这样就会比较麻烦

所以就有了另一种代理方式:动态代理

动态代理
JDK中生成代理对象的API

特点:

  1. 不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了
  2. 要实现动态代理必须要有接口的,代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理,如果没有接口的话我们可以考虑cglib代理(下边会讲到)。源码中的判断:Proxy.java->newProxyInstance()->getProxyClass0->proxyClassCache->WeakCache->supplier.get()->valueFactory.apply;
        // 验证接口
	// 1. 验证类加载器加载的对象接口是否是同一个
	// 2. 验证类对象是否是一个接口
	// 3. 验证此接口是否重复
	for (Class<?> intf : interfaces) {
		/*
		 * Verify that the class loader resolves the name of this
		 * interface to the same Class object.
		 */
		Class<?> interfaceClass = null;
		try {
		     interfaceClass = Class.forName(intf.getName(), false, loader);
		} catch (ClassNotFoundException e) {
		}
		if (interfaceClass != intf) {
			throw new IllegalArgumentException(
				intf + " is not visible from class loader");
		}
		/*
		 * Verify that the Class object actually represents an
		 * interface.
		 */
		if (!interfaceClass.isInterface()) {
			throw new IllegalArgumentException(
				interfaceClass.getName() + " is not an interface");
		}
		/*
		 * Verify that this interface is not a duplicate.
		 */
		if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
			throw new IllegalArgumentException(
				"repeated interface: " + interfaceClass.getName());
		}
	}
  1. JDK中生成代理对象的API

代理类所在包:java.lang.reflect.Proxy

JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

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

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
 ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
 Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
 InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

实例代码
 接口、被代理类不变,我们构建一个handler类来实现InvocationHandler接口,样例都可以直接跑通。

public class ProxyHandler implements InvocationHandler{
    private Object target;
    public ProxyHandler(Object target){  
        this.target= target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke "  + method.getName());
        method.invoke(target, args); //第一个参数时代理对象,参数传错了直接死循环,
        System.out.println("After invoke " + method.getName());
        return null;
    }

  // 生成代理类的方法也可以放到代理类中   
  /**public Object CreatProxyedObj()    {        
      return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);    
  }  **/
}

测试:

 public static void main(String[] args) {
      // 在main方法最前面增加该行代码,这样会输出代理class文件,路径com/sun/proxy/$Proxy0.class
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

        BuyTicketInterface buyTicket = new Person();
        //Proxy.newProxyInstance方法也可以提到代理类中
        BuyTicketInterface proxy = (BuyTicketInterface ) Proxy.newProxyInstance(buyTicket.getClass().getClassLoader(), buyTicket.getClass().getInterfaces(), new ProxyHandler(buyTicket)); 
        proxy.buyTicket();
    }

也可以使用这种方式:

  public static void main(String[] args1) {
       BuyTicketInterface buyTicket = new Person();
       BuyTicketInterface proxy= (BuyTicketInterface) Proxy.newProxyInstance(buyTicket.getClass().getClassLoader(), buyTicket.getClass().getInterfaces(), (proxy, method, args) -> {
                method.invoke(buyTicket, args);
                return null;
        });
        proxy.buyTicket();
    }
CGLIB代理

Cglib代理:上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理

特点:

  1. JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现
  2. Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类
  3. 代理的类不能为final,否则报错
  4. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

实例代码:

/**
 * 目标对象,没有实现任何接口
 */
public class BuyTicket {
    public void buyTicket() {
        System.out.println("----我要买票!----");
    }
}
public class ProxyFactory implements MethodInterceptor{		  
     //代理对象
     private Object target;
     public ProxyFactory(Object target) {
        this.target = target;
     }
     //生成代理对象
     public Object getProxyInstance(){
        //1.自带工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
     }  
     @Override    
     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
	    System.out.println("加钱");
	    method.invoke(target,objects);   //还有其他写的方式,写错的话会出现死循环
	    System.out.println("服务结束");
	    return null;
	}		
}

测试类:

 public static void main(String[] args1) {
       BuyTicket buyTicket = new BuyTicket();
       ProxyFactory factory = new ProxyFactory(buyTicket)//代理对象
       BuyTicket proxy = (BuyTicket)factory.getProxyInstance();
        //执行代理对象的方法
       proxy.buyTicket();
    }

总结:

  1. 静态代理方式需要为每个接口实现一个代理类,而这些代理类中的代码几乎是一致的,难以维护
  2. jdk代理解决了静态代理需要为每个业务接口创建一个代理类的问题,jdk代理的限制也是比较明显的,即其需要被代理的对象必须实现一个接口。这里如果被代理对象没有实现任何接口,或者被代理的业务方法没有相应的接口,我们则可以使用另一种方式来实现,即Cglib代理。
  3. 不仅解决了静态代理需要创建多个代理类的问题,还解决了jdk代理需要被代理对象实现某个接口的问题。这里需要注意的是,根据Cglib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么Cglib代理是无法正常工作的,因为final类型方法不能被重写。

代理模式涉及到的源码还是挺多的,写这篇都用了好久时间,网上一些实例实现方式可能会有出入,整体思想还是一致的。坚持总结的第一周,加油!!
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码厚炮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值