动态代理
文章目录
动态代理: 基于反射机制。
1、动态代理总括
1.1、★ 什么是动态代理 ?
使用 jdk 的反射机制,创建对象的能力, 创建的是代理类的对象。 而不用你创建代理类文件。不用写java文件。
动态: 在程序执行时,调用 jdk 提供的方法才能创建代理类的对象。
注意: jdk 动态代理,必须有接口,目标类必须实现接口, 没有接口时,需要使用 cglib
动态代理。
1.2、★ 知道动态代理能做什么 ?
可以在不改变原来目标方法功能的前提下, 可以在代理中增强自己的功能代码。
eg.
你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用,GoNong.class , GoNong gn = new GoNong(), gn.print();
你发现这个功能,现在还缺点, 不能完全满足我项目的需要。 我需要在 gn.print() 执行后,需要自己在增加代码。
用代理实现 gn.print() 调用时, 增加自己代码, 而不用去改原来的 GoNong 文件。
2、什么是代理?
代购, 中介,换 ip,商家等等。
比如有一家美国的大学, 可以对全世界招生。 留学中介(代理)
留学中介(代理): 帮助这家美国的学校招生, 中介是学校的代理, 中介是代替学校完成招生功能。
代理特点:
- 中介和代理他们要做的事情是一致的: 招生;
- 中介是学校代理, 学校是目标;
- 家长 — 中介(学校介绍,办入学手续)---- 美国学校;
- 中介是代理,不能白干活,需要收取费用;
- 代理不让你访问到目标。
在开发中也会有这样的情况, 你有 a 类, 本来是调用 c 类的方法, 完成某个功能。 但是 c 不让 a 调用。
a ----- 不能调用 c的方法。
在 a 和 c 直接 创建一个 b 代理,c 让 b 访问。
a -- 访问b --- 访问c
实际的例子: 登录,注册有验证码, 验证码是手机短信。
中国移动, 联通能发短信。
中国移动, 联通能有子公司,或者关联公司,他们面向社会提供短信的发送功能
张三项目发送短信 ---- 子公司,或者关联公司 ----- 中国移动, 联通
3、★ 使用代理模式的作用
- ★功能增强: 在你原有的功能上,增加了额外的功能。 新增加的功能,叫做功能增强。
- 控制访问: 代理类不让你访问目标,例如商家不让用户访问厂家。
4、代理的分类
-
静态代理:①代理类是自己手工实现的,自己创建一个 java 类,表示代理类。②同时你所要代理的目标类是确定的。
优点:
- 实现简单
- 容易理解
缺点【当项目中,目标类和代理类很多时候】:
- 当目标类增加了, 代理类可能也需要成倍的增加。 代理类数量过多;
- 当你的接口中功能增加了, 或者修改了,会影响众多的实现类,厂家类,代理都需要修改。
影响比较多。
在静态代理中目标类很多时候,可以使用动态代理,避免静态代理的缺点。
-
动态代理:在程序执行过程中,使用 jdk 的反射机制,创建代理类对象, 并动态的指定要代理目标类。
优点:
- 代理类数量可以很少
- 当你修改了接口中的方法时,不会影响代理类
缺点:
- 实现复杂
- 不容易理解
5、★代理类的功能
- 调用目标方法,执行目标方法的功能
- 功能增强:在目标方法调用时,增加功能
6、静态代理的实现
6.1、业务需求
模拟一个用户购买 u 盘的行为:
用户是客户端类;
商家:代理,代理某个品牌的 u 盘。
三者的关系: 用户(客户端)— 商家(代理【类】)— 厂家(目标【类】)
商家和厂家都是卖 u 盘的,他们完成的功能是一致的,都是卖 u 盘。
6.2、实现步骤
- 创建一个接口,定义卖 u 盘的方法, 表示你的厂家和商家做的事情;
- 创建厂家类,实现步骤 1 的接口;
- 创建商家,就是代理,也需要实现步骤 1 中的接口;
- 创建客户端类,调用商家的方法买一个 u 盘。
6.3、代码模板
6.3.1、接口【表示功能的,厂家,商家都要完成的功能】
public interface UsbSell {
//返回值表示一个u盘的价格。
float sell(int count);
}
6.3.2、目标类【厂家】
package org.example.factory;
import org.example.service.UsbSell;
//目标类: 金士顿厂家, 不接受用户的单独购买。
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int count) {
System.out.println("目标类中的方法调用 , UsbKingFactory 中的sell ");
//一个128G的u盘是 85元。
return 85;
}
}
6.3.3、代理类【某公司】
package org.example.enterprise;
import org.example.factory.UsbKingFactory;
import org.example.service.UsbSell;
public class JD implements UsbSell {
//声明 商家代理的厂家具体是谁
private UsbSell factory = new UsbKingFactory();
@Override
public float sell(int count) {
//向厂家发送订单,告诉厂家,我买了u盘,厂家发货
float price = factory.sell(1);
//商家 需要加价, 也就是代理要增加价格。
price += 40;//增强功能,代理类在完成目标类方法调用后,增强了功能。
System.out.println("京东送您100京东豆!");
return price;
}
}
6.3.4、客户端【我们自己】
package org.example;
import org.example.enterprise.JD;
import org.example.enterprise.TaoBao;
public class Customer {
public static void main(String[] args) {
/* TaoBao t = new TaoBao();
float price = t.sell(1);
System.out.println(price);*/
JD j = new JD();
float price = j.sell(1);
System.out.println(price); //41
}
}
6.4、总结
- 此时我们只有一家 JD 卖金士顿 u 盘,当在来一家 TaoBao 也要卖金士顿 u 盘时,就要再写一个代理类,这要是有 100 家企业要买 u 盘就要写 100 个代理类,会显得代理类数量过多。并且当目标类【u 盘厂家】多起来了之后,代理类都是成倍数上涨的!
- 当你的接口中功能增加了, 或者修改了,会影响众多的实现类,厂家类,代理都需要修改。影响比较多。
7、★ 动态代理
7.1、总括
- 动态代理是一种创建 java 对象的能力,让你不用创建 JD 类,就能创建代理类对象。
- 动态代理使用 java 反射包中的类和接口实现动态代理的功能
- 动态代理使用到的三个类:反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy。
7.2、动态代理的分类
- jdk 动态代理:
- 使用 java 反射包中的类和接口实现动态代理的功能;
- 使用反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy。
- cglib 动态代理:
- cglib 是第三方的工具库, 创建代理对象;
- cglib 的原理是继承, cglib 通过继承目标类,创建它的子类,在子类中重写父类中同名的方法, 实现功能的修改;
- 因为 cglib 是继承,重写方法,所以要求目标类不能是 final 的, 方法也不能是 final 的;
- cglib 的要求目标类比较宽松, 只要能继承就可以了。cglib 在很多的框架中使用, 比如 mybatis ,spring 框架中都有使用。
7.3、★ InvocationHandler , Method, Proxy 解析
7.3.1、InvocationHandler【接口】
- 表示你的代理类要干什么;
- InvocationHandler 接口(调用处理器):就一个方法 invoke();
- invoke():表示代理对象要执行的功能代码。你的代理类要完成的功能就写在 invoke() 方法中。
7.3.1.1、 invoke()方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
参数:
- Object proxy:jdk 创建的代理对象,无需赋值;
- Method method:目标类中的方法,jdk 提供 method 对象的;
- Object[] args:目标类中方法的参数, jdk 提供的
7.3.1.2、★ 怎么用?
- 创建类实现接口
InvocationHandler
- 重写
invoke()
方法, 把原来静态代理中代理类要完成的功能,写在这。
7.3.2、Method
-
通过 Method 可以执行某个目标类的方法,Method.invoke();
method.invoke(调用该方法的对象,方法的参数);
注意: Method 类中的 invoke() 方法和 InvocationHandler 接口的 invoke() 方法只是名字一样,两个并不一样!
7.3.3、Proxy
核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法()
,现在我们是使用 Proxy 类的方法,代替 new 的使用。
7.3.3.1、newProxyInstance() 方法【静态方法】
-
作用:创建代理对象, 等同于静态代理中的
TaoBao taoBao = new TaoBao();
-
方法原型:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数:
- ClassLoader loader:类加载器,负责向内存中加载对象的。 使用反射获取对象的 ClassLoader.【类a , a.getCalss().getClassLoader(), 目标对象的类加载器】
- Class<?>[] interfaces: 接口, 目标对象实现的接口,也是反射获取的。【类a , a.getClass().getInterfaces().】
- InvocationHandler h : 我们自己写的,代理类要完成的功能
- 返回值:就是代理对象
8、动态代理的实现
8.1、业务需求
模拟一个用户购买 u 盘的行为
用户是客户端类
商家:代理,代理某个品牌的 u 盘
三者的关系: 用户(客户端)— 商家(代理【类】)— 厂家(目标【类】)
商家和厂家都是卖u盘的,他们完成的功能是一致的,都是卖 u 盘。
8.2、实现步骤
- 创建一个接口,定义卖 u 盘的方法, 表示你的厂家和商家做的事情;
- 创建厂家类,实现步骤 1 的接口;
- 创建类实现接口
InvocationHandler
重写invoke()
方法, 把原来静态代理中代理类要完成的功能,写在这; - 创建客户端类,调用商家的方法买一个 u 盘。
8.3、代码模板
8.3.1、接口【表示功能的,厂家,商家都要完成的功能】
package org.example.service;
public interface UsbSell {
float sell(int count);
}
8.3.2、目标类【厂家】
package org.example.factory;
import org.example.service.UsbSell;
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int count) {
System.out.println("目标类中的方法调用 , UsbKingFactory 中的sell ");
return 85;
}
}
8.3.3、创建类实现接口 InvocationHandler
package org.example.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//必须实现InvocationHandler接口,完成代理类要做的功能(1.调用目标方法,2.功能增强)
public class MySellHandler implements InvocationHandler {
//传入进来的对象
private Object target;
//动态代理:目标对象是活动的,不是固定的,需要传入进来。
//传入是谁,就给谁创建代理。
public MySellHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
//float price = factory.sell(1);
res = method.invoke(target, args);//执行目标方法
//商家 需要加价, 也就是代理要增加价格。
//price = price + 25; //增强功能,代理类在完成目标类方法调用后,增强了功能。
if (res != null){
Float price = (Float) res;
price += 25;
res = price;
}
//在目标类的方法调用后,你做的其它功能,都是增强的意思。
System.out.println("淘宝送您优惠券一张!");
return res;
}
}
8.3.4、客户端【我们自己】
package org.example;
import org.example.factory.UsbKingFactory;
import org.example.handler.MySellHandler;
import org.example.service.UsbSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Customer {
public static void main(String[] args) {
//创建代理对象,使用Proxy
//1. 创建目标对象
UsbSell factory = new UsbKingFactory();
//2.创建InvocationHandler对象
InvocationHandler handler = new MySellHandler(factory);
//3.创建代理对象
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handler);
//com.sun.proxy.$Proxy0 : 这是jdk动态代理创建的对象类型。
System.out.println(proxy.getClass().getName());
//4.通过代理执行方法
float price = proxy.sell(1);//执行的是MySellHandler类里面的invoke方法
System.out.println(price);
}
}
8.4、程序执行流程
程序执行到 proxy.sell(1)
语句会调用 InvocationHandler 实现类的 invoke()
方法,然后一次执行下来,最终返回值返回给方法调用处 proxy.sell(1)。
注意:proxy 是com.sun.proxy.$Proxy0
,所以才会调用 InvocationHandler 实现类的 invoke()
方法。
9、★ 实现动态代理步骤
- 创建接口,定义目标类要完成的功能;
- 创建目标类实现接口;
- 创建 InvocationHandler 接口的实现类,在 invoke 方法中完成代理类的功能
- 调用目标方法
- 增强功能
- 使用 Proxy 类的静态方法,创建代理对象。 并把返回值转为接口类型。
10、动态代理代码模板
10.1、InvocationHandler 接口实现类模板
public class MySellHandler implements InvocationHandler {
//传入进来的对象
private Object target;
public MySellHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
//float price = factory.sell(1);
res = method.invoke(target, args);//执行目标方法
//增强功能代码
return res;
}
}
10.2、客户端类模板
public class Customer {
public static void main(String[] args) {
//创建代理对象,使用Proxy
//1. 创建目标对象
UsbSell factory = new UsbKingFactory();
//2.创建InvocationHandler对象
InvocationHandler handler = new MySellHandler(factory);
//3.创建代理对象
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handler);
//4.通过代理执行方法
float price = proxy.sell(1);//执行的是MySellHandler类里面的invoke方法
System.out.println(price);
}
}