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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 切面编程的艺术: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>



相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
22天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
15天前
|
安全 程序员 API
|
12天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
12天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
25天前
|
自然语言处理 Java API
Spring Boot 接入大模型实战:通义千问赋能智能应用快速构建
【10月更文挑战第23天】在人工智能(AI)技术飞速发展的今天,大模型如通义千问(阿里云推出的生成式对话引擎)等已成为推动智能应用创新的重要力量。然而,对于许多开发者而言,如何高效、便捷地接入这些大模型并构建出功能丰富的智能应用仍是一个挑战。
97 6
|
23天前
|
Java 开发者 UED
Java编程中的异常处理机制解析
在Java的世界里,异常处理是确保程序稳定性和可靠性的关键。本文将深入探讨Java的异常处理机制,包括异常的类型、如何捕获和处理异常以及自定义异常的创建和使用。通过理解这些概念,开发者可以编写更加健壮和易于维护的代码。
|
28天前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
69 2
|
29天前
|
Java 数据安全/隐私保护 Spring
Spring进阶:初识动态代理
本文介绍了Spring框架中AOP切面编程的基础——动态代理。通过定义Vehicle接口及其实现类Car和Ship,展示了如何使用动态代理在不修改原代码的基础上增强功能。文章详细解释了动态代理的工作原理,包括通过`Proxy.newProxyInstance()`方法创建代理对象,以及`InvocationHandler`接口中的`invoke()`方法如何处理代理对象的方法调用。最后,通过一个测试类`TestVehicle`演示了动态代理的具体应用。
|
19天前
|
设计模式 SQL 安全
Java编程中的单例模式深入解析
【10月更文挑战第24天】在软件工程中,单例模式是设计模式的一种,它确保一个类只有一个实例,并提供一个全局访问点。本文将探讨如何在Java中使用单例模式,并分析其优缺点以及适用场景。
11 0

推荐镜像

更多