这一篇博客主要是介绍两种代理模式的概念以及基本使用,下一篇博客则是动态代理的深入理解。
其实很简单,生活中很常见。
比如你在宾馆看到一张小卡片,照着卡片上的号码拨过去,接电话的可能是一位抠脚大汉,等你们商量好了之后,过一会可能就会有女生来敲门。。。然后发生一些。。。。。
OK,打住,让我们举个正常点的例子。
比如你想出国留学,OK,你有钱,但是不能有钱到把每一家美国大学都拜访个遍吧,也不能把每一家英国大学都拜访个遍吧,也不能把…。所以你要找个中介(代理),这个中介不仅可以节约你的时间和金钱,还可以代替学校完成招生功能。
OK,我们来梳理一下上面的例子的特点:
①代理是学校代理,学校是目标。
②你无法直接访问到目标,只能通过代理
为啥找中介?
家长没有能力直接去访问学校。或者说这个学校不接受个人来访。
比如你买东西都是去找商家,基本没有找厂家的(不接受访问),这个商家也是一个代理。
我们在开发中也有这种情况,
比如a要访问c,c不让a调用,但是让b调用(b是代理),所以a可以通过访问b再访问c。我们把abc的关系就叫做代理模式
代理模式是指:
为其他对象提供一种代理,以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。
举个实际的代码例子:比如手机验证码登录功能。比如你写了一段程序,功能是可以法验证码给用户。但是这个验证码不是你自己直接找移动公司或者联通电信公司,让他们员工发的,而是写了一段程序,这个程序可以理解成移动公司发短信业务的代理。
①功能增强:比如中介不仅可以帮你入学,可能还可以帮你买票安排车程之类的。即在原有的功能上增加了额外的功能。
②控制访问:代理类不让你访问目标。比如商家不让你直接访问厂家。
①代理类是自己手工实现的,也就是说自己需要创建代理类。
②同时你所要代理的目标类是确定的
比如模拟一个买U盘过程,有用户(客户端)和商家(代理)和厂家(目标)。要知道商家和厂家完成的功能有一致的部分,即卖U盘。
实现步骤
①创建一个接口,定义卖U盘的方法(这个表示你的厂家和商家都做的事情)
②创建厂家类,实现①中的接口
③创建商家类,实现①中的接口。
④创建客户端类,调用商家的方法买一个U盘
具体代码
接口
public interface UsbSell {
//定义卖U盘的方法
float sellUsb(int num);
//float是U盘的价钱,num是买的数量
}
工厂类
//这是工厂
public class UsbFactory implements UsbSell{
@Override
public float sellUsb(int num) {
//一个U盘十块钱,订购的总价钱是数量乘以10
return num*10;
}
}
代理类(注意这里,首先是调用了厂家的sellUsb方法)
//定义商家,也就是代理类
public class TaoBao implements UsbSell{
//首先,声明你代理的是什么厂家
private UsbFactory factory = new UsbFactory();
@Override
public float sellUsb(int num) {
//从厂家订货(这个方法之后的功能都是增强功能)
float inputPrice = factory.sellUsb(num);
//加利润(这是增强的功能)
float outputPrice = inputPrice + num*10;//一个U盘赚十块钱
return outputPrice;
}
}
这里代理类中相应方法完成的功能:
①目标类中方法的调用
②功能增强
客户类
public class Client {
void buyUsb(int num) {
//创建商家对象
TaoBao taobao = new TaoBao();
//开始买
float price = taobao.sellUsb(num);
System.out.println("你花了"+price+"钱");
}
}
购买
public static void main(String[] args) {
Client client = new Client();
client.buyUsb(1);
}
优点:
①实现简单,容易理解
缺点:
②当目标类增多时,代理类可能会成倍地增加(代理类数量过多)
③当接口中代码修改时,会影响众多的实现类,代码基本都会需要更改。
当目标类过多的时候,可以使用动态代理,以避免静态代理的上述缺点。
也就是说即使目标类很多,代理类的数量仍然可以很少,当修改了接口中的方法时,也不会影响代理类。
动态代理是在程序执行过程中,使用jdk的反射机制,创建代理类对象,并动态的指定要代理的目标类。(不需要定义代理类的.java源文件)
换句话说,动态代理是一种创建Java对象的能力,让你不用创建上述例子中的Taobao类,就能创建代理类对象。
①jdk动态代理(需要掌握)
使用Java反射包中的类和接口实现动态代理的功能。
反射包:java.lang.reflect,里面有三个类和接口:InvocationHandler,Method,Proxy
②cglib动态代理(了解即可)
cglib是第三方的工具库,它的原理是继承,cglib通过继承目标类,创建它的子类,在子类中重写父类中同名的方法,实现功能的修改。
因为cglib需要继承并重写方法,所以要求目标类不能是final的,方法也不能是final的。cglib对目标类的要求比较宽松,只要能继承就可以了。cglib在mybatis,spring等框架中都有使用
复习下反射,并熟悉下Method方法
假如有一个接口和一个实现类,要执行接口中的某个方法可以这样操作
//利用反射执行类中的方法,核心是Method
oneInterface obj = new interfaceImpl();
//获得sayHello方法名对应的 类对象(需要抛出异常)
Method method = oneInterface.class.getMethod("sayHello", String.class);
//通过Method执行sayHello方法
/*
invoke是Method类中的一个方法,它可以用来执行方法,比如这里的sayHello方法
它有两个参数,第一个要传入对象,即执行哪个对象的某个方法
第二个要传入参数,即方法执行的参数
同时还有一个返回值,表示方法执行后的返回值
*/
method.invoke(obj,"小王");
要抛出异常
这里invoke的源码声明是这样的
public Object invoke(Object obj, Object... args)
第一个要传入对象,即执行哪个对象的某个方法
第二个要传入参数类型的类,即方法执行的参数的class类型
jdk动态代理的实现,主要是用到三个东西:InvocationHandler,Method,Proxy,同时,目标类必须实现某个接口。没有接口时,使用cglib动态代理。
①InvocationHandler接口
它被称为调用处理器,里面就定义了一个方法,即invoke(),invoke()表示代理对象要执行的功能代码,代理类完成的功能就写在invoke方法当中。
源码中的定义如下
public Object invoke(Object proxy, Method method, Object[] args)
参数解释
Object proxy表示jdk创建的代理对象,这里无需手动赋值
Method method是目标类中的方法,也无需手动赋值,因为jdk提供method对象
Object[] args表示目标类中方法的参数,也无需手动赋值,也是jdk提供
②Method类,表示目标类中的方法。
通过method.invoke()可以执行某个目标类的方法。
注意,这里invoke和前面的invoke不一样,只是重名了而已。前面的invoke是InvocationHandler接口里面定义的唯一方法,这里的invoke是Method里面的方法。
(3)Proxy类,它可以表示代理对象,通过它可以创建代理对象,代替new的使用。
(4)如何创建代理对象?
使用静态方法newProxyInstance()
源码定义
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数解释:
Ⅰ.ClassLoader loader,是类加载器,作用是向内存中加载对象。使用反射可以获得目标对象的ClassLoader
Ⅱ.Class>[] interfaces是接口,代表目标对象实现的接口,这里也是通过反射来获取。
Ⅲ.InvocationHandler h是我们自己写的,也就是代理类需要完成的功能
返回值就是代理对象
①创建接口,定义目标类要完成的功能
public interface UsbSell {
//定义卖U盘的方法
float sellUsb(int num);
//float是U盘的价钱,num是买的数量
}
②创建目标类,实现接口(这里之前和静态代理步骤一样)
//这是工厂
public class UsbFactory implements UsbSell{
@Override
public float sellUsb(int num) {
//一个U盘十块钱,订购的总价钱是数量乘以10
return num*10;
}
}
③创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能,即调用目标类的目标方法和增强功能
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MySellHandler implements InvocationHandler {
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;//不能百分之百确定返回值是什么类型,所以用Object类接受
res = method.invoke(target,args);//参数为目标对象和参数
if(res != null){
Float price = (Float)res;
price = price + 20;
res = price;
}
return res;
}
}
④使用Proxy类的静态方法创建代理对象,并把返回值转为接口类型
//1.创建目标对象
UsbFactory factory = new UsbFactory();
//2.创建InvocationHandler对象
InvocationHandler handler = new MySellHandler(factory);
//3.创建代理对象
UsbSell proxy = (UsbSell)Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler);
System.out.println(proxy.sellUsb(2));
这里可以很明显的看出:
动态代理可以在不改变原来目标方法的前提下,在代理中增强自己的功能代码。
比如在实际的程序开发中,有一个功能是其他人写的,但是还不能满足你的需要,这个时候你就可以用代理,调用它的方法之后再增强它的功能。这样你并没有直接改变目标方法。一个是你没有时间去改变,再一个是这个功能是别人写的,一般也不会把.java文件直接发给你
//接口
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "超人的信仰";
}
@Override
public void eat(String food) {
System.out.println("超人喜欢吃" + food);
}
}
/*
要想实现动态代理,需要解决的问题?
①如何根据加载到内存中的被代理类,动态地创建一个代理类及其对象
②当通过代理类的对象调用方法时,如何动态地去调用被代理类中的同名方法
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象,解决问题一
public static Object getProxyInstance(Object obj){
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
myInvocationHandler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),myInvocationHandler);
}
}
class MyInvocationHandler implements InvocationHandler{
//被代理类的对象
Object obj;
//对被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//上述方法的返回值就作为当前类中invoke()的返回值
Object returnValue = method.invoke(obj,args);
return returnValue;
}
}
public class Main {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human)ProxyFactory.getProxyInstance(superMan);
proxyInstance.eat("北京烤鸭");
System.out.println(proxyInstance.getBelief());
}
}
输出结果
超人喜欢吃北京烤鸭
超人的信仰