Java基础相关 | 静态/动态代理
说起代理、代购、中介、换ip、商家等等,都是生活中的代理的例子
比如有一家X国的大学,可以对全世界招生。留学中介(代理)
留学中介(代理):帮助这家X国的学校招生,中介是学校的代理,中介是代替学校完成招生功能。
代理特点:
- 中介和代理他们要做的事情是一致的:招生。
- 中介是学校代理,学校是目标。
- 家长—中介(学校介绍,办入学手续)----X国学校。
- 中介是代理,不能白干活,需要收取费用。
- 代理不让你访问到目标。
为什么要找代理/中介?
- 中介是专业的,方便
- 家长现在不能自己去找学校。家长没有能力访问学校。或者美国学校不接收个人来访。
- 买东西都是商家卖,商家是某个商品的代理,你个人买东西,肯定不会让你接触到厂家的。
在开发中也会有这样的情况,你有a类,本来是调用c类的方法,完成某个功能
但是c不让a调用:
a 不能调用c的方法。
解决方法: 在a和c直接创建一个b代理,c让b访问。
a --> 访问b --> 访问c
实际的例子:登录,注册有验证码,验证码是手机短信。中国移动,联通或者旗下的子机构才能发短信。
使用代理模式的作用
- 功能增强:在你原有的功能上,增加了额外的功能。新增加的功能,叫做功能增强。
- 控制访问:代理类不让你访问目标,例如商家不让用户访问厂家。
1. 静态代理
- 代理类是自己手工实现的,自己创建一个java类,表示代理类
- 同时你所要代理的目标类是确定的。
优点:
- 实现简单
- 容易理解。
缺点:
当目标类和代理类比较多的时候,有如下缺点:
- 目标类增加了,代理类也可能成倍的增加,导致代理类数量过多;
- 接口中的功能增加了,或者修改了,会影响众多的实现类,厂家类、代理类都需要修改,耦合性极强!
1.1 实例
模拟一个用户购买u盘的行为
- 用户是客户端类
- 商家:代理,代理某个品牌的u盘
- 厂家:目标类.
三者的关系:用户(客户端)–> 商家(代理)–> 厂家(目标)工商家和厂家都是卖u盘的,他们完成的功能是一致的,都是卖u盘。
实现步骤:
- 创建一个接口:定义卖u盘的方法,表示你的厂家和商家做的事情
- 创建厂家类:实现1步骤的接口
- 创建商家:就是代理,也需要实现1步骤中的接口
- 创建客户端类:调用商家的方法买一个u盘
代理类完成的功能:
- 目标类中的方法调用
- 功能增强
落地实现:
1.接口:
public interface UsbSell{
float sell(int amount);
}
2.厂家类:
public class UsbKingFactory implements UsbSell{
@Override
public float sell(int amount){
return 85.0f;
}
}
public class TaoBao implements UsbSell{
private UsbKingFactory factory = new UsbKingFactory;
@Override
public float sell(int amount){
float price = factory.sell(amount);
price += 25;
System.out.println("送您一个优惠券下次可以用!");
return price;
}
}
Main主启动类实现:
InvocationHandler接口的实现类:
二者之间的关系:
2. 动态代理
2.1 什么是动态代理?
使用JDK反射机制,创建对象的能力,创建的是代理类对象,而不用创建类文件,也不用写JAVA文件。
动态: 在程序执行时,调用jdk提供的方法才能创建代理的对象。
注: jdk动态代理必须有接口,目标类必须实现接口;没实现接口需要使用CGlib动态代理;
2.2 动态代理能做什么?
可以在不改变原来目标方法的前提下,在代理中增强目标方法。
如调用其他人写好的文件(.class文件),但是功能还缺一点(如缺少print)可以通过动态代理实现,无需修改源文件;降低耦合性;
2.1 JDK动态代理
这是默认的生成代理方式
JDK动态代理通过反射实现,Method类表示方法,通过Method类可以执行多个方法。
JDK动态代理的实现:
在java.lang.reflect包中,有三个类:
- InvocationHandler:
只有一个方法:invoke()
:表示对象要执行的功能代码,代理类的功能写在该方法中;
方法参数:
InvocationHandler(Object proxy, Method method, Object[] args)
- proxy:jdk创建的代理对象,无需赋值;
- method:目标类中的方法,jdk提供method对象的;
- args:目标类中方法的参数,jdk提供的;
如何使用?
- 创建类去实现InvokeHandler接口;
- 重写invoke()方法;
- Method:表示目标类中的方法
作用:
- 通过Method可以执行某个目标类的方法,
Method.invoke();
- method.invoke(目标对象,方法的参数) 如:
Object ret = method.invoke(service2,"李四");
说明: method.invoke()就是用来执行目标方法的,等同于静态代理中的向厂家发送订单,告诉厂家,我买了u盘,厂家发货
float price = factory.sell (amount); //厂家的价格。
- Proxy:核心的对象,创建代理对象,静态对象是通过new类的构造方法,但是proxy可以代替new;
方法: 静态方法newProxyInstance()
(创建代理对象,等同于Taobao tb = new Taobao();)
参数: public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvokeHandler h)
- loader:目标对象的类加载器,一般获取方法:
a.getClass().getClassLoader()
- interfaces:目标对象实现的接口,通过反射获取
- h:代理类要完成的功能(我们自己写的)
- 返回值:返回自定义代理对象
落地实现:
目标接口:
public interface UsbSell{
float sell(int amount);
}
目标类:
public class UsbKingFactory implements UsbSell{
@Override
public float sell(int amount){
System.out.println("目标类中执行sell方法!");
return 85.5f;
}
}
实现:
class MySellHandler{
private Object target = null;
public MySellHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
Object res = null;
res = method.invoke(target, args);
price += 35;
System.out.println("淘宝商家,返回优惠券!");
}
}
定义Main方法:
public class MainShop{
public static void main(String[] args){
UsbSell factory = new UsbKingFactory;
InvocationHandler handler = new MySellHandler(factory);
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handler);
}
}
2.2 CGLib动态代理
CGLib在这里只做简述: 它是通过asm字节码生成器生成被代理类的子类,效率没有反射快,而且被代理类不能被final修饰否则不能被继承。