Java代理模式源码剖析及使用场景

一、介绍

Java的代理模式是一种结构型设计模式,它为另一个对象提供一个代理或占位符,以便控制对该对象的访问。代理模式的主要作用包括:

  1. 远程代理(Remote Proxy):为一个位于不同地址空间的对象提供一个本地的代理,隐藏了对真实对象的访问细节,使得客户端能像访问本地对象一样访问远程对象。

  2. 虚拟代理(Virtual Proxy):在需要创建开销很大的对象时,可以使用虚拟代理来代替真实对象,直到真实对象真正需要时才去创建它。

  3. 保护代理(Protection Proxy):用于控制对一个对象的访问权限,可以让代理完全拒绝或允许访问某些方法。

  4. 智能引用代理(Smart Reference Proxy):当一个对象被引用时,进行一些附加操作,比如计算该对象被引用的次数。

代理模式的核心思想是引入一个中介对象,来包装、增强或替换原有对象。代理对象与真实对象实现相同的接口,在客户端看来是无差别的,但代理对象可以在访问真实对象前后做一些控制和处理工作。

代理模式的主要角色有:

  1. Subject(抽象主题角色):定义了代理和真实对象实现的接口。
  2. Proxy(代理角色):持有一个真实对象的引用,并且提供与真实对象相同的接口,在访问真实对象之前或之后完成自己的逻辑。
  3. RealSubject(真实主题角色):定义代理所代表的真实对象,是我们最终要引用的对象。

下面是一个简单的虚拟代理的示例代码:

//抽象主题
interface Image {
    void display();
}

//真实主题
class RealImage implements Image {
    private final String fileName;
    
    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }
    
    @Override
    public void display() {
        System.out.println("Displaying " + fileName);
    }
    
    private void loadFromDisk(String fileName) {
        System.out.println("Loading " + fileName);
    }
}

//代理
class ProxyImage implements Image {
    private RealImage image = null;
    private final String fileName;
    
    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }
    
    @Override
    public void display() {
        if (image == null) {
            image = new RealImage(fileName);
        }
        image.display();
    }
}

在这个例子中,ProxyImage是一个代理,它有一个字段持有RealImage的引用。只有当display()方法被调用时,才会去创建RealImage对象,从而达到了延迟加载的目的。通过使用代理,可以节省内存并提高性能。

二、影院系统项目案例使用

需求分析:我们有一个影院系统,用户需要先购买电影票,然后才能进入影院观看电影。我们可以使用代理模式来控制对影厅的访问权限。

1.首先,定义一个Theater接口,它包含了观看电影的watchMovie方法:

public interface Theater {
    void watchMovie(String movie);
}

2.实现一个真实的Theater对象,CinemaTheater:

public class CinemaTheater implements Theater {
    @Override
    public void watchMovie(String movie) {
        System.out.println("您正在观看电影: " + movie);
    }
}

3.定义一个代理TheaterProxy,它持有一个CinemaTheater对象的引用,并在watchMovie方法中添加权限控制逻辑:

public class TheaterProxy implements Theater {
    private CinemaTheater theater;
    private boolean ticketBought;

    public TheaterProxy(CinemaTheater theater) {
        this.theater = theater;
    }

    @Override
    public void watchMovie(String movie) {
        if (ticketBought) {
            theater.watchMovie(movie);
        } else {
            System.out.println("您需要先购买电影票!");
        }
    }

    public void buyTicket() {
        System.out.println("您已购买电影票!");
        ticketBought = true;
    }
}

在这个代理类中,我们有一个buyTicket方法,用户需要先调用该方法购买电影票,然后才能通过watchMovie方法观看电影。如果没有购买票,代理会拒绝访问。

4.客户端代码中使用代理:

public class Client {
    public static void main(String[] args) {
        CinemaTheater theater = new CinemaTheater();
        TheaterProxy proxy = new TheaterProxy(theater);

        // 没有买票,无法观看电影
        proxy.watchMovie("复仇者联盟4");

        // 购买票后,可以观看电影
        proxy.buyTicket();
        proxy.watchMovie("复仇者联盟4");
    }
}

输出结果:

您需要先购买电影票!
您已购买电影票!
您正在观看电影: 复仇者联盟4

在这个示例中,TheaterProxy充当了控制对CinemaTheater访问权限的代理。通过引入代理,我们可以在不修改CinemaTheater类的情况下,添加额外的控制逻辑。这样做增强了代码的可维护性和可扩展性。

三、JDK动态代理实现业务

有一个订单服务接口OrderService和一个具体的实现类OrderServiceImpl。在处理订单时,我们需要对订单进行一些额外的操作,比如记录日志、进行权限检查等。这时我们可以使用动态代理来实现这些横切逻辑,而无需修改OrderServiceImpl的代码。

首先,定义OrderService接口和OrderServiceImpl类:

// 订单服务接口
public interface OrderService {
    void createOrder(String name, double amount);
}

// 订单服务实现
public class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder(String name, double amount) {
        System.out.println("Creating order for " + name + ", amount: $" + amount);
        // 执行创建订单的具体逻辑
    }
}

接下来,创建一个InvocationHandler实现类,在这个类中我们可以添加横切逻辑:

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

public class OrderServiceInvocationHandler implements InvocationHandler {
    private final OrderService orderService;

    public OrderServiceInvocationHandler(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 执行前置逻辑
        System.out.println("Performing additional operations before creating order...");

        // 调用目标方法
        Object result = method.invoke(orderService, args);

        // 执行后置逻辑
        System.out.println("Performing additional operations after creating order...");

        return result;
    }
}

invoke方法中,我们可以在调用目标方法之前和之后执行额外的逻辑,如记录日志、权限检查等。

最后,创建一个客户端类,使用Proxy.newProxyInstance方法创建代理对象并调用其方法:

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService orderService = new OrderServiceImpl();

        // 创建代理对象
        OrderService proxyOrderService = (OrderService) Proxy.newProxyInstance(
                OrderService.class.getClassLoader(),
                new Class[]{OrderService.class},
                new OrderServiceInvocationHandler(orderService));

        // 调用代理对象的方法
        proxyOrderService.createOrder("John", 99.99);
    }
}

运行Client类的main方法,输出结果如下:

Performing additional operations before creating order...
Creating order for John, amount: $99.99
Performing additional operations after creating order...

在这个示例中,我们使用JDK动态代理创建了一个OrderService的代理对象proxyOrderService。当调用proxyOrderService.createOrder方法时,实际上会执行OrderServiceInvocationHandlerinvoke方法。在invoke方法中,我们添加了一些前置和后置逻辑,如记录日志。最后,代理对象会调用真正的OrderServiceImpl对象执行业务逻辑。

四、CGLib动态代理实现业务

与JDK动态代理不同的是,CGLib可以对任何类进行代理,而不仅限于实现了接口的类。

有一个商品服务类ProductService和一个具体的实现ProductServiceImpl。在调用商品服务的方法时,能够记录日志和进行权限检查。

使用CGLib需要引入相关的依赖库,例如在Maven项目中添加以下依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

1.定义ProductServiceProductServiceImpl类:

// 商品服务类
public class ProductService {
    public void createProduct(String name, double price) {
        System.out.println("Creating product: " + name + ", price: $" + price);
        // 执行创建商品的具体逻辑
    }
}

// 商品服务实现
public class ProductServiceImpl extends ProductService {
}

2.使用CGLib创建一个方法拦截器ProductServiceMethodInterceptor,在这个拦截器中我们可以添加横切逻辑:

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

import java.lang.reflect.Method;

public class ProductServiceMethodInterceptor implements MethodInterceptor {
    private final Object target; // 目标对象

    public ProductServiceMethodInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 执行前置逻辑
        System.out.println("Performing additional operations before creating product...");

        // 调用目标方法
        Object result = proxy.invokeSuper(obj, args);

        // 执行后置逻辑
        System.out.println("Performing additional operations after creating product...");

        return result;
    }
}

intercept方法中,我们可以在调用目标方法之前和之后执行额外的逻辑,如记录日志、权限检查等。

3.创建一个客户端类,使用CGLib创建代理对象并调用其方法:

import net.sf.cglib.proxy.Enhancer;

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        ProductService productService = new ProductServiceImpl();

        // 创建增强器
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ProductService.class);
        enhancer.setCallback(new ProductServiceMethodInterceptor(productService));

        // 创建代理对象
        ProductService proxyProductService = (ProductService) enhancer.create();

        // 调用代理对象的方法
        proxyProductService.createProduct("iPhone", 999.99);
    }
}

运行Client类的main方法,输出结果如下:

Performing additional operations before creating product...
Creating product: iPhone, price: $999.99
Performing additional operations after creating product...

使用CGLib创建了一个ProductService的代理对象proxyProductService。当调用proxyProductService.createProduct方法时,实际上会执行ProductServiceMethodInterceptorintercept方法。在intercept方法中,我们添加了一些前置和后置逻辑,如记录日志。最后,代理对象会调用真正的ProductServiceImpl对象执行业务逻辑。

总结:使用CGLib动态代理可以为任何类添加横切逻辑,而不仅限于实现了接口的类。与JDK动态代理相比,CGLib动态代理的实现方式更加底层,它通过字节码技术为目标类创建一个子类,并在子类中采用方法拦截的方式来实现代理。

五、JDK动态代理和CGLib动态代理源码分析

在Java中,代理模式主要通过JDK动态代理和CGLib动态代理两种方式来实现。下面我将分别介绍这两种实现方式的源码。

  1. JDK动态代理

JDK动态代理主要通过java.lang.reflect.Proxy类实现。该类提供了一个静态方法newProxyInstance用于创建代理对象。我们来看一下newProxyInstance方法的实现:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) {
    // 一些参数校验...

    // 获取代理类的构造器
    Class<?> cl = getProxyClass(loader, interfaces);
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    
    // 创建代理对象
    final InvocationHandler ih = h;
    return cons.newInstance(new Object[]{h});
}

这个方法首先通过getProxyClass方法获取代理类的构造器,然后通过反射创建代理对象。其中getProxyClass方法主要做了两件事:

  1. 缓存代理类构造器,避免重复生成
  2. 使用ProxyClassFactory生成代理类的字节码

生成的代理类实现了被代理对象的所有接口,并继承了Proxy类。当调用代理对象的方法时,实际上是调用了Proxy类的invoke方法,由InvocationHandler处理实际逻辑。

  1. CGLib动态代理

CGLib是一个第三方库,它可以在运行时生成被代理对象的子类作为代理对象。这种方式不需要被代理对象实现接口,因此有更好的通用性。来看一下CGLib的实现:

Enhancer enhancer = new Enhancer();
// 设置被代理对象
enhancer.setSuperclass(RealSubject.class);
// 设置回调函数
enhancer.setCallback(new MethodInterceptor() {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 执行前置处理
        // ...

        // 调用目标方法
        Object result = proxy.invokeSuper(obj, args);

        // 执行后置处理
        // ...

        return result;
    }
});

// 创建代理对象
RealSubject proxySubject = (RealSubject) enhancer.create();

在上面的代码中,Enhancer是CGLib的核心类,用于生成代理对象。首先通过setSuperclass方法设置被代理对象的类,然后通过setCallback方法设置一个MethodInterceptor对象作为回调函数,该对象中的intercept方法会在代理对象的任意方法调用时被执行。最后通过create方法创建代理对象。

代理对象是被代理对象的子类,当执行代理对象的方法时,实际上会执行MethodInterceptor中的intercept方法。在该方法中,可以通过proxy.invokeSuper调用目标方法,也可以在目标方法执行前后加入其他逻辑。

六、Spring AOP框架源码分析

1. 基于JDK动态代理

对于实现了接口的目标对象,Spring AOP使用JDK动态代理来创建代理对象。核心源码在JdkDynamicAopProxy类中:

// JdkDynamicAopProxy.java
public Object getProxy(ClassLoader classLoader) {
    // 使用Proxy创建动态代理
    return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    // ...
    Object retVal = invocation.proceed();
    // ...
    return retVal;
}

getProxy方法中,Spring使用Proxy.newProxyInstance创建了一个代理对象,并将JdkDynamicAopProxy自身作为InvocationHandler的实例传入。

当调用代理对象的方法时,实际上会执行JdkDynamicAopProxyinvoke方法。在该方法中,Spring会创建一个ReflectiveMethodInvocation对象,通过proceed方法执行目标对象的方法。在proceed方法执行前后,Spring会根据配置插入不同的通知(Advice),从而实现AOP的功能。

2. 基于CGLib动态代理

对于没有实现接口的目标对象,Spring AOP使用CGLib动态代理来创建代理对象。核心源码在CglibAopProxy类中:

// CglibAopProxy.java
public Object getProxy(ClassLoader classLoader) {
    // 创建Enhancer
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(this.advised.getTargetClass());
    enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new ClassLoaderAwareUndecidedStrategy(classLoader));

    // 设置回调
    Callback[] callbacks = getCallbacks(this.advised);
    enhancer.setCallbackFilter(new CglibAopProxy.DynamicAdvisedInterceptor(this.advised.getConfigurationOnlyCopy()));
    enhancer.setCallbacks(callbacks);

    // 创建代理对象
    return enhancer.create();
}

在上面的代码中,Spring使用CGLib的Enhancer为目标对象创建一个子类作为代理对象。Enhancer设置了目标对象的父类、接口等信息,并设置了一个DynamicAdvisedInterceptor作为回调。

当调用代理对象的方法时,实际上会执行DynamicAdvisedInterceptorintercept方法,在该方法中,Spring会根据配置决定是执行目标方法还是执行通知方法。如果执行目标方法,则通过methodProxy.invokeSuper调用目标对象的方法。在目标方法执行前后,Spring会根据配置插入不同的通知(Advice),从而实现AOP的功能。

3. 选择代理方式

Spring AOP会根据目标对象是否实现了接口来选择使用JDK动态代理还是CGLib动态代理:

// AopProxyFactory.java
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // ...
    if (config.isInterfaceProxied()) {
        // 使用JDK动态代理
        return new JdkDynamicAopProxy(config);
    } else {
        // 使用CGLib动态代理
        return new ObjenesisCglibAopProxy(config);
    }
}

在上面的代码中,如果目标对象实现了接口,则使用JDK动态代理;否则使用CGLib动态代理。

通过代理模式的应用,Spring AOP能够在不修改目标对象的情况下,为其增加横切逻辑(如日志、事务管理等)。代理对象充当了中介的角色,在目标方法执行前后插入了通知代码,从而实现了横切关注点的织入。这种设计模式提高了代码的可维护性和可扩展性,符合开闭原则。

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java语录精选

你的鼓励是我坚持下去的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值