posts - 23,comments - 66,trackbacks - 0
by lostfire
如果我们把一个JSP文件发布到Tomcat里边,JSP是可以动态改变得,也就是说随着这个JSP文件的改变,通过浏览器访问,可以获得不同的结果。然而有没有想过能够让java代码像jsp那样动态的改变?
实际上通过java的反射机制和内建的代理模式,完全可以做到这一点。下面就一步一步的说一下这是如何实现的。

1,准备知识
  • 代理模式
代理模式是一种对象的结构模式,简单的说就是给某个对象提供一个代理对象,并通过代理对象来访问真正的对象。
按照《java与模式》里边说说的,proxy其实有很多种用法:Remote proxy,Vitual Proxy,Copy-on-Write Proxy,Cache Proxy,Firewall proxy,Synchronization Proxy,Smart Reference Proxy等等。总体来说,都起到一个网络中代理服务器的作用,只不过在代理中添加一些代码实现不同的功能。
代理模式的要点就是不直接访问要访问的对象,而是通过代理对象,因此就可以在调用实际对象前或调用后利用消息传递做一些额外的工作。
  • 反射机制与动态编译
java的反射机制是java被视为动态语言的重要特性,主要是通过java.lang.reflect包中提供的工具对于给定名称的class,filed,method进行访问。这种动态性给程序提供了很多的灵活性,本文要介绍的功能就得益于java的这一机制。
动态编译算是java反射的加强补充,在j2se 1.6以前的版本里边是通过tools.jar中的com.sun.tools.javac包来提供的,在当前已经发布的j2se1.6 beta2中已经将动态编译作为j2se的一部分了。
要让java代码能够像jsp那样动态的使用,需要探测到java文件的改变,并动态编译成class文件,再用ClassLoader将编译好的class载入,因此这一部分是不可或缺的。
  • Java的内建代理
j2se在1.3以后提供了Proxy、InvocationHandler来支持代理模式。
对于java内建的代理来说,就是client不直接访问对象,而是通过Proxy对象来访问,而在访问实际对象前后我们可以自己定义一些其他的操作。
具体来讲,client访问的对象通过Proxy.newProxyInstance ()给出,client要访问的实际的类可以通过Proxy.getProxyClass获得,而实际的调用将访问到我们实现的 InvocationHandler对象的invoke()方法中来。在invoke()方法中我们可以为实际的调用添加额外的代码以满足不同的需要。
在我后边讲到的具体实现中就可以看到,我们正是在实现InvocationHandler的MyInvocationHandler的invoke()方法中来判断java文件的改变,对于改变动态的编译和装载和调用来达到我们预期的目标的,java内建的代理模式可谓居功至尾。

2,基本服务

为了演示像jsp一样的java的效果,让我们来定义的一种服务。

public interface Postman {
    void deliverMessage(String msg);
}
具体的实现中,候选的有两种方案:
  • 第一种是将输出字符串到控制台:
public class PostmanImpl implements Postman {

    private PrintStream output;
    public PostmanImpl() {
        output = System.out;
    }
   
    public void deliverMessage(String msg) {
        output.println("[Postman] " + msg);
        output.flush();
    }
}
  • 第二种是输出字符串到文本:
public class PostmanImpl implements Postman {

    private PrintStream output;
   
    public PostmanImpl() throws IOException {
       output = new PrintStream(new FileOutputStream("msg.txt"));
    }

    public void deliverMessage(String msg) {
        output.println("[Postman] " + msg);
        output.flush();
    }
}
在程序运行中,我们就是要通过动态修改PostmanImpl 来观察这个JSP一样的现象。

3,访问服务

这里要介绍一些访问服务的main程序,以便看到这种方式的优越性。

public class PostmanApp {

    public static void main(String[] args) throws Exception {
        BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in));
        Postman postman = getPostman();
        while (true) {
            System.out.print("Enter a message: ");
            String msg = sysin.readLine();
            postman.deliverMessage(msg);
        }
    }

    private static Postman getPostman() {
        DynaCode dynacode = new DynaCode();
        dynacode.addSourceDir(new File("dynacode"));
        return (Postman) dynacode.newProxyInstance(Postman.class,
                "sample.PostmanImpl");
    }
}
我们可以看到获取PostMan对象,只是在初始化的过程中做过一次,后边只是访问其deliverMessage()方法,而 sample.PostmanImpl这一实现的动态改变完全被掩藏在这个小小框架的后边。Java文件改变后的编译、重新载入、对象实例化和方法的调用过程完全不可见,是不是很神奇的实现。

4,DynaCode

看了这样的主程序,你可能会首先看一下DynaCode的实现,然而我不准备详细讲述DynaCode的实现,尽管它的实现最为复杂。因为 DynaCode只是简单的包装了java.lang.reflect.Proxy,通过添加几个处理class路径和java路径的方法辅助来完成工作的,我不想重点介绍java反射机制的,因此我们只来看DynaCode一个重要的方法。

public Object newProxyInstance(Class interfaceClass, String implClassName)
            throws RuntimeException {
        MyInvocationHandler handler = new MyInvocationHandler(
                implClassName);
        return Proxy.newProxyInstance(interfaceClass.getClassLoader(),
                new Class[] { interfaceClass }, handler);
}
好了,看到了吧,DynaCode只是在内部做了一个InvocationHandler实现,并简单的使用了Proxy的newProxyInstance()方法。

5,InvocationHandler实现

根据上面提到的java的内建代理模式,要实现一个InvocationHandler,下面这段代码展示了MyInvocationHandler 的主要部分:

private class MyInvocationHandler implements InvocationHandler {
        String backendClassName;//实际的类名
        Object backend;//实际的类对象
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /**
              *  在第一载入的时候通过loadClass来载入所需要的服务实现对象,后边每次检查java文件是否被修改过,
              *  如果被修改就unload当前的对象,编译并重新载入该类。
              */
            Class clz = loadClass(backendClassName);
            if (backend.getClass() != clz) {
                backend = newDynaCodeInstance(clz);
            }

            // 调用有效的对象的方法
            return method.invoke(backend, args);
        }
    }
在MyInvocationHandler中保存了实现接口的类名和该类的一个对象,该对象也就是通过newDynaCodeInstance()方法得到的对象。在每一次调用该对象的方法的时候,java的Proxy机制保证了系统会自动调用MyInvocationHandler的invoke方法。
这里采用反射进行动态载入的程序,调用的实际工作是在InvocationHandler的invoke方法中做的,因此InvocationHandler要保存实际的对象。
代理模式的好处是从使用者看来如同调用实际的对象是一样的,而实际上可以通过代理对象,程序可以动态采用不同的接口实现来完成工作,这一过程只需要在Proxy.newProxyInstance()中给定不同的实现类即可。

本文主要参照Add dynamic Java code to your application (by LiYang),完整的代码可以从这里下载
posted on 2006-06-28 23:28 rd2pm 阅读(3162) 评论(0)  编辑  收藏 所属分类: java language

只有注册用户登录后才能发表评论。


网站导航: