目录
代理模式介绍
什么是代理模式?
代理模式能带给我们什么?
解耦
高扩展性
代理模式结构
Java中的代理模式有哪些?
静态代理
接口实现方式
2继承实现方式
结论
动态代理
JDK 动态代理机制
JDK 动态代理机制介绍
思考1:如何只增强需要的方法:
思考2底层JDK如何完成的动态代理?
CGLIB 动态代理机制
CGLIB 动态代理机制介绍
底层CGLIB如何完成的动态代理?
总结
JDK 动态代理和 CGLIB 动态代理对比
静态代理与动态代理的区别
总结
代理模式和装饰者模式的区别
1.目的:
2.对象的扩展:
3.装饰的方式:
4.构建的位置:
代理设计模式是一种结构型设计模式,它允许您提供一个代理对象,以控制对其它对象的访问。代理对象充当了被代理对象的接口,以便客户端可以通过代理对象访问被代理对象。
代理模式,就是给一个对象提供一种代理对象以控制对该对象的访问。在这个模式中,我们通过创建代理对象作为“替身”替代了原有对象,从而达到我们控制对象访问的目的。
通俗来说,代理=代替处理。是由另一个对象来代替原对象来处理某些逻辑。举个例子:房产中介,代跑腿业务,送外卖...
代理模式能带给我们控制访问某个对象的能力,在某些情况下,一个对象的某些方法想要进行屏蔽或者某种逻辑的控制,则我们可以通过代理的方式进行。再此能力上,引申出来的作用,也是目前在开发中经常使用的一个作用,就是——“在不修改原对象代码的基础上,对原对象的功能进行修改或者增强”。
什么是耦合?
耦合本来是一个工业上的概念,指的就是像齿轮一样卡咬到一起,一个齿轮运转的时候,其他咬死的齿轮也会跟着发生运转。而此概念平移到我们的开发领域,就是项目中的各个模块或者各个组件,当一个发生变动或者调用的时候,其他有一些模块或组件也会跟着发生变动或者调用,产生了强联系。这样的情况就说两个模块或组件耦合到一起了。
什么是解耦?
大家看这些齿轮,如果在运行过程中我发现一个齿轮大小不对要更换或者要移动位置,其他的齿轮会如何?
当模块或组件耦合度过高,会带来难以扩展、维护性差、纠错困难等各种问题,所以我们在设计的时候,要尽可能的避免模块或组件之间的耦合度过高。在设计过程中,削减耦合度或者消除耦合度的操作,就是解耦。
当我们想要给某个对象添加一些额外的逻辑时(例如访问权限的控制,日志的记录...),使用代理模式。我们可以不修改原代码,只针对这些额外的功能进行编码。在整个过程中,原对象的逻辑和额外增加的逻辑完全解耦,互不干扰。
由于模块间是解耦的,所以我们随时可以添加任意的功能或者修改之前的功能而不回影响到原模块的正常执行。相当于我们的代码像钢铁侠的战衣一样,可以随时添加各种战斗模组以适应不同的作战环境。这就是高扩展性。
代理(Proxy)模式分为三种角色:,
抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
真实主题(Real Subject)类: 实现了抽象主题中的具体业务是代理对象所代表的真实对 象,是最终要引用的对象。
代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访 问、控制或扩展真实主题的功能。
Java中代理有三种方式来创建代理对象:静态代理(多态)、基于JDK(接口)的动态代理、基于CGLIB(父类)的动态代理。
静态代理和动态代理都是实现代理模式的方法,它们的主要区别在于代理类的创建时间和方式不同。
静态代理是在编译时就已经确定代理类的代码,代理类与被代理类的关系在编译时就已经确定。在程序运行之前,代理类的.class文件就已经存在。代理类和被代理类在编译时就已经确定了,因此它们的关系是固定的,无法在运行时动态改变。
动态代理是在程序运行时动态地创建代理类的实例,代理类与被代理类的关系是在程序运行时确定的。动态代理需要一个接口,代理类会实现这个接口并在运行时通过Java反射机制来动态地生成代理类的实例。因为代理类是在程序运行时动态创建的,所以可以在运行时动态地改变代理类和被代理类的关系。
因此,动态代理的优点是可以减少重复代码的编写,提高代码复用性,并且可以在运行时动态地改变代理类和被代理类的关系。而静态代理则需要手动编写代理类,比较繁琐,代理类和被代理类的关系是固定的,无法在运行时动态改变。
静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
静态代理,也就是我们会手写代理类的代码,工程中有代理类的源码,代理类会编译后执行。
编写代理类,实现目标类的接口或者直接继承目标类,完成逻辑的修改。
是不是发现其实静态代理和多态有异曲同工之妙呢?
我先简单带大家回顾一下多态,多态是指同一个方法或者同一个类,在不同的情况下表现出不同的行为。在面向对象编程中,多态是通过继承、接口实现、方法重载等机制实现的
虽然静态代理模式和多态没有直接的关系,但是在实际的编程中,我们可以使用多态来简化代理对象的实现。
目标类和代理类都要实现目标接口的方法,所以可不可以说他们的功能其实是一样的?所以代理类可以在一定程度替代目标类,但是代理类方法实现又可以与目标类不同,这样就实现了代理
顾客类:
public class Customer implements OrderInterface{
@Override
public String order(String foodName){
// 上万行
return "已经下单点了"+foodName;
}
}
外卖小哥类:
public class DeliveryClerk2 implements OrderInterface {
// 把原来的对象传入,并报存到成员位置。也就是目标类对象
private OrderInterface source;
public DeliveryClerk2(OrderInterface source) {
this.source = source;
}
@Override
public String order(String foodName) {
String result = source.order(foodName);
System.out.println("已经接受到订单,正前往取餐途中...");
System.out.println("已经取餐,正在派送...");
return result+",已经搅拌均匀";
}
}
订单接口:
public interface OrderInterface {
public String order(String foodName);
}
测试类:
public class StaticDemoTest {
public static void main(String[] args) {
// 创建一个顾客对象
Customer customer = new Customer();
// 创建代理对象,也就是外卖小哥对象
OrderInterface deliveryClerk = new DeliveryClerk2(customer);
// 调用代理对象的方法,可以看到增强之后的效果
String result = deliveryClerk.order("红烧肉");
System.out.println(result);
}
}
父类有的方法,子类也有,但是代理类作为子类,可以覆盖掉父类的方法,重写方法体,就能完成逻辑修改,实现代码增强
顾客类:
public class Customer{
public String order(String foodName){
return "已经下单点了"+foodName;
}
}
外卖小哥类:
public class DeliveryClerk extends Customer{
@Override
public String order(String foodName) {
String result = super.order(foodName);
System.out.println("已经接受到订单,正前往取餐途中...");
System.out.println("已经取餐,正在派送...");
return result+",已经搅拌均匀";
}
测试类:
public class StaticDemoTest {
public static void main(String[] args) {
//注意,new的不是顾客而是外卖员
Customer customer = new DeliveryClerk();
String result = customer.order("麻婆豆腐");
System.out.println(result);
}
}
静态代理虽然能够实现我们所说的代理模式,完成了解耦,但是静态代理类的代码维护依然非常复杂。以接口实现方式为例,当你需要实现新的功能,往接口增加了一个方法,所有的实现类都要修改。你的业务只需要修改接口中的某一个方法,但是你任然需要覆写接口中的所有方法,这就是静态代理的弊端,随着业务逻辑越来越复杂,也会越来越难以维护。
使用代理模式的初衷是不想修改目标类,但是现在为了不修改目标类,任然要大量的修改代理类,一旦接口或者父类发生了变动,则代理类的代码就得随之修改,代理类多的时候维护比较麻烦。
所以在实际开发的时候,一般使用动态代理的方式。
动态代理技术,是在内存中生成代理对象的一种技术。也就是整个代理过程在内存中进行,我们不需要手写代理类的代码,也不会存在代理类编译的过程,而是直接在运行期,在JVM中“凭空”造出一个代理类对象供我们使用。
用最简单的话来说,动态代理就是将目光聚焦于变化的部分(也就是需要加强和修改逻辑的地方)而原本的代码则不需要理他
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。
动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等
JDK自带的动态代理技术,需要使用一个静态方法来创建代理对象。
它要求被代理对象,也就是目标类,必须实现接口。
生成的代理对象和原对象都实现相同的接口,是兄弟关系。
Proxy.newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)
主要关注参数列表:
ClassLoader loader:
固定写法,指定目标类对象的类加载器即可。用于加载目标类及其接口的字节码文件,通常,使用目标类的字节码对象调用getClassLoader()方法即可得到
Class>[] interfaces:
固定写法,指定目标类的实现的所有接口的字节码对象的数组,通常,使用目标类的字节码对象调用getInterfaces()方法即可得到
InvocationHandler h:
这个参数是一个接口,主要关注它里面唯一一个方法,invoke方法。它会在代理类对象调用方法时执行,也就是说,我们在代理类对象中调用任何接口中的方法时,都会执行到invoke中。所以,我们在此方法中完成对增强或者扩展代码逻辑的编写。
Object invoke(Object proxy, Method method, Object[] args)
proxy:就是代理类对象的一个引用,也就是Proxy.newProxyInstance的返回值,此引用几乎不回用到,忽略即可。
method:对应的是触发invoke执行的方法的Method对象。假如我们调用了xxx方法,该方法触发了invoke的执行,那么,method就是xxx方法对应的反射对象(Method对象)
args:代理对象调用方法时,传递的实际参数
顾客类:
public class Customer implements OrderInterface{
@Override
public String order(String foodName){
// 上万行
return "已经下单点了"+foodName;
}
@Override
public void test(){
System.out.println("我是test");
}
@Override
public void test2(){}
}
测试类:
public class DynamicTest {
public static void main(String[] args) {
// 准备一个目标类对象,也就是顾客对象
Customer customer = new Customer();
// 使用JDK的API,动态的生成一个代理对象
OrderInterface deliveryClerk = (OrderInterface) Proxy.newProxyInstance(
customer.getClass().getClassLoader(),
customer.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(customer, args);
System.out.println("已经接受到订单,正前往取餐途中...");
System.out.println("已经取餐,正在派送...");
return result + ",已经搅拌均匀";
}
}
);
//调用代理对象,执行对应方法
String result = deliveryClerk.order("麻婆豆腐");
System.out.println(result);
// deliveryClerk.test();
}
}
在调用jdk的API之前,这个对象在我们的内存中是不存在的,只有JVM真正执行到这一行代码的时候,才动态的在内存中开辟了一块空间,这个空间存储的就是这个对象,这个对象的功能就是实现了这个接口,并且增强了功能
在上述代码中,调用costomer类中的order方法和test方法都会增强,这显然不是我们需要的。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("order".equals(method.getName())) {
Object result = method.invoke(customer, args);
System.out.println("已经接受到订单,正前往取餐途中...");
System.out.println("已经取餐,正在派送...");
return result + ",已经搅拌均匀";
}else{
// 使用method反射调用,在原对象(目标类对象)中执行该方法,并不修改其逻辑。
return method.invoke(customer, args);
}
}
JDK动态的从内存中帮助我们生成了代理对象,在这里我们模拟一下当JVM执行到Proxy.newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
方法中时,会API在内存中实现这么一个类,把handler(外部传递的对象)和接口接收过来,并且对业务逻辑进行增强
代码如下(非源码):
public class DeliveryClerk implements OrderInterface {
// 接收外部传递过来的InvocationHandler对象
private final InvocationHandler handler;
public DeliveryClerk(InvocationHandler handler) {
this.handler = handler;
}
@Override
public String order(String foodName) {
//每个方法的实现,实际上并没有做其他的事情,而是直接调用了InvocationHandler中的invoke方法
try {
// 调用的是order方法,则反射获取order对应的method对象,传入invoke中
Method method = OrderInterface.class.getMethod("order", String.class);
// 调用InvocationHandler中的invoke方法
Object result = handler.invoke(this, method, new Object[]{foodName});
// 将返回值返回
return (String) result;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
@Override
public void test() {
//每个方法的实现,实际上并没有做其他的事情,而是直接调用了InvocationHandler中的invoke方法
try {
// 调用的是test方法,则反射获取test对应的method对象,传入invoke中
Method method = OrderInterface.class.getMethod("test");
// 调用InvocationHandler中的invoke方法
Object result = handler.invoke(this, method,null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void test2() {
//每个方法的实现,实际上并没有做其他的事情,而是直接调用了InvocationHandler中的invoke方法
try {
// 调用的是test2方法,则反射获取test2对应的method对象,传入invoke中
Method method = OrderInterface.class.getMethod("test2");
// 调用InvocationHandler中的invoke方法
Object result = handler.invoke(this, method,null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
测试类(用自己写的类,代替JDK的API实现动态代理):
public class DynamicTest {
public static void main(String[] args) {
// 准备一个目标类对象,也就是顾客对象
Customer customer = new Customer();
// 把InvocationHandler的定义抽取出来
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("order".equals(method.getName())) {
Object result = method.invoke(customer, args);
System.out.println("已经接受到订单,正前往取餐途中...");
System.out.println("已经取餐,正在派送...");
return result + ",已经搅拌均匀";
}else{
return method.invoke(customer, args);
}
}
};
OrderInterface deliveryClerk = new DeliveryClerk(handler);
//调用代理对象,执行对应方法
String result = deliveryClerk.order("麻婆豆腐");
System.out.println(result);
}
}
总结:
基于接口的动态代理,实际上是在内存中生成了一个对象,该对象实现了指定的目标类对象拥有的接口。所以代理类对象和目标类对象是兄弟关系。
兄弟关系:并列的关系,不能互相转换,包容性比较差。在后续会学习Spring框架,如果配置JDK的动态代理方式,一定要用接口类型接收代理类。
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
第三方CGLIB的动态代理技术,也是可以使用一个静态方法来创建代理对象。它不要求目标类实现接口,但是要求目标类不能是最终类,也就是不能被final修饰。因为CGLIB是基于目标类生成改类的一个子类作为代理类,所以目标类必须可被继承。
因为是第三方类库,所以在使用前需要导包。以maven为例
cglib
cglib
创建代理类对象:
Enhancer.create(Class type, Callback callback);
主要参数列表:
Class type:指定我们要代理的目标类的字节码对象,也就是指定目标类的类型
Callback callback:此单词的意思叫做回调,意思就是我们提供一个方法,它会在合适的时候帮我们调用它。回来调用的意思。
Callback是一个接口,由于该接口只是一个名称定义的作用,并不包含方法的声明。所以我们使用时通常使用它的一个子接口MethodInterceptor,此单词的意思叫做方法拦截器。
MethodInterceptor接口中也只有一个方法,叫做intercept
Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
methodProxy:方法的代理对象,一般也不作处理,可以暂时忽略
顾客类:
public class Customer{
public String order(String foodName){
return "已经下单点了"+foodName;
}
public void test(){
System.out.println("我是test");
}
public void test2(){}
}
测试类:
public class DynamicTest {
public static void main(String[] args) {
// 创建一个目标类对象,也就是顾客对象
Customer customer = new Customer();
// 使用CGLIB创建代理对象
Customer deliveryClerk = (Customer) Enhancer.create(customer.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 判断,如果是order方法,则增强
if("order".equals(method.getName())){
Object result = method.invoke(customer, args);
System.out.println("已获得订单,去取餐路上...");
System.out.println("已取餐,正在送餐...");
return result +",额外添加美味佐料:口水一下";
}else{
return method.invoke(customer, args);// 原封不动的调用原来的逻辑。反射调用
}
}
});
// 调用代理类的方法
String result = deliveryClerk.order("鱼香肉丝");
System.out.println(result);
}
}
模拟在内存中调用CGLIB的Enhancer.create(Class type, Callback callback)方法后,该方法内部写了一个什么样的代理类
public class DeliveryClerk extends Customer {
// 保存方法拦截器
private final MethodInterceptor methodInterceptor;
public DeliveryClerk(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
@Override
public String order(String foodName) {
try {
Method method = Customer.class.getMethod("order", String.class);
Object result = methodInterceptor.intercept(this, method, new Object[]{foodName}, null);
return (String) result;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return super.order(foodName);
}
@Override
public void test() {
try {
Method method = Customer.class.getMethod("test", String.class);
Object result = methodInterceptor.intercept(this, method, null, null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void test2() {
try {
Method method = Customer.class.getMethod("test2", String.class);
Object result = methodInterceptor.intercept(this, method, null, null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
基于父类的动态代理,是在内存中生成了一个对象,该对象继承了原对象(目标类对象)。所以代理类对象实际上是目标类对象的儿子。
父子关系:父子关系,代理类对象是可以用父类的引用接收的。
JDK 动态代理和 CGLIB 动态代理是两种常用的动态代理方式,它们在实现原理和适用场景上存在一些差异。以下是它们的对比:
实现原理: JDK 动态代理使用 Java 的反射机制,在运行时创建代理对象并动态生成代理类,代理类实现了指定的接口,并把方法的调用委托给被代理的对象。 CGLIB 动态代理使用 ASM 库操作字节码,在运行时动态生成被代理类的子类作为代理类,并重写被代理类的方法,从而实现代理功能。
适用场景: JDK 动态代理适用于接口代理,即被代理对象必须实现接口,否则无法使用 JDK 动态代理。 CGLIB 动态代理适用于类代理,即被代理对象无需实现接口,CGLIB 可以通过生成被代理类的子类来实现代理功能。
性能: 由于 JDK 动态代理是基于接口的代理方式,因此在方法调用时需要通过反射调用被代理对象的方法,相对于直接调用方法会产生一定的性能损耗。而 CGLIB 动态代理则是直接生成被代理类的子类,在方法调用时直接调用被代理类的方法,因此性能略优于 JDK 动态代理。
综上所述,JDK 动态代理和 CGLIB 动态代理各有优劣,需要根据实际情况选择合适的代理方式。如果需要代理接口,或者对性能要求不高,可以选择 JDK 动态代理;如果需要代理类,或者对性能要求较高,可以选择 CGLIB 动态代理。
静态代理和动态代理都是实现代理模式的方法,它们的区别在于代理类的创建方式和时间不同。
1创建时间不同:
静态代理:代理类在编译时就已经确定,代理类和被代理类的关系在程序运行前就已经确定。
动态代理:代理类是在程序运行时动态生成的,代理类和被代理类的关系在程序运行时确定。
2实现方式不同:
静态代理:需要手动编写代理类,并在代理类中实现被代理类的所有方法,代码比较繁琐。
动态代理:通过Java反射机制动态生成代理类,无需手动编写代理类,只需要一个接口即可。
3代理类的关系不同:
静态代理:代理类和被代理类的关系是固定的,在编译时就已经确定,无法在运行时动态改变。
动态代理:代理类和被代理类的关系是在程序运行时确定的,可以动态地改变代理类和被代理类的关系。
总之,静态代理和动态代理都有各自的优缺点和适用场景,需要根据具体的需求和情况来选择使用哪种代理方式。
1. 代理模式在Java开发中是广泛应用的,特别是在框架中底层原理经常涉及到代理模式(尤其是动态代理)
2. 静态代理和动态代理,实际使用时还是动态代理使用的比较多。原因就是静态代理需要自行手写代码,维护、修改非常繁琐,会额外引入很多工作量。也不能很好的使用配置完成逻辑的指定,所以使用较少。
3. 基于JDK和基于CGLIB,实际使用时两个都会用到。
1. 在spring中,默认情况下它就支持了两种动态代理方式。如果你指定的目标类实现了接口,spring就会自动选择jdk的动态代理。而如果目标类没有实现接口,则spring会使用CGLIB。
2. 我们在开发时,由于基于JDK的动态代理要求比较多,更不容易实现,所以很多人习惯于统一配置为使用CGLIB进行代理。也就是CGLIB更通用。
3. 如果使用dubbo+zookeeper,底层进行代理时,最好配置定死使用CGLIB的方式进行代理。因为dubbo会使用基于包名的扫描方式进行类的处理,而JDK的代理类生成的包名类似于com.sun.proxy....格式。我们实际需要让代理类和目标类保持一样的包名,所以只有CGLIB能保持原包名不变生成代理类。
代理模式的主要目的是控制对对象的访问以及保护和隐藏目标对象,并提供一种间接的方式来访问对象。代理模式通常会在目标对象之前或之后执行一些额外的逻辑,如权限控制、延迟加载、远程访问等。
装饰者模式的主要目的是动态地增强对象并且添加额外的功能。装饰者模式通过将对象放入包装器中,以组合的方式来扩展对象的功能。
代理模式通常通过间接访问的方式对目标对象进行增强,并在此过程中添加额外的功能。代理通常持有对目标对象的引用,但本身不对目标对象进行修改。
装饰者模式通过组合的方式来扩展对象的功能,将对象放入包装器中,并通过调用装饰者的方法来添加额外的功能。
代理模式通过代理类来对目标对象进行控制和管理,代理类在访问目标对象之前或之后执行一些操作。
装饰者模式通过包装器类来扩展对象的功能,装饰者类在调用被包装对象的方法前后添加额外的行为。
装饰者是由外界传递进来,可以通过构造方法传递
代理模式是在代理类内部创建,以此来隐藏目标对象