切面编程的艺术: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>



相关文章
|
11天前
|
XML 安全 前端开发
Spring Security 重点解析(下)
Spring Security 重点解析
28 1
|
11天前
|
缓存 前端开发 Java
【框架】Spring 框架重点解析
【框架】Spring 框架重点解析
27 0
|
11天前
|
安全 NoSQL Java
Spring Security 重点解析(上)
Spring Security 重点解析
28 1
|
11天前
|
XML 监控 安全
Spring特性之一——AOP面向切面编程
Spring特性之一——AOP面向切面编程
20 1
|
11天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
38 3
|
11天前
|
安全 Java 开发者
深入理解Spring Boot配置绑定及其实战应用
【4月更文挑战第10天】本文详细探讨了Spring Boot中配置绑定的核心概念,并结合实战示例,展示了如何在项目中有效地使用这些技术来管理和绑定配置属性。
21 1
|
1天前
|
Java 开发者
探索Java并发编程:Fork/Join框架的深度解析
【5月更文挑战第25天】在多核处理器日益普及的今天,并发编程成为了提升应用性能的关键。Java语言提供了多种并发工具,其中Fork/Join框架是一个高效且强大的工具,用于处理分而治之的任务。本文将深入探讨Fork/Join框架的原理、使用及其在实际应用中的优化策略,旨在帮助开发者更好地利用这一框架以解决复杂的并发问题。
|
2天前
|
程序员 编译器 C++
C++中的模板与泛型编程技术深度解析
C++中的模板与泛型编程技术深度解析
|
11天前
|
前端开发 Java 开发者
【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用
【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用
18 0
|
11天前
|
开发框架 监控 Java
深入探索Spring Boot的监控、管理和测试功能及实战应用
【5月更文挑战第14天】Spring Boot是一个快速开发框架,提供了一系列的功能模块,包括监控、管理和测试等。本文将深入探讨Spring Boot中监控、管理和测试功能的原理与应用,并提供实际应用场景的示例。
21 2

推荐镜像

更多