美文网首页
设计模式之代理模式

设计模式之代理模式

作者: 夜色流冰 | 来源:发表于2021-04-14 09:38 被阅读0次

    说道Java的代理模式,很可能就想到类Proxy和接口InvocationHandler两个东西,事实上代理模式的思想应用还是不止于此,本篇就做个梳理;

    先看看InvocationHandler

    //proxy:宿主对象的代理对象
    //method:宿主对象的某个方法
    //args:method方法所需的参数
     public Object invoke(Object proxy, Method method, Object[] args)
    

    需要注意第二个参数Method,该对象表明的是宿主对象的某个方法。该对象使得我们可以通过其invoke方法执行宿主的某个方法:

    //obj:当前方法所属的对象,不能为null
    //args:当然方法所需的参数
    Object invoke(Object obj, Object... args)
    

    所以我们在执行method的invoke方法时需要这么做(伪代码):

    //初始化一个宿主对象
    SubjectImpl  subject = new SubjectImpl();
    
    //执行subject对象的某个方法
    method.invoke(subject,params);
    

    综合上面的说明,所以网上关于动态代理的例子就很好理解为什么是如下所示了:

    
    /**
     * 实现了InvocationHandler接口的类:
     */
    public class SubjectInvocationHandler implements InvocationHandler {
        //宿主对象
        private ISubject subject;
    
        public SubjectInvocationHandler(ISubject subject) {
            this.subject = subject;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("--代理类工作:实际是宿主在执行相关的invoke方法--");
            return method.invoke(subject, args);
        }
    
        /**
         * 为宿主对象subject创建代理类对象
         * @return
         */
        public ISubject createProxy() {
            ISubject subjectProxy = (ISubject) Proxy.newProxyInstance(getClass().getClassLoader(),
                    subject.getClass().getInterfaces(),
                    this);
            return subjectProxy;
        }
    
    }
    
    public interface ISubject {
        void doSomething();
    }
    
    public class SubjectImpl implements ISubject {
        public void doSomething() {
            System.out.println("SubjectImple do Somthing");
        }
    }
    

    客户端的调用也很简单:

           SubjectInvocationHandler subjectInvocationHandler = new SubjectInvocationHandler(new SubjectImpl());
           //运行时创建代理类对象
            ISubject proxy = subjectInvocationHandler.createProxy();
            //代理执行doSomething
            proxy.doSomething();
    

    这段代码仿写很容易,<font color="#ff00ff">但是有什么用呢?换句话说我都拿到了SubjectImpl还费劲创建一个代理对象干嘛?从上面的代码来看proxy.doSometing()执行的仍然是SubjectImpl这个宿主类的doSmething方法。何必多此一举不直接使用宿主呢?</font>

    回答这个问题之前先回顾一下问题的本质:<font color="#ff00ff">什么是动态代理?</font>代理类或者对象在程序运行时创建的代理被成为 动态代理。动态代理模式理论上的有点就不细说了,关于更多的讨论可以参考知乎的这篇文章。下面就结合实际例子来看看动态代理的强大作用。


    作为android开发人员,Retrofit这个网络框架可能都不陌生,该框架就是借助于动态代理实现了这么牛叉的网络框架。使用该框架你需要先定制一个接口:

    public interface MyService{
        @FormUrlEncoded
        @POST("you url")
        Call<YourJavaBean>  loadData(@FieldMap Map<String, Object> params);
    }
    

    上面的接口很简单,就是配置了三个注解@FormUrlEncoded,@POST,@FieldMap.关键是我们在使用这个接口进行网络请求的时候,没有自己implements MyService,直接如下调用即可:

    MyService myService = retrofit.create(MyService.class)
    myService.loadData(map);
    

    从上面的代码看,调用Retrofit对象的create方法即可创建一个MyService对象;其实也没啥神秘的地方,就是使用了动态代理模式:

    public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
    
              @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
                 
                ServiceMethod<Object, Object> serviceMethod =
                    (ServiceMethod<Object, Object>) loadServiceMethod(method);
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
      }
    

    看到了吧,在create方法中主要是Proxy.newProxyInstance动态创建了MyService的代理对象,在我们执行loadData的时候调用代理对象的invoke方法;通过invoke方法的第二个参数Method对象,我们可以拿到loadData方法配置的注解信息以及loadData方法的map参数的值,从而执行网络请求。只不过Retrofit又将method封装到了ServiceMethod里面而已。从Retrofit的例子中相信你们可以意识到了,<font color="#ff00ff">动态代理其实不需要宿主对象也能发挥其强大的作用</font>。在retrofit我们只是拿到了接口方法的Method对象,然后通过操控method对象获取注解以及参数信息来执行网络请求而已。关于Retrofit的更多细节,可以点此了解更多


    实际上动态代理理念的运用不仅仅体现在上面的Proxy和InvocationHandler接口。其思想运用的很广泛,比如android开发程序员EventBus,ButteKinfe等等,都是运用了注解+动态代理的思想;或者从某种程度来说EventBus和ButterKnife运用了AOP的思想。

    比如ButterKnife的运用,ButterKnife事先是不知道宿主对象是谁,在程序运行的时候需要拿到具体的宿主对象,然后横向切入到宿主代码中去,这其实就是动态代理和AOP思想的结合。

    还是用代码来说话,没使用ButterKnife的代码如下所示

    public class MyActivity extends Activity {
      
         TextView helloWoldTxtView;
    
         protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
           //初始化helloWoldTxtView
            helloWoldTxtView = findViewById(R.id.hello_world)
            helloWoldTxtView.setText("Hello  Android")
          }
    

    而使用了ButterKnife的代码则如下所示:

    public class MyActivity extends Activity {
         @BindView(R.id.hello_world)
         TextView helloWoldTxtView;
    
         protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
           //绑定butterknife
            ButterKnife.bind(this)
            //直接使用(看起来没有初始化)
            helloWoldTxtView.setText("Hello  Aop")
          }
    }
    

    从上面代码来看这就是代理模式的体现,比如把初始化控件的方法代理给了ButterKnife,由ButterKnife代劳android组件的初始化工作。那么ButterKnife是怎么做到的呢?其原理也很简单,在用apt或者annotationProcessor这种技术,在程序编译期间扫描程序,对标有@BindView注解的类进行扫描,然后生成一个Activity的代理类其命名规则为MyActivity_ViewBinding:

    public class MyActivity_ViewBinding implements Unbinder {
       //宿主类
        private MyActivity target;
        
          @UiThread
         public MyActivity_ViewBinding (MyActivity  target) {
            this(target, target.getWindow().getDecorView());
        }
        
          @UiThread
      public ActiveWebActivity_ViewBinding(ActiveWebActivity target, View source) {
        this.target = target;
        target.helloWoldTxtView= Utils.findRequiredViewAsType(source, R.id.helloWoldTxtView, "field 'helloWoldTxtView'", TextView.class);
      }
    
    }
    

    可以看出宿主组件helloWoldTxtView的初始化代理给了MyActivity_ViewBinding 这个类。该类调用 Utils.findRequiredViewAsType这个方法,其内部也即是调用了android的findViewById(R.id.helloWoldTxtView)方法来完成了初始化。
    从上面代码可以看出,编译期间动态生成的代码MyActivity_ViewBinding 有两个构造函数,那么什么时候初始化这个代理类呢?答案是调用ButterKnife.bind(this)的时候,会调用createBind方法:

    //target就是宿主Activity
      public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();
        return createBinding(target, sourceView);
      }
    

    所以进入createBinding里看看发生了什么:

      private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
        Class<?> targetClass = target.getClass();
        //根据Activity所在的class所在的路径拼接_ViewBinding查抄对应代理类的Class对象
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
        
        //代理类初始化
          return constructor.newInstance(target, source);
     
      }
    
     private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
       //获取你的Activity的class的路径
        String clsName = cls.getName();
    
       //拼接代理类所在的路径
          Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
         //获取代理类的构造函数
          bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
     
        return bindingCtor;
      }
    

    上面代码思路也很清晰:
    1、获取宿主类的Class对象
    2、根据该Class对象的 cls.getName()来拼接出编译期间动态生成的代理类的路径
    3、根据代理类所在的路径获取其构造器,然后初始化代理对象
    4、在构造器对android组件进行初始化


    到此为止,代理模式的分析讲解完毕。从中也体会到了注解+代理模式的强大作用。如有不当之处欢迎批评指正。

    相关文章

      网友评论

          本文标题:设计模式之代理模式

          本文链接:https://www.haomeiwen.com/subject/dckklltx.html