如果ioc容器比作是一篇文章由大大小小的bean组成不同的章节,那aop这个功能就是一篇文章的插入点,他可以作用于整个文章可在切点前后插入内容,最后增强的文章重新提交。这是一段我所理解的抽象概念。
AOP 基本概念
切面(Aspect):切面是一个模块化的横切关注点实现,它包括了连接点和通知。可以通过配置文件、注解等方式定义切面。
连接点(Joinpoint):程序中能够被切面插入的点,典型的连接点包括方法调用、方法执行过程中的某个时点等等。
通知(Advice):在连接点处执行的代码。通知分为各种类型,如前置通知、后置通知、环绕通知等。
切点(Pointcut):用于定义哪些连接点上应该应用通知。切点通过表达式进行定义,如匹配所有 public 方法或匹配某个包下的所有方法等。
织入(Weaving):指将切面应用到目标对象并创建新的代理对象的过程。织入可以在运行时完成,也可以在编译时完成。 Spring AOP 提供了两种织入方式:编译期织入和运行期织入。
Spring AOP提供了以下几种类型的增强:
1.前置增强(Before Advice):在目标方法执行之前执行的逻辑。
2.后置增强(After Advice):在目标方法执行之后执行的逻辑,不管目标方法是否抛出异常。
3.返回增强(After Returning Advice):在目标方法正常返回时执行的逻辑。
4.异常增强(After Throwing Advice):在目标方法抛出异常时执行的逻辑。
5.环绕增强(Around Advice):在目标方法执行前后都可以执行的逻辑,它可以完全控制目标方法的执行。
代码示例
// 业务代码(普通 Service,不需要任何注解)
@Service
public class UserService {
public void getUserInfo() {
System.out.println("【UserService】获取用户信息...");
try {
Thread.sleep(300); // 模拟业务耗时
} catch (Exception e) {
}
}
}
// AOP 切面,我们直接使用包扫描 + 方法匹配
@Aspect
@Component
public class TimeAspect {
/**
* 切点:匹配 cn.jason.service 包下所有方法
*/
@Pointcut("execution(* cn.jason.service..*(..))")
public void pointcut() {
}
/**
* 环绕通知:统计执行时间
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
// 执行真实方法
Object result = pjp.proceed();
long end = System.currentTimeMillis();
System.out.println("【AOP】方法 " + pjp.getSignature().getName()
+ " 执行耗时:" + (end - start) + "ms");
return result;
}
}
// 启动类(开启 AOP)
@SpringBootApplication
@EnableAspectJAutoProxy
public class AopDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AopDemoApplication.class, args);
}
}
IOC中具体实现
- 在Bean的生命周期中初始化后的bean会调用 BeanPostProcessor(后置处理器)
- 这里就是aop的入口,AOP 的核心组件 AnnotationAwareAspectJAutoProxyCreator 会判断该 Bean 是否匹配任何切面,如果匹配则为其创建代理对象。
- Spring 优先使用 JDK 动态代理(基于接口),如果 Bean 没有实现接口,则使用 CGLIB 创建子类代理。
- 代理对象内部维护一条 AOP 方法拦截器链(Advice Chain),方法执行时会通过 invoke 调用,分别执行前置、后置、环绕等增强逻辑,最后调用目标对象的方法。
- 最终 IoC 容器中存放的是代理对象,而不是原始对象,因此所有对 Bean 的调用都会自动附带 AOP 增强。
JDK与CGLIB动态代理代码示例
// JDK
public class StudentServiceProxy implements InvocationHandler {
private Object target; // 目标对象
public StudentServiceProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前打印执行方法和参数类型和值");
System.out.println("Method: " + method.getName());
// 获取方法的参数名称和值
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
System.out.println("parameter: " + parameters[i].getType() + " value: " + args[i]);
}
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("方法执行后打印返回结果");
System.out.println(method.getName()+" : "+result);
return result;
}
// 创建代理对象
public static Object newProxyInstance(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),// 代理对象的类加载器
target.getClass().getInterfaces(),// 代理对象实现的接口
new StudentServiceProxy(target));// 代理对象的方法
}
}
// CGLIB
public class StudentServiceCGLIBProxy implements MethodInterceptor {
// 创建代理对象
public Object createProxy(Object target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置目标类为代理对象的父类
enhancer.setCallback(this); // 设置回调
return enhancer.create(); // 创建代理对象
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("方法执行前打印执行方法和参数类型和值...");
System.out.println("Method: " + method.getName());
// 获取方法的参数名称和值
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
System.out.println("parameter: " + parameters[i].getType() + " value: " + args[i]);
}
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("方法执行后打印返回结果");
System.out.println(method.getName()+" : "+result);
return result;
}
}
查看完整demo示例可访问Jason/Dynamic-Proxy