摘要

在上一阶段中,我们学习了AOP的基础知识和简单的切面实现方法。在这一阶段,我们将进一步深入,探索如何通过注解实现自定义切点,以及使用动态代理机制实现AOP功能。文章包含详细的代码示例,并为每段代码添加详细注释,帮助初学者快速理解和上手。

引言

切面编程的真正强大之处在于其灵活性,特别是在复杂应用中,它可以极大地提高代码的可维护性。本阶段主要涵盖以下两部分内容:

  1. 如何结合注解自定义切点,动态定义哪些方法需要增强。
  2. 动态代理机制的底层实现,包括JDK动态代理和CGLIB。

无论你是希望更深入理解AOP机制,还是希望将其灵活应用到项目中,这篇文章都能为你提供有力的帮助。


在这里插入图片描述

博主 默语带您 Go to New World.
个人主页—— 默语 的博客👦🏻 优秀内容
《java 面试题大全》
《java 专栏》
《idea技术专区》
《spring boot 技术专区》
《MyBatis从入门到精通》
《23种设计模式》
《经典算法学习》
《spring 学习》
《MYSQL从入门到精通》数据库是开发者必会基础之一~
🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭
🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄之助。苟未尽善尽美,敬请批评指正,以资改进。!💻⌨


默语是谁?

大家好,我是 默语,别名默语博主,擅长的技术领域包括Java、运维和人工智能。我的技术背景扎实,涵盖了从后端开发到前端框架的各个方面,特别是在Java 性能优化、多线程编程、算法优化等领域有深厚造诣。

目前,我活跃在CSDN、掘金、阿里云和 51CTO等平台,全网拥有超过10万的粉丝,总阅读量超过1400 万。统一 IP 名称为 默语 或者 默语博主。我是 CSDN 博客专家、阿里云专家博主和掘金博客专家,曾获博客专家、优秀社区主理人等多项荣誉,并在 2023 年度博客之星评选中名列前 50。我还是 Java 高级工程师、自媒体博主,北京城市开发者社区的主理人,拥有丰富的项目开发经验和产品设计能力。希望通过我的分享,帮助大家更好地了解和使用各类技术产品,在不断的学习过程中,可以帮助到更多的人,结交更多的朋友.


我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。


切面技术进阶

正文

1. 结合注解实现自定义切点

自定义注解是切面技术中的一项重要功能,允许开发者灵活标记需要增强的方法。

步骤 1:定义自定义注解

创建一个注解,用于标记需要增强的方法:

package com.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/
 * 自定义注解,用于标记需要记录执行时间的方法。
 * - @Retention(RetentionPolicy.RUNTIME):注解在运行时有效。
 * - @Target(ElementType.METHOD):注解仅能作用于方法。
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
}
步骤 2:创建业务类并使用注解

在业务类的方法上添加自定义注解:

package com.example.service;

import com.example.annotation.LogExecutionTime;
import org.springframework.stereotype.Service;

/
 * 产品服务类,包含商品价格计算逻辑。
 */
@Service
public class ProductService {

    /
     * 计算商品价格的方法,使用自定义注解标记。
     */
    @LogExecutionTime
    public void calculatePrice() {
        System.out.println("Calculating product price...");
        // 模拟耗时操作
        try {
            Thread.sleep(2000); // 让线程休眠2秒,模拟复杂的业务逻辑
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Price calculation completed.");
    }
}
步骤 3:创建切面并处理注解

使用切面捕获注解并添加增强逻辑:

package com.example.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/
 * 切面类,用于拦截带有@LogExecutionTime注解的方法,并记录其执行时间。
 */
@Aspect
@Component
public class ExecutionTimeAspect {

    /
     * 定义环绕通知,拦截@LogExecutionTime注解标记的方法。
     * @param joinPoint 切点,表示目标方法的上下文信息。
     * @return 目标方法的返回值。
     * @throws Throwable 如果目标方法抛出异常,继续向上抛出。
     */
    @Around("@annotation(com.example.annotation.LogExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis(); // 方法执行开始时间
        Object proceed = joinPoint.proceed();   // 执行目标方法
        long executionTime = System.currentTimeMillis() - start; // 计算执行时间

        System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
        return proceed;
    }
}
步骤 4:运行并验证

调用ProductService.calculatePrice()方法时,将自动记录其执行时间。

控制台输出:

Calculating product price...
Price calculation completed.
void com.example.service.ProductService.calculatePrice() executed in 2002ms

2. 动态代理与切面编程

AOP的底层实现依赖于动态代理技术。Spring AOP默认使用JDK动态代理或CGLIB实现切面功能。

2.1 JDK动态代理

JDK动态代理仅适用于接口类型。以下是一个简单的示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义支付服务接口
interface PaymentService {
    void processPayment();
}

// 支付服务实现类
class PaymentServiceImpl implements PaymentService {
    @Override
    public void processPayment() {
        System.out.println("Processing payment...");
    }
}

// 动态代理处理器,用于在方法调用前后添加日志逻辑
class LoggingHandler implements InvocationHandler {
    private final Object target; // 目标对象

    public LoggingHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[LOG] Method " + method.getName() + " is called");
        return method.invoke(target, args); // 调用目标方法
    }
}

public class JDKProxyExample {
    public static void main(String[] args) {
        PaymentService service = new PaymentServiceImpl(); // 创建目标对象

        // 创建代理对象
        PaymentService proxy = (PaymentService) Proxy.newProxyInstance(
                service.getClass().getClassLoader(),
                new Class[]{PaymentService.class},
                new LoggingHandler(service)
        );

        proxy.processPayment(); // 调用代理对象的方法
    }
}

输出:

[LOG] Method processPayment is called
Processing payment...
2.2 CGLIB动态代理

CGLIB通过生成目标类的子类实现代理,因此适用于没有接口的类。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

// 用户服务类
class UserService {
    public void createUser() {
        System.out.println("Creating user...");
    }
}

public class CGLIBProxyExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class); // 设置目标类

        // 设置回调逻辑
        enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
            System.out.println("[LOG] Method " + method.getName() + " is called");
            return proxy.invokeSuper(obj, args1); // 调用目标方法
        });

        UserService proxy = (UserService) enhancer.create(); // 创建代理对象
        proxy.createUser(); // 调用代理对象的方法
    }
}

输出:

[LOG] Method createUser is called
Creating user...

总结

本篇文章详细介绍了如何结合注解实现自定义切点,并深入讲解了JDK动态代理和CGLIB在AOP中的底层实现。每段代码都添加了详细注释,确保初学者能够快速理解其原理和用法。

通过这些内容,你不仅可以轻松实现简单的AOP功能,还能深入理解Spring AOP的核心机制。


参考资料

  1. Spring AOP官方文档
  2. JDK动态代理官方文档
  3. CGLIB GitHub项目

添加我的微信,一起交流技术吧!


如对本文内容有任何疑问、建议或意见,请联系作者,作者将尽力回复并改进📓;(联系微信:Solitudemind )
点击下方名片,加入IT技术核心学习团队。一起探索科技的未来,共同成长。

更多推荐