网上很多人在介绍AOP时都这样说:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。个人认为这句话是错误。AOP和OOP一样,是一种程序设计思想,而非技术手段。
程序设计有六大原则,其中第一原则就是 单一职责原则 。意思就是一个类只负责一件事情。这与OOP的封装特性相得益彰。在这个条件下,我们的程序会被分散到不同的类、不同的方法中去。这样做的好处是降低了类的复杂性,提高了程序的可维护性。但是同时,它也使代码变得啰嗦了。例如,我们要为方法添加调用日志,那就必须为所有类的所有方法添加日志调用,尽管它们都是相同的。为了解决上述问题,AOP应运而生了。
AOP旨在将 横切关注点 与业务主体进行分类,从而提高程序代码的模块化程度。横切关注点是一个抽象的概念,它是指那些在项目中贯穿多个模块的业务。上个例子中日志功能就是一个典型的横切关注点。
动态代理是一种设计模式。它有以下特征:
以下面这个例子为例,我们看一下动态代理的类图结构。
通常我们的APP都有一部分功能要求用户登录之后才能访问。如修改密码、修改用户名等功能。当用户打算使用这些功能时,我们一般要对用户的登录状态进行判断,只有用户登录了,才能正常使用这些功能。而如果用户未登录,我们的APP要跳转到登录页。就以修改密码为例我们看一下动态代理的类图。
InvocationHandler是Java JDK提供的动态代理的入口,用来对被代理对象的方法做处理。
代码如下:
public static class LoginCheckHandler implements InvocationHandler { private static <S, T extends S> T proxy(S source, Class<T> tClass) { return (T) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{tClass}, new LoginCheckHandler(source)); } private Object mSource; LoginCheckHandler(Object source) { this.mSource = source; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(!checkLogin()){ jumpToLoginActivity(); return null; } return method.invoke(mSource, args); } private boolean checkLogin(){ System.out.println("用户未登录"); return false; } private void jumpToLoginActivity(){ System.out.println("跳转到登录页"); } } public class Client { public static void main(String[] args) { IUserSetting source = new UserSetting(); IUserSetting iUserSetting = LoginCheckHandler.proxy(source,IUserSetting.class); iUserSetting.changePwd("new Password"); } } 复制代码
经过这样封装之后,检查登录跳转登录页的逻辑作为 横切关注点 就和业务主体进行了分离。当有新的需求需要登录检查时,我们只需要通过LoginCheckHandler生成新的代理对象即可。
APT(Annotation Processing Tool)是一种编译期注解处理技术。它通过定义注解和处理器来实现编译期生成代码的功能,并且将生成的代码和源代码一起编译成.class文件。通过APT技术,我们将 横切关注点 封装到注解处理器中,从而实现 横切关注点 与业务主体的分离。更详细的介绍请移步 Android编译期插桩,让程序自己写代码(一) 。
AspectJ就是一种编译器,它在Java编译器的基础上增加了关键字识别和编译方法。因此,AspectJ可以编译Java代码。它还提供了Aspect程序。在编译期间,将开发者编写的Aspect程序织入到目标程序中,扩展目标程序的功能。开发者通过编写AspectJ程序实现AOP功能。更详细的介绍请移步 Android编译期插桩,让程序自己写代码(二) 。
Transform是Android Gradle提供的,可以操作字节码的一种方式。App编译时,源代码首先会被编译成class,然后再被编译成dex。在class编译成dex的过程中,会经过一系列 Transform
处理。 Javassist/ASM 是一个能够非常方便操作字节码的库。我们通过它们可以修改编译的.class文件。利用这种方式,我们将 横切关注点 封装到Transform,来达到与业务主体分离的目的。更详细的介绍请移步 Android编译期插桩,让程序自己写代码(三) 。