好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

面试官:讲讲SpringAOP的底层代理模式

哈喽,大家好,我是指北君。

代理模式相信大家经常听说,在设计模式中相对而言是比较难理解的。这次指北君来给大家通俗的介绍介绍。

1.什么是代理模式

Provide a surrogate or placeholder for another object to control access to it.

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

说人话:在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能,比如Spring AOP。

2.代理模式定义

①Subject

抽象主题角色,可以是抽象类,可以是接口,是一个最普通的业务类定义,无特殊要求。

②RealSubject

真实主题角色,也叫被代理角色,是业务逻辑的具体执行者。

③Proxy

代理主题角色,也叫代理类,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并在真实主题角色处理前后做一些预处理或善后工作。

通用代码如下:

 /**    * 抽象主题类    */  public interface Subject  {  void doSomething (  )  ;   } 
 /**    * 真实主题角色    */  public class RealSubject implements Subject {  @Override
    public void doSomething (  )   {   // TODO 具体执行的事  }   } 
 /**    * 代理主题角色    */  public class Proxy implements Subject {   // 要代理的具体实现类
    private Subject realSubject ;  public Proxy ( Subject realSubject )  {  this .realSubject   =  realSubject ;   }  @Override
    public void doSomething (  )   {  this .before  (  )  ;  realSubject .doSomething  (  )  ;  this .after  (  )  ;   }   //  预处理
    private void before (  )  {   //  TODO  }   //  善后处理
    private void after (  )  {   //  TODO  }   } 

3.代理模式的两种实现

比如用代理模式实现统计某个接口的耗时。

3.1 静态代理

①基于接口编程

抽象主题类:

public interface IUserController  {   //  登录
    String login ( String username , String password )  ;   //  注册
    String register ( String username , String password )  ;   } 

具体主题类:

public class UserController implements IUserController {  @Override
    public String login ( String username ,  String password )   {   //  TODO 登录逻辑
        return  null  ;   }  @Override
    public String register ( String username ,  String password )   {   //  TODO 注册逻辑
        return  null  ;   }   } 

代理主题类:

public class UserControllerProxy implements IUserController {  private IUserController userController ;  public UserControllerProxy ( IUserController userController )  {  this .userController   =  userController ;   }  @Override
    public String login ( String username ,  String password )   {   long  startTime  =  System .currentTimeMillis  (  )  ;   //  登录逻辑
        userController .login  (  "username"  ,  "password"  )  ;   long  endTime  =  System .currentTimeMillis  (  )  ;   long  responseTime  =  endTime  -  startTime ;  System .out  .println  (  "接口响应时间:"  + responseTime )  ;  return  null  ;   }  @Override
    public String register ( String username ,  String password )   {   long  startTime  =  System .currentTimeMillis  (  )  ;   //  注册逻辑
        userController .register  (  "username"  ,  "password"  )  ;   long  endTime  =  System .currentTimeMillis  (  )  ;   long  responseTime  =  endTime  -  startTime ;  System .out  .println  (  "接口响应时间:"  + responseTime )  ;  return  null  ;   }   } 

测试:

因为原始类 UserController 和代理类 UserControllerProxy 实现相同的接口,是基于接口而非实现编程,将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码

public class StaticProxyTest  {  public static void main ( String [  ]  args )   {  IUserController userController  =  new UserControllerProxy ( new UserController (  )  )  ;  userController .login  (  "username"  ,  "password"  )  ;  userController .register  (  "username"  ,  "password"  )  ;   }   } 

在上面的代码中,代理类和具体主题类需要实现相同的接口,假如具体主题类没有实现接口,并且不是我们开发维护的(比如来自第三方接口),我们要统计这个第三方接口的耗时,那应该如何实现代理模式呢?

②基于继承

继承具体主题类,然后扩展其方法即可,直接看代码。

public class UserControllerProxy extends UserController  {  @Override
    public String login ( String username ,  String password )   {   long  startTime  =  System .currentTimeMillis  (  )  ;   //  登录逻辑
        super .login  (  "username"  ,  "password"  )  ;   long  endTime  =  System .currentTimeMillis  (  )  ;   long  responseTime  =  endTime  -  startTime ;  System .out  .println  (  "接口响应时间:"  + responseTime )  ;  return  null  ;   }  @Override
    public String register ( String username ,  String password )   {   long  startTime  =  System .currentTimeMillis  (  )  ;   //  注册逻辑
        super .register  (  "username"  ,  "password"  )  ;   long  endTime  =  System .currentTimeMillis  (  )  ;   long  responseTime  =  endTime  -  startTime ;  System .out  .println  (  "接口响应时间:"  + responseTime )  ;  return  null  ;   }   } 

3.2 动态代理

在上面的例子中,有两个问题:

①我们需要在代理类中,将具体主题类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑,如果方法很多,重复代码也会很多。

②如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。

那该如何解决上面的问题呢?答案就是动态代理(Dynamic Proxy)。

动态代理:不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

JDK动态代理:

public class DynamicProxyHandler implements InvocationHandler  {  private Object target ;  public DynamicProxyHandler ( Object target )  {  this .target   =  target ;   }  @Override
    public Object invoke ( Object proxy ,  Method method ,  Object [  ]  args )  throws Throwable  {   long  startTime  =  System .currentTimeMillis  (  )  ;  Object result  =  method .invoke  ( this .target  ,  args )  ;   long  endTime  =  System .currentTimeMillis  (  )  ;   long  responseTime  =  endTime  -  startTime ;  System .out  .println  (  "接口响应时间:"  + responseTime )  ;  return result ;   }   } 

测试:

public class DynamicProxyTest  {  public static void main ( String [  ]  args )   {   //   1 、创建具体主题类
        IUserController userController  =  new UserController (  )  ;   //   2 、创建 Handler
        DynamicProxyHandler proxyHandler  =  new DynamicProxyHandler ( userController )  ;   //   3 、动态产生代理类
        IUserController o  =   ( IUserController ) Proxy .newProxyInstance  ( userController .getClass  (  )  .getClassLoader  (  )  ,  userController .getClass  (  )  .getInterfaces  (  )  ,  proxyHandler )  ;  o .login  (  "username"  ,  "password"  )  ;  o .register  (  "username"  ,  "password"  )  ;   }   } 

这是 JDK 动态代理,利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,代理对象是在程序运行时产生的,而不是编译期,要求是具体主题类必须实现接口。

另外一种方式是 Cglib 动态代理。CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,也就是通过修改字节码生成子类来处理。

Cglib 动态代理:

public class UserController {  public String login ( String username ,  String password )   {   //  TODO 登录逻辑
        System .out  .println  (  "登录"  )  ;  return  null  ;   }  public String register ( String username ,  String password )   {   //  TODO 注册逻辑
        System .out  .println  (  "注册"  )  ;  return  null  ;   }   } 

注意:真实主题类是没有实现接口的。

public class CglibDynamicProxy implements MethodInterceptor  {  private Object target ;  public CglibDynamicProxy ( Object target )  {  this .target   =  target ;   }   //  给目标创建代理对象
    public Object newProxyInstance (  )  {   //   1 .工具类
        Enhancer enhancer  =  new Enhancer (  )  ;   //   2 .设置父类
        enhancer .setSuperclass  ( target .getClass  (  )  )  ;   //   3 .设置回调函数
        enhancer .setCallback  ( this )  ;   //   4 .创建子类(代理对象)
        return enhancer .create  (  )  ;   }  @Override
    public Object intercept ( Object o ,  Method method ,  Object [  ]  args ,  MethodProxy methodProxy )  throws Throwable  {   long  startTime  =  System .currentTimeMillis  (  )  ;  Object result  =  method .invoke  ( this .target  ,  args )  ;   long  endTime  =  System .currentTimeMillis  (  )  ;   long  responseTime  =  endTime  -  startTime ;  System .out  .println  (  "接口响应时间:"  + responseTime )  ;  return result ;   }   } 

测试:

public class CglibDynamicProxyTest  {  public static void main ( String [  ]  args )   {  UserController userController  =  new UserController (  )  ;  CglibDynamicProxy cglibDynamicProxy  =  new CglibDynamicProxy ( userController )  ;  UserController o  =   ( UserController ) cglibDynamicProxy .newProxyInstance  (  )  ;  o .login  (  "username"  ,  "password"  )  ;  o .register  (  "username"  ,  "password"  )  ;   }   } 

4.代理模式优点

①职责清晰

真实的角色就是实现实际的业务逻辑, 不用关心其他非本职责的事务, 通过后期的代理完成一件事务, 附带的结果就是编程简洁清晰。

②高扩展性

具体主题角色是随时都会发生变化的, 只要它实现了接口, 甭管它如何变化,代理类完全都可以在不做任何修改的情况下使用。

5.代理模式应用场景

①业务系统的非功能性需求开发

这是最常用的一个场景。比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。

典型例子就是 SpringAOP。

②RPC

RPC(远程代理) 框架也可以看作一种代理模式,通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。

原文地址:https://mp.weixin.qq测试数据/s/0P0rockipWxi-S7copY-UA

查看更多关于面试官:讲讲SpringAOP的底层代理模式的详细内容...

  阅读:10次