什么是代理模式
“代理”,为一个对象提供一个替身或者占位符以访问这个对象。关系示意图如下:
当我希望调用被代理者的某个方法时,需要经过代理这个中间环节,它会决定被代理者的哪些方法可以被调用,哪些方法,不能被调用,或者为被代理者加上一些别的行为。
一、远程调用(RMI)
当需要引用一个对象,而这个对象处于另一个虚拟机上,这时就需要使用远程调用,远程调用的思想就是通过RMI通信获取远程对象的信息,在本地虚拟机上创建一个远程对象的代理对象,代替它实现一些方法,反馈一些信息。RMI和Socket原理类似,只是基于不同的通信协议,实现不同的功能,RMI主要用于远程对象的方法调用,使用简单,不需要传输很多信息,只求调用的方便,而Socket通道更多应用于远程大量数据的可靠传输。
回归正题,继续聊RMI,被代理者(服务器)实现远程接口:Remote接口,并实现需要被远程调用的方法,然后注册名称在远程对象上:MyRemote service=new MyRemoteImpl();Naming.rebind(“RemoteHello”,service);
然后,客户端通过MyRemote service=(MyRemote)Naming.lookup(“rmi://127.0.0.1/RemoteHello”);然后就可以通过service对象调用远程对象的方法了。
底层原理及基于字节的数据包通信,以及对象的序列化与反序列化的过程。在这整个过程中需要两个代理,一个是在服务器端的客户端代理,一个是在客户端的服务器代理,这两个代理实现了远程对象的调用,掩盖了对象在远程的事实。
二、避免加载大对象的GUI挂起
原理:当需要加载一张比较大的图片时(从网上获取资源),主线程是阻塞的,这时就会导致GUI图形界面挂起,无法响应任何操作。这个问题的解决思路就是创建一个图片的代理,在加载图片的过程中先在屏幕上显示等待加载的提示符,把加载操作放在线程中执行,加载完之后,代理让出显示的权利,让图片显示在界面上。这部分我通过代码实现了一遍,如下:
图片代理类:
public class ImageProxy implements Icon{
ImageIcon imageIcon;
URL imageURL;
boolean retrieving=false;
/**
* 构造方法,传入一个地址
* @param url
*/
public ImageProxy(URL url){
imageURL=url;
}
/**
* 画图
*/
public void paintIcon(final Component c, Graphics g, int x, int y) {
if(imageIcon!=null){
imageIcon.paintIcon(c, g, x, y); //存在即直接画出
}else{ //不存在则先显示等待,开一个线程加载,加载后重绘
g.drawString("Loading............", x+300, y+190);
if(!retrieving){
retrieving=true;
new Thread(){
public void run(){
try{
imageIcon=new ImageIcon(imageURL,"CD Cover");
c.repaint();
}catch(Exception e){
e.printStackTrace();
}
}
}.start();
}
}
}
/**
* 获取宽
*/
public int getIconWidth() {
if(imageIcon!=null){
return imageIcon.getIconWidth();
}else{
return 800;
}
}
/**
* 获取高
*/
public int getIconHeight() {
if(imageIcon!=null){
return imageIcon.getIconHeight();
}else{
return 600;
}
}
}
组件类:
public class ImageComponent extends JComponent{
private Icon icon;
public ImageComponent(Icon icon) {
this.icon=icon;
}
public void setIcon(Icon icon){
this.icon=icon;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
int w=icon.getIconWidth();
int h=icon.getIconHeight();
int x=(800-w)/2;
int y=(600-h)/2;
icon.paintIcon(this, g, x, y);
}
}
测试类:
public class ImageProxyTestDrive {
ImageComponent imageComponent;
JFrame frame;
JMenuBar menuBar;
JMenu menu;
Hashtable cds=new Hashtable();
public static void main(String[] args)throws Exception{
ImageProxyTestDrive testDrive=new ImageProxyTestDrive();
}
public ImageProxyTestDrive()throws Exception{
cds.put("picture", "http://images.amazon.com/images/P/B000003S2K.01.LZZZZZZZ.jpg");
cds.put("picture1", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548705461289&di=c8bce9258b61574c9dcf0f7949659470&imgtype=0&src=http%3A%2F%2Fimg15.3lian.com%2F2015%2Ff2%2F57%2Fd%2F93.jpg");
cds.put("picture2", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548705461832&di=c16d6bd46460b06baee886395b318dec&imgtype=0&src=http%3A%2F%2Fpic33.photophoto.cn%2F20141101%2F0034034426605241_b.jpg");
cds.put("picture3", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548705461833&di=04e28a8c36ff98458218c43b95b15f11&imgtype=0&src=http%3A%2F%2Fpic32.photophoto.cn%2F20140915%2F0034034424700073_b.jpg");
cds.put("picture4", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548705461833&di=c0d4b36f00832fe7e25f059299417054&imgtype=0&src=http%3A%2F%2Fpic35.photophoto.cn%2F20150402%2F0011024033183316_b.jpg");
URL url=new URL((String)cds.get("picture1"));
frame=new JFrame("picture Viewer");
menuBar=new JMenuBar();
menu=new JMenu("Favourite picture");
menuBar.add(menu);
frame.setJMenuBar(menuBar);
Iterator<String> iterator=cds.keySet().iterator();
while(iterator.hasNext()){
JMenuItem jMenuItem=new JMenuItem(iterator.next());
menu.add(jMenuItem);
jMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
imageComponent.setIcon(new ImageProxy(getCDUrl(e.getActionCommand())));
frame.repaint();
}
});
}
Icon icon=new ImageProxy(url);//图标对象
imageComponent=new ImageComponent(icon);
frame.getContentPane().add(imageComponent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setSize(800, 600);
frame.setVisible(true);
}
/**
* 获取URL的方法
* @param name
* @return
*/
private URL getCDUrl(String name){
try{
return new URL((String)cds.get(name));
}catch(MalformedURLException e){
e.printStackTrace();
return null;
}
}
}
以上代码实现了图片显示代理,这个思想网页加载时的代理显示中也经常看见,把阻塞的方法放在后台执行,确保主线程的通畅,避免的GUI的挂起。
三、访问控制代理
概念:这个代理可以理解为明星的经纪人,想和明星做些什么事情首先需要通过经纪人这个环节,经纪人决定了我们对于明星的访问权。对象访问代理既可以为对象的方法在执行的时候加上一些新的内容,也可以选择性地决定对象的哪些方法是可以调用的,哪些方法是不可以调用的。这种代理思想一般分为静态代理和动态代理两种,“静态”表示在Java程序执行前就以及创建了代理对象,即对象的代理内容是确定了的,“动态”是指程序运行期间才创建代理对象,采用的是Java反射的思想。下面我们来介绍这两种代理。
静态代理:静态代理很容易理解,代理者持有被代理者的引用,二者实现相同的接口,外部只能直接调用代理者的方法,在方法中,代理者决定了是否调用被代理者的方法,或者在调用的同时是否添加一些新的内容。示意图就如同文章开篇给出的那张图。
动态代理:动态代理使用到了反射机制,在程序运行中创建代理对象。和静态代理不同的是它需要实现一个InvocationHandler接口,这个接口的对象是一个调用处理器,它只有一个方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
代理对象对任何方法的调用都实际上需要通过这个方法,接下来我们分析一下这个调用过程:
实现一个买房代理的例子:
写一个接口,那就是买房的接口,代理者和被代理者都是它的实例:
public interface SearchHouse {
public abstract void buyHouse();
}
被代理者实现这个接口:
public class Master implements SearchHouse{
public void buyHouse() {
System.out.println("买了一套房子");
}
}
实现一个调用处理器,先贴出代码,稍后再详细解释:
public class DymanicProxy implements InvocationHandler{
private SearchHouse searchHouse;
public DymanicProxy(SearchHouse searchHouse){
this.searchHouse=searchHouse;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("找房,看房");
Object result=method.invoke(searchHouse, args);//两个参数,一个是实际调用方法的对象,一个是参数
System.out.println("装修房子");
return result;
}
}
测试代码:
public static void main(String[] args){
try{
SearchHouse master=new Master();//买房的被代理对象
SearchHouse searchProxy=(SearchHouse)Proxy.newProxyInstance(SearchHouse.class.getClassLoader(),
new Class[]{SearchHouse.class}, new DymanicProxy(master));//通过类加载器,类接口,处理器对象得到代理对象
searchProxy.buyHouse();//代理对象来执行买房操作
}catch(Exception e){
e.getMessage();
}
}
输出了:
分析一个这个过程:先new一个买房者(被代理者)的实例,然后创建一个代理者的实例,之前我们提到过,代理者实在程序运行中才被创建的,所以它并没有一个单独的实现类,Proxy类是代理类,可以通过它的静态方法获取实例。代理者和被代理者是联系起来的,代理者需要持有被代理者的类加载器和类接口(这里可以得到类的全部信息),另外代理者还需要被赋予一些特殊的职责,那么它还需要持有调用处理器的实例。这样就可以得到一个代理的实例了。然后调用代理对象的方法:代理对象得到了要调用的方法,就会通过来加载机制获得方法对象,然后实际调用invoke()方法,把方法对象,参数等传入其中,接下来执行代理的特殊代码外,再执行method的invoke方法(注意这里的参数是被代理者实例和参数,处理器是持有被调用者实例的),就可以实现多态代理的效果了。
动态代理的优点:可以在程序运行时灵活调用不同的方法,可以实现多个调用处理器,调用代码和具体代理实现解耦,代码的可维护性和扩展性大大提高。
代理模式和适配器模式的区别:代理模式和适配器都在访问中隔了一个对象,这个对象对于方法的调用做了一些中间的处理,中间环节持有被代理/适配者的引用,二者有相似之处。不同的地方是,代理模式的代理者和被代理者实现了相同的接口,且目的是访问的控制;在适配器模式中,目的是转换接口,二者实现的是不同的接口。
一些别的代理
1、防火墙代理:控制网络资源的访问,保护主题免于“坏客户的侵害”。
2、智能引用代理:当主题被引用时,进行额外动作,比如记录访问次数。
3、缓存代理:为开销大的运算结果提供暂时的存储;也允许多个客户共享结果,以减少计算或者网络延迟。
4、同步代理:在多线程下为主体提供安全访问。
5、复杂隐藏代理:也称外观代理,进行访问控制,用来隐藏一个类的复杂集合的复杂度。
6、写入时复制代理:用来控制对象的复制,方法是延迟对象的复制,直到客户真正需要为止,这是虚拟代理的变体。