技术开发 频道

.NET开发人员的J2EE基础

  要理解 J2EE 应用程序体系结构的基本概念,第一步是确定如何将现有的 ASP.NET 应用程序移植到一个基于 J2EE 的模型,或者确定如何从头编写一个 J2EE 应用程序。我们将考察几个 ASP.NET 模型,以及可能如何将它们转换为根据 J2EE 组件构建的模型。您将看到简单的“意大利面条式的代码”如何演进为一个更优雅、可复用和可扩展的环境。在研究一些代码例子之前,让我们首先进一步考察一下 Web 应用程序中使用得最多的两个 J2EE 组件:Java Servlets 和 JavaServer Pages 技术。

  Java Servlets 编程:基础

  编写 Java Servlets 是为了以编程方式控制来自浏览器的 URL 请求。典型的 servlet 调用类似如下:

  客户端向 Web 服务器发出请求,同时指定一个 servlet 作为 URL 的一部分——例如:

     <FORM action="/myWebApp/LoginServlet" method="POST">

  Web 服务器将该请求转发给应用服务器,后者查找 servlet 类实例的位置。

  然后 Web 容器调用该 servlet。(该 servlet 的单个实例将加载到内存中,每个请求在不同的线程中调用该单个实例。)

  注意 HTML 表单中 servlet 的 URL包括该 servlet 的名称和一个称为 上下文根(context root) 的前缀(在上面的例子中是 /myWebApp )。上下文根大致等价于一个 IIS 虚拟目录。

  图 4 描述了这些步骤。

  图 4. Servlet 调用

  Servlet 扩展了 HttpServlet 类。您可以根据需要重载 HttpServlet 的以下方法:

  init() :在应用服务器加载某个 servlet 时调用

  destroy() :在应用服务器卸载某个 servlet 时调用

  doGet() :在 servlet 通过 HTTP GET 被调用时调用

  doPost() :在 servlet 通过 HTTP POST 被调用时调用

  Servlet 的编写涉及编写代码来处理 HTTP 请求、处理任何参数,以及直接返回 HTML 内容或委托其他资源(比如 JSP 页面)来处理响应。不推荐从 servlet 直接返回 HTML 内容,因为在 Java 类中管理 HTML 代码很麻烦。

  当应用服务器调用 doGet() 或 doPost() 方法时,将有两个对象作为参数被传递:

  HttpServletRequest 允许访问任何请求参数和导致产生该 servlet 调用的 HTTP 请求中的其他信息。

  HttpServletResponse 充当返回客户端的通信渠道,允许 servlet 直接返回内容或返回其他 HTTP 返回代码(错误、重定向,等等)。

  清单 20 和 21 显示了两个 Hello World servlet。清单 20 直接返回内容,而清单 21 使用 JSP 页面来返回内容。

  清单 20. HelloWorld servlet: 直接返回内容

1  public class HelloWorldServlet extends HttpServlet {
2
3   /**
4
5   * Handles all HTTP POST requests
6
7   *
8
9   * @param request Object that encapsulates the request to the servlet
10
11   * @param response Object that encapsulates the response from the servlet
12
13   */
14
15   public void doPost(
16
17   javax.servlet.http.HttpServletRequest request,
18
19   javax.servlet.http.HttpServletResponse response)
20
21   throws ServletException, IOException {
22
23   try {
24
25   PrintWriter out = response.getWriter();
26
27   out.println("");
28
29   out.println("Hello World");
30
31   out.println("");
32
33   } catch (Throwable t) {
34
35   ...
36
37   }
38
39   }
40
41   }
42
43

  注意您从响应对象中得到了一个 PrintWriter ,并将 HTML 一次一行地输出到客户端。

  清单 21. HelloWorld servlet: 使用 JSP 页面来返回内容

1 public class HelloWorldServlet extends HttpServlet {
2
3   /**
4
5   * Handles all HTTP POST requests
6
7   *
8
9   * @param request Object that encapsulates the request to the servlet
10
11   * @param response Object that encapsulates the response from the servlet
12
13   */
14
15   public void doPost(
16
17   javax.servlet.http.HttpServletRequest request,
18
19   javax.servlet.http.HttpServletResponse response)
20
21   throws ServletException, IOException {
22
23   try {
24
25   RequestDispatcher rd = getServletContext().getRequestDispatcher("helloworld.jsp");
26
27   rd.forward(request, response);
28
29   } catch (Throwable t) {
30
31   ...
32
33   }
34
35   }
36
37   }
38
39

  RequestDispatcher 是您想要向其转发请求的资源的包装器。注意要同时包括原始请求和响应对象,以便目标资源能够访问它们。从 getServletContext() 返回的 ServletContext 允许 servlet 与底层应用服务器通信,以获得一个 RequestDispatcher 。 注意所有 servlet 都能够通过 getServletContext() 方法访问它们的 ServletContext 。

  使用 JavaServer Pages 技术来编程:基础

  JSP 技术使您能够使用 Java 语言进行服务器端编程。JSP 页面是包含 HTML 和 Java 代码的复合页面,其中的 Java 代码由应用服务器在将页面返回客户端之前处理。应用服务器处理该嵌入代码,以便在将页面返回客户端之前生成静态内容。像 .aspx 文件一样,JSP 文件通常看起来像具有一些附加标签和 Java 代码片断的 HTML。

  J2EE 应用服务器在 JSP 页面第一次被请求时,将每个JSP 页面转换为一个特殊的 servlet。该 servlet 将编译并加载到内存中。只要该 JSP 源代码没有修改,它就一直为针对该页面的请求提供服务。当源代码修改时,该过程又重来一次,这样就产生了一个新版本的 servlet。

  可以在 JSP 页面中使用几个特殊的 JSP 标签,用户还可以定义他们自己开发的标签的行为。这些自定义的标签大致等价于 ASP.NET 中的自定义组件。还可以向 JSP 页面的不同部分添加一些 Java 代码块。J2EE 运行时环境使得许多变量(称为 隐含变量)对您的这些 Java 代码片断可用。隐含变量的例子包括:

  request:与页面的特定调用相关联的 HttpServletRequest

  response:与页面的特定调用相关联的 HttpServletResponse

  out:与 HttpServletResponse 相关联的 PrintWriter

  清单 22 显示了 JSP 页面的一个例子,其中包含 HTML 和 Java 代码的混合。 标签之间的 Java 代码部分称为 scriptlet。

  清单 22. HelloWorld servlet:使用 JSP 页面来返回内容

1 <html>
2 <title>JSP page example</title>
3 The date is:
4 <%
5 Date date = new Date();
6 out.println(date);
7 %>
8 Some more HTML
9 The value of <em>e</em> is:
10 <%
11 double e = Math.exp(1.0);
12 out.println(e);
13 %>
14 Yet even  more HTML
15 The value of PI is:
16 <%
17 double pi = 22.0/7.0;
18 out.println(pi);
19 %>
20 </html>
21

  注意其中使用了 JSP 隐含变量 out 来将内容写回客户端,还要注意 HTML 和 Java 代码的交织。

  一个例子场景

  为了说明各种体系结构选项,我们将使用一个简单的用户登录场景,它包含:

  一个具有 HTML 表单的登录页面,它从用户那里收集用户名和密码

  验证用户凭据(可能使用数据库)并重定向站点主页的应用逻辑

  这个简单的例子允许我们描述各种各样的应用体系结构。

  “意大利面条式”代码

  在 ASP.NET 方,“意大利面条式”代码方法使用单个 .aspx 文件来同时包含应用逻辑和 HTML 表单。不存在代码分离(code-behind)。(不推荐对现实中的例子采用这种方法,因为这样所有表示逻辑和应用逻辑都将是单个文件,从而阻止了您复用用于验证用户凭据的代码。)该代码的轮廓看起来类似清单 23 中所示的代码。

  清单 23. ASP.NET 中的“意大利面条式”代码

1 <html>
2 <head>
3 <title>Login example</title>
4 <script language="C#" runat=server>
5 ...
6 private void btnLogin_Click(object sender, System.EventArgs e)
7 {
8     // Get the form field values
9     ...
10     // Validate the username and password
11     if (ValidateUser(username, password) {
12       Response.Redirect("mainpage.aspx");
13     }
14 }
15 private bool ValidateUser(string userName, string password) {
16       ...
17 }
18 ...
19 </script>
20 </head>
21 <body>
22     <form runat="server">
23     <!-- login form fields go here -->
24     </form>
25 </body>
26 </html>
27

  正如清单 24 所示,您可以在 J2EE 中采用相同的方法,使用单个 JSP 页面来同时包含表单和应用逻辑。

  清单 24. J2EE 中的“意大利面条式”代码

1 <html>
2 <head>
3 <title>Login example</title>
4 <%!
5 private boolean validateUser(String userName, String password) {
6       ...
7 }
8 %>
9 ...
10 </head>
11 <body>
12 <%
13     if (request.getMethod().equals("GET") ) { %>
14     <form method="POST" target="login.jsp">
15     <!-- login form fields go here -->
16     </form>  <% }
17     else  {
18          String userName = request.getParameter("username");
19          String password = request.getParameter("password");
20           if (validateUser(userName, password)) {
21              response.sendRedirect("mainpage.jsp");
22          }
23          ...
24    } %>
25 </body>
26 </html>
27

  JSP 模型不是事件驱动的,因此您需要检查表单是否被发送回去了,方法是检查该请求,并在它不是 POST 请求时包括表单的 HTML。如果它是 POST 请求,您将使用 JSP 中声明的一个方法来验证登录。注意 )对方法无效。还要注意如何使用 if/then/else 编程结构来容易地包括或排斥较大的 HTML 块。与在 ASP.NET 例子中一样,不推荐将此方法用于 J2EE 开发。表示代码(HTML)和应用逻辑的混合仅允许很少的复用,并且使得代码难于维护。

  改进的“意大利面条式”代码

  在 ASP.NET 方,一种更好的方法建立在前一个例子基础上,不过除了 .aspx 文件外,它还使用了代码分离文件。事件处理代码和用户验证代码转移到了一个代码分离文件中,原先的 .aspx 文件只剩下 HTML 表单和其他 HTML 元素。这相对于前一种方法来说当然是一种进步;表示代码更清楚地分离了,这样可以让一个 HTML 设计师负责表示,让一个程序员负责代码分离文件。

  如果使用标准 J2EE 组件,您就无法使用 ASP.NET 的事件驱动的代码分离文件方法。J2EE 端的一种更好方法是将应用逻辑转移到一个 Java Servlet,从而使 JSP 页面仅限于使用 HTML 组件。就像 ASP.NET 中的代码分离文件方法相对于“意大利面条式”代码是一种改进一样,这同样也是一种改进。清单 25 显示了如何通过把应用逻辑放到 servlet 中来简化 JSP 页面。

  清单 25. J2EE 中改进的“意大利面条式”代码 JSP page

1 <html>
2 <head>
3 <title>Login example</title>
4 ...
5 </head>
6 <body>
7 <form method="POST" target="LoginServlet">
8 <!-- login form fields go here -->
9 </form>
10 </body>
11 </html>
12

  清单 26 显示了 servlet 中的验证代码和导航逻辑。

  清单 26. J2EE 中改进的“意大利面条式”代码 Java Servlet

1 public class LoginServlet extends HttpServlet {
2
3   /**
4
5   * Handles all HTTP POST requests
6
7   *
8
9   * @param request Object that encapsulates the request to the servlet
10
11   * @param response Object that encapsulates the response from the servlet
12
13   */
14
15   public void doPost(
16
17   javax.servlet.http.HttpServletRequest request,
18
19   javax.servlet.http.HttpServletResponse response)
20
21   throws ServletException, IOException {
22
23   try {
24
25   String userName = request.getParameter("username");
26
27   String password = request.getParameter("password");
28
29   if (validateUser(userName, password)) {
30
31   response.sendRedirect("mainpage.jsp");
32
33   }
34
35   ...
36
37   } catch (Throwable t) {
38
39   ...
40
41   }
42
43   }
44
45   private boolean validateUser(String userName, String password) {
46
47   ...
48
49   }
50
51   }
52
53

  清单 26 中的 servlet 是表单提交的目标,并且充当一个控制器――处理用户输入并基于该输入调出适当的页面。注意 HttpServlet 父类允许您通过提供可重载的方法( doGet() 和 doPost() ),同时处理 GET 和 POST 请求。

  这种方法的主要缺点在于,凭据验证代码(它很可能要访问数据库)对 ASP.NET 例子来说是代码分离文件的一部分,对 J2EE 例子来说是 servlet 的一部分。如果不同的页面需要使用这个逻辑,您就必须重复它。重复的代码更难于维护和更易于出错,因为您必须跟踪多个副本。

  模型-视图-控制器(MVC)方法

  下面我们将展示如何对这个例子使用更纯的 MVC 方法。在 ASP.NET 端,这体现为将凭据验证逻辑转移到一个单独的类库中,然后您可以在代码分离文件中访问该类库。在 J2EE 端,凭据验证代码将转移到一个单独的类中,然后从 servlet 访问该类。清单 27 中的代码片断显示了该 servlet 看起来的样子。

  清单 27. J2EE 中的 MVC Java Servlet

1 1 import com.ibm.businessobjects.UserValidation
2 2
3 3   public class LoginServlet extends HttpServlet {
4 4
5 5   /**
6 6
7 7   * Handles all HTTP POST requests
8 8
9 9   *
10 10
11 11   * @param request Object that encapsulates the request to the servlet
12 12
13 13   * @param response Object that encapsulates the response from the servlet
14 14
15 15   */
16 16
17 17   public void doPost(
18 18
19 19   javax.servlet.http.HttpServletRequest request,
20 20
21 21   javax.servlet.http.HttpServletResponse response)
22 22
23 23   throws ServletException, IOException {
24 24
25 25   try {
26 26
27 27   String userName = request.getParameter("username");
28 28
29 29   String password = request.getParameter("password");
30 30
31 31   UserValidation user = new UserValidation();
32 32
33 33   if (user.validate(userName, password)) {
34 34
35 35   response.sendRedirect("mainpage.jsp");
36 36
37 37   }
38 38
39 39   ...
40 40
41 41   } catch (Throwable t) {
42 42
43 43   ...
44 44
45 45   }
46 46
47 47   }
48 48
49 49   }
50 50
51 51

  清单 28 显示了在一个单独的类中的凭据验证代码。

  清单 28. J2EE 中的 MVC User-validation class

1 package com.ibm.businessobjects;
2
3   public class UserValidation {
4
5   public boolean validate(String username, String password) {
6
7   ...
8
9   }
10
11   }
12
13

  凭据验证 servlet 所使用的类中进行。现在该 servlet 不必知道关于验证如何进行的信息。只要您保持相同的公开接口不变,就可以随意更改 UserValidation 类,不再需要改变 servlet 代码。

  其他 J2EE 组件

  前面的例子展示了如何将业务逻辑分离到一个或一组单独的类中。这样允许您以各种不同的方式实现业务逻辑,而不会影响 servlet 和 JSP 页面。与使用一个需要访问数据库(使用 JDBC)的业务对象不同,您可以通过各种不同的方式实现这个逻辑。例如:

  使用 JCA 来访问诸如大型机等遗留系统:JCA 是一个 J2EE API,它允许 J2EE 应用程序使用标准接口来与遗留系统(比如 CICS 或者 IMS)交互。

  使用 Web 服务客户端 API 来访问 Web 服务:这种方法涉及使用 Apache Axis 或其他某种 Java Web 服务客户端 API 来访问 Web 服务。

  使用 JMS 将消息作为验证请求来发送:这种方法涉及使用标准 API 来访问 JMS 相容的消息中间件(比如 WebSphere MQ)。

  使用 EJB 技术来实现验证逻辑:这种方法涉及使用 EJB 组件(而不是简单的 Java 类)来实现业务逻辑。EJB 组件能够使用应用服务器中的服务来管理(数据库或其他)事务,管理方法级的安全,以及提供群集和高速缓存。将此类任务委托给应用服务器,您可以把精力集中在业务逻辑上,而不必担心系统级的问题。

0
相关文章