代理模式(Proxy)
介绍
1、代理模式:为对象提供一个替身,以控制对这个对象的访问,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作
(即:扩展目标对象的功能,例如Spring AOP)
2、被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象
3、代理模式有不同的形式,主要有三种:
静态代理(也是一种基于接口的代理)
动态代理(JDK代理/接口代理)
Cglib代理(在内存中动态创建对象,而不需要实现接口,属于动态代理的范畴)
这里是代理的示意图:
静态代理
静态代理在使用时,需要定义接口或父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同的接口
代理对象与目标对象实现相同的接口,通过调用相同的方法来调用目标对象方法。
代码示例
// 定义接口 interface Subject { void doAction(); } // 目标对象 class RealSubject implements Subject { @Override public void doAction() { System.out.println("RealSubject is doing action"); } } // 代理对象 class ProxySubject implements Subject { //这里聚合一个RealSubject对象 private RealSubject realSubject; public ProxySubject(RealSubject realSubject) { this.realSubject = realSubject; } @Override public void doAction() { System.out.println("ProxySubject is doing something before action"); realSubject.doAction(); System.out.println("ProxySubject is doing something after action"); } } // 测试类 public class StaticProxyExample { public static void main(String[] args) { RealSubject realSubject = new RealSubject(); ProxySubject proxySubject = new ProxySubject(realSubject); // 通过代理对象调用目标对象的方法 proxySubject.doAction(); } }
小结
优点:
易于理解和实现: 静态代理模式相对简单,易于理解和实现,适用于一些简单的代理场景。
对目标对象进行控制: 通过代理可以对目标对象的访问进行控制和管理,例如在调用目标对象方法前后进行额外的处理。
实现业务功能的解耦: 代理对象可以在不修改目标对象的情况下,增加额外的功能,实现了业务功能的解耦,提高了系统的可维护性。
保护目标对象: 代理对象可以充当一个保护层,控制用户对目标对象的访问权限,增加系统的安全性。
缺点:
静态: 静态代理在编译时就已经确定了代理类和目标类,如果需要代理多个类或者频繁地更换代理对象,会导致代码量增加,不利于系统的扩展和维护。
灵活性差: 静态代理模式的灵活性较低,一旦接口或者目标对象发生变化,代理类也需要相应地修改,不够灵活。
代码重复: 如果有多个接口或者多个目标对象需要被代理,会导致代理类的重复设计和编码,增加了开发工作量。
只能代理固定的目标对象: 静态代理模式在编译时就已经确定了代理类和目标类的关系,无法动态地切换或者增加新的代理对象。
总的来说,静态代理模式在一些简单的场景下是适用的,但在面对复杂的系统、多样的需求和频繁变化的代理对象时,可能会显得不够灵活和可扩展。在这种情况下,可以考虑使用动态代理模式或者其他结构型模式来实现更灵活、可扩展的代理功能。
动态代理
介绍
1、代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
2、代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
3、动态代理又称:JDK代理,接口代理
JDK中生成代理对象的API
1、代理类所在包:java.lang.reflect.Proxy
2、JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收3个参数,完整写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
代码示例
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 定义接口 interface Subject { void doAction(); } // 目标对象 class RealSubject implements Subject { @Override public void doAction() { System.out.println("目标对象正在执行"); } } // InvocationHandler 实现类 class ProxyHandler implements InvocationHandler { private Object target; public ProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("目标对象执行前。。。"); Object result = method.invoke(target, args); System.out.println("目标对象执行后。。。"); return result; } } // 测试类 public class DynamicProxyExample { public static void main(String[] args) { RealSubject realSubject = new RealSubject(); // 创建动态代理对象 //newProxySubject的三个参数: //1、ClassLoader:指定当前目标对象使用的类加载器,获取加载器的方法固定 //2、Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确定类型 //3、InvocationHandler h:事情处理。执行目标对象方法时,会触发事情处理方法,会把当前执行的目标对象方法作为参数 Subject proxySubject = (Subject) Proxy.newProxyInstance( Subject.class.getClassLoader(), new Class[]{Subject.class}, new ProxyHandler(realSubject)); // 通过代理对象调用目标对象的方法 proxySubject.doAction(); } }
小结
优点:
灵活性: 动态代理模式相比静态代理模式更加灵活,可以在运行时动态地创建代理对象,无需提前知道目标对象的具体类型。
减少代码量: 动态代理可以通过反射机制自动生成代理类,减少了手动编写代理类的代码量。
适应性强: 动态代理模式适用于多个目标类共享一个代理类或者频繁切换代理对象的场景。
可扩展性: 动态代理模式可以通过自定义
InvocationHandler
的实现类,实现对目标对象方法的统一处理,并且可以根据需求灵活地扩展功能。
缺点:
性能影响: 动态代理在运行时通过反射机制调用目标对象的方法,相比直接调用目标对象的方法,性能上会有一定的影响。
复杂性增加: 动态代理模式相对静态代理模式来说,涉及到更多的类和概念,代码结构可能会变得更加复杂,不利于代码的理解和维护。
无法代理私有方法: 动态代理无法直接代理目标对象中的私有方法,只能代理公共方法。
动态代理模式在一些需要灵活性和可扩展性的场景下非常有用。它可以减少代码量、提高代码的可复用性,并且可以在运行时动态地创建代理对象。然而,它也存在一些性能影响和复杂性增加的问题,需要根据具体情况权衡使用。
Cglib代理
介绍
1、静态代理和JDK代理都要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——Cglib代理
2、Cglib代理也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些地方也将Cglib代理归属动态代理
3、Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
4、Cglib代理广泛使用在AOP框架中,例如Spring AOP,实现方法拦截。
在AOP编程中,如何选择代理模式呢?
目标对象需要实现接口——JDK代理
目标对象不需要实现接口——Cglib代理
5、Cglib包底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
实现步骤
引入cglib的jar文件
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.4.1</version> </dependency>
因为在内存中动态构建子类,代理类不能是final,否则会报错
目标对象的方法如果是final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * cglib代理测试 */ class HelloWorld { public void sayHello() { System.out.println("CGLIB动态代理模式!"); } } class CGLIBExampleProxy implements MethodInterceptor { /** * 指定cglib代理模式的代理类 */ private Object target; public Object bind(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); //设置超类方法 enhancer.setSuperclass(this.target.getClass()); //设置一个回调方法,用来设置哪个类为代理类,this表示当前类为代理类 enhancer.setCallback(this); //创建代理对象 return enhancer.create(); } //重写接口中的intercept方法,增强并执行代理方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("CGLIB代理前"); Object object = proxy.invokeSuper(obj, args); System.out.println("CGLIB代理后"); return object; } } public class CglibProxy { public static void main(String[] args) { //1.获取代理对象 CGLIBExampleProxy proxy=new CGLIBExampleProxy(); //2.获取代理对象 HelloWorld hello=(HelloWorld)proxy.bind(new HelloWorld()); //3.执行代理方法 hello.sayHello(); } }
如果报错
原因: 因为在使用 CGLIB 进行代理时,出现了对 Java 9+ 模块系统的限制导致的异常。在 Java 9+ 中,模块系统对于反射和类加载引入了更严格的访问控制。 解决: 尝试通过添加以下 JVM 参数来打开 java.base 模块的反射权限: --add-opens java.base/java.lang=ALL-UNNAMED
小结
优点:
不需要接口: CGLib 动态代理不要求目标对象实现接口,可以直接代理目标类的方法,这样就避免了 JDK 动态代理必须基于接口的限制。
性能高: CGLib 动态代理是通过生成目标类的子类来实现代理,因此在调用目标方法时比 JDK 动态代理更快,无需通过反射调用。
灵活性: CGLib 可以代理非公共的类,包括 final 类。同时,CGLib 也支持对类中的 final 方法进行代理。
功能丰富: CGLib 提供了丰富的 API,可以在代理过程中实现更复杂的逻辑,满足更多场景的需求。
缺点:
引入依赖: 使用 CGLib 动态代理需要引入额外的库,增加了项目的依赖性,可能会对项目构建和部署造成一定影响。
类加载器: 由于 CGLib 是通过生成目标类的子类来实现代理,因此需要使用字节码技术,在某些情况下可能会导致类加载问题或者冲突。
无法代理静态方法: CGLib 无法代理目标对象中的静态方法,因为动态代理实际上是通过生成目标对象的子类来实现代理,而静态方法是属于类级别的而不是实例级别的。
内存占用: 由于 CGLib 动态代理是通过生成子类来实现代理,可能会占用更多的内存,特别是在代理大量对象时可能会影响系统性能。
总的来说,CGLib 动态代理适合对类进行代理,无需目标对象实现接口的情况下使用。它具有高性能、灵活性强等优点,但也存在一些缺点,如引入依赖、无法代理静态方法等。在选择动态代理方式时,需要根据具体需求和场景来权衡选择适合的代理方式。
几种常见的代理模式介绍——几种变体
1、防火墙代理
内网通过代理穿透防火墙,实现对公网的访问
2、缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取不到,就到公网或数据库中取
3、远程代理 把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息
4、同步代理
用于多线程编程中,完成多线程间同步工作。
通过一个代理的服务器去访问真正提供服务的。
代理模式总结
本人理解:通过一个代理对象访问并增强另一个对象的功能。
代理模式是一种结构型设计模式,其目的是通过引入一个代理对象来控制对另一个对象的访问。代理对象可以在不改变原始对象的情况下,对其进行一些额外的操作,如控制对原始对象的访问权限、延迟加载、缓存数据、记录日志等。
角色:
抽象主题(Subject):定义了代理对象和真实对象的共同接口,客户端可以通过该接口访问真实对象。
真实主题(Real Subject):实际执行业务逻辑的对象。
代理(Proxy):包含了对真实主题的引用,并提供与真实主题相同的接口,负责控制对真实主题的访问。
优势:
代理对象可以拦截对真实对象的访问,可以在调用真实对象之前或之后执行一些额外的逻辑。
可以实现延迟加载:代理对象可以延迟创建或加载真实对象,从而提高系统性能和节省资源。
实现访问控制:代理对象可以控制对真实对象的访问权限,实现安全性控制。
实现缓存:代理对象可以缓存真实对象的结果,避免重复计算或获取数据。
缺点
增加系统复杂度
性能损耗
可能引入单点故障
可能导致循环依赖
应用场景:
远程代理:控制访问远程对象。
虚拟代理:控制访问创建开销大的对象。
缓存代理:为耗时的操作结果提供缓存。
保护代理:控制对敏感对象的访问权限。
智能引用:在访问对象时执行额外操作,如引用计数。
代理模式能够增加代码的灵活性和可维护性,同时提供了一种优雅的方式来控制对象的访问和行为。通过合理地使用代理模式,可以更好地实现代码解耦和功能扩展。