实际上通过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,基本服务在我后边讲到的具体实现中就可以看到,我们正是在实现InvocationHandler的MyInvocationHandler的invoke()方法中来判断java文件的改变,对于改变动态的编译和装载和调用来达到我们预期的目标的,java内建的代理模式可谓居功至尾。
为了演示像jsp一样的java的效果,让我们来定义的一种服务。
public interface Postman {
void deliverMessage(String msg);
}
具体的实现中,候选的有两种方案:
- 第一种是将输出字符串到控制台:
private PrintStream output;
public PostmanImpl() {
output = System.out;
}
public void deliverMessage(String msg) {
output.println("[Postman] " + msg);
output.flush();
}
}
- 第二种是输出字符串到文本:
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),完整的代码可以从这里下载。
相关推荐
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM...
主要讲述Java反射机制与设计模式之一:代理模式的原理与应用同时详细讲述了Java对代理模式的支持以及Java中动态代理的原理,应用与实践 本课程要求大家对Java泛型知识有所了解,因为程序代码中大量使用了泛型相关...
Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...
具有众多特点是跨平台的优点,采用java编写应用程序界面和web界面越来越流 行。而swing是目前Java中的一个重要的界面工具组类库,是建立GUI的强大工 具,它比起Java的其他界面组件来说具有更多的优点,相信以后用...
同时详细讲述了Java对代理模式的支持以及Java中动态代理的原理,应用与实践 本课程要求大家对Java泛型知识有所了解,因为程序代码中大量使用了泛型相关知识,对于不熟悉该部分内容的读者,我会在下次课程中对JDK5.0...
1.1.2web应用程序 2 1.2使用java开发web应用 3 1.2.1面向对象的编程语言 3 1.2.2丰富的框架技术 4 1.2.3xml、css的应用 4 1.2.4使用javascript与ajax提升用户体验 7 1.3多种集成开发环境 9 1.3.1集成开发环境...
java.awt.datatransfer 提供在应用程序之间和在应用程序内部传输数据的接口和类。 java.awt.dnd Drag 和 Drop 是一种直接操作动作,在许多图形用户界面系统中都会遇到它,它提供了一种机制,能够在两个与 GUI 中...
实例1 开发第一个Java程序 7 第2章 Java基础类型与运算符(教学视频:39分钟) 9 2.1 基础类型 9 实例2 自动提升 9 实例3 自动转换 10 实例4 常用基础类型之强制转换 11 2.2 运算符 12 ...
在本系统中,代理商类、可口可乐公司类和百事可乐公司类都使用到了代理模式,而且代理商类使用了懒汉式来实现单例模式,可口可乐公司类使用了饿汉式实现单例模式,而百事可乐类使用了双重检查锁来实现单例模式。...
Proxy是比较有用途的一种模式,而且变种较多,应用场合覆盖从小结构到整个系统的大结构,Proxy是代理的意思,我们也许有代理服务器等概念,代理概念可以解释为:在出发点到目的地之间有一道中间层,意为代理. 设计模式中...
(1)单例模式(2)观察者模式(3)代理模式 2.集合应用 设计一个案例,用集合解决实际问题 3.访问数据库 设计一个案例,连接数据库,进行添加、删除、修改、查询 4.综合应用 设计一个用图形界面的数据库应用程序(小型管理...
java.awt.datatransfer 提供在应用程序之间和在应用程序内部传输数据的接口和类。 java.awt.dnd Drag 和 Drop 是一种直接操作动作,在许多图形用户界面系统中都会遇到它,它提供了一种机制,能够在两个与 GUI 中显示...
实例1 开发第一个Java程序 7 第2章 Java基础类型与运算符(教学视频:39分钟) 9 2.1 基础类型 9 实例2 自动提升 9 实例3 自动转换 10 实例4 常用基础类型之强制转换 11 2.2 运算符 12 实例5...
java.awt.datatransfer 提供在应用程序之间和在应用程序内部传输数据的接口和类。 java.awt.dnd Drag 和 Drop 是一种直接操作动作,在许多图形用户界面系统中都会遇到它,它提供了一种机制,能够在两个与 GUI 中显示...
3. 反射和字节码操作:学习使用Java的反射机制和字节码操作库,实现动态代理、代码生成和运行时修改类的功能。 4. 函数式编程:学习Java 8及更高版本引入的函数式编程特性,如Lambda表达式和Stream API,提升代码...
java.awt.datatransfer 提供在应用程序之间和在应用程序内部传输数据的接口和类。 java.awt.dnd Drag 和 Drop 是一种直接操作动作,在许多图形用户界面系统中都会遇到它,它提供了一种机制,能够在两个与 GUI 中...
Java范例开发大全(全书源程序),目录如下: 第1篇 Java编程基础 第1章 Java开发环境的搭建(教学视频:9分钟) 2 1.1 理解Java 2 1.2 搭建Java所需环境 3 1.2.1 下载JDK 3 1.2.2 安装JDK 4 1.2.3 配置环境...