重学设计模式(三、设计模式-代理模式)

94 阅读7分钟

1、代理模式

​ 其实在我们的生活中,我们买房子、租房子要自己去找相应的资源非常耗时间,一般都会通过中介完成,这就是代理模式。同样,在软件设计中,使用代理模式的例子也很多,我们在讲迪米特法则的时候,其实隐隐约约的接触了我们的代理模式思想。

1.1、什么是代理模式

  • 定义:代理模式是一种结构型设计模式,可以让你为另一个对象提供替代或占位符。代理控制对原始对象的访问,允许您在请求到达原始对象之前或之后执行某些操作。

​ 为什么要控制对对象的访问?

​ 一方面,为了节约资源。站在迪米特法则的角度看,如果对于一个非常耗资源的大型对象,如果所有的客户端不管是否使用都去初始化这个对象其实是非常耗资源的。

​ 另一方面,为了系统安全。假设我们有一个可以在系统上运行的命令类,我们正常使用它没有问题,但如果我们想将此程序提供给客户端应用程序,它可能会出现严重问题,因为客户端程序可以发出命令来删除一些系统文件或更改一些您不想要的设置。

​ 代理模式主要有静态代理动态代理 (包括:Cglib 代理,可以在内存 动态的创建对象,而不需要实现接口 )

​ 代理的核心角色有:接口(Subject)、委托类(RealSubject)、代理类(ProxySubject)。

代理模式

代理模式

1.2、代理模式的优缺点

  • 优点

​ 1)可以在客户端不知道的情况下控制服务对象,在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

​ 2)代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性。

  • 缺点

​ 1)代理模式会造成系统设计中类的数量增加,增加了系统的复杂度;

​ 2)在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢。

1.3、创建方式

1.3.1、静态代理

​ 静态代理有两种实现方式:1)通过继承实现;2)通过组合实现。这里我们用组合方式来写:

1)ChinaMobileCompany-接口

/**
 * 中国移动公司
 * 1)提供SIM卡办理
 * 2)话费充值
 * 3)手机号码注销
 */
public interface ChinaMobileCompany {

 public boolean handleSIMCard();
 
 public boolean rechargeCallFee();
 
 public boolean numberLogout();
}

2)MobileBusinessHall-委托类

/**
 * 移动营业厅
 */
public class MobileBusinessHall implements ChinaMobileCompany{

 private String name;
 
 public MobileBusinessHall(String name){
  this.name = name;
 }
 
 @Override
 public boolean handleSIMCard() {
        System.out.println(name+"办理SIM卡");
  return true;  
 }

 @Override
 public boolean numberLogout() {
  System.out.println(name+"注销手机号");
  return true;
 }

 @Override
 public boolean rechargeCallFee() {
        System.out.println(name+"充值话费");
        return true;
 }

}

3)MobileProxy-代理类

/**
 * 移动代理商
 * 1)支持代理办理SIM卡业务
 * 2)支持代理手机充值业务
 */
public class MobileProxy implements ChinaMobileCompany{

 private MobileBusinessHall mbh;
 
 public MobileProxy(String name){
  if(mbh==null){
   mbh = new MobileBusinessHall(name);
  }
 }
 @Override
 public boolean handleSIMCard() {
  return mbh.handleSIMCard();
 }

 @Override
 public boolean numberLogout() {
  System.out.println("请到移动营业大厅办理:注销手机号业务");
  return false;  
 }

 @Override
 public boolean rechargeCallFee() {
  return mbh.rechargeCallFee();
 }

}

4)消费者

public class Client {

 public static void main(String[] args) {
  //顾客去代理店办理业务
  MobileProxy mbp = new MobileProxy("中山路代理店");
  mbp.rechargeCallFee();
  mbp.handleSIMCard();
  mbp.numberLogout();
 }
 
}
  • 案例效果

image.png

1.3.2、动态代理

​ 在静态代理中,MobileProxy-代理类和MobileBusinessHall-委托类,都实现了ChinaMobileCompany接口,代理类只是对委托类的功能做了进一步的处理或者限制,我们知道移动代理商不止一家,附加的杂食小店、手机专卖店等等都可能有代理,如果每个代理都要这么声明,那会产生很多的代理类。

​ 同时,如果ChinaMobileCompany中新增了功能,比如代理套餐办理业务,那么所有的代理都得跟着改。

​ 而动态代理,就是在运行时生成"虚拟"的代理类,被ClassLoader加载,从而避免了静态代理那样需要声明大量的代理类,也避免了增加新功能而需要大量修改代理类的情况。

​ JDK从1.3版本就开始支持动态代理类的创建。其核心就是反射,主要涉及的类有2个:java.lang.reflect.Proxy、和java.lang.reflect.InvocationHandler

1)JDKMobileProxy-JDK动态代理

/**
 * jdk动态代理,使用反射方法,在运行时帮你生成一个代理对象
 * 相当于MobileProxy
 * jdk动态代理bean.getClass().getInterfaces()只能代理接口
 */
public class JDKMobileProxy{
 
 private Object bean;
 
 public JDKMobileProxy(Object obj){
  this.bean = obj;
 }
 
 public Object getProxy(){
        //newProxyInstance的三个参数:
        //第一个参数:ClassLoader loader,定义了由哪个ClassLoader来对生成的代理对象进行加载
        //第二个参数:Class[] interfaces,需要代理的类的接口,就像我们自己写静态代理类一样
        //第三个参数:InvocationHandler h,它也是一个接口,动态代理对象在调用方法的时候,决定关联到哪一个InvocationHandler对象上的invoke方法
        //byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces),生成字节码,然后通过这些字节码实现代理类的生成。
  return Proxy.newProxyInstance(this.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if("numberLogout".equals(method.getName())){
     throw new UndeclaredThrowableException(null"不能代理注销");
    }
    Object invoke = null;
    try {
     invoke = method.invoke(bean, args);
    } catch (InvocationTargetException e) {
     throw e.getCause();
    }
    return invoke;
   }
  });
 }
}

2)Client-调用者

public class Client {

 public static void main(String[] args) {
  //顾客去代理店办理业务
  ChinaMobileCompany cm = new MobileBusinessHall("中山路代理店");
  JDKMobileProxy jdkProxy = new JDKMobileProxy(cm);
        //获取代理对象
  ChinaMobileCompany yddl = (ChinaMobileCompany) jdkProxy.getProxy();
  yddl.handleSIMCard();
  
  ChinaMobileCompany cm1 = new MobileBusinessHall("阳明路代理店");
  JDKMobileProxy jdkProxy1 = new JDKMobileProxy(cm1);
  ChinaMobileCompany yddl1 = (ChinaMobileCompany) jdkProxy1.getProxy();
  yddl1.handleSIMCard();
  yddl1.rechargeCallFee();
  yddl1.numberLogout();
  
 }
 
}
  • 案例效果

Img

Img

1.3.3、CGLIB代理

​ CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,使用字节码处理框架ASM,来转换字节码并生成要代理类的子类,并重写代理类的所有不是final的方法。

​ java有了动态代理,为什么还要使用 CGLIB?

​ JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。

​ 注:使用CGLIB需要cglib.jar包和asm.jar包

​ cglib.jar下载地址:mvnrepository.com/artifact/cg…

​ 注:如果使用cglib-3.3.0.jar jdk应该需要1.8及以上;

​ asm.jar下载地址:mvnrepository.com/artifact/or…

1)方式一,代理——代理类的对象

public class Client {

 public static void main(String[] args) {
  //顾客去代理店办理业务 
  //方式一Cglib 代理对象
  Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MobileBusinessHall.class);
        enhancer.setCallback(new CglibProxyObject(new MobileBusinessHall("中山路代理店")));
        MobileBusinessHall yddl1= (MobileBusinessHall) enhancer.create();
        yddl1.handleSIMCard();
  yddl1.rechargeCallFee();
  yddl1.numberLogout();
 }
}

class CglibProxyObject implements MethodInterceptor{
 private Object obj;
    public CglibProxyObject(Object obj) {
        this.obj = obj;
    }
 @Override
 public Object intercept(Object target, Method method, Object[] params,
   MethodProxy proxy) throws Throwable {
  System.out.println("cglib代理");
  if("numberLogout".equals(method.getName())){
   throw new Exception("不能代理注销");
  }
  //代理的类的实例,如果是对象则不能使用invokeSuper
  return proxy.invoke(obj, params);
 }
}

2)方式二,还可以这样写

public class Client1 {

 public static void main(String[] args) {
  Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MobileBusinessHall.class);
  enhancer.setCallback(new CglibProxy());
  //无参MobileBusinessHall yddl2= (MobileBusinessHall) enhancer.create()
  MobileBusinessHall yddl2= (MobileBusinessHall) enhancer.create(new Class[]{String.class},new Object[]{"中山路代理店"});
  yddl2.handleSIMCard();
  yddl2.rechargeCallFee();
  yddl2.numberLogout();
 }
}
class CglibProxy implements MethodInterceptor{
 
 @Override
 public Object intercept(Object target, Method method, Object[] params,
   MethodProxy proxy) throws Throwable {
  System.out.println("cglib代理");
  if("numberLogout".equals(method.getName())){
   throw new Exception("不能代理注销");
  }
  //用错会死循环return proxy.invoke(target, params);
  //target代理的类
  return proxy.invokeSuper(target, params);
        //主要就是invoke和invokeSuper的区别
 }
}
  • 案例效果都一样

image-20220601022434843

image-20220601022434843

1.4、总结及建议

​ 当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

应用场景:

1)安全代理:屏蔽对真实角色的直接访问;

2)远程代理:通过代理类处理远程方法调用(RMI);

3)延迟加载:先加载轻量级的代理对象,真正需要时再加载真实对象。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。

4)记录请求:想要保留对服务对象的请求历史记录的时候可以使用。

JDK中代理模式的应用:

1)java.lang.reflect.Proxy

2)javax.persistence.PersistenceContext