AOP详解
1:AOP:AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
它提倡的是针对同一类问题的统一处理,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2:AOP主要应用场景
日志记录,性能统计,异常处理等等。
日志处理:
在不知道AOP的情况下,一般的处理都是:先设计一个日志输出模块,这个模块提供日志输出API,比如Android中的Log类。然后,其他模块需要输出日志的时候调用Log类的几个函数,比如e(TAG,...),w(TAG,...),d(TAG,...),i(TAG,...)等。
从OOP角度看,除了日志模块本身,其他模块大部分情况下应该都不会包含日志输出功能。
随意加日志输出功能,使得其他模块的代码和日志模块耦合非常紧密。而且,将来要是日志模块修改了API,则使用它们的地方都得改。
- 第一,我们要认识到OOP世界中,有些功能是横跨并嵌入众多模块里的,比如打印日志,比如统计某个模块中某些函数的执行时间等。这些功能在各个模块里分散得很厉害,可能到处都能见到。
- l 第二,AOP的目标是把这些功能集中起来,放到一个统一的地方来控制和管理。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。比如我们可以设计两个Aspects,一个是管理某个软件中所有模块的日志输出的功能,另外一个是管理该软件中一些特殊函数调用的权限检查。
AOP解决了此问题,AOP是一种新的编程思想,是OOP的一种补充; OOP专心负责核心业务,AOP负责其它杂七杂八的业务
3:各个时期的AOP:
4:AOP分为静态AOP和动态AOP
1)静态AOP:APT和AspectJ(静态代理)
AspectJ 意思就是Java的Aspect,Java的AOP。AspectJ属于静态AOP,它是在编译时进行增强,会在编译时期将AOP逻辑织入到代码中。
由于是在编译器织入,所以它的优点是不影响运行时性能,缺点是不够灵活。
AspectJ框架用法:在所有的方法开始和结束的时候加上日志,以及方法耗时统计:
// 使用@Aspect注解
描述一个切面
,使用该注解修饰的类会被AspectJ编译器
识别为切面类
@Aspect
public class LoggingAspect {
private static final String TAG = "LoggingAspect";
// 在 Application 类中的所有方法上添加切入点
@Pointcut("within(android.app.Application+) && execution(* *(..))")
public void applicationMethods() {}
// 在方法执行前添加日志
@Before("applicationMethods()")
public void beforeMethodExecution(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Log.d(TAG, "Before method: " + methodName);
}
// 在方法执行后添加日志
@After("applicationMethods()")
public void afterMethodExecution(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Log.d(TAG, "After method: " + methodName);
}
// 使用 Around 操作,在方法执行前后添加日志
@Around("applicationMethods()")
public Object aroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Log.d(TAG, "Before method (Around): " + methodName);
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
Log.d(TAG, "Method " + methodName + " executed in " + executionTime + " ms (Around)");
Log.d(TAG, "After method (Around): " + methodName);
return result;
}
}
基于AspectJ的框架Hugo:
@DebugLog public String getName(String first, String last) { SystemClock.sleep(15); return first + " " + last; }
运行程序会在控制台会打印函数耗时日志
⇢ getName(first="Jack", last="James")
⇠ getName [16ms] = "Jack James"
动态AOP:
1)JDK动态代理(反射机制),使用InvocationHandler接口:
缺点:反射,影响性能;被代理类必须要实现一个接口。
举例:
接口Calculator:
public interface Calculator { int execute(int a, int b); }
被代理对象:
public class AddCalculator implements Calculator { @Override public int execute(int a, int b) { return a+b; } }
代理过程:
public class CalculatorInvocationHandler<T> implements InvocationHandler { T target; public CalculatorInvocationHandler(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before read"); Object result = method.invoke(target, args); System.out.println("After reading"); return result; } }
用法:
public class ProxyTest { public static void test() { Calculator addCalculator = new AddCalculator(); InvocationHandler calHandler = new CalculatorInvocationHandler<Calculator>(addCalculator); Calculator calculatorProxy = (Calculator) Proxy.newProxyInstance(Calculator.class.getClassLoader(), new Class<?>[]{Calculator.class}, calHandler); calculatorProxy.execute(2, 1); } }
为什么动态代理只能代理实现了接口的类:
动态代理的工作原理是:当客户端调用代理对象的方法时,实际上会触发代理对象的 InvocationHandler 中的 invoke 方法。在这个方法中,可以使用反射等方式调用被代理类的相应方法。因此,代理对象需要持有被代理类的实例,以便在 invoke 方法中能够调用被代理类的方法。
由于动态代理需要持有被代理类的实例,并在代理过程中调用被代理类的方法,这就要求被代理类需要实现一个共同的接口。这个共同的接口定义了被代理类的方法签名,使得动态代理能够通过接口来调用被代理类的方法。
2)动态字节码生成GCLib
CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。
在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中,没有接口也可以织入,但扩展类的实例方法为final时,则无法进行织入。比如Cglib
CGLIB
是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。
3)自定义类加载器
在运行期,目标加载前,将切面逻辑加到目标字节码里。如:Javassist
Javassist
是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。
4)ASM
ASM可以在编译期直接修改编译出的字节码文件,也可以像Javassit一样,在运行期,类文件加载前,去修改字节码。
AOP相关工具对比:
https://blog.csdn.net/u011101777/article/details/121190881