Skip to content

动态代理

Spring AOP(面向切面编程)允许开发者定义横切关注点,如日志记录、事务管理等,并将这些逻辑应用于业务代码中而无需修改原有的业务逻辑。Spring AOP可以使用JDK动态代理和CGLIB来实现AOP的功能。

JDK Proxy

JDK Proxy是Java自带的代理机制,它要求被代理的类必须实现一个或多个接口。JDK Proxy通过在运行时创建实现相同接口的新类来生成代理对象。

例子:

假设我们有一个简单的UserService接口及其实现:

java
public interface UserService {
    void saveUser();
}

public class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("Saving user...");
    }
}

我们可以创建一个基于JDK Proxy的简单切面:

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

public class JdkProxyExample {

    public static void main(String[] args) {
        // 创建目标对象
        final UserService userService = new UserServiceImpl();

        // 通过JDK Proxy创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
            UserService.class.getClassLoader(),
            new Class[]{UserService.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Before method execution");
                    Object result = method.invoke(userService, args);
                    System.out.println("After method execution");
                    return result;
                }
            });
        // 使用代理对象调用方法
        proxy.saveUser();
    }
}

CGLIB

CGLIB是一个强大的高性能代码生成库,它可以在运行期扩展Java类与实现Java接口(使用ASM技术,效率是不如JDK Proxy的)。CGLIB适用于没有实现任何接口的目标类(当然如果实现了接口也是可以的,因为本质上是生成目标类的子类,所以必须可继承就可以)。

例子:

我们继续使用上面的UserServiceImpl,但这次不实现接口。

java
public class UserServiceImpl {
    public void saveUser() {
        System.out.println("Saving user...");
    }
}

然后,使用CGLIB创建代理:

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

import java.lang.reflect.Method;

public class CglibProxyExample {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("Before method execution");
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("After method execution");
                return result;
            }
        });
        UserServiceImpl userService = (UserServiceImpl) enhancer.create();
        userService.saveUser();
    }
}

Spring AOP

Spring AOP可以自动选择使用JDK Proxy还是CGLIB来创建代理。如果目标对象实现了至少一个接口,则默认使用JDK Proxy;否则使用CGLIB。

Spring AOP利用了AspectJ的规范,然后具体实现动态代理还是依赖JDK Proxy和CGLIB技术。

例子:

首先配置Spring上下文并定义切面:

xml
<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
           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.example.UserService"/>

    <aop:config>
        <aop:aspect ref="loggingAspect">
            <aop:before method="logBefore" pointcut="execution(* com.example..*(..))"/>
        </aop:aspect>
    </aop:config>

    <bean id="loggingAspect" class="com.example.LoggingAspect"/>
</beans>

定义切面类:

java
package com.example;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {
    @Before("execution(* com.example..*(..))")
    public void logBefore() {
        System.out.println("Before method execution");
    }
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore2(JoinPoint joinPoint) {
        // 获取方法签名
        String methodName = joinPoint.getSignature().getName();
        // 获取目标类名称
        String className = joinPoint.getTarget().getClass().getSimpleName();
        // 获取传入参数
        Object[] args = joinPoint.getArgs();

        System.out.println("Executing: " + className + "." + methodName + "()");
        if (args.length > 0) {
            System.out.println("Arguments: ");
            for (Object arg : args) {
                System.out.println("\t" + arg);
            }
        }
    }
}

这样就配置了一个简单的Spring AOP切面,在com.example包下的所有方法执行前打印一条消息。

以上就是JDK Proxy、CGLIB以及Spring AOP的基本示例。在实际项目中,通常推荐直接使用Spring AOP,因为它提供了更高级别的抽象和便利性。

Released under the MIT License.