33、基于Java的Web应用开发:Servlet与JSP实践

基于Java的Web应用开发:Servlet与JSP实践

1. JSP与Servlet基础

JSP(JavaServer Pages)与Servlet都是Java用于开发Web应用的重要技术。JSP本质上是嵌入了Java代码的HTML文档,而Servlet则是嵌入了HTML的Java代码,二者各有优势。例如,JSP对于网页设计师来说更容易编辑,而Servlet则更适合处理业务逻辑。

以下是一个简单的JSP示例 hello.jsp ,它使用 Rete 对象打印 “Hello World from Jess!” 消息:

<html>
    <%@ page import="jess.*" %>
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <H1><%
            Rete engine = new Rete();
            engine.addOutputRouter("page", out);
            engine.executeCommand("(printout page " +
            "\"Hello World from Jess!\" crlf)");
        %></H1>
    </body>
</html>

在这个示例中, out 变量等同于 Hello Servlet 中 response.getWriter() 的返回值,并且所有JSP都可以自动使用它。

要在Tomcat中安装JSP,只需将源代码复制到Web应用目录的顶层。Tomcat会识别JSP扩展名,首次请求该文件时,它会被编译成Servlet并执行。

2. Servlet与JSP协作

Servlet和JSP可以轻松协作。通常,Servlet执行计算,将结果存储在 HttpServletRequest 对象中,然后将增强后的请求对象转发给JSP。JSP从 HttpServletRequest 中获取所需数据并生成HTML页面。

以下是一个Servlet将 Rete 对象传递给JSP的示例:

Rete engine = new Rete();
request.setAttribute("engine", engine);
ServletContext servletContext = getServletContext();
RequestDispatcher dispatcher =
    servletContext.getRequestDispatcher("/hello.jsp");
dispatcher.forward(request, response);
return;

对应的JSP代码如下:

<html>
    <%@ page import="jess.*" %>
    <jsp:useBean id="engine" class="jess.Rete" scope="request"/>
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <H1><%
            engine.addOutputRouter("page", out);
            engine.executeCommand("(printout page " +
            "\"Hello World from Jess!\" crlf)");
        %></H1>
    </body>
</html>

jsp:useBean 标签会自动从 HttpServletRequest 中提取 Rete 对象,并将其分配给正确类型的变量。

为了方便调用,可以将JSP转发机制封装到一个方法中:

protected void dispatch(HttpServletRequest request,
                        HttpServletResponse response,
                        String page)
throws IOException, ServletException {
    ServletContext servletContext = getServletContext();
    RequestDispatcher dispatcher =
        servletContext.getRequestDispatcher(page);
    dispatcher.forward(request, response);
}

使用 dispatch 方法后,前面的Servlet代码可以简化为:

Rete engine = new Rete();
request.setAttribute("engine", engine);
dispatch(request, response, "/hello.jsp");
return;
3. 应用架构概述

在开始实现之前,我们先重新审视一下应用架构。以下是构成推荐代理应用的各个文件及其描述:
| 源文件 | 描述 |
|------------------|--------------------------------------------------------------|
| index.html | 简单的登录屏幕 |
| Catalog.java | 创建订单对象并列出产品的Servlet |
| catalog.jsp | 显示产品表和订购表单的JSP |
| Recommend.java | 接受提交的订单表单并运行推荐代理的Servlet |
| recommend.jsp | 显示推荐信息并提供将推荐添加到订单的表单的JSP |
| Purchase.java | 接受已完成订单的Servlet |
| purchase.jsp | 最终屏幕 |

在这个架构中,我们主要关注Servlet和JSP中的Java代码,JSP中的HTML部分通常由网页设计师进行完善。

4. 登录屏幕

客户看到的第一个页面是登录屏幕。这里我们使用一个简单的HTML表单,让客户输入一个字符串作为客户ID:

<HTML>
    <HEAD>
        <TITLE>Welcome to TekMart.com!</TITLE>
    </HEAD>
    <BODY>
        <H1>Welcome to TekMart.com</H1>
        Welcome to the TekMart.com online store!
        <P>
        To get started, please enter your customer id:
        <FORM action="/Order/catalog" method="POST">
            <INPUT name="customerId">
            <INPUT type="submit" value="Log in">
        </FORM>
        </P>
    </BODY>
</HTML>

客户可以在输入字段中输入登录名,然后点击 “Log in” 按钮。表单提交结果将发送到 action 属性指定的URL,这里是 /Order/catalog

5. Catalog Servlet

Catalog Servlet需要完成以下任务:
1. 获取登录屏幕上输入的登录名;如果没有输入,则返回登录屏幕。
2. 调用 get-new-order-number 获取唯一的订单号。
3. 使用登录名和订单号创建新的用户会话。
4. 使用 all-products 查询获取产品事实列表。
5. 将结果转发给JSP进行显示。

以下是Catalog Servlet的详细实现:

5.1 初始化Jess
public class Catalog extends BaseServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
    throws IOException, ServletException {
        checkInitialized();

为了提高效率,所有客户将共享一个 Rete 对象。 checkInitialized 方法用于检查 Rete 对象是否已初始化,如果未初始化,则创建并初始化一个新的 Rete 对象:

protected void checkInitialized()
    throws ServletException {
    ServletContext servletContext = getServletContext();
    String rulesFile =
      servletContext.getInitParameter("rulesfile");
    String factsFile =
     servletContext.getInitParameter("factsfile");
    if (servletContext.getAttribute("engine") == null) {
        try {
            Rete engine = new Rete(this);
            new Batch().batch(rulesFile, engine);
            engine.reset();
            if (new File(factsFile).exists())
                engine.executeCommand("(load-facts " +
                    factsFile + ")");
            servletContext.setAttribute("engine", engine);
        } catch (Exception je) {
            throw new ServletException(je);
        }
    }
}

BaseServlet 类还实现了 doPost 方法,让所有Servlet可以免费获得对POST请求的处理能力:

public void doPost(HttpServletRequest request,
                   HttpServletResponse response)
throws IOException, ServletException {
    doGet(request, response);
}
5.2 获取登录名
String customerId =
        (String) request.getParameter("customerId");
if (customerId == null || customerId.length() == 0) {
    dispatch(request, response, "/index.html");
    return;
}

如果没有登录名,Servlet将调用登录屏幕。

5.3 启动用户会话
request.getSession().invalidate();
HttpSession session = request.getSession();
session.setAttribute("customerId", customerId);
session.setAttribute("orderNumber",
                     String.valueOf(getNewOrderNumber()));

getNewOrderNumber 方法调用共享 Rete 对象上的 defquery 来获取下一个订单号:

private int getNewOrderNumber() throws JessException {
    ServletContext servletContext = getServletContext();
    Rete engine = (Rete) servletContext.getAttribute("engine");
    String command = "(get-new-order-number)";
    int nextOrderNumber =
        engine.executeCommand(command).intValue(null);
    return nextOrderNumber;
}
5.4 查询产品列表
ServletContext servletContext = getServletContext();
Rete engine = (Rete) servletContext.getAttribute("engine");
Iterator result =
    engine.runQuery("all-products", new ValueVector());
request.setAttribute("queryResult", result);

这里使用了三种不同的方式在Web应用的各个部分之间共享信息:
- Rete 对象存储在 ServletContext 中,供所有Servlet和JSP在整个Web应用生命周期内共享。
- 登录名和订单号存储在 HttpSession 中,仅在单个浏览器会话中可用。
- 使用 HttpServletRequest 将数据从Servlet传递给JSP,该数据仅在响应单个浏览器请求时存在。

5.5 调用JSP
try {
    // 前面的代码
} catch (JessException je) {
    throw new ServletException(je);
}
dispatch(request, response, "/catalog.jsp");

如果出现异常,将抛出 ServletException ,Tomcat会返回一个错误页面。

5.6 Catalog JSP

Catalog JSP用于显示Catalog Servlet准备的产品列表:

<HTML>
    <%@ page import="jess.*" %>
    <jsp:useBean id="queryResult"
                 class="java.util.Iterator" scope="request"/>
    <HEAD>
        <TITLE>Ordering from Tekmart.com</TITLE>
    </HEAD>
    <BODY>
        <H1>Tekmart.com Catalog</H1>
        Select the items you wish to purchase and press
    "Check Order" to continue.
        <FORM action="/Order/recommend" method="POST">
            <TABLE border="1">
                <TR>
                    <TH>Name</TH>
                    <TH>Catalog #</TH>
                    <TH>Price</TH>
                    <TH>Purchase?</TH>
                </TR>
                <% while (queryResult.hasNext()) {
                    Token token = (Token) queryResult.next();
                    Fact fact = token.fact(1);
                    String partNum =
                        fact.getSlotValue("part-number")
                        .stringValue(null); %>
                <TR>
                    <TD><%= fact.getSlotValue("name")
                            .stringValue(null) %></TD>
                    <TD><%= partNum %></TD>
                    <TD><%= fact.getSlotValue("price")
                            .floatValue(null) %></TD>
                    <TD><INPUT type="checkbox" name="items"
                         value=<%= '"’ + partNum + '"'%>></TD>
                </TR>
            <% } %>
            </TABLE>
            <INPUT type="submit" value="Check Order">
        </FORM>
    </BODY>
</HTML>

这个JSP使用Java的 while 循环渲染产品表格,每个产品对应一行,包含产品名称、目录号、价格和一个复选框。

6. 测试

使用Web浏览器测试Web应用既繁琐又容易出错。幸运的是,有许多自动化测试工具可供选择,主要分为两类:
- 负载测试工具:模拟大量同时访问您网站的用户。
- 页面测试工具:调用特定URL并检查生成的页面是否正确。

对于开发者来说,页面测试工具在早期阶段更有用。这里推荐使用开源的 HttpUnit/ServletUnit 框架进行测试。在开始编写和测试大型应用之前,建议先使用Jess的命令行界面尽可能仔细地测试和调试规则。

7. Recommend Servlet

当登录表单提交时,Catalog Servlet会获取一个名为 customerId 的请求参数。当目录表单提交时,每个客户购买的产品都会有一个 items 请求参数,这些参数将作为Recommend Servlet的输入。

Recommend Servlet需要完成以下任务:
1. 收集商品、客户ID和订单号。
2. 确保客户已登录并正在订购商品。
3. 重置订单,以防表单被重新提交。
4. 创建订单事实。

以上就是基于Java的Web应用开发中Servlet和JSP的详细介绍和实践,通过合理使用这些技术,可以构建出高效、可维护的Web应用。

基于Java的Web应用开发:Servlet与JSP实践

8. Recommend Servlet详细实现
8.1 收集信息

首先,Recommend Servlet需要从请求和会话中收集商品、客户ID和订单号。以下是示例代码:

public class Recommend extends BaseServlet {
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
    throws IOException, ServletException {
        // 获取客户ID和订单号
        HttpSession session = request.getSession();
        String customerId = (String) session.getAttribute("customerId");
        String orderNumber = (String) session.getAttribute("orderNumber");

        // 获取客户购买的商品
        String[] items = request.getParameterValues("items");

在这段代码中,我们从 HttpSession 中获取客户ID和订单号,从 HttpServletRequest 中获取客户购买的商品列表。

8.2 验证客户状态

接下来,需要确保客户已登录并正在订购商品:

        if (customerId == null || customerId.length() == 0 || items == null || items.length == 0) {
            dispatch(request, response, "/index.html");
            return;
        }

如果客户未登录或没有选择商品,将重定向到登录页面。

8.3 重置订单

为了防止表单被重新提交,需要重置订单:

        ServletContext servletContext = getServletContext();
        Rete engine = (Rete) servletContext.getAttribute("engine");
        try {
            engine.executeCommand("(reset-order " + orderNumber + ")");
        } catch (JessException je) {
            throw new ServletException(je);
        }

这里调用了 reset-order 函数来重置订单。

8.4 创建订单事实

最后,根据收集到的信息创建订单事实:

        for (String item : items) {
            try {
                engine.executeCommand("(create-order-fact " + orderNumber + " " + item + ")");
            } catch (JessException je) {
                throw new ServletException(je);
            }
        }

通过遍历商品列表,为每个商品创建订单事实。

8.5 调用推荐逻辑并转发到JSP

完成上述步骤后,运行推荐逻辑并将结果转发到 recommend.jsp

        try {
            Iterator result = engine.runQuery("recommendations", new ValueVector());
            request.setAttribute("recommendations", result);
        } catch (JessException je) {
            throw new ServletException(je);
        }
        dispatch(request, response, "/recommend.jsp");
    }
}

这里运行了 recommendations 查询,并将结果存储在 HttpServletRequest 中,然后转发到 recommend.jsp 进行显示。

9. Recommend JSP

recommend.jsp 用于显示推荐信息并提供将推荐添加到订单的表单:

<HTML>
    <%@ page import="jess.*" %>
    <jsp:useBean id="recommendations"
                 class="java.util.Iterator" scope="request"/>
    <HEAD>
        <TITLE>Recommendations from Tekmart.com</TITLE>
    </HEAD>
    <BODY>
        <H1>Tekmart.com Recommendations</H1>
        Here are some products we recommend for you:
        <FORM action="/Order/purchase" method="POST">
            <TABLE border="1">
                <TR>
                    <TH>Name</TH>
                    <TH>Catalog #</TH>
                    <TH>Price</TH>
                    <TH>Add to Order?</TH>
                </TR>
                <% while (recommendations.hasNext()) {
                    Token token = (Token) recommendations.next();
                    Fact fact = token.fact(1);
                    String partNum =
                        fact.getSlotValue("part-number")
                        .stringValue(null); %>
                <TR>
                    <TD><%= fact.getSlotValue("name")
                            .stringValue(null) %></TD>
                    <TD><%= partNum %></TD>
                    <TD><%= fact.getSlotValue("price")
                            .floatValue(null) %></TD>
                    <TD><INPUT type="checkbox" name="recommendedItems"
                         value=<%= '"’ + partNum + '"'%>></TD>
                </TR>
            <% } %>
            </TABLE>
            <INPUT type="submit" value="Place Order">
        </FORM>
    </BODY>
</HTML>

这个JSP使用Java的 while 循环渲染推荐产品表格,每个推荐产品对应一行,包含产品名称、目录号、价格和一个复选框,用户可以选择将推荐产品添加到订单中。

10. Purchase Servlet

Purchase Servlet用于接受已完成的订单:

public class Purchase extends BaseServlet {
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
    throws IOException, ServletException {
        HttpSession session = request.getSession();
        String customerId = (String) session.getAttribute("customerId");
        String orderNumber = (String) session.getAttribute("orderNumber");

        String[] items = request.getParameterValues("items");
        String[] recommendedItems = request.getParameterValues("recommendedItems");

        // 合并普通商品和推荐商品
        List<String> allItems = new ArrayList<>();
        if (items != null) {
            allItems.addAll(Arrays.asList(items));
        }
        if (recommendedItems != null) {
            allItems.addAll(Arrays.asList(recommendedItems));
        }

        // 处理订单
        ServletContext servletContext = getServletContext();
        Rete engine = (Rete) servletContext.getAttribute("engine");
        try {
            engine.executeCommand("(process-order " + orderNumber + " " + String.join(" ", allItems) + ")");
        } catch (JessException je) {
            throw new ServletException(je);
        }

        dispatch(request, response, "/purchase.jsp");
    }
}

在这个Servlet中,我们首先从请求和会话中获取客户ID、订单号以及用户选择的商品列表,然后合并普通商品和推荐商品,最后调用 process-order 函数处理订单,并将结果转发到 purchase.jsp

11. Purchase JSP

purchase.jsp 是最终屏幕,用于显示订单处理结果:

<HTML>
    <HEAD>
        <TITLE>Order Confirmation</TITLE>
    </HEAD>
    <BODY>
        <H1>Order Confirmation</H1>
        Your order has been successfully placed! Thank you for shopping at Tekmart.com.
    </BODY>
</HTML>

这个JSP只是简单地显示一个订单确认消息。

12. 整体流程总结

下面是整个Web应用的流程图:

graph LR
    A[登录页面(index.html)] --> B[Catalog Servlet]
    B --> C[catalog.jsp]
    C --> D[Recommend Servlet]
    D --> E[recommend.jsp]
    E --> F[Purchase Servlet]
    F --> G[purchase.jsp]

整个流程如下:
1. 用户访问登录页面,输入客户ID并提交表单。
2. Catalog Servlet处理登录请求,创建订单对象,查询产品列表,并将结果转发到 catalog.jsp
3. 用户在 catalog.jsp 中选择商品并提交表单。
4. Recommend Servlet处理商品选择请求,运行推荐逻辑,并将推荐结果转发到 recommend.jsp
5. 用户在 recommend.jsp 中选择推荐商品并提交表单。
6. Purchase Servlet处理最终订单,调用订单处理逻辑,并将结果转发到 purchase.jsp
7. 用户在 purchase.jsp 中看到订单确认消息。

13. 总结与展望

通过以上的实践,我们展示了如何使用Servlet和JSP构建一个完整的Web应用。在这个过程中,我们学习了如何使用JSP和Servlet协作,如何在不同组件之间共享信息,以及如何处理用户请求和业务逻辑。

在实际开发中,还可以对这个应用进行进一步的优化和扩展。例如,可以添加更多的业务规则,完善用户界面,提高系统的安全性和性能等。同时,合理使用自动化测试工具可以帮助我们更快地发现和解决问题,提高开发效率。

总之,Servlet和JSP是Java Web开发中非常重要的技术,掌握它们可以让我们构建出更加高效、可维护的Web应用。希望本文能对大家有所帮助,让大家在Java Web开发的道路上更进一步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值