NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

了解war包和ear包

| Comments

上一次,讲到了如何如何手动编译Java,简单的介绍打Jar包(提示:在classpath的基础上,一定将包的目录结构打进去)。Jar包的目的,将编译过的class文件有效合理的组装起来,方便管理和使用。这一次,滕老板继续跟我们讲解了如何打War包。

在本篇文章中,将分别介绍War包和Ear包(在项目中遇到的Ear包的模块,不清楚是什么,所以随便一起弄清楚)。

War包

只要做过Java开发的同志,肯定知道War包,至少听说过。

War包(Web application ARchieve)也是一种Jar包文件,它被用来描述由JSP,Servlet,Java类,XML文件,tag库,静态web页面等资源组成的集合,它们组合在一起成文一个web应用。

从命令上说,打War包的命令和打Jar包的命令一样,比较简单,主要是要了解一个War包的目录结构:

MANIFEST.MF是一个可选文件,用来描述额外的元数据信息。

WEB-INF目录包含了War中的私有文件,也就是说,当web应用被部署之后,该目录下的文件不能够由Web客户端(浏览器)直接访问的。

WEB-INF/lib/用来放置你代码中需要使用的第三方的jar文件。

WEB-INF/classes/用来放置你自己编译的class文件。

WEB-INF/web.xml是web部署描述器,JavaEE配置web模块的标准描述器,这里不详细解释。

最后是公共的静态文件。

那么在打包的时候,按照这个目录结构打包,然后将War包放置到tomcat的webapp目录下,tomcat在运行时就会自动帮你解包并运行,或者你也可以直接将包含该目录结构的目录直接拷贝到tomcat的webapp下,一样可以运行,打包只不过是一个封装和压缩过程。

那什么是Ear包呢?

Ear(Enterprise ARchieve)用于在Java EE中将一个或者多个模块封装到一个文件中,这样,多个不同模块在应用服务器上的部署就可以同时并持续的进行。

Java EE应用以Jar文件,War文件和Ear文件形式呈现。War或者Ear文件都是标准的Jar文件,只不过扩展名是.war和.ear。通过Jar,War和Ear等文件或模块的方式,使得用一些相同组件,来构建不同的JavaEE应用成为可能。不需要额外的编码,只需要将不同的JavaEE模块打包到不同的Java EE的Jar包,War包或Ear包的文件中。

一个Ear文件是由Java EE模块和可选的部署描述器组成。部署描述器是一个带有.xml扩展名的XML文档,描述了一个应用,模块或者组件的部署设置。因为部署描述其的信息是声明式的,所以可以直接修改它,与我们的源代码没有关系。在运行时中,Java EE的服务器会读取部署描述器的内容,并根据描述对应用,模块或者组件做相对应的操作。

上面两个图,分别摘自Jboss at Work(A practice guide)和JavaEE6 Tutorial。

一个Java EE模块是由一个或者多个为同一个容器类型准备的Java EE组件组成,当然还包含一个可选的部署描述器文件。

Java EE模块有以下几种类型:

EJB模块,它包含企业级别的bean类文件,一个EJB的部署描述器(可选),EJB模块会以Jar包形式组装。

Web模块,它包含了servlet类文件,web文件,其他相关class文件,图片,html静态文件,和一个web.xml部署文件(可选),并最终以war包形式组装。

应用客户端模块,包含了class文件,应用客户端部署描述器文件(可选)并以Jar包形式组装。

资源适配模块,包含所有的Java接口,类,原生库,部署描述器(可选)。组合在一起,实现JavaEE的Connector架构。同样是以Jar包方式组装,但是文件以.rar扩展名结尾。

就和War包包含一个web.xml的部署描述器,一个Ear包文件包含一个名字为application.xml的文件。它是一个必要的打包列表,告诉Java EE服务器Ear包种包含什么东西,以及去哪里找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee             http://java.sun.com/xml/ns/j2ee/application_1_4.xsd"
             version="1.4">
    <display-name>JBossAtWorkEAR</display-name>
    <module>
        <web>
            <web-uri>webapp.war</web-uri>
            <context-root>jaw</context-root>
        </web>
    </module>
    <module>
        <java>common.jar</java>
    </module>
</application>

application.xml中的元素都应该是自解释的。在告诉应用程序服务器每一个Jar包的名字,以及它的作用(功能)。

例如:

1
2
3
4
5
6
<module>
    <web>
        <web-uri>webapp.war</web-uri>
        <context-root>jaw</context-root>
    </web>
</module>

用来告诉你web应用站点名字,如果你是直接打成War包,那么War包的名字就是这个站点名。

总而言之,Ear包是由Java EE多个模块组成,相互配合,完成各自职责,形成完整的Java EE应用。

参考资料:

http://docs.oracle.com/javaee/6/tutorial/doc/bnaby.html

Jboss at work A practice guide(Chapter 3)(http://oreilly.com/catalog/jboss/chapter/ch03.pdf)

丢掉IDE,回到Java的第一堂课

| Comments

最近,《实现领域驱动设计》的译者,ThoughtWorks的“滕老板”(腾云)给我们上了一堂基础课,就是没有IDE的情况下,去编译和运行用文本编辑器编写的Java代码。

这本应该是基础中的基础,我记得应该是Java第一堂课就应该讲到的东西。但是我们这群用惯了IDE的程序员,在没有了IDE之后,就开始不知所措了。虽然不知道这些知识,对于普通开发似乎是没有影响,但毕竟是Java的重要基础知识,了解这些知识可以帮助你理解IDE帮你做了什么,可以更快速的定位问题。

还是举例子,滕老板的例子:

目录结构

src

–client

—-Client.java

–service

—-Service.java

1
2
3
4
5
6
7
8
package service;

public class Service {
  public String run(){
      String str = new String("as your service");
      return str;
  }
}
1
2
3
4
5
6
7
8
9
10
package client;

import service.*;

public class Client {
  public static void main(String args[]){
      Service service = new Service();
      System.out.println(service.run());
  }
}

ok,代码写完了,你知道该怎么运行吗?

命令行:进入到src目录下

1
2
javac client/Client.java
java client.Client

这样就会输出正确结果:as your service。

再来看一下目录结构

src

–client

—-Client.java

—-Client.class

–service

—-Service.java

—-Service.class

我们知道javac命令编译的是.java文件,而java命令运行是.class文件。

但是这里还是有几个疑问:

1.javac client/Client.java时,它怎么知道去哪里找Service类?

2.对于上一个疑问,你的答案可能是import service.*;告诉它去service包下找,那么第二疑问,为什么是import service.*;而不是import src.service.*?

3.第三个疑问和第一个疑问类似,答案也类似,java client.Client,怎么知道去哪里找service.class文件?

面对这三个问题,答案是,要引入两个概念。

sourcepath和classpath。

classpath大家听得比较多,用来告诉JVM或者Java编译器到哪里去寻找用户定义的类和包。

sourcepath和classpath一样,只不过它是用来告诉编译器到哪里去寻找源文件。

在上面的javac和java命令中,既没有显示的指定sourcepath,也没有显示的指定classpath。但是它们都有默认值,就是将当前目录作为各自的路径。

比如:我现在从当前路径src,进入到它的一个子目录中src/sub。

然后我运行:javac ../client/Client.java命令

它提示我找不到Service类。 那是因为当前的sourcepath是src/sub,在这个路径下没有Service.java。我们改一下,显示的设置它sourcepath。

运行命令:javac -sourcepath ../ ../client/Client.java

编译成功。

那么运行呢?

直接在src/sub路径下,运行:java client.Client。能行吗?肯定不行。

Error: Could not find or load main class client.Client

必须要显示的指定它的classpath为上一级目录:

java -classpath ../ client.Client

ok,成功。

现在再来让我们回到刚才的第二个问题。为什么import的不是src.service.* ,因为我们设置它的sourcepath,它就成为一个相对路径。

这不禁让我想到一个问题。package和import到底有什么用?为什么package名字和目录结构一致?

这不是白痴问题吗?

package是作为命名空间,因为不同开发商,开发出来的类库可能会有相同的类名。为了区分,防止冲突,用不同的包名区分。

import是根据你要使用的类,从classpath下找到你要使用的类,并引入到你的类中。

我做了一个实验:

将Service类的package定义,改为service.something,这样包名和目录结构就不一致了。

1
2
3
4
5
6
7
package service.something;

public class Service {
  public String run(){
      return "as your service";
  }
}

然后还是照常编译。

client/Client.java:7: error: cannot access Service
    Service service = new Service();
    ^
    bad source file: ./service/Service.java
    file does not contain class service.Service
Please remove or make sure it appears in the correct subdirectory of the sourcepath.
1 error

第四行,bad source file: ./service/Service.java表明他在编译Client.java文件时,通过import找到了该文件,说明import是通过文件结构去找到的。

但是他又说file does not contain class service.Service,说明在这个Service.java中找不到对应的Service类。

为什么呢?因为我的包名是service.something,但是他要找的是service。

所以包的作用是和类的名字组成完整的类名,这里的Service类的全名应该是service.something.Service。但是他去找到的是service.Service。所以它找不到。

那是不是将import改为import service.something.*;就可以呢?

不行,前面说了,import是根据目录结构去找到的,根本就没有service/something这个目录结构,所以不可能找到。

client/Client.java:3: error: package service.something does not exist
import service.something.*;
^
client/Client.java:7: error: cannot find symbol
    Service service = new Service();
    ^
    symbol:   class Service
    location: class Client
    client/Client.java:7: error: cannot find symbol
    Service service = new Service();
                          ^
    symbol:   class Service
    location: class Client

你看,第一行:client/Client.java:3: error: package service.something does not exist

回过头来想,这也是为什么,有时候,当同时导入两个不同包中名字相同的类时,其中有一个要用完全限定名。因为类的名字本来就是由包的名字和类的自身名字组成。

总而言之,这堂课的收获还是挺大的,重点要掌握上面提到的两个概念。

课上本来还有一点关于,打jar包和如何引用jar包的内容,以及通过java -d命令,将class文件编译到指定的目录下。

这里我就不多说了,万变不离其宗。

javac -classpath “.:service.jar” client/Client.java //linux下面是冒号,windows下面是分号

java -classpath “.:service.jar” client.Client

只不过要注意一点的是,打jar包时,一定要按照package定义的目录结构去打。因为在将jar包加入到classpath时和将一个目录下的class文件加入是一样的,相对路径。

java -d out client/Client.java,会将class文件编译到out目录下,且内部目录结构与包的结构一致。