跳转至

从静态网站到JSP+Servlet+JavaBean

引言

从上世纪90年代至今,Web开发飞速发展。现在当你打开各种关于JavaWeb的网课时,时常会看到满屏“白学了”的弹幕,而这篇文章主要讲述的就是这些“白学”的内容😄。

静态网站

随着互联网的诞生和发展,静态网页率先出场。

它们通常由HTML编写,程序内容固定于文件之中,通过HTTP协议从服务器传输到客户端游览器进行展示。 这样的方式缺点是显而易见的(甚至直接写在了名字上),那就是不够“动态”,由于程序内容固定,网页展示的内容需要修改程序后重新展示才可得到更新,无法实时根据用户请求动态响应。

就比如一个在线购物平台,用户将商品加入购物车后,购物车页面却无法实时更新购物车内的商品数量和总价,用户端体验较差。由此,动态网站应运而生。

Servlet

为了应对动态网站的需求,Java首先推出Servlet接口,定义处理网络请求的规范。

Servlet.class
public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

需要注意的是,Servlet仅仅只是个规范,实现了Servlet接口的类,还不能直接处理网络请求。就好比你有一套如何写信的规范,但信怎么发到你手里和你怎么发出信,还是个问题。

怎么解决这个问题呢?我们需要一个信使!

这个信使需要做什么呢?

首先,它得收发信。
在HTTP协议中,“信”通过TCP/IP网络发送,“信使”需要能够监听网络端口,捕获到“信”。
其次,它得解析请求和处理响应。
由于客户游览器与服务器消息往来遵循HTTP协议,需要“信使”解析请求报文,知道“怎么发”(GET还是POST)、“发去哪”(URL)、“内容主体是什么”(请求体)等;还需根据HTTP协议处理Servlet的响应。

为了得到有着上述作用的“信使”,早期Java开发人员需要利用Java自带的Socket类手动实现对HTTP请求的解析和相应逻辑,这里不做重点,实现方法可参考Socket实现简单的Web服务器

可以想象得到,这样操作无疑是非常繁琐的。已知HTTP协议报文结构清晰以及监听网络的代码较为雷同重复,既然如此,为什么不制作一个程序封装这些操作呢?

Tomcat

为了解决上述问题,Apache基金会推出了Tomcat

Tomcat是一个开源、免费、轻量级的Servlet容器和Web服务器,它不仅能够高效地处理网络通信和HTTP协议解析,还能根据Servlet规范自动调用相应的Servlet来处理请求,极大地简化了JavaWeb开发的复杂度。

Tomcat服务器接受客户游览器请求并做出响应的过程如下:
  1. 客户端访问Web服务器,发送HTTP请求
  2. Web服务器接收到请求后,传递给Servlet容器
  3. Servlet容器加载并实例化相应Servlet对象,然后向对象传递请求对象Request和响应对象Response
  4. Servlet实例根据请求对象作出相应处理,并构建响应对象
  5. Servlet实例将响应对象送回Web服务器,Web服务器处理后发送给客户端

下边是Tomcat的简单用例,详细用法可参照IDEA中使用Tomcat和Servlet

tomcat/conf/server.xml
1
2
3
4
5
6
<!-- 其他配置内容-->
<!-- 配置端口号-->
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />
<!-- 其他配置内容-->
project/src/webapp/WEB-INF/web.xml
<!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>
  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>ServletDemo</servlet-name>
    <servlet-class>com.xxx.web.ServletDemo</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>ServletDemo</servlet-name>
    <url-pattern>/sd</url-pattern>
  </servlet-mapping>
</web-app>
project/src/main/java/com.xxx.web/ServletDemo.class
@WebServlet("/sd") // 利用注解的方式简化Servlet的映射,有这步操作web.xml的高亮部分可省略
public class ServletDemo implements Servlet{
    public void init(ServletConfig servletConfig) throws ServletException {}
    public ServletConfig getServletConfig() {return null;}
    // 重点实现service函数
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String username = servletRequest.getParameter("username");
        /*
            在localhost:8080/project/sd上显示得到的username变量的字符串
         */
        servletResponse.setContentType("html/text;charset=UTF-8");
        PrintWriter out = servletResponse.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<p>" + username + "</p>");
        out.println("</body></html>");
    }
    public String getServletInfo() {return "";}
    public void destroy() {}
}
project/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>GET Request Demo</title>
</head>
<body>
<!-- 定义一个输入框,输入后点击按钮发送请求,请求资源./sd -->
<form action="./sd" method="GET"> 
    <input type="text" name="username" placeholder="请输入内容">
    <input type="submit" value="发送请求">
</form>
</body>
</html>
localhost:8080/project/sd
页面显示用户在请求页面输入的对应字符串

可以看到,这样网页就实现了动态交互。整个过程大概遵循以下逻辑:

graph LR
  A[游览器] -->|请求资源,发送Request|B{Tomcat服务器};
  B -->|解析好Request后发送| C[对应Servlet];
  C -->|返回Response| B;
  B -->|依照HTTP协议处理响应,并发给游览器| A;

但解决这一切后,又有一个显著的问题摆在眼前,这代码也太难写了!

关于Tomcat和Servlet的其它知识

由于篇幅问题,很多有意思的东西都难以说明,如:
1. 会话跟踪技术中的Cookie和Session,以及它们的扩展token、JWT等等
2. JavaWeb三大组件(Servlet、Filter、Listener)中的Filter和Listener
3. 还有HTTP响应状态码、转发和重定向机制等等
感兴趣可以自行查找资源学习……

JSP

很明显,上边ServletDemo.class的代码有点难写,原因是它需要我们在Java中写HTML代码—— 导致代码可读性异常得差。

针对这种情况,Sun Microsystems公司推出了Java Server Page(简称JSP)技术,以简化动态网页的开发。

JSP = Java + HTML,JSP允许将Java代码直接嵌入到HTML页面中,如下:

利用Java中Date类获取当前时间,嵌入HTML并在网页上输出
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Simple JSP Page</title>
</head>
<body>
    <h1>Hello, JSP!</h1>
    <p>Current time: 
        <%= 
            new java.util.Date() 
        %>
    </p>
</body>
</html>
JSP脚本中
<%@...%>:用来设置JSP页面的属性,如<%@ page import="java.util.*" %>
<%...%>:内容会直接放在_jspService()方法中
你可能会问`_jspService()`是什么,其实你直接把它当做Servlet中的`service()`就行
<%=...%>:作用等同于Servlet中response.getWriter().write(...)
<%!...%>:内容会放在除_jspService()方法之外

比较难绷和抽象的是:JSP甚至允许Java代码不是连续的,你能想象一个for循环{}中间还可能会临时中断插入一段HTML代码吗……


根据上边描述,聪明的你应该会想到:其实,JSP本质上是个Servlet

当请求JSP资源时,Tomcat会执行以下操作:
  1. 检查该JSP文件是否已经编译过。如是,则从缓存中取出直接使用;如否,则编译它
  2. Tomcat会解析JSP中的代码,将JSP文件编译为一个Servlet类
  3. 之后当其为正常的Servlet来使用

通过这种方式,JSP页面能够动态生成内容,同时保持代码的可读性和开发的便捷性。你程序员是爽了,那对于用户,还有哪些方面可以进一步提升体验呢?

EL表达式和JSTL标签库

为了进一步优化JSP代码,还出现了EL表达式JSTL标签库
EL表达式:方便JSP获取数据,主要用于转发技术。
JSTL标签库:使Java代码HTML化,极大美观代码。
详见超链接。

AJAX

在一些网站的注册页面中,我们常常会发现,当输入用户名后,页面会立即提示该用户名是否可用,而不是要提交表单、请求资源后,页面刷新才给出提示。这种动态实时验证的功能就是通过AJAX(Asynchronous JavaScript and XML)实现的。

AJAX是一种在无需重新加载整个网页的情况下,能够与服务器进行异步数据交换并更新部分网页内容的技术。它允许网页在用户与页面交互的过程中,后台与服务器进行数据通信,从而实现动态更新页面内容的效果。

graph LR
  A[游览器:处理前的页面] --> |发送资源请求|B{服务器处理};
  B -->|处理完请求| C[游览器:刷新页面,得到处理后的页面];
graph LR
  A[游览器:处理前的页面] --> |AJAX发送请求|B{服务器处理};
  A --> |用户正常使用页面|D[游览器:即使处理中,也能正常与用户交互];
  B -->|处理完请求| C[游览器:无需刷新页面,直接得到处理后的页面];
  D --> |用户正常使用|C
AJAX工作流程:
  1. 网页中发生事件,JavaScript创建XMLHttpRequest类对象
  2. 该对象向服务器发送请求
  3. 服务器处理请求并将响应发送回网页

想象你和朋友约好通过写信的方式交流。传统的网页交互就像是你写了一封信,你要亲自送去并将回信带回来,整个过程是同步的,你无法在等待往返期间做其他事情。

而AJAX技术的出现,就好比你找了个中间邮递员。你把信交给邮递员(AJAX请求),邮递员会去送信并等待对方回复。在这期间,你可以继续做自己的事情。当邮递员拿到回信后,他会自动通知你(AJAX响应),你再根据回信内容采取下一步行动。

下边是AJAX使用的简单实例:

AJAX根据服务器响应异步更新网页内容
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>AJAX 示例</title>
</head>
<body>
    <div id="demo">
        <h2>让 AJAX 更改这段文本</h2>
        <button type="button" onclick="loadDoc()">更改文本</button>
    </div>

    <script>
        function loadDoc() {
            // 创建 XMLHttpRequest 对象
            var xhttp = new XMLHttpRequest();
            // 配置请求的回调函数
            xhttp.onreadystatechange = function() {
                if (this.readyState === 4 && this.status === 200) {
                    // 使用响应数据更新页面内容
                    document.getElementById("demo").innerHTML = this.responseText;
                }
            };
            // 打开GET请求并设置为异步
            xhttp.open("GET", "ajax_info.txt", true);
            xhttp.send();
        }
    </script>
</body>
</html>

至此,现代网页的基本功能都已经有了实现。那么接下来该讨论如何优化开发结构的问题了。

AXIOS和JSON

AXIOS:AXIOS异步框架,可以简化AJAX请求的编写,提高代码的可读性和维护性。
JSON:全称JavaScript Object Notation(JavaScript对象表示法),其多用于作为数据载体,在网络中进行数据传输。
二者都是简化Web开发的好手,详见超链接。

JSP + Servlet + JavaBean

看到标题你可能会很疑惑,不是哥们,JavaBean是什么?

起初我也非常困惑,但后边了解后才知道这玩意基本天天见。

所谓JavaBean,就是如下般的类:

JavaBean

JavaBean的特点:

1. 字段访问权限私有

2. 具有默认构造方法

3. 具有Getter和Setter方法

4. 可序列化,实现了Serializable接口

作用:JavaBean通常用于Web开发当中,可以提高代码的可维护性和重用性。

UserBean.class
public class UserBean implements Serializable {
    private String name;

    // 默认构造方法
    public UserBean() {
    }

    // Getter 和 Setter 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

三层架构和MVC设计模式

众所周知,软件架构通常为三层架构,三层架构将应用程序分为:
  1. 表现层:负责与用户进行交互,展示数据和接受用户的输入
  2. 业务逻辑层:负责处理应用程序的核心业务逻辑和规则
  3. 数据访问层:负责与数据库进行交互,执行数据的增删改查

MVC(Model-View-Controller)设计模式便是一种针对表现层设计模式,将表现层开发分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。

在MVC中,JavaBean主要用于处理模型层的事务。 前文一直在弱化模型层的表现,原因是不想让文章太过臃肿。

在实际开发中,可以将MVC设计模式应用于表示层,而业务逻辑层和数据访问层则按照三层架构的方式进行设计。这样可以充分利用两种架构模式的优势,构建更加清晰、模块化和可维护的应用程序。

JSP + JavaBean (Model1)

针对MVC设计模式,JSP + JavaBean(简称Model1)风靡一时。

Model1中,JSP因其“Java+HTML”的特性,直接用于处理视图层(View)和控制层(Controller);而JavaBean则用于处理模型层(Model)。

graph LR
  A[客户端游览器] --> |1.请求|B[JSP页面];
  B <--> |2.与JavaBean交互|C[JavaBean];
  C <--> |3.与数据库交互|D[Database];
  B --> |4.响应|A

这样,开发实现初步解耦。但不难发现,虽然该模式编写代码较为容易,但JSP混杂了View层和Controller层,耦合程度还是太高。因此又提出了JSP+Servlet+JavaBean的Model2模式。

JSP+Servlet+JavaBean(Model2)

Model2中,添加了Servlet来作处理Controller层的主力,而JSP仅需要处理View层和少量Controller层即可。

这无疑是进步的,这样的模式下,JSP中可以大量减少Java的代码,达到仅使用EL表达式和JSTL就够的效果。

这样JSP中基本不涉及任何的业务逻辑,前端工程师可以在JSP中专注样式,后端工程师可以在Servlet和JavaBean中专注逻辑,实现进一步开发结构的解耦。

graph LR
  A[客户端游览器] --> |1.请求|B[Servlet];
  B <--> |2.与JavaBean交互|C[JavaBean];
  C <--> |3.与数据库交互|D[Database];
  B --> |4.转发或重定向|E[JSP页面];
  E --> |5.响应|A

需要注意的是,Model2还不是一个完全标准的MVC设计模式,还可以继续与三层架构结合,将JavaBean继续分割成业务逻辑层数据持久层……

纵观软件设计的发展,无非就是一个不断提升“高聚合,低耦合”的过程。而这个过程往往伴随着分层这一操作,因为在计算机领域,没有什么是加多一层不能解决的,如果有,那就两层……

尾言

至此,本篇文章就结束了。接下来就是各种框架的舞台了……

到时,我们会遇见灵活易用的前端框架Vue,还会学习将Java推至“神坛”的Spring全家桶——或许到那时,面对如此便捷的开发工具,你会发出和本文引言一样的疑惑:“白学了吗?”,答案请藏在心底。我不会说就是白学了🤡