代理模式是一种结构型设计模式,其目的是通过创建一个代理对象来控制对另一个对象的访问。代理对象充当了被代理对象的中间人,客户端通过代理对象来间接访问被代理对象,从而可以在访问被代理对象前后进行一些额外的操作。
代理模式通常涉及三种角色:
抽象接口(Subject):定义了被代理对象和代理对象的共同接口,客户端通过这个接口来访问被代理对象。
被代理对象(Real Subject):实际执行业务逻辑的对象,客户端最终想要访问的对象。
代理对象(Proxy):实现了抽象接口,并在内部维护了一个被代理对象的引用。代理对象对客户端的请求进行一些额外处理,然后将请求委派给被代理对象。
代理模式可以应用于多种场景,包括:
远程代理(Remote Proxy):在客户端和远程对象之间建立一个代理对象,客户端通过代理对象来访问远程对象,实现远程调用的功能。
虚拟代理(Virtual Proxy):用于延迟加载对象,代理对象在真正需要时才创建和加载被代理对象,从而节省系统资源。
保护代理(Protection Proxy):控制对敏感对象的访问,代理对象在访问被代理对象前进行权限检查,以确保客户端具有足够的权限。
缓存代理(Cache Proxy):在访问对象时添加缓存机制,代理对象在访问被代理对象前检查缓存,如果存在缓存则直接返回缓存结果,从而提高系统性能。
代理模式可以提高系统的灵活性、安全性和性能,但同时也会增加系统的复杂性。因此,在设计中需要根据实际需求和场景来选择是否使用代理模式。
动态代理和静态代理都是代理模式的不同实现方式,它们在实现上有一些区别。
静态代理是在编译时就已经确定了代理类和被代理类的关系。在静态代理中,代理类是直接编码实现的,它和被代理类实现了相同的接口或继承了相同的父类,从而可以实现相同的方法。静态代理中的代理类需要对被代理类的方法进行封装,并且在方法调用前后可以执行一些额外的操作。
静态代理的优点是实现简单,易于理解和控制。但缺点是代理类和被代理类之间的关系在编译时就已经确定,因此不够灵活,如果需要代理多个类的多个方法,就需要编写大量的代理类。
package com.test.test_proxy;
//静态代理
public class StaticProxyDemo {
public static void main(String[] args) {
RealMovie realmovie = new RealMovie();
Movie movie = new Cinema(realmovie);
movie.play();
}
}
interface Movie {
void play();
}
//创建被代理类
class RealMovie implements Movie {
@Override
public void play() {
System.out.println("您正在观看电影 《肖申克的救赎》");
}
}
//创建代理类
class Cinema implements Movie {
RealMovie movie;
public Cinema(RealMovie movie) {
this.movie = movie;
}
@Override
public void play() {
System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
movie.play();
System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
}
}
动态代理是在运行时动态生成代理类的一种代理方式。Java 中的动态代理是通过反射机制来实现的,它可以在运行时动态地创建代理类和对象,并在代理类中动态地处理被代理类的方法调用。
动态代理的优点是减少了编码工作量,提高了代码的灵活性。缺点是由于动态代理使用了反射机制,因此性能可能相对较低。
JDK 动态代理是 Java 提供的一种动态代理实现方式,它允许在运行时动态生成代理类和对象,并通过代理类来间接调用目标对象的方法。JDK 动态代理是基于接口的代理,它要求目标对象实现一个或多个接口。
JDK 动态代理的核心类和接口包括:
java.lang.reflect.Proxy
:代理类的主要类,用于生成代理类和对象。java.lang.reflect.InvocationHandler
:代理对象的调用处理程序接口,定义了代理对象中的方法调用处理逻辑。java.lang.reflect.Method
:代表目标对象的方法,被动态代理的接口中的方法都将被转发给 InvocationHandler 接口中的 invoke 方法进行处理。动态代理的基本步骤如下:
newProxyInstance()
创建代理对象,传入目标对象的类加载器、目标对象实现的接口列表和 InvocationHandler 对象。下面是一个简单的示例代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface Hello {
void sayHello();
}
// 实现 InvocationHandler(调用处理器) 接口
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method invocation");
Object result = method.invoke(target, args);
System.out.println("After method invocation");
return result;
}
}
public class Main {
public static void main(String[] args) {
//创建被代理对象
Hello realSubject = new Hello() {
@Override
public void sayHello() {
System.out.println("Hello World");
}
};
//创建 InvocationHandler(调用处理器)对象
MyInvocationHandler handler = new MyInvocationHandler(realSubject);
// 创建代理对象
/*param1:被代理类的类加载器
* param2:被代理类的接口,如果有多个,就是数组形式传入
* param3:InvocationHandler(调用处理器)对象
* */
Hello proxy = (Hello) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler
);
// 调用代理对象的方法
proxy.sayHello();
}
}
输出:
Before method invocation
Hello World
After method invocation
在这个示例中,Hello
接口代表目标对象,MyInvocationHandler
实现了 InvocationHandler 接口,定义了对目标对象方法的调用处理逻辑。通过 Proxy.newProxyInstance()
方法创建代理对象,该方法需要传入目标对象的类加载器、目标对象实现的接口列表和 InvocationHandler 对象。最后,通过代理对象调用方法时,方法调用会被转发给 InvocationHandler 的 invoke() 方法进行处理。
CGLIB(Code Generation Library)是一个基于ASM开源项目,通过字节码生成技术来动态生成代理类的库。相比于JDK动态代理,CGLIB可以代理没有实现接口的类,它能够生成目标类的子类作为代理类。
CGLIB 动态代理的基本原理是:在运行时动态生成一个目标类的子类,并重写其中的方法来实现代理逻辑。被代理的方法在子类中被重写,并在重写的方法中调用代理逻辑。因此,使用 CGLIB 动态代理时,目标类无需实现接口,而代理类将会成为目标类的子类。
下面是一个简单的示例代码,演示了如何使用 CGLIB 动态代理:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 目标类
class RealSubject {
public void sayHello() {
System.out.println("Hello World");
}
}
// 方法拦截器
class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method invocation");
Object result = proxy.invokeSuper(obj, args); // 调用目标类的方法
System.out.println("After method invocation");
return result;
}
}
public class Main {
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 设置目标类
enhancer.setSuperclass(RealSubject.class);
// 设置回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
RealSubject proxy = (RealSubject) enhancer.create();
// 调用代理对象的方法
proxy.sayHello();
}
}
输出:
Before method invocation
Hello World
After method invocation
在这个示例中,我们首先定义了一个目标类 RealSubject
,然后通过 Enhancer
类创建一个代理类。通过 setSuperclass()
方法设置目标类,通过 setCallback()
方法设置方法拦截器。最后,通过 create()
方法创建代理对象。当调用代理对象的方法时,方法调用会被转发给方法拦截器进行处理。
需要注意的是,CGLIB 动态代理会生成目标类的子类作为代理类,因此目标类和代理类之间是继承关系,而不是实现了相同的接口关系。
JDK 动态代理和 CGLIB 动态代理是两种不同的动态代理实现方式,它们在实现上有一些区别,主要包括以下几点:
代理对象的类型:
实现原理:
java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口实现的,它在运行时动态生成代理对象,并通过反射机制来调用目标对象的方法。性能:
应用场景:
综上所述,JDK 动态代理和 CGLIB 动态代理各有优劣,适用于不同的场景。在选择时,可以根据具体的需求和情况来决定使用哪种动态代理方式。
框架中,像servlet的filter、包括spring提供的aop以及struts2的拦截器都使用了动态代理功能。我们日常看到的mybatis分页插件,以及日志拦截、事务拦截、权限拦截这些几乎全部由动态代理的身影。