草庐IT

Android代理模式(静态代理,动态代理,Retrofit代理模式分析)

qfh-coder 2023-04-04 原文

文章目录

代理模式

前言:AOP编程(面向切面编程)

一. 由来

概念:面向切面编程是对面向对象继承体系的一个补充,它可以在水平方向上面做一些事情,从而完善整个java代码的设计体系。

我们看看下面这个图的分析:

上面这幅图抛出AOP,也就是水平方向上面做事情。下面我们就通过几个类写代码简单模拟一下这个过程。

声明一个A接口,B,C分别实现了A接口并且复写了test方法,所以B,C是在水平方向上的同级别的类,我们现在解决的问题就是在B,C的test的方法中加多一句代码System.out.println(“JAVA”);,但是前提是B,C的代码不能动,我们用过通过外部的一个方式来统一添加这句代码,为什么呢,因为假设不只是B,C实现了A接口,而是有很多个类都实现了A,那么同一水平方向上面的类数量很多,如果我们一个个去修改实现类里面的代码,那么工作量是很繁重的,所以我们应该通过外界封装一个类来完成这个需求。


public interface A {
    void test();
}

public class B implements A{
    @Override
    public void test() {
        System.out.println("B");
    }
}

public class C implements A{
    @Override
    public void test() {
        System.out.println("C");
    }

}

这是我们根据上面的分析定义的额外的一个Uils类,来看看怎么搞。

public class Utils {
    //定义了一个接口变量的引用
    A a;
	//通过构造方法赋值给引用,这里是多态的性质,传入的是A的实现类对象
    public Utils(A a) {
        this.a = a;
    }
	//在这里我们实现上面的需求,通过这个方法我们就可以统一的在B或者C或者其他实现类中增加代码而不用手动单独去到某个实现类里面去修改代码。
    void abc() {
        System.out.println("JAVA");
        a.test();
    }

}

class Test {
    public static void main(String[] args) {
        //这里我们调用测试C类,
        C c = new C();
        //传入c对象,赋值给引用A a,所以此时a对象指向c对象的地址
        Utils utils = new Utils(c);
        utils.a = c;
        
        //调用方法测试, 里面a.test();就会调用到c的test方法中,同理我们也可以实例化出B对象给A a引用赋值。
        utils.abc();
    }
}

//输出 这样就可以打印出字符串JAVA并且在没有修改C类代码的情况下,B类实现也是如此。
JAVA
C

小结:从上面的代码和现象可以看出,Utils这个类持有一个A a的引用,那么我们就可以对A接口下面的水平方向的对象进行插入或者植入代码,如果我们仔细观察,其实发现Utils和A毫无关系,只是持有A的引用罢了。

接下来我们抛出一个问题,

假设我们现在又有一个X类,里面有个specialX方法,里面再调用a.test(),传入不同的A的子类对象进来,那么它所完成的功能是不一样的。那么假设我又希望在调用a.test()的之前输出一句System.out.println(“JAVA”);跟上面我们说的一样的需求

public class X {
    //X类里面独有的方法
    void specialX(A a) {
        a.test();
    }
}

//原本我们写好的Utils类是没办法满足我们的需求的,因为Utils和A接口没有任何的继承关系,所以无法传到specialX(A a)中。
public class Utils {
    A a;

    public Utils(A a) {
        this.a = a;
    }

    void abc() {
        System.out.println("JAVA");
        a.test();
    }

}


class Test {
    public static void main(String[] args) {
        C c = new C();
        Utils utils = new Utils(c);
        utils.a = c;
        utils.abc();
    }
}

接下来我们改写一下Utils类看看,

//我们这里也让Utils实现A接口,复写test方法
public class Utils implements A {
    A a;

    @Override
    public void test() {
        System.out.println("JAVA");
        a.test();
    }
}

class Test {
    public static void main(String[] args) {
        C c = new C();
        Utils utils = new Utils(c);
        utils.a = c;
		
        //此时我们已经可以把Utils的对象传进去了,specialX的 a.test();就会根据传入的A的子类去执行对应子类的test方法,同时可以扩展一些新的业务功能
        X x = new X();
        x.specialX(utils);
    }
}


那么这里我们就引出代理思想,Utils是代理类,它所生成的utils就是代理对象。

一. 代理思想

为真实对象提供代理,然后供其他对象通过代理访问真实对象,真正做事情的还是代理对象去执行对应的代码。

1. 静态代理

其实我们上面的例子就是一个简单的静态代理的模型,再回顾一下我们上面的知识,如果我们要代理A接口中水平方向上的对象。步骤:写一个类,实现A接口,复写A接口抽象方法,在这个类中写一个接口类型的变量引用,然后利用这个引用去调用接口中的方法,那么对应执行的就是其传进来的实现子类的方法。

这里我们思考一个问题,假设我们要代理不同接口的对象呢,那我们该怎么办, 假设我们现在又有一个O接口,这样我们也可以在O接口的水平方向的子类去进行代理了。

public interface O {
    void testO();
}
//还是拿我们上面的Utils,再实现一个O接口
public class Utils implements A, O {
    A a;
	//o的变量
    O o;

    public Utils(A a) {
        this.a = a;
    }

    @Override
    public void test() {
        System.out.println("JAVA");
        a.test();
    }

    @Override
    public void testO() {
        //这里写我们需要添加的前置代码
        //调接口方法, 
		o.test();
        //这里写我们需要添加的后置代码
    }
}

class Test {
    public static void main(String[] args) {
        C c = new C();
        Utils utils = new Utils(c);
        utils.a = c;

        X x = new X();
        x.specialX(utils);
    }
}

缺点:这么做法还是有一定的局限性的,Utils是我们的代理类,当我们需要代理某个接口的水平方向上的子类对象时候,就需要去实现对应的接口,而且还要复写很多的方法和代码,对于编程的设计思想来说尽量不要去动已经定义好的类,不要试图去修改一个类,可以添加类,但尽量不要修改一个类。

我们上面的Utils实现了两个接口,显示这是不好的,我们代理A用一个代理类,如果还需要代理O,那么我们再新建一个O的代理类即可,如下

public class UtilsO implements O {
    O o;

    @Override
    public void testO() {

    }
}

所以呢静态代理,我们想要代理哪个接口就去新建一个对应的类,实现对应的接口,持有一个代理接口类型的变量,重写接口中需要代理的方法,将需要插入的代码写在里面。

2. 动态代理

静态代理我们也看到了它的缺点,每代理一个接口就要额外定义一个类,导致类的数目不断增加。

下面我们看看动态代理。我们先回顾一下java的知识,我们每新建一个类它都是.java类型的文件通过javac命令编译成了.class文件,通过IO流的操作,从硬盘中把.class文件加载到内存中,变成一个Class对象,动态代理其实就是JDK给我们提供好的。

3. 动态代理的实现

– 我要代理哪个接口

– 被代理对象是谁

– 插入的代码是什么

public class Student implements Person{
    @Override
    public void test() {
        System.out.println("Student");
    }
}

public class Teacher implements Person{
    @Override
    public void test() {
        System.out.println("Teacher");
    }
}

public class A implements InvocationHandler {
    //这个引用会和静态代理一样指向我们的要代理的真实对象
    Object object;

    //这个地方写我们需要额外插入的代码
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("java前");
        //首先我们要先调用一下被代理对象的被代理方法。
        method.invoke(object, args);
        System.out.println("java后");
        return null;
    }
}


public interface Person {
    void test();
}

class Test {
    public static void main(String[] args) {
        //第一步先生成我们想代理的对象,我们想代理的这个对象是Student
        Student student = new Student();
        //A是我们的代理类,生成我们的代理对象
        A a = new A();
        //引用指向我们的被代理对象student
        a.object = student;
        //第一个参数写被代理对象的Classloader
        //p指向Proxy.newProxyInstance所生成的对象,它是代理的接口所生成的对象
        Person p = (Person) Proxy.newProxyInstance(student.getClass().getClassLoader(), student.getClass().getInterfaces(), a);
       //访问的是代理接口的那个test方法
        p.test();
    }
}

输出
java前
Student
java后



代理的方法一定要属于某个接口的。

二. Retrofit代理模式分析

Retrofit其实就是对Okhttp的一次封装,先回顾一下Okhttp的用法。

public class MainActivity extends AppCompatActivity {
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.tv_view);
        OkHttpClient okHttpClient = new OkHttpClient();
        RequestBody body = new FormBody.Builder().add("InfoOne", textView.getText().toString()).build();
        final Request request = new Request.Builder().url("https//www.baidu.com/").post(body).build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()){
                    String string = response.body().toString();

                }
            }
        });
    }
}

接下来我们看Retrofit,其实它最终也是通过Okhttp来发送网络请求,只不过在外面加多了一层封装,更好的提升了代码的复用性,扩展性。

Retrofit的流程

  1. 首先会定义好一个接口层,代理前统一处理:把接口解析成okhttprequest,这个解析的过程就用到了我们的动态代理,通过invoke方法将它们解析成不同的call,可以访问网络,然后就丢给Okhttp的网络请求类,

我们进入到retrofit.create方法里面,

//这里其实就用到了我们的动态代理模式,
public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(service);
  }
   //这里就构建出了一个动态的代理类,在我们的代码运行之前是不存在的,调用之前.class字节码文件是未生成的,通过JDK动态生成
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
      new InvocationHandler() {
        private final Platform platform = Platform.get();
        private final Object[] emptyArgs = new Object[0];

        @Override public @Nullable Object invoke(Object proxy, Method method,
            @Nullable Object[] args) throws Throwable {
          // If the method is a method from Object then defer to normal invocation.
          if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
          }
          if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args);
          }
          return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
        }
      });
}


//这个接口是我们自定义的,不是源码,在调用creat的时候就会将这个接口里面的参数进行收集,然后通过一个统一的方式去构建出一个okhttp的request,最终构建出我们的Call对象
public interface Github {
    @GET("/repos/{owner}/{repo}/contributor")
    Call<List<Contributor>> contributors(@Path("owner") String owner, @Path("repo") String repo);
}


public class MainActivity extends AppCompatActivity {
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.tv_view);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://www.baidu.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        Github github = retrofit.create(Github.class);
        //这里构建出call对象
        Call<List<Contributor>> call = github.contributors("Square", "retrofit");
        try {
            List<Contributor> contributors = call.execute().body();
            for (Contributor contributor : contributors) {
                
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

小结:为什么它用到代理模式呢,因为retrofit自己本身是不做什么事情的,最后的请求网络还是由okhttp完成,okhttp这里可以看成是委托类,所以它是一个代理类。为什么它不用静态代理而用动态代理呢,因为请求网络会有各种各样的接口,如果一个个的去写对应接口的代理类,那么数量就会很多,代码量就会很大,

优点:

• 可以配置不同HTTP client来实现网络请求,如okhttp、httpclient等;

• 请求的方法参数注解都可以定制;

• 支持同步、异步和RxJava;

• 超级解耦;

• 可以配置不同的反序列化工具来解析数据,如json、xml等;

   }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}


小结:为什么它用到代理模式呢,因为retrofit自己本身是不做什么事情的,最后的请求网络还是由okhttp完成,okhttp这里可以看成是委托类,所以它是一个代理类。为什么它不用静态代理而用动态代理呢,因为请求网络会有各种各样的接口,如果一个个的去写对应接口的代理类,那么数量就会很多,代码量就会很大,

优点:

•  可以配置不同HTTP client来实现网络请求,如okhttp、httpclient等;

•  请求的方法参数注解都可以定制;

•  支持同步、异步和RxJava;

•  超级解耦;

•  可以配置不同的反序列化工具来解析数据,如json、xml等;

•  使用非常方便灵活;

有关Android代理模式(静态代理,动态代理,Retrofit代理模式分析)的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  6. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  7. ruby - 在 Ruby 中动态创建数组 - 2

    有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.

  8. ruby - 是否可以将 IRB 提示配置为动态更改? - 2

    我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO

  9. ruby - HTTP 请求中的用户代理,Ruby - 2

    我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)

  10. ruby-on-rails - capybara poltergeist - 覆盖用户代理 - 2

    有人知道如何将capybarapoltergeist的用户代理覆盖到移动用户代理以进行测试吗?我发现了一些有关为seleniumwebdriver配置它的信息:http://blog.plataformatec.com.br/2011/03/configuring-user-agents-with-capybara-selenium-webdriver/这在capybara闹鬼中怎么可能? 最佳答案 请参阅poltergeistgithub页面上的链接:https://github.com/teampoltergeist/polte

随机推荐