Java软件架构设计概论

yanzhiguo 发表于 2010-07-26 16:27 浏览次数:248 views 来源:

 

开始之初的架构设计决定着软件产品的生死存亡。“好的开始相当于成功一半”。

开始的架构设计也是最难的,需要调研同类产品的情况以及技术特征,了解当前世界上对这种产品所能提供的理论支持 和技术平台支持。再结合自己项目的特点(需要透彻的系统分析),才能逐步形成自己项目的架构蓝图。

比如要开发网站引擎系统,就从Yahoo的个人主页生成工具 到虚拟主机商提供的网站自动生成系统,以及IBM Webphere Portal的特点和局限 从而从架构设计角度定立自己产品的位置。

好的设计肯定需要经过反复修改,从简单到复杂的循环测试是保证设计正确的一个好办法

由于在开始选择了正确的方向,后来项目的实现过程也验证了这种选择,但在一些架构设计的细部方面,还需要对方案进行修改,属于那种螺旋上升的方式,显 然这是通过测试第一的思想和XP工程方法来实现的。

如果我们开始的架构设计在技术平台定位具有一定的世界先进水平,那么,项目开发实际有一半相当于做实验,是研发,存在相当的技术风险。

因此,一开始我们不可能将每个需求都实现,而是采取一种简单完成架构流程的办法,使用最简单的需求将整个架构都简单的完成一遍(加入人工干预),以检 验各个技术环节是否能髋浜瞎ぷ?非常优秀先进的两种技术有时无法在一起工作),同时也可以探知技术的深浅,掌握项目中的技术难易点。这个过程完成后,我 们就对设计方案做出上面的重大修改,丰富完善了设计方案。

设计模式是支撑架构的重要组件

架构设计也类似一种工作流,它是动态的,这点不象建筑设计那样,一开始就能完全确定,架构设计伴随着整个项目的进行过程之中,有两种具体操作保证架构 设计的正确完成,那就是设计模式(静态)和工程项目方法(RUP或XP 动态的)。

设计模式是支撑架构的一种重要组件,这与建筑有很相象的地方,一个建筑物建立设计需要建筑架构设计,在具体施工中,有很多建筑方面的规则和模式。

我们从J2EE蓝图模式分类http://java.sun.com/blueprints/patterns/catalog.html中 就可以很清楚的看到J2EE这样一个框架软件的架构与设计模式的关系。

架构设计是骨架,设计模式就是肉

这样,一个比较丰富的设计方案可以交由程序员进一步完成了,载辅助以适当的工程方法,这样就可保证项目的架构设计能正确快速的完成。

时刻牢记架构设计的目标

由于架构设计是在动态中完成的,因此在把握架构设计的目标上就很重要,因此在整个项目过程中,甚至每一步我们都必须牢记我们架构设计的总体目标,可以 概括下面几点:

1. 最大化的重用:这个重用包括组件重用 和设计模式使用等多个方面。

比如,我们项目中有用户注册和用户权限系统验证,这其实是个通用课题,每个项目只是有其内容和一些细微的差别,如果我们之前有这方面成功研发经验,可 以直接重用,如果没有,那么我们就要进行这个子项目的研发,在研发过程中,不能仅仅看到这个项目的需求,也要以架构的概念去完成这个可以称为组件的子项 目。

2. 尽可能的简单明了:我们解决问题的总方向是将复杂问题简单化,其实这也是中间件或多层体系技术的根本目标。但是在具体实施设计过程中,我们可能会将简单问 题复杂化,特别是设计模式的运用上很容易范这个错误,因此如何尽可能的做到设计的简单明了是不容易的。

我认为落实到每个类的具体实现上要真正能体现系统事物的本质特征,因为事物的本质特征只有一个,你的代码越接近它,表示你的设计就是简单明了,越简单 明了,你的系统就越可靠。更多情况是,一个类并不能反应事物本质,需要多个类的组合协调,那么能够正确使用合适的设计模式就称为重中之重。

我们看一个具备好的架构设计的系统代码时,基本看到的都是设计模式,宠物店(pet store)就是这样的例子。或者可以这样说,一个好的架构设计基本是由简单明了的多个设计模式完成的。

3. 最灵活的拓展性:架构设计要具备灵活性 拓展性,这样,用户可以在你的架构上进行二次开发或更加具体的开发。

要具备灵活的拓展性,就要站在理论的高度去进行架构设计,比如现在工作流概念逐步流行,因为我们具体很多实践项目中都有工作流的影子,工作流中有一个 树形结构权限设定的概念就对很多领域比较通用。

树形结构是组织信息的基本形式,我们现在看到的网站或者ERP前台都是以树形菜单来组织功能的,那么我们在进行架构设计时,就可以将树形结构和功能分 开设计,他们之间联系可以通过树形结构的节点link在一起,就象我们可以在圣诞树的树枝上挂各种小礼品一样,这些小礼品就是我们要实现的各种功能。

有了这个概念,通常比较难实现的用户级别权限控制也有了思路,将具体用户或组也是和树形结构的节点link在一起,这样就间接实现了用户对相应功能的 权限控制,有了这样的基本设计方案的架构无疑具备很灵活的拓展性。

探秘Java 7新增垃圾回收器G1特性

yanzhiguo 发表于 2010-07-26 15:12 浏览次数:118 views 来源:

 

G1垃圾回收器(简称G1 GC)是JDK 7中Java HotSpot VM新引入的垃圾回收器,Java SE 6 Update 14中已经包含了一个G1的体验版本(据51CTO之前的报导,在Java SE 6 u14于6月初登场时,原本Sun的声明是:G1垃圾回收器需要收费方能使用。然而之后不久,Sun表示这是一个误会,修改了原本的发布声明,并表示现在 以及将来对G1的使用都是完全免费的),G1是设计用于替代HotSpot低延迟的并行标记/清除垃圾 回收器(也叫做CMS)的。
Java 7 G1属性

G1是一个服务端垃圾回收器,有以下属性:

◆并行和并发性:G1利用了当今硬件中存在的并行性,当Java应用程序的线程被停止时,它使用所有可用的CPU(核 心,硬件线程等)加速其停止,在停止过程中运行Java线程最小化整个堆栈。

◆代:和其他HotSpot GC一样,G1是一代,意味着它在处理新分配的对象(年轻代)和已经生存了一段时间的对象(年老代)时会不同,它主要集中于新对象上的垃圾回收活动,因为 它们是最可能回收的,旧对象只是偶尔访问一下,对于大多数Java应用程序,代的垃圾回收对 于替代方案具有重要优势。

◆压缩:和CMS不同,G1会随时间推移对堆栈进行压缩,压缩消除了潜在的碎片问题,确保长时间运行的操作流畅和一致。

◆可预测性:G1比CMS预测性更佳,这都是由于消除了碎片问题带来的好处,再也没有CMS中停止期间出现的负面影响,另外,G1有一个暂停预测模 型,允许它满足(或很少超过)暂停时间目标。

Java 7 G1描述

和其它HotSpot GC相比,G1采用了一个非常不同的堆栈布局方法,在G1中,年轻代和年老代之间没有物理隔离,相反,它们之间有一个连续的堆栈,被分成大小一样的区域 (region),年轻代可能是一套非连续的区域,年老代也一样,这就允许G1在年轻代和年老代之间灵活地移动资源。

G1中的回收是通过消除暂停发生的,在此期间,幸存者指的是回收集被转移到另一个区域,以便回收区域可以再生,消除暂停是并行的,所有可用的CPU都 会参加,大多数消除暂停收集可用的年轻区域,和其它HotSpot GC中的年轻回收是一样的,在暂停期间偶尔也会选择年老区域回收,因为G1在年轻一代回收上还肩负了年老代的回收活动。

和CMS相同的是,G1会定期执行一个并发标记暂停,这个阶段的主要职责是识别哪一个年老区域的垃圾对象是最完整的,因为这些是最有效和最值得回收 的,和CMS不同的是,G1不会执行并发清除暂停,相反,最有用的年老区域是通过并发标记暂停标识的,在随后的消除暂停期间进行回收。

使用G1

G1仍然被看做是试验品,可以使用下面两个参数开启它:

-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC 为了设置一个GC暂停时间目标,使用下面的参数:

-XX:MaxGCPauseMillis =50  (暂停时间目标50ms) 使用G1时还可以指定时间间隔,当GC暂停持续时间没有上面给出的时间长时可以这么用:

-XX:GCPauseIntervalMillis =200  (暂停间隔目标200ms) 注意上面两个选项表示的目标,没有承诺和保证,在某些情况下它们可能能够工作,GC不是总是能够执行它们。

另外,年轻代的大小可以明确指定影响消除暂停时间:

-XX:+G1YoungGenSize=512m (年轻代大小512M) G1也使用幸存空间(可能是非连续的区域),它们的大小可以使用一个常见的参数指定,如:

-XX:SurvivorRatio=6 最后,为了运行G1充分发挥其潜力,尝试设置以下两个默认被禁用了的参数,因为它们可能会暴露一个罕见的竞争状态:

-XX:+G1ParallelRSetUpdatingEnabled   -XX:+G1ParallelRSetScanningEnabled  注意当设置了-XX:+PrintGCDetails后,G1比起其它 HotSpot GC要啰嗦得多,因为它会打印每个GC线程的计时和其它有助于进行故障排除的信息,如果你想使GC日志更简单,请使用-verbosegc参数。

Java 7 G1最新进展

G1开发现在主要集中在遗留的可靠性问题和改善性能,同时也 在逐步移除下面的限制:

◆G1不能完全支持JVM工具接口(JVM TI)或Java管理扩展(JMX),因此关于G1的监视和管理工具很可 能不能正常工作;

◆G1不支持增量永久性代回收,如果一个应用程序产生了许多类转储,需要永久性代回收,这在完整GC期间是可以实现的;

◆从GC暂停时间来说,G1有时表现比CMS好有时比CMS差。

原文:Java HotSpot Garbage Collection

动态调用动态语言之Java脚本API

yanzhiguo 发表于 2010-07-26 14:09 浏览次数:90 views 来源:

 

我们不需要将动态语言编译为 Java字节码就可以在 Java 应用程序中使用它们。使用 Java Platform, Standard Edition 6 (Java SE)中添加的脚本包(并且向后兼容 Java SE 5),Java 代码可以在运行时以一种简单的、统一的方式调用多种动态语言。本系列文章共分两个部分,第 1 部分将介绍 Java 脚本 API 的各种特性。文章将使用一个简单的 Hello World 应用程序展示 Java 代码如何执行脚本代码以及脚本如何反过来执行 Java 代码。第 2 部分将深入研究 Java 脚本 API 的强大功能。

Java 开发人员清楚 Java 并不是在任何情况下都是最佳的语言。今年,1.0 版本的 JRuby 和 Groovy 的发行引领了一场热潮,促使人们纷纷在自己的 Java 应用程序中添加动态语言。Groovy、JRuby、Rhino、Jython 和一些其他的开源项目使在所谓的脚本语言中编写代码并在 JVM 中运行成为了可能(请参阅 参考资料)。通常,在 Java 代码中集成这些语言需要对各种解释器所特有的 API 和特性有所了解。

Java SE 6 中添加的 javax.script 包使集成动态语言更加容易。通过使用一小组接口和具体类,这个包使我们能够简单地调用多种脚本语言。但是,Java 脚本 API 的功能不只是在应用程序中编写脚本;这个脚本包使我们能够在运行时读取和调用外部脚本,这意味着我们可以动态地修改这些脚本从而更改运行应用程序的行为。

Java 脚本 API

脚本与动态的对比

术语脚本通常表示在解释器 shell 中运行的语言,它们往往没有单独的编译步骤。术语动态通常表示等到运行时判断变量类型或对象行为的语言,往往具有闭包和连续特性。一些通用的编程语言同时 具有这两种特性。此处首选脚本语言是因为本文的着重点是 Java 脚本 API,而不是因为提及的语言缺少动态特性。

2006 年 10 月,Java 语言添加了脚本包,从而提供了一种统一的方式将脚本语言集成到 Java 应用程序中去。对于语言开发人员,他们可以使用这个包编写粘连代码(glue code),从而使人们能够在 Java 应用程序中调用他们的语言。对于 Java 开发人员,脚本包提供了一组类和接口,允许使用一个公共 API 调用多种语言编写的脚本。因此,脚本包类似于不同语言(比如说不同的数据库)中的 Java Database Connectivity (JDBC) 包,可以使用一致的接口集成到 Java 平台中去。

以前,在 Java 代码中,动态调用脚本语言涉及到使用各种语言发行版所提供的独特类或使用 Apache 的 Jakarta Bean Scripting Framework (BSF)。BSF 在一个 API 内部统一了一组脚本语言(请参阅 参考资料)。使用 Java SE 6 脚本 API,二十余种脚本语言(AppleScript、Groovy、JavaScript、Jelly、PHP、Python、Ruby 和 Velocity)都可以集成到 Java 代码中,这在很大程序上依赖的是 BSF。

脚本 API 在 Java 应用程序和外部脚本之间提供了双向可见性。Java 代码不仅可以调用外部脚本,而且还允许那些脚本访问选定的 Java 对象。比如说,外部 Ruby 脚本可以对 Java 对象调用方法,并访问对象的属性,从而使脚本能够将行为添加到运行中的应用程序中(如果在开发时无法预计应用程序的行为)。

调用外部脚本可用于运行时应用程序增强、配置、监控或一些其他的运行时操作,比如说在不停止应用 程序的情况下修改业务规则。脚本包可能的作用包括:

·在比 Java 语言更简单的语言中编写业务规则,而不用借助成熟的规则引擎。
·创建插件架构,使用户能够动态地定制应用程序。
·将已有脚本集成到 Java 应用程序中,比如说处理或转换文件文章的脚本。
·使用成熟的编程语言(而不是属性文件)从外部配置应用程序的运行时行为。
·在 Java 应用程序中添加一门特定于域的语言(domain-specific language)。
·在开发 Java 应用程序原型的过程中使用脚本语言。
·在脚本语言中编写应用程序测试代码。

你好,脚本世界

HelloScriptingWorld 类(本文中的相关代码均可从 下载部分 获得)演示了 Java 脚本包的一些关键特性。它使用硬编码的 JavaScript 作为示例脚本语言。此类的 main() 方法(如清单 1 所示)将创建一个 JavaScript 脚本引擎,然后分别调用五个方法(在下文的清单中有显示)用于突出显示脚本包的特性。

清单 1. HelloScriptingWorld main 方法

  1. public static void main(String[] args) throws ScriptException, NoSuchMethodException {
  2.  
  3. ScriptEngineManager scriptEngineMgr = new ScriptEngineManager();
  4. ScriptEngine jsEngine = scriptEngineMgr.getEngineByName("JavaScript");
  5.  
  6. if (jsEngine == null) {
  7. System.err.println("No script engine found for JavaScript");
  8. System.exit(1);
  9. }
  10.  
  11. System.out.println("Calling invokeHelloScript…");
  12. invokeHelloScript(jsEngine);
  13.  
  14. System.out.println("\nCalling defineScriptFunction…");
  15. defineScriptFunction(jsEngine);
  16.  
  17. System.out.println("\nCalling invokeScriptFunctionFromEngine…");
  18. invokeScriptFunctionFromEngine(jsEngine);
  19.  
  20. System.out.println("\nCalling invokeScriptFunctionFromJava…");
  21. invokeScriptFunctionFromJava(jsEngine);
  22.  
  23. System.out.println("\nCalling invokeJavaFromScriptFunction…");
  24. invokeJavaFromScriptFunction(jsEngine);
  25. }

复制代码

main() 方法的主要功能是获取一个 javax.script.ScriptEngine 实例(清单 1 中的前两行代码)。脚本引擎可以在特定的语言中加载并执行脚本。它是 Java 脚本包中使用最为频繁、作用最为重要的类。我们从 javax.script.ScriptEngineManager 获取一个脚本引擎(第一行代码)。通常,程序只需要获取一个脚本引擎实例,除非使用了很多种脚本语言。

ScriptEngineManager 类

ScriptEngineManager 可能是脚本包中惟一一个经常使用的具体类;其他大多数都是接口。它或许是脚本包中惟一的一个要直接或间接地(通过 Spring Framework 之类的依赖性注入机制)实例化的类。ScriptEngineManager 可以使用以下三种方式返回脚本引擎:

·通过引擎或语言的名称,比如说 清单 1 请求 JavaScript 引擎。
·通过该语言脚本共同使用的文件扩展名,比如说 Ruby 脚本的 .rb。
·通过脚本引擎声明的、知道如何处理的 MIME 类型。

本文示例为什么要使用 JavaScript?

本文中的 Hello World 示例使用了部分 JavaScript 脚本,这是因为 JavaScript 代码易于理解,不过主要还是因为 Sun Microsystems 和 BEA Systems 所提供的 Java 6 运行时环境附带有基于 Mozilla Rhino 开源 JavaScript 实现的 JavaScript 解释器。使用 JavaScript,我们无需在类路径中添加脚本语言 JAR 文件。

ScriptEngineManager 间接查找和创建脚本引擎。也就是说,当实例化脚本引擎管理程序时,ScriptEngineManager 会使用 Java 6 中新增的服务发现机制在类路径中查找所有注册的 javax.script.ScriptEngineFactory 实现。这些工厂类封装在 Java 脚本 API 实现中;也许您永远都不需要直接处理这些工厂类。

ScriptEngineManager 找到所有的脚本引擎工厂类之后,它会查询各个类并判断是否能够创建所请求类型的脚本引擎 —— 清单 1 中为 JavaScript 引擎。如果工厂说可以创建所需语言的脚本引擎,那么管理程序将要求工厂创建一个引擎并将其返回给调用者。如果没有找到所请求语言的工厂,那么管理程序将返 回 null,清单 1 中的代码将检查 null 返回值并做出预防。

ScriptEngine 接口

如前所述,代码将使用 ScriptEngine实例执行脚本。脚本引擎充当脚本代码和最后执行代码的底层语言解释器或编译器之间的中间程序。这样,我们就不需要了解各个解释器 使用哪些类来执行脚本。比如说,JRuby 脚本引擎可以将代码传递给 JRuby 的 org.jruby.Ruby类的一个实例,首先将脚本编译成中间形式,然后再调用它计算脚本并处理返回值。脚本引擎实现隐藏了一些细节,包括解释器如何 与 Java代码共享类定义、应用程序对象和输入/输出流。

图 1 显示了应用程序、Java 脚本 API 和ScriptEngine 实现、脚本语言解释器之间的总体关系。我们可以看到,应用程序只依赖于脚本 API,它提供了ScriptEngineManager 类和 ScriptEngine 接口。ScriptEngine实现组件处理使用特定脚本语言解释器的细节。


图 1:脚本 API 组件关系

您可能会问:如何才能获取脚本引擎实现和语言解释器所需的 JAR 文件呢?最好的方法是在 java.net 上托管的开源 Scripting项目中查找脚本引擎实现(请参阅 参考资料)。您可以在 java.net 上找到许多语言的脚本引擎实现和其他网站的链接。Scripting项目还提供了各种链接,通过这些链接可以下载受支持的脚本语言的解释器。

在 清单 1 中,main() 方法将ScriptEngine 传递给各个方法用于计算该方法的 JavaScript 代码。第一个方法如清单 2所示。invokeHelloScript() 方法调用脚本引擎的 eval 方法计算和执行 JavaScript代码中的特定字符串。ScriptEngine 接口定义了 6 个重载的 eval() 方法,用于将接收的脚本当作字符串或java.io.Reader 对象计算,java.io.Reader 对象一般用于从外部源(例如文件)读取脚本。

清单 2. invokeHelloScript 方法

  1. private static void invokeHelloScript(ScriptEngine jsEngine) throws ScriptException {
  2. jsEngine.eval("println(‘Hello from JavaScript’)");
  3. }

复制代码

脚本执行上下文

HelloScriptingWorld 应用程序中的示例脚本 使用JavaScript println()函数向控制台输出结果,但是我们拥有输入和输出流的完全控制权。脚本引擎提供了一个选项用于修改脚本执行的上下文,这意味着我们可以修 改标准输入流、标准输出流和标准错误流,同时还可以定义哪些全局变量和 Java 对象对正在执行的脚本可用。

invokeHelloScript()方法中的 JavaScript 将 Hello from JavaScript 输出到标准输出流,在本例中为控制台窗口。(清单 6 含有运行HelloScriptingWorldApplication 时的完整输出。)

注意,类中的这一方法和其他方法都声明抛出了javax.script.ScriptException。这个选中的异常(脚本包中定义的惟一一个异 常)表示引擎无法解析或执行给定的代码。所有脚本引擎 eval() 方法都声明抛出一个 ScriptException,因此我们的代码需要适当处理这些异常。

清单 3显示了两个有关的方法:defineScriptFunction() 和invokeScriptFunctionFromEngine()。defineScriptFunction() 方法还使用一段硬编码的JavaScript 代码调用脚本引擎的 eval() 方法。但是有一点需要注意,该方法的所有工作只是定义了一个 JavaScript 函数sayHello()。并没有执行任何代码。sayHello() 函数只有一个参数,它会使用 println()语句将这个参数输出到控制台。脚本引擎的 JavaScript 解释器将这个函数添加到全局环境,以供后续的 eval 调用使用(该调用发生在invokeScriptFunctionFromEngine() 方法中,这并不奇怪)。

清单 3. defineScriptFunction 和 invokeScriptFunctionFromEngine 方法

  1. private static void defineScriptFunction(ScriptEngine engine) throws ScriptException {
  2. // Define a function in the script engine
  3. engine.eval(
  4. "function sayHello(name) {" +
  5. " println(‘Hello, ‘ + name)" +
  6. "}"
  7. );
  8. }
  9.  
  10. private static void invokeScriptFunctionFromEngine(ScriptEngine engine)
  11. throws ScriptException
  12. {
  13. engine.eval("sayHello(‘World!’)");
  14. }

复制代码

这两个方法演示了脚本引擎可以维持应用程序组件的状态,并且能够在后续的 eval()方法调用过程中使用其状态。invokeScriptFunctionFromEngine() 方法可以利用所维持的状态,方法是调用定义在eval() 调用中的 sayHello() JavaScript 函数。

许多脚本引擎在 eval()调用之间维持全局变量和函数的状态。但是有一点值得格外注意,Java 脚本 API 并不要求脚本引擎提供这一特性。本文中所使用的JavaScript、Groovy 和 JRuby 脚本引擎确实在 eval() 调用之间维持了这些状态。

清单 4中的代码在前一个示例的基础上做了几分修改。原来的 invokeScriptFunctionFromJava() 方法在调用sayHello() JavaScript 函数时没有使用 ScriptEngine 的 eval() 方法或 JavaScript代码。与此不同,清单 4 中的方法使用 Java 脚本 API 的 javax.script.Invocable接口调用由脚本引擎所维持的函数。invokeScriptFunctionFromJava() 方法将脚本引擎对象传递给 Invocable接口,然后对该接口调用 invokeFunction() 方法,最终使用给定的参数调用 sayHello() JavaScript函数。如果调用的函数需要返回值,则 invokeFunction() 方法会将值封装为 Java 对象类型并返回。

清单 4. invokeScriptFunctionFromJava 方法

  1. private static void invokeScriptFunctionFromJava(ScriptEngine engine)
  2. throws ScriptException, NoSuchMethodException
  3. {
  4. Invocable invocableEngine = (Invocable) engine;
  5. invocableEngine.invokeFunction("sayHello", "from Java");
  6. }

复制代码

使用代理实现高级脚本调用

当脚本函数或方法实现了一个 Java 接口时,就可以使用高级Invocable。Invocable 接口定义了一个 getInterface() 方法,该方法使用接口做为参数并且将返回一个实现该接口的Java 代码对象。从脚本引擎获得代理对象之后,可以将它作为正常的 Java 对象对待。对该代理调用的方法将委托给脚本引擎通过脚本语言执行。

注意,清单 4 中没有 JavaScript 代码。Invocable 接口允许 Java代码调用脚本函数,而无需知道其实现语言。如果脚本引擎无法找到给定名称或参数类型的函数,那么 invokeFunction() 方法将抛出一个java.lang.NoSuchMethodException。

Java 脚本 API 并不要求脚本引擎实现 Invocable 接口。实际上,清单 4 中的代码应该使用 instanceof 运算符确保脚本引擎在转换(cast)之前实现了 Invocable 接口。

通过脚本代码调用 Java 方法

清单 3 和 清单 4 中的示例展示了 Java代码如何调用脚本语言中定义的函数或方法。您可能会问:脚本语言中编写的代码是否可以反过来对 Java 对象调用方法呢?答案是可以。清单 5 中的invokeJavaFromScriptFunction() 方法显示了如何使脚本引擎能够访问 Java 对象,以及脚本代码如何才能对这些Java 对象调用方法。明确的说,invokeJavaFromScriptFunction() 方法使用脚本引擎的 put() 方法将HelloScriptingWorld 类的实例本身提供给引擎。当引擎拥有 Java 对象的访问权之后(使用 put()调用所提供的名称),eval() 方法脚本中的脚本代码将使用该对象。

清单 5. invokeJavaFromScriptFunction 和 getHelloReply 方法

  1. private static void invokeJavaFromScriptFunction(ScriptEngine engine)
  2. throws ScriptException
  3. {
  4. engine.put("helloScriptingWorld", new HelloScriptingWorld());
  5. engine.eval(
  6. "println(‘Invoking getHelloReply method from JavaScript…’);" +
  7. "var msg = helloScriptingWorld.getHelloReply(vJavaScript’);" +
  8. "println(‘Java returned: ‘ + msg)"
  9. );
  10. }
  11.  
  12. /** Method invoked from the above script to return a string. */
  13. public String getHelloReply(String name) {
  14. return "Java method getHelloReply says, ‘Hello, " + name + "’";
  15. }

复制代码

清单 5 中的 eval() 方法调用中所包含的 JavaScript 代码使用脚本引擎的 put() 方法调用所提供的变量名称helloScriptingWorld 访问并使用 HelloScriptingWorld Java 对象。清单 5 中的第二行JavaScript 代码将调用 getHelloReply() 公有 Java 方法。getHelloReply() 方法将返回 Javamethod getHelloReply says, ‘Hello, <parameter>’ 字符串。eval() 方法中的JavaScript 代码将 Java 返回值赋给 msg 变量,然后再将其打印输出给控制台。

Java 对象转换

当脚本引擎使运行于引擎环境中的脚本能够使用 Java对象时,引擎需要将其封装到适用于该脚本语言的对象类型中。封装可能会涉及到一些适当的对象-值转换,比如说允许 Java Integer对象直接在脚本语言的数学表达式中使用。关于如何将 Java对象转换为脚本对象的研究是与各个脚本语言的引擎特别相关的,并且不在本文的讨论范围之内。但是,您应该意识到转换的发生,因为可以通过测试来确 保所使用的脚本语言执行转换的方式符合您的期望。

ScriptEngine.put 及其相关 get() 方法是在运行于脚本引擎中的Java 代码和脚本之间共享对象和数据的主要途径。(有关这一方面的详细论述,请参阅本文后面的 Script-execution scope一节。)当我们调用引擎的 put() 方法时,脚本引擎会将第二个参数(任何 Java对象)关联到特定的字符串关键字。大多数脚本引擎都是让脚本使用特定的变量名称来访问 Java 对象。脚本引擎可以随意对待传递给 put()方法的名称。比如说,JRuby 脚本引擎让 Ruby 代码使用全局 $helloScriptingWorld 对象访问helloScriptingWorld,以符合 Ruby 全局变量的语法。

脚本引擎的 get() 方法检索脚本环境中可用的值。一般而言,Java 代码通过 get() 方法可以访问脚本环境中的所有全局变量和函数。但是只有明确使用 put() 与脚本共享的 Java 对象才可以被脚本访问。

外部脚本在运行着的应用程序中访问和操作 Java 对象的这种功能是扩展 Java 程序功能的一项强有力的技巧。(第 2 部分将通过示例研究这一技巧)。

运行 HelloScriptingWorld 应用程序

您可以通过下载和构建源代码来运行 HelloScriptingWorld 应用程序。此 .zip 中文件含有一个 Ant 脚本和一个 Maven 构建脚本,可以帮助大家编译和运行示例应用程序。请执行以下步骤:

·下载 此.zip文件。
·创建一个新目录,比如说 java-scripting,并将步骤 1 中所下载的文件解压到该目录中。
·打开命令行 shell 并转到该目录。
·运行 ant run-hello 命令。

您应该可以看到类似于清单 6 的 Ant 控制台输出。注意,defineScriptFunction() 函数没有产生任何输出,因为它虽然定义了输出但是却没有调用 JavaScript 函数。

清单 6. 运行 HelloScriptingWorld 时的输出

  1. Calling invokeHelloScript…
  2. Hello from JavaScript
  3.  
  4. Calling defineScriptFunction…
  5.  
  6. Calling invokeScriptFunctionFromEngine…
  7. Hello, World!
  8.  
  9. Calling invokeScriptFunctionFromJava…
  10. Hello, from Java
  11.  
  12. Calling invokeJavaFromScriptFunction…
  13. Invoking getHelloReply method from JavaScript…
  14. Java returned: Java method getHelloReply says, ‘Hello, JavaScript’

复制代码

Java 5 兼容性

Java SE 6 引入了 Java 脚本API,但是您也可以使用 Java SE 5 运行此 API。只需要提供缺少的 javax.script包类的一个实现即可。所幸的是,Java Specification Request 223 参考实现中含有这个实现(请参阅 参考资料获得下载链接。)JSR 223 对 Java 脚本 API 做出了定义。

如果您已经下载了 JSR 223参考实现,解压下载文件并将 script-api.jar、script-js.jar 和 js.jar文件复制到您的类路径下。这些文件将提供脚本 API、JavaScript 脚本引擎接口和 Java SE 6 中所附带的 JavaScript脚本引擎。

脚本执行作用域

与简单地调用引擎的 get() 和put() 方法相比,如何将 Java 对象公开给运行于脚本引擎中的脚本具有更好的可配置性。当我们在脚本引擎上调用 get() 或 put()方法时,引擎将会在 javax.script.Bindings 接口的默认实例中检索或保存所请求的关键字。(Bindings 接口只是一个Map 接口,用于强制关键字为字符串。)

当代码调用脚本引擎的 eval()方法时,将使用引擎默认绑定的关键字和值。但是,您可以为 eval() 调用提供自己的 Bindings对象,以限制哪些变量和对象对于该特定脚本可见。该调用外表上类似于 eval(String, Bindings) 或 eval(Reader,Bindings)。要帮助您创建自定义的 Bindings,脚本引擎将提供一个 createBindings()方法,该方法和返回值是一个内容为空的 Bindings 对象。使用 Bindings 对象临时调用 eval 将隐藏先前保存在引擎默认绑定中的Java 对象。

要添加功能,脚本引擎含有两个默认绑定:其一为 get() 和 put() 调用所使用的 “引擎作用域”绑定 ;其二为 “全局作用域” 绑定,当无法在 “引擎作用域”中找到对象时,引擎将使用第二种绑定进行查找。脚本引擎并不需要使脚本能够访问全局绑定。大多数脚本都可以访问它。

“全局作用域” 绑定的设计目的是在不同的脚本引擎之间共享对象。 ScriptEngineManager 实例返回的所有脚本引擎都是 “全局作用域”绑定对象。您可以使用 getBindings(ScriptContext.GLOBAL_SCOPE) 方法检索某个引擎的全局绑定,并且可以使用setBindings(Bindings, ScriptContext.GLOBAL_SCOPE) 方法为引擎设置全局绑定。

ScriptContext 是一个定义和控制脚本引擎运行时上下文的接口。脚本引擎的 ScriptContext 含有 “引擎” 和 “全局”作用域绑定,以及用于标准输入和输出操作的输入和输出流。您可以使用引擎的 getContext() 方法获取并操作脚本引擎的上下文。

一些脚本 API 概念,比如说作用域、绑定 和上下文,开始看来会令人迷惑,因为它们的含义有交叉的地方。本文的源代码下载文件含有一个名为ScriptApiRhinoTest 的 JUnit 测试文件,位于 src/test/java directory 目录,该文件可以通过Java 代码帮助解释这些概念。

使用OCI驱动连接Oracle数据库

yanzhiguo 发表于 2010-07-26 14:07 浏览次数:170 views 来源:

 

  公司各种产品的前端、后端差不多都是用java开发的Web应用且运行在tomcat里。根据oracle的说 法,java程序连接oracle数据库时,用oci驱动要比用thin驱动性能好些。 主要的区别是使用thin驱动时,不需要安装oracle的客户端,而使用oci时则要安装oracle 的客户端。

从使用thin驱动切换到oci驱动在配置来说很简单,只需把连接字符串java: oracle:thin: @hostip:1521:实例名换为java: oracle: oci@本地服务名即可。如:

jdbc: oracle:thin: @10.1.1.2:1521: shdb

改成

jdbc: oracle: oci8: @shdb

但这里这台机需安装oracle数据库的客户端并配置本地服务名,同时还需指定NLS_LANG环境变量,NLS_LANG环境变量是用来控制客户端 在显示oracle数据库的数据时所用的字符集和本地化习惯。通常把NLS_LANG的字符集部分指定为数据库所用的字符集则就不会存在java显示的乱 码问题了。

对于oracle数据库客户端的安装,有二种选择,一是老实的用oracle数据库的安装光盘安装对应版本的oracle客户端。二是下载 oracle提从的即时客户端,即时客户端是不用安装的,把下载包解压即可。

要使java web正常的通过oci驱动访问oracle,还需要客户端正确的配置一下相关变 量。主要如下:

对于windows系统并使用oracle客户端时:

1. 把%ORACLE_HOME%\lib加到PATH环境变量.

2. 把%ORACLE_HOME%\jdbc\lib\classes12.jar加到CLASSPATH环境变量里.也可以把classes12.jar拷 贝到tomcat的comman\lib目录下。

对于windows系统并使用oracle的即时客户端时(假定即时客户端解压在d盘):

1. 把d:\instantclient_10_2加到PATH环境变量

2. 把d:\instantclient_10_2\classes12.jar加到CLASSPATH环境变量里.也可以把classes12.jar拷贝 到tomcat的comman\lib目录下。

对于linux系统并使用oracle客户端时:

1. 在使用tomcat的用户主目录下的.bash_profile文件中加入

exprot ORACLE_HOME=/u01/app/oracle/product/9.2.0.4

export LD_LIBRARY_PATH=$ORACLE_HOME/lib

2. 把classes12.jar拷贝到tomcat的comman\lib目录下。

对于linux系统并使用oracle即时客户端时:

1. 在使用tomcat的用户主目录下的.bash_profile文件中加入

exprot ORACLE_HOME=/instantclient_10_2

export LD_LIBRARY_PATH=$ORACLE_HOME/lib

2. 把instantclient_10_2目录下的classes12.jar拷贝到tomcat的comman\lib目录下。

如果一个tomcat下带了几个应用,且几个应用都要连接oracle数据库时,则要注意的时,不要在每个应用的WEB-INF/lib目录下放入 oracle的classes12.jar/zip文件。而应该把classes12.jar/zip文件放到tomcat的common/lib目录 下。否则会出来ojdbclib9/10库重复加载的错误。

使用oracle即时客户端是,本地服务名的建立可以在目录instantclient_10_2下建立tnsnames.ora下添加连接串,如:

SHDB =(DESCRIPTION =(ADDRESS_LIST =(ADDRESS = (PROTOCOL = TCP)(HOST = 10.1.1.236)(PORT = 1521)))
(CONNECT_DATA =(SERVICE_NAME = shdb)))

即可

解决Spring和Hibernate整合时HQL查询语句乱码问题

yanzhiguo 发表于 2010-04-11 13:05 浏览次数:146 views 来源:

 

只需在配置文件ApplicationContext.xml中修 改代码如下:

  1. <bean id="sessionFactory"
  2.          class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
  3.          destroy-method="destroy" >
  4.          <property name="dataSource" ref="dataSource" />
  5.          <property name="mappingResources">
  6.            <list>
  7.              <value>com/snsoft/crm/domain/SnRukudan.hbm.xml</value>
  8.              <value>com/snsoft/crm/domain/SnKehu.hbm.xml</value
  9.          </list>
  10.          </property>
  11.          <property name="hibernateProperties">
  12.            <props>
  13.              <prop key="hibernate.query.factory_class">
  14.                  org.hibernate.hql.classic.ClassicQueryTranslatorFactory
  15.              </prop>
  16.              <prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
  17.              <prop key="hibernate.show_sql">false</prop>
  18.            </props>
  19.          </property>        
  20.    </bean>

复制代码

程序员低级错误大集合,各个痛心疾首

yanzhiguo 发表于 2010-04-11 13:03 浏览次数:166 views 来源:

 

先汇总一下错误的分类吧
1 全角问题
2 =和==的问题
3 空格问题
4 拼写问题
5 疏忽,忘记做该做的事情
6 新东西,不是很熟悉

下面是明细,呵呵呵。

1 老紫竹(java2000_net)
我最难忘的一次,是自作聪明的在一个for循环后面加上了一个分号,而且是在大量的代码重构之后,运 行时偶尔发生这个错误。
大概用了我近1周的时间。

从那之后,我的代码的大括号,就都放在代码行的后面了!

  • for(int i=0;i<=10;i++);
  • {
  •   //
  • }

for(int i=0;i<=10;i++);{//}

  • for(int i=0;i<=10;i++);{
  •   //
  • }

for(int i=0;i<=10;i++);{//}

编辑器格式化代码时,可以有很大的帮助。
说说大家曾经常犯的低级错误吧,也好让其它朋友有个心理准备,想拿块豆腐砸自己脑袋的冲动少几次。

2 jsp页面莫名的报空指针,而且有时出有时不出。最后发 现是jsp代码里混了个全角的空格,排版比较乱的时候看不出来。然后那空格被当变量名的一部分了,偏 偏那变量还不常用。悲剧啊( ̄(工) ̄)

3 当年用vc,貌似写个类似于jTable的东西,在我的机器上一切都好,在老板(小公司,cto也是老板之一)的机器上一跑就死。。。。。。。。然后发 现,我的机器分辨率是640×480,老板的是800×600的,结果数组溢出。。。。。。。

4 写了2个preparestatement,一顿addbatch,然而最后只写了一个 preparestatement.executeBatch();找了n长时间才看到问题所在。

5 三层架构里面的业务逻辑层比如:

  • public bool Add(hLink.Model.FavoriteInfo model) {
  •   return dal.Add(model) > 0;
  • }

public bool Add(hLink.Model.FavoriteInfo model) {return dal.Add(model) > 0;}
写成了

  • public bool Add(hLink.Model.FavoriteInfo model) {
  •   return Add(model) > 0;
  • }

public bool Add(hLink.Model.FavoriteInfo model) {return Add(model) > 0;}
还好碰到了一次,以后报stackoverflow…异常就知道什么错了,呵呵、

6 struct CXTEST_DAT
{

WORD wLen;

}
int TestData(…, DWORD dwLen);
使用时:
CXTEST_DAT stData;
TestData(…, stData.wLen);
自动扩展为DWORD后,长度总是不对

7 我也经常犯低级错误,但我觉得并不可笑,每次发现自己犯低级错误后,自己的印象也更加深刻,重复犯错的机率就小了很多。

比如,我第一次使用Java枚举时是这样写:

  • /**
  • * SQLCommandType SQL命令类型枚举
  • * @author CodingMouse
  • * @version 1.0.0.1
  • */
  • public enum SQLCommandType {
  •  
  •     /**
  •      * SQL查询数据命令
  •      */
  •     SELECT,
  •     /**
  •      * SQL插入数据命令
  •      */
  •     INSERT,
  •     /**  
  •      * SQL修改数据命令
  •      */
  •     UPDATE,
  •     /**
  •      * SQL删除数据命令
  •      */
  •     DELETE
  •  
  • }

/*** SQLCommandType SQL命令类型枚举* @author CodingMouse* @version 1.0.0.1*/public enum SQLCommandType {/*** SQL查询数据命令*/SELECT,/*** SQL插入数据命令*/INSERT,/*** SQL修改数据命令*/UPDATE,/*** SQL删除数据命令*/DELETE}

当在方法中switch传入的枚举参数值时:

  • switch(枚举变量) {
  •     case SQLCommandType.SELECT
  •         // 中间的逻辑处理
  •          break;
  •     case SQLCommandType.INSERT
  •         ……
  • }

switch(枚举变量) {case SQLCommandType.SELECT// 中间的逻辑处理break;case SQLCommandType.INSERT……}

语法老报错,却不知道怎么回事,明明Java中的switch分支是支持枚举的啊?
后来摆渡了一下才知道原来switch分支中case枚举时是不需要加枚举类型前缀的

8 oracle ,写sql 语句时候 怎么老是提示错误 ,语句老长 ,调试了一天 ,眼睛看花了 ,结果一看 ,掉了个冒号!!

9 又一次 装了个防火墙 运行myeclipse的时候 报错,说端口有问题 ,我还以为是被占用 或者tomcat的问题 弄了半天 百度 Google 都无果,猛然发现是被防火墙拦截了

10 struts.xml ——>sturts.xml

11 用C言语时
if(i=1)
{
}本来我是想判断真假
结果……

12 在修改配置文件的时候,就因为一 个空格,而找了半天的错误

13 在维护公司一个比较老的项目的时候,自作聪明地写了这么一个类(主要是为了共用数据库连接)
public class DataQuery{
//静态的connection
private static Connection con=ConnectionFactory.getConnection();

private Collection <User> getUsers(int groupId){
Statement st=null;
ResultSet rs=null;
try{
st=con.createStatement();
String SQL="……";
……
}catch(Exception e){
e.printStackTrace();
}
}

//其他查询方法,共用一个connection……
}

因为这个项目已经运行了5年了,Struts1.1的时候就做的这个项目,老紫竹的家实在没有办法在里面配连接池,于是自作聪明在里面做了这种事

后果很显然啊
要么是connection太多,要么是某个地方把connection关了导致其他方法抛空指针
还好,出问题的时候拿过来跟了一把马上意识到了这个愚蠢的问题
唉。。。

14 以前犯过一个错误,在while()中==写成=了
因为开始的时候写的是正确的,后来不知道怎么不小心误操作删掉一个等号,结果程序死活不对了。当时是在一个嵌入式平台上,开发环境很垃圾,所以还老以为是编译环境的bug。

昨天写程序调出了n个bug,一个是在finally加返回语句了,程序输出的正好有别的异常,所以没往这考虑。搞了半个下午才发现问题。
还有指针的问题是最让人头疼的了。

15 用中文输入法输入全角的分号,结果编译不通过,还查不出原因

16 select * from a where a.idin(…..)

17 前10分钟写JAVA代码,写好后再写javascript代码
错误:
for(int i = 0 ; i < xxx.length; i++){
}
正确:
for(var i = 0 ; i < xxx.length; i++){
}
老紫竹备注:这个错误太长见了,我也经常如此

18 我的第一个错误是,UPDATE 是后面没有加条件,结果,所有数据全部,UPdate了,呵 呵,老板猛劈!!!

19 有此写存储过程传进来的参数USERId, 有张涉及到的表有字段userId
存储过程有一个条件为 update *** where userId = USERID;
把数据库干翻了,后悔得要死。
不区分大小写啊!

20 把表单里面的input标签的类型写成了submit,本来这是正常的,但是我的目的是想在 input类型中触发某一个方法,然后在页面上显示相应的数据,并不是想提交整个表单,结果我一点input中的按钮,页面始终没有反应,新数据就是不能 够显示,搞了整整两天才发现这个简单的问题!

21 for (int i = 0,len = arr.size(); i < len; i++) {
temp = arr.get(0);
}
。。。循环下来都取的第一个元素。。。

22 改linux内核的时候缓冲区长度定义为128字节,后来改了方案,老紫竹的家需要512字节的 缓冲区,忘了改定义,结果一赋值缓冲区溢出,内核崩溃,查了几个月才查出是什么原因

23 写SQL存储过程的时候拼接字符串的长度给的太小,导致多条件查询的时候总是出现bad results。。。

24 VB的代码
FOR I=1 to N
Next
忘记给I加一了

老紫竹的家
25 操作注册表是造成了溢出,把堆破坏了,造成了之后的代码,只要申请new,malloc就出错,查了半天才查出来。
主要是写注册表的函数的参数是传出参数,返回指超出了定义的长度。
还有就是c下,segment错误, 数组越界,很难查,都是运行时出错, 访问了不能访问或无效的地址。

26 嵌套循环用同一个变量

  • int i=0;
  • while(i<10){
  •   for(i=0;i<5;i++){
  •     ….
  •   }
  • }

int i=0;while(i<10){for(i=0;i<5;i++){….}}

27 印象最深刻的就是c#的一个循环
if后面忘加{}

调了一个下午

28 引用窗体类指针是直接定义头文件用 没有在App中存放 造成指针不一样,调不出相应成员(VC)

29

  • Sring date  = request.getParameter("date");
  •  
  • if(!"".equals("date")){
  •      …..
  • }
  •  
  • ……..

Sring date  = request.getParameter("date");if(!"".equals("date")){…..}……..

最低级的错误……
老紫竹备注:我也犯过多次,习惯的就把双引号加上去了

30 循环变量搞错了
for( int i=0; i!=5; ++i )
{
for( int j=0 j!=5; ++i )
{

31 记得初学Delphi 的时候,把程序代码保存在了一个中文命名的目录下,按F9 编译加运行,
始终运行失败,到处找人帮忙看,整个项目组高手都找遍了,都觉得奇怪,折腾了好久,都解决不了,各种方法都用尽了,
就是不能正常运行,后来把程序重新抄了一遍,换了个文件夹,问题解决了。
从那以后再也不敢把程序放在中文文件夹了,这事过去五六年了还记得。

32 打包的时候不修改数据库配置文件,然用户在测试数据库上

33 昨晚写条件语句时把If()的括号输成了全角的格式。偏偏那个IF括号还是嵌套的,盛怒之下卸 掉了所有中文输入法,今天又重新装上

34 一次用了ImageButton,结果又用js进行的提交,照成了冗余数据,后来才知道ImageButton 是会submit的~

35 数据库时的case语句
case when then
后面总是忘了写end

36 写SQL语句用全角的逗号然后调试总是报sql语句错误动员很多同事帮我查错,最后发现被同事 鄙视很久

37 我最近也在维护公司的一个网站,发现原来写程序的那个人也是用了全局的静态的连接,导致访问的 人多时老是出现数据列不存在的错误,他竟然在数据层中用一个这样的变量,搞得上面说我写的程序有问题,自从修改后就没出现不存在数据列的问题了.呵呵.
老紫竹备注: static 这东西,你一定要知道他到死是干啥的再用,否则你还是去掉比较保险,虽然浪费点内存。但问题出现的越早越好啊。

38 一个变量命名中的0写成O,不知道几个兄弟可以不戴眼镜找出来

39 form写成了from。。。。。。。

接口、类、抽象类、对象的另类解释

yanzhiguo 发表于 2010-04-08 12:19 浏览次数:134 views 来源:

 

大家也许都知道做工艺器或是工厂里做生产某些产品的模具模具。如做一个金属的五角星,只要将钢水罐到五角星的模具模具里就可以很容易地制做五角星。

    我们也可以将类比喻成做五角星的模具。  而生产出的一个个五角星就是一个个对象。 为了建立不同的对象(有的是金属的、有的塑料的,等等),可通过向模具里灌入不同的液态材料就可以制做。这也相当于向类的构造方法中传入不同的参数(相当 于不同的液态材料)。

    还有就是接口,如果说类是对象的抽象,那么接口就是类的抽象。
我们也可以将接口看成是制做这个五角星模具的规格列表(相当于接口中的抽象方法)。也就是说,只有这个模具(类)符合(实现)相应的规格(接口), 才会成为制做五角星的模具模具。

哈哈,当然,这个使用模具生成五角星及其他产品的工厂可以看成是对象工厂。

  1. class ObjectFactory
  2. {
  3.     public static 五角星 create五角星() {}
  4.    
  5.     public static 圆 create圆() {}

  6.       public static 椭圆 create椭圆() {}
  7.    
  8. }

复制代码

也就是说,类和接口都不是实际的产品,都不能拿来用,类相当于模具(没人会拿模具模具在商店里卖吧,估计只有对象工厂会买),而接口只相当于制做模 具的规格列表,如尺寸、角度等。而这些规格列表的内容需要在具体类中实现才可能成为具体的模具,如长度和宽度,如果在类中实现为长度等于宽度,那么这个类 就成为一个成方形的模具,如果长度不等于宽度,就是一个长方形的模具。然后再实例化这个模具(类),就生成出一个个正方形或长方形对象。

    在面向对象理论中还有一个抽象类,这个抽象类其实就相当于一个半成品的模具。如只实现在长度和宽度(相当于在抽象类中已实现的方法),并没有实现深度(这 个深度相当于抽象类中的抽象方法,这个抽象方法需要在抽象类的子类中实现)。所以这个模具就是半成品了。因此,是无法直接拿半成品的模具(抽象类)去生产 (实例化)产品(对象)的。

Struts2:上传任意多个文件

yanzhiguo 发表于 2010-04-08 12:17 浏览次数:306 views 来源:

 

一、上传单个文件
    上传文件是很多Web程序都具有的功能。在Struts1.x中 已经提供了用于上传文件的组件。而在Struts2中提供了一个更为容易操作的上传文件组件。所不同的 是,Struts1.x的上传组件需要一个ActionForm来传递文件,而Struts2的上传组件是一个拦截器(这个拦截器不用配置,是自动装载的)。在本文中先介绍一下如何用struts2上传单个文件,最后介绍一下用struts2上传任意多个文件。
    要用Struts2实现上传单个文件的功能非常容易 实现,只要使用普通的Action即可。但为了获得一些上传文件的信息,如上传文件名、上传文件类型以 及上传文件的Stream对象,就需要按着一定规则来为Action类增加一些gettersetter方法。
    Struts2中,用于获得和设置java.io.File对象(Struts2将文件上传到临时路径,并使用java.io.File打开这个临时文件)的 方法是getUploadsetUpload。获 得和设置文件名的方法是getUploadFileNamesetUploadFileName,获得和设置上传文件内容类型的方法是getUploadContentTypesetUploadContentType。下面是用于上传的动作类的完整代码:

    • package action;

    • import java.io.*;
    • import com.opensymphony.xwork2.ActionSupport;

    • public class UploadAction extends ActionSupport
    • {
    •     private File upload;
    •     private String fileName;
    •     private String uploadContentType;
    •    
    •     public String getUploadFileName()
    •     {
    •         return fileName;
    •     }

    •     public void setUploadFileName(String fileName)
    •     {
    •         this.fileName = fileName;
    •     }

    •     public File getUpload()
    •     {
    •         return upload;
    •     }

    •     public void setUpload(File upload)
    •     {
    •         this.upload = upload;
    •     }
    •     public void setUploadContentType(String contentType)
    •     {
    •         this.uploadContentType=contentType;
    •    
    •     }
    •    
    •     public String getUploadContentType()
    •     {
    •         return this.uploadContentType;
    •     }
    •     public String execute() throws Exception
    •     {   
    •         java.io.InputStream is = new java.io.FileInputStream(upload);
    •         java.io.OutputStream os = new java.io.FileOutputStream("d:\\upload\\" + fileName);
    •         byte buffer[] = new byte[8192];
    •         int count = 0;
    •         while((count = is.read(buffer)) > 0)
    •         {
    •             os.write(buffer, 0, count);
    •         }
    •         os.close();
    •         is.close();
    •         return SUCCESS;
    •     }
    • }

复制代码

  在execute方法中的实现代码就很简单了,只是从临 时文件复制到指定的路径(在这里是d:\upload)中。上传文件的临时目录的默认值是javax.servlet.context.tempdir的值,但可以通过struts.properties(和struts.xml在同一个目录下)的struts.multipart.saveDir属性设 置。Struts2上传文件的默认大小限制是2M2097152字节),也可以通过struts.properties文件中的struts.multipart.maxSize修改,如struts.multipart.maxSize=2048 表示 一次上传文件的总大小不能超过2K字节。
下面的代码是上传文件的JSP页面代码

    • <%@ page language="java" import="java.util.*" pageEncoding="GBK"%>
    • <%@ taglib prefix="s" uri="/struts-tags"%>

    • <html>
    •     <head>
    •         <title>上传单个文件</title>
    •     </head>

    •     <body>
    •         <s:form action="upload" namespace="/test"
    •             enctype="multipart/form-data">
    •             <s:file name="upload" label="输入要上传的文件名" />
    •             <s:submit value="上传" />
    •         </s:form>

    •     </body>
    • </html>

复制代码

也可以在success.jsp页中通过<s:property>获得文件的属性文件名和 文件内容类型),代码如下

    <s:propertyvalue="uploadFileName"/>

二、上传任意多个文件

Struts2中,上传任意多个文件也非常容易实现。首先,要想上 传任意多个文件,需要在客户端使用DOM技术生成任意多个<input type=”file” />标签。name属性值都相同。代码如下:

    • <html>
    •     <head>
    •         <script language="javascript">

    • function addComponent()
    • {
    •         var uploadHTML = document.createElement( "<input type=’file’  name=’upload’/>");
    •         document.getElementById("files").appendChild(uploadHTML);
    •         uploadHTML = document.createElement( "<p/>");
    •         document.getElementById("files").appendChild(uploadHTML);
    • }
    • </script>
    •     </head>
    •     <body>
    •         <input type="button" onclick="addComponent();" value="添加文件" />
    •         <br />
    •         <form onsubmit="return true;" action="/struts2/test/upload.action"
    •             method="post" enctype="multipart/form-data">
    •             <span id="files"> <input type=’file’ name=’upload’ />
    •                 <p />
    •             </span>
    •             <input type="submit" value="上传" />
    •         </form>
    •     </body>

    • </html>

复制代码

上面的javascript代码可以生成任意多个<input type=’file’>标签,name的值都为file(要注意的是,上面的 javascript代码只适合于IE浏览器,firefox等其他浏览器需要使用他的代码)。至于Action类,和上传单个文件的Action类基本一至,只需要将三个属性的类型改为List即可。代码如下:

    • package action;

    • import java.io.*;
    • import com.opensymphony.xwork2.ActionSupport;

    • public class UploadMoreAction extends ActionSupport
    • {
    •     private java.util.List<File> uploads;
    •     private java.util.List<String> fileNames;
    •     private java.util.List<String> uploadContentTypes;

    •     public java.util.List<String> getUploadFileName()
    •     {
    •         return fileNames;
    •     }
    •     public void setUploadFileName(java.util.List<String> fileNames)
    •     {
    •         this.fileNames = fileNames;
    •     }
    •     public java.util.List<File> getUpload()
    •     {
    •         return uploads;
    •     }

    •     public void setUpload(java.util.List<File> uploads)
    •     {
    •         this.uploads = uploads;
    •     }

    •     public void setUploadContentType(java.util.List<String> contentTypes)
    •     {
    •         this.uploadContentTypes = contentTypes;

    •     }

    •     public java.util.List<String> getUploadContentType()
    •     {
    •         return this.uploadContentTypes;
    •     }

    •     public String execute() throws Exception
    •     {
    •         if (uploads != null)
    •         {
    •             int i = 0;
    •             for (; i < uploads.size(); i++)
    •             {
    •                 java.io.InputStream is = new java.io.FileInputStream(uploads.get(i));
    •                 java.io.OutputStream os = new java.io.FileOutputStream(
    •                         "d:\\upload\\" + fileNames.get(i));
    •                 byte buffer[] = new byte[8192];
    •                 int count = 0;
    •                 while ((count = is.read(buffer)) > 0)
    •                 {
    •                     os.write(buffer, 0, count);
    •                 }
    •                 os.close();
    •                 is.close();
    •             }
    •         }
    •         return SUCCESS;
    •     }
    • }

复制代码

  在execute方法中,只是对List对象进行枚举,在循环中的代码和上传单个文件时的代 码基本相同。如果读者使用过struts1.x的上传组件,是不是感觉Struts2的上传功能更容易实现呢?在Struts1.x中上传多个文件时,可是需要建立带索引的属性的。而 在Struts2中,就是这么简单就搞定了。图1是上传任意多个文件的界面。

                                 图1
  在本文中给出了用Struts2上传任意多个文件的一个方法,如果哪位读者有更好的方法,请跟贴!




返回首页 | 关于我们 | 联系我们 | 乘车路线| 地铁路线| 学生公寓| 周边环境| 诚聘英才 | 网站地图 | 友情链接 | 版权声明