aop(面向切面编程)

如果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中具体实现

  1. 在Bean的生命周期中初始化后的bean会调用 BeanPostProcessor(后置处理器)
  2. 这里就是aop的入口,AOP 的核心组件 AnnotationAwareAspectJAutoProxyCreator 会判断该 Bean 是否匹配任何切面,如果匹配则为其创建代理对象。
  3. Spring 优先使用 JDK 动态代理(基于接口),如果 Bean 没有实现接口,则使用 CGLIB 创建子类代理。
  4. 代理对象内部维护一条 AOP 方法拦截器链(Advice Chain),方法执行时会通过 invoke 调用,分别执行前置、后置、环绕等增强逻辑,最后调用目标对象的方法。
  5. 最终 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

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容