从前在看代理模式的时候,经常是一头雾水,和工厂模式相比,感觉这种模式很高深莫测,但是经过一段时间的开发,甚至用过很多反射模式之后。再来看代理模式,感觉也不是很难理解,以下是我对代理模式的学习过程。
对于代理,经过在网上学习了一段时间,也阅读了很多文章,个人总结为在不对对象本身进行修改的情况下,对对象进行功能的扩展即为代理。
通过网上的学习,代理一般可以分为静态代理与动态代理
假设我们现在有如下接口与对象,我们要对其添加一些功能
public interface PlayerInterface {
void interview();
void playBall();
}
public class Player implements PlayerInterface {
public void interview() {
System.out.println("接受采访");
}
public void playBall() {
System.out.println("开始打球");
}
}
我们举例这里有一个球员,球员所进行的采访动作需要经纪人进行安排,而打球不需要,但是球员本身不需要考虑这些。
所以我们就要去通过代理(经纪人),给他去设定新的功能,而不改变球员自身的功能。
一、静态代理
所谓静态代理,即使创建一个新的对象,让其与原球员实现同一个接口,使其方法与原球员完全一致,而后对所有方法进行额外的操作,具体代码如下。
//这里实现了和原对象相同的接口
public class PlayerProxy implements PlayerInterface {
//将需要代理的对象变为代理对象的一个属性
private Player player;
public PlayerProxy(Player player) {
this.player = player;
}
public void interview() {
//在进行真正的player.interview()之前,首先添加其他的操作
System.out.println("proxy -> 安排");
player.interview();
}
public void playBall() {
player.playBall();
}
public static void main(String[] args) {
PlayerProxy playerProxy = new PlayerProxy(new Player());
playerProxy.interview();
playerProxy.playBall();
}
}
我们通过与原球员实现同样的接口,新创建一个球员代理类,这样我们就可以固定写好每个方法都需要进行什么额外的操作。
我们首先将需要代理的对象作为此新对象的一个参数。当我们调用interview方法的时候,我们本来是想进行一次采访,我们在真正对于球员的操作之前,添加一个新的操作,如代码所示,进行安排。这样就可以在不改变原对象的情况下,对对象的每个方法进行重新的包装,我们可以在真正执行方法之前进行操作,也可以在之后进行操作,甚至可以改变最终的返回值。当然这就是根据业务进行的自定义操作了。
如上所说,打球是不需要安排的,所以我们并不需要给playball这个方法去添加其他的操作,直接调用原对象的相同方法即可。
//所以调用Main方法的输出结果如下
proxy -> 安排
接受采访
开始打球
但是久而久之会发现这样的方法并不是很好,如果当我们修改接口时,我们的代理类与原对象类,都需要进行重新的编写,如果代理类很多的话,这样将造成很庞大的代码修改量,所以就出现了下一种方法,也就是动态代理。
二、动态代理
我们的接口没有任何改变。但是对象类有了一些变化,仅仅是添加了一个我自己编写的自定义注解。
//声明生命周期
@Retention(RetentionPolicy.RUNTIME)
//保证只能在方法上使用
@Target({ElementType.METHOD})
public @interface WithoutProxy {
}
public class Player implements PlayerInterface {
public void interview() {
System.out.println("接受采访");
}
//添加的注解
@WithoutProxy
public void playBall() {
System.out.println("开始打球");
}
}
注解的作用随后再说,在动态代理中,我们主要使用反射的技术,通过invoke方法去调用代理类的方法,来实现代理类的构建,具体代码如下。
public class ProxyFactory {
//需要代理的类
private Player player;
public ProxyFactory(Player player) {
this.player = player;
}
public PlayerInterface getProxyInstance() {
return (PlayerInterface) Proxy.newProxyInstance(
player.getClass().getClassLoader(),
player.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Annotation[] annotations = method.getDeclaredAnnotations();
boolean needProxy = true;
for (Annotation annotation:
annotations) {
if(annotation.annotationType().isAssignableFrom(WithoutProxy.class)){
needProxy = false;
}
}
if(needProxy) {
System.out.println("proxy -> 安排");
}
return method.invoke(player, args);
}
}
);
}
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new Player());
PlayerInterface playerInterface = proxyFactory.getProxyInstance();
playerInterface.interview();
playerInterface.playBall();
}
}
首先我们可以看出,这是一个工厂,我们的这个工厂,将会去返回一个基于原对象类的一个代理类。然后当使用这个代理类进行操作的时候,他回去触发invoke方法,在这里我们可以对这里的所有方法进行包装,也就是说我们不用去对每一个方法都执行相同的处理,导致代码冗余了。
Proxy这个类也是出自 java.lang.reflect包下的,可以看出来他完全是利用的反射的理念去做的动态构建。
这里我们只需要调用Proxy.newProxyInstance方法就可以进行构建了
这个方法需要三个参数:
(1)原类的类加载器
(2)原类所实现的所有接口
(3)处理方法的包装逻辑,我们只需要重写InvocationHandler方法即可
到这里我再说一下我为什么要加上@WithoutProxy注解
首先我们对于所有方法的处理逻辑都写在了InvocationHandler中,所以如果我想对方法进行个性化操作,例如playball方法,我不需要进行代理,那么我必须要有一个特殊的逻辑。
当然我们也可以不用注解,去使用method.getName()方法去按照方法名去判断,那这样也未免太不优美了。
所以我决定通过判断这个方法有没有被@WithoutProxy注解去修饰,如果有的话,则跳过代理,直接执行原方法。
执行代码,会出现如下结果
proxy -> 安排
接受采访
开始打球
三、Cglib动态代理
这个也是我在网上新学到的知识,从上面几个方法可以看出,我们这些操作都需要原类去实现一个接口,才可以进行操作。
首先我们的静态代理需要和原类去实现同一个接口。
我们的动态代理也需要在方法中拿接口去当做参数。
这样就会出现一个问题,当我们频繁使用代理的时候,如果这个类没有接口并且不方便去实现一个接口,该怎么去做。
在网上了解到,Cglib会通过ASM字节码,来根据原类去动态生成一个代理类(原类的子类,所以我们必须要求这个类不是final,即不可继承的,如果类为final,则抛出异常,如果方法为final,则此方法不会被代理),而不需要实现任何的接口,其具体的代码如下。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
public class ProxyCglib implements MethodInterceptor{
private PlayerNoInterface player;
public ProxyCglib(PlayerNoInterface player) {
this.player = player;
}
public PlayerNoInterface getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(player.getClass());
enhancer.setCallback(this);
return (PlayerNoInterface) enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Annotation[] annotations = method.getDeclaredAnnotations();
boolean needProxy = true;
for (Annotation annotation:
annotations) {
if(annotation.annotationType().isAssignableFrom(WithoutProxy.class)){
needProxy = false;
}
}
if(needProxy) {
System.out.println("proxy -> 安排");
}
return method.invoke(player, objects);
}
public static void main(String[] args) throws NoSuchMethodException {
ProxyCglib proxyFactory = new ProxyCglib(new PlayerNoInterface());
PlayerNoInterface playerNoInterface = proxyFactory.getProxyInstance();
playerNoInterface.interview();
playerNoInterface.playBall();
}
}
与动态代理的代码基本相同,只是实现了一个接口并且生成代理类的方式不同了而已。
在这里我们使用cglib的Enhancer类来进行代理类的生成。
我们指定父类为原类
指定回调函数为我们当前类,即指定下面编写的intercept为包装处理的方法。
我们通过执行main函数,最后也可以得到相同的结果。
proxy -> 安排
接受采访
开始打球
这就是全部的学习笔记了,如果有大神有更加完善的见解,请批评指正
简单的代码已经传到了git上,如有需要可以看一下
https://github.com/JinxLbj/design_proxy_project.git