1.简介
代理模式 是常用的Java设计模式,该模式的特点是代理类与委托类共享相同的接口。代理类主要负责预处理消息、过滤消息、将消息转发给委托类,并在事后处理消息等。代理类与委托类之间通常存在关联关系,一个代理类对象与一个委托类对象关联。代理类对象本身不真正实现服务,而是通过调用委托类对象的相关方法来提供特定的服务。
代理模式主要包括以下角色:
- 抽象主题(Subject):定义代理类和委托类(RealSubject)的共同接口。这个接口规定了代理类和委托类必须实现的方法,代理类可以通过这个接口来调用委托类的方法。
- 真实主题(RealSubject):实现抽象主题,定义委托类的操作。它包含了实际的业务逻辑,是客户端实际需要调用的对象。
- 代理类(Proxy):实现抽象主题,持有对委托类的引用,并在其方法被调用时进行控制。代理类在调用委托类的方法前后可以添加一些额外的功能,如日志记录、权限控制、事务处理等。
2.静态代理
静态代理: 在编译时期确定代理类和目标类的关系,代理类和目标类都要实现同一个接口。
定义一个简单的例子:假如一个租客需要租房子,他可以直接通过房东(委托类) 去租房,也可以经过中介(代理类) 去租房。房东(realsubject)和中介(proxy)都需要实现subject接口实现房子出租。
- 确定接口具体行为
首先创建一个Person接口。这个接口是房东和中介的共同接口,租房行为可以被中介代理。
java
public interface Person {
// 出租房子
void hire();
}
- 编写委托类业务逻辑
创建一个委托类,实现subject接口,并编写业务逻辑
java
public class Landlord implements Person{
// 房东直售
@Override
public void hire() {
System.out.println("出租,收款1000元");
}
}
- 代理类增强方法
创建一个代理类,同样实现subject接口,对委托类的方法进行增强
java
public class Agency implements Person{
private final Landlord landlord;
public Agency(Landlord landlord) {
this.landlord = landlord;
}
// 中介出租,额外收取费用
@Override
public void hire() {
System.out.println("开始办理租房手续");
landlord.hire();
System.out.println("额外收取中介费200元");
}
}
- 测试类使用代理对象
java
public class Main {
public static void main(String[] args) {
// 获取代理对象
Landlord landlord = new Landlord();
Agency agency = new Agency(landlord);
// 使用代理方法
agency.hire();
}
}
输出结果如下,可以发现对方法进行了增强
3.动态代理
动态代理: 在程序运行时动态生成代理类(subject的实现类)。
相比于静态代理, 动态代理的优势在于其较高的灵活性和代码复用性。同一个动态代理处理器可以代理多个目标对象,而静态代理则需要创建大量的代理类。
在Java中,可以通过JDK和CGLIB实现动态代理。
3.1.JDK动态代理
实现原理
JDK动态代理:在java的java.lang.reflect
包下提供了Proxy
类和InvocationHandler
接口,利用这两个类和接口,可以在运行时动态生成指定接口的实现类。
Proxy
类就是用来创建一个代理对象的类,在JDK动态代理中我们需要使用其newProxyInstance
方法。
java
public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);
这个方法的作用就是创建一个代理类对象,它接收以下三个参数:
- loader:一个ClassLoader对象,指定哪个ClassLoader将加载生成的代理类。
- interfaces:一个Interface对象数组,定义代理对象实现的一组接口,代理类可以调用这些接口中声明的所有方法。
- h:一个InvocationHandler对象,指定代理对象的方法调用将关联到哪个InvocationHandler对象,由它处理实际的方法调用。
InvocationHandler
接口提供了一个invoke
方法,当代理对象调用方法时,invoke
方法会被调用。通过实现这个接口,可以在方法调用前后添加自定义逻辑。
java
/**
* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* args:指代代理对象方法传递的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
代码实现
- 确定接口具体行为
这里我们设计两个接口
java
public interface HouseServiceA {
// 出租房子
void hire();
}
java
public interface HouseServiceB {
// 转租房子
void sublet();
}
- 编写委托类业务逻辑
委托类实现这两个接口,并且定义具体的业务逻辑
java
public class HouseServiceImpl implements HouseServiceA, HouseServiceB {
@Override
public void hire() {
System.out.println("出租房子");
}
@Override
public void sublet() {
System.out.println("转租房子");
}
}
- 编写代理工厂代码
代理工厂负责在运行时动态生成代理类,需要实现InvocationHandler
接口重写invoke
方法来做方法增强,使用Proxy
类创建代理对象。
java
/**
* JDK动态代理实现InvocationHandler接口
*/
public class HouseFactory implements InvocationHandler {
private Object target;
//定义获取代理对象的方法(将目标对象传入进行代理)
public Object getJDKProxy(Object target){
//为目标对象target赋值
this.target = target;
//JDK动态代理只能针对实现了接口的类进行代理,因此需要传递接口的class
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class<?>[]{HouseServiceA.class, HouseServiceB.class},
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理开始");
// 调用invoke方法,result存储该方法的返回值
Object result = method.invoke(target, args);
System.out.println("JDK动态代理结束");
return result;
}
}
- 测试类使用代理对象
在实际使用时,只需要将工厂类生成的代理对象转为需要的代理类,即可实现同时代理多个接口的方法。
java
public class Main {
public static void main(String[] args) {
// 创建代理类工厂
HouseFactory factory = new HouseFactory();
// 动态生成接口A的代理类
HouseServiceA houseProxyA = (HouseServiceA) factory.getJDKProxy(new HouseServiceImpl());
houseProxyA.hire();
System.out.println("=====================");
// 动态生成接口B的代理类
HouseServiceB houseProxyB = (HouseServiceB) factory.getJDKProxy(new HouseServiceImpl());
houseProxyB.sublet();
}
}
返回结果:
3.2.CGLIB动态代理
实现原理
CGLIB动态代理:依赖于ASM下的Enhancer
类和MethodInterceptor
接口,可以在运行时动态生成目标类的子类。
Enhancer
类是用来创建代理对象的类。在CGLIB动态代理中,我们需要使用其create
方法。
java
public class Enhancer {
public Object create();
// 其他方法
}
这个方法的作用是创建一个代理类对象,通常还需要设置以下几个属性:
- setSuperclass:设置被代理的目标类,CGLIB通过生成目标类的子类来实现代理。
- setCallback:设置回调接口,用于处理代理对象的方法调用。
MethodInterceptor
接口提供了一个intercept
方法,当代理对象调用方法时,intercept
方法会被调用。通过实现这个接口,可以在方法调用前后添加自定义逻辑。
java
/**
* obj: 代理对象
* method: 被代理的方法
* args: 方法的参数
* proxy: 用于调用父类方法的代理
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
与JDK动态代理不同,CGLIB代理不需要目标类实现接口。CGLIB通过生成目标类的子类并重写方法来实现代理,因此它可以代理没有实现接口的类。
代码实现
- 首先导入依赖
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
- 编写委托类业务逻辑(无需实现接口)
java
public class HouseServiceImpl {
public void hire() {
System.out.println("出租房子");
}
public void sublet() {
System.out.println("转租房子");
}
}
- 测试类使用代理对象
java
public class Main {
public static void main(String[] args) {
HouseFactory factory = new HouseFactory();
HouseServiceImpl cglibProxy = (HouseServiceImpl) factory.getCglibProxy(new HouseServiceImpl());
cglibProxy.hire();
System.out.println("=====================");
cglibProxy.sublet();
}
}
返回结果:
4.总结
静态代理
实现方式:
- 由程序员显式编写代理类。代理类在编译期确定,编译前就存在代理类的字节码文件。
- 需要实现与目标对象相同的接口,且在代理类中显式调用目标对象的方法。
优点:
- 结构简单,容易理解。
缺点:
- 每增加一个接口,都需要编写对应的代理类,代码量大,维护成本高。静态代理类在编译期生成,灵活性差。
JDK动态代理
实现方式:
- 使用
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。 - 代理类在运行时动态生成,不需要显式编写代理类。
优点:
- 代理类在运行时生成,增加了代码的灵活性和可维护性。
缺点:
- 只能代理实现了接口的类,不能代理没有实现接口的类。
CGLIB动态代理
实现方式:
- 使用CGLIB(Code Generation Library),依赖ASM字节码生成框架。
- 代理类在运行时动态生成,不需要显式编写代理类。
优点:
- 不要求目标类实现接口,可以代理普通的类。
- 性能通常比JDK动态代理更高,尤其在代理大量方法调用时更为显著。
缺点:
- 不能代理
final
类和final
方法。
适用场景:
-
静态代理:需要手动编写代理类,适用于简单的场景,但不够灵活,维护成本高。
-
JDK动态代理:适用于实现了接口的类,代理类在运行时生成,灵活性高,但只能代理接口。
-
CGLIB动态代理 :适用于没有实现接口的类,性能优于JDK动态代理,但不能代理
final
类和final
方法,且使用复杂度稍高。