AOP即面向切面编程,一种编程范式(思想),指导开发者如何组织程序结构。
作用:在不惊动原始设计的基础上为其进行功能的增强。具有无侵入式。
一些定义:
1:连接点:即原始方法,也就是需要进行功能增强的方法;
2:通知:共性的方法,也就是功能增强的具体内容;
3:通知类:专门写通知的类,通知(方法)写在这个类里面,这个类就称作为通知类了;
4:切入点:指明哪些方法需要进行功能增强;
5;切面:通知和切入点之间,把通知和切入点连接起来,指明该切入点对应哪一个通知。也就是 说:切面就是一个桥梁,把通知和切入点绑定起来了。
以SpringBoot举个简单例子:
第一步:导入AOP相关坐标:
<!-- 引入aop支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:定义连接点:待会需要对此进行功能增强(记得添加@Component注解)
@Component
public class ConnectionPoint {
//连接点(待会需要增强功能)
public void spring() {
System.out.println("spring is running...");
}
}
第三步:创建通知类,并在此类中添加通知(方法),即为功能增强的具体内容,添加切入点以及切面把连接点和通知连接起来。
@Pointcut("execution(void com.aurora.aop.ConnectionPoint.spring())")为定义切入点,其中的
void com.aurora.aop.ConnectionPoint.spring())为:指定为ConnectionPoint类下的spring()方法做
功能增强。
@Before("method()")为定义切面,注解下面方法的内容便是增强功能的具体内容(通知)。其中的method()就是切入点的名称,就是@Pointcut注解下面方法的名称。注意@Before为:在需要增强的方法(连接点)的前面执行该功能增强的具体内容(通知)。最后加上@Component和
@Aspect注解。@Aspect注解就会让spring在扫描到此通知类的时候,知道这个类是要做功能的增强。
@Component
@Aspect
//通知类 通知类中定义通知 通知:共性的功能
public class MyAdvice {
@Pointcut("execution(void com.aurora.aop.ConnectionPoint.spring())") //切入点:指明那些方法要进行增强功能
private void method(){}
@Before("method()") //切面:通知和切入点之间,把通知和切入点连接起来,指明该切入点对应哪一个通知
public void print() { //此方法为通知
System.out.println("public is running...");
}
}
最后在测试类中测试:
@SpringBootTest
class AopApplicationTests {
@Autowired
private ConnectionPoint connectionPoint;
@Test
void testAOP() {
connectionPoint.spring();
}
}
运行结果:
可以看到:原本的spring()方法只有System.out.println("spring is running..."),但是最后却在此之前添加了System.out.println("public is running...")。所以最后运行结果如上图所示。
小细节:
在springboot环境下,由于存在spring-boot-autoconfigure依赖,默认会注入AopAutoConfiguration配置类,该类的作用等同于@EnableAspectJAutoProxy注解,所以在这种情况下可以不加@EnableAspectJAutoProxy注解,AopAutoConfiguration可以通过spring.aop.auto属性控制;所以我们无需在启动类上加上@EnableAspectJAutoProxy注解。
接下来具体来学习AOP
一:切入点表达式:
就是上文中:@Pointcut("execution(void com.aurora.aop.ConnectionPoint.spring())")中的execution(void com.aurora.aop.ConnectionPoint.spring())。
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数名)异常名)
例如:
execution(public User com.aurora.service.UserService.findById(int))
动作关键字:描述切入点的行为动作,例如execution表示执行到指定的连入点(以后大部分用这个)。
其他的不在此赘述了。
此外,我们可以使用通配符描述切入点,达到快速描述的目的,以及冗余的描述。
*:单个字符的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现。
如:
execution(public * com.aurora.*.UserService.fing*(*))
表示:匹配com.aurora包下的任意包中的UserService类或接口中所有以find开头的带有一个参数的方法。
..:多个连续的字符,可以独立出现,常用于简化包名与参数的书写。
如:
execution(public User com..UserService.findById(..))
表示:匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法。
+:专用于匹配子类类型。
如:
execution(* *..*Service+.*(..))
表示:任意返回值,任意包下面以Service结尾的类或接口的子类的任意方法任意参数。
二:AOP通知类型
在前面我们使用的是@Before注解,但是还有其他注解代表着在原始方法的什么位置进行增强功能。定义切面的所有注解总共有5种:
1:前置通知(@Before):在原始方法被调用之前调用通知功能;
2:后置通知(@After):在原始方法完成之后调用通知,此时不会关心方法的输出是什么;
3:环绕通知(@Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。作为重点
4:返回通知(@After-returning):在目标方法成功执行之后调用通知;了解就好
5:异常通知(@After-throwing):在目标方法抛出异常后调用通知;了解就好
我们来重点学环绕通知(@Around)。
示例:
1:定义连接点:
@Component
public class ConnectionPoint {
//连接点(待会需要增强功能)
public void spring() {
System.out.println("spring is running...");
}
}
2:创建通知类,添加通知,添加切入点,添加切面。把连接点和通知连接起来
@Component
@Aspect
public class MyAdvice {
//切入点
@Pointcut("execution(void com.aurora.aop.ConnectionPoint.spring())")
private void method(){}
//切面
@Around("method()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around before..."); //通知
Object returnValue = proceedingJoinPoint.proceed();//表示对原始操作的调用
System.out.println("around after...");
return returnValue;//把原始方法的返回值,给返回回去
}
}
运行结果:
在原始方法前加了一句System.out.println("around before..."),在原始方法的后面加了一句System.out.println("around after..."),所以最后的运行结果为上图所示。
@Around注意事项
1:环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知;
2:通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行;
3:对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型;
4:原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object;
5:由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象;
深入了解ProceedingJoinPoint和JoinPoint。
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中。是
JoinPoint的基础上暴露出 proceed() 这个方法。proceed()很重要,作用就是调用原始方法。
Object returnValue = proceedingJoinPoint.proceed();//表示对原始操作的调用
proceed()方法的返回值就是原始方法的返回值。
通过JpointPoint对象可以获取到下面信息
1:返回目标对象,即被代理的对象
Object target = proceedingJoinPoint.getTarget();
2:获取连接点的所有参数名称
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();
3:获取连接点里面的所有参数值
Object[] args = joinPoint.getArgs();
4:获取连接点的签名
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
还有其他一些方法,以后遇到了在idea中很容易就能看到。
最后:贴下个人理解的AOP的关系图: