大家好,我是北君。相信大家经常听说代理模式在设计模式中是比较难理解的。这次智贝老师就来给大家科普一下。1.什么是代理模式为另一个对象提供一个代理或占位符来控制对它的访问。代理模式:为其他对象提供一个代理来控制对这个对象的访问。说人话:在不改变原类(或调用代理类)代码的情况下,通过引入代理类,为原类增加功能,如SpringAOP。2.代理模式定义①Subject抽象主题角色,可以是抽象类,也可以是接口,是最常见的业务类定义,没有特殊要求。②RealSubject的真实主体角色,也叫委托角色,是业务逻辑的具体执行者。③Proxy代理主题角色,也叫代理类,负责真实角色的应用,将抽象主题类定义的所有方法限制的实现委托给真实主题角色,在真实主题角色执行前后做一些预处理或善后工作处理。大致代码如下:/***抽象主题类*/publicinterfaceSubject{voiddoSomething();}/***真正的主题角色*/publicclassRealSubjectimplementsSubject{@OverridepublicvoiddoSomething(){//TODO具体要执行什么}}/***代理主体作用*/publicclassProxyimplementsSubject{//具体要代理的实现类privateSubjectrealSubject;publicProxy(SubjectrealSubject){this.realSubject=realSubject;}@OverridepublicvoiddoSomething(){this.before();realSubject.doSomething();这个.after();}//preprocessingprivatevoidbefore(){//TODO}//postprocessingprivatevoidafter(){//TODO}}3.代理模式的两种实现例如使用代理模式实现耗时统计的一个接口。3.1静态代理①基于接口的编程抽象主题类:publicinterfaceIUserController{//loginStringlogin(Stringusername,Stringpassword);//注册Stringregister(Stringusername,Stringpassword);}具体主题类:publicclassUserControllerimplementsIUserController{@OverridepublicStringlogin(Stringusername,Stringpassword){//TODO登录逻辑returnnull;}@OverridepublicStringregister(Stringusername,Stringpassword){//TODO注册逻辑returnnull;}}代理主题类:publicclassUserControllerProxyimplementsIUserController{privateIUserControlleruserController;publicUserControllerProxy(IUserControlleruserController){this.userController=userController;}@OverridepublicStringlogin(Stringusername,Stringpassword){longstartTime=System.currentTimeMillis();//登录逻辑userController.login("username","password");longendTime=System.currentTimeMillis();长回应时间=结束时间-开始时间;System.out.println("接口响应时间:"+responseTime);返回空值;}@OverridepublicStringregister(Stringusername,Stringpassword){longstartTime=System.currentTimeMillis();//注册逻辑userController.register("username","password");longendTime=System.currentTimeMillis();长响应时间=结束时间-开始时间;System.out.println("接口响应时间:"+responseTime);返回空值;}}测试:因为原类UserController和代理类UserControllerProxy实现了相同的接口,所以是基于接口而不是实现编程。UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码userController.login("用户名","密码");userController.register("用户名","密码");}}在上面的代码中,代理类和具体的主题类需要实现相同的接口。如果具体的主题类没有实现接口,也不是我们自己开发维护的(比如来自第三方的接口),我们要统计这个第三方接口的耗时。我们应该怎么做?实施代理模式呢?②在继承的基础上继承具体的主题类,然后对其方法进行扩展,直接看代码就可以了//登录逻辑super.login("用户名","密码");longendTime=System.currentTimeMillis();长响应时间=结束时间-开始时间;System.out.println("接口响应时间:"+responseTime);返回空值;}@OverridepublicStringregister(Stringusername,Stringpassword){longstartTime=System.currentTimeMillis();//注册逻辑super.register("username","password");longendTime=System.currentTimeMillis();长响应时间=结束时间-开始时间;System.out.println("接口响应时间:"+responseTime);返回空值;}}3.2动态代理在上面的例子中,存在两个问题:①我们需要在代理类中,重新实现特定主题类中的所有方法,并为每个方法附加类似的代码逻辑。如果方法很多,就会有很多重复的代码。②如果要添加的附加功能的类不止一个,我们需要为每个类创建一个代理类。那么如何解决上述问题呢?答案是动态代理。动态代理:不是事先为每个原始类编写一个代理类,而是在运行时动态创建一个与原始类对应的代理类,然后在系统中用代理类替换原始类。JDK动态代理:publicclassDynamicProxyHandlerimplementsInvocationHandler{privateObjecttarget;publicDynamicProxyHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{longstartTime=System.currentTimeMillis();对象结果=method.invoke(this.target,args);longendTime=System.currentTimeMillis();长响应时间=结束时间-开始时间;System.out.println("接口响应时间:"+responseTime);返回结果;}}Test:publicclassDynamicProxyTest{publicstaticvoidmain(String[]args){//1.创建一个具体的主题类IUserControlleruserController=newUserController();//2.创建一个处理程序DynamicProxyHandlerproxyHandler=newDynamicProxyHandler(userController);//3.动态生成代理类IUserControllero=(IUserController)Proxy.newProxyInstance(userController.getClass().getClassLoader(),userController.getClass().getInterfaces(),proxyHandler);o.login("用户名","密码");o.register("用户名","密码");}}这是一个JDK动态代理,利用反射机制生成一个实现代理接口的匿名类,调用InvokeHandler处理后再调用具体方法。代理对象是在程序运行时产生的,而不是在编译时产生的。要求是具体的主题类必须实现该接口。另一种方式是Cglib动态代理。CGLIB(CodeGenerationLibrary)是一个基于ASM的字节码生成库,它允许我们在运行时修改和动态生成字节码,即通过修改字节码来生成子类。Cglib动态代理:publicclassUserController{publicStringlogin(Stringusername,Stringpassword){//TODO登录逻辑System.out.println("login");返回空值;}publicStringregister(Stringusername,Stringpassword){//TODO注册逻辑System.out.println("Registration");返回空值;}}注意:真正的主题类并没有实现该接口。公共类CglibDynamicProxy实现MethodInterceptor{privateObjecttarget;publicCglibDynamicProxy(Objecttarget){this.target=target;}//为目标创建代理对象publicObjectnewProxyInstance(){//1.工具类Enhancerenhancer=newEnhancer();//2.设置父类enhancer.setSuperclass(target.getClass());//3.设置回调函数enhancer.setCallback(this);//4.创建子类(代理对象)returnenhancer.create();}@OverridepublicObjectintercept(Objecto,Methodmethod,Object[]args,MethodProxymethodProxy)throwsThrowable{longstartTime=System.currentTimeMillis();对象结果=method.invoke(this.target,args);longendTime=System.currentTimeMillis();长响应时间=结束时间-开始时间;System.out.println("接口响应时间:"+responseTime);返回结果;}}测试:公共类CglibDynamicProxyTest{publicstaticvoidmain(String[]args){UserControlleruserController=newUserController();CglibDynamicProxycglibDynamicProxy=newCglibDynamicProxy(userController);UserControllero=(UserController)cglibDynamicProxy.newProxyInstance();o.login("用户名","密码");o.register("用户名","密码");}}4。代理模式优点①职责明确,真正的作用是实现实际的业务逻辑,不需要关心其他非职责事务,通过后期代理完成一个事务,附带的结果是编程简洁明了②高扩展性具体的主题角色会随时发生变化,只要实现了接口,无论怎么变化,代理类都可以不做任何修改就可以使用。5、代理模式应用场景①业务系统非功能需求的开发这是最常用的场景。例如:监控、统计、认证、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放在代理类中统一处理,让程序员只需要专注于业务开发。一个典型的例子就是SpringAOP。②RPCRPC(remoteproxy)框架也可以看作是一种代理模式,通过远程代理,隐藏网络通信和数据编解码等细节。客户端使用RPC服务时,就像使用本地函数一样,不知道与服务端交互的细节。另外,RPC服务的开发者只需要开发业务逻辑,就像开发本地使用的功能一样,不需要关注与客户端交互的细节。
