代理模式
代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。
这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
被代理的对象可以是远程对象、创建开销大的对象或 需要安全控制的对象
代理模式有不同的形式, 主要有三种静态代理、理动态代理 (JDK代理、接口代理 )和 Cglib代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)。
概括:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
核心:可以在目标对象实现的基础上,增强额外的,即扩展目标对象的功能。
模式结构
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
类图
优缺点
优点:
- 在客户端毫无察觉的情况下控制服务对象。
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
- 开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理。
缺点:
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢
- 增加了系统的复杂度
- 服务响应可能会延迟。
与其他模式关系
适配器模式能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰模式则能为对象提供加强的接口。
外观模式与代理的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。
应用场景
- 延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。
- 访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。
- 本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。
- 记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。 代理可以在向服务传递请求前进行记录。
- 缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。
静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类应用实例。
由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
实现:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
静态代理优缺点
- 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
一旦接口增加方法,目标对象与代理对象都要维护
示例代码
使用静态代理模拟工厂代理生产手机
public class ChinaPhoneFactory implements Factory{
Factory factory;
public ChinaPhoneFactory(Factory factory) {
this.factory = factory;
}
@Override
public void produce() {
this.factory.produce();
System.out.println("代理生产苹果手机");
}
}
public interface Factory {
void produce();
}
public class IphoneFactory implements Factory{
@Override
public void produce() {
System.out.println("生产苹果手机");
}
}
public class StaticProxy {
public static void main(String[] args) {
ChinaPhoneFactory factory = new ChinaPhoneFactory(new IphoneFactory());
factory.produce();
}
}
动态代理
代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
动态代理也叫做:JDK代理、接口代理
在程序运行时,运用反射机制动态创建而成,需要借助反射机制实现
示例代码:
public class DynamicProxy {
public static void main(String[] args) {
ProxyInterface proxyInstance = (ProxyInterface)ProxyFactory.getProxyInstance(new Teacher());
proxyInstance.teacher();
}
}
public class ProxyFactory {
public static Object getProxyInstance(Object obj){
System.out.println("代理类开始工作");
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), (proxy, method, args) -> method.invoke(obj, args));
}
}
public interface ProxyInterface {
void teacher();
}
public class Teacher implements ProxyInterface {
@Override
public void teacher() {
System.out.println("老师开始上课");
}
}
Cglib 代理模式
- 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只
是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理
Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib 代理归属到动态代理。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截
在AOP编程中如何选择代理模式:
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要实现接口,用Cglib代理
Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
Cglib 代理需要导入第三方架包cglib 才能执行
示例代码
public class CglibProxy {
public static void main(String[] args) {
Teacher factory = (Teacher) new ProxyFactory(new Teacher()).getProxyInstance();
factory.teacher();
}
}
public class ProxyFactory implements MethodInterceptor {
Object tarTeacher;
public ProxyFactory(Object tarTeacher) {
this.tarTeacher = tarTeacher;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib开始代理");
return methodProxy.invokeSuper(tarTeacher, args);
}
public Object getProxyInstance(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(tarTeacher.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}
public class Teacher {
public void teacher() {
System.out.println("上课");
}
}