Java EE简介

Java EE简介

第一节 背景

  从1997年的1.1版本开始,Java平台被称为JDK,但到了版本1.2,很明显JDK和平台不再是同一样技术。从1998 年底的1.2版本开始,Java技术栈被分割为以下关键部分:

  • Java是一门包含了严格和强类型语法的语言,你现在已经熟悉它了。
  • Java2平台标准版本,也被称为J2SE,指的是平台以及java.lang和java.io包中包含的类。它是构建Java应用程序的基础。
  • Java虚拟机或JVM是一个可以运行编译后Java代码的软件虚拟机。因为被编译过的Java代码只是字节码,JVM将在运行代码之前,把字节码编译成机器码(通常被称作即时编译器或JIT编译器)。JVM还负责管理内存,从而实现了应用程序代码的简化。
  • Java开发工具包或JDK曾经并且现在也仍然是Java开发者创建应用程序所需的软件。它包含了Java语言编译器、文档生成器、与本地代码协作的工具和用于调试平台类的Java源代码。
  • Java运行时环境或JRE曾经并且现在也仍然是终端用户用于运行编译后Java应用程序的软件。它包含了JVM但不含任何JDK中的开发工具。不过JDK中确实也包含了一个JRE。

  这5个组件曾经都只是规范,而不是实现,任何公司都可以创建自己的Java技术栈实现。尽管Sun提供了Java、J2SE、JVM、JDK和JRE的标准实现,但IBM、Oracle和Apple仍然创建了包含不同特性的实现。随着多年的发展,许多语言都可以被编译为Java字节码(在某些情况下可以被编译为机器码),并运行在JVM上。其中最引人注目的有Clojure(Lisp方言)、Groovy、JRuby(基于Java的Ruby实现)、Jython(基于Java的Python实现)、Rhino和Scala。

  随着Internet的发展和Web应用程序的流行,Sun公司已经意识到应用程序开发对高级开发工具的需求。1998年,就在J2SE 1.2发布之前,Sun宣布它正在开发一个称为Java专业版本或JPE的产品。同时它还研发了一门称为Servlet的技术,这是一个能够处理HTTP请求的小型应用程序。在1997年,Java Servlets 1.0与Java Web Server一起发布,因为该服务器缺少许多Java社区需要的特性,所以它并未流行起来。

  Servlet和JPE在经历过几次内部迭代过程之后,Sun于1999年12月12日发布了Java 2平台的企业版(或J2EE),版本为1.2。版本号对应着当时的Java和J2SE版本,该规范包括:

  • Servlets 2.2
  • JDBC Extension API 2.0
  • Java Naming and Directory Interface (JNDI) 1.0
  • JavaServer Pages (JSP) 1.2
  • Enterprise JavaBeans (EJB) 1.1
  • Java Message Service (JMS) 1.0
  • Java Transaction API (JTA) 1.0
  • JavaMail API 1.1
  • JavaBeans Activation Framework (JAF) 1.0.

  J2EE 1.3在2001年9月发布,Java和J2SE 1.3的发布稍晚一点,但在Java/J2SE 1.4发布之前。它的大多数组件都进行了小的升级,并且也添加了一些新的特性。下面的技术也加入了J2EE规范,并且它们的实现也得到了扩展和升级:

  • Java API for XML Processing (JAXP) 1.1
  • JavaServer Pages Standard Tag Library (JSTL) 1.0
  • J2EE Connector Architecture 1.0
  • Java Authentication and Authorization Service (JAAS) 1.0

  J2EE 1.4代表着Java平台企业版的一次极大飞跃。在2003年11月发布时(大约在Java/J2SE 5.0 发布一年之前,Java/J2SE 1.4发布两年之后),它包含了Servlets 2.4和JSP 2.0。在该版本中,JDBC Extension API、JNDI和JAAS规范被移除了,因为它们被认为是Java的必需部分,被移入Java/J2SE 1.4。该版本还代表着J2EE组件被分割成了几个更高级别的分类:

  • Web服务技术:包括JAXP 1.2和J2EE 1.1中的新Web服务、Java API for XML-based RPC (JAX-RPC) 1.1、Java API for XML Registries (JAXR) 1.0
  • Web应用程序技术: 包括Servlet、JSP和JSTL 1.1组件,还有新的Java Server Faces (JSF) 1.1
  • 企业级应用程序技术:包括EJB 2.1、Connector Architecture 1.5、JMS 1.1、JTA、JavaMail 1.3和JAF
  • 管理和安全技术:包括Java Authorization Service Provider Contract for Containers (JACC) 1.0、Java Management Extensions (JMX) 1.2、Enterprise Edition Management API 1.0和Enterprise Edition Deployment API 1.1

  2004年,Sun公司决定使用Java平台标准版取代Java 2平台标准版,使用全新的缩写名字Java SE。2006年发布的Java SE 6开始正式使用该名称,直到今天名字和版本的命名方式都未再改变。Java SE 6实际对应着1.6,Java SE 7实际对应着1.7,Java SE 8实际对应着1.8。J2EE也采用了新的命名和版本约定,现在使用的名字为Java SE。

  2006年5月发布的Java EE 5再次发生了变化,它包含了众多修改和改进,直到今天它仍然是应用程序最广泛的一 个Java EE版本。它包括以下修改和补充:

  • JAXP和JMX被移到了J2SE 5.0 中,不再包含在Java EE 5中。
  • Java API for XML-based Web Services (JAX-WS) 2.0、 Java Architecture for XML Binding (JAXB) 2.0、Web Service Metadata for the Java Platform 2.0、SOAP with Attachments API for Java (SAAJ) 1.2以及Streaming API for XML (StAX) 1.0被添加到了Web服务技术中。
  • Java Persistence API (JPA) 1.0和Common Annotations API 1.0被添加到了企业级应用程序技术中。

  2006年12月Java SE 6的发布标志着Java SE持续了大概5年的发展停滞期。在这段时期内,许多Java社区都感到沮丧甚至是生气。Sun公司继续承诺在Java SE 7中添加新的语言特性和API,但计划推迟了一年又一年。与此同时其他技术,例如C#语言和.NET平台,赶上并超越了Java语言的特性和平台API,许多人都猜测Java是否已经到了生命的终结。更糟的是,Java EE也进入了发展停滞期直到2009年,距离Java EE 5发布已经过去了三年多的时间。不过,这并不是终结。Java EE 6的开发在2009年初重新开始,并在2009年12月发布,距离Java EE 5的发布已经过去了3年零7个月,距离Java SE 6的发布几乎接近3年。

  此时,Java企业版已经变得极其庞大:

  • SAAJ、StAX和JAF被移到了Java SE 6中。
  • Java API for RESTful Web Services (JAX-RS) 1.1和Java APIs for XML Messaging (JAXM) 1.3规范被添加到 Web服务技术中。
  • Java Unified Expression Language (JUEL或称为EL) 2.0被添加到Web应用程序技术中。
  • Management and Security Technologies中添加了Java Authentication Service Provider Interface for Containers (JASPIC) 1.0。
  • 企业级应用程序技术增加了大量的新特性,包括Contexts and Dependency Injection for Java (CDI) 1.0、Dependency Injection for Java 1.0、Bean Validation 1.0、Managed Beans 1.0和 Interceptors 1.1,还对它所有其他的组件做了更新。

  Java EE 6 还代表着 Java EE架构在两个技术上的重大转折点:

  • 该版本引入了基于注解的配置和编程式应用程序配置,是对已经使用超过10年的传统XML配置的补充。
  • 该版本标志着Java EE Web Profile的引入。

  由于Java EE已经变得如此庞大(维护和更新公认的实现变得相当困难),Web Profile验证程序为认证只包含完整Java EE平台一个子集的Java EE实现提供了机会。该子集包含了对于大量应用程序都十分关键的特性,排除了一些只被少数应用程序使用的规范。对于Java EE 6来说:

  • 所有的Web服务或者管理和安全组件都不是Java EE Web Profile的一部分。
  • 该Web Profile包含了Web应用程序技术和企业级应用程序技术的所有内容,除了Java EE Connector Architecture、JMS和JavaMail。

  就在这5年的发展停滞期中,Oracle公司于2010年1月收购了Sun公司。除了Java SE发展的停滞,该事件为Java社区带来了新的担忧。Oracle并不愿意或积极与开源项目合作,许多人担心购买了Sun的Oracle会关闭Java。不过,事实并不是这样的。在初期,Oracle开始重组Java团队,创建与开源社区的沟通渠道,并发布了未来Java SE和EE版本的规划蓝图,这比Sun的承诺要更加实际。首先完成的是Java SE 7,Oracle在2011年6月按时发布,距离Java SE 6的发布几乎已经过去了5年。第二个Java EE发展停滞期在2013年6月结束,此时发布了Java EE 7,距离Java EE 6的发布已经过去了3年零7个月。Oracle现在表示Java的发展已经步入正轨,以后每两年将会同时发布两个平台的新版本(轮流发布),让我们拭目以待。

1.1 Java SE 7的新特性

  Java SE 7增加了对动态语言64位压缩指针(用于改善64位JVM的性能)的支持。它还添加了几种新的语言特性,可以使开发Java应用程序更容易。可能菱形操作符(<>)就是其中最有用的改进之一——泛型实例化的简写。

  在Java 7之前,泛型类型的变量声明和变量赋值都必须包含泛型参数。例如,下面是一个非常复杂的 java.util.Map变量的声明和赋值:

1
Map<String, Map<String, Map<Integer, List<MyBean>>>> map = newHashtable<String, Map<String, Map<Integer, List<MyBean>>>>(); 

  当然,该声明语句中包含了大量的冗余信息。将任何不是Map<String, Map<String, Map<Integer, List>>>类型的对象赋给该变量都是非法的,那么为什么还需要再次指定所有的类型参数呢?使用了Java 7菱形操作符之后,上述声明和赋值语句将变得非常简单。编译器将会为实例化生成的java.util.Hashtable推断出它的类型参数。

1
Map<String, Map<String, Map<Integer, List<MyBean>>>> map = new Hashtable<>(); 

  在Java 7之前Java中的另一个常见问题是:使用try-catch-finally块管理可关闭的资源。尤其是下面这样有点讨厌的JDBC代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Connection connection = null; 
PreparedStatement statement = null;
ResultSetresultSet = null;
try {
connection = dataSource.getConnection();
statement = connection.prepareStatement(...);
//set up statement
resultSet = statement.executeQuery();
// do something with result set
} catch(SQLException e) {
// do something with exception
} finally {
if(resultSet != null) {
try {
resultSet.close();
}catch(SQLException ignore) {

}
}
if(statement != null) {
try {
statement.close();
}catch(SQLException ignore) {

}
}
if(connection != null && !connection.isClosed()) {
try {
connection.close();
}catch(SQLException ignore) {

}
}
}

  Java 7的try-with-resource极大地简化了这个任务。任何实现了java.lang.AutoCloseable的类都可用于try-with-resources结构中。JDBC Connection、PreparedStatement和ResultSet接口都继承了这个接口。下面的例子使用了try-with-resources结构,在try关键字后面的圆括号中声明的资源,将会在隐式的finally块中自动关闭。任何在这段清理过程中抛出的异常将被添加到现有异常的抑制异常中,或者如果之前未有任何异常发生,那么该异常将在所有的资源都关闭后抛出。

1
2
3
4
5
6
7
8
9
try(Connection connection = dataSource.getConnection(); 
PreparedStatement statement = connection.prepareStatement(...)) {
//set up statement
try(ResultSetresultSet = statement.executeQuery()) {
//do something with resul set
}
} catch(SQLException e) {
//do something with exception
}

  对于try-catch-finally的另一处改进是添加了multi-catch(捕捉多个异常)。在Java 7中可以在单个catch块中同时捕捉多个异常,使用单个竖线隔开异常类型即可。

1
2
3
4
5
try { 
//do something
} catch(MyException | YourException e) {
//handle these exceptions the same way
}

  需要注意的是,不能同时捕捉多个相互之间有继承关系的异常。例如,下面的代码是不可行的,因为FileNotFoundException继承了IOException:

1
2
3
4
5
try { 
//do something
} catch(IOException | FileNotFoundException e) {
//handle these exceptions the same way
}

  当然,这可以被认为是一个常识性的问题。在这种情况下,只需要捕捉IOException即可,这样两种异常类型都可以被捕捉到。

  Java 7的一些其他语言特性包括字节码/整数的二进制字面量(可以将字面量1928写作0b11110001000)以及在数字字面量中使用下划线(如果愿意的话,可将相同的字面量1928写作1_928和ob111_1000_1000)。另外,终于可以将字符串用作switch的参数了。

1.3 Java EE 7的新特性

  Java EE 7在2013年6月12日发布,它包含许多变动和新特性。Java EE 7中的变动有:

  • JAXB被添加到了Java SE 7 中,并且不再包含在Java EE中。
  • Batch Applications for the Java Platform 1.0和Concurrency Utilities for Java EE 1.0被添加到了企业级应用程序技术中。
  • Web应用程序技术中添加了Java API for WebSockets 1.0和Java API for JSON Processing 1.0。
  • Java Unified Expression Language得到了极大的扩展,其中包括lambda表达式和对Java SE 8 Collections Stream API的模拟。
  • Web Profile得到了少许扩展,其中添加了在通用Web应用程序中使用较多的一些规范:JAX-RS、Java API for WebSockets和 Java API for JSON Processing。

1.2 Java SE 8的新特性

  Java8的新特性


第二节 基本的Web应用程序结构

2.1 Servlet、过滤器、监听器和JSP

  Servlet是用于接受和响应HTTP请求的Java类。几乎发送到应用程序中的所有请求都将经过某种类型Servlet的处理,除了错误的或被其他组件拦截的请求。

  过滤器就是这样一种组件,可以拦截发送给Servlet的请求。通过使用过滤器可以满足各种需求,包括数据格式化、对返回的数据进行压缩、认证和授权。

  Java EE Web应用程序支持各种不同类型的监听器。这些监听器可以通知代码多种事件,例如应用程序启动、应用程序关闭、HTTP会话创建和会话销毁。

  Java EE工具中最强大的一个就是JavaServer Pages技术或JSP。通过使用JSP可以为Web应用程序创建动态的、基于HTML的图形用户界面,不需要手动向OutputStream或PrintWriter中输入HTML的字符串。JSP技术包含了许多不同的内容,包括JavaServer Pages Standard Tag Library、Java Unified Expression Language、自定义标签、国际化和本地化。

为什么不建议深入学习JSP?

  • 前后端职能分离,各司其职,界面不应再由后端工程师去协助实现
  • JSP作为一个后端渲染技术,会使动静资源耦合,无法做到有效的动静分离
  • JSP依赖于支持Java的Web服务器
  • JSP效率很低,在前端的发展潮流中已被淘汰

  虽然JSP已经不再流行,但其技术原理还是需要我们学习掌握的,当然其重要性就没那么高了,可以往自己的学习队列末尾移动。

2.2 目录结构和WAR文件

  标准Java EE Web应用程序将作为WAR文件未归档的Web应用程序目录进行部署。JAR文件只是一个简单的ZIP格式归档文件,其中包含了可被JVM识别的标准目录结构。没有专门的JAR文件格式,任何ZIP归档应用程序都可以创建和读取JAR文件。Web应用程序归档或WAR是Java EE Web应用程序对应的归档文件。

  无论是归档文件还是未归档文件,它们的目录结构约定都是相同的。如同JAR文件一样,该结构包含了类和其他应用程序资源,但这些类并未像JAR文件一样存储在应用程序根目录的相对路径上。相反,类文件都存储在/WEB-INF/classes中WEB-INF目录存储了一些包含了信息和指令的文件,Java EE Web应用程序服务器使用它们决定如何部署和运行应用程序。它的classes目录被用作包的根目录。所有编译后的应用程序类文件和其他资源都被存储在该目录中。

  不同于标准的JAR文件,WAR文件可以包含应用程序所依赖的JAR文件,它们被存储在/WEB-INF/lib中。JAR文件中所有在该目录中的类对于在应用程序类路径上的应用程序都是可用的。目录/WEB-INF/tags和/WEB-INF/tld分别用于存储 JSP标签文件和标签库描述符。

2.3 部署描述符

  部署描述符是用于描述Web应用程序的元数据,并为Java EE Web应用程序服务器部署和运行Web应用程序提供指令。从传统上来说,所有元数据都来自于部署描述符文件/WEB-INF/web.xml。该文件通常包含Servlet、监听器和过滤器的定义,以及HTTP会话、JSP和应用程序的配置选项。

2.4 类加载器架构

  在使用Java EE Web应用程序时,有必要理解类加载器(ClassLoader)架构,因为它不同于你所熟悉的标准Java SE应用程序。在典型的应用程序中,Java SE平台中的java.*类将被加载到特定的根类加载器中,并且不能被覆盖。这是一种安全的方式,它阻止了恶意代码的执行,例如恶意代码可能会替换String类,或者重定义Boolean.TRUE和Boolean.FALSE。

  在根类加载器之后是扩展类加载器,它将加载JRE安装目录中的扩展JAR。最后,应用程序Class Loader将加载应用程序中的所有其他类。这组成了类加载器的层次,根类加载器是所有类加载器最早的祖先。当低级别类加载器申请加载一个类时,它总是首先将该任务委托给它的父类加载器。继续向上委托直至根类加载器确认成功。如果它的父类加载器未能找到该类,那么当前的类加载器将尝试从自己的JAR文件和目录中加载该类

  这种类加载的方法被称为双亲优先类加载委托模式,尽管这种方法适用于许多类型的应用程序,但它并不完全适用于Java EE Web应用程序。运行Java EE Web应用程序的服务器通常相当复杂,许多供应商都可以提供其实现。服务器可能使用了与个人应用程序使用的相同的第三方库,但它们的版本可能相互冲突。另外,不同的Web应用程序也可能使用了同一第三方库的冲突版本,导致更多的问题为了解决这些问题,就需要使用子女优先类加载委托模式

  在Java EE Web应用程序服务器中,每个Web应用程序都被分配了一个自由的相互隔离的类加载器,它们都继承自公共的服务器类加载器。通过隔离不同的应用程序,它们不能访问相互的类。这不仅消除了类冲突的风险,还是一种阻止Web应用程序被其他Web应用程序干扰或伤害的安全方式。另外,Web应用程序类加载器通常会在自己无法加载某个类的时候,请求它的父类加载器帮助加载。通过这种方式,类加载的任务会在最后而不是首先委托给它的父类,Web应用程序中的类和库会被优先使用,而不是服务器提供的版本优先使用。为了维持绑定的Java SE类的安全状态,Web应用程序类加载器仍然会在尝试加载任何类之前与根类加载器确认。尽管几乎在所有的情况下,这种委托模式都更适用于Web应用程序,但仍然有它不适用的情况。出于这个原因,兼容Java EE的服务器通常会提供修改委托模式的方法,从父类最后改为父类首先。


参考博客和文章书籍等:

《JavaWeb高级编程——涵盖WebSockets、Spring Framework、JPA Hibernate和Spring Security》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容