一. 由来
概念:面向切面编程是对面向对象继承体系的一个补充,它可以在水平方向上面做一些事情,从而完善整个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就是代理对象。
为真实对象提供代理,然后供其他对象通过代理访问真实对象,真正做事情的还是代理对象去执行对应的代码。
其实我们上面的例子就是一个简单的静态代理的模型,再回顾一下我们上面的知识,如果我们要代理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() {
}
}
所以呢静态代理,我们想要代理哪个接口就去新建一个对应的类,实现对应的接口,持有一个代理接口类型的变量,重写接口中需要代理的方法,将需要插入的代码写在里面。
静态代理我们也看到了它的缺点,每代理一个接口就要额外定义一个类,导致类的数目不断增加。
下面我们看看动态代理。我们先回顾一下java的知识,我们每新建一个类它都是.java类型的文件通过javac命令编译成了.class文件,通过IO流的操作,从硬盘中把.class文件加载到内存中,变成一个Class对象,动态代理其实就是JDK给我们提供好的。
– 我要代理哪个接口
– 被代理对象是谁
– 插入的代码是什么
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其实就是对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的流程
我们进入到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等;
• 使用非常方便灵活;