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>