41、Java Web开发:JSP、Servlet与相关技术深度解析

Java Web开发:JSP、Servlet与相关技术深度解析

1. 使用JSP与JavaBeans

在Java Web开发中,JavaBeans与JSP的结合使用是一项重要技术。

1.1 JavaBean的基本要求

一个类要成为JavaBean,需要满足以下基本要求:
1. 拥有无参(空)构造函数。
2. 没有公共实例变量(字段)。
3. 通过名为getXxx(或isXxx)和setXxx的方法访问持久值。

1.2 JavaBean的基本使用

在JSP中使用JavaBean,主要有以下几种方式:
- <jsp:useBean id="name" class="package.Class" /> :用于声明并实例化一个JavaBean。
- <jsp:getProperty name="name" property="property" /> :用于获取JavaBean的属性值。
- <jsp:setProperty name="name" property="property" value="value" /> :用于设置JavaBean的属性值,其中value属性可以接受JSP表达式。

1.3 将属性与请求参数关联

可以将JavaBean的属性与请求参数进行关联,具体方式如下:
- 单个属性

<jsp:setProperty 
    name="entry"
    property="numItems"
    param="numItems" />
  • 自动类型转换 :对于基本类型,会根据包装类的valueOf方法进行自动类型转换。
  • 所有属性
<jsp:setProperty name="entry" property="*" />
1.4 共享JavaBean:jsp:useBean的scope属性

jsp:useBean scope 属性用于指定JavaBean的共享范围,常见的取值有:
| 范围 | 描述 |
| ---- | ---- |
| page | 默认值,表示除了绑定到局部变量外,Bean对象应在当前请求期间放置在PageContext对象中。 |
| application | 表示除了绑定到局部变量外,Bean将存储在通过预定义的application变量或调用getServletContext()可访问的共享ServletContext中。 |
| session | 表示除了绑定到局部变量外,Bean将存储在与当前请求关联的HttpSession对象中,可以使用getValue方法检索。 |
| request | 表示除了绑定到局部变量外,Bean对象应在当前请求期间放置在ServletRequest对象中,可以通过getAttribute方法访问。 |

1.5 条件性Bean创建

<jsp:useBean> 元素只有在找不到具有相同id和范围的Bean时才会实例化一个新的Bean。如果找到具有相同id和范围的Bean,则将现有的Bean绑定到id引用的变量。可以使 <jsp:setProperty> 语句依赖于新Bean的创建:

<jsp:useBean ...>
    statements
</jsp:useBean>
2. 创建自定义JSP标签库

自定义JSP标签库可以提高代码的复用性和可维护性。

2.1 标签处理类

要创建自定义标签库,需要实现Tag接口,通常通过扩展TagSupport(无标签体或标签体逐字包含)或BodyTagSupport(操作标签体)来实现。标签处理类有以下几个重要方法:
- doStartTag :在标签开始时运行的代码。
- doEndTag :在标签结束时运行的代码。
- doAfterBody :处理标签体的代码。

2.2 标签库描述符文件

标签库描述符文件(TLD)在 taglib 元素中为每个标签处理程序包含一个 tag 元素。例如:

<tag>
    <name>prime</name>
    <tagclass>coreservlets.tags.PrimeTag</tagclass>
    <info>Outputs a random N-digit prime.</info>
    <bodycontent>EMPTY</bodycontent>
    <attribute>
        <name>length</name>
        <required>false</required>
    </attribute>
</tag>
2.3 JSP文件

在JSP文件中使用自定义标签库,需要进行以下操作:
- 使用 <%@ taglib uri="some-taglib.tld" prefix="prefix" %> 指令引入标签库。
- 使用 <prefix:tagname /> <prefix:tagname>body</prefix:tagname> 来使用标签。

2.4 为标签分配属性
  • 标签处理类 :为每个属性xxx实现setXxx方法。
  • 标签库描述符 :在 tag 元素中添加 attribute 元素来定义属性。
<tag>
    ...
    <attribute>
        <name>length</name>
        <required>false</required>
        <rtexprvalue>true</rtexprvalue> <!-- sometimes -->
    </attribute>
</tag>
2.5 包含标签体
  • 标签处理类 :从 doStartTag 方法返回 EVAL_BODY_INCLUDE 而不是 SKIP_BODY
  • 标签库描述符 :在 tag 元素中设置 <bodycontent>JSP</bodycontent>
2.6 可选地包含标签体

标签处理类可以根据请求时间参数的值,在不同时间返回 EVAL_BODY_INCLUDE SKIP_BODY

2.7 操作标签体

要操作标签体,需要扩展BodyTagSupport类,实现 doAfterBody 方法,调用 getBodyContent 方法获取描述标签体的BodyContent对象。BodyContent有三个关键方法: getEnclosingWriter getReader getString 。从 doAfterBody 方法返回 SKIP_BODY

2.8 多次包含或操作标签体

要再次处理标签体,从 doAfterBody 方法返回 EVAL_BODY_TAG ;要完成处理,返回 SKIP_BODY

2.9 使用嵌套标签

嵌套标签可以使用 findAncestorWithClass 方法找到它们嵌套的标签,并将数据放置在封闭标签的字段中。在标签库描述符中,无论实际页面中的嵌套结构如何,都应单独声明所有标签。

3. 集成Servlet和JSP

Servlet和JSP的集成是Java Web开发中的常见模式。

3.1 整体流程
  • Servlet处理初始请求,读取参数、cookie、会话信息等。
  • Servlet进行所需的计算和数据库查询。
  • Servlet将数据存储在JavaBean中。
  • Servlet将请求转发到多个可能的JSP页面之一,以呈现最终结果。
  • JSP页面从JavaBean中提取所需的值。
3.2 请求转发语法
String url = "/path/presentation1.jsp";
RequestDispatcher dispatcher =
    getServletContext().getRequestDispatcher(url);
dispatcher.forward();
3.3 转发到常规HTML页面
  • 如果初始Servlet仅处理GET请求,则无需更改。
  • 如果初始Servlet处理POST请求,则将目标页面从SomePage.html更改为SomePage.jsp,以便它也能处理POST请求。
3.4 设置全局共享Bean
  • 初始Servlet
Type1 value1 = computeValueFromRequest(request);
getServletContext().setAttribute("key1", value1);
  • 最终JSP文档
<jsp:useBean id="key1" class="Type1" scope="application" />
3.5 设置会话Bean
  • 初始Servlet
Type1 value1 = computeValueFromRequest(request);
HttpSession session = request.getSession(true);
session.putValue("key1", value1);
  • 最终JSP文档
<jsp:useBean id="key1" class="Type1" scope="session" />
3.6 解释目标页面中的相对URL

转发请求时使用原始Servlet的URL。浏览器不知道实际URL,因此它将相对于原始Servlet的URL解析相对URL。

3.7 通过替代方式获取RequestDispatcher(仅适用于2.2版本)
  • 按名称:使用ServletContext的getNamedDispatcher方法。
  • 相对于初始Servlet位置的路径:使用HttpServletRequest的getRequestDispatcher方法,而不是ServletContext的方法。
3.8 包含静态或动态内容
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("...");
RequestDispatcher dispatcher =
    getServletContext().getRequestDispatcher("/path/resource");
dispatcher.include(request, response);
out.println("...");

JSP等效的是 <jsp:include> ,而不是JSP包含指令。

3.9 从JSP页面转发请求
<jsp:forward page="Relative URL" /> 
4. 使用HTML表单

HTML表单是Web应用程序中收集用户输入的重要方式。

4.1 FORM元素

通常的形式为:

<FORM ACTION="URL" ...> ... </FORM> 

常见属性有:ACTION(必需)、METHOD、ENCTYPE、TARGET、ONSUBMIT、ONRESET、ACCEPT、ACCEPT - CHARSET。

4.2 文本字段

通常的形式为:

<INPUT TYPE="TEXT" NAME="..." ...> 

常见属性有:NAME(必需)、VALUE、SIZE、MAXLENGTH、ONCHANGE、ONSELECT、ONFOCUS、ONBLUR、ONKEYDOWN、ONKEYPRESS、ONKEYUP。不同浏览器对于在文本字段中按Enter键提交表单的规则不同,因此应包含一个明确提交表单的按钮或图像映射。

4.3 密码字段

通常的形式为:

<INPUT TYPE="PASSWORD" NAME="..." ...> 

常见属性与文本字段类似,但包含密码字段的表单应始终使用POST方法。

4.4 文本区域

通常的形式为:

<TEXTAREA NAME="..." ROWS=xxx COLS=yyy> ... 
Some text
</TEXTAREA> 

常见属性有:NAME(必需)、ROWS(必需)、COLS(必需)、WRAP(非标准)、ONCHANGE、ONSELECT、ONFOCUS、ONBLUR、ONKEYDOWN、ONKEYPRESS、ONKEYUP。初始文本中的空白会被保留,起始和结束标签之间的HTML标记会按字面意思处理,除了字符实体如 &lt; &copy; 等。

4.5 提交按钮

通常的形式为:

<INPUT TYPE="SUBMIT" ...> 

常见属性有:NAME、VALUE、ONCLICK、ONDBLCLICK、ONFOCUS、ONBLUR。当点击提交按钮时,表单会发送到FORM元素的ACTION参数指定的Servlet或其他服务器端程序。

4.6 替代按钮
  • 替代提交按钮
<BUTTON TYPE="SUBMIT" ...>
    HTML Markup
</BUTTON> 

仅适用于Internet Explorer。
- 重置按钮

<INPUT TYPE="RESET" ...> 

常见属性有:VALUE、NAME、ONCLICK、ONDBLCLICK、ONFOCUS、ONBLUR,除了VALUE属性外,其他属性仅用于JavaScript。
- 替代重置按钮

<BUTTON TYPE="RESET" ...>
    HTML Markup
</BUTTON> 

仅适用于Internet Explorer。
- JavaScript按钮

<INPUT TYPE="BUTTON" ...> 

常见属性有:NAME、VALUE、ONCLICK、ONDBLCLICK、ONFOCUS、ONBLUR。
- 替代JavaScript按钮

<BUTTON TYPE="BUTTON" ...>
    HTML Markup
</BUTTON> 

仅适用于Internet Explorer。

4.7 复选框

通常的形式为:

<INPUT TYPE="CHECKBOX" NAME="..." ...> 

常见属性有:NAME(必需)、VALUE、CHECKED、ONCLICK、ONFOCUS、ONBLUR。只有当复选框被选中时,才会传输名称/值对。

4.8 单选按钮

通常的形式为:

<INPUT TYPE="RADIO" NAME="..." VALUE="..." ...> 

常见属性有:NAME(必需)、VALUE(必需)、CHECKED、ONCLICK、ONFOCUS、ONBLUR。通过为一组单选按钮提供相同的NAME来表示它们属于同一组。

4.9 组合框

通常的形式为:

<SELECT NAME="Name" ...>
    <OPTION VALUE="Value1">Choice 1 Text
    <OPTION VALUE="Value2">Choice 2 Text
    ...
    <OPTION VALUE="ValueN">Choice N Text
</SELECT> 

SELECT元素的常见属性有:NAME(必需)、SIZE、MULTIPLE、ONCLICK、ONFOCUS、ONBLUR、ONCHANGE;OPTION元素的常见属性有:SELECTED、VALUE。

4.10 文件上传控件

通常的形式为:

<INPUT TYPE="FILE" ...> 

常见属性有:NAME(必需)、VALUE(忽略)、SIZE、MAXLENGTH、ACCEPT、ONCHANGE、ONSELECT、ONFOCUS、ONBLUR(非标准)。在FORM声明中应使用 ENCTYPE="multipart/form-data"

4.11 服务器端图像映射

通常的形式为:

<INPUT TYPE="IMAGE" ...> 

常见属性有:NAME(必需)、SRC、ALIGN。也可以为 <A HREF...> 元素内的标准IMG元素提供ISMAP属性。

4.12 隐藏字段

通常的形式为:

<INPUT TYPE="HIDDEN" NAME="..." VALUE="..."> 

常见属性有:NAME(必需)、VALUE。

4.13 Internet Explorer特性
  • FIELDSET(带有LEGEND):用于分组控件。
  • TABINDEX:用于控制制表顺序。这两个功能都是HTML 4.0规范的一部分,但Netscape 4不支持。
5. 使用小程序作为Servlet前端

小程序可以作为Servlet的前端,实现数据的交互。

5.1 使用GET发送数据并显示结果页面
String someData =
    name1 + "=" + URLEncoder.encode(val1) + "&" +
    name2 + "=" + URLEncoder.encode(val2) + "&" +
    ...
    nameN + "=" + URLEncoder.encode(valN);
try {
    URL programURL = new URL(baseURL + "?" + someData);
    getAppletContext().showDocument(programURL);
} catch(MalformedURLException mue) { ... }
5.2 使用GET发送数据并直接处理结果(HTTP隧道)
graph TD;
    A[创建URL对象] --> B[创建URLConnection对象];
    B --> C[禁止浏览器缓存数据];
    C --> D[设置HTTP头部];
    D --> E[创建输入流];
    E --> F[读取文档每一行];
    F --> G[关闭输入流];

具体步骤如下:
1. 创建一个引用小程序主机的URL对象,通常根据小程序加载的主机名构建URL。

URL currentPage = getCodeBase();
String protocol = currentPage.getProtocol();
String host = currentPage.getHost();
int port = currentPage.getPort();
String urlSuffix = "/servlet/SomeServlet";
URL dataURL = new URL(protocol, host, port, urlSuffix);
  1. 创建一个URLConnection对象。
URLConnection connection = dataURL.openConnection();
  1. 指示浏览器不要缓存URL数据。
connection.setUseCaches(false); 
  1. 设置所需的HTTP头部。
connection.setRequestProperty("header", "value");
  1. 创建一个输入流,常见的是BufferedReader。
BufferedReader in =
    new BufferedReader(new InputStreamReader(
        connection.getInputStream()));
  1. 读取文档的每一行,直到读取到null。
String line;
while ((line = in.readLine()) != null) {
    doSomethingWith(line);
}
  1. 关闭输入流。
in.close();
5.3 发送序列化数据
  • 小程序代码
graph TD;
    A[创建URL对象] --> B[创建URLConnection对象];
    B --> C[禁止浏览器缓存数据];
    C --> D[设置HTTP头部];
    D --> E[创建ObjectInputStream];
    E --> F[读取数据结构];
    F --> G[关闭输入流];

具体步骤如下:
1. 创建一个引用小程序主机的URL对象。

URL currentPage = getCodeBase();
String protocol = currentPage.getProtocol();
String host = currentPage.getHost();
int port = currentPage.getPort();
String urlSuffix = "/servlet/SomeServlet";
URL dataURL = new URL(protocol, host, port, urlSuffix);
  1. 创建一个URLConnection对象。
URLConnection connection = dataURL.openConnection();
  1. 指示浏览器不要缓存URL数据。
connection.setUseCaches(false); 
  1. 设置所需的HTTP头部。
connection.setRequestProperty("header", "value");
  1. 创建一个ObjectInputStream。
ObjectInputStream in =
    new ObjectInputStream(connection.getInputStream());
  1. 使用readObject方法读取数据结构,并进行类型转换。
SomeClass value = (SomeClass)in.readObject();
doSomethingWith(value);
  1. 关闭输入流。
in.close();
  • Servlet代码
graph TD;
    A[指定二进制内容类型] --> B[创建ObjectOutputStream];
    B --> C[写入数据结构];
    C --> D[刷新流];

具体步骤如下:
1. 指定发送的是二进制内容,将响应的MIME类型设置为 application/x-java-serialized-object

String contentType =
    "application/x-java-serialized-object";
response.setContentType(contentType);
  1. 创建一个ObjectOutputStream。
ObjectOutputStream out = 
    new ObjectOutputStream(response.getOutputStream());
  1. 使用writeObject方法写入数据结构,自定义类需要实现Serializable接口。
SomeClass value = new SomeClass(...);
out.writeObject(value);
  1. 刷新流,确保所有内容都已发送到客户端。
out.flush();
5.4 使用POST发送数据并直接处理结果(HTTP隧道)
graph TD;
    A[创建URL对象] --> B[创建URLConnection对象];
    B --> C[禁止浏览器缓存数据];
    C --> D[允许发送数据];
    D --> E[创建ByteArrayOutputStream];
    E --> F[附加输出流];
    F --> G[将数据放入缓冲区];
    G --> H[设置Content-Length头部];
    H --> I[设置Content-Type头部];
    I --> J[发送实际数据];
    J --> K[打开输入流];
    K --> L[读取结果];

具体步骤如下:
1. 创建一个引用小程序主机的URL对象。

URL currentPage = getCodeBase();
String protocol = currentPage.getProtocol();
String host = currentPage.getHost();
int port = currentPage.getPort();
String urlSuffix = "/servlet/SomeServlet";
URL dataURL = 
    new URL(protocol, host, port, urlSuffix);
  1. 创建一个URLConnection对象。
URLConnection connection = dataURL.openConnection();
  1. 指示浏览器不要缓存结果。
connection.setUseCaches(false); 
  1. 告诉系统允许发送数据。
connection.setDoOutput(true);
  1. 创建一个ByteArrayOutputStream来缓冲要发送到服务器的数据。
ByteArrayOutputStream byteStream =
    new ByteArrayOutputStream(512);
  1. 将输出流附加到ByteArrayOutputStream,使用PrintWriter发送普通表单数据,使用ObjectOutputStream发送序列化数据结构。
PrintWriter out = new PrintWriter(byteStream, true);
  1. 将数据放入缓冲区,使用print方法发送表单数据,使用writeObject方法发送高级序列化对象。
String val1 = URLEncoder.encode(someVal1);
String val2 = URLEncoder.encode(someVal2);
String data = "param1=" + val1 +
    "&param2=" + val2; 
out.print(data);
out.flush(); 
  1. 设置Content-Length头部。
connection.setRequestProperty
    ("Content-Length", String.valueOf(byteStream.size()));
  1. 设置Content-Type头部,对于普通表单数据,应显式设置为 application/x-www-form-urlencoded ,对于序列化数据,该值无关紧要。
connection.setRequestProperty
    ("Content-Type", "application/x-www-form-urlencoded");
  1. 发送实际数据。
byteStream.writeTo(connection.getOutputStream());
  1. 打开输入流,通常使用BufferedReader处理ASCII或二进制数据,使用ObjectInputStream处理序列化Java对象。
BufferedReader in =
    new BufferedReader(new InputStreamReader
        (connection.getInputStream()));
  1. 读取结果,具体细节取决于服务器发送的数据类型。
String line;
while((line = in.readLine()) != null) {
    doSomethingWith(line);
}
5.5 绕过HTTP服务器

小程序可以使用以下方式直接与它们的主机服务器通信:
- 原始套接字
- 带有对象流的套接字
- JDBC
- RMI
- 其他网络协议

Java Web开发:JSP、Servlet与相关技术深度解析

6. 技术对比与总结

在Java Web开发中,JSP、Servlet、JavaBeans、自定义标签库、HTML表单以及小程序前端等技术各自发挥着重要作用,下面对它们进行对比总结。

技术 主要用途 优点 缺点
JSP与JavaBeans 动态生成网页内容,分离业务逻辑和表现层 代码复用性高,易于维护,可实现数据的封装和共享 可能导致JSP页面代码臃肿,尤其是包含大量脚本时
自定义JSP标签库 提高代码复用性和可维护性,封装复杂逻辑 使JSP页面更简洁,增强代码可读性 开发和维护标签库需要一定的技术成本
集成Servlet和JSP 实现业务逻辑处理和页面呈现的分离 遵循MVC架构,便于团队协作开发,提高代码可维护性 开发流程相对复杂,需要理解Servlet和JSP的交互机制
HTML表单 收集用户输入信息 简单易用,是Web应用中常见的交互方式 安全性较低,需要进行额外的验证和防护
小程序作为Servlet前端 实现客户端和服务器端的数据交互 可以提供丰富的用户界面和交互体验 小程序的兼容性和性能可能受到浏览器和设备的限制
7. 实际应用案例分析

为了更好地理解上述技术在实际中的应用,下面通过一个简单的用户注册系统案例进行分析。

7.1 需求分析

用户可以在注册页面输入用户名、密码、邮箱等信息,点击注册按钮后,系统将用户信息保存到数据库中,并显示注册成功页面。

7.2 技术选型
  • 使用HTML表单收集用户输入信息。
  • 使用Servlet处理用户注册请求,验证用户信息,并将数据保存到数据库中。
  • 使用JSP和JavaBeans实现页面的动态生成和数据的封装。
7.3 详细实现步骤
  1. 创建HTML注册表单页面(register.html)
<!DOCTYPE html>
<html>
<head>
    <title>用户注册</title>
</head>
<body>
    <form action="registerServlet" method="post">
        <label for="username">用户名:</label>
        <input type="text" id="username" name="username" required><br>
        <label for="password">密码:</label>
        <input type="password" id="password" name="password" required><br>
        <label for="email">邮箱:</label>
        <input type="email" id="email" name="email" required><br>
        <input type="submit" value="注册">
    </form>
</body>
</html>
  1. 创建JavaBean类(User.java)
import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private String password;
    private String email;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
  1. 创建Servlet类(RegisterServlet.java)
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取用户输入信息
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String email = request.getParameter("email");

        // 创建User对象
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        user.setEmail(email);

        // 模拟将用户信息保存到数据库
        // 这里可以添加实际的数据库操作代码

        // 将User对象存储到request中
        request.setAttribute("user", user);

        // 转发到注册成功页面
        request.getRequestDispatcher("registerSuccess.jsp").forward(request, response);
    }
}
  1. 创建JSP注册成功页面(registerSuccess.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.example.User" %>
<!DOCTYPE html>
<html>
<head>
    <title>注册成功</title>
</head>
<body>
    <h1>注册成功!</h1>
    <p>用户名:<jsp:getProperty name="user" property="username" /></p>
    <p>邮箱:<jsp:getProperty name="user" property="email" /></p>
</body>
</html>
7.4 案例总结

通过这个案例可以看到,HTML表单用于收集用户输入,Servlet处理业务逻辑,JavaBeans封装用户数据,JSP页面显示注册结果,实现了业务逻辑和表现层的分离,提高了代码的可维护性和可扩展性。

8. 技术发展趋势与展望

随着互联网技术的不断发展,Java Web开发技术也在不断演进。以下是一些可能的发展趋势:

  • 微服务架构 :将大型Web应用拆分成多个小型、自治的服务,每个服务专注于单一业务功能,提高开发效率和系统的可伸缩性。
  • 前后端分离 :前端使用现代前端框架(如Vue.js、React.js)构建用户界面,后端使用Servlet、Spring等框架处理业务逻辑,通过RESTful API进行数据交互,提高开发效率和用户体验。
  • 容器化和云原生 :使用Docker容器化应用,利用Kubernetes进行容器编排和管理,实现应用的快速部署和弹性伸缩。
  • 人工智能和大数据 :将人工智能和大数据技术应用到Java Web开发中,如智能推荐系统、数据分析和可视化等,为用户提供更个性化的服务。

在未来的Java Web开发中,我们需要不断学习和掌握新的技术,结合实际项目需求,灵活运用各种技术,以开发出更高效、更稳定、更具创新性的Web应用。

9. 学习建议与资源推荐

对于想要深入学习Java Web开发的开发者,以下是一些学习建议和资源推荐。

9.1 学习建议
  • 理论与实践结合 :不仅要学习理论知识,还要通过实际项目进行实践,加深对知识的理解和掌握。
  • 阅读优秀代码 :学习开源项目和优秀的代码示例,了解他人的编程思路和最佳实践。
  • 参与技术社区 :加入技术社区,与其他开发者交流经验,分享学习心得,及时了解行业动态。
  • 持续学习 :Java Web技术不断发展,要保持学习的热情,不断更新自己的知识体系。
9.2 资源推荐
  • 书籍 :《Effective Java》《Java核心技术》《Servlet与JSP核心编程》等。
  • 在线课程 :慕课网、网易云课堂、Coursera等平台上有很多优质的Java Web开发课程。
  • 开源项目 :GitHub上有许多优秀的Java Web开源项目,可以参考学习。
  • 技术博客 :InfoQ、开源中国、博客园等网站上有很多关于Java Web开发的技术文章和经验分享。

通过不断学习和实践,相信你能够掌握Java Web开发的核心技术,成为一名优秀的Java Web开发者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值