写这篇博客是为了解决《[Dota兄订餐——静态代理(java)](http://blog.csdn.net/xiaoxian8023/article/details/9261851)》 里最终的遗留问题。
想必大家都或多或少了解一些Dos命令,在调用外部程序时,经常会用到dos命令来完成。其中有一条万能的命令,就是用Explorer.exe来打开任意程序,就相当于直接双击该程序。
先给大家看一下我们要调用的外部资源,放到一个文件夹中,包括图片、音乐、视频、文本文档、word文档、还有其他文档,最后还会加上一个网址。
![](https://box.kancloud.cn/2016-02-18_56c53c4864487.jpg)
我把Dos命令写入到一个bat中,也放入同一个文件夹中,命令如下:
~~~
@echo off
rem 调用默认程序打开图片
explorer 1.jpg
rem 调用默认程序打开pdf
explorer 42种方法全面提升宝宝的智能.pdf
rem 调用默认程序打开快捷方式
explorer SSH视频.lnk
rem 调用默认程序打开文本文档
explorer X光下看腾讯.txt
rem 调用默认程序打开chm
explorer 电脑故障维修大全.chm
rem 调用默认程序打开mp4
explorer 黄梅戏女驸马谁料皇榜中状元选段.mp4
rem 调用默认程序打开flv
explorer 马云创业演讲.flv
rem 调用默认程序打开音乐
explorer 我会很诚实.mp3
rem 调用默认程序打开Word文档
explorer 验收标准V3.1.docx
rem 调用默认浏览器打开百度
explorer http://www.baidu.com
~~~
现在我们双击这个bat文件,系统会自动调用相对应的默认程序去打开相应的文件。
![](https://box.kancloud.cn/2016-02-18_56c53c487cd88.jpg)
![](https://box.kancloud.cn/2016-02-18_56c53c4893e8f.jpg)
在上面的例子中,每种格式的文件都对应一种默认的打开程序。而且这个默认程序是可以手动更换的。而Explorer就相当于这里面的动态代理。我们把具体的参数传递给代理Explorer,它会根据不同的文档格式,启动对应的默认程序,然后再执行相应的操作。当默认程序换了,就会启动新设定的默认程序。而且不仅仅局限于一类文件或程序,而是可以打开任意程序或文档。这就是动态代理模式的应用。
静态代理是一个代理类服务一个接口,且代理是针对于这个接口编写特定代码,其字节码是编译期生成。而动态代理的字节码(即编译后的class文件)在程序运行时由java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。Java.lang.reflect包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。
先说说Proxy类,它就是一个动态代理类,最终返回Proxy这个动态代理类所代理的接口类对象,使用的就是Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 这个方法。它有三个参数:
- ClassLoader loader----指定被代理对象的类加载器
- Class[] Interfaces----指定被代理对象所实现的接口
- InvocationHandler h----指定需要调用的InvocationHandler对象
实现InVocationHandler接口,必须实现invoke()方法,该方法就是Proxy这个动态代理类所代理的接口类的抽象方法的真实实现。它有三个参数:
- Object proxy-----代理类对象
- Method method-----被代理对象的方法(这里不是接口的抽象方法了,是具体的实现类中的方法)
- Object[] args-----该方法的参数数组
JDK中具体的动态代理类是怎么产生的呢?
1. 产生代理类$Proxy0类
执行了Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
将产生$Proxy0类,它继承Proxy对象,并根据第二个参数,实现了被代理类的所有接口,自然就可以生成接口要实现的所有方法了(这时候会重写hashcode,toString和equals三个方法),但是还没有具体的实现体;
2. 将代理类$Proxy0类加载到JVM中
这时候是根据Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第一个参数----就是被代理类的类加载器,把当前的代理类加载到JVM中;
3. 创建代理类$Proxy0类的对象
调用的$Proxy0类的$Proxy0(InvocationHandler)构造函数,生成$Proxy0类的对象。参数就是Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第三个参数。这个参数就是我们自己实现的InvocationHandler对象,我们知道InvocationHandler对象中组合加入了代理类所代理的接口类的实现类;所以,$Proxy0对象调用所有要实现的接口的方法,都会调用InvocationHandler对象的invoke()方法实现。
简单的说了一下JDK动态代理的实现原理。下面我们用代码把上面的例子实现一下:
代码结构图:
![](https://box.kancloud.cn/2016-02-18_56c53c48c00e1.jpg)
其中【com.bjpowernode.pattern.explorer.doc包】
OfficeSoftWare.java
~~~
package com.bjpowernode.pattern.explorer.doc;
/**
* Office软件接口
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-8 下午08:09:55
*/
public interface OfficeSoftWare {
/**
* 打开文件
* @param filePath 文件路径
*/
public void openFile(String filePath);
/**
* 保存文件
* @param filePath 文件路径
*/
public void saveFile(String filePath);
//其他功能...
}
~~~
WinWord.java
~~~
package com.bjpowernode.pattern.explorer.doc;
/**
* WinWord软件
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-8 下午09:01:54
*/
public class WinWord implements OfficeSoftWare {
/**
* 打开文件
* @param filePath 文件路径
*/
@Override
public void openFile(String filePath) {
System.out.println("打开文档 "+filePath);
}
/**
* 保存文件
* @param filePath 文件路径
*/
@Override
public void saveFile(String filePath) {
System.out.println("保存文档 "+filePath);
}
/**
* 返回名称
* @return
*/
@Override
public String toString() {
return "WinWord软件";
}
}
~~~
【com.bjpowernode.pattern.explorer.image】包:
ImageSoftWare.java
~~~
package com.bjpowernode.pattern.explorer.image;
/**
* 图片显示软件接口
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-8 下午08:08:13
*/
public interface ImageSoftWare {
/**
* 显示图片
* @param imgPath 图片路径
*/
public void viewImg(String imgPath);
/**
* 删除图片
* @param imgPath 图片路径
*/
public void delImg(String imgPath);
//其他功能...
}
~~~
ImageView.java
~~~
package com.bjpowernode.pattern.explorer.image;
/**
* Windows 图片查看器
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-8 下午09:03:23
*/
public class ImageView implements ImageSoftWare {
/**
* 返回软件名称
* @return
*/
@Override
public String toString() {
return "Windows图片查看器";
}
/**
* 删除图片
* @param imgPath 图片路径
*/
@Override
public void delImg(String imgPath) {
System.out.println("删除图片 "+imgPath);
}
/**
* 显示图片
* @param imgPath 图片路径
*/
@Override
public void viewImg(String imgPath) {
System.out.println("显示图片 "+imgPath);
}
}
~~~
【com.bjpowernode.pattern.explorer.music】包:
MusicSoftWare.java
~~~
package com.bjpowernode.pattern.explorer.music;
/**
* 音乐软件接口
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-8 下午08:16:43
*/
public interface MusicSoftWare {
/**
* 加载歌曲
* @param songPath 歌曲路径
*/
public void loadSong(String songPath);
/**
* 播放歌曲
* @param songPath
*/
public void playSong(String songPath);
//其他功能...
}
~~~
QQMusicPlayer.java
~~~
package com.bjpowernode.pattern.explorer.music;
/**
* QQ音乐播放器
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-8 下午09:00:25
*/
public class QQMusicPlayer implements MusicSoftWare {
/**
* 加载歌曲
* @param songPath 歌曲路径
*/
@Override
public void loadSong(String songPath) {
System.out.println("加载歌曲 "+songPath);
}
/**
* 播放歌曲
* @param songPath
*/
@Override
public void playSong(String songPath) {
System.out.println("播放歌曲 "+songPath);
}
/**
* 返回名称
* @return
*/
@Override
public String toString() {
return "QQ音乐播放器";
}
}
~~~
TTPlayer.java
~~~
package com.bjpowernode.pattern.explorer.music;
/**
* 千千静听播放器
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-8 下午09:03:21
*/
public class TTPlayer implements MusicSoftWare {
/**
* 加载歌曲
* @param songPath 歌曲路径
*/
@Override
public void loadSong(String songPath) {
System.out.println("加载歌曲 "+songPath);
}
/**
* 播放歌曲
* @param songPath
*/
@Override
public void playSong(String songPath) {
System.out.println("播放歌曲 "+songPath);
}
/**
* 返回名称
* @return
*/
@Override
public String toString() {
return "千千静听播放器";
}
}
~~~
【com.bjpowernode.pattern.explorer】包:
ExplorerHandler.java
~~~
package com.bjpowernode.pattern.explorer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* jdk动态代理代理类
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-9 下午02:46:03
*/
public class ExplorerHandler implements InvocationHandler{
/**
* 目标对象
*/
private Object tagObj = null;
/**
* 通过反射,获取目标对象的代理对象
* @param tagObj 目标对象
* @return
*/
public Object newProxyInstance(Object tagObj){
this.tagObj = tagObj;
return Proxy.newProxyInstance(tagObj.getClass().getClassLoader(), tagObj.getClass().getInterfaces(), this);
}
/**
* 通过代理对象,调用方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("\n---->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//获取文档类型
String fileType = args[0].toString().substring(args[0].toString().lastIndexOf(".")+1);
System.out.println("您正在操作的文档类型为:【" + fileType + "】,需要用【" + tagObj.toString() + "】打开");
System.out.print("执行操作--->>>");
//调用方法,并获取执行结果
Object res = method.invoke(tagObj, args);
//返回结果返回值
return res;
}
}
~~~
Client.java
~~~
package com.bjpowernode.pattern.explorer;
import com.bjpowernode.pattern.explorer.doc.OfficeSoftWare;
import com.bjpowernode.pattern.explorer.doc.WinWord;
import com.bjpowernode.pattern.explorer.image.ImageSoftWare;
import com.bjpowernode.pattern.explorer.image.ImageView;
import com.bjpowernode.pattern.explorer.music.MusicSoftWare;
import com.bjpowernode.pattern.explorer.music.QQMusicPlayer;
import com.bjpowernode.pattern.explorer.music.TTPlayer;
/**
* 客户端测试动态代理
*
* @author : Longxuan
* @group : tgb8
* @Version : 1.00
* @Date : 2013-7-9 下午02:45:06
*/
public class Client {
public static void main(String[] args) {
//实例化一个jdk动态代理代理对象
ExplorerHandler explorer = new ExplorerHandler();
//实例化一个window图片查看器的代理对象
ImageSoftWare isw = (ImageSoftWare) explorer.newProxyInstance(new ImageView());
//通过代理对象,执行显示图片
isw.viewImg("1.jpg");
//实例化一个Word的代理对象
OfficeSoftWare osw = (OfficeSoftWare) explorer.newProxyInstance(new WinWord());
//通过代理,执行显示图片
osw.openFile("验收标准V3.1.docx");
//实例化一个QQ音乐播放器的代理对象
MusicSoftWare msw = (MusicSoftWare) explorer.newProxyInstance(new QQMusicPlayer());
//通过代理对象,执行加载歌曲
msw.loadSong("我会很诚实.mp3");
System.out.println("\n---->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println("切换音乐默认程序为【 千千静听】");
//实例化一个千千静听的代理对象
msw = (MusicSoftWare) explorer.newProxyInstance(new TTPlayer());
//通过代理对象,执行播放歌曲
msw.playSong("我会很诚实.mp3");
}
}
~~~
运行结果:
![](https://box.kancloud.cn/2016-02-18_56c53c48d4cad.jpg)
有了动态代理,在面对100个不同接口时,不用再像静态代理中编写100个特定的代理类。正如上面刚刚说过的,在动态代理模式中,只有一个动态代理类,每一个具体的代理对象都是在运行期生成的。这样不光节省了编码时间,最重要的是减少了维护的工作量和难度。比如就像例子中的,我需要在执行代码前,统一有一个操作(检验操作的文件类型),只要在代理类的invoke方法中编写一遍就可以了,而在静态代理中,需要给每个代理类都需要编写这段代码。在后期的更改中,也需要对每个代理类修改。现在换成动态代理,只需改一处即可。时间少,效率高,真是程序员必备之“良药”呀!
简单的介绍了一下动态代理,如果想有更为详细的了解,请移步《[Java 动态代理机制分析及扩展,第 1 部分](http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/)》 和《[Java 动态代理机制分析及扩展,第 2 部分](http://www.ibm.com/developerworks/cn/java/j-lo-proxy2/)》