Intro 代理模式是一种设计模式,允许一个对象充当另一个对象的替代或中介。代理对象可以控制对目标对象的访问,在不修改原目标对象核心逻辑的情况下,实现额外的功能,如日志记录、权限控制或延迟加载,甚至监控等;
代理模式的应用场景
AOP : 通过代理类来拦截目标对象方法的调用,实现对目标对象方法的增强。权限校验 :通过代理类来控制对某些方法或资源的访问。日志记录 :在调用目标对象的方法时,记录相关日志信息;性能监控 :在代理类中记录方法的执行时间,便于分析性能瓶颈。懒加载 :通过代理对象延迟初始化某些资源,以节省内存或提高性能。远程代理 :为远程调用提供一个本地代理,使得本地方法调用就像调用远程服务一样。通过在代理中实现功能(如日志打印、性能监控),而不是直接修改源代码,能够有效地将功能与业务逻辑解耦
单一职责原则 : 将日志记录、性能监控等功能放在代理中,目标类专注于业务逻辑。这样,业务逻辑的实现和横切关注点(Cross-Cutting Concerns) 之间的依赖关系被削弱,符合单一职责原则。未来如果日志记录或监控的需求变化,需要改变日志记录的格式、级别或存储方式等,那么只对代理类进行修改即可,而无需改动每个目标类的代码代码复用与一致性 : 代码复用:代理类可以被多个目标类共享,避免了在每个目标类中都实现相同的功能(如日志记录),减少冗余代码 确保一致性:在所有目标类中应用相同的日志或监控逻辑;所有日志都遵循相同的格式,更易于后期分析和监控易于扩展和修改 功能扩展:如果需要添加新功能(如异常处理、输入验证等),可以在代理类中实现,而无需干扰目标类的业务逻辑。例如,可以在日志记录的基础上添加性能监控的功能,只需扩展 invoke 方法 灵活性:可以在运行时动态决定使用哪个代理,例如,可以使用不同的代理实现来添加不同的功能,避免修改目标类,这在团队合作中可以减少冲突。
在 Java 中,代理模式主要分为两种:静态代理 和动态代理 。这两种类型的代理虽然都能够控制对目标对象的访问,但它们的实现方式有所不同。
Static Proxy 假设你有一个接口 BankAccount
,它定义了 3 个方法。其中一个方法为withdraw()
假设有一个实现了这个接口的类 BankAccountImpl
,它负责具体的取钱操作。现在,你可以创建一个代理类BankAccountProxy
,它也实现了该接口,但在调用 BankAccountImpl
的withdraw
方法前后,增加一些额外的操作,比如记录日志或进行权限检查。
Steps
定义接口,BankAccount Interface
定义 class 实现接口,BankAccountImpl implements BankAccount
编写代理类,BankAccountProxy
调用BankAccountProxy
的构造器,创建静态代理对象实例 package proxydemo ;
// 账户服务接口,定义金融操作
public interface BankAccount {
void viewBalance (); // 查看余额
void withdraw (); // 取款
void deposit (); // 存款
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package proxydemo ;
// 账户服务实现类
public class BankAccountImpl implements BankAccount {
@ Override
public void viewBalance () {
System . out . println ( " Displaying account balance. " );
}
@ Override
public void withdraw () {
System . out . println ( " Withdrawing funds from account. " );
}
@ Override
public void deposit () {
System . out . println ( " Depositing funds into account. " );
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package proxydemo ;
// 账户服务代理类
// 代理类负责控制对实际账户服务操作的访问。只有管理员角色才能执行
// withdraw()、deposit()和 transfer()操作,而普通用户只能查看余额
// 和退出服务。
public class BankAccountProxy implements BankAccount {
private BankAccountImpl BankAccount ;
private String userRole ;
// 构造方法,传入实际的账户服务对象和用户角色
public BankAccountProxy ( BankAccountImpl BankAccount , String userRole ) {
this. BankAccount = BankAccount ;
this. userRole = userRole ;
System . out . println ( " BankAccountProxy initialized with user role: " + userRole );
}
@ Override
public void viewBalance () {
System . out . println ( " User with role " + userRole + " is viewing balance. " );
BankAccount . viewBalance (); // 所有用户都可以查看余额
}
@ Override
public void withdraw () {
if ( " ADMIN " . equals ( userRole )) {
BankAccount . withdraw (); // 只有管理员可以取款
} else {
System . err . println ( " Access denied for user with role " + userRole + " : Only ADMIN can withdraw funds. " );
}
}
@ Override
public void deposit () {
if ( " ADMIN " . equals ( userRole )) {
BankAccount . deposit (); // 只有管理员可以存款
} else {
System . err . println ( " Access denied for user with role " + userRole + " : Only ADMIN can deposit funds. " );
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package proxydemo ;
public class Main {
public static void main ( String [] args ) {
BankAccountImpl BankAccount = new BankAccountImpl ();
// 创建管理员用户的代理
System . out . println ( " Admin user: " );
BankAccount adminProxy = new BankAccountProxy ( BankAccount , " ADMIN " );
adminProxy . viewBalance (); // 管理员可以查看余额
adminProxy . withdraw (); // 管理员可以取款
adminProxy . deposit (); // 管理员可以存款
// 创建普通用户的代理
System . out . println ( " \n Regular user: " );
BankAccount userProxy = new BankAccountProxy ( BankAccount , " USER " );
userProxy . viewBalance (); // 普通用户可以查看余额
userProxy . withdraw (); // 普通用户无法取款
userProxy . deposit (); // 普通用户无法存款
}
}
Admin user:
BankAccountProxy initialized with user role: ADMIN
User with role ADMIN is viewing balance.
Displaying account balance.
Withdrawing funds from account.
Depositing funds into account.
Regular user:
BankAccountProxy initialized with user role: USER
User with role USER is viewing balance.
Displaying account balance.
Access denied for user with role USER: Only ADMIN can withdraw funds.
Access denied for user with role USER: Only ADMIN can deposit funds.
优点 :
可以在不修改BankAccountImpl
类的情况下,添加新的功能;代理类与目标类之间的耦合度低。 缺点 :
如果目标类有很多方法,手动编写代理类会非常繁琐。 代理类的代码量较多,且每次更改都需要手动维护。 Dynamic Proxy 静态代理是在编译期间就需要完成的,即代理类的字节码都已经确定,每个类都要创建一个代理类,导致代码冗余。动态代理不需要手动编写每个代理类,而是在运行时才会生成代理类,并写入 class 文件,相较于静态代理代码冗余更少。
Java 中的动态代理主要有两种方式:
JDK 动态代理 :基于接口的代理,它只能代理实现了接口的类。CGLIB 动态代理 :基于继承的代理,它可以代理没有实现接口的类。JDK 动态代理 Java JDK 动态代理是基于 java.lang.reflect.Proxy
和 InvocationHandler
实现的。
当调用代理类的方法时,会将其委托给InvocationHandler
实例的invoke
方法来运行。 Proxy
类基于目标类实现的接口和 InvocationHandler
接口,通过反射机制来创建代理类对象。JDK 动态代理的原理在这里不赘述,感兴趣可参考彻底明白 JDK 动态代理的底层原理
Steps
定义接口,BankAccount Interface
定义 class 实现接口,BankAccountImpl implements BankAccount
创建InvocationHandler
接口的实现类 使用java.lang.reflect.Proxy
的newProxyInstance
方法创建动态代理对象的实例 package proxydemo ;
// 账户服务接口,定义金融操作
public interface BankAccount {
void deposit ( double amount );
void withdraw ( double amount );
double getBalance ();
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package proxydemo ;
// 账户服务实现类
public class BankAccountImpl implements BankAccount {
private double balance ;
@ Override
public void deposit ( double amount ) {
balance += amount ;
System . out . println ( " Deposited: " + amount );
}
@ Override
public void withdraw ( double amount ) {
if ( balance >= amount ) {
balance -= amount ;
System . out . println ( " Withdrew: " + amount );
} else {
System . out . println ( " Insufficient balance " );
}
}
@ Override
public double getBalance () {
return balance ;
}
}
定义 JDK 动态代理类
InvocationHandler
是 Java 反射包 java.lang.reflect 中的一个接口,用于处理代理实例的方法调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import java . lang . reflect . InvocationHandler ;
import java . lang . reflect . Method ;
public class BankAccountProxyHandler implements InvocationHandler {
private final BankAccount target ;
public BankAccountProxyHandler ( BankAccount target ) {
this. target = target ;
}
/**
* 当代理实例上的方法被调用时,这个方法会被触发。它接收三个参数:代理实例、被调用的方法对象和方法参数数组。该方法负责处理方法调用并返回结果
*
* @param proxy 代理对象本身。通常在方法中不会直接使用它。
* @param method 表示正在调用的目标方法。它是一个 Method 对象,封装了该方法的元数据信息(如方法名、参数类型等)。
* @param args 目标方法调用时传入的实际参数,是一个 Object[] 数组。如果没有参数,则为 null。
* @return method的返回值(如果有返回值)。
* @throws Throwable 如果调用目标对象的方法抛出异常,则会向上抛出异常。
*/
@ Override
public Object invoke ( Object proxy , Method method , Object [] args ) throws Throwable {
// 可以在调用方法之前执行部分操作;
// 例如,在目标方法调用前添加日志信息,记录执行时间等等
long startTime = System . currentTimeMillis ();
System . out . println ( " Method " + method . getName () + " is called with arguments: " + ( args != null ? java . util . Arrays . toString ( args ) : " [] " ));
// 使用 Method 对象调用目标对象(真实对象)的方法,并传递参数 args
Object result = method . invoke ( target , args );
// 同样地,可以在调用方法之后执行部分操作;
// 例如,在方法调用后添加日志
long endTime = System . currentTimeMillis ();
System . out . println ( " Method " + method . getName () + " executed in " + ( endTime - startTime ) + " ms " );
// 返回调用结果(如果方法有返回值,则返回该值;如果没有返回值,返回 null)
return result ;
}
}
Proxy 类是 Java 反射 API 中的一个重要类,位于 java.lang.reflect
包内。它的主要作用是用来动态创建代理对象,即在运行时生成一个实现特定接口的代理类
Proxy.newProxyInstance
是创建动态代理对象的核心方法。它接受三个参数,分别是:
类加载器 ClassLoader
:用于加载代理类的类加载器。通常是目标类的类加载器。 接口数组 Class<?>[]
:指定代理类要实现的接口。代理对象将实现这些接口中的所有方法。 InvocationHandler
实例:一个 InvocationHandler 的实例,它负责处理代理对象的方法调用。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package proxydemo ;
import java . lang . reflect . Proxy ;
public class Main {
public static void main ( String [] args ) {
// 创建银行账户的真实对象
BankAccount realAccount = new BankAccountImpl ();
// 创建代理对象
BankAccount proxyAccount = ( BankAccount ) JdkProxyFactory . getProxy ( realAccount );
// 使用代理对象执行方法
proxyAccount . deposit ( 100 );
proxyAccount . withdraw ( 50 );
System . out . println ( " Balance: " + proxyAccount . getBalance ());
}
}
class JdkProxyFactory {
public static Object getProxy ( Object target ) { // target: real object which implements BankAccount Interface
// 通过 Proxy 类创建动态代理对象
// 参数1:类加载器,通常使用目标对象的类加载器
// 参数2:目标对象实现的接口,可指定多个
// 参数3:InvocationHandler 实现类
return Proxy . newProxyInstance (
target . getClass (). getClassLoader (),
target . getClass (). getInterfaces (),
new BankAccountProxyHandler (( BankAccount ) target ));
}
}
Method deposit is called with arguments: [100.0]
Deposited: 100.0
Method deposit executed in 20 ms
Method withdraw is called with arguments: [50.0]
Withdrew: 50.0
Method withdraw executed in 1 ms
Method getBalance is called with arguments: []
Method getBalance executed in 3 ms
Balance: 50.0
CGLIB 动态代理 CGLIB(Code Generation Library) 依赖于 ASM 库,允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。
CGLIB 在 Spring 框架中有所应用 Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a common open-source class definition library (repackaged into spring-core). If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used. All of the interfaces implemented by the target type are proxied. If the target object does not implement any interfaces, a CGLIB proxy is created.
CGLIB 与 JDK 动态代理的区别
代理对象类型:JDK 动态代理:只能代理实现了接口的类。 CGLIB 动态代理:可以代理普通的类(没有实现接口的类)。 实现方式:JDK 动态代理:通过反射实现,代理类实现目标对象的接口。 CGLIB 动态代理:通过生成目标类的子类来实现。 限制:CGLIB 不能代理 final 类和 final 方法,因为它是通过生成子类来实现的,而 final 方法不能被重写。 Steps
引入 CGLIB 依赖 创建目标类,BankAccount
创建 MethodInterceptor 实现类:处理方法拦截逻辑。 生成代理对象:通过 CGLIB 创建代理对象的实例 java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @a67c67e
Your CGLIB proxy usage may face limitations with the JDK 9+ platform module system. As a typical case, you cannot create a CGLIB proxy for a class from the java.lang package when deploying on the module path. Such cases require a JVM bootstrap flag –add-opens=java.base/java.lang=ALL-UNNAMED which is not available for modules. 或者,可以换 jdk8
public class BankAccount {
public void deposit ( double amount ) {
System . out . printf ( " Depositing: " + amount );
}
public void withdraw ( double amount ) {
System . out . printf ( " Withdrawing: " + amount );
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import net . sf . cglib . proxy . MethodInterceptor ;
import net . sf . cglib . proxy . MethodProxy ;
import java . lang . reflect . Method ;
public class BankAccountInterceptor implements MethodInterceptor {
/**
* 拦截器方法
*
* @param proxyObj 代理对象
* @param method 被代理的方法
* @param args 方法的参数
* @param proxy 方法的代理对象,用于调用父类的方法
* @return 方法的返回值
* @throws Throwable 抛出的异常
*/
@ Override
public Object intercept ( Object proxyObj , Method method , Object [] args , MethodProxy proxy ) throws Throwable {
System . out . println ( " Method " + method . getName () + " is being called with arguments: " + java . util . Arrays . toString ( args ));
Object res = proxy . invoke ( proxyObj , args );
System . out . println ( " Method " + method . getName () + " execution completed " );
return res ;
}
}
import net . sf . cglib . proxy . Enhancer ;
public class Main {
public static void main ( String [] args ) {
// 创建动态代理类
Enhancer enhancer = new Enhancer ();
enhancer . setSuperclass ( BankAccount . class );
enhancer . setCallback ( new BankAccountInterceptor ());
// 创建代理对象
BankAccount proxyAccount = ( BankAccount ) enhancer . create ();
// 通过代理对象调用方法
proxyAccount . deposit ( 100 );
proxyAccount . withdraw ( 50 );
}
}
Method deposit is being called with arguments: [100.0]
Depositing: 100.0
Method deposit execution completed.
Method withdraw is being called with arguments: [50.0]
Withdrawing: 50.0
Method withdraw execution completed.
Ref Proxy Java SE 21 & JDK 21