一、为什么要用AOP?什么是AOP?
为什么要用AOP?
面向对象的特点是继承、封装和多态,而封装要求将功能分散到不同的对象中,伴随着业务系统越来越复杂,我们的核心业务中会参杂一些特殊业务,比如日志记录、权限验证、性能检测、错误检测等,这些外围操作会带来核心业务代码混乱、重复冗余逻辑散布、代码扩展困难等种种问题。
我们希望这些模块能够具有热插拔特性,而不需要将这些外围代码入侵到核心模块中,假如把日志、权限、性能监测、错误检测等外围业务看作单独的关注点,每个关注点都可以在需要的时候被运用而且无需整合到核心代码中,如下图,每个关注点与核业务模块分离,作为单独的功能,横切几个核心业务模块,核心模块只需关注自己相关的业务。这些关注点叫横切关注点,这种技术叫面向切面编程,即AOP(Aspect Oriented Program)。
什么是AOP?
AOP从本质上来说是将那些重复的代码从业务逻辑中剥离出来,独立封装成一个类和若干方法,这样剥离出来的类和方法称为横切逻辑和增强,剥离后的业务逻辑的类和方法称为目标逻辑,剥离的位置称为切点。此外,增强有不同的增强类型,AOP编程就是在正确的切点使用正确的增强类型,将横切逻辑织入目标逻辑中。
什么是AOP的静态织入与动态织入?
在AOP中,将切面(aspect)应用到目标函数(类)的过程叫做织入,织入的过程分为两类:静态织入和动态织入。动态织入的方式是在运行时动态地将要增强的代码织入到目标类中,一般通过动态代理技术完成,如Java JDK动态代理或CGLIB动态代理,Spring AOP就采用这种动态代理技术;静态织入是在编译期将代码织入到目标类中,AspectJ采用静态织入方式,AspectJ的织入依赖acj编译器。
二、AspectJ AOP Demo
AspectJ是一个Java实现的AOP框架,也是目前实现AOP框架中最成熟、功能最丰富的语言,AspectJ语言与Java完全兼容,AspectJ编译器能够对Java代码进行AOP编译,让Java代码具有AOP功能,这个过程一般是在编译期进行。此外,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java语言,只需加上AspectJ注解。因此使用AspectJ有两种方式:1.完全使用AspectJ语言;2.使用纯Java语言,然后使用AspectJ注解。
AspectJ AOP简单Demo
创建maven项目并引入AspectJ依赖包
...
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
...
同时引入AspectJ maven编译插件(注:不引入无法正确变异AspectJ文件)
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.8</version>
</plugin>
</plugins>
</build>
...
创建测试类,即目标逻辑
public class AspectJDemo {
public void sayHello() {
System.out.println("hello world");
}
public Object doErr(Integer i) {
if(i.equals(0)) {
}
return null;
}
public static void main(String ... args) {
AspectJDemo demo = new AspectJDemo();
demo.sayHello();
System.out.println(demo.doErr(1));
}
}
使用AspectJ语言创建横切逻辑(注:引入AspectJ包后才能创建),AspectJ类后缀为.aj
/**
* 记录日志切面
*/
public aspect AspectJRecordLog {
/**
* 定义切点:日志记录切点
*/
pointcut recordLog() : call(public * sayHello(..));
/**
* 前置通知
*/
before() : recordLog() {
System.out.println("before record log");
}
/**
* 后置通知
*/
after() : recordLog() {
System.out.println("after record log");
}
}
/**
* 异常处理切面
*/
public aspect AspectHandleErr {
/**
* 处理异常
*/
pointcut handleErr() : call(public * com.xiaofan.aop.AspectJDemo.doErr(..));
/**
* 环绕通知
*/
Object around() : handleErr() {
try {
System.out.println("before proceed");
Object result = proceed();
System.out.println("after proceed correctly");
return Integer.MAX_VALUE;
} catch (Throwable e) {
System.out.println("proceed and catch error");
}
return null;
}
}
使用Java语言,通过注解实现横切逻辑:
@Aspect
public class AspectJRecordLogJava {
@Before("execution(* sayHello(..))")
public void beforeSayHello(){
System.out.println("before record log1");
}
@After("execution(* sayHello(..))")
public void afterSayHello() {
System.out.println("after record log");
}
}
@Aspect
public class AspectJHandleErrJava {
@Around("execution(* doErr(..))")
public Object aroundDoErr(ProceedingJoinPoint joinPoint) {
try {
System.out.println("before proceed");
System.out.println("joinPoint args : " + joinPoint.getArgs()[0]);
System.out.println("joinPoint target : " + joinPoint.getTarget().getClass());
Object result = joinPoint.proceed();
System.out.println("after proceed correctly");
return Integer.MAX_VALUE;
} catch (Throwable e) {
System.out.println("proceed and catch error");
}
return null;
}
}
运行测试类,返回结果
before record log1
hello world
after record log
before proceed
joinPoint args : 1
joinPoint target : class com.xiaofan.aop.AspectJDemo
after proceed correctly
2147483647
AspectJ AOP结合Annotation
创建测试注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface TestAnnotation {
}
创建横切面。注意这里需要制定 “execution(* *(..))”,让增强方法作用于注解方法本身,否则该增强方法将被同时织入到注解方法和调用点,导致重复织入问题(可反编译对比横切效果)。
@Aspect
public class AspectJAnnotation {
@Before("execution(* *(..)) && @annotation(com.xiaofan.aop.TestAnnotation)")
public void testAnnotation() {
System.out.println("before test annotation");
}
}
创建测试目标逻辑:
public class AspectJDemo {
@TestAnnotation
public void sayHello() {
System.out.println("hello world");
}
public static void main(String ... args) {
AspectJDemo demo = new AspectJDemo();
demo.sayHello();
}
}
测试输出
before test annotation
hello world