31.从零开始学springboot-再谈切面“AOP”
前言
说起Java,就不得不提Spring,提到Spring,就不得不提IOC(控制反转)和AOP(切面), 本章就详细介绍一下AOP(切面)思想以及它在Spring中的应用.
WechatIMG2.jpeg
概念
我们先看看百度此条对AOP的解释
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。
这样的描述还是过于空泛,我们简单的举个例子,帮助大家理解.
案例讲解
假设我们需要实现一个日志记录的功能.大家会怎么实现呢?
在AOP之前无非是两种:
方案一:
在每个业务的节点加入日志记录的代码.
//实例
业务一
public String ServiceA(){
//记录日志的实现1
}
业务二
public String ServiceB(){
//记录日志的实现2
}
……
方案二:
写一个通用的日志模块,在每个需要用到的业务节点调用该日志功能.
//实例
//日志模块
public class LogUtils {
public void log(String data){
//记录日志
}
}
//业务一
public String ServiceA(){
LogUtils.log();
}
//业务二
public String ServiceB(){
LogUtils.log();
}
我们仔细看方案一和二,不难看出,方案一就是面向过程的一种编程思想,而方案二就是OOP(面向对象)的一种编程思想.
方案一的做法会使得代码非常冗余,而且后续日志调整,每处都要调整,十分不好维护.
方法二比方法一好了一点,至少调整日志时只需调整一处,但是还是避免不了代码过于分散的问题,比如,需要在新的业务节点加上日志,就不得不添加新的调用代码.不过,方案二的这种把“公用”方法提取出来作为一个单独模块的思想,已经有了点AOP的思想了.
那么,有没有更好的做法呢 ?
有没有一种可以不增加新的“调用代码”(无需修改代码),就能在新的业务节点上输出日志的方法呢?
下面,我们来仔细聊聊AOP究竟是怎么一回事.
几个概念
为了更好的理解AOP,我们先抛出几个概念.
-
Advice(增强)
Advice定义了在PointCut里面定义的程序点具体要做的操作,它通过Before、After和Around来区别是在每个JoinPoint之前、之后还是代替执行的代码。
通知定义了切面是什么以及何时调用,何时调用包含以下几种类型- Before 在方法被调用之前调用通知
- After 在方法完成之后调用通知,无论方法执行是否成功
- After-returning 在方法成功执行之后调用通知
- After-throwing 在方法抛出异常后调用通知
- Around 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
总结: Advice就是你想要的功能,可以是日志/验证等等,同时Advice可以定义在PointCut的什么位置触发.
- JoinPoint(连接点)
JoinPoint就是程序执行的某个特定的位置,如:类开始初始化前、类初始化后、类的某个方法调用前、类的某个方法调用后、方法抛出异常后等.Spring只支持类的方法前、后、抛出异常后的连接点.
总结: JoinPoint就是允许你使用Advice的地方.每个类中的每个方法都是一个JoinPoint,假设一个类有3个方法,那它就有3个JoinPoint.
- PointCut(切点)
表示一组JoinPoint,这些JoinPoint或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的Advice将要发生的地方。
总结: PointCut是用来筛选JoinPoint的(你可以假设成mysql的查询条件). 假设你的一个类里,有增删改查4个方法,那就有4个连接点,但是你并不想在所有方法都使用Advice,你只是想让其中几个,比如只想在insert方法执行的前后进行日志记录等Advice操作,那么就用PointCut来定义这几个方法,让PointCut来筛选JoinPoint,选中那几个你想要的方法。
- Aspect(切面)
Aspect声明类似于Java中的类声明,在Aspect中会包含着一些PointCut以及相应的Advice。
- Target(目标对象)
织入Advice的目标对象。
总结:Target就是我们需要对它进行Advice的业务类,如果没有AOP,那么该业务类就得自己实现需要的功能。
- Weaving(织入)
将Advice添加到Target类具体JoinPoint上的过程
通过以上几个概念,我想大家对AOP已经有了一下感悟,下面,我们就这些概念结合实例再来归纳一下.
实例讲解
假设我们需要对业务上增加一个日志记录的功能.
假设我们有两个业务类ServiceA和ServiceB
ServiceA
package com.aopdemo.service;
import java.util.List;
@Service
public class ServiceA{
Long insert(Order order){
}
int delete(Long id){
}
int update(Order order){
}
List<Order> query(Condition condition){
}
}
ServiceB
package com.aopdemo.service;
import java.util.List;
@Service
public class ServiceB{
Long insert(PayLog payLog){
}
int delete(Long id){
}
int update(PayLog payLog){
}
List<PayLog> query(Condition condition){
}
}
我们可以看到,ServiceA和ServiceB都有对应的增删改查方法.假设,我们现在需要对每个业务的插入方法的前后做一个日志记录的功能.
我们就需要定义一个AOP类
//Spring中的声明该类为AOP的注解
@Aspect
@Component
public class LogAop {
//PointCut切点,用来过滤JoinPoint连接点,这句的意思即为:
//该日志Advice只对com.aopdemo.service下的所有service类中的insert方法生效
@Pointcut("execution(public * com.aopdemo.service.*.insert(..))")
public void writeLog() {
//记录日志
}
//前置通知,在某切入点@Pointcut之前的通知
@Before("writeLog()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("IP : " + request.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
//后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
@After("writeLog()")
public void after(JoinPoint jp) {
System.out.println("方法最后执行.....");
}
//返回后通知,方法执行return之后,可以对返回的数据做加工处理
@AfterReturning(returning = "ret", pointcut = "writeLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
System.out.println("方法的返回值 : " + ret);
}
//抛出异常通知,程序出错跑出异常会执行该通知方法
@AfterThrowing("writeLog()")
public void throwss(JoinPoint jp) {
System.out.println("方法异常时执行.....");
}
//环绕增强,在方法的调用前、后执行,相当于MethodInterceptor
@Around("writeLog()")
public Object arround(ProceedingJoinPoint pjp) {
System.out.println("方法环绕start.....");
try {
Object o = pjp.proceed();
System.out.println("方法环绕proceed,结果是 :" + o);
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
总结
AOP就是在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想。
小结
一句话总结IOC: Spring容器管理对象,不用自己“new”对象.