• 简说设计模式——代理模式(续)


      之前说了代理模式,即为其他对象提供一种代理以控制对这个对象的访问,详情见《简说设计模式——代理模式》,而代理模式常见的实现方式为静态代理及动态代理。

    一、静态代理

      所谓静态代理类是指:由程序员创建或由特定工具自动生成源代码,再对其进行编译。在程序运行之前,代理类的.class文件就已经存在了。UML结构图如下:

      这里以持久化层的数据交互类为例,IUserDao是与数据库进行交互的接口,UserDao为IUserDao接口的实现类,UserDaoProxy为代理类,二者皆实现了IUserDao接口。说具体点就是,UserDao类是IUserDao接口的具体实现者,而UserDaoProxy类是通过调用UserDao类的相关方法来提供特定服务的。

      1. IUserDao接口

      这里为抽象目标类/接口。

    1 public interface IUserDao {
    2     
    3     public void save();
    4 
    5 }

       2. UserDao接口

      这里为具体目标类或被代理的对象。实现了IUserDao中的接口方法。

    1 public class UserDao implements IUserDao {
    2 
    3     @Override
    4     public void save() {
    5         System.out.println("数据已保存!!!");
    6     }
    7     
    8 }

       3. 静态代理类

      首先在静态代理类中引入IUserDao接口,通过调用UserDao类的相关方法来提供特定服务。

     1 public class UserDaoProxy implements IUserDao {
     2     
     3     private IUserDao iUserDao;
     4 
     5     public UserDaoProxy(IUserDao iUserDao) {
     6         this.iUserDao = iUserDao;
     7     }
     8     
     9     @Override
    10     public void save() {
    11         System.out.println("开始事务...");
    12         iUserDao.save();    //执行目标对象
    13         System.out.println("提交事务...");
    14     }
    15 
    16 }

      4. Client客户端

    1 public class Client {
    2     
    3     public static void main(String[] args) {
    4         UserDao userDao = new UserDao();
    5         UserDaoProxy proxy = new UserDaoProxy(userDao);
    6         proxy.save();
    7     }
    8 
    9 }

      运行结果如下:

      

      总结一下就是,在代理类中注入依赖,即引入需要代理的实体类,通过代理类来调用实体类中的方法来实现静态代理。

      静态代理由我们自己去生成固定的代码进行编译。需要定义接口或抽象的父类作为抽象目标类,具体目标类和代理类一起实现相同的接口或继承相同的类,然后通过调用相同的方法来调用目标对象的方法。

      静态代理需要目标对象和代理对象实现相同的接口。可以在不修改目标对象功能的前提下,对目标功能进行扩展。

      虽然静态代理可以很好的对目标对象进行功能扩展,但对每一个服务都需要建立代理类,工作量较大且不易管理,而且如果接口发生改变的话,代理类也得进行相应的修改,这时动态代理的作用就显现出来了。

    二、动态代理

      动态代理与静态代理的区别在于:在程序运行时,动态代理类是运用反射机制创建而成的。在抽象工厂模式的最后有提到用反射来代理switch语句进行选择,这里就运用到了类似的思想。

      通过动态代理,我们不再需要手动创建代理类,只需编写一个动态处理器即可,而真正的代理对象由JDK在运行时帮我们创建。所以我们也将之称为JDK动态代理。

      方法步骤如下:

    1. 写一个代理类实现InvocationHandler接口,通过构造函数把代理对象(具体目标类)传入到此处理器中,在invoke()方法中增加method.invoke(realSubject, args)
    2. 在调用方法时,通过java.lang.reflect.Proxy和newProxyInstance()来获取代理实现类,生成代理对象时,直接调用方法即可。

      下面看一个例子,UML结构图如下:

      

      1. IBusiness接口

      被代理的接口。

    1 public interface IBusiness {
    2 
    3     public void doWork();
    4     
    5 }

       2. Business类

      具体实现类/被代理的对象。

    1 public class Business implements IBusiness {
    2 
    3     @Override
    4     public void doWork() {
    5         System.out.println("进行业务逻辑处理");
    6     }
    7 
    8 }

       3. BusinessHandler类

      BusinessHandler类实现类Invocation接口,它是方法调用接口,声明了负责调用任意一个方法的invoke()方法,参数proxy指定动态代理类实例,参数method指定被调用的方法,参数args指定向被调用方法传递的参数,而invoke()方法的返回值表示被调用方法的返回值。其中 method.invoke(iBusiness, args) 相当于 iBusiness.method(args) 。

     1 public class BusinessHandler implements InvocationHandler {
     2     
     3     private IBusiness iBusiness;
     4     
     5     public BusinessHandler(IBusiness iBusiness) {
     6         this.iBusiness = iBusiness;
     7     }
     8 
     9     @Override
    10     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    11         System.out.println("before");
    12         method.invoke(iBusiness, args);
    13         System.out.println("after");
    14         return null;
    15     }
    16 
    17 }

       4. Client客户端

     1 public class Client {
     2     
     3     public static void main(String[] args) {
     4         Business business = new Business();
     5         
     6         //生成代理类对象
     7         IBusiness proxy = (IBusiness) Proxy.newProxyInstance(
     8                 business.getClass().getClassLoader(), 
     9                 business.getClass().getInterfaces(), 
    10                 new BusinessHandler(business));
    11         
    12         proxy.doWork();
    13     }
    14 
    15 }

       此处通过java.lang.reflect.Proxy类的newProxyInstance()方法来生成代理类对象,它的完整定义如下:

       public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException 

      参数loader指定动态代理类的类加载器,参数interfaces指定动态代理类需要实现的所有接口,参数handler指定与动态代理类相关联的InvocationHandler对象。所以我们只需调用newProxyInstance()方法就可以某一个对象的代理对象了。(有关ClassLoader类加载器的内容这里就不再赘述了,它的作用是将class文件加载到jvm虚拟机中去)。

      运行结果如下:

      

      相比于静态代理,动态代理的优势还是很明显的,不仅减少了对业务接口的依赖,还降低了耦合度,但它还是无法摆脱对接口的依赖。那么对于没有接口的类应该如何实现动态代理呢?

    三、CGLIB代理

      cglib是一个强大的高性能代码生成包,底层是通过使用一个小而快的字节码处理框架ASM来转换并生成新的类,所以我们一般也称之为cglib字节码生成。

      与JDK动态代理不同,cglib是针对类来实现代理的,所以对于没有接口的类我们可以通过cglib字节码生成来实现代理。原理是对指定的业务类生成一个子类,并覆盖其中的业务方法实现代理。但因为采用的是继承,所以不能对final修饰的类进行代理。

      下面看一个使用cglib进行代理的实例,需先导入相应的jar包(asm及cglib包)。

      方法步骤如下:

    1. 创建被代理类,创建拦截器(实现MethodInterceptor)
    2. 实现Enhancer工具类,允许为非接口类型创建一个Java代理
    3. 使用setSuperclass()方法设置父类
    4. 使用setCallback()方法设置回调函数(默认执行intercept方法)
    5. 使用create()方法创建子类

      1. 被代理类

      首先定义业务类,无需实现接口。

    1 public class Hello {
    2     
    3     public void sayHello() {
    4         System.out.println("Hello World!");
    5     }
    6 
    7 }

      2. 拦截器

      定义一个拦截器,通过实现MethodInterceptor接口的intercept()方法来实现回调方法,通过invokeSuper()执行目标对象的方法。

     1 public class HelloMethodInterceptor implements MethodInterceptor {
     2 
     3     @Override
     4     public Object intercept(Object object, Method method , Object[] objects , MethodProxy methodProxy ) throws Throwable {
     5         System.out.println("before " + method.getName());
     6         methodProxy.invokeSuper(object, objects);
     7         System.out.println("after " + method.getName());
     8         return null;
     9     }
    10 
    11 }

       3. Client客户端

      通过Enhancer加强类来创建动态代理类,通过它的setSuperclass()方法来指定要代理的业务类(即为下方生成的代理类指定父类),然后通过create()方法生成代理类对象。

      在enhance.create()创建完代理对象后,在代理类调用方法中,会被我们实现的方法拦截器HelloMethodInterceptor拦截。如果被代理类被final修饰,则该类不能被继承,即不能被代理;同样,如果被代理类存在final修饰的方法,则该方法不能被代理。

     1 public class Client {
     2     
     3     public static void main(String[] args) {
     4         Enhancer enhancer = new Enhancer();        //工具类
     5         enhancer.setSuperclass(Hello.class);    //继承被代理类
     6         enhancer.setCallback(new HelloMethodInterceptor());        //设置回调
     7         
     8         Hello hello = (Hello) enhancer.create();    //生成代理类对象
     9         hello.sayHello();
    10     }
    11 
    12 }

       运行结果如下:

      

      综上所述,cglib采用的是动态创建子类的方法,所以对final修饰的类不能进行代理。以Spring AOP编程为例,JDK动态代理及cglib代理的区别在于,有接口的目标对象采用JDK代理,无接口的目标对象采用cglib代理。

      使用cglib的前提条件为:

    • 需要引入cglib的jar文件
    • 目标类不能为final
    • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法 

      源码地址:https://gitee.com/adamjiangwh/GoF

  • 相关阅读:
    复旦大学数学学院18级高等代数II期中考试第七大题的三种证法及其推广
    复旦大学2019--2020学年第一学期高等代数I期末考试情况分析
    复旦大学2019--2020学年第一学期(19级)高等代数I期末考试第六大题解答
    复旦大学2019--2020学年第一学期(19级)高等代数I期末考试第七大题解答
    复旦大学2019--2020学年第一学期(19级)高等代数I期末考试第八大题解答
    复旦高等代数I(19级)每周一题
    复旦大学数学学院 18 级本科生对每周一题的评价
    复旦大学2018--2019学年第二学期高等代数II期末考试情况分析
    复旦大学2018--2019学年第二学期(18级)高等代数II期末考试第六大题解答
    CentOS7安装MySQL
  • 原文地址:https://www.cnblogs.com/adamjwh/p/10907526.html
Copyright © 2020-2023  润新知