定义
为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
代理模式有不同的形式,主要有三种
- 静态代理
- 动态代理(JDK代理、接口代理)
- Cglib代理(可以在内存动态的创建对象,而不需要实现接口,它是属于动态代理的范畴
UML图
代理模式的三个关键角色
- 抽象主题类(RealObject):声明公用的方法,定义可供客户端使用的统一功能
- 主题实现类(RealObjectImpl):实现了抽象主题类的所有方法
- 代理类(Proxy):实现了抽象主题类的方法,并隐藏在代理后面可能其他类的实现
为什么使用代理模式?
- 第一个,客户端有时无法直接操作某些对象。
- 第二个,客户端执行某些耗时操作容易造成服务端阻塞
- 第三个,服务端需要控制客户端的访问权限。
代理模式除了前面提到的扩展功能外,另一个更为重要的功能是做权限控制。比如,某一项业务由于安全原因只能让一部分特定的用户去访问,如果在原有功能的基础上再增加权限过滤功能就会增加代码的耦合性,并且也不方便组件的复用。其实,这时做一个代理类就可以解决该问题,对于特定的接口来说,只需要指定所有请求必须通过该代理类,然后由该代理类做权限判断即可
使用场景分析
第一类,虚拟代理,适用于延迟初始化,用小对象表示大对象的场景。
这个“大对象”会包含大量 IO 资源,比如图片、大文件、模型文件等。我们都知道,大对象通常很占用内存空间,一直保持其运行会很消耗系统资源,这时就可以使用代理模式。那怎么来做呢?可以先创建一个消耗相对较小的对象来代理这个大对象的创建,而实际上真实的大对象只会在真正需要时才会被创建,这样的代理方式就被称为虚拟代理。比如,在 Java 中的 CopyOnWriteArrayList 数组对象的实现就是使用了虚拟代理的方式,目的就是要让操作延迟,只有对象被真正用到的时候才会被克隆。
/**
* @Description java.util.concurrent.CopyOnWriteArrayList 部分源码
*/
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
}
- 第二类,保护代理,适用于服务端对客户端的访问控制场景
代理模式有一个非常重要的应用场景就是控制一个对象对另一个对象的访问与使用权限。当客户端通过代理对象访问服务端的原始对象时,代理对象会根据具体的规则来判断客户端是否有访问权限。比如,防火墙其实就是一种保护代理的具体实践。
- 第三类,远程代理,适用于需要本地执行远程服务代码的场景
在这种场景中,代理对象会隐藏处理所有与网络相关的复杂细节。随着微服务架构的流行,越来越多的程序应用部署在多台服务器上,各自服务都更专注于各自的业务,当需要使用其他服务时就会频繁进行远程服务调用,但不可能所有的业务都要自己实现网络调用,于是就出现了的远程代理框架,比如,gRpc、Dubbo 等
- 第四类,日志记录代理,适用于需要保存请求对象历史记录的场景
比如,日志监控。客户端在调用请求时,并不会感知到日志记录,这是因为代理对象在原始对象周围添加了监控功能。
- 第五类,缓存代理,适用于缓存客户请求结果并对缓存生命周期进行管理的场景
比如,商品详情页通常包含大量图片和文字介绍,代理对象可以对重复请求相同的结果进行缓存。
总结一下代理模式试图解决的问题:
- 无法直接调用某些对象
- 耗时的操作
- 某个接口可能需要外部额外操作,如日志记录、权限管控、重复操作等
- 一直保存大对象
- 需要控制访问权限
静态代理
- **优点:**在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
- **缺点:**因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类一旦接口增加方法,目标对象与代理对象都要维护
public interface ITeacherDao {
void teach();
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("授课中...");
}
}
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao target;
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理 ...");
target.teach();
System.out.println("代理结束 ...");
}
}
public class Client {
public static void main(String[] args) {
TeacherDao teacherDao = new TeacherDao();
TeacherDaoProxy proxy = new TeacherDaoProxy(teacherDao);
proxy.teach();
}
}
JDK代理
public interface ITeacherDao {
void teach();
String sayHello(String name);
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师在授课");
}
@Override
public String sayHello(String name) {
String str = " sayHello to : " + name;
System.out.println(" sayHello to : " + name);
return "return : "+str;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Description
*/
public class ProxyFactory<T> {
private final T target;
public ProxyFactory(T target) {
this.target = target;
}
public Object getProxyInstance() {
// 1. ClassLoader 指定当前对象使用的类加载器,获取加载器的方法 固定
// 2. Class<?> interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
// 3. InvocationHandler : 事件处理,执行目标对象的方法时,会触发事情处理器方法
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("JDK代理开始...");
// 反射机制调用 目标方法
// System.out.println(method.getName());
Object returnObj = method.invoke(target, args);
System.out.println("JDK代理结束...");
return returnObj;
}
});
}
}
/**
* @Description 测试代码
*/
public class Client {
public static void main(String[] args) {
// 创建目标对象
ITeacherDao dao = new TeacherDao();
// 给目标对象 创建代理对象 可以转成ITeacherDao
ProxyFactory<ITeacherDao> factory = new ProxyFactory<>(dao);
Object proxyInstance = factory.getProxyInstance();
// com.yaron.proxy.jdkproxy.TeacherDao@4566e5bd
// System.out.println(proxyInstance);
// class com.sun.proxy.$Proxy0
// System.out.println(proxyInstance.getClass());
ITeacherDao proxyDao = (ITeacherDao)proxyInstance;
// proxyDao.teach();
String jack = proxyDao.sayHello("jack");
System.out.println(jack);
}
}
Cglib代理
-
静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只1)是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理
-
Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功2能扩展,有些书也将Cglib代理归属到动态代理
-
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展iava类与实现iava接口.它广泛的被许多AOP的框架使用,例如Spring
AOP,实现方法拦截
-
在AOP编程中如何选择代理模式:
1.目标对象需要实现接口,用JDK代理
2.目标对象不需要实现接口,用Cglib代理 -
Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
public class TeacherDao {
public void teach() {
System.out.println("Cglib. 老师在授课...");
}
}
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @Description 代理工厂
*/
public class ProxyFactory implements MethodInterceptor {
private Object target;
/**
* 传入被 代理 的对象
*
* @param target
*/
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 返回target的代理对象
*
* @return
*/
public Object getProxyInstance() {
// 1. 创建工具类
Enhancer enhancer = new Enhancer();
// 2. 设置父类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
// 4. 创建子类对象,即代理对象
return enhancer.create();
}
/**
* 调用 目标对象的方法
*
* @param obj
* @param method
* @param args
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib代理开始...");
Object returnObj = method.invoke(target, args);
System.out.println("Cglib代理结束...");
return returnObj;
}
}
/**
* @Description 测试代码
*/
public class Client {
public static void main(String[] args) {
TeacherDao teacherDao = new TeacherDao();
ProxyFactory proxyFactory = new ProxyFactory(teacherDao);
TeacherDao proxy = (TeacherDao) proxyFactory.getProxyInstance();
proxy.teach();
}
}