java设计模式(8)--代理模式

本文介绍了代理模式的概念和在Java中的应用,包括静态代理和动态代理。静态代理通过编写代理类实现,优点是执行速度快,缺点是不够灵活且代码冗余。动态代理分为JDK动态代理和CGLIB动态代理,动态代理在运行时生成,更加灵活,但速度相对较慢。JDK动态代理要求目标对象实现接口,而CGLIB则通过继承目标类实现。代理模式常用于增强或控制目标对象的访问,例如在Spring AOP中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近学习spring-aop内容时,提到了代理模式,粗略了解了一下,发现代理模式实现的功能挺强大的,在很多地方都有用到,所以在此整理一下自己所了解的一些关于代理模式的一些知识。

一、概念(理解概念很重要,个人比较喜欢带着概念去理解代码。)
1.代理:生活中代理这个词很常见,没错我说的这个代理就是你现在头脑正在想的那个代理。
在生活中当我们客户不能直接或不想直接接触目标服务时,或者目标服务所提供的服务对客户有所不足时,我们就引入了代理。
example:火车站太远了,天太冷不想去,于是我们选择去了火车票代售点。这里的代售点也就成了代理,(但这里要注意的是这里虽然是票是代售点给的,但实际票还是来自火车站发放的。1代售点很近我们很快就能买到票,2但是却要收手续费5元,这两个情况也是代售点给我们带来的额外服务(虽然第二个我很不想要))
2.代理模式:代理模式给某一个对象提供一个代理对象,用来控制真实对象访问时的权限以及实现客户端与目标对象的沟通,还有添加额外目标对象所没有的的额外服务(如一些日志事物处理等)。
3.代理模式结构(四个角色)
● 抽象主题角色:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。

  ● 真实主题角色:定义了代理对象所代表的目标对象,真正实现业务逻辑的类

  ● 代理主题角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。它可以增加一些真实主题里面没有的功能。
  ● Main:客户端,使用代理类和主题接口完成一些工作。
代理模式可分为两大类:静态代理和动态代理
静态代理:对于指定类编写该类的代理类,在程序编译器该类.class文件就已经存在
类图结构如下:这里写图片描述

实例代码:

  • 抽象主题角色

package com.yc.Interface;
//订票,改签, 退票--》抽象主题角色
public interface RailwayStation {
        public void ByTicket();
        public void changeTicket();
        public void exitTicket();
}
  • 真实主题角色
package com.yc.Impl;

import com.yc.Interface.RailwayStation;
//实现类,具体的方法实现--》真实主题角色
public class RailwayStationImpl implements RailwayStation {

    @Override
    public void ByTicket() {
        System.out.println("成功购买一张票");
    }
    @Override
    public void changeTicket() {
        System.out.println("成功完成改签。。");
    }
    @Override
    public void exitTicket() {
        System.out.println("成功退票。。。");
    }
}
  • 代理主题角色
package com.yc.Impl;

import com.yc.Interface.RailwayStation;
//代理类,也实现抽象主题角色,和委托对象有着相同的接口--》代理主题角色
public class RailwayStationImplProxy implements RailwayStation {
        RailwayStation rs;//获得抽象主题角色的引用 
        public  RailwayStationImplProxy(RailwayStation railwaySation) {
            this.rs=railwaySation;
        }//在构造方法中得到具体主题的实例。(为下文委托类调用方法埋下伏笔)
    @Override
    public void ByTicket() {
        docheck();//做身份验证处理,(额外的功能。原来买票时候可没有哦。)
        rs.ByTicket();//调用委托类的买票方法,所以说在使用代理类时,本质上还是用了具体主题类,也就是所说的,代售点的票还是火车站给的。
        dolog();//做点日志记录,(这和验证一样都是我们可以在代理类中新添加一些杂七杂八的东西)
    }

    @Override
    public void changeTicket() {
        docheck();
        rs.changeTicket();
        dolog();

    }

    @Override
    public void exitTicket() {
        docheck();
        rs.exitTicket();
        dolog();
    }

    //代理类额外添加的业务需求(核实身份,和记录购买记录)
    private void docheck() {
            System.out.println("已核实该用户身份,合法公民");    
    }

    private void dolog() {
        System.out.println("xxx已成功完成上述操作");

    }
}
  • 测试类
package com.yc.Test;

import org.junit.Test;

import com.yc.Impl.RailwayStationImpl;
import com.yc.Impl.RailwayStationImplProxy;
import com.yc.Interface.RailwayStation;

public class Customer {

    @Test
    public void testStatic() {
            RailwayStation rs1=new RailwayStationImplProxy(new RailwayStationImpl());   
            //传入具体主题角色,通过构造函数直接过得代理对象。
            rs1.ByTicket();
            rs1.changeTicket();
            rs1.exitTicket();
    }
}

测试结果:已核实该用户身份,合法公民
成功购买一张票
xxx已成功完成上述操作
已核实该用户身份,合法公民
成功完成改签。。
xxx已成功完成上述操作
已核实该用户身份,合法公民
成功退票。。。
xxx已成功完成上述操作

由结果可知:通过代理类我们不仅实现了我们的业务(订票,改签,买票),还额外的实现了对买票者身份的核实,以及买票的相关记录,而且这新添加的功能是在没有改动原主题角色类的情况下的,如果那天我们不想要这些额外功能了,就可以直接去了,完全不影响原来的业务逻辑的执行。(这也就是引入代理的很重要的原因)

总结一下静态代理的优缺点:
优点:1、代理类的.class文件在编译期就已经存在,所以执行速度很快。
2、隐藏了真实对象,不会修改真实对象的类代码

缺点:1、代理类的.class文件在编译期就已经存在,不够灵活(好尴尬,刚刚还是优点,尴尬人事变换莫测)
2、每一个具体主题角色都要配一个代理类,会造成类的膨胀(讲道理这样也是不合理的)
3、每一个方法都要配置我们额外的功能,造成了代码的冗余。

综合以上优比缺:2:3,所以我不得不再向你们介绍一下动态代理模式,来弥补静态代理的不足( 然后动态自己也有些不足,这就非常尴尬了)

动态代理(很重要,我就是看到spring-aop底层运行原理就是动态代理,所以才来了解代理模式的)
动态代理又可以分为两种
1.JDK实现动态代理;
2.Cglib实现动态代理;
我们还是先理解下什么是动态代理:从上次静态代理中我们可以看出,代理类在程序编译期就已经存在,而我们可以将该代理类的出现的时间放到程序运行时。也就是所编译时并不指定明确的代理类,而是在运行时要用到代理类时才去动态生成,这种在程序运行时动态生成代理的方式也就是动态代理。。。

1.jdk动态代理类:
正因为动态代理有这样灵活的特性,所以我们在设计动态代理类(DynamicProxy)时不用显式地让它实现与真实主题类(RealSubject)相同的接口(interface),而是把这种实现推迟到运行时。
为了能让DynamicProxy类能够在运行时才去实现RealSubject类已实现的一系列接口并执行接口中相关的方法操作,需要让 DynamicProxy类实现JDK自带的Java.lang.reflect.InvocationHandler接口,该接口中的invoke() 方法能够让DynamicProxy实例在运行时调用被代理类的“对外服务”,即调用被代理类需要对外实现的所有接口中的方法,也就是完成对真实方法的调 用,Java帮助文档中称这些真实方法为处理程序。
按照上面所述,我们肯定必须先把被代理类RealSubject已实现的所有interface都加载到JVM中,不然JVM怎么能够找到这些方法呢?明白了这个道理,那么我们就可以创建一个被代理类的实例,获得该实例的类加载器ClassLoader。
所谓的类加载器ClassLoader,就是具有某个类的类定义,即类的内部相关结构(包括继承树、方法区等等)。
更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到控制被代理对 象的行为的目的。请详看下面代码中的DynamicProxy类,其中必须实现的invoke()方法在调用被代理类的真实方法的前后都可进行一定的特殊 操作。这是动态代理最明显的优点

类图如下:这里写图片描述

代码实现:(还是之前得需求)

package com.yc.Impl;

import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler{

    private Object targetObj;

    public  Object newInstanceProxy(Object targetObj){
        this.targetObj=targetObj;
        // 第一个参数,目标对象的类加载器(就是具有某个类的类定义,即类的内部相关结构(包括继承树、方法区等等)。)
        // 第二个参数,目标接口已实现的所有接口,而这些是动态代理类要实现的接口列表
        // 第三个参数, 调用实现了InvocationHandler的对象生成动态代理实例,当你一调用代理,代理就会调用InvocationHandler的invoke
        return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),targetObj.getClass().getInterfaces(),this); 
    }   

    //当调用代理实例中的方法时,就会自动执行下面的invoke方法,传入的是我们要执行的方法和参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
            Object result=null;
            docheck();//额外添加的方法
            result=method.invoke(targetObj, args);//执行具体主题角色类的中的方法
            dolog();//额外添加的方法
            return result;//返回执行方法的结果。。

    }

    //代理类额外添加的业务需求(核实身份,和记录购买记录)
    private void docheck() {
            System.out.println("已核实该用户身份,合法公民");    
    }

    private void dolog() {
        System.out.println("xxx已成功完成上述操作");
    }       
}

测试类:

    @Test
    public void testDynamic() { 
            DynamicProxy dp=new DynamicProxy();//不要误会这个可不是代理类的实例

            RailwayStation rs2=(RailwayStation)  dp.newInstanceProxy(new RailwayStationImpl());
            //rs2这个才是对应RailwanStationimpl类的代理实例
            rs2.ByTicket();//调用代理实例时会执行invoke方法,实现我们的所有需求
            rs2.changeTicket();
            rs2.exitTicket();
    }

看了动态代理发现是不是神奇了很多,简单总结一下他的优缺点;
优点:1、代理实例在程序运行时才会产生,让程序更为灵活。
2、通过实现InvocationHandler接口,每次调用代理时都会自动运行其中的invoke 方法,然后根据调用的方法找到真实对象中的方法,不需要每个方法都写一次,避免了代码的冗余。
3、很明显这里可个类可以创建很多个真实类的代理实例,所以避免了类的膨胀
缺点:1、相比于静态代理,动态代理是在运行时才生成,所以在整个运行时,速度较慢。
2、只能对实现接口的类进行代理

所以静态代理和动态代理各有各的优点,各有各的缺点,具体使用哪一种要根据程序自身的需求来判断

2.Cglib实现动态代理;

AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。
两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的 ,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通 过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有 上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛, 且在效率上更有优势。
JDK的动态代理机制只能代理实现了接口的类,否则不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

介绍:
CGLIB的核心类:
net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。
net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;
第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method 对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使 用,因为它更快

package com.yc.Impl;

import java.lang.annotation.Target;
import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibDynaproxy implements MethodInterceptor {

        private Object targetObj;

        public Object proxynewInstance(Object targetObj){//获取代理对象实例

            this.targetObj=targetObj;

            Enhancer enhancer=new Enhancer();// 用这个类来创建代理对象(被代理类的子类):

            enhancer.setSuperclass(targetObj.getClass());//设置父类,也就是我们的目标类,使我们的代理类成为主题类的子类

            enhancer.setCallback(this);//设置回调函数,当程序调用代理中的方法是,就会自动执行下面的intercept方法

            enhancer.create();//到此完成对代理实例的创建

            return enhancer;

        }
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {
           Object result = null;
           docheck();//额外添加身份核实功能
            result=methodProxy.invokeSuper(obj, args); //调用新生成的cglib的代理对象 所属的"父类"的被代理的方法
            dolog();//额外添加日志功能
            return result;

    }

    //代理类额外添加的业务需求(核实身份,和记录购买记录)
        private void docheck() {
                System.out.println("已核实该用户身份,合法公民");    
        }

        private void dolog() {
            System.out.println("xxx已成功完成上述操作");
        } 
}

测试类: @Test
public void testCglibDynamic() {

    CglibDynaproxy cdp=new CglibDynaproxy();
    RailwayStationImpl rs3=  (RailwayStationImpl) cdp.proxynewInstance(new RailwayStationImpl());//可以直接使用具体主题类,不在需要接口
        rs3.ByTicket();
        rs3.changeTicket();
        rs3.exitTicket();
}
运行结果:同上。

差不多代理模式介绍就这样了。剩下的就是看大家如何灵活的去用代理模式了。

参考文章:http://blog.csdn.net/tanggao1314/article/details/51001272(哈哈,参考的有点多,感觉和转载都差不多了,。。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值