2006年04月29日

一篇关于web.xml配置的详细说明

(转载)[TOMCAT]用web.xml控制Web应用的行为



1 定义头和根元素

部署描述符文件就像所有XML文件一样,必须以一个XML头开始。这个头声明可以使用的XML版本并给出文件的字符编码。
DOCYTPE声明必须立即出现在此头之后。这个声明告诉服务器适用的servlet规范的版本(如2.2或2.3)并指定管理此文件其余部分内容的语法的DTD(Document Type Definition,文档类型定义)。
所有部署描述符文件的顶层(根)元素为web-app。请注意,XML元素不像HTML,他们是大小写敏感的。因此,web-App和WEB-APP都是不合法的,web-app必须用小写。

2 部署描述符文件内的元素次序

XML 元素不仅是大小写敏感的,而且它们还对出现在其他元素中的次序敏感。例如,XML头必须是文件中的第一项,DOCTYPE声明必须是第二项,而web- app元素必须是第三项。在web-app元素内,元素的次序也很重要。服务器不一定强制要求这种次序,但它们允许(实际上有些服务器就是这样做的)完全拒绝执行含有次序不正确的元素的Web应用。这表示使用非标准元素次序的web.xml文件是不可移植的。
下面的列表给出了所有可直接出现在web-app元素内的合法元素所必需的次序。例如,此列表说明servlet元素必须出现在所有servlet-mapping元素之前。请注意,所有这些元素都是可选的。因此,可以省略掉某一元素,但不能把它放于不正确的位置。
l icon icon元素指出IDE和GUI工具用来表示Web应用的一个和两个图像文件的位置。
l display-name display-name元素提供GUI工具可能会用来标记这个特定的Web应用的一个名称。
l description description元素给出与此有关的说明性文本。
l context-param context-param元素声明应用范围内的初始化参数。
l filter 过滤器元素将一个名字与一个实现javax.servlet.Filter接口的类相关联。
l filter-mapping 一旦命名了一个过滤器,就要利用filter-mapping元素把它与一个或多个servlet或JSP页面相关联。
l listener servlet API的版本2.3增加了对事件监听程序的支持,事件监听程序在建立、修改和删除会话或servlet环境时得到通知。Listener元素指出事件监听程序类。
l servlet 在向servlet或JSP页面制定初始化参数或定制URL时,必须首先命名servlet或JSP页面。Servlet元素就是用来完成此项任务的。
l servlet-mapping 服务器一般为servlet提供一个缺省的URL:http://host/webAppPrefix/servlet/ServletName。但是,常常会更改这个URL,以便servlet可以访问初始化参数或更容易地处理相对URL。在更改缺省URL时,使用servlet-mapping元素。
l session-config 如果某个会话在一定时间内未被访问,服务器可以抛弃它以节省内存。可通过使用HttpSession的setMaxInactiveInterval方法明确设置单个会话对象的超时值,或者可利用session-config元素制定缺省超时值。
l mime-mapping 如果Web应用具有想到特殊的文件,希望能保证给他们分配特定的MIME类型,则mime-mapping元素提供这种保证。
l welcom-file-list welcome-file-list元素指示服务器在收到引用一个目录名而不是文件名的URL时,使用哪个文件。
l error-page error-page元素使得在返回特定HTTP状态代码时,或者特定类型的异常被抛出时,能够制定将要显示的页面。
l taglib taglib元素对标记库描述符文件(Tag Libraryu Descriptor file)指定别名。此功能使你能够更改TLD文件的位置,而不用编辑使用这些文件的JSP页面。
l resource-env-ref resource-env-ref元素声明与资源相关的一个管理对象。
l resource-ref resource-ref元素声明一个资源工厂使用的外部资源。
l security-constraint security-constraint元素制定应该保护的URL。它与login-config元素联合使用
l login-config 用login-config元素来指定服务器应该怎样给试图访问受保护页面的用户授权。它与sercurity-constraint元素联合使用。
l security-role security-role元素给出安全角色的一个列表,这些角色将出现在servlet元素内的security-role-ref元素的role-name子元素中。分别地声明角色可使高级IDE处理安全信息更为容易。
l env-entry env-entry元素声明Web应用的环境项。
l ejb-ref ejb-ref元素声明一个EJB的主目录的引用。
l ejb-local-ref ejb-local-ref元素声明一个EJB的本地主目录的应用。

3 分配名称和定制的UL

在web.xml中完成的一个最常见的任务是对servlet或JSP页面给出名称和定制的URL。用servlet元素分配名称,使用servlet-mapping元素将定制的URL与刚分配的名称相关联。
3.1 分配名称
为了提供初始化参数,对servlet或JSP页面定义一个定制URL或分配一个安全角色,必须首先给servlet或JSP页面一个名称。可通过 servlet元素分配一个名称。最常见的格式包括servlet-name和servlet-class子元素(在web-app元素内),如下所示:
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>moreservlets.TestServlet</servlet-class>
</servlet>
这表示位于WEB-INF/classes/moreservlets/TestServlet的servlet已经得到了注册名Test。给 servlet一个名称具有两个主要的含义。首先,初始化参数、定制的URL模式以及其他定制通过此注册名而不是类名引用此servlet。其次,可在 URL而不是类名中使用此名称。因此,利用刚才给出的定义,URL http://host/webAppPrefix/servlet/Test 可用于 http://host/webAppPrefix/servlet/moreservlets.TestServlet 的场所。
请记住:XML元素不仅是大小写敏感的,而且定义它们的次序也很重要。例如,web-app元素内所有servlet元素必须位于所有servlet- mapping元素(下一小节介绍)之前,而且还要位于5.6节和5.11节讨论的与过滤器或文档相关的元素(如果有的话)之前。类似地,servlet 的servlet-name子元素也必须出现在servlet-class之前。5.2节"部署描述符文件内的元素次序"将详细介绍这种必需的次序。
例如,程序清单5-1给出了一个名为TestServlet的简单servlet,它驻留在moreservlets程序包中。因为此servlet是扎根在一个名为deployDemo的目录中的Web应用的组成部分,所以TestServlet.class放在deployDemo/WEB- INF/classes/moreservlets中。程序清单5-2给出将放置在deployDemo/WEB-INF/内的web.xml文件的一部分。此web.xml文件使用servlet-name和servlet-class元素将名称Test与TestServlet.class相关联。图 5-1和图5-2分别显示利用缺省URL和注册名调用TestServlet时的结果。

程序清单5-1 TestServlet.java
package moreservlets;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

/** Simple servlet used to illustrate servlet naming
* and custom URLs.
* <P>
* Taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* & 2002 Marty Hall; may be freely used or adapted.
*/

public class TestServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String uri = request.getRequestURI();
out.println(ServletUtilities.headWithTitle("Test Servlet") +
"<BODY BGCOLOR=\"#FDF5E6\">\n" +
"<H2>URI: " + uri + "</H2>\n" +
"</BODY></HTML>");
}
}


程序清单5-2 web.xml(说明servlet名称的摘录)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<!– … –>
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>moreservlets.TestServlet</servlet-class>
</servlet>
<!– … –>
</web-app>

3.2 定义定制的URL
大多数服务器具有一个缺省的serlvet URL:
http://host/webAppPrefix/servlet/packageName.ServletName。虽然在开发中使用这个URL很方便,但是我们常常会希望另一个URL用于部署。例如,可能会需要一个出现在Web应用顶层的URL(如,http: //host/webAppPrefix/Anyname),并且在此URL中没有servlet项。位于顶层的URL简化了相对URL的使用。此外,对许多开发人员来说,顶层URL看上去比更长更麻烦的缺省URL更简短。
事实上,有时需要使用定制的URL。比如,你可能想关闭缺省URL映射,以便更好地强制实施安全限制或防止用户意外地访问无初始化参数的servlet。如果你禁止了缺省的URL,那么你怎样访问servlet呢?这时只有使用定制的URL了。
为了分配一个定制的URL,可使用servlet-mapping元素及其servlet-name和url-pattern子元素。Servlet- name元素提供了一个任意名称,可利用此名称引用相应的servlet;url-pattern描述了相对于Web应用的根目录的URL。url- pattern元素的值必须以斜杠(/)起始。
下面给出一个简单的web.xml摘录,它允许使用URL http://host/webAppPrefix/UrlTest而不是http://host/webAppPrefix/servlet/Test或
http: //host/webAppPrefix/servlet/moreservlets.TestServlet。请注意,仍然需要XML头、 DOCTYPE声明以及web-app封闭元素。此外,可回忆一下,XML元素出现地次序不是随意的。特别是,需要把所有servlet元素放在所有 servlet-mapping元素之前。
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>moreservlets.TestServlet</servlet-class>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name>Test</servlet-name>
<url-pattern>/UrlTest</url-pattern>
</servlet-mapping>
URL模式还可以包含通配符。例如,下面的小程序指示服务器发送所有以Web应用的URL前缀开始,以..asp结束的请求到名为BashMS的servlet。
<servlet>
<servlet-name>BashMS</servlet-name>
<servlet-class>msUtils.ASPTranslator</servlet-class>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name>BashMS</servlet-name>
<url-pattern>/*.asp</url-pattern>
</servlet-mapping>
3.3 命名JSP页面
因为JSP页面要转换成sevlet,自然希望就像命名servlet一样命名JSP页面。毕竟,JSP页面可能会从初始化参数、安全设置或定制的URL中受益,正如普通的serlvet那样。虽然JSP页面的后台实际上是servlet这句话是正确的,但存在一个关键的猜疑:即,你不知道JSP页面的实际类名(因为系统自己挑选这个名字)。因此,为了命名JSP页面,可将jsp-file元素替换为servlet-calss元素,如下所示:
<servlet>
<servlet-name>Test</servlet-name>
<jsp-file>/TestPage.jsp</jsp-file>
</servlet>
命名JSP页面的原因与命名servlet的原因完全相同:即为了提供一个与定制设置(如,初始化参数和安全设置)一起使用的名称,并且,以便能更改激活 JSP页面的URL(比方说,以便多个URL通过相同页面得以处理,或者从URL中去掉.jsp扩展名)。但是,在设置初始化参数时,应该注意,JSP页面是利用jspInit方法,而不是init方法读取初始化参数的。
例如,程序清单5-3给出一个名为TestPage.jsp的简单JSP页面,它的工作只是打印出用来激活它的URL的本地部分。TestPage.jsp放置在deployDemo应用的顶层。程序清单5-4给出了用来分配一个注册名PageName,然后将此注册名与http://host/webAppPrefix/UrlTest2/anything 形式的URL相关联的web.xml文件(即,deployDemo/WEB-INF/web.xml)的一部分。

程序清单5-3 TestPage.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>
JSP Test Page
</TITLE>
</HEAD>
<BODY BGCOLOR="#FDF5E6">
<H2>URI: <%= request.getRequestURI() %></H2>
</BODY>
</HTML>


程序清单5-4 web.xml(说明JSP页命名的摘录)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<!– … –>
<servlet>
<servlet-name>PageName</servlet-name>
<jsp-file>/TestPage.jsp</jsp-file>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name> PageName </servlet-name>
<url-pattern>/UrlTest2/*</url-pattern>
</servlet-mapping>
<!– … –>
</web-app>


4 禁止激活器servlet

对servlet或JSP页面建立定制URL的一个原因是,这样做可以注册从 init(servlet)或jspInit(JSP页面)方法中读取得初始化参数。但是,初始化参数只在是利用定制URL模式或注册名访问 servlet或JSP页面时可以使用,用缺省URL http://host/webAppPrefix/servlet/ServletName 访问时不能使用。因此,你可能会希望关闭缺省URL,这样就不会有人意外地调用初始化servlet了。这个过程有时称为禁止激活器servlet,因为多数服务器具有一个用缺省的servlet URL注册的标准servlet,并激活缺省的URL应用的实际servlet。
有两种禁止此缺省URL的主要方法:
l 在每个Web应用中重新映射/servlet/模式。
l 全局关闭激活器servlet。
重要的是应该注意到,虽然重新映射每个Web应用中的/servlet/模式比彻底禁止激活servlet所做的工作更多,但重新映射可以用一种完全可移植的方式来完成。相反,全局禁止激活器servlet完全是针对具体机器的,事实上有的服务器(如ServletExec)没有这样的选择。下面的讨论对每个Web应用重新映射/servlet/ URL模式的策略。后面提供在Tomcat中全局禁止激活器servlet的详细内容。
4.1 重新映射/servlet/URL模式
在一个特定的Web应用中禁止以http://host/webAppPrefix/servlet/ 开始的URL的处理非常简单。所需做的事情就是建立一个错误消息servlet,并使用前一节讨论的url-pattern元素将所有匹配请求转向该 servlet。只要简单地使用:
<url-pattern>/servlet/*</url-pattern>
作为servlet-mapping元素中的模式即可。
例如,程序清单5-5给出了将SorryServlet servlet(程序清单5-6)与所有以http://host/webAppPrefix/servlet/ 开头的URL相关联的部署描述符文件的一部分。

程序清单5-5 web.xml(说明JSP页命名的摘录)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<!– … –>
<servlet>
<servlet-name>Sorry</servlet-name>
<servlet-class>moreservlets.SorryServlet</servlet-class>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name> Sorry </servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
<!– … –>
</web-app>


程序清单5-6 SorryServlet.java
package moreservlets;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

/** Simple servlet used to give error messages to
* users who try to access default servlet URLs
* (i.e., http://host/webAppPrefix/servlet/ServletName)
* in Web applications that have disabled this
* behavior.
* <P>
* Taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* & 2002 Marty Hall; may be freely used or adapted.
*/

public class SorryServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String title = "Invoker Servlet Disabled.";
out.println(ServletUtilities.headWithTitle(title) +
"<BODY BGCOLOR=\"#FDF5E6\">\n" +
"<H2>" + title + "</H2>\n" +
"Sorry, access to servlets by means of\n" +
"URLs that begin with\n" +
"http://host/webAppPrefix/servlet/\n" +
"has been disabled.\n" +
"</BODY></HTML>");
}

public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


4.2 全局禁止激活器:Tomcat
Tomcat 4中用来关闭缺省URL的方法与Tomcat 3中所用的很不相同。下面介绍这两种方法:
1.禁止激活器: Tomcat 4
Tomcat 4用与前面相同的方法关闭激活器servlet,即利用web.xml中的url-mapping元素进行关闭。不同之处在于Tomcat使用了放在 install_dir/conf中的一个服务器专用的全局web.xml文件,而前面使用的是存放在每个Web应用的WEB-INF目录中的标准 web.xml文件。
因此,为了在Tomcat 4中关闭激活器servlet,只需在install_dir/conf/web.xml中简单地注释出/servlet/* URL映射项即可,如下所示:
<!–
<servlet-mapping>
<servlet-name>invoker</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
–>
再次提醒,应该注意这个项是位于存放在install_dir/conf的Tomcat专用的web.xml文件中的,此文件不是存放在每个Web应用的WEB-INF目录中的标准web.xml。
2.禁止激活器:Tomcat3
在Apache Tomcat的版本3中,通过在install_dir/conf/server.xml中注释出InvokerInterceptor项全局禁止缺省 servlet URL。例如,下面是禁止使用缺省servlet URL的server.xml文件的一部分。
<!–
<RequsetInterceptor
className="org.apache.tomcat.request.InvokerInterceptor"
debug="0" prefix="/servlet/" />
–>

5 初始化和预装载servlet与JSP页面

这里讨论控制servlet和JSP页面的启动行为的方法。特别是,说明了怎样分配初始化参数以及怎样更改服务器生存期中装载servlet和JSP页面的时刻。
5.1 分配servlet初始化参数
利用init-param元素向servlet提供初始化参数,init-param元素具有param-name和param-value子元素。例如,在下面的例子中,如果initServlet servlet是利用它的注册名(InitTest)访问的,它将能够从其方法中调用getServletConfig(). getInitParameter("param1")获得"Value 1",调用getServletConfig().getInitParameter("param2")获得"2"。
<servlet>
<servlet-name>InitTest</servlet-name>
<servlet-class>moreservlets.InitServlet</servlet-class>
<init-param>
<param-name>param1</param-name>
<param-value>value1</param-value>
</init-param>
<init-param>
<param-name>param2</param-name>
<param-value>2</param-value>
</init-param>
</servlet>
在涉及初始化参数时,有几点需要注意:
l 返回值。GetInitParameter的返回值总是一个String。因此,在前一个例子中,可对param2使用Integer.parseInt获得一个int。
l JSP中的初始化。JSP页面使用jspInit而不是init。JSP页面还需要使用jsp-file元素代替servlet-class。
l 缺省URL。初始化参数只在通过它们的注册名或与它们注册名相关的定制URL模式访问Servlet时可以使用。因此,在这个例子中,param1和 param2初始化参数将能够在使用URL http://host/webAppPrefix/servlet/InitTest 时可用,但在使用URL http://host/webAppPrefix/servlet/myPackage.InitServlet 时不能使用。
例如,程序清单5-7给出一个名为InitServlet的简单servlet

2006年04月21日

“在按照我的理解方式审查了软件开发的生命周期后,我得出一个结论:实际上满足工程设计标准的唯一软件文档,就是源代码清单。”


                                                                            ——Jack Reeves
       Reeves认为软件系统的源代码是它的主要设计文档。用来描绘源代码的图示只是设计的附属物而不是设计本身。
       软件设计是一个抽象的概念。它和程序的概括形状(Shape)、结构以及每一个模块、类和方法的详细形状和结构有关。可以使用许多不同的媒介去描绘它,但是它最终体现为源代码。最后,源代码就是设计。

      拙劣设计的症状

  • 僵化性(Rigidity):设计难以改变。很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的其他改动。
  • 脆弱性(Fragility):设计易于遭到破坏。对系统的改动会导致系统中和改动的地方在概念上无关的许多地方发现问题。
  • 牢固性(Immobility):设计难以重用。很难解开系统的纠结,使之成为一些可在其他系统中重用的组件。
  • 粘滞性(Viscosity):难以做正确的事情。做正确的事情比做错误的事情要困难。
  • 不必要的复杂性(Needless Complexity):过分设计。设计中包含有不具有任何直接好处的基础结构。
  • 不必要的重复性(Needlsee Repetition):滥用复制/粘贴。设计中包含有重复的结构,而该重复的对象本可以使用单一的抽象进行统一。
  • 晦涩性(Opacity):很难阅读、理解。没有很好的表现出意图。

      面向对象设计原则

  • 单一职责原则(The Single Responsibility Principle,简称SRP)
  • 开放-封闭原则(The Open-Close Principle,简称OCP)
  • Liskov替换原则(The Liskov Subsititution Principle,简称LSP)
  • 依赖倒置原则(The Dependency Inversion Principle,简称DIP)
  • 接口隔离原则(The Interface Segregation Interface,简称ISP)

 单一职责原则(SRP)
     就一个类而言,应该仅有一个引起它变化的原因。
     在SRP中,我们把职责定义为“变化的原因”。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的原则。
     应用FACADE或PROXY模式对设计进行重构可以帮助分离类的职责。
开放—封闭原则(OCP)
    软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改的。
    遵循开放—封闭原则设计的模块具有两个主要的特征,他们是:
   1、“对于扩展是开放的”(Open for extension)。
        这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块的功能。
   2、“对于更改是封闭的”(Closed for modification)。
    对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者Java的“.jar”文件,都无需改动。
    这两个特征好像是互相矛盾的。扩展模块的行为的通常方式就是修改该模块的源代码。不允许修改的模块常常被认为是具有固定的行为。
   
    在面向对象的语言中,可以创建出固定却能够描述一组任意个可能行为的抽象体。这个抽象体就是抽象基类。而这一组任意个可能的行为则可以表现为可能的派生类。
    模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,所以它对于更改是可以关闭的。同时,通过这个抽象体派生,也可以扩展此模块的行为。
  
   实际上使用抽象接口比使用抽象类通常具有更好的效果。
  
   在许多方面,OCP是面向对象设计的核心所在。遵循这个原则可带来面向对象技术所声称的巨大好处(灵活性、可重用性以及可维护性)。然而,对于应用程序的每个部分都肆意地进行抽象并不是一个好主意。应该仅仅对程序中呈现出频繁变化的那部分作出抽象。拒绝不成熟的抽象和抽象本身一样重要。
 
Liskov简单替换原则(LSP)
       OCP背后的主要机制是抽象(abstraction)和多态(polymorphisim),支持抽象和多态的关键机制之一是继承。采用Liskov简单替换原则将保证系统使用了最佳的继承层次,正确使用继承不违反OCP。
      子类型(subtype)必须能够替换掉他们的基类型(base type)
    “这里需要如下替换性质:若对每个类型S的对象s1,都存在一个类型T的对象s2,使得在所有针对T编写的程序P中,用s1替换s2后,程序P行为功能不变,则S是T的子类型。”


——Barbara Liskov

       LSP让我们得出一个非常重要的结论:一个模型,如果孤立的看,并不具有真正意义上的有效性。模型的有效性只能通过它的客户程序来表现。
      在考虑一个特定设计是否恰当时,不能完全孤立地来看这个解决方案。必须要根据该设计的使用者作出的合理假设来审视它。
       1、基于契约设计
       为了使“合理假设”明确化,更好支持LSP,可以采用基于契约设计(Design Contract,简称DBC)方法。
        基于DBC方法,派生类的前置条件和后置条件规则是:
       在重新声明的派生类中的例程(routine)时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件。
       2、不易察觉的违反LSP的设计
       a、 派生类的退化函数
       派生类的某些函数退化(变得没有用处),Base的使用者不知道不能调用f,会导致替换违规。在派生类中存在退化函数并不总是表示违反了LSP,但是当存在这种情况时,应该引起注意。
      b、从派生类抛出异常
      如果在派生类的方法中添加了其基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把他们添加到派生类的方法中就可以能会导致不可替换性。
      LSP是保证OCP的重要原则。
 
依赖倒置原则(DIP)
     高层模块不应该依赖于底层模块。二者都应该依赖于抽象。
     抽象不应该依赖于细节,细节应该依赖于抽象。
 
      1、本质上倒置的应该是接口所有权。
      低层模块实现了在高层模块中声明并被高层模块调用的接口。
      2、依赖于抽象
      程序中所有的依赖关系都应该终止于抽象类或接口。
      任何变量都不应该持有一个具体类的指针和引用。
      任何类都不应该从具体类派生。
      任何方法都不应该覆写它的任何基类中已经实现的方法。
 
接口隔离原则(ISP)
      不应该强迫客户依赖于它们不使用的方法。
       一个对象的客户不是必须通过该对象的接口去访问它,也可以通过委托或者该对象的基类去访问它。
      如何使用ISP
      1、对客户进行分组
      常常可以根据客户调用的服务方法来对客户进行分组。这种分组方法使得可以为每组而不是每个客户创建分离的接口。
      2、改变接口
       在维护面相对象的程序时,常常会改变现有的类和组件的接口。为了减少这些改变的影响。可以通过为现有的对象增加新接口的方法来缓解,而不是去改变现有的接口。
胖类(fat class)会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该类进行一个改动时,会影响到其他所有的客户程序。因此,客户程序应该依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定客户程序的接口,可以实现这个目标。
2006年04月20日

[J2ME]跟我学制作Pak文件


作者:cleverpig



版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:cleverpig(http://blog.matrix.org.cn/page/cleverpig)
原文:http://www.matrix.org.cn/resource/article/43/43966_J2ME_Pak.html
关键字:pak,j2me,减肥


序言:

    由于前些时间,一些matrixer常问关于j2me中使用Pak文件的问题。本人虽学艺不深,但满怀热心的做了一番探索,现将制作Pak文件的看法和方法公布出来,大家多多提意见。

一、什么是Pak文件:

    Pak文件就是将多个文件打包为一个单独文件,在这个文件中保存着多个文件的数据,当然还有一些描述文件结构的数据。所以将“Pak”作为文件的后缀是一种常规的用法,大家可以自定义其它的文件后缀。

二、为什么使用Pak文件:

    由于MIDP对发布安装的j2me程序大小进行了限制,所以缩小发布程序就意味着能够提供更多的程序或者内容(如图片、音乐)给用户。而通过研究发现zip/jar算法对大文件的压缩率高于对等量的多个小文件的压缩率。

    当然还有其它方法,这里简单做一下讨论比如使用混淆器ProGuard的“-overloadaggressively”选项使jar文件缩小,但也会导致一些错误,因为这种方法生成jar中的class符合java byte code标准,但是与java语法相悖,严重的可能造成一些jre对Object的序列化错误。

    所以使用Pak方法将程序中要用到的资源(图片、音乐、文本)组合为单一文件是一个安全有效的方法。而且对于一些商用程序,完全可以在pak文件中对文件数据进行加密,很好的保护了作者和公司的权益。本人的sample中使用了简单的“加减法”加密,对于手机这类设备来讲是一个效率较高的选择。

三、Pak文件的结构:

    大家可以自己设计Pak文件结构,本人这里只是抛砖引玉的作个sample。下面就是本人设计的Pak文件结构:

PAK File Header:Pak文件的头部


  * 签名:6字节char数组
* 版本号:32位float
* 文件table数量:32位整数
* 密码行为:8位字节
* 密码:8位字节
* 文件唯一ID:10字节char数组
* 保留位:32位整数(4字节)



File Table:Pak文件中包含文件的列表,在一个Pak文件中一个被包含的文件对应一个File Table。


* 文件名:30字节char数组
* 文件大小:32位整型
* 文件在pak文件中的位移:32位整数



Concatenated File Data:按File Table的顺序连接在一起的文件数据。


* 文件数据



四、程序框架:
    
    说明:由于Pak文件的制作和使用分别要使用两个java应用领域:j2se和j2me,所以本人将PakUtil类制作了2个版本(j2se和j2me)。

    程序框架如下:
    1。PakHeader类,定义了Pak文件头。
    2。PakFileTable类,定义Pak文件table。
    3。PakUtil类(j2se版),具备两个功能:将多个png图片合成一个Pak文件,并使用简单的加减加密法对其进行加密;从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)或者写为文件。
       PakUtil类(j2me版),具备的功能:从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)。

五、PakHeader和PakFileTable类:
    
PakHeader.java:


package cn.org.matrix.gmatrix.gameLab.util.pak;

/**
* Pak文件头:
* 结构:
*   签名:6字节char数组
*   版本号:32位float
*   文件table数量:32位整数
*   密码行为:8位字节
*   密码:8位字节
*   文件唯一ID:10字节char数组
*   保留位:32位整数(4字节)
* @author cleverpig
*
*/
class PakHeader {
        //定义文件唯一ID长度
        public static final int UNIQUEID_LENGTH=10;
        //定义文件签名长度
        public static final int SIGNATURE_LENGTH=6;
        //定义加法运算
        public static final int ADDITION_CIPHERACTION=0;
        //定义减法运算
        public static final int SUBTRACT_CIHOERACTION=1;
        //文件签名
        private char[] signature=new char[SIGNATURE_LENGTH];
        //版本号
        private float version=0f;
        //文件table数量
        private long numFileTableEntries=0;
        //密码使用方法:在原数据上进行加法还是减法
        private byte cipherAction=ADDITION_CIPHERACTION;
        //密码值
        private byte cipherValue=0x00;
        //唯一ID
        private char[] uniqueID=new char[UNIQUEID_LENGTH];
        //保留的4字节
        private long reserved=0;
        
        public PakHeader(){        
        }
        
        /**
         * 构造方法
         * @param signature 签名
         * @param version 版本
         * @param numFileTableEntries 文件table数量
         * @param cipherAction 密码使用方法
         * @param cipherValue 密码值
         * @param uniqueID 唯一ID
         * @param reserved 保留的2字节
         */
        public PakHeader(char[] signature,float version,
                        long numFileTableEntries,byte cipherAction,
                        byte cipherValue,char[] uniqueID,long reserved){
                for(int i=0;i<SIGNATURE_LENGTH;this.signature[i]=signature[i],i++)
                        ;
                this.version=version;
                this.cipherAction=cipherAction;
                this.numFileTableEntries=numFileTableEntries;
                this.cipherValue=cipherValue;
                for(int i=0;i<UNIQUEID_LENGTH;this.uniqueID[i]=uniqueID[i],i++)
                        ;
                
                this.reserved=reserved;
        }
        
        public byte getCipherValue() {
                return cipherValue;
        }
        public void setCipherValue(byte cipherValue) {
                this.cipherValue = cipherValue;
        }
        public long getNumFileTableEntries() {
                return numFileTableEntries;
        }
        public void setNumFileTableEntries(long numFileTableEntries) {
                this.numFileTableEntries = numFileTableEntries;
        }
        public long getReserved() {
                return reserved;
        }
        public void setReserved(long reserved) {
                this.reserved = reserved;
        }
        public char[] getUniqueID() {
                return uniqueID;
        }
        public void setUniqueID(char[] uniqueID) {
                for(int i=0;i<UNIQUEID_LENGTH;this.uniqueID[i]=uniqueID[i],i++)
                        ;
        }
        public float getVersion() {
                return version;
        }
        public void setVersion(float version) {
                this.version = version;
        }
        public byte getCipherAction() {
                return cipherAction;
        }

        public void setCipherAction(byte cipherAction) {
                this.cipherAction = cipherAction;
        }

        public char[] getSignature() {
                return signature;
        }

        public void setSignature(char[] signature) {
                for(int i=0;i<SIGNATURE_LENGTH;this.signature[i] = signature[i],i++)
                        ;
        }
        
        /**
         * 返回PakHeader的大小
         * @return 返回PakHeader的大小
         */
        public static int size(){
                return SIGNATURE_LENGTH+4+4+1+1+UNIQUEID_LENGTH+4;
        }
        
        public String toString(){
                String result="";
                result+="\t签名:"+new String(this.signature).trim()
                        +"\t版本号:"+this.version
                        +"\t文件table数量:"+this.numFileTableEntries
                        +"\t密码行为:" +this.cipherAction
                        +"\t密码:"+this.cipherValue
                        +"\t文件唯一ID:"+new String(this.uniqueID).trim()
                        +"\t保留位:"+this.reserved;
                return result;
        }

}



PakFileTable.java


package cn.org.matrix.gmatrix.gameLab.util.pak;

/**
* Pak文件table类
* 文件table结构:
*         文件名:30字节char数组
*         文件大小:32位整型
*         文件在pak文件中的位移:32位整数
* @author cleverpig
*
*/
class PakFileTable {
        public static final int FILENAME_LENGTH=30;
        //文件名
        private char[] fileName=new char[FILENAME_LENGTH];
        //文件大小
        private long fileSize=0L;
        //文件在pak文件中的位移
        private long offSet=0L;
        
        public PakFileTable(){
        }
        
        /**
         * 构造方法
         * @param fileName 文件名
         * @param fileSize 文件大小
         * @param offSet 文件在Pak文件中的位移
         */
        public PakFileTable(char[] fileName,
                        long fileSize,long offSet){
                for(int i=0;i<FILENAME_LENGTH;this.fileName[i]=fileName[i],i++)
                        ;
                this.fileSize=fileSize;
                this.offSet=offSet;
        }
        
        public char[] getFileName() {
                return fileName;
        }
        public void setFileName(char[] fileName) {
                for(int i=0;i<fileName.length;this.fileName[i]=fileName[i],i++)
                        ;
        }
        public long getFileSize() {
                return fileSize;
        }
        public void setFileSize(long fileSize) {
                this.fileSize = fileSize;
        }
        public long getOffSet() {
                return offSet;
        }
        public void setOffSet(long offSet) {
                this.offSet = offSet;
        }
        /**
         * 返回文件Table的大小
         * @return 返回文件Table的大小
         */
        public static int size(){
                return FILENAME_LENGTH+4+4;
        }
        
        public String toString(){
                return "\t文件名:"+new String(this.fileName).trim()
                        +"\t文件大小:"+this.fileSize
                        +"\t文件位移:"+this.offSet;
        }
}



六、PakUtil类(j2se版):

PakUtil.java


package cn.org.matrix.gmatrix.gameLab.util.pak;

import java.io.*;
import java.util.Vector;
/**
* Pak工具类
* 功能:
* 1.将多个png图片合成一个Pak文件,并使用简单的加减加密法对其进行加密;
* 2.从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)或者写为文件
* @author cleverpig
*
*/
public class PakUtil {

        public PakUtil(){
        }
        
        /**
         * 返回文件长度
         * @param filePath 文件路径
         * @return 文件长度
         */
        private long getFileSize(String filePath){
                File file=new File(filePath);
                return file.length();
        }
        
        /**
         * 返回文件名
         * @param filePath 文件路径
         * @return 文件名
         */
        private String getFileName(String filePath){
                File file=new File(filePath);
                return file.getName();
        }
        
        /**
         * 计算文件位移的起始点
         * @return 文件位移的起始点
         */
        private long workOutOffsetStart(PakHeader header){
                //计算出文件头+文件table的长度
                return PakHeader.size()+header.getNumFileTableEntries()*PakFileTable.size();
        }
        
        /**
         * 计算文件位移
         * @param fileIndex 文件序号
         * @param lastFileOffset 上一个文件位移
         * @return  文件在pak文件中的位移
         */
        private long workOutNextOffset(long sourceFileSize,long lastFileOffset){
                return lastFileOffset+sourceFileSize;
        }
        
        /**
         * 生成文件table
         * @param sourceFileName 源文件名
         * @param sourceFileSize 源文件长度
         * @param currentFileOffset 当前文件位移
         * @return 生成的PakFileTable对象
         */
        private PakFileTable generateFileTable(String sourceFileName,
                        long sourceFileSize,long currentFileOffset){
                PakFileTable ft=new PakFileTable();
                ft.setFileName(sourceFileName.toCharArray());
                ft.setFileSize(sourceFileSize);
                ft.setOffSet(currentFileOffset);
                return ft;
        }
        
        /**
         * 将char字符数组写入到DataOutputStream中
         * @param toWriteCharArray 被写入的char数组
         * @param dos DataOutputStream
         * @throws Exception
         */
        private void writeCharArray(char[] toWriteCharArray,DataOutputStream dos) throws Exception{
                for(int i=0;i<toWriteCharArray.length;dos.writeChar(toWriteCharArray[i]),i++);
        }
        
        /**
         * 使用文件头中的密码对数据进行加密
         * @param buff 被加密的数据
         * @param buffLength 数据的长度
         * @param header 文件头
         */
        private void encryptBuff(byte[] buff,int buffLength,PakHeader header){
                for(int i=0;i<buffLength;i++){
                        switch(header.getCipherAction()){
                        case PakHeader.ADDITION_CIPHERACTION:
                                buff[i]+=header.getCipherValue();
                                break;
                        case PakHeader.SUBTRACT_CIHOERACTION:
                                buff[i]-=header.getCipherValue();
                                break;
                        }
                }
        }
        
        /**
         * 使用文件头中的密码对数据进行解密
         * @param buff 被解密的数据
         * @param buffLength 数据的长度
         * @param header 文件头
         */
        private void decryptBuff(byte[] buff,int buffLength,PakHeader header){
                for(int i=0;i<buffLength;i++){
                        switch(header.getCipherAction()){
                        case PakHeader.ADDITION_CIPHERACTION:
                                buff[i]-=header.getCipherValue();
                                break;
                        case PakHeader.SUBTRACT_CIHOERACTION:
                                buff[i]+=header.getCipherValue();
                                break;
                        }
                }
        }
        
        /**
         * 制作Pak文件
         * @param sourceFilePath 源文件路径数组
         * @param destinateFilePath 目的文件路径(Pak文件)
         * @param cipherAction 密码行为
         * @param cipherValue 密码
         * @throws Exception
         */
        public void makePakFile(String[] sourceFilePath,
                        String destinateFilePath,PakHeader header) throws Exception{
                
                PakFileTable[] fileTable=new PakFileTable[sourceFilePath.length];
                //计算文件位移起始点
                long fileOffset=workOutOffsetStart(header);
                //逐个建立文件table
                for(int i=0;i<sourceFilePath.length;i++){
                        String sourceFileName=getFileName(sourceFilePath[i]);
                        long sourceFileSize=getFileSize(sourceFilePath[i]);
                        PakFileTable ft=generateFileTable(sourceFileName,sourceFileSize,fileOffset);
                        //计算下一个文件位移
                        fileOffset=workOutNextOffset(sourceFileSize,fileOffset);
                        fileTable[i]=ft;
                }
                //写入文件头
                File wFile=new File(destinateFilePath);
                FileOutputStream fos=new FileOutputStream(wFile);
                DataOutputStream dos=new DataOutputStream(fos);
                writeCharArray(header.getSignature(),dos);
                dos.writeFloat(header.getVersion());
                dos.writeLong(header.getNumFileTableEntries());
                dos.writeByte(header.getCipherAction());
                dos.writeByte(header.getCipherValue());
                writeCharArray(header.getUniqueID(),dos);
                dos.writeLong(header.getReserved());
                //写入文件table
                for(int i=0;i<fileTable.length;i++){
                        writeCharArray(fileTable[i].getFileName(),dos);
                        dos.writeLong(fileTable[i].getFileSize());

                        dos.writeLong(fileTable[i].getOffSet());
                }
                //写入文件数据
                for(int i=0;i<fileTable.length;i++){
                        File ftFile=new File(sourceFilePath[i]);
                        FileInputStream ftFis=new FileInputStream(ftFile);
                        DataInputStream ftDis=new DataInputStream(ftFis);
                        byte[] buff=new byte[256];
                        int readLength=0;
                        while((readLength=ftDis.read(buff))!=-1){
                                encryptBuff(buff,readLength,header);
                                dos.write(buff,0,readLength);
                        }
                        ftDis.close();
                        ftFis.close();
                }
                dos.close();        
        }
        
        /**
         * 从DataInputStream读取char数组
         * @param dis DataInputStream
         * @param readLength 读取长度
         * @return char数组
         * @throws Exception
         */
        private char[] readCharArray(DataInputStream dis,int readLength) throws Exception{
                char[] readCharArray=new char[readLength];
                
                for(int i=0;i<readLength;i++){
                        readCharArray[i]=dis.readChar();
                }
                return readCharArray;
        }
        
        /**
         * 从PAK文件中读取文件头
         * @param dis DataInputStream
         * @return PakHeader
         * @throws Exception
         */
        private PakHeader readHeader(DataInputStream dis) throws Exception{
                PakHeader header=new PakHeader();
                char[] signature=readCharArray(dis,PakHeader.SIGNATURE_LENGTH);
                header.setSignature(signature);
                header.setVersion(dis.readFloat());
                header.setNumFileTableEntries(dis.readLong());
                header.setCipherAction(dis.readByte());
                header.setCipherValue(dis.readByte());
                char[] uniqueID=readCharArray(dis,PakHeader.UNIQUEID_LENGTH);
                header.setUniqueID(uniqueID);
                header.setReserved(dis.readLong());
                return header;
        }
        
        /**
         * 读取所有的文件table
         * @param dis DataInputStream
         * @param fileTableNumber 文件表总数
         * @return 文件table数组
         * @throws Exception
         */
        private PakFileTable[] readFileTable(DataInputStream dis,int fileTableNumber) throws Exception{
                PakFileTable[] fileTable=new PakFileTable[fileTableNumber];
                for(int i=0;i<fileTableNumber;i++){
                        PakFileTable ft=new PakFileTable();
                        ft.setFileName(readCharArray(dis,PakFileTable.FILENAME_LENGTH));
                        ft.setFileSize(dis.readLong());
                        ft.setOffSet(dis.readLong());
                        fileTable[i]=ft;
                }
                return fileTable;
        }
        
        /**
         * 从pak文件读取文件到byte数组
         * @param dis DataInputStream
         * @param fileTable PakFileTable
         * @return byte数组
         * @throws Exception
         */
        private byte[] readFileFromPak(DataInputStream dis,PakHeader header,PakFileTable fileTable) throws Exception{
                dis.skip(fileTable.getOffSet()-workOutOffsetStart(header));
                //
                int fileLength=(int)fileTable.getFileSize();
                byte[] fileBuff=new byte[fileLength];
                int readLength=dis.read(fileBuff,0,fileLength);
                if (readLength<fileLength){
                        System.out.println("读取数据长度不正确");
                        return null;
                }
                else{
                        decryptBuff(fileBuff,readLength,header);
                        return fileBuff;
                }
        }
        
        /**
         * 将buffer中的内容写入到文件
         * @param fileBuff 保存文件内容的buffer
         * @param fileName 文件名
         * @param extractDir 文件导出目录
         * @throws Exception
         */
        private void writeFileFromByteBuffer(byte[] fileBuff,String fileName,String extractDir) throws Exception{
                String extractFilePath=extractDir+fileName;
                File wFile=new File(extractFilePath);
                FileOutputStream fos=new FileOutputStream(wFile);
                DataOutputStream dos=new DataOutputStream(fos);
                dos.write(fileBuff);
                dos.close();
                fos.close();
        }
        
        /**
         * 从pak文件中取出指定的文件到byte数组,如果需要的话可以将byte数组写为文件
         * @param pakFilePath  pak文件路径
         * @param extractFileName pak文件中将要被取出的文件名
         * @param writeFile 是否需要将byte数组写为文件
         * @param extractDir 如果需要的话可以将byte数组写为文件,extractDir为取出数据被写的目录文件
         * @return byte数组
         * @throws Exception
         */
        public byte[] extractFileFromPak(String pakFilePath,
                        String extractFileName,boolean writeFile,String extractDir) throws Exception{
                File rFile=new File(pakFilePath);
                FileInputStream fis=new FileInputStream(rFile);
                DataInputStream dis=new DataInputStream(fis);
                PakHeader header=readHeader(dis);
                PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());

                boolean find=false;
                int fileIndex=0;
                for(int i=0;i<fileTable.length;i++){
                        String fileName=new String(fileTable[i].getFileName()).trim();
                        if (fileName.equals(extractFileName)){
                                find=true;
                                fileIndex=i;
                                break;
                        }
                }
                if (find==false){
                        System.out.println("没有找到指定的文件");
                        return null;
                }
                else{
                        byte[] buff=readFileFromPak(dis,header,fileTable[fileIndex]);
                        if (writeFile){
                                writeFileFromByteBuffer(buff,extractFileName,extractDir);
                        }
                        else{
                                dis.close();
                                fis.close();
                        }
                        return buff;
                }
        }
        
        
        /**
         * 从pak文件中取出指定的Pak文件的信息
         * @param pakFilePath  pak文件路径
         * @return 装载文件头和文件table数组的Vector
         * @throws Exception
         */
        public Vector showPakFileInfo(String pakFilePath) throws Exception{
                File rFile=new File(pakFilePath);
                
                FileInputStream fis=new FileInputStream(rFile);
                DataInputStream dis=new DataInputStream(fis);
                
                PakHeader header=readHeader(dis);
                PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());

                Vector result=new Vector();
                result.add(header);
                result.add(fileTable);
                return result;
        }
        
        public static void main(String[] argv) throws Exception{
                PakUtil pu=new PakUtil();
                
                //构造文件头
                char[] signature=new char[PakHeader.SIGNATURE_LENGTH];
                signature=new String("012345").toCharArray();
                char[] uniqueID=new char[PakHeader.UNIQUEID_LENGTH];
                uniqueID=new String("0123456789").toCharArray();
                PakHeader header=new PakHeader();
                header.setSignature(signature);
                header.setNumFileTableEntries(3);
                header.setCipherAction((byte)PakHeader.ADDITION_CIPHERACTION);
                header.setCipherValue((byte)0x0f);
                header.setUniqueID(uniqueID);
                header.setVersion(1.0f);
                header.setReserved(0L);
                
                String[] filePathArray={"F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\apple.png",
                                "F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\cushaw.png",
                                "F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\flash.png"};
                String extractFilePath="F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\test.pak";
                //制作Pak文件
                System.out.println("制作Pak文件...");
                pu.makePakFile(filePathArray,extractFilePath,header);
                System.out.println("制作Pak文件完成");
                
                //从Pak文件中取出所有的图片文件
                Vector pakInfo=pu.showPakFileInfo(extractFilePath);
                header=(PakHeader)pakInfo.elementAt(0);
                System.out.println("Pak文件信息:");
                System.out.println("文件头:");
                System.out.println(header);
                
                PakFileTable[] fileTable=(PakFileTable[])pakInfo.elementAt(1);
                for(int i=0;i<fileTable.length;i++){
                        System.out.println("文件table["+i+"]:");
                        System.out.println(fileTable[i]);
                }
                
                String restoreDir="F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\extract\\";
                String restoreFileName=null;
                byte[] fileBuff=null;
                for(int i=0;i<fileTable.length;i++){
                        restoreFileName=new String(fileTable[i].getFileName()).trim();
                        System.out.println("从Pak文件中取出"+restoreFileName+"文件...");
                        fileBuff=pu.extractFileFromPak(extractFilePath,restoreFileName,true,restoreDir);
                        System.out.println("从Pak文件中取出"+restoreFileName+"文件保存在"+restoreDir+"目录");
                }
        }
}



七、PakUtil类(j2me版):

PakUtil.java


package cn.org.matrix.gmatrix.gameLab.util.pak;

import java.io.*;
import java.util.Vector;
/**
* Pak工具类
* 功能:
* 从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)
* @author cleverpig
*
*/
public class PakUtil {
        
        public PakUtil(){
        }
        
        /**
         * 计算文件位移的起始点
         * @return 文件位移的起始点
         */
        private long workOutOffsetStart(PakHeader header){
                //计算出文件头+文件table的长度
                return PakHeader.size()+header.getNumFileTableEntries()*PakFileTable.size();
        }
        
        /**
         * 从DataInputStream读取char数组
         * @param dis DataInputStream
         * @param readLength 读取长度
         * @return char数组
         * @throws Exception
         */
        private char[] readCharArray(DataInputStream dis,int readLength) throws Exception{
                char[] readCharArray=new char[readLength];
                
                for(int i=0;i<readLength;i++){
                        readCharArray[i]=dis.readChar();
                }
                return readCharArray;
        }
        
        /**
         * 从PAK文件中读取文件头
         * @param dis DataInputStream
         * @return PakHeader
         * @throws Exception
         */
        private PakHeader readHeader(DataInputStream dis) throws Exception{
                PakHeader header=new PakHeader();
                char[] signature=readCharArray(dis,PakHeader.SIGNATURE_LENGTH);
                header.setSignature(signature);
                header.setVersion(dis.readFloat());
                header.setNumFileTableEntries(dis.readLong());
                header.setCipherAction(dis.readByte());
                header.setCipherValue(dis.readByte());
                char[] uniqueID=readCharArray(dis,PakHeader.UNIQUEID_LENGTH);
                header.setUniqueID(uniqueID);
                header.setReserved(dis.readLong());
                return header;
        }
        
        /**
         * 读取所有的文件table
         * @param dis DataInputStream
         * @param fileTableNumber 文件表总数
         * @return 文件table数组
         * @throws Exception
         */
        private PakFileTable[] readFileTable(DataInputStream dis,int fileTableNumber) throws Exception{
                PakFileTable[] fileTable=new PakFileTable[fileTableNumber];
                for(int i=0;i<fileTableNumber;i++){
                        PakFileTable ft=new PakFileTable();
                        ft.setFileName(readCharArray(dis,PakFileTable.FILENAME_LENGTH));
                        ft.setFileSize(dis.readLong());
                        ft.setOffSet(dis.readLong());
                        fileTable[i]=ft;
                }
                return fileTable;
        }
        
        /**
         * 从pak文件读取文件到byte数组
         * @param dis DataInputStream
         * @param fileTable PakFileTable
         * @return byte数组
         * @throws Exception
         */
        private byte[] readFileFromPak(DataInputStream dis,PakHeader header,PakFileTable fileTable) throws Exception{
                dis.skip(fileTable.getOffSet()-workOutOffsetStart(header));
                //
                int fileLength=(int)fileTable.getFileSize();
                byte[] fileBuff=new byte[fileLength];
                int readLength=dis.read(fileBuff,0,fileLength);
                if (readLength<fileLength){
                        System.out.println("读取数据长度不正确");
                        return null;
                }
                else{
                        decryptBuff(fileBuff,readLength,header);
                }
                return fileBuff;
        }
        
        /**
         * 使用文件头中的密码对数据进行解密
         * @param buff 被解密的数据
         * @param buffLength 数据的长度
         * @param header 文件头
         */
        private void decryptBuff(byte[] buff,int buffLength,PakHeader header){
                for(int i=0;i<buffLength;i++){
                        switch(header.getCipherAction()){
                        case PakHeader.ADDITION_CIPHERACTION:
                                buff[i]-=header.getCipherValue();
                                break;
                        case PakHeader.SUBTRACT_CIHOERACTION:
                                buff[i]+=header.getCipherValue();
                                break;
                        }
                }
        }
        
        /**
         * 从pak文件中取出指定的文件到byte数组
         * @param pakResourceURL  pak文件的资源路径
         * @param extractResourceName pak文件中将要被取出的文件名
         * @return byte数组
         * @throws Exception
         */
        public byte[] extractResourceFromPak(String pakResourceURL
                        ,String extractResourceName) throws Exception{
                InputStream is=this.getClass().getResourceAsStream(pakResourceURL);
                DataInputStream dis=new DataInputStream(is);
                PakHeader header=readHeader(dis);
//                System.out.println("文件头:");
//                System.out.println(header);
                PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());
//                for(int i=0;i<fileTable.length;i++){
//                        System.out.println("文件table["+i+"]:");
//                        System.out.println(fileTable[i]);
//                }
                boolean find=false;
                int fileIndex=0;
                for(int i=0;i<fileTable.length;i++){
                        String fileName=new String(fileTable[i].getFileName()).trim();
                        if (fileName.equals(extractResourceName)){
                                find=true;
                                fileIndex=i;
                                break;
                        }
                }
                if (find==false){
                        System.out.println("没有找到指定的文件");
                        return null;
                }
                else{
                        byte[] buff=readFileFromPak(dis,header,fileTable[fileIndex]);
                        return buff;
                }
        }
        
        
        /**
         * 从pak文件中取出指定的Pak文件的信息
         * @param pakResourcePath  pak文件资源路径
         * @return 装载文件头和文件table数组的Vector
         * @throws Exception
         */
        public Vector showPakFileInfo(String pakResourcePath) throws Exception{
                InputStream is=this.getClass().getResourceAsStream(pakResourcePath);
                DataInputStream dis=new DataInputStream(is);
                
                PakHeader header=readHeader(dis);
                PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());

                Vector result=new Vector();
                result.addElement(header);
                result.addElement(fileTable);
                return result;
        }
        
        public static void main(String[] argv) throws Exception{
                PakUtil pu=new PakUtil();
                String extractResourcePath="/test.pak";
                //从Pak文件中取出所有的图片文件
                Vector pakInfo=pu.showPakFileInfo(extractResourcePath);
                PakHeader header=(PakHeader)pakInfo.elementAt(0);
                System.out.println("Pak文件信息:");
                System.out.println("文件头:");
                System.out.println(header);
                
                PakFileTable[] fileTable=(PakFileTable[])pakInfo.elementAt(1);
                for(int i=0;i<fileTable.length;i++){
                        System.out.println("文件table["+i+"]:");
                        System.out.println(fileTable[i]);
                }
                
                String restoreFileName=null;
                byte[] fileBuff=null;
                for(int i=0;i<fileTable.length;i++){
                        restoreFileName=new String(fileTable[i].getFileName()).trim();
                        System.out.println("从Pak文件中取出"+restoreFileName+"文件数据...");
                        fileBuff=pu.extractResourceFromPak(extractResourcePath,restoreFileName);
                        System.out.println("从Pak文件中取出"+restoreFileName+"文件数据完成");
                }
        }
}



八、源代码使用简介:

    Pak过程:j2se版的PakUtil将testFiles目录中的三个png文件Pak成为test.pak文件。
    UnPak过程:j2se版的PakUtil将testFiles目录中test.pak文件释放到testFiles\extract目录下;j2me版的PakUtil从res目录中的test.pak文件读取出其中所包含的3个png文件数据并装入到byte数据,用来构造Image对象,大家请运行PakUtilTestMIDlet.java便可看到输出的信息。

九、源代码下载:

j2se版源代码[下载文件]
j2sme版源代码[下载文件]

资源
·CleverPig的blog:http://blog.matrix.org.cn/page/cleverpig
·Matrix-Java开发者社区:http://www.matrix.org.cn/
·ProGuard
·j2me.org上的Topic: Give me all your tricks for minimizing jar file size