中国建设银行的招投标网站,vue做网站好吗,win7系统做网站服务器,网站建设微站前言#xff08;关于源码航行#xff09;
在准备面试和学习的过程中#xff0c;我阅读了还算多的源码#xff0c;比如 JUC、Spring、MyBatis#xff0c;收获了很多代码的设计思想#xff0c;也对平时调用的 API 有了更深入的理解#xff1b;但过多散乱的笔记给我的整理…前言关于源码航行
在准备面试和学习的过程中我阅读了还算多的源码比如 JUC、Spring、MyBatis收获了很多代码的设计思想也对平时调用的 API 有了更深入的理解但过多散乱的笔记给我的整理复习带来了比较大的麻烦。 在 C 站零零散散发了 JUC 的源码解析和集合源码解析收到了很多朋友的喜爱这里我准备将一些源码解析的文章整合起来为了方便阅读和归纳在这里整合成目录源码航行阅读目录大家感兴趣的话可以关注一下 ————————————————
第一篇基础知识介绍 这一部分我们来谈一下关于 Spring AOP 的浮在表面上的知识比如什么是 AOP、它有什么好处、如何使用等等 为什么需要 AOP
AOPAspect Oriented Programming意为面向切面编程通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续是软件开发中的一个热点也是 Spring 框架中的一个重要内容是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性同时提高了开发的效率。
举一个简单的例子如果我们想要在每次调用一个类中的方法之前输出一个日志那其实是挺容易实现的画出流程图就是这个样子
那如果我们要在这些方法之前加一个鉴权呢同样按照前面的方式无非就是多 copy 一次嘛 就这样通过多重 copy 的方式最终完成了这个业务而这也就代表着每次扩展新的方法你都要去进行 n 次复制粘贴每次要更改鉴权或者日志的逻辑你都要去进行 n 次复制粘贴每次。。。。。。
听起来都让人非常头大但如果我们将逻辑改成这样呢 我们将这两个方法封装成一个公共方法每次在方法之前去调用这个公共的方法不就实现了可维护性吗这其实就有一点切面的感觉了。
这样我们虽然每次都不用去复制代码了但还是需要在我们需要的位置去调用这个接口而调用这个代码的位置其实就是 切面。我们用具体的代码来看一下下面实现了一个统一的接口 BeforeAdvice然后在每个方法之前去调用它。
public interface BeforeAdvice {void before();
}public class LoggingBeforeAdvice implements BeforeAdvice {Overridepublic void before() {System.out.println(方法调用之前的日志);}
}public class MyService {private BeforeAdvice beforeAdvice new LoggingBeforeAdvice();public void myMethodA() {beforeAdvice.before();// 业务逻辑System.out.println(执行 myMethod1A);}public void myMethodB() {beforeAdvice.before();// 业务逻辑System.out.println(执行 myMethodB);}
}到这里我们就自己实现了一个简单的 AOP但每次都这样去创建一个这样的类其实也是非常复杂的这时候我们就可以借助 Spring AOP 的力量它帮我们实现了简单、直观、极其易于配置的切面编程方式。
AOP 概念辨析 在正式讲解如何使用 Spring AOP 之前我们来讲点无聊的内容关于 AOP 的概念辨析这部分内容是一些术语但是如果能理解它们就会对 AOP 整个流程有较为清晰的把握。 连接点Join Point连接点是程序执行中的一个点这个点可以是方法调用、方法执行、异常抛出等。在 Spring AOP 中连接点主要是指方法的调用或执行。连接点是通知的实际应用点。
切点PointCut由于连接点可能很多比如一个类中的所有方法想要把所有连接点罗列出现显然有些困难切点则定义了在应用通知的连接点的集合。切点通过切点表达式例如execution(* com.example.service.*.*(..))来指定匹配的方法和类。切点表达式用于筛选连接点使得通知只在特定的连接点上执行。
通知Advice通知是在切点处执行的代码。通知定义了具体的横切逻辑决定了在方法执行的什么阶段之前、之后、环绕等插入横切逻辑。通知有五种类型我们会在下一部分进行详细的了解通知就是在 何时 执行 怎样 的逻辑。
切面Aspect切面是 AOP 的核心模块它封装了跨越多个类的关注点例如日志记录、事务管理或安全控制。切面通过通知Advice和切点Pointcut来定义在何时、何地应用这些关注点可以将切面看作是切点Pointcut和通知Advice的组合。切面定义了在何处切点以及何时通知应用横切逻辑。
五种通知类型 在 Spring AOP 中通知Advice是指在程序执行过程中插入的代码它定义了在何时以及在什么情况下进行切面的操作。通知是切面中的实际动作部分是横切关注点的具体实现直观来说就是要插入的那一组方法。 除了上面提到的在执行方法之前执行的 Before Advice还有其他四种类型的通知也就是说 Spring AOP 为我们提供了五个插入代码的位置选择。 前置通知Before Advice
在目标方法执行之前执行的通知。可以用来执行日志记录、安全检查等。
Aspect
Component
public class LoggingAspect {Before(execution(* com.example.service.*.*(..)))public void beforeAdvice() {System.out.println(前置通知方法调用之前执行);}
}后置通知After Advice
在目标方法执行之后执行的通知无论方法是成功返回还是抛出异常。常用于清理资源等。
Aspect
Component
public class LoggingAspect {After(execution(* com.example.service.*.*(..)))public void afterAdvice() {System.out.println(后置通知方法调用之后执行);}
}返回后通知After Returning Advice
在目标方法成功返回结果之后执行的通知。可以用来记录返回值或对返回值进行处理。
Aspect
Component
public class LoggingAspect {AfterReturning(pointcut execution(* com.example.service.*.*(..)), returning result)public void afterReturningAdvice(Object result) {System.out.println(返回后通知方法返回值为 result);}
}抛出异常后通知After Throwing Advice
在目标方法抛出异常后执行的通知。可以用来记录异常信息或执行异常处理逻辑。
Aspect
Component
public class LoggingAspect {AfterThrowing(pointcut execution(* com.example.service.*.*(..)), throwing exception)public void afterThrowingAdvice(Exception exception) {System.out.println(抛出异常后通知异常为 exception.getMessage());}
}环绕通知Around Advice
环绕通知在目标方法执行的前后都执行可以完全控制目标方法的执行包括决定是否执行目标方法以及在目标方法执行前后添加自定义逻辑。环绕通知最为强大和灵活。
Aspect
Component
public class LoggingAspect {Around(execution(* com.example.service.*.*(..)))public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println(环绕通知方法调用之前);Object result joinPoint.proceed(); // 执行目标方法System.out.println(环绕通知方法调用之后);return result;}
}这五种通知方式的执行顺序是这样的
前置通知Before Advice**环绕通知Around Advice**的前半部分目标方法执行**环绕通知Around Advice**的后半部分返回后通知After Returning Advice如果目标方法成功返回抛出异常后通知After Throwing Advice如果目标方法抛出异常后置通知After Advice
切点表达式 前面提到过切面直观来讲就是插入方法的位置在前面五种通知类型中我们已经看到了如何通过注解选择方法的执行位置但是诸如 * com.example.service.*.*(..)) 这样定位方法位置的格式其实是没有提及的这一部分重点来讲一下如何配置方法的位置。 切点则表示一组 joint point这些 joint point 或是通过逻辑关系组合起来或是通过通配、正则表达式等方式集中起来它定义了相应的 Advice 将要发生的地方。
切点表达式由以下几部分组成
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)其中各部分的含义如下注意上面的问号(?)表示可选
execution指定切点类型为方法执行。modifiers-pattern可选方法的访问修饰符如 public、protected、private。通常省略不写表示任意访问修饰符。ret-type-pattern方法的返回类型模式例如 void、String、任意返回类型。declaring-type-pattern可选方法所在类的全限定名称模式如 com.example.service.*。指定要匹配的类或包。name-pattern方法名称模式如 Service、get*。支持通配符 匹配任意字符序列和 ..匹配任意数量和类型的参数。param-pattern方法参数模式如 ()无参数、(*)一个任意类型的参数、(..)任意数量和类型的参数。throws-pattern可选方法可能抛出的异常模式。
下面来做几个小练习
1指定 com.example.service 包下的所有类的所有方法
Pointcut(execution(* com.example.service.*.*(..)))
private void serviceMethods() {}2com.example.service 包下所有类的方法中第一个参数为 String 类型的方法
Pointcut(execution(* com.example.service.*.*(String, ..)))
private void stringParamMethods() {}通过上面的练习相信大家对切点表达式的书写方式有了一定的掌握下面我们来看看除了 execution 方法执行切点Spring 还为我们提供了哪些指定切点的方式
within限定匹配特定类型的连接点。within(com.example.service.*) 匹配 com.example.service 包及其子包中所有方法。
this限定匹配特定类型的 bean。this(com.example.service.MyService) 匹配实现 com.example.service.MyService 接口的 bean。
target限定匹配特定类型的目标对象。target(com.example.service.MyService) 匹配目标对象是 com.example.service.MyService 的连接点。
args限定匹配特定参数类型的方法。args(String, ..) 匹配第一个参数是 String 类型的方法。
annotation限定匹配特定注解的方法。annotation(org.springframework.transaction.annotation.Transactional) 匹配标注有 Transactional 注解的方法。
因为 within、this 和 target都可以通过 execution 作为一定程度上的替代所以这里我们重点关注一下匹配特定注解的方式即 annotation 即可。
但同时这些表达式其实是可以共用的比如通过这样的方式
Before(execution(* com.example.service.UserService.getUserById(int)) args(userId))public void logBeforeGetUserById(JoinPoint joinPoint, int userId) {System.out.println(Before calling getUserById with userId: userId);}上面的 userId 参数是通过切点表达式中的 args(userId) 指定的所以在方法体内可以直接使用 userId 参数来获取方法执行时的具体值但如果仅仅使用 joinPoint 的话就需要 getArgs() 再拿取参数了这里只涉及写法上的偏好我们平时使用的大部分的内容通过 execution 和 annotation 都是可以实现的。
正式使用 通过前面的介绍我们已经辨析了 AOP 的基本概念了解了控制何时执行逻辑的通知类型Advice定义在什么位置执行的切点表达式PointCut下面我们正式来尝试使用 AOP 来解决一些现实的问题。 就举一个前面提到的日志记录的 AOP 吧
定义一个简单的服务类
Service
public class UserService {public String getUserById(int userId) {// 模拟方法体return User: userId;}
}创建日志记录的切面类
Aspect
Component
public class LoggingAspect {Before(execution(* com.example.service.UserService.getUserById(int)))public void logBeforeGetUserById(JoinPoint joinPoint) {// 获取方法参数Object[] args joinPoint.getArgs();System.out.println(Before calling getUserById with userId: args[0]);}AfterReturning(pointcut execution(* com.example.service.UserService.getUserById(int)), returning result)public void logAfterReturningGetUserById(JoinPoint joinPoint, Object result) {System.out.println(After returning from getUserById with result: result);}
}然后我们去做一个简单的测试可以看到如下的输出
Before calling getUserById with userId: 123
After returning from getUserById with result: User: 123
User retrieved: User: 123关于 JoinPoint 和 ProceedingJoinPoint 在 Spring AOP 中JoinPoint 和 ProceedingJoinPoint 是两个重要的接口用于在切面中获取方法执行时的信息和控制方法执行我们在书写切面逻辑的时候需要的大部分参数或者方法信息等都是从这里面获取的。 JoinPoint 接口
JoinPoint 接口是 Spring AOP 提供的一个核心接口用于描述正在执行的连接点join point它可以用来获取方法的签名、参数等信息但是不能直接控制方法的执行流程。
常用方法
Signature getSignature(); // 获取代表被通知方法签名的对象可以进一步获取方法名、声明类型等信息。Object[] getArgs(); // 获取被通知方法的参数对象数组。Object getTarget(); // 获取目标对象即被通知的目标类实例。Object getThis(); // 获取代理对象的引用即代理对象本身。Object[] getArgs(); // 获取调用方法时传递的参数其中有个特殊一点的是 Signature方法签名接口
public interface Signature {String toString(); // 返回方法的字符串表示形式。String toShortString(); // 返回方法的简短字符串表示形式。String toLongString(); // 返回方法的长字符串表示形式。String getName();// 获取方法名。 int getModifiers(); // 获取方法的修饰符返回一个整数具体取值需要通过 java.lang.reflect.Modifier 类来解析。Class getDeclaringType(); // 获取声明该方法的类的 Class 对象。String getDeclaringTypeName(); // 获取声明该方法的类的全限定名。
}大家可以自己写个方法测试一下这里就不过多赘述了。
ProceedingJoinPoint 接口
ProceedingJoinPoint 接口继承自 JoinPoint 接口它扩展了 JoinPoint 接口提供了控制方法执行流程的能力。通常在 Around Advice 中使用 ProceedingJoinPoint 来调用目标方法并可以控制是否继续执行该方法以及在执行前后进行额外的处理。
常用方法
Object proceed() throws Throwable; // 继续执行连接点即目标方法返回方法的返回值。Object proceed(Object[] args) throws Throwable; // 按照给定的参数继续执行连接点。由于是继承所以 JoinPoint 提供的方法也都可以使用。
区别和用途
JoinPoint 主要用于获取方法的元数据信息如方法名、参数等不具备控制方法执行流程的能力。ProceedingJoinPoint 继承自 JoinPoint可以控制方法的执行流程在 Around Advice 中使用可以决定是否继续执行目标方法以及在执行前后进行额外的处理。