切面编程的艺术:Spring动态代理解析与实战

简介: 切面编程的艺术:Spring动态代理解析与实战


Spring 动态代理

Spring 动态代理是 Spring 框架提供的一种代理机制,它可以在运行时动态地创建代理对象。

在 Spring 中,有两种常用的动态代理方式:JDK 动态代理和 CGLIB 动态代理。Spring 会根据具体情况选择使用 JDK 动态代理还是 CGLIB 动态代理来创建代理对象。在配置文件中,可以通过配置 aop:config 元素来声明需要使用代理的类和代理方式。也可以使用基于注解的方式,通过在目标类或方法上添加相关注解来实现动态代理。

动态代理不需要创建代理类的文件,代理类是在 JVM 运行期间动态生成的。

JDK 动态代理

JDK 动态代理是通过 Java 反射机制来实现的。当目标对象实现了接口时,Spring 会使用 JDK 动态代理来创建代理对象。代理对象会实现与目标对象相同的接口,并将方法的调用委托给目标对象。

JDK 动态代理的主要步骤如下:

  • 定义一个 InvocationHandler 接口的实现类,在 invoke 方法中编写代理逻辑。
  • 使用 Proxy 类的 newProxyInstance 静态方法创建代理对象。需要传入目标对象的类加载器、目标对象实现的接口以及 InvocationHandler 实例。

优点:JDK 动态代理不需要依赖第三方库,能够直接使用 Java 的标准库实现。

缺点:只能为实现了接口的类创建代理对象。

CGLIB 动态代理

当目标对象没有实现任何接口时,Spring 会使用 CGLIB 动态代理来创建代理对象。CGLIB 是一个强大的字节码生成库,它通过继承目标对象来创建代理对象,并重写目标对象的方法。

CGLIB 动态代理的主要步骤如下:

  • 定义一个 MethodInterceptor 接口的实现类,通过实现 intercept 方法编写代理逻辑。
  • 使用 Enhancer 类创建代理对象。需要设置目标对象的类和 MethodInterceptor 实例。

优点:CGLIB 动态代理可以为没有实现接口的类创建代理对象。

缺点:CGLIB 动态代理需要依赖 CGLIB 库,生成的代理对象继承了目标对象,对 final 方法和 private 方法无法进行代理。

开发 Demo

创建原始对象

package world.xuewei.proxy;
/**
 * 用户服务接口
 *
 * @author 薛伟
 * @since 2023/10/18 17:48
 */
public interface UserService {
    void register();
    void login();
}
package world.xuewei.proxy;
/**
 * 用户服务实现
 *
 * @author 薛伟
 * @since 2023/10/18 17:49
 */
public class UserServiceImpl implements UserService {
    @Override
    public void register() {
        System.out.println("UserServiceImpl.register");
    }
    @Override
    public void login() {
        System.out.println("UserServiceImpl.login");
    }
}

创建通知类

需要实现 MethodBeforeAdvice 接口,并实现 before 方法。

package world.xuewei.proxy;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
 * 额外功能 MethodBeforeAdvice 接⼝
 *
 * @author 薛伟
 * @since 2023/10/18 17:53
 */
public class Before implements MethodBeforeAdvice {
    /**
     * 需要把运⾏在原始⽅法执⾏之前运⾏的额外功能,书写在 before ⽅法中
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("Before.before");
    }
}

Spring 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  <!-- 注册原始对象 -->
    <bean id="userService" class="world.xuewei.proxy.UserServiceImpl"/>
    <!-- 注册通知 -->
    <bean id="before" class="world.xuewei.proxy.Before"/>
    <!-- 配置动态代理 -->
    <aop:config>
        <!-- 所有方法都作为切入点,加入额外功能 -->
        <aop:pointcut id="p1" expression="execution(* *(..))"/>
        <!-- 整合组装 -->
        <aop:advisor advice-ref="before" pointcut-ref="p1"/>
    </aop:config>
</beans>

程序测试

/**
  * 测试动态代理
  */
@Test
public void test7() {
    // 通过原始对象的 id 就可以获取到代理对象
    UserService p = context.getBean("userService", UserService.class);
    p.login();
}

MethodBeforeAdvice 接口

Spring 的 MethodBeforeAdvice 接口是 Spring AOP 框架提供的一个通知接口,用于在被拦截的方法执行之前执行特定的逻辑操作。MethodBeforeAdvice 通常用于实现前置通知(Before Advice),在目标方法执行之前执行一些预处理操作。例如,可以在 before 方法中添加日志记录、参数校验、权限验证等操作。

MethodBeforeAdvice 只能进行前置通知,如果需要在方法执行结束后进行操作,可以使用其他类型的通知,如 AfterReturningAdvice、ThrowsAdvice 等。

MethodBeforeAdvice 接口定义了一个 before 方法。方法签名如下:

void before(Method method, Object[] args, Object target) throws Throwable;
  • method:表示要被执行的方法对象。
  • args:表示执行方法时所需的参数数组,可以为空数组(即方法没有参数)。
  • target:表示被代理的目标对象,即要执行方法的对象。
  • throws Throwable:表示方法可能抛出的异常。

注意:如果 before 方法抛出异常,将阻止被拦截的方法的执行。

package world.xuewei.proxy;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
 * 额外功能 MethodBeforeAdvice接⼝
 *
 * @author 薛伟
 * @since 2023/10/18 17:53
 */
public class Before implements MethodBeforeAdvice {
    /**
     * 需要把运⾏在原始⽅法执⾏之前运⾏的额外功能,书写在 before ⽅法中
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("Before.before");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  <!-- 注册原始对象 -->
    <bean id="userService" class="world.xuewei.proxy.UserServiceImpl"/>
    <!-- 注册通知 -->
    <bean id="before" class="world.xuewei.proxy.Before"/>
    <!-- 配置动态代理 -->
    <aop:config>
        <!-- 所有方法都作为切入点,加入额外功能 -->
        <aop:pointcut id="p1" expression="execution(* *(..))"/>
        <!-- 整合组装 -->
        <aop:advisor advice-ref="before" pointcut-ref="p1"/>
    </aop:config>
</beans>

MethodInterceptor 接口(aopalliance)

MethodInterceptor 接口是 Spring AOP 框架提供的一个通知接口,用于在被拦截的方法执行前后执行一些特定的逻辑操作。和其他类型的通知(如MethodBeforeAdvice)不同,MethodInterceptor 接口通常用于实现环绕通知(Around Advice),可以在目标方法执行前后以及出现异常时执行特定的逻辑操作,并且对方法的返回值进行处理。比如对数据库事务的控制都需要在原始方法之前和之后都需要处理。

MethodInterceptor 接口定义了一个 invoke 方法,方法签名如下:

Object invoke(MethodInvocation invocation) throws Throwable;
  • invocation:表示方法调用对象,包含被调用的方法、目标对象以及方法参数等信息。
  • throws Throwable:表示方法可能抛出的异常。
  • Object:原始方法执行后的返回值,通过 invocation.proceed() 获取。

注意:在 MethodInterceptor 中,我们需要手动调用 invocation.proceed() 方法来执行被拦截的方法。

注意:MethodInterceptor 可以对返回值进行处理,并且允许我们在出现异常时执行特定的逻辑。

注意:由于 MethodInterceptor 是在目标方法执行前后都会被调用,因此需要特别注意对性能的影响。

package world.xuewei.proxy;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * 自定义方法拦截器
 *
 * @author 薛伟
 * @since 2023/10/19 17:32
 */
public class Around implements MethodInterceptor {
    /**
     * 编写自定义处理逻辑
     *
     * @param methodInvocation 表示方法调用对象,包含被调用的方法、目标对象以及方法参数等信息。
     * @return 原始方法执行后的返回值,通过 invocation.proceed() 获取。
     * @throws Throwable 表示方法可能抛出的异常。
     */
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        // 在此处编写前置操作
        System.out.println("Before");
        Object result = null;
        try {
            result = methodInvocation.proceed();
        } catch (Exception e) {
            // 在此处编写异常操作
            System.out.println("Exception");
        }
        // 在此处编写后置操作
        System.out.println("After");
        // 可以在此处编写新的返回值并返回
        return result;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  <!-- 注册原始对象 -->
    <bean id="userService" class="world.xuewei.proxy.UserServiceImpl"/>
    <!-- 注册通知 -->
    <bean id="around" class="world.xuewei.proxy.Around"/>
    <!-- 配置动态代理 -->
    <aop:config>
        <!-- 所有方法都作为切入点,加入额外功能 -->
        <aop:pointcut id="p1" expression="execution(* *(..))"/>
        <!-- 整合组装 -->
        <aop:advisor advice-ref="around" pointcut-ref="p1"/>
    </aop:config>
</beans>



相关文章
|
5天前
|
canal 缓存 关系型数据库
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
|
5天前
|
C++
C++:深度解析与实战应用
C++:深度解析与实战应用
8 1
|
5天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
5天前
|
大数据 图形学 云计算
EDA设计:技术深度解析与实战代码应用
EDA设计:技术深度解析与实战代码应用
|
6天前
|
安全 Java Maven
[AIGC] Spring Boot中的切面编程和实例演示
[AIGC] Spring Boot中的切面编程和实例演示
|
17天前
|
SQL API 数据库
Python中的SQLAlchemy框架:深度解析与实战应用
【4月更文挑战第13天】在Python的众多ORM(对象关系映射)框架中,SQLAlchemy以其功能强大、灵活性和易扩展性脱颖而出,成为许多开发者首选的数据库操作工具。本文将深入探讨SQLAlchemy的核心概念、功能特点以及实战应用,帮助读者更好地理解和使用这一框架。
|
17天前
|
监控 Java 数据库连接
Spring高手之路17——动态代理的艺术与实践
本文深入分析了JDK和CGLIB两种动态代理技术在Spring框架中的应用。讨论了动态代理的基础概念,通过实例展示了如何实现和应用这两种方法,并比较了它们的性能差异及适用场景。进一步,探讨了在动态代理中实现熔断限流和日志监控的策略,以及如何利用动态代理优化Spring应用的设计和功能。
33 6
Spring高手之路17——动态代理的艺术与实践
|
18天前
|
XML Java 数据格式
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
21 0
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
|
18天前
|
XML Java 数据格式
从入门到精通:Spring基础注解的全面解析
从入门到精通:Spring基础注解的全面解析
35 2
从入门到精通:Spring基础注解的全面解析
|
18天前
|
Java Spring
切面编程的锋芒:Spring切入点的玩法与技巧
切面编程的锋芒:Spring切入点的玩法与技巧
16 0
切面编程的锋芒:Spring切入点的玩法与技巧

推荐镜像

更多