前言
如果你阅读过我的上一篇说代理的文章, 你应该能充分的理解了代理到底是个什么东西,当谈论到代理相关问题的时候,也相信你能在面试官面前侃侃而谈。
动态代理在Spring中的使用
一、首先我们快速回顾一下Spring中的两种"员工"
1. JDK动态代理::严格遵守合同的"白领"
less
// 必须基于接口
public interface UserService {
void saveUser();
}
@Service
public class UserServiceImpl implements UserService {
@Override
@Transactional // 事务代理
public void saveUser() {
// 业务代码
}
}
特点:
- 只代理接口方法(像只按合同办事)
- Spring默认选择(规范优先)
- 生成类名:com.sun.proxy.$ProxyX
2. CGLIB代理:灵活多变的"蓝领"
typescript
@Service
public class OrderService { // 没有接口!
@Cacheable // 缓存代理
public Order getOrder() {
// 业务代码
}
}
特点:
- 直接继承目标类(像"私生子"继承家产)
- 能代理普通方法(不局限于接口)
二、在看一下Spring的智能选择策略
Spring就像精明的HR,自动选择最佳代理:
objectivec
if (有接口 && !强制CGLIB) {
雇JDK代理;
} else {
雇CGLIB代理;
}
三、简述一下Spring代理的四大"工作场景"(快速浏览下就行)
1. 事务管理(@Transactional)
csharp
// 代理前
public void transfer() {
// 要自己写事务代码
}
// 代理后
public void transfer() {
TransactionManager.begin();
try {
// 业务代码
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
}
}
2. 缓存优化(@Cacheable)
kotlin
@Cacheable("users")
public User getUser(Long id) {
// 代理会自动添加缓存逻辑:
// 1. 先查缓存
// 2. 缓存没有才查数据库
// 3. 结果存入缓存
}
3. 安全控制(@Secured)
java
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser() {
// 代理会先检查权限
// 无权限直接抛异常
}
4. 性能监控(自定义切面)
java
@Around("execution(* com.example..*(..))")
public Object logTime(ProceedingJoinPoint pjp) {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
System.out.println("方法耗时:" + (System.currentTimeMillis() - start));
return result;
}
鉴于上面提到工作场景,只是浅尝辄止,大家有个印象就行,下面将会详细的介绍一下AOP,详细的时候说一下用法,和代码实现。
AOP详解
1. 什么是AOP?
先看官方解释:AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它允许开发者将横切关注点(如日志、事务、安全等)从业务逻辑中分离出来,从而提高代码的模块化程度。
emmmmm, 什么是范式,什么是横切,什么是面向切面编程,当我第一眼看到这个概念的时候,眼前一黑。似乎好像懂了,但只是似乎。通俗地说,AOP就像一把"手术刀",可以精准地在程序执行的特定位置"切入"一些通用功能,而不需要修改原有业务代码。 如果你还不动,直接看下面的图片,你就明白了:
上述图片给了最简单的用户登录、登出、注册功能。调用的方式可能如下:
scss
// 登录
userService.userLogin();
// 注册
userService.userRegister();
// 登出
useService.userLogout();
看图片中"打印调用记录"这个功能本身是个和我们用户登录,注册,登录是没有任何业务联系的。再反过来看一下AOP的定义,用于我们的代码中就可样这样描述: AOP它允许我们将打印日志功能(横向关注点),从业务中分离出来。
相信看完上述的解释,你已经知道了AOP到底是个什么东西。
AOP具体使用
为了能更清楚的展示AOP的功能,我还是用日志的功能进行讲解(没错, 他实现起来最简单。)我们先看一段没有AOP的日志打印。
csharp
public class OrderService {
public void createOrder() {
// 记录日志
System.out.println("开始执行createOrder方法");
long start = System.currentTimeMillis();
try {
// 业务逻辑
System.out.println("创建订单中...");
// 模拟耗时
Thread.sleep(1000);
// 记录日志
System.out.println("createOrder方法执行成功");
} catch (Exception e) {
// 异常处理
System.out.println("createOrder方法执行失败: " + e.getMessage());
} finally {
// 性能监控
long end = System.currentTimeMillis();
System.out.println("createOrder方法执行耗时: " + (end - start) + "ms");
}
}
}
可以看到,日志、性能监控等代码与业务逻辑混杂在一起,导致:
- 代码重复
- 业务逻辑不清晰
- 难以维护 在看下使用AOP后的代码:
csharp
public class OrderService {
public void createOrder() {
System.out.println("创建订单中...");
// 模拟耗时
Thread.sleep(1000);
}
}
java
@Aspect
@Component
public class LoggingAspect {
// 定义切入点(在哪些方法上应用切面)
@Pointcut("execution(* com.example.service.OrderService.*(..))")
public void orderServiceMethods() {}
// 前置通知
@Before("orderServiceMethods()")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("开始执行 " + joinPoint.getSignature().getName() + " 方法");
}
// 后置通知(方法成功执行后)
@AfterReturning("orderServiceMethods()")
public void afterReturningMethod(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + " 方法执行成功");
}
// 异常通知
@AfterThrowing(pointcut = "orderServiceMethods()", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {
System.out.println(joinPoint.getSignature().getName() + " 方法执行失败: " + ex.getMessage());
}
// 环绕通知(可以控制方法执行)
@Around("orderServiceMethods()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = null;
try {
result = joinPoint.proceed(); // 执行目标方法
} finally {
long end = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().getName() +
" 方法执行耗时: " + (end - start) + "ms");
}
return result;
}
}
我们可以观察到的是,在真正的业务代码实现那里,我们省去了大部分打印日志的语句,更聚焦于业务代码的实现。把相关日志都交给AOP做统一处理。
下面我将结合上述代码案例,再说下AOP中的"通知",没错你看错就是通知(Advice)
AOP通知
通知(Advice)就是AOP在特定时机执行的代码块,就像闹钟在特定时间响铃一样。AOP通知告诉系统:"当方法执行到某个点时,帮我插入这段代码"。我们以上述代码,进行详细说明。
前置通知(@Before)
java
// 前置通知
@Before("orderServiceMethods()")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("开始执行 " + joinPoint.getSignature().getName() + " 方法");
}
前置通知就是在代码执行的时候,先去执行前置通知中的代码。
后置通知(@AfterReturning
java
// 后置通知(方法成功执行后)
@AfterReturning("orderServiceMethods()")
public void afterReturningMethod(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + " 方法执行成功");
}
后置通知就是在执行完业务功能后,再去执行后置通知的代码。
异常通知(@AfterThrowing)
less
// 异常通知
@AfterThrowing(pointcut = "orderServiceMethods()", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {
System.out.println(joinPoint.getSignature().getName() + " 方法执行失败: " + ex.getMessage());
}
}
业务代码执行的时候,如果有异常,会执行异常通知里的代码。
最终通知(@After)
kotlin
@After("制作奶茶的方法")
public void 清理操作台() {
System.out.println("清理操作台,准备下一单");
}
不管业务代码执行成功或失败,都执行最终通知。
环绕通知(@Around)
java
// 环绕通知(可以控制方法执行)
@Around("orderServiceMethods()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = null;
try {
result = joinPoint.proceed(); // 执行目标方法
} finally {
long end = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().getName() +
" 方法执行耗时: " + (end - start) + "ms");
}
return result;
}
全流程管控我们的业务代码。
上面就是AOP中的一些总结,下面再看下,真正在项目开发中的一些使用。
@Around:功能最全,适合需要控制方法执行的场景,比如事务管理、权限控制、日志记录 @AfterReturning:适合方法成功后的处理,比如缓存更新、成功通知 @AfterThrowing:统一异常处理,比如错误日志记录、异常报警
关于AOP所有的一些概念性的一些东西已经全部介绍完毕,这个内容旨在于加深我们对于AOP的理解和印象,可以结合我们自己的业务代码,看下AOP都为我们实现了什么功能。后续应该还会更新一篇AOP相关内容,是深入底层源码分析,看下Spring中是如何通过代码实现这个功能。