一、什么是AOP
关于OOP
大家对于OOP(面向对象编程)肯定不会陌生,核心思想为:万事万物皆对象,OOP推荐开发者把所有的事物当作对象来处理。
假设有一条鱼,我们就可以创建一个Fish类并构造出它的实例来当作是这条鱼并对它进行操作。OOP就是我们把鱼抽象成为Fish类的过程,通过这个过程我们可以获得更加清晰搞笑的逻辑单元划分。
关于AOP
AOP(面向切面编程),这个中文名字翻译的很到位:切面,可以脑部一下假设有一个平面,然后用一个工具去切这个平面,就形成了一个切面的过程。这个平面就是我们的程序,程序里面会有很多功能,像:登录,设置,支付等。假设有一个工具它的作用是检查是否登录,在程序每次支付时要检查是否登录,这个工具就可以像一把刀一样切向支付前的位置,这整个过程就是一个面向切面的过程。
如果你理解了的话其实可以发现OOP和AOP有着本质上的差异。AOP面对的是处理某个过程或步骤,会有切入点,会有切入的动作。然而OOP是一个帮助我们更加清晰搞笑划分事物单元的一种思想。
二、关于AOP的例子
一般我们听到AOP这个名字是在Java后台开发,后台所使用的spring框架一个很核心点就是AOP,通过AOP来解耦。我们作为Android开发人员为什么要去学习这个呢?AOP作为一种编程思想,作用的场景是不区分安卓和后台的,就像各种设计模式,用在合适的地方就能解决痛点。
现在有一个程序,有一个人类的接口,里面有一个方法sayHello()
public interface Person {
void sayHello();
}
和它的实现类:男人
public class Man implements Person {
@Override
public void sayHello() {
Log.i("AopLog", "你好,Android");
}
}
然后我们有3个界面,分别是FirstActivity、SecondActivity、ThirdActivity,3个页面中只有同一个操作,就是让一个男人sayHello
public class FirstActivity extends AppCompatActivity {
Button mSayHello;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_frist);
mSayHello = findViewById(R.id.sayHello);
mSayHello.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
test();
}
});
}
public void test() {
Person man = new Man();
man.sayHello();
}
}
点击按钮输出的结果为:
你好,Android
其实我们只需要关注3个页面中的test()方法就好了。现在我想要在sayHello的前后分别加一行分割线,让输出的结果变成这样:
---------我是上分割线---------
你好,Android
---------我是下分割线---------
最简单的办法就是直接修改test()方法,像这样:
public void test() {
Log.i("AopLog", "---------我是上分割线---------");
Person man = new Man();
man.sayHello();
Log.i("AopLog", "---------我是下分割线---------");
}
很好,3个页面,代码要复制3遍,如果有100个这样的界面,那有得你爽。有人会说在Man的sayHello方法里面加,像这样:
public class Man implements Person {
@Override
public void sayHello() {
Log.i("AopLog", "---------我是上分割线---------");
Log.i("AopLog", "你好,Android");
Log.i("AopLog", "---------我是下分割线---------");
}
}
虽然不用复制代码了,但是这就违背了面向对象的思想了,sayHello()方法中做的事情就只有打印”你好,Android”,并不需要包括打印下划线,所以加在这个位置很明显也是不合适的。
静态代理
这个时候我们想到了静态代理模式,可以创建一个代理对象来帮我们完成:
public class PersonProxy implements Person {
private Person mMan;
public PersonProxy(Person man) {
this.mMan = man;
}
@Override
public void sayHello() {
Log.i("AopLog", "---------我是上分割线---------");
mMan.sayHello();
Log.i("AopLog", "---------我是下分割线---------");
}
}
修改一下test()方法
public void test() {
Person man = new Man();
PersonProxy personProxy = new PersonProxy(man);
personProxy.sayHello();
}
让代理对象来处理,这样就变得比较优雅了,输出结果很明显是:
---------我是上分割线---------
你好,Android
---------我是下分割线---------
使用静态代理来解决已经可以比较好的解决这个问题了,但是我们并不会轻易满足。假设现在又有2个新的接口Car、Computer和他们的实现类
public interface Car {
void run();
}
public class BmwCar implements Car {
@Override
public void run() {
Log.i("AopLog", "我是一辆宝马骑车,我在快速行驶中");
}
}
public interface Computer {
void caculate();
}
public class HuaShuoComputer implements Computer {
@Override
public void caculate() {
Log.i("AopLog", "我是华硕电脑,我善于计算,10 + 10 = 20");
}
}
要求在Car的run()方法前后 和 Computer的caculate()方法前后都加上下划线。如果这里还要使用静态代理的话就需要创建3个静态代理对象了:PersonProxy、CarProxy、ComputerProxy,这样就非常浪费资源了。
动态代理
我们都知道通过反射可以获取到一个对象中的大部分信息,同时,Java还提供了动态代理方式。主要有一个Proxy类和一个InvocationHandler接口,通过他们可以生成动态代理类或动态代理对象。
一般的动态代理方式
假设为Person创建动态代理,首先我们要新建一个PersonInvocationHandler实现InvocationHandler类
public class PersonInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("AopLog", "动态代理啦啦啦");
return method.invoke(new Man(), args);
}
}
使用起来也很简单:
public void test() {
PersonInvocationHandler handler = new PersonInvocationHandler();
Person manProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
new Class[]{Person.class},
handler);
manProxy.sayHello();
}
输出接口为:
动态代理啦啦啦
你好,Android
这里要注意2点:
1. 动态代理所有的方法都会经过过调用invoke方法,也就是说如果Person接口中还有另外一个方法的话,当这个方法运行时也会打印一行日志”动态代理啦啦啦”;
2. 动态代理只能使用接口,Proxy.newProxyInstance()方法中第二个参数只能传入接口,这也就是作者为什么要创建Person、Car、Computer这3个没啥用的接口,而不是直接使用他们的实现类。
以上就是动态代理的基本使用方式了,但是上面的代码也只能支持Person的动态代理,还不够,还需要再稍微封装一下来适配Car、Computer或者更多的接口。
让我们新建一个MyInvocationHandler:
public class MyInvocationHandler implements InvocationHandler {
private Object mObject;
public MyInvocationHandler(Object target) {
this.mObject = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("AopLog", "---------我是上分割线---------");
Object result = method.invoke(mObject, args);
Log.i("AopLog", "---------我是下分割线---------");
return result;
}
}
然后再创建一个MyProxy:
public class MyProxy {
public static Object getProxy(Object target) {
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
myInvocationHandler);
}
}
使用:
public void test() {
Man man = new Man();
Person manProxy = (Person) MyProxy.getProxy(man);
manProxy.sayHello();
Computer computer = new HuaShuoComputer();
Computer computerProxy = (Computer) MyProxy.getProxy(computer);
computerProxy.caculate();
Car car = new BmwCar();
Car carProxy = (Car) MyProxy.getProxy(car);
carProxy.run();
}
输出结果:
---------我是上分割线---------
你好,Android
---------我是下分割线---------
---------我是上分割线---------
我是华硕电脑,我善于计算,10 + 10 = 20
---------我是下分割线---------
---------我是上分割线---------
我是一辆宝马骑车,我在快速行驶中
---------我是下分割线---------
很好,大功告成,现在无论是用Person、Car、Computer还是其他更多的接口,都给他们加上了下划线。
总结
- 关于AOP的理解,面向切面编程,结合文章的例子,用一张图来表示一下:
- 静态代理的使用和动态代理的使用
- 文章想要讲清楚AOP的思想,希望不要纠结于动态代理的代码中去。。。
- 面向切面编程可以做很多事情,比如:在多个Activity的跳转过程中要检查登录与否,这个跳转可能在Activity的任何一个方法中。这就需要用到第三方AOP框架了。
- 谢谢大家阅读