JAVA设计模式之代理模式

一、定义

为其他对象提供一种代理以控制对这个对象的访问。

二、概述

当用户与某个对象打交道时,但程序可能不希望用户直接访问对象,而是提供一个特别的对象,这个特别的对象就被称为代理对象。

在代理模式中,代理的特点是:

(1)代理对象与目标对象实现了相同的接口。通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;
(2)通过代理对象对原有的业务增强;

比如秘书是老板的代理,老板和秘书都有接电话的方法,但用户必须首先和秘书通电话,当秘书确认老板可以接电话时,老板才会去接听用户的电话。

代理模式包含三个角色:
在这里插入图片描述

1、抽象角色(Subject):代理角色和真实角色对外提供的公共方法,一般为一个接口。
2、真实角色(RealSubject):需要实现抽象角色接口。定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此。
3、代理角色(Proxy):需要实现抽象角色接口,同时又含有真是角色的变量,用来控制真是角色的访问。并可以附加自己的操作。

代理的分类:静态代理、动态代理(jdk动态代理、cglib动态代理)。

三、为什么要用代理模式?

1、中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,

其特征是代理类和委托类实现相同的接口。

2、开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,还可以通过给代理类增加额外的功能来扩展委托类的功能,

这样做只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如想给项目加入缓存、日志这些功能, 就可以使用代理类来完成,而没必要打开已经封装好的委托类。

四、举例:

1、静态代理(需要接口)

<1> 创建服务接口,即抽象角色

public interface BuyHouse {
    void buyHosue();
}

<2> 实现服务接口,即真是角色

public class BuyHouseImpl implements BuyHouse {
    @Override
    public void buyHosue() {
        System.out.println("我要买房");
    }
}

<3> 创建代理类

public class BuyHouseProxy implements BuyHouse {
      private BuyHouse buyHouse;
      public BuyHouseProxy(final BuyHouse buyHouse) {
          this.buyHouse = buyHouse;
      }

      @Override
      public void buyHosue() {
          System.out.println("买房前准备");
          buyHouse.buyHosue();
          System.out.println("买房后装修");
      }
}

<4> 测试

public class ProxyTest {
    public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
        buyHouseProxy.buyHosue();
    }
}

静态代理总结:

优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

2、动态代理之jdk代理(需要接口)

是指在使用时再创建代理类和实例

<1> 创建服务类接口,即抽象角色

	    public interface BuyHouse {
            void buyHosue();
        }

<2> 实现服务接口,即真实对象

	    public class BuyHouseImpl implements BuyHouse {

            @Override
            public void buyHosue() {
                System.out.println("我要买房");
            }
        }

<3> 创建一个代理管理类

public class ProxyCompany  implements InvocationHandler {

    private Object factory;

    public void setFactory(Object factory) {
        this.factory = factory;
    }

    public Object getProxyInstance(){

        return Proxy.newProxyInstance(factory.getClass().getClassLoader(),factory.getClass().getInterfaces(),this);

    }

    private void saleBefore(){
        System.out.println("购买之前的准备");
    }

    private void saleAfter(){
        System.out.println("买房后装修");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        saleBefore();
        Object result = method.invoke(factory,args);
        saleAfter();

        return result;
    }

}

Proxy.newProxyInstance()方法接受三个参数:

ClassLoader loader : 指定当前目标对象使用的类加载器,获取加载器的方法是固定的
Class<?>[] interfaces : 指定目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler : 指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

<4> 编写测试类

public class DynamicProxyTest {
     public static void main(String[] args) {
          ProxyCompany proxyCompany = new ProxyCompany();
          BuyHouse buyHouse = new BuyHouseImpl();
          proxyCompany.setFactory(buyHouse);
          BuyHouse proxyBuyHouse = (BuyHouse) proxyCompany.getProxyInstance();  
          proxyBuyHouse.buyHosue();
     }
}

优点:只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码
更强的灵活性
缺点:效率低,相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法

实现原理

通过调试模式发现,动态代理里代理类的类名是这样的$Proxy0:
在这里插入图片描述

在java文件编译后的目录里其实找不到这个名为$Proxy0的class文件的。那么它是如何执行被代理对象的相关方法呢?进入Proxy.newProxyInstance方法,找到与创建对象有关的代码主要有:

获得代理类的class对象:
在这里插入图片描述
获得代理类的构造器:
在这里插入图片描述
创建代理类的实例并返回
在这里插入图片描述
看来关键点就是如何获得代理类的class对象,进入getProxyClass0方法。
在这里插入图片描述
通过proxyClassCache.get方法,可以推测,JDK内部使用了某种机制缓存了代理类的class对象,同时get方法接受的参数是被代理类的类加载器和类实现的的接口。进入get方法,除去和缓存相关的操作,同时用到了被代理类的类加载器和类实现的的接口这两个参数的是:
在这里插入图片描述
subKeyFactory是一个接口,看一下它的实现类:
在这里插入图片描述
其中就有一个ProxyClassFactory,看名字就应该知道这是与代理相关的,进入它的apply方法,前面的基本都是一些检查操作,主要看一下方法的最后:
在这里插入图片描述
可以看到proxyName就是生成的类的类名,其中proxyClassNamePrefix可以点击看下:
在这里插入图片描述
看到这里就应该知道$Proxy0的类名是如何来的了。最终生成代理类的class对象是defineClass0方法,这个方法是个native方法,无法深究它,但是通过这个方法的参数可以明显看到它接收了上面所生成的byte数组。虽然无法探究,但自己写个工具类,将这个byte数组写入文件,看一下内部的具体实现:

public class ProxyUtil {

    //ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    public static void generateClassFile(Class clazz,String proxyName){

        byte[] bytes= ProxyGenerator.generateProxyClass(proxyName, new Class[]{clazz});

        String paths = clazz.getResource(".").getPath();

        System.out.println("输出路径===" + paths);

        FileOutputStream out = null;

        try {
            out = new FileOutputStream(new File(paths + proxyName + ".class"));
            out.write(bytes);
            out.flush();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                out.close();
            }catch (Exception e1){
                e1.printStackTrace();
            }
        }
    }

}

//调用
public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        proxyCompany.setFactory(buyHouse);
        BuyHouse proxyBuyHouse = (BuyHouse) proxyCompany.getProxyInstance();
        proxyBuyHouse.buyHosue();
        ProxyUtil.generateClassFile(buyHouse.getClass(),proxyBuyHouse.getClass().getSimpleName());

    }

在输出路径中找到生成的$Proxy0.class文件。
在这里插入图片描述
在类中找到实现了业务接口的方法:

在这里插入图片描述
在这里插入图片描述
而h则来自父类Proxy中

在这里插入图片描述
这个h的实例就是在创建代理类的实例时传入的
在这里插入图片描述

3、动态代理之cglib代理(不需要接口)

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLIB代理不需要目标对象有实现接口,它通过构建目标对象的子类对目标功能进行扩展。

<1> 创建实际主题

public class BuyHouseImpl{
    @Override
    public void buyHosue() {
        System.out.println("我要买房");
    }
}	

<2> 创建CGLIB代理类

public class CglibProxy implements MethodInterceptor {
     private Object target;

     public CglibProxy(Object target) {
         this.target = target;
     }
     //给目标对象创建代理对象
     public Object getInstance() {
          /**
            * 工具类,允许为非接口类型创建一个Java代理。Enhancer动态创建了给定类型的子类但是拦截了所有的方法。
             * 和Proxy不一样的是,不管是接口还是类他都能正常工作
           */
           Enhancer enhancer = new Enhancer();
		   //设置父类
           enhancer.setSuperclass(this.target.getClass());
		    //设置回掉函数(因为MethodInterceptor继承了Callback类,默认执行intercept方法)
           enhancer.setCallback(this);
			//创建子类
            return enhancer.create();
      }

      public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
          System.out.println("买房前准备");
          Object result = methodProxy.invokeSuper(object, args);
          System.out.println("买房后装修");
           return result;
      }
}	

<3> 创建测试类

public class CglibProxyTest {
    public static void main(String[] args){
        BuyHouseImpl buyHouse = new BuyHouseImpl();
        CglibProxy cglibProxy = new CglibProxy(buyHouse);
        BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance();
        buyHouseCglibProxy.buyHosue();
     }
}	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值