目录
一、基础概念
二、UML类图
1、静态代理类图
2、动态代理类图
三、角色设计
四、案例分析
1、静态代理
2、JDK动态代理
3、Cglib动态代理
五、总结
代理模式是一种结构型设计模式,它用一个代理对象来封装一个目标对象,通常是为了对目标对象的访问进行增强或控制。主要作用是扩展目标对象的功能,比如延迟加载、访问控制、远程访问和日志记录等。
角色 | 描述 |
---|---|
抽象主题角色 | 定义了实际主题和代理对象必须实现的接口 |
实际主题角色 | 实现了抽象主题中的业务逻辑,是代理对象所代表的实际对象 |
代理角色 | 内部含有对实际主题的引用,从而可以操作实际主题对象;代理对象提供与实际主题相同的接口,其接口的实现方式可以在执行前后做一些额外的操作 |
客户端角色 | 通过抽象主题角色访问实际主题角色,这样可以在任何时刻都用代理对象替换实际主题对象 |
静态代理在程序编译期间就已经存在代理类的情况下产生代理关系,这种代理关系在运行前就已经确定。
其具体实现方式如下:
1、定义一个接口及其实现类,作为被代理对象。
2、创建一个代理类,实现与原对象相同的接口。
3、在代理类中维护一个被代理对象的引用。
4、代理类中的方法实现,会调用被代理对象中的对应方法,并在前后添加其他处理。
5、客户端通过代理类访问被代理对象。
静态代理的特点:
1、代理类和被代理类实现相同的接口。
2、代理关系在程序运行前就确定了。
3、编译时生成代理类,执行效率高。
4、但不易维护,如果接口增加方法、代理类和被代理类都要修改。
5、灵活性较差,代理类只能为一个被代理类服务。
静态代理模式可以解决一些简单的代码封装、容错、访问控制等问题,但灵活性和复用性较差。
我们通过一个简单的的Demo来实现静态代理,代码如下:
定义一个图片接口(抽象主题角色),默认包含一个显示图片的方法:
interface Image {
void display();
}
定义具体的图片实体(实体角色)去实现这个接口:
public class RealImage implements Image {
@Override
public void display() {
System.out.println("显示图片");
}
}
定义一个图片代理类(代理角色):
public class ProxyImage implements Image{
private RealImage image;
public ProxyImage() {
image = new RealImage();
}
@Override
public void display() {
System.out.println("代理开始...");
image.display();
System.out.println("代理结束...");
}
}
客户端:
public class Client {
public static void main(String[] args) {
ProxyImage proxyImage = new ProxyImage();
proxyImage.display();
}
}
运行结果如下:
JDK动态代理是在程序运行期间通过反射机制动态创建代理类及其对象,从而实现对目标对象的代理。动态代理的实现步骤:
1、定义一个接口及其实现类,作为被代理对象。
2、创建一个代理类,该类实现InvocationHandler接口。
3、实现invoke方法,调用被代理对象的方法,并在前后添加其他处理。
4、使用Proxy类的newProxyInstance方法生成被代理对象的代理类。
5、客户端通过代理类访问被代理对象的方法。
动态代理的特点:
1、代理类的生成时机是在程序运行期间,不需要实现接口。
2、动态代理类可以服务任何接口实现类,灵活性强。
3、使用反射机制,执行效率较低。
4、可以动态改变代理的逻辑实现。
动态代理通过在运行时动态创建代理,更加灵活,可以按需实现代理类,避免了代理类膨胀问题,但执行效率相对较低。
下面我们提供一个Demo案例来实现,代码如下:
定义一个Hello接口,并提供默认的sayHello方法:
public interface IHello {
void sayHello();
}
定义一个具体的Hello实体去实现接口并重写sayHello方法:
public class Hello implements IHello {
@Override
public void sayHello() {
System.out.println("Hello World!");
}
}
创建动态代理类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 动态代理类
public class DynamicProxy {
// 维护目标对象
private Object target;
// 构造方法,传入目标对象
public DynamicProxy(Object target) {
this.target = target;
}
//给目标对象动态生成一个代理对象
public Object getProxyInstance() {
//利用反射里面一个类Proxy调用newProxyInstance方法
/*
* 这个方法三个参数
* 1.ClassLoader loader:当前目标对象的类加载器
* 2.Class>[] interface:目标对象实现的接口,使用泛型方法确认类型
* 3.InvocationHandler h:事情处理,执行目标对象方法时,会触发
* 事情处理器方法
* */
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理开始...");
Object res = method.invoke(target, args);
System.out.println("代理结束...");
return res;
}
});
}
}
客户端:
public class Client {
public static void main(String[] args) {
IHello target = new Hello();
IHello proxyInstance = (IHello)new DynamicProxy(target).getProxyInstance();
proxyInstance.sayHello();
}
}
运行结果如下:
Cglib代理和JDK动态代理的区别如下:
1、JDK动态代理只能代理实现了接口的类,而Cglib可以代理未实现任何接口的类。
2、JDK动态代理是通过反射机制生成一个实现了代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而Cglib动态代理是通过生成目标类的子类来实现的。
3、JDK动态代理效率略低于Cglib动态代理,因为Cglib是通过修改字节码生成子类来避免反射调用。
4、JDK动态代理只需实现InvocationHandler接口,Cglib需要实现MethodInterceptor接口。
5、Cglib需要依赖cglib库,JDK动态代理是Java自带的。
6、JDK动态代理可直接代理标识了接口的类,Cglib可代理未标识接口的类。
如果要代理的类实现了接口可以直接用JDK动态代理,如果要代理的类没有接口则需要用Cglib。Cglib通过生成子类提高了效率,但也更复杂。在非必要时建议使用JDK动态代理。二者思想都是一样的,都是在运行时动态生成代理类。
这里我搭建了个SpringBoot项目,Spring里面集成了对应Jar包,主要代理类这边进行了变动,代码如下:
package com.example.api;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//代理对象,Cglib代理(和jdk动态代理不同的是目标对象不需要实现接口)
public class DynamicProxy implements MethodInterceptor {
//维护一个目标对象
private Object target;
//构造器,传入一个被代理的对象
public DynamicProxy(Object target) {
this.target = target;
}
//返回一个代理对象:是 target 对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
//重写intercept方法,会调用目标对象的方法
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("Cglib代理模式开始...");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib代理模式结束...");
return returnVal;
}
}
客户端:
@SpringBootTest
class ApiApplicationTests {
@Test
void contextLoads() {
//创建目标对象
IHello target = new Hello();
//获取到代理对象,并且将目标对象传递给代理对象
Hello proxyInstance = (Hello) new DynamicProxy(target).getProxyInstance();
//执行代理对象的方法,触发intecept 方法,从而实现对目标对象的调用
proxyInstance.sayHello();
}
}
运行结果如图:
优点:
1、代理对象可以在目标对象操作前后增加额外功能,它像一个中介一样在目标对象和客户端之间起作用。
2、代理对象可以控制对目标对象的访问,保护目标对象的安全。
3、使用代理对象可以简化客户端代码,客户端只需要跟代理对象交互。
4、可以根据需要创建不同的代理类,扩展软件系统的灵活性。
缺点:
1、会增加系统的复杂度,需要新增代理类。
2、动态代理的效率可能比较低。
应用场景:
1、远程服务代理:本地代理代表远程服务对象,方便访问远程服务。
2、日志记录代理:代理对象在调用目标对象方法前后记录日志。
3、权限验证代理:代理检查客户端权限后再调用目标对象方法。
4、缓存代理:代理先检查缓存,缓存没命中再访问目标对象。
5、延迟加载代理:代理可以在需要时才创建目标对象。
符合的设计原则:
1、单一职责原则(Single Responsibility Principle)
代理类只负责代理的工作,目标类只负责业务自身的工作,二者职责明确,符合单一职责原则。
2、开闭原则(Open Closed Principle)
代理类和目标类均实现了相同的接口,对接口进行编程,扩展代理类不影响目标类,符合开闭原则。
3、依赖倒转原则(Dependence Inversion Principle)
抽象接口不依赖具体实现类,具体类依赖抽象接口,符合依赖倒转原则。
4、组合复用原则(Composite Reuse Principle)
代理类中维护对目标对象的引用,与目标对象建立聚合关系,符合组合复用原则。
5、迪米特法则(Law of Demeter)
代理类只与目标类交互,符合迪米特法则中的最少知道原则。
综上,代理模式通过建立中间代理层主要用于控制、增强对目标对象的访问,但也会增加系统复杂度。