代理就是中介,中间人。法律上也有代理,比如代理律师之类,委托人将自己的一部分权限委托给代理者,
代理者就拥有被代理者(委托人)的部分权限,并且可以以被代理人的名义来实行这些权限,此时代理者
与委托人等同,当然代理人也可以在实行权限时配合自己的能力来进行,当然不能超出这个权限。
Java中的代理模式类似于上面的代理,也是为一个类(委托类)创建一个代理类,来代表它来对外提供功能。
如何在Java中创建一个类的代理类呢?
很简单,我们需要创建一个公共接口,委托类要实现这个接口,再创建一个接口的实现类作为代理类,
在这个类中的方法中可以直接调用委托类中的同名方法,外部类要进行访问时,可以使用接口指向代理类实例,
调用代理类中的方法,从而间接调用委托类中的具体方法实现。
静态代理
以法律上的委托代理为例
总接口:ZiRanRen
public interface ZiRanRen {
void Quanli();
}
委托人:MaYun
public class MaYun implements ZiRanRen {
public void eat() {
System.out.println("今天吃满汉全席");
}
public void drink() {
System.out.println("今天喝大西洋");
}
@Override
public void Quanli() {
System.out.println("我赋予我的代理律师来行使这些权利,此时代理律师全权代理我处理某些事务");
}
}
代理律师:LvShi
public class LvShi implements ZiRanRen {
@Override
public void Quanli() {
new MaYun().Quanli();
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
ZiRanRen ls = new LvShi();
ls.Quanli();
}
}
执行结果
我赋予我的代理律师来行使这些权利,此时代理律师全权代理我处理某些事务
可以看出,我们想对外开放某些功能,就可以将这些功能在代理类中被引用,如此一来,
屏蔽了我们不想外露的功能,只将我们想开放的功能开放出来。
即委托类中其实是可以有很多方法的,很多功能的,我们可以酌情对外开放,
代理类犹如一道大门,将委托类与外部调用者隔绝开来,只将部分功能赋予这个大门,
来代替委托类行使这个功能,哪怕最终还是要牵扯到自身(因为最终还是要调用委托类的对应方法实现)。
总结
代理模式很简单,只要记住以下关键点,简单易实现:
(1)代理类与委托类实现同一接口
(2)在委托类中实现功能,在代理类的方法中中引用委托类的同名方法
(3)外部类调用委托类某个方法时,直接以接口指向代理类的实例,这正是代理的意义所在:屏蔽。
代理模式场景描述:
(1)当我们想要隐藏某个类时,可以为其提供代理类
(2)当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现
(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中进行权限判断
来进行不同权限的功能调用)
(3)当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展
(只针对简单扩展,可在引用委托类的语句之前与之后进行)
代理模式虽然实现了调用者与委托类之间的强耦合,但是却增加了代理类与委托类之间的强耦合
(在代理类中显式调用委托类的方法),而且增加代理类之后明显会增加处理时间,拖慢处理时间。
动态代理
为了解决上面静态代理的缺陷,动态代理就产生了。
动态代理有两种方式,一种是JDK实现的动态代理,一种是CGLIB实现的动态代理,
其中JDK动态代理是基于接口的,CGLIB是基于类的,明显CGLIB要高效一些,因为它免去了接口的消耗与限制。
JDK动态代理
JDK动态代理是基于接口实现的,采用的是反射原理。
要使用JDK动态代理需要实现InvocationHandler接口,InvocationHandler接口是JDK专为
动态代理而设计的接口,其有数个实现类,是JDK中使用动态代理来实现一些其他的功能,
我们的实现类似于它们,完全可以参考其代码。
InvocationHandler接口中只有一个方法invoke,这个方法用于调用具体的实现来完成功能,
调用的过程需要借助于Java反射原理。
自然人接口:ZiRanRen
public interface ZiRanRen {
void quanli();
}
马云:MaYun
public class MaYun implements ZiRanRen {
public void eat() {
System.out.println("今天吃满汉全席");
}
public void drink() {
System.out.println("今天喝大西洋");
}
@Override
public void quanli() {
System.out.println("我赋予我的代理律师来行使这些权利,此时代理律师全权代理我处理某些事务");
}
}
InvocationHandler实例:MyInvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler{
private Object target;
MyInvocationHandler(){super();}
MyInvocationHandler(Object target){
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("quanli".equals(method.getName())){
return method.invoke(target, args);
}else
return method.invoke(target, args);
}
}
测试类:Clienter
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Clienter {
public static void main(String[] args) {
//动态代理实现
ZiRanRen ls = new MaYun();
InvocationHandler invocationHandler = new MyInvocationHandler(ls);
ZiRanRen Proxyer = (ZiRanRen)Proxy.newProxyInstance(ls.getClass().getClassLoader(), ls.getClass().getInterfaces(), invocationHandler);
Proxyer.quanli();
}
}
执行结果
我赋予我的代理律师来行使这些权利,此时代理律师全权代理我处理某些事务
(1)MyInvocationHandler实现类中定义的Object target中的target指的是接口的实现类,
即之前所说的委托类,本段代码中指的就是MaYun类,这个从最后测试类中第7、8行可以看出,
我们将MyYun类的实例作为参数来生成MyInvocationHandler实例。
(2)代码编写的阶段并未显式调用MaYun类的quanli方法,但是最后却是实实在在被调用了,这时怎么回事呢?
其实这正是动态代理的魅力所在,动态代理的动态二字的含义是不局限于某一个具体类,
当我们实现了一个InvocationHandler接口的实例之后,就可以使用这个实例进行任意类的代理,
被代理的类是在运行时才会确定(这也正是反射的能力之所在),编码阶段根本无从知晓,复用性极强。
(3)InvocationHandler接口的三个参数问题:第一个参数proxy表示的是被代理的实例
(此处指MaYun类的实例ls),第二个参数method代表要执行的被代理类中的方法
(此处指MaYun类中的quanli方法),第三个参数表示被代理类中方法的参数列表
(此处指MaYun类的quanli方法的参数列表)。如此看来,InvocationHandler的三个参数
就是精确指定要执行哪个类的哪个方法,参数是***。当然在代码中还是泛指,这个精确要到
程序运行时才能真正体现。
(4)测试类中创建代理类实例的模式与静态代理的模式一致,表示与被代理类实现同一接口。
使用Proxy代理类的静态方法newProxyInstance方法来完成代理类实例的创建,
其有三个参数:
第一个是实例类的加载器,
第二个是实例类实现的接口,
第三个是InvocationHandler接口的实例。
CGLIB动态代理
CGLIB动态代理不同于JDK提供的动态代理,它不再基于接口,而是直接基于被代理类来完成代理模式。
我们可以将CGLIB看作是一个代理类的生成工具,通过编程的方式来完成一个类的创建及实例的创建,
不同于我们以前直接使用Class来创建类的方式,明显复杂性提升很多,但是CGLIB将这种复杂的类生成功能屏蔽化,
我们只需要借助于它所提供的API就可以完成代理类的生成和实例的创建。
要使用CGLIB,需要向导入cglib的jar包,此处我们使用:cglib-nodep-2.2.2.jar。
被代理类:MaYun
public class MaYun {
public void eat() {
System.out.println("今天吃满汉全席");
}
public void drink() {
System.out.println("今天喝大西洋");
}
public void quanli() {
System.out.println("我赋予我的代理律师来行使这些权利,此时代理律师全权代理我处理某些事务");
}
}
方法回调器:Cgliber
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* Cglib就犹如一个代码式的类生成器,一般手动创建类的内容涉及到的东西,都需要通过Cglib提供的API来完成。
* 如下代码中:设置超类和设置回调,对应于正常类编写时,编写的继承类与方法回调
* 这里实现的是MethodInterceptor接口(方法拦截器),这个接口的作用就是实现方法拦截(回调)
*
*/
public class Cgliber implements MethodInterceptor{
private Object target;//这个target指的就是被代理类的实例
/*
* 生成代理实例(依据被代理类来生成代理类),Enhancer则是代理类生成工具
*/
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());//设置被继承类(超类)
enhancer.setCallback(this);//设置回调
return enhancer.create();
}
/*
* 回调方法
* intercept是拦截之意,此处是指使用代理方法proxy来调用原类(obj)中的指定方法(method),带参数(args),
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
proxy.invokeSuper(obj, args);
return null;
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
/*
* Cgliber可以看成是一个带有类生成能力的方法回调器
*/
Cgliber cgliber = new Cgliber();
/*
* 使用方法回调器的类生成功能生成代理类实例:mayunCgliber
* 由于getInstance方法返回值为Object类型,并不是MaYun类的子类型,所以需要将其强转为MaYun类型,实际上mayunCgliber是代理实例
*/
MaYun mayunCgliber = (MaYun) cgliber.getInstance(new MaYun());
mayunCgliber.quanli();
}
}
执行结果
我赋予我的代理律师来行使这些权利,此时代理律师全权代理我处理某些事务
使用CGLIB的关键就在方法回调器类Cgliber中:
(1)实现MethodInterceptor接口,该接口中只有一个intercept方法,这个方法的作用就是拦截和回调,
拦截的意思是我们可以在具体执行方法的前、后加上其他的代码来扩展功能,回调之意为在这个方法中调用父类
(被代理类)中的指定方法。
(2)为了生存代理类及其实例,需要在Cgiber中增加创建代理类的代码,使用CGLIB提供的API来进行类生成,
CGLIB使用Enhancer作为类生成器,使用其下的各个接口就能完成类和实例的创建。
其实在测试类中我们可以发现一点,无论是JDK的动态代理还是CGLIB的动态代理,
代理类与被代理类之间是存在耦合的,这与静态代理一致,这个耦合体现在方法的调用之上。