设计模式之代理模式

说道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();

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

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


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

public interface MyService{
    @FormUrlEncoded
    @POST("you url")
    Call  loadData(@FieldMap Map params);
}

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

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

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

public  T create(final Class service) {
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service },
        new InvocationHandler() {

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
             
            ServiceMethod serviceMethod =
                (ServiceMethod) loadServiceMethod(method);
            OkHttpCall 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的例子中相信你们可以意识到了,动态代理其实不需要宿主对象也能发挥其强大的作用。在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 constructor = findBindingConstructorForClass(targetClass);
    
    //代理类初始化
      return constructor.newInstance(target, source);
 
  }

 private static Constructor findBindingConstructorForClass(Class cls) {
   //获取你的Activity的class的路径
    String clsName = cls.getName();

   //拼接代理类所在的路径
      Class bindingClass = Class.forName(clsName + "_ViewBinding");
     //获取代理类的构造函数
      bindingCtor = (Constructor) bindingClass.getConstructor(cls, View.class);
 
    return bindingCtor;
  }

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


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

你可能感兴趣的:(设计模式之代理模式)