目录
- 一、什么是动态代理,有什么作用
- 1、代理是什么
- 2、代理在开发中的使用情况
- 3、使用代理模式的作用
- 二、静态代理
- 1、静态代理实现示例
- 三、动态代理
- 1、动态代理的实现(有两种)
- 1、CGLIB动态代理(了解)
- 2、jdk动态代理(理解)
- 1、InvocationHandler 接口(调用处理器)
- 2、Method类
- 3、Proxy类
- 2、动态代理实现示例
一、什么是动态代理,有什么作用
JDK动态代理是Java中的一种动态代理方式,它使用JDK的
反射机制
来创建代理类的对象,而无需编写具体的代理类代码。在程序执行时,通过调用JDK提供的方法才能创建代理类的对象。
这种代理方式主要基于接口实现动态代理,其核心是Proxy类和InvocationHandler接口。Proxy类通过调用其静态方法newProxyInstance()来返回代理对象,而InvocationHandler接口中的invoke方法则用于对目标方法进行增强。
JDK动态代理的设计初衷是为了适应那些在编译期间无法确定代理类需求的情况。
在传统的静态代理模式下,需要为每一个具体的服务接口预先创建一个对应的代理类,当系统中存在大量接口时,这种方式会带来大量的重复代码和维护困难。而JDK动态代理通过在运行时根据接口动态生成代理类的方式,极大地简化了这一过程,使得开发人员无需手动编写具体的代理类代码,只需关注代理逻辑本身。
JDK动态代理在诸如Spring Boot这样的企业级开发框架中被广泛应用,用于实现诸如AOP(面向切面编程)、服务代理、事务管理等高级功能。由于代理类是在运行时生成的,因此可以根据不同的场景灵活地定制代理行为,无需提前预知所有可能的代理情况。
JDK动态代理是一种强大而灵活的技术,它允许在不修改原始代码的情况下对目标对象的方法进行功能增强,从而提高了代码的可维护性和可扩展性。
例如:你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用。
// GoNong.class
GoNong gn = new GoNong();
gn.print();`
你发现这个功能,现在还缺一点, 不能完全满足我项目的需要。需要在gn.print()执行后,需要自己在增加代码。用代理实现 gn.print()
调用时, 增加自己代码, 而不用去改原来的 GoNong文件。
1、代理是什么
在JDK动态代理中,“代理”的含义主要是为其他对象提供一种替代或中介,以控制对这个对象的访问。JDK动态代理通过创建目标对象的代理对象,在代理对象与目标对象之间建立一个中间层,以便对目标对象中的方法进行功能性增强。
这种代理方式的核心在于,它允许在不修改原始类代码的情况下,对类的方法进行动态拦截和增强。在程序运行时,通过调用JDK提供的方法,可以动态地创建代理类的对象,并根据需要指定要代理的目标类。这样,代理对象就可以作为目标对象的一个替代,对目标对象的方法进行增强或修改。
在JDK动态代理中,代理对象的创建是基于反射机制的,而且是在程序运行时动态生成的。这使得代理对象具有高度的灵活性和可扩展性,可以根据不同的需求动态地创建和配置代理对象。
JDK动态代理中的“代理”是一种用于控制对象访问和增强对象功能的机制,它通过创建代理对象来实现对目标对象的间接访问和修改。
2、代理在开发中的使用情况
例如:在开发中的情况,你有A类,本来是调用C类的方法,完成某个功能,但是C不让A调用。
A 不能调用 C的方法。所以在A 和 C之间创建一个 B 代理,C让 B 访问。则访问流程变成:
A --> 访问B —> 访问C
例子: 登录,注册有验证码, 验证码是手机短信。
中国移动, 联通能发短信。中国移动, 联通能有子公司,或者关联公司,他们面向社会提供短信的发送功能。
张三项目发送短信 ----> 子公司,或者关联公司 -----> 中国移动, 联通 ----> 验证码
3、使用代理模式的作用
- 功能增强: 在你原有的功能上,增加了额外的功能。 新增加的功能,叫做功能增强。
- 控制访问: 代理类不让你访问目标,例如商家不让用户访问厂家。
二、静态代理
静态代理 : 代理类是自己手工实现的,自己创建一个java类,表示代理类。同时你所要代理的目标类是确定的。代理类应具有以下特点:实现简单,容易理解,提前确定好了代理的关系。
当你的项目中,目标类和代理类很多时候,有以下的缺点:
- 当目标类增加了, 代理类可能也需要成倍的增加,导致代理类数量过多。
- 当你的接口中功能增加或修改,会影响众多的实现类,厂家类。这时,所有的代理都需要修改。
1、静态代理实现示例
模拟一个用户购买u盘的行为。用户是客户端类,商家是代理,代理某个品牌的u盘,而厂家则是目标类。
三者的关系: 用户(客户端) ----> 商家(代理) ----> 厂家(目标)
商家和厂家都是卖u盘的,他们完成的功能是一致的,都是卖u盘。
实现步骤:
- 创建一个接口,定义卖u盘的方法, 表示你的厂家和商家做的事情。
// 定义一个接口UsbDriveSeller,其中包含一个卖U盘的方法sellUsbDrive。
public interface UsbDriveSeller { void sellUsbDrive();
}
- 创建厂家类,实现1步骤的接口。
// 创建Manufacturer类,实现UsbDriveSeller接口。
public class Manufacturer implements UsbDriveSeller { @Override public void sellUsbDrive() { System.out.println("厂家生产并销售U盘。"); }
}
- 创建商家,就是代理,也需要实现1步骤中的接口。
// 创建Merchant类,也实现UsbDriveSeller接口。商家类作为代理,可能会添加一些额外的逻辑,比如加价销售等。public class Merchant implements UsbDriveSeller { private UsbDriveSeller manufacturer; public Merchant(UsbDriveSeller manufacturer) { this.manufacturer = manufacturer; } @Override public void sellUsbDrive() { // 商家在销售前可能做一些准备工作,比如宣传、包装等 System.out.println("商家开始销售U盘。"); // 调用厂家的卖U盘方法 manufacturer.sellUsbDrive(); // 商家销售后的动作,比如记录销售、售后服务等 System.out.println("商家销售完成,并提供售后服务。"); }
}
- 创建客户端类,调用商家的方法买一个u盘。
// 创建Client类,模拟客户端调用商家的方法购买U盘。public class Client { public static void main(String[] args) { // 创建厂家对象 UsbDriveSeller manufacturer = new Manufacturer(); // 创建商家对象,并将厂家对象作为参数传入 UsbDriveSeller merchant = new Merchant(manufacturer); // 客户端调用商家的卖U盘方法 merchant.sellUsbDrive(); }
}
- 输出结果
商家开始销售U盘。
厂家生产并销售U盘。
商家销售完成,并提供售后服务。
这个简单的例子展示了如何使用接口定义卖U盘的行为,然后分别通过厂家类和商家类(作为代理)来实现这个行为。客户端通过调用商家的方法来间接调用厂家的方法,实现了基本的代理模式。
代理类完成的功能:
- 目标类中方法的调用。
- 功能增强。
三、动态代理
在静态代理中目标类很多时候,可以使用动态代理,避免静态代理的缺点。
动态代理中目标类即使很多, 但也可以做到:
- 代理类数量可以很少。
- 当你修改了接口中的方法时,不会影响代理类。
动态代理: 在程序执行过程中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类。
换句话说: 动态代理是一种创建java对象的能力,让你不用创建实体类,就能创建代理类对象。
在Java中,要想创建对象:
- 创建类文件, java文件编译为class。
- 使用构造方法,创建类的对象。
1、动态代理的实现(有两种)
1、CGLIB动态代理(了解)
CGLIB动态代理是一种基于字节码生成的代理模式,它通过扩展被代理类来生成代理类。这种代理方式不需要被代理类实现接口,因此它可以代理那些没有实现接口的类。
在运行时,CGLIB通过修改字节码的方式动态生成被代理类的子类,并重写父类中的方法来实现代理功能。这样,通过调用子类的方法来间接调用父类的方法,可以达到对目标类方法的代理控制。
简单来说:CGLIB是第三方的工具库, 创建代理对象。CGLIB的原理是继承, CGLIB通过继承目标类,创建它的子类,在子类中重写父类中同名的方法, 实现功能的修改。因为CGLIB是继承,重写方法,所以要求目标类不能是final的, 方法也不能是final的。CGLIB的要求目标类比较宽松, 只要能继承就可以了。
CGLIB动态代理的优点主要有两个。首先,由于它不需要通过反射调用被代理类的方法,因此性能相对较高。其次,通过FastClass机制调用方法,比JDK动态代理的反射机制效率要高。
然而,CGLIB动态代理也有一些缺点。例如,它生成的代理类可能会比较庞大,占用更多的内存空间。此外,由于CGLIB是通过子类化实现代理的,因此它无法代理final类以及final方法。
总的来说,CGLIB动态代理提供了一种强大的代理机制,适用于那些需要代理没有实现接口的类,或者需要对final方法和final类进行代理的场景。CGLIB在很多的框架中使用, 比如 MyBatis ,Spring框架中都有使用。
2、jdk动态代理(理解)
使用java反射包中的类和接口实现动态代理的功能。
反射包 java.lang.reflect,里面有三个类: InvocationHandler
, Method
和 Proxy
。
- InvocationHandler: 接口方法原型,参数。
- Method method:目标类中的方法,jdk提供method对象的。
- Object proxy:jdk创建的代理对象,无需赋值。
- Object[] args:目标类中方法的参数, jdk提供的。
1、InvocationHandler 接口(调用处理器)
就一个方法invoke():表示代理对象要执行的功能代码。
你的代理类要完成的功能就写在invoke()方法中。
代理类完成的功能:
- 调用目标方法,执行目标方法的功能。
- 功能增强,在目标方法调用时,增加功能。
InvocationHandler 接口:表示你的代理要干什么。
怎么用:
- 创建类实现接口InvocationHandler。
- 重写invoke()方法, 把原来静态代理中代理类要完成的功能,写在这。
2、Method类
表示方法的, 确切的说就是目标类中的方法。和InvocationHandler 接口不同。
作用:通过Method可以执行某个目标类的方法,Method.invoke()
。
// 语法:method.invoke(目标对象,方法的参数)Object ret = method.invoke(service2, "李四");// 说明: method.invoke()就是用来执行目标方法的,等同于静态代理中的// 向厂家发送订单,告诉厂家,我买了u盘,厂家发货float price = factory.sell(amount); // 厂家的价格。
3、Proxy类
核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法()。现在我们是使用Proxy类的方法,代替new的使用。
方法: 静态方法 newProxyInstance()
。
作用是: 创建代理对象, 等同于静态代理中的 TaoBao taoBao = new TaoBao();
。
静态方法 newProxyInstance() 参数:
ClassLoader
类加载器,负责向内存中加载对象的。- 使用反射获取对象的ClassLoader类的a类,
a.getCalss().getClassLoader();
,目标对象的类加载器。 - Class<?>[] interfaces: 接口,目标对象实现的接口,也是反射获取的。
- InvocationHandler handle:我们自己写的,代理类要完成的功能。返回值就是代理对象。
2、动态代理实现示例
实现JDK动态代理主要遵循以下步骤:
- 定义业务接口:首先,需要有一个或多个业务接口,这些接口将定义你想要代理的方法。
public interface MyService { void doSomething();
}
- 实现业务接口:接着,需要实现这些接口的具体业务逻辑。
public class MyServiceImpl implements MyService { @Override public void doSomething() { System.out.println("Doing something..."); }
}
- 创建InvocationHandler实现类:现在,需要创建一个实现了InvocationHandler接口的类。这个类将负责处理代理实例上的方法调用,并决定是调用实际对象的方法,还是添加额外的逻辑。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在调用方法前可以添加额外的逻辑 System.out.println("Before method call."); // 调用实际对象的方法 Object result = method.invoke(target, args); // 在调用方法后可以添加额外的逻辑 System.out.println("After method call."); return result; }
}
- 创建并获取代理对象:最后,你使用Proxy类的newProxyInstance方法创建代理对象。这个方法需要三个参数:类加载器、代理类实现的接口列表,以及InvocationHandler实例。
import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { // 创建实际对象 MyService myService = new MyServiceImpl(); // 创建InvocationHandler实例 MyInvocationHandler handler = new MyInvocationHandler(myService); // 创建代理对象 MyService proxy = (MyService) Proxy.newProxyInstance( MyService.class.getClassLoader(), myService.getClass().getInterfaces(), handler ); // 调用代理对象的方法,实际上会调用InvocationHandler的invoke方法 proxy.doSomething(); }
}
运行上述主类Main的main方法。你将看到在调用 doSomething()
方法前后,MyInvocationHandler
的 invoke()
方法中的额外逻辑被执行了。
这样就完成了JDK动态代理的实现。这个过程中,Proxy类负责生成代理类的字节码,并创建代理对象,而InvocationHandler负责实现代理方法的行为。通过这种方式,可以在不修改原始类代码的情况下,为原始类添加额外的逻辑或行为。