一、动态代理
代理对象
可以对对象的行为进行定制和扩展。它在代码重用、解耦和业务逻辑分离、性能优化以及系统架构中起到了重要的作用。
代理对象的创建方式
实现业务扩展(日志记录)
下列是一个学生类, 实现BaseStudent
接口, 现在有需求, 需要在调用Student
下的方法前后记录日志
// 基础学生类
@Data
public class Student implements BaseStudent{
private Long id;
private String name;
private String sex;
private Integer age;
@MyLogAnnotation
public void study() {
System.out.println("开始学习......");
}
}
// 接口
public interface BaseStudent {
void study();
}
JDK 实现
/**
* 使用 Jdk方式创建代理对象
* @param obj 被代理的目标对象
* @return Object
*/
private static Object getInstanceByJdk(Object obj) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
// 在方法执行前添加自定义逻辑
System.out.println("开始记录日志...... Jdk");
result = method.invoke(obj, args);
// 在方法执行后添加自定义逻辑
System.out.println("方法执行完成.... Jdk");
return result;
}
};
// 返回的对象id还是那个id,但是对象变了。
// 通过动态代理获得代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler);
}
测试:
public static void main(String[] args) {
BaseStudent st = new Student();
BaseStudent obj = (BaseStudent) getInstanceByJdk(st);
obj.study();
}
执行结果:
----------------------------------------------------------------
开始记录日志...... Jdk
开始学习......
方法执行完成.... Jdk
Process finished with exit code 0
----------------------------------------------------------------
CGLib 实现
/**
* 使用 Cglib方式创建代理对象
* @param obj obj
* @return Object
*/
private static Object getInstanceByCglib(Object obj) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
// 调用方法前的逻辑
System.out.println("开始记录日志.... Cglib");
Object o1 = methodProxy.invokeSuper(o, objects);
// 调用方法后的逻辑
System.out.println("方法执行完成.... Cglib");
return o1;
}
});
return enhancer.create();
}
测试
public static void main(String[] args) {
BaseStudent st = new Student();
BaseStudent obj = (BaseStudent) getInstanceByCglib(st);
obj.study();
}
执行结果:
----------------------------------------------------------------
开始记录日志.... Cglib
开始学习......
方法执行完成.... Cglib
Process finished with exit code 0
----------------------------------------------------------------
两种实现优缺点
二、Spring AOP
1. 概念
AOP
(Aspect Oriented Programming)面向切面编程
- Spring AOP 是 Spring 框架的核心模块之一
- Spring 采用 AspectJ 作为 其 AOP 框架
- 通过 AOP 可以实现主从业务逻辑的隔离和解耦
- AOP 是对 OOP(面向对象编程)的补充和完善
作用
- 分离系统中的主从业务,将主业务和从业务分离开来
- 能够保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能
应用场景
事务管理
日志记录
权限验证
性能监测
相关名词术语
横切关注点
切面(Aspect)
连接点(JoinPoint)
切入点(PointCut)
通知(Advice)
目标对象(Target)
织入(Weaving)
代理(Proxy)
2. AOP 通知类型
前置通知(Before)
后置通知(After)
返回值通知(AfterReturning)
异常通知(AfterThrowing)
环绕通知(Around)
引入通知(Introduction)
3. 完整代码示例:
基础类
/**
* <p>
*
* @author dpl
* @since 2023/12/8 11:11
*/
public interface BizService {
// 插入方法
void insert();
// 保存方法
void save();
// 删除方法
void delete();
}
/**
* <p>
*
* @author dpl
* @since 2023/12/8 11:11
*/
@Service("bizService")
public class BizServiceImpl implements BizService {
@SneakyThrows
public void insert() {
System.out.println("insert 插入数据......");
// 此处手动抛出异常
throw new RuntimeException("");
}
@Override
public void save() {
System.out.println("save 保存数据......");
}
@Override
public void delete() {
System.out.println("delete 删除数据......");
}
}
配置类
/**
* <p>
*
* @author dpl
* @since 2023/12/12 14:46
*/
@Configuration
@EnableAspectJAutoProxy // 启用AOP
@ComponentScan("com.dpl.spring.aop")
public class AopConfig {
}
切面类
/**
* <p>
* 定义切面类
* @author dpl
* @since 2023/12/12 14:59
*/
@Aspect
@Component
public class AopAspect {
/**
* 定义一个可复用的切入点表达式
* 使用方法名来引用当前的切入点表达式:
* 本类中调用、 直接调用方法名:@Before("baseExecution()")
* 跨类调用、加类名:@Before("AopAspect.baseExecution()")
* 跨包调用、加全类名:@Before("com.xx.yy.AopAspect.baseExecution()")
*/
@Pointcut("execution(* com.dpl.spring.aop..BizServiceImpl.*(..))")
public void baseExecution() {
}
/**
* 前置通知(Before)
*
* @param point 可以加上 `JoinPoint` 对象,Spring自动注入,
* 它可以访问到连接点信息,如方法签名、方法参数等。
*/
@Before("baseExecution()")
public void before(JoinPoint point) {
String name = point.getSignature().getName();
System.out.println("开始调用 执行" + name + "方法前的业务逻辑......");
}
// 后置通知(After)
@After("baseExecution()")
public void after(JoinPoint point) {
System.out.println("方法调用完成, 执行结束逻辑......");
}
// 返回值通知(AfterReturning)
@AfterReturning("baseExecution()")
public void afterReturning(JoinPoint point) {
System.out.println("获取到方法返回值, 执行其他业务逻辑......");
}
// 异常通知(AfterThrowing)
@AfterThrowing("baseExecution()")
public void afterThrowing(JoinPoint point) {
System.out.println("方法调用异常, 执行异常处理业务逻辑......");
}
/**
* 环绕通知(Around)
* 环绕通知是最强大的通知类型,它可以将目标方法的执行过程完全控制,
* 相当于 `其他几种通知的集合`,可以完成任何通知类型的操作。
* 一般在使用环绕通知后, 不会再使用其他通知类型。
*
* @param pjp 在使用环绕通知时`ProceedingJoinPoint`用于获取连接点信息,Spring自动注入
*/
@Around("baseExecution()")
public Object around(ProceedingJoinPoint pjp) {
Object obj = null;
try {
String name = pjp.getSignature().getName();
System.out.println("1. 开始调用方法前的逻辑, 打印方法名: " + name);
obj = pjp.proceed();
System.out.println("2. 方法调用完成, 得到返回值: " + obj);
} catch (Throwable e) {
System.out.println("3. 捕获到异常, 异常处理逻辑...");
} finally {
System.out.println("4. 整个方法调用结束后的逻辑....");
}
return obj;
}
}
测试
其他几种通知组合 ( 将代码注释掉
环绕通知
的部分 )public class AopTest { @Test public void test() { ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); BizService bizService = (BizService)context.getBean("bizService"); bizService.insert(); } } 测试结果: ------------------------------------------------ 开始调用 执行insert方法前的业务逻辑...... insert 插入数据...... 方法调用异常, 执行异常处理业务逻辑...... 方法调用完成, 执行结束逻辑...... java.lang.RuntimeException: at com.dpl.spring.aop.BizServiceImpl.insert(BizServiceImpl.java:18) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) at ------------------------------------------------
使用 环绕通知 (
自行注释掉其他通知类型的代码
)public class AopTest { @Test public void test() { ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); BizService bizService = (BizService)context.getBean("bizService"); bizService.insert(); } } 测试结果: ------------------------------------------------ 1. 开始调用方法前的逻辑, 打印方法名: insert insert 插入数据...... 3. 捕获到异常, 异常处理逻辑... 4. 整个方法调用结束后的逻辑.... Process finished with exit code 0 ------------------------------------------------
4. 切入点表达式
- 1、execution(public void com.test.service.Impl.AccountServiceImpl.save(int,java.lang.String))
- 2、execution( com.test..ServiceImpl.*(..))
- 3、execution(* com.test.service.Impl.AccountServiceImpl.save())
- 4、execution(void com..*.save())
- 5、execution( save()) 或 execution( *())
- 6、!execution( save()) 或 not execution( save())
- 7、execution( save()) || execution( update())) 或 execution( save()) or execution( update()))
- 8、execution( (*))
- 9、execution( (..))
- 10、bean(*Service)
5. 切面优先级
- 在 AOP 编程中,@Order 可以用于控制切面的执行顺序
- @Order注解指定切面优先级,值越小优先级越高、优先级越高越先执行
@Aspect
@Order(1) // 值越小优先级越高
@Component
public class AopAspect1 {
@Before("execution(* com.dpl.spring.aop..BizServiceImpl.*(..))")
public void before(JoinPoint point) {
String name = point.getSignature().getName();
System.out.println("开始调用 执行" + name + "方法前的业务逻辑......1");
}
}
@Aspect
@Order(2)
@Component
public class AopAspect2 {
@Before("execution(* com.dpl.spring.aop..BizServiceImpl.*(..))")
public void before(JoinPoint point) {
String name = point.getSignature().getName();
System.out.println("开始调用 执行" + name + "方法前的业务逻辑......2");
}
}
测试结果:
---------------------------------------------------------
开始调用 执行insert方法前的业务逻辑......1
开始调用 执行insert方法前的业务逻辑......2
insert 插入数据......
---------------------------------------------------------
三、Spring AOP 源码分析
1. 完整调用流程 - 下载
流程简述
- 在启动容器时,把切面织入的方法与执行链的关系做了绑定
- 在调用时,通过方法名可以获取到执行链
- 在启动AOP过程中,做好很多关系的映射
- 在AOP调用过程中,通过“索引+递归”完成方法的顺序逻辑
- 多切面支持
2. 源码分析
1)、AOP 动作在源码中的位置
Spring AOP动作在源码中的位置,位于:初始化方法的后置处理器中
代码位置:
AbstractAutowireCapableBeanFactory
进入 initializeBean()
进入 applyBeanPostProcessorsAfterInitialization
初始化的后置处理器方法
查看 processor.postProcessAfterInitialization(result, beanName)
的实现: postProcessAfterInitialization
为何wrapIfNecessary() 方法会走AbstractAutoProxyCreator类的实现?下一步解答.
2)、理解 @EnableAspectJAutoProxy 注解
进一步查看 AspectJAutoProxyRegistrar.class
类关系
进一步看做了什么
将AnnotationAwareAspectJAutoProxyCreator.class
注册到了容器中, 它是自动代理创建器组件
,是AOP的核心
故前置的问题:为何wrapIfNecessary() 方法会走 AbstractAutoProxyCreator
类的实现?
3)、AnnotationAwareAspectJAutoProxyCreator
执行时机
此类自动创建代理,发生的位置和时间节点 有两次
第一次,目标类创建之前尝试生成代理
**AbstractAutowireCapableBeanFactory
.createBean
(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)**第二次,目标类完成初始化被增强生成代理类
AbstractAutowireCapableBeanFactory
.initializeBean
(String beanName, Object bean, @Nullable RootBeanDefinition mbd)4)、
如何生成代理对象
5)、
代理对象的调用流程