开始
近几日看到一篇有关于代理模式的文章,然后回忆早先年轻时搞过的程序和东西,想起当时在刚开始学Spring的时候才刚刚明白代理是什么东西,这是感叹时间过的太快了,随即也进行整理一下吧。
什么是代理
代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式,即通过代理对象访问目标对象。这样做的好处是,可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能。这就符合了设计模式的开闭原则,即在对既有代码不改动的情况下进行功能的扩展。
举个例子吧:明星与经纪人之间就是被代理和代理的关系,明星出演活动的时候,明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决。这就是代理思想在现实中的一个例子。
代理模式是较常见的设计模式之一,在许多框架中经常见到,比如Spring的面向切面的编程,MyBatis中缓存机制对PooledConnection的管理等。代理模式使得客户在使用目标对象的时候间接通过操作代理对象进行,代理对象是对目标对象的增强,看一下下面的示意图:
- 客户访问关心的功能,但是并不在意谁提供了服务,上图的Subject。
- 而服务真正实现的是上图的RealSubject,但是它不会和客户直接接触,而且是通过代理。
- 代理就是上图的Proxy,由于它实现了Subject,所以它能直接和客户对接。
- 当客户和Proxy对接的时候,Proxy内部调用了RealSubject。
Java中的代理分为静态代理和动态代理两种形式,我们来讨论下吧。
静态代理
说静态代理前我先给大家看个例子,音像发行公司委托音像店进行售卖某歌星CD专辑,但是音像店在售卖该歌星CD专辑的时候,肯定要有自己其他的售卖收益,比如提供其他歌星的CD专辑发行广告,加多少钱就可赠送歌星的演唱会门票,下面我们用代码开演示一下:
创建一个音像Video接口:
package com.wlee.test;
public interface Video {
public void sell();
}
接下来我们创建一个真正的歌星Xxx的Video接口实现类:
package com.wlee.test;
public class SingerXxxVideo implements Video {
@Override
public void sell() {
System.out.println("音像店正在售卖Xxx歌星CD专辑");
}
}
音像店代理类:
package com.wlee.test;
public class SingerXxxVideoProxy implements Video {
private Video video;
public SingerXxxVideoProxy(Video video) {
this.video = video;
}
@Override
public void sell() {
beforeSell();
video.sell();
afterSell();
}
private void beforeSell() {
System.out.println("售卖前派发其他歌星专辑发行广告");
}
private void afterSell() {
System.out.println("加100元获得该歌星的演唱会门票");
}
}
测试:
package com.wlee.test;
public class Test {
public static void main(String[] args) {
Video singerVideo = new SingerXxxVideo();
Video videoProxy = new SingerXxxVideoProxy(singerVideo);
videoProxy.sell();
}
}
运行结果:
售卖前派发其他歌星专辑发行广告
音像店正在售卖XX歌星CD专辑
加100元获得该歌星的演唱会门票
从以上案例可以看出,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。这个就是是静态代理的内容,那为什么叫做静态?因为它的类型是事先设定好的,比如上面代码中的 VideoProxy 这个类。
静态代理的优缺点:很明显可以做到在不修改目标对象的功能前提下,对目标功能扩展;那缺点也很明显,因为代理的对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要一起增加维护。没懂?上面售卖Xxx歌星的,所以有一个和它对应的接口实现类 SingerXxxVideo 和代理类 SingerXxxVideoProxy ,那再增加一个Yyy歌星呢?是不是也要相应增加对应的接口实现类接口实现类。
动态代理
与静态代理对应的是动态代理类,动态代理是由Java反射机制动态生成,动态代理类不仅简化了编程工作,因为Java 反射机制可以生成任意类型的动态代理类。
动态代理 - JDK代理
JDK动态代理是通过 java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口提供了生成动态代理的。那上面刚才的那个案例,这时候增加Yyy歌星的CD专辑售卖,请看如下代码:
Video接口没有变:
package com.wlee.test;
public interface Video {
public void sell();
}
歌星Yyy的Video接口实现类:
package com.wlee.test;
public class SingerYyyVideo implements Video {
@Override
public void sell() {
System.out.println("音像店正在售卖Yyy歌星CD专辑");
}
}
这个时候,如果按照静态代理,就得需要创建一个对应的代理类 SingerYyyVideoProxy 才行,这里不做演示了,直接进行动态代理:
package com.wlee.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxy implements InvocationHandler {
private Object targetObj; //这里定义了一个Object对象
public JDKProxy(Object targetObj) {
this.targetObj = targetObj;
}
public Object createProxy() {//生成代理类
return Proxy.newProxyInstance(
targetObj.getClass().getClassLoader(),
targetObj.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeSell();
Object invoke = method.invoke(targetObj, args);
afterSell();
return invoke;
}
private void beforeSell() {
System.out.println("售卖前派发其他歌星专辑发行广告");
}
private void afterSell() {
System.out.println("加100元获得该歌星的演唱会门票");
}
}
测试:
package com.wlee.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//生成歌星Yyy的代理
SingerYyyVideo singerYyy = new SingerYyyVideo();
JDKProxy proxy = new JDKProxy(singerYyy);
Video dynamicProxy = (Video) proxy.createProxy();
dynamicProxy.sell();
}
}
运行结果:
售卖前派发其他歌星专辑发行广告
音像店正在售卖Yyy歌星CD专辑
加100元获得该歌星的演唱会门票
看到这里,你会发现我们并没有像静态代理那样为 SingerYyyVideo 再生成一个代理类 SingerYyyVideoProxy ,我们利用了JDK的动态代理完成的。我们顺便把歌星Xxx一起代理了吧:
package com.wlee.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//生成歌星Yyy的代理
SingerYyyVideo singerYyy = new SingerYyyVideo();
JDKProxy proxy = new JDKProxy(singerYyy);
Video dynamicProxy = (Video) proxy.createProxy();
dynamicProxy.sell();
//我们直接把之前的歌星Xxx一起代理了吧
SingerXxxVideo singerXxx = new SingerXxxVideo();
JDKProxy proxy2 = new JDKProxy(singerXxx);
Video dynamicProxy2 = (Video) proxy2.createProxy();
dynamicProxy2.sell();
}
}
很明显,两个歌星都使用了同一个代理类 JDKInvocationHandler ,都是通过 Proxy.newProxyInstance() 方法,产生了 SingerYyyVideo 和 SingerXxxVideo 两种接口的实现类代理,这就是动态代理的。如果你有兴趣可以去看一看源码中它们是怎么运转实现的。
动态代理 - CGLib代理
上面说JDK的动态代理,它是只能基于接口的,那么如果是类要动态代理怎么办呢?我们得说一下另外一个动态代理——CGLib代理。
使用CGLib第一个事需要进行导入CGLib的jar包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>5.0.3</version>
</dependency>
继续:
package com.wlee.test;
public class SingerZzzVideo {
public void sell() {
System.out.println("音像店正在售卖Zzz歌星CD专辑");
}
}
我这里就是一个普通的类,没有实现任何接口,继续使用GCLib生成代理:
package com.wlee.test;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibProxy implements MethodInterceptor {
private Object targetObj; //这里定义了一个Object对象
public CGLibProxy(Object targetObj) {
this.targetObj = targetObj;
}
public Object createProxy() {//生成代理类
Enhancer en = new Enhancer(); //1.Enhancer工具
en.setSuperclass(targetObj.getClass()); //2.设置父类
en.setCallback(this); //3.设置回调函数
return en.create(); //4.创建代理对象
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
beforeSell();
Object invoke = methodProxy.invokeSuper(o, objects);
afterSell();
return invoke;
}
private void beforeSell() {
System.out.println("售卖前派发其他歌星专辑发行广告");
}
private void afterSell() {
System.out.println("加100元获得该歌星的演唱会门票");
}
}
上面代码在生成代理的时候只设置了一个类,并不需要接口。如果你有兴趣可以去看看源码是怎么实现的,CGLib代理类实现了什么??是实现了一个 MethodInterceptor ,Interceptor 这个单词不陌生吧,“拦截器”,其实源码中就是通过最后调用 CGLibProxy 中的 intercept() 方法,从而完成了由代理对象访问到目标对象的动态代理实现。
- CGLib 可以在运行期扩展 Java 类与实现 Java 接口。
- 用 CGLib 生成代理类是目标类的子类。
- 用 CGLib 生成代理类不需要接口。
- 用 CGLib 生成的代理类重写了父类的各个方法。
- 拦截器中的 intercept 方法内容正好就是代理类中的方法体。
总结
- 代理分为静态代理和动态代理两种。
- 静态代理,代理类需要自己编写代码写成,目标对象要实现接口,而且代理对象需要实现接口。
- 动态代理有JDK动态代理和CGLib动态代理。
- 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
- JDK动态代理不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
- CGLib动态代理对象不需要实现接口,目标对象也可以是单纯的一个对象。
最后,忽然想起,大部分人应该是在学习 SpringAOP 的时候接触到代理。