设计模式之代理模式

本文深入介绍了代理模式的概念、作用和优缺点,通过实例展示了静态代理和动态代理的实现。静态代理通过预处理和后续处理扩展了目标对象的功能,而动态代理则利用JDK的反射机制在运行时创建代理对象,适用于未实现接口的目标对象。此外,文章提到了CGLIB代理作为动态代理的一种补充,用于处理未实现接口的类。代理模式在软件开发中常用于控制访问、功能扩展和降低耦合度。
摘要由CSDN通过智能技术生成

在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。

代理模式的定义与特点

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的主要优点有:

  1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  2. 代理对象可以扩展目标对象的功能;
  3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性。

其主要缺点是(可以使用动态代理方式解决这些缺点):

  1. 代理模式会造成系统设计中类的数量增加;
  2. 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  3. 增加了系统的复杂度。

代理模式的结构与实现

代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。

模式的结构

代理模式的主要角色如下。

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

在这里插入图片描述
在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。

根据代理的创建时期,代理模式分为静态代理和动态代理。

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态:在程序运行时,运用反射机制动态创建而成
模式的实现

1.静态代理

// 抽象主题(Subject)
public interface Subject {
    void request();
}

// 真实主题(Real Subject)
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("访问真实主题方法..");
    }
}

// 代理(Proxy)
public class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("访问真实主题之前的预处理。");
        realSubject.request();
        System.out.println("访问真实主题之后的后续处理。");
    }
}

// 测试类
public class Main {

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Proxy proxy = new Proxy(realSubject);
        proxy.request();
    }
}

// 测试结果
访问真实主题之前的预处理。
访问真实主题方法..
访问真实主题之后的后续处理。

2.动态代理

 使用jdk的反射机制,创建对象的能力,创建的是代理类的对象。而不用创建类文件。不用写java文件。
 动态:在程序执行时,调用jdk提供的方法才能创建代理类的对象。

jdk动态代理的实现:
反射包: java.lang.reflect,里面有三个类:InvcationHandler,Method,Proxy
1.InvcationHandler接口(调用处理器):就一个invoke()
   invoke():表示代理对象要调用执行的功能代码。你的代理类要完成的功能就写在invoke()方法中。
   代理类完成的功能:
     1.调用目标方法,执行目标方法的功能
     2.功能增强,在目标方法调用时,增强功能
  方法原型:
    参数:Object proxy: jdk创建的代理对象,无需赋值
          Method method:目标类中的方法,jdk提供method对象
          Object[] args: 目标类中方法的参数,jdk提供的
          
    public Object invoke(Object proxy,Metjod,Object[] args)        
     
    InvcationHandler接口:表示你的代理要干什么
    1.创建类实现接口InvcationHandler
    2.重写invoke()方法,把原来静态代理中代理类要完成的功能,写在这里
    
    Method类:表示方法的,确切的说就是目标类中的方法
    作用:通过Method可以执行某个目标类的方法,Method.invoke();
          Method.invoke(目标对象,方法的参数); 
        
    Proxy类:核心对象,创建代理对象;使用Proxy类型代替之前的new
    1.静态方法 newProxyInstance()
    2.作用:创建代理对象,等同于new操作
    3.参数:public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)    
      ClassLoader loader:类加载器,负责向内存中加载对象(目标对象的类加载器)
      Class<?>[] interfaces:,目标对象实现的接口,也是反射获取   
      InvocationHandler h:需要自己写的,主要描述代理对象主要完成的功能                                    

// 抽象主题(Subject)
public interface Subject {
    void request();
}

// 真实主题(Real Subject)
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("访问真实主题方法..");
    }
}
// 代理对象需要完成的功能
public class ProxyHandler implements InvocationHandler {

    private Subject tar;

    public ProxyHandler(Subject tar) {
        this.tar = tar;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke "  + method.getName());
        tar.request();
        System.out.println("After invoke " + method.getName());
        return null;
    }
}
// 测试类
public class Main {

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxyHandler realSubjectProxy = new ProxyHandler(realSubject);
        // 创建代理对象
        Subject o = (Subject) java.lang.reflect.Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), realSubjectProxy);
        o.request();
    }
}

// 测试结果
Before invoke request
访问真实主题方法..
After invoke request
工作中的实践

比如你所在的项目中,有一个功能是其他人(公司的其他部门,其他小组的人)写好的,你可以使用。

 GoNong.java ,
 GoNong gn = new GoNong();
 gn.print();

你发现这个功能,现在还缺点,不能满足自己的项目的需要,需要在gn.print() 执行后,需要自己在增加代码。
用代理实现gn.print()调用时,增加自己的代码,而不用去修改原来的GoNong文件

扩展

CGLIB代理
1.原理
代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。JDK的动态代理用起来非常简单,当它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用CGLIB包
2.什么是cglib
CGLIB是一个强大的高性能的代码生成包。

   1>它广泛的被许多AOP的框架使用,例如:Spring AOP和dynaop,为他们提供方法的interception(拦截);

   2>hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取,是采用其他机制实现的);

   3>EasyMock和jMock是通过使用模仿(moke)对象来测试java代码的包。

   它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。

3.底层
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。除了CGLIB包,脚本语言例如 Groovy和BeanShell,也是使用ASM来生成java的字节码。当不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。所以cglib包要依赖于asm包,需要一起导入。下图为cglib与一些框架和语言的关系(CGLIB Library and ASM Bytecode Framework)
在这里插入图片描述

Spring AOP和Hibernate同时使用JDK的动态代理和CGLIB包。Spring AOP,如果不强制使用CGLIB包,默认情况是使用JDK的动态代理来代理接口。

4.实例场景模拟

// 目标对象实现接口
public interface Subject {
    void request();
}

// 目标对象
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("访问真实主题方法..");
    }
}

// 代理对象
//public class CGLibProxy extends RealSubject {

//    private RealSubject realSubject;

//    public CGLibProxy(RealSubject realSubject) {
//        this.realSubject = realSubject;
//    }

    @Override
//    public void request() {
//        System.out.println("Before invoke ");
//        realSubject.request();
//        System.out.println("After invoke ");
//    }
//}

public class CGLibProxy implements MethodInterceptor{
	public Object getProxyOfTarget(Class cls){
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(cls);
		enhancer.setCallback(this);
		return enhancer.create();
	}
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		 System.out.println("Before invoke");
		 methodProxy.invokeSuper(proxy, args);
		 System.out.println("After invoke");
		return null;
	}
}

// 测试类
public class Main {

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        RealSubject CgLibProxy = (RealSubject)(new CglibProxy().getProxyOfTarget(RealSubject.class));
		CgLibProxy.request();
    }
}

// 测试结果 
Before invoke 
访问真实主题方法..
After invoke 

5.缺点:

  • CGLib通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理final的情况(final修饰的方法不能被复写。)
  • cglib动态代理需要导入相应jar包,它是基于字节码的,是生成了代理真实类的子类,无论代理真实类有没有实现接口都可以使用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值