1. Tomcat
Tomcat(全称为Apache Tomcat)是一个开源的Java Servlet容器,也是一个能够托管Java Web应用的Web服务器。
Tomcat的主要功能是解析和执行Java Servlet、JavaServer Pages(JSP)和相关的Java EE技术。它可以作为一个独立的Web服务器运行,也可以与其他HTTP服务器(如Apache HTTP服务器)集成。
1.2 主要特点
-
Servlet容器:Tomcat是一个Java Servlet容器,能够解析和执行Servlet代码,处理HTTP请求和响应。
-
JSP支持:Tomcat还支持JavaServer Pages(JSP),它是一种动态网页技术,可以将Java代码嵌入到HTML页面中。
-
Java EE支持:Tomcat支持Java EE规范的一部分,包括JavaServer Faces(JSF)、Java Message Service(JMS)、Java Naming and Directory Interface(JNDI)等。
-
安全性:Tomcat提供了许多安全特性,包括SSL / TLS支持、访问控制、认证和授权等。
-
轻量级:相比于其他Java Web服务器,Tomcat是相对轻量级的,它的核心功能是专注于Servlet和JSP的执行。
-
可扩展性:Tomcat允许通过添加和配置插件(如valves、realm等)来扩展其功能。
-
开放源代码:Tomcat是开源的,遵循Apache License,可以自由使用、修改和分发。
1.3 目录结构
Apache Tomcat的目录结构如下:
-
bin:包含了Tomcat的可执行脚本和命令。
-
conf:包含了Tomcat的配置文件。
-
server.xml:主要的Tomcat服务器配置文件,包括端口配置、连接器配置、虚拟主机配置等。
-
web.xml:Servlet和JSP容器的配置文件,定义了Web应用的部署描述符。
-
catalina.properties: Tomcat的核心配置文件,定义了全局属性。
-
logging.properties: Tomcat的日志配置文件,定义了日志记录器的行为。
-
context.xml:在Web应用级别定义的上下文配置文件,包括数据源、参数、资源等。
-
-
lib:包含了Tomcat的类库文件。
-
logs:包含了Tomcat的日志文件。
-
temp:Tomcat在运行时使用的临时文件的存储位置。
-
webapps:Web应用程序的根目录。
-
ROOT: Tomcat默认的Web应用目录,对应根访问路径“/”。
-
examples:包含了一些示例的Web应用程序。
-
host-manager:默认的虚拟主机管理Web应用程序。
-
manager:默认的Web应用程序管理器。
-
各个部署的Web应用程序:每个部署的Web应用程序都会在该目录下创建一个对应的目录。
-
-
work:用于存储Tomcat在运行时生成的工作文件。
除了上述目录外,Tomcat还可以包含其他自定义目录和文件,这些可以根据实际需求进行配置和扩展。
热部署
热部署(Hot Deployment)是一种软件开发和部署的技术,可以在不停止应用程序的情况下,将新的代码、配置或资源文件应用到正在运行的系统中。通过热部署,开发人员可以更快地进行代码调试、功能添加和问题修复,而无需重新启动整个应用程序或停止正在运行的服务。
传统的部署方式通常要求开发人员修改代码或配置文件后重新编译和重新部署整个应用程序。这对于大型、复杂的应用来说可能会非常耗时,并且可能会导致程序长时间的停机。而热部署通过使用特定的工具或框架,允许开发人员在运行时更新应用程序的部分内容,而无需重新启动整个应用。
2. JavaWeb项目
Java Web项目是使用Java技术开发的Web应用程序。它通常由前端页面(HTML、CSS、JavaScript)和后端业务逻辑(Java代码)组成。以下是一个典型的Java Web项目的结构和要素:
-
前端页面:通常使用HTML、CSS和JavaScript来创建用户界面。前端页面负责展示数据和与用户进行交互,通过发送HTTP请求与后端进行通信。
-
后端代码:使用Java编写的代码,处理后台业务逻辑和数据处理。主要包括以下要素:
-
Servlet:处理HTTP请求和响应的Java类,实现运行在Web容器中的业务逻辑。
-
JSP(JavaServer Pages):将Java代码嵌入到HTML页面中,用于动态生成页面内容。
-
JavaBean: Java类用于封装和处理数据,通常与数据库交互。
-
数据库:持久化数据的存储介质,常用的数据库有MySQL、Oracle、PostgreSQL等。可以使用Java的JDBC或ORM框架(如Hibernate、MyBatis)来与数据库进行交互。
-
-
配置文件:用于配置项目的各项参数和设置,常见的配置文件包括:
-
web.xml:定义Web应用的部署描述符,包括Servlet、过滤器、监听器等的配置。
-
数据库配置文件:用于配置与数据库的连接参数、驱动等信息。
-
日志配置文件:定义日志记录器的行为,如输出级别、日志格式等。
-
-
第三方库和框架:Java Web开发通常会使用多个第三方库和框架来简化开发过程和提供额外的功能。常见的框架有Spring MVC、Struts2等,常见的库有Apache Commons、Gson、Apache HttpClient等。
-
构建工具:用于管理项目的依赖关系和构建过程,常用的构建工具有Maven和Gradle。
Java Web项目的开发流程通常包括需求分析、数据库设计、前后端开发、单元测试、集成测试、部署和维护等阶段。通过Java的跨平台特性和丰富的开发资源,Java Web项目可以在不同的操作系统和服务器上运行,并且具有可扩展性和稳定性。
2.1 JavaWeb项目目录结构
Java Web项目的目录结构可以根据具体的项目需求和开发团队的约定而有所不同。以下是一个典型的Java Web项目的基本目录结构:
-
src:包含项目的Java源代码。
-
com.example.project:项目的Java包,根据实际情况进行命名。
-
controller:控制器类,处理请求和响应。
-
model:数据模型类,用于封装数据。
-
dao:数据访问对象,与数据库进行交互。
-
service:业务逻辑处理类。
-
util:通用工具类。
-
-
-
web:包含项目的Web资源文件。
-
WEB-INF:包含不对外访问的资源。
-
classes:编译后的类文件。
-
lib:项目所需的第三方库和框架。
-
web.xml:项目的部署描述符。
-
-
css:存放CSS样式文件。
-
js:存放JavaScript代码文件。
-
img:存放项目所需的图片文件。
-
index.html:默认的首页文件。
-
-
config:包含项目的配置文件。
-
application.properties:项目的配置文件,包含数据库连接等配置项。
-
log4j.properties:日志配置文件。
-
-
test:项目的单元测试代码。
- com.example.project.test:测试用例的包。
-
lib:项目依赖的第三方库。
-
build.xml:Ant构建脚本文件,用于自动化构建、发布和部署项目。
-
dist:打包后的项目文件。
-
doc:项目的文档文件,包括需求分析、设计文档、API文档等。
2.2 第一个web项目的创建
项目结构
project07-servlet-begin
├─src
│ └─com
│ └─lowell
│ ├─fruit
│ │ ├─dao
│ │ │ │ FruitDAO.java
│ │ │ ├─base
│ │ │ │ BaseDAO.java
│ │ │ └─impl
│ │ │ FruitDAOImpl.java
│ │ └─pojo
│ │ Fruit.java
│ │
│ └─servlets
│ AddServlet.java
└─web
│ add.html
│
└─WEB-INF
web.xml
以上为该项目的目录结构:
com.lowell.fruit.pojo.Fruit
:定义了Fruit
实体,包含必要的字段,getter和setter函数以及toString()
com.lowell.fruit.dao
:定义了与数据库交互的相关操作com.lowell.servlets.addServlet
:创建一个servlet类,获取客户端发送的数据,并调用DAO中的方法完成添加add.html
:前端页面展示数据,用于发送Http请求与后端交互
addServlet
中doPost()
方法如下:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取请求参数
String fname = request.getParameter("fname");
String priceStr = request.getParameter("price");
Integer price = Integer.parseInt(priceStr);
String fcountStr = request.getParameter("fcount");
Integer fcount = Integer.parseInt(fcountStr);
String remark = request.getParameter("remark");
// 调用函数对数据库进行操作
FruitDAO fruitDAO = new FruitDAOImpl();
boolean flag = fruitDAO.addFruit(new Fruit(0 , fname , price , fcount , remark));
// 是否添加成功
System.out.println(flag ? "添加成功!" : "添加失败!");
}
项目启动
将项目启动后访问http://localhost:8080/project07/add.html
<img src="https://gitee.com/Lowell_37/picgoImg/raw/master/202309061105764.png" alt="image-20230906110559695" style="zoom:50%;" />
敲入如下数据后点击添加
<img src="https://gitee.com/Lowell_37/picgoImg/raw/master/202309061111301.png" alt="image-20230906111142230" style="zoom: 50%;" />
前端会发送一个HTTP请求给后端,后端接收到请求后,后执行doPOst()
,打印添加成功
<img src="https://gitee.com/Lowell_37/picgoImg/raw/master/202309061122242.png" alt="image-20230906112259153" style="zoom:50%;" />
<img src="https://gitee.com/Lowell_37/picgoImg/raw/master/202309061117413.png" alt="image-20230906111559425" style="zoom: 50%;" />
查询数据库,数据存在,我敲了两遍
<img src="https://gitee.com/Lowell_37/picgoImg/raw/master/202309061120842.png" alt="image-20230906112017764" style="zoom:50%;" />
完整过程
3. Servlet
Servlet是JavaEE中的一种技术,它是运行在Web服务器上的Java类,用于处理来自客户端的请求并生成响应。Servlet是高度可移植且开发灵活的,可以处理多种类型的请求,并且适用于构建动态的、交互式的Web应用程序。
Servlet的工作原理如下:
-
客户端发送HTTP请求到Web服务器。
-
Web服务器接收到请求,并将其转发给相应的Servlet处理。
-
Servlet处理请求,通过获取请求参数、调用业务逻辑、访问数据库等操作,生成需要返回给客户端的响应。
-
Servlet将响应发送回Web服务器。
-
Web服务器将响应发送回客户端。
3.1特点和优势
-
可移植性:Servlet是基于Java的,可以在任何支持Java的Web服务器上运行,如Tomcat、Jetty等。
-
灵活性:Servlet可以处理各种类型的请求,如GET、POST、PUT、DELETE等,且可以根据业务需求进行自定义扩展。
-
多线程支持:Servlet运行在多线程环境中,可以同时处理多个请求,提高系统的并发处理能力。
-
动态内容生成:Servlet可以根据请求动态生成内容,例如读取数据库数据并返回给客户端。
-
安全性:Servlet提供了安全机制,可以对请求进行身份验证和权限控制。
-
易于开发和维护:Servlet使用Java语言开发,具有丰富的开发资源和开发工具支持,易于编写和调试。
Servlet可以用于处理各种类型的HTTP请求,例如GET、POST、PUT和DELETE。它们可以访问请求的参数、cookie、会话等,以及生成动态内容作为响应。Servlet还可以处理文件上传、表单验证、会话管理等常见的Web应用程序需求。
为了编写一个Servlet,需要创建一个类,继承自javax.servlet.http.HttpServlet
,并重写doGet()
、doPost()
等方法来处理请求和生成响应。Servlet可以通过注解、XML配置或者在Web部署描述符(如web.xml文件)中进行配置和映射。
3.2 web.xml文件
web.xml
是Java Web应用程序的配置文件,位于Web应用的WEB-INF目录下。它是基于XML格式的,用于配置Web应用程序的部署描述信息和其他相关配置。
web.xml
文件包含了一系列的元素,每个元素对应一个配置项。以下是一些常用的元素及其作用:
web-app
: 是web.xml
文件的根元素,表示整个Web应用程序的配置信息。display-name
: 指定Web应用程序的显示名称。servlet
: 配置Servlet相关信息。包含servlet-name
和servlet-class
用于指定Servlet的名称和类路径。servlet-mapping
: 配置Servlet的映射信息。包含servlet-name
和url-pattern
,将Servlet的名称和URL模式进行映射。filter
: 配置过滤器相关信息。包含filter-name
和filter-class
用于指定过滤器的名称和类路径。filter-mapping
: 配置过滤器的映射信息。包含filter-name
和url-pattern
,将过滤器的名称和URL模式进行映射。listener
: 配置监听器相关信息。用于监听Web应用程序的生命周期事件。context-param
: 配置上下文参数,包含param-name
和param-value
,用于设置全局的上下文参数。error-page
: 配置错误页面,可以根据不同的HTTP错误代码指定不同的错误页面。
如下是第一个web项目的web.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>AddServlet</servlet-name>
<servlet-class>com.lowell.servlets.AddServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AddServlet</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>
</web-app>
从Java EE 6开始,Servlet 3.0引入了基于注解的配置方式,可以使用注解来替代部分web.xml
文件中的配置。可以采用如下注解来替代上面的配置,这样可以简化配置,并提高开发效率。
@WebServlet("/add")
public class AddServlet extends HttpServlet {
}
3.3 Servlet继承关系
继承关系
在Java Servlet API中,Servlet接口是所有Servlet类的父接口,它定义了处理请求和生成响应的方法。
javax.servlet.Servlet
接口
javax.servlet.GenericServlet
抽象类
javax.servlet.http.HttpServlet
抽象子类
接口Servlet
是所有Servlet
类的顶级父接口,它定义了一个核心的方法service(),用于处理HTTP请求。
抽象类GenericServlet
是Servlet
接口的实现类,它实现了Servlet
接口的所有方法,并提供了通用的Servlet处理的支持。
标准类HttpServlet
是抽象类GenericServlet
的子类,它进一步扩展了GenericServlet,在HTTP协议上提供了更便捷的处理方法。HttpServlet类为处理HTTP请求和生成HTTP响应提供了更高级的抽象。
相关方法
Servlet类中常用的方法有:
-
init(ServletConfig config)
: 初始化方法,容器在加载并实例化Servlet时调用此方法。可以在该方法中进行一些初始化工作,如读取配置文件、建立数据库连接等。 -
service(HttpServletRequest request, HttpServletResponse response)
: 服务方法,用于处理客户端的请求并生成响应。容器在接收到客户端的请求后会调用此方法,根据请求类型(GET/POST等)调用对应的doGet()
、doPost()
等方法。 -
doGet(HttpServletRequest request, HttpServletResponse response)
: 处理GET请求的方法。当服务方法调用时,如果请求类型为GET,容器会调用此方法处理请求。 -
doPost(HttpServletRequest request, HttpServletResponse response)
: 处理POST请求的方法。当服务方法调用时,如果请求类型为POST,容器会调用此方法处理请求。 -
destroy()
: 销毁方法,容器在关闭或重新加载Web应用程序时调用此方法。可以在该方法中执行一些清理操作,释放资源等。
此外,还有一些其他的方法用于处理请求和生成响应,例如:
-
HttpServletRequest
中的方法:getParameter(String name)
: 获取请求参数的值。getAttribute(String name)
: 获取请求属性的值。getSession()
: 获取与请求关联的会话对象。getInputStream()
: 获取请求的输入流。
-
HttpServletResponse
中的方法:setContentType(String type)
: 设置响应的Content-Type头字段。setStatus(int sc)
: 设置响应的状态码。setHeader(String name, String value)
: 设置响应头字段的值。getWriter()
: 获取用于发送响应数据的PrintWriter对象。
3.4 Servlet生命周期
Servlet的生命周期是指Servlet在运行过程中经历的一系列阶段和方法调用。Servlet生命周期主要包括以下三个阶段:
-
初始化阶段:
- Servlet容器加载并实例化Servlet类。
- 调用
Servlet
的init()
方法进行初始化,该方法在Servlet的整个生命周期中只被调用一次。 - 可以在
init()
方法中进行一些初始化工作,如读取配置文件、建立数据库连接等。
-
服务阶段:
- 一旦初始化完成,Servlet便进入可服务状态,可以响应客户端的请求。
- Servlet容器会根据请求的类型(GET/POST等)调用对应的
service()
方法。 service()
方法根据请求类型调用doGet()
、doPost()
等具体处理请求的方法。- 在
service()
方法中,Servlet可以根据请求的内容进行相应的处理,生成并返回响应给客户端。
-
销毁阶段:
- 当Servlet容器关闭或重新加载Web应用程序时,会调用Servlet的
destroy()
方法。 destroy()
方法在Servlet的生命周期结束时调用,用于执行一些清理操作,释放资源等。- 一旦
destroy()
方法被调用,Servlet实例将被销毁,不再对客户端请求进行响应。
- 当Servlet容器关闭或重新加载Web应用程序时,会调用Servlet的
生命周期示意图如下:
+---------------+
| Servlet Class |
+---------------+
|
|
v
+---------------+
| init() |
+---------------+
|
|
v
+----------------------+
| service() - doGet() |
| or doPost() |
+----------------------+
|
|
v
+----------------+
| destroy() |
+----------------+
3.5 init
方法
init()
方法是在Servlet生命周期的初始化阶段调用的,用于执行一些初始化的任务。一般情况下,init()
方法只会被调用一次,但如果Servlet配置了多个实例,则每个实例都会调用其对应的init()
方法。
在init()
方法中,你可以执行一些初始化任务,比如读取配置参数、初始化对象、建立数据库连接等。需要注意的是,init()
方法应该尽量保持简洁和快速,避免执行耗时的操作,以免影响Servlet容器的性能。
在Servlet生命周期中的初始化阶段,Servlet容器会调用Servlet的init()
方法来进行初始化操作。Servlet的init()
方法有两种重载形式:init()
和init(config)
。
-
init()
方法:- 这是Servlet的无参数初始化方法。
- 在调用
init()
方法之前,Servlet容器会实例化Servlet
对象。 - 在init()方法中,您可以执行一些初始化操作,例如加载配置文件、建立数据库连接等。
- 通常情况下,不带参数的
init()
方法会被调用,此时Servlet的初始化参数通过在web.xml
文件中进行配置。
-
init(config)
方法:- 这是Servlet的带有
ServletConfig
参数的初始化方法。 - 在调用
init(config)
方法之前,Servlet容器会实例化Servlet对象并将ServletConfig
对象传递给它。 - 通过
ServletConfig
对象,您可以获取Servlet的初始化参数、获取ServletContext
对象等。 - 您可以使用这些参数和对象执行特定的初始化任务,例如读取配置信息、创建资源等。
- 这是Servlet的带有
无论使用哪种初始化方法,init()
方法在Servlet的生命周期中只会被调用一次,通常是在Servlet被实例化后立即调用。这两个初始化方法可以根据需要选择使用,如果需要获取Servlet的配置信息,则可以使用带参数的init(config)
方法。
下面是一个示例的init(config)
方法的实现:
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// 读取初始化参数
String paramValue = getInitParameter("paramName");
// 获取ServletContext对象
ServletContext context = config.getServletContext();
// 执行其他初始化操作
// ...
}
3.6 ServletContext
ServletContext
是一个被Servlet
容器用来共享信息的对象,它代表整个Web应用程序的上下文环境。每个Web应用程序都有一个唯一的ServletContext
对象。
ServletContext
对象可以用来做以下几方面的事情:
-
上下文初始化参数:使用
ServletContext
对象可以获取和设置在部署描述符(web.xml)中配置的上下文初始化参数。可以使用getInitParameter(String name)
方法获取指定的参数值,使用getInitParameterNames()
方法获取所有参数名称。 -
全局资源访问:通过
ServletContext
对象获取Web应用程序中的任何资源,包括静态文件、图片、配置文件等。使用getResource(String path)
方法获取指定资源的URL,使用getResourceAsStream(String path)
方法获取资源的输入流。 -
请求调度:通过
ServletContext
对象进行请求调度,将请求转发到其他Servlet或JSP页面处理。使用getRequestDispatcher(String path)
方法获取请求调度器实例,然后调用其forward(ServletRequest request, ServletResponse response)
方法进行转发。 -
全局共享数据:使用
ServletContext
对象在整个Web应用程序中共享数据,不同的Servlet或JSP页面通过ServletContext对象来获取共享数据。可以使用setAttribute(String name, Object value)
方法设置共享数据,使用getAttribute(String name)
方法获取共享数据。 -
上下文范围的事件监听:ServletContext对象允许注册监听器来监听Web应用程序的上下文事件,如应用程序启动、关闭、属性变化等。可以通过在部署描述符中配置监听器,或者使用
addListener(Class<? extends EventListener> listenerClass)
方法动态添加监听器。
4. Session
在JavaWeb中,Session 是服务器端的一个机制,用于在多个请求之间存储和共享用户的数据。Session 是在用户第一次访问服务器时创建,服务器会为每个用户分配一个唯一的 Session ID,并将这个 Session ID 存储在一个名为 JSESSIONID 的 cookie 中发送给客户端。
4.1 特点
- 存储用户数据:Session 对象可以用来存储和获取用户的数据,这些数据可以是任意类型的对象。
- 生命周期:Session 的生命周期从创建到销毁,通常是用户访问服务器时创建,用户关闭浏览器或者 Session 超时时销毁。Session 生命周期中的数据会在多个请求之间共享。
- 唯一标识:每个用户的 Session 有一个唯一的 Session ID,该 ID 会在用户访问服务器时自动生成,并且存储在客户端的 cookie 中,用于在多个请求之间找到对应的 Session 对象。
- 会话状态管理:Session 可以跟踪用户的会话状态,例如用户的登录状态、购物车信息等。
通过在 Servlet 中使用 HttpSession 对象,可以获取和操作当前用户的 Session。下面是一个简单的示例:
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// 获取当前用户的 Session 对象
HttpSession session = request.getSession();
// 存储数据到 Session
session.setAttribute("username", "John");
// 从 Session 中获取数据
String username = (String) session.getAttribute("username");
System.out.println("Username: " + username);
// 移除 Session 中的数据
session.removeAttribute("username");
// 销毁 Session
session.invalidate();
}
}
在上面的示例中,我们首先通过 request.getSession()
方法获取当前用户的 Session 对象。然后使用 setAttribute()
方法存储一个名为 "username" 的数据到 Session 中,之后使用 getAttribute()
方法获取 Session 中的 "username" 数据,并打印出来。然后通过 removeAttribute()
方法从 Session 中移除 "username" 数据,最后通过 invalidate()
方法销毁当前会话的 Session。
Session 在 JavaWeb 中被广泛应用于用户身份验证、购物车管理、用户数据管理等场景,可以方便地实现跨请求的状态管理和数据共享。
4.2会话跟踪技术
会话跟踪是一种用于在多个请求之间跟踪用户会话状态的技术。它用于在无连接的 HTTP 协议中实现跨请求的状态管理。
以下是几种常见的会话跟踪技术:
-
Cookie:Cookie 是最常见和最简单的会话跟踪技术。通过在服务器的响应中设置一个名为 JSESSIONID 的 Cookie,服务器可以将一个唯一的 Session ID 发送给客户端,客户端在后续的请求中通过发送该 Cookie 来维持会话。服务器可以通过解析请求中的 Cookie 来识别和关联用户的会话信息。
-
URL 重写:URL 重写是另一种常见的会话跟踪技术,尤其在禁用 cookie 的情况下使用。服务器在生成页面中的 URL 时,在每个链接和表单的 URL 后追加 Session ID 参数。例如,
http://example.com/page?session_id=xyz123
。服务器通过解析请求中的 Session ID 参数来识别和关联用户的会话。 -
隐藏表单字段:在 HTML 表单中添加一个隐藏字段,用于存储 Session ID。在用户提交表单时,服务器可以通过读取该隐藏字段来获取当前会话的 Session ID。
-
静态隐藏表单字段:在每个页面中插入一个隐藏的表单字段,存储 Session ID。在每个从该页面派生的页面中,该隐藏字段的值会被自动包含在表单中,并随着表单的提交传递到服务器。
-
IP 地址和用户代理:通过分析用户的 IP 地址和浏览器的用户代理信息来识别和关联用户的会话状态。这种方法通常不太可靠,因为用户可能使用代理服务器、动态 IP 或者多个设备访问。
以上是一些常见的会话跟踪技术。在选择合适的技术时,需要考虑到安全性、可靠性、跨平台兼容性和用户体验等因素。根据具体的需求,可以选择或者结合使用这些技术来实现会话管理和状态跟踪。
4.3服务器内部转发与重定向
服务器内部转发(Server-side Forwarding)和客户端重定向(Client-side Redirection)是两种常见的Web开发中用于控制页面跳转的技术。
- 服务器内部转发:
服务器内部转发是指在服务器端直接将请求转发给其他URL处理,并将处理结果返回给客户端,客户端对此过程是不可见的。在Java中,常见的实现是通过Servlet的
RequestDispatcher
来进行服务器内部转发。 以下是服务器内部转发的步骤:- 在Servlet中获取
RequestDispatcher
对象,可以通过request.getRequestDispatcher("目标URL")
方法来获取。 - 使用
RequestDispatcher
的forward(request, response)
方法来将请求转发给目标URL处理。 - 目标URL的处理结果将直接返回给客户端,客户端对此过程是不可见的。
- 在Servlet中获取
- 客户端重定向:
客户端重定向是指在服务器端接收到请求后,向客户端发送一个特殊的响应,通知客户端发送新的请求到指定的URL。客户端会自动根据响应中的重定向信息发送新的请求。
以下是客户端重定向的步骤:
- 在服务器端接收到请求后,将客户端的响应状态码设置为
302 Found
(或其他适当的重定向状态码)。 - 在响应头中设置
Location
字段,值为重定向目标URL。 - 客户端收到响应后,会根据响应中的重定向信息自动发送新的请求到指定的URL。
- 在服务器端接收到请求后,将客户端的响应状态码设置为
4.4 线程不安全
线程不安全(Thread-Unsafe)是指当多个线程同时对共享的资源进行读写操作时,无法保证操作的正确性和一致性,可能会导致意外的结果或错误。
线程不安全可能会出现以下情况:
- 竞态条件(Race Condition):多个线程对共享资源进行读写操作时,由于执行顺序的不确定性,可能导致数据的错误或不一致。
- 数据竞争(Data Race):多个线程同时对一个变量进行读写操作,由于执行顺序的不确定性,可能导致数据的错误或不一致。
- 死锁(Deadlock):多个线程竞争资源而相互等待,导致程序无法继续执行。
- 活锁(Livelock):多个线程在执行过程中相互谦让,结果导致程序无法继续向前推进。
- 临界区问题:多个线程同时对共享资源进行写操作时,由于执行顺序的不确定性,可能导致数据的错误或不一致。
线程不安全的产生往往是由于多个线程访问共享资源时未进行适当的同步控制或互斥操作。在多线程编程中,为了保证线程安全,需要使用线程同步机制,如锁、信号量、条件变量等,以确保在某个时间点只有一个线程能够访问共享资源,从而避免竞态条件和数据竞争等问题。
为了解决线程不安全的问题,开发人员需要仔细分析和设计多线程程序的并发访问方式,使用合适的同步机制来保证共享资源的正确性和一致性。同时,还可以选择使用线程安全的数据结构和类库,或使用不可变对象来避免线程不安全问题的发生。
下面是一个简单的线程不安全的例子,演示了多个线程同时对一个公共变量进行自增操作的情况:
public class ThreadUnsafeExample {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) {
ThreadUnsafeExample example = new ThreadUnsafeExample();
Runnable runnable = () -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
};
// 创建多个线程同时执行自增操作
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Count: " + example.getCount());
}
}
ThreadUnsafeExample
类表示一个线程不安全的示例,主要包含一个共享的计数器count
。increment()
方法用于对计数器进行增加操作,但该操作不是原子操作,可能导致线程安全问题。getCount()
方法用于获取当前的计数器值。- 在
main
方法中,创建了一个ThreadUnsafeExample
示例对象example
。 - 创建一个
Runnable
对象runnable
,并实现run
方法,在该方法中调用example.increment()
来进行计数操作。 - 创建两个线程
thread1
和thread2
,分别使用runnable
对象作为线程的执行逻辑。 - 启动线程
thread1
和thread2
。 - 使用
thread1.join()
和thread2.join()
方法等待线程thread1
和thread2
结束。 - 最后打印最终的计数器值
example.getCount()
。注意,由于线程不安全,多次执行该程序可能会得到不同的结果,并不是预期的 2000。
通过使用同步机制(如锁)或者使用线程安全的数据结构,可以解决线程不安全的问题,保证多个线程对共享资源的安全访问。
4.5 Servlet在容器中是单例的、线程不安全的
在Java Servlet规范中,Servlet是在Servlet容器中运行的,容器负责创建、初始化和管理Servlet实例的生命周期。
-
单例的: Servlet在容器中是单例的,意味着容器只会创建一个Servlet实例,该实例会被多个线程共享。当第一个请求到达时,容器会创建Servlet实例并调用其
init()
方法进行初始化。之后的请求都会使用同一个Servlet实例来处理。这有助于节省内存和资源,但同时要注意保证Servlet的线程安全性。 -
线程不安全的: Servlet是由多个线程并发访问的,而Servlet容器并不保证对Servlet的并发性进行任何控制。这意味着在没有任何额外的同步机制的情况下,Servlet本身是线程不安全的。如果多个线程同时访问Servlet实例并修改其中的共享数据,可能导致数据不一致或线程安全问题。因此,在编写Servlet时应注意并发访问带来的潜在问题,采取适当的线程安全措施,如使用同步机制或避免使用共享状态。
4.6 保存作用域
四种常用的保存作用域的方法:
-
请求作用域(Request Scope):数据在同一次请求中共享,常用于将数据从一个Servlet传递到另一个Servlet或JSP页面。使用
request.setAttribute(name, value)
来设置值,在同一次请求内的任何位置都可以通过request.getAttribute(name)
来获取值。 -
会话作用域(Session Scope):数据在用户会话期间保持,常用于在不同的请求之间共享数据。使用
session.setAttribute(name, value)
来设置值,在任何请求中都可以通过session.getAttribute(name)
来获取值。 -
上下文作用域(Application Scope):数据在整个应用程序中都有效,对所有用户共享。通常用于存储全局配置信息或共享数据。使用
ServletContext.setAttribute(name, value)
来设置值,在应用程序的任何位置都可以通过ServletContext.getAttribute(name)
来获取值。 -
页面作用域(Page Scope):数据在当前JSP页面中有效,常用于将数据从Servlet传递到JSP页面。使用
pageContext.setAttribute(name, value)
来设置值,在当前JSP页面中可以通过pageContext.getAttribute(name)
来获取值。
5. MVC
MVC(Model-View-Controller)是一种软件设计模式,常用于构建用户界面(UI)和应用程序的分层架构。它将应用程序的逻辑分为三个组件:模型(Model)、视图(View)和控制器(Controller)。
-
模型(Model):应用程序的数据和业务逻辑。
它负责处理数据的获取、存储、验证和处理操作。模型通常是应用程序中最核心的部分,用于封装数据和提供与数据相关的操作。
-
视图(View):直接显示模型的数据给用户,并将用户的输入传递给控制器进行处理。
视图通常是用户界面的组成部分,可以是图形用户界面(GUI)、网页、报表等形式。它只负责数据的展示,不应包含业务逻辑。
-
控制器(Controller):模型和视图之间的中介,负责处理用户的输入并对模型进行更新。
它接收来自视图的用户请求,根据请求调用模型的相关操作,并将处理结果返回给视图进行显示。控制器也可以处理其他一些与用户交互相关的逻辑。
5.1 MVC基本流程
MVC 的基本流程如下:
-
用户与视图进行交互,如点击按钮或输入表单数据。
-
视图将用户的输入发送给控制器。
-
控制器接收到用户的输入后,根据输入调用相应的模型操作。
-
模型完成操作后,将结果返回给控制器。
-
控制器将处理结果传递给视图。
-
视图接收到处理结果后,将数据展示给用户。
MVC 的优势包括了逻辑分层清晰、各组件之间解耦、代码重用性高等。它能够提高开发效率、软件维护性和测试性,并促进团队协作,使应用程序的开发更加可靠和可扩展。MVC 被广泛应用于各类软件开发框架和平台,例如Java的Spring MVC、Ruby的Ruby on Rails等。
5.2 模型对象
5.2.1 pojo(值对象模型)
POJO(Plain Old Java Object)是一个纯粹的、简单的Java对象,它没有任何限制或依赖于特定的框架。POJO类通常只包含属性(字段)和对应的getter
和setter
方法,以及一些简单的业务逻辑。
以下是一些POJO类的特征和约定:
-
基本功能:POJO类通常仅包含属性和getter/setter方法,用于访问和修改属性的值。
-
无继承或接口依赖:POJO类不继承或实现任何框架特定的父类或接口,以便保持独立和可复用。
-
可序列化:POJO类往往需要实现
Serializable
接口,以便对象可以在网络或计算平台之间进行传输。 -
简单的业务逻辑:POJO类可能包含一些简单的业务逻辑,例如属性验证、计算或格式化等。
POJO类是Java对象的一种简化形式,它的目标是使代码更加简洁和易于阅读、理解和测试。POJO类可用于各种场景,如数据传输对象(DTO)、实体类、值对象等。
以下是一个示例POJO类的示例:
public class Person {
private String name;
private int age;
// Constructors, getters/setters
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// Other methods
}
以上示例是一个简单的Person
类,只包含了name
和age
两个属性,以及对应的getter
和setter
方法。
5.2.2 VO(值对象模型)
VO(Value Object)是一种特殊类型的Java对象,用于表示不可变的值。VO类中的属性只有getter
方法,没有setter
方法,以确保其不可变性。VO类通常用于封装一组相关的值,并在应用程序中传递和比较。
以下是一些VO类的特征和约定:
-
不可变性:VO类的属性应该是不可变的,即在对象创建后不能被修改。这可以通过将属性声明为
final
和不提供setter方法来实现。 -
重写equals和hashCode方法:VO类应该重写
equals
和hashCode
方法,以便能够正确比较两个对象的值。 -
实现Serializable接口(可选):如果VO对象需要在网络或不同平台之间进行传输,可以实现
Serializable
接口。 -
只包含属性和getter方法:VO类通常只包含属性和对应的getter方法,以便外部代码可以安全地访问属性的值。
-
不依赖于外部依赖:VO类应该独立于任何特定的框架或依赖,以便能够在不同的环境中使用。
VO类通常用于表示纯粹的数据值,而不包含业务逻辑或行为。它们通常用于数据传输对象(DTO)、值传递和数据持久化等场景。
以下是一个示例VO类的示例:
public class Address {
private final String street;
private final String city;
public Address(String street, String city) {
this.street = street;
this.city = city;
}
public String getStreet() {
return street;
}
public String getCity() {
return city;
}
// Equals and hashCode methods
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(street, address.street) && Objects.equals(city, address.city);
}
@Override
public int hashCode() {
return Objects.hash(street, city);
}
}
以上示例是一个简单的Address
类,它只包含了street
和city
两个属性,以及对应的getter
方法。注意,这个类是不可变的,因为属性是通过构造函数初始化并被声明为final
的。
5.2.3 DAO(数据访问模型)
DAO(Data Access Object)是一种设计模式,用于将数据访问逻辑与业务逻辑分离。DAO模式通过抽象出对数据存储的访问细节,提供了一种标准化的方式来访问数据,并隐藏了底层的数据访问实现细节。
DAO模式的主要目标是提供一种独立于数据源的接口,以便在更换数据源时能够轻松地修改数据访问逻辑而不影响其他代码。通过将数据操作封装在DAO接口中,业务逻辑可以与底层的数据访问逻辑解耦,提高代码的可维护性和可测试性。
以下是一些常见的DAO模式的特征和约定:
-
接口定义:DAO模式通常使用接口定义数据访问方法,以提供统一的访问接口。接口定义了对数据的增删改查等操作。
-
实现类:DAO接口通常有一个或多个实现类,每个实现类负责具体的数据访问实现。实现类可以使用不同的持久化技术(例如关系型数据库、NoSQL数据库等)来实现数据访问逻辑。
-
封装实体:DAO模式通常将实体对象与数据操作逻辑分离。实体对象是业务逻辑的载体,而DAO则负责将实体对象持久化到数据源中。
-
事务管理:DAO模式通常用于管理事务。它可以提供事务的开始、提交和回滚等方法,以确保数据访问的一致性和完整性。
-
异常处理:DAO模式通常使用异常来表示数据访问操作的失败情况。它可以定义一些特定的异常类,以便上层代码可以根据需要进行异常处理。
以下是一个简单的DAO模式示例,假设我们有一个User实体类,我们要使用DAO模式来对User对象进行数据访问:
public interface UserDao {
void save(User user);
User findById(int id);
List<User> findAll();
void update(User user);
void delete(User user);
}
public class UserDaoImpl implements UserDao {
// 数据访问实现
}
public class User {
private int id;
private String name;
// getter和setter方法
// ...
}
在上面的示例中,UserDao是DAO接口,定义了对User对象进行数据访问的方法。UserDaoImpl是UserDao接口的具体实现类,根据具体的数据访问技术(例如关系型数据库)实现了数据的保存、查找、更新和删除等操作。
5.2.4 BO(业务逻辑模型)
BO(Business Object)是业务对象的简称,它是面向业务的领域模型,用于表示和处理业务中的数据和操作。BO封装了业务逻辑和数据处理的方法,并提供对应的属性和行为。
BO具有以下特点:
-
对象化:BO代表了现实世界中的业务实体,如订单、用户、产品等。它们通过属性来描述其状态和特征,通过方法来定义其行为。
-
高度抽象:BO是从业务需求中抽象出来的,它关注业务本身,而不关注实现细节。BO将业务逻辑封装到内部,提供接口供外部调用,隐藏了具体的实现细节。
-
独立性:BO是独立于特定的技术和平台的。它可以在不同的系统和环境中使用,并且可以在不同的层次上进行复用,如数据访问、业务流程等。
-
可重用性:BO具有良好的可重用性,可以在不同的业务场景中被多次使用。通过组合和继承等方式,可以构建复杂的业务模型。
-
封装性:BO将相关的数据和方法封装到一个对象中,通过定义访问控制和数据保护机制,保证数据的安全性和一致性。
-
可测试性:BO的独立性和高度抽象性使得其易于测试。通过对各个BO进行单元测试或集成测试,可以验证其正确性和可靠性。
以下是一个简单的示例,展示了如何使用BO来表示和处理订单的业务数据和操作。
// 订单类(Order)
public class Order {
private String orderId;
private String customerName;
private List<Product> products;
// 构造函数
public Order(String orderId, String customerName, List<Product> products) {
this.orderId = orderId;
this.customerName = customerName;
this.products = products;
}
// 获取订单号
public String getOrderId() {
return orderId;
}
// 获取顾客姓名
public String getCustomerName() {
return customerName;
}
// 获取产品列表
public List<Product> getProducts() {
return products;
}
// 计算订单总价
public BigDecimal calculateTotalPrice() {
BigDecimal totalPrice = BigDecimal.ZERO;
for (Product product : products) {
totalPrice = totalPrice.add(product.getPrice());
}
return totalPrice;
}
// 提交订单
public void submitOrder() {
// 实现提交订单的逻辑
// ...
}
// 取消订单
public void cancelOrder() {
// 实现取消订单的逻辑
// ...
}
}
// 产品类(Product)
public class Product {
private String productId;
private String name;
private BigDecimal price;
// 构造函数
public Product(String productId, String name, BigDecimal price) {
this.productId = productId;
this.name = name;
this.price = price;
}
// 获取产品ID
public String getProductId() {
return productId;
}
// 获取产品名称
public String getName() {
return name;
}
// 获取产品价格
public BigDecimal getPrice() {
return price;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建产品列表
List<Product> products = new ArrayList<>();
products.add(new Product("P001", "手机", new BigDecimal("1999.99")));
products.add(new Product("P002", "耳机", new BigDecimal("99.99")));
// 创建订单对象
Order order = new Order("O001", "张三", products);
// 打印订单信息
System.out.println("订单号:" + order.getOrderId());
System.out.println("顾客姓名:" + order.getCustomerName());
System.out.println("产品列表:");
for (Product product : order.getProducts()) {
System.out.println(product.getProductId() + " - " + product.getName() + " - ¥" + product.getPrice());
}
System.out.println("订单总价:¥" + order.calculateTotalPrice());
// 提交订单
order.submitOrder();
// 取消订单
order.cancelOrder();
}
}
在上面的示例中,Order
类和Product
类分别表示订单和产品对象,通过属性和方法来描述它们的特征和行为。Order
类具有计算订单总价、提交订单和取消订单等业务逻辑方法。通过创建Order
对象并操作其方法,可以完成对订单的处理操作。
5.2.5 DTO(数据传输对象)
DTO(Data Transfer Object,数据传输对象)是一种用于传输数据的简单的Java类,主要用于在不同层之间传递数据。它通常与业务逻辑层(Service)和表示层(UI)之间进行数据传输和转换。DTO的主要目的是将多个领域对象的数据聚合到一个单一的、扁平的对象中,以便于传输和处理。
以下是一个简单的Java示例,展示了如何使用DTO来传输用户信息:
// 定义用户DTO类
public class UserDTO {
private String username;
private String email;
// 默认构造函数
public UserDTO() {}
// 构造函数
public UserDTO(String username, String email) {
this.username = username;
this.email = email;
}
// Getter和Setter方法
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
在上述示例中,UserDTO类定义了一个简单的用户数据传输对象,包含了用户名和电子邮件字段以及对应的Getter和Setter方法。
在实际应用中,我们可以在业务逻辑层(Service)中将领域对象转换为DTO对象,并在表示层(UI)中使用DTO对象传输数据。这样可以实现数据的解耦和安全性,同时减少网络传输量和数据处理复杂性。
例如,当前后端进行数据传输时,可以使用DTO来封装和传输用户的信息:
// 业务逻辑层的方法示例,将领域对象转换为DTO对象
public UserDTO getUserDTOById(int id) {
User user = userRepository.findById(id);
// 将User对象转换为UserDTO对象
UserDTO userDTO = new UserDTO(user.getUsername(), user.getEmail());
return userDTO;
}
// 表示层(UI)中的使用示例
UserDTO userDTO = userService.getUserDTOById(1);
System.out.println("Username: " + userDTO.getUsername());
System.out.println("Email: " + userDTO.getEmail());
6.反射技术
反射(Reflection)是一种在运行时检查、调查和修改程序结构的能力。通过反射,程序可以动态地获取对象的类型信息,调用对象的方法和访问对象的属性,而不需要事先知道对象的具体类型。
6.1 Java反射
Java反射是指在运行时动态地获取类的信息并操作对象的能力。通过反射,我们可以在运行时检查类的成员(字段、方法、构造函数等)并动态调用它们,而不需要在编译时知道类的具体信息。
在Java中,反射技术主要涉及以下几个类:Class、Method、Field、Constructor。
- Class类:代表了一个类或接口,在运行时可以通过该类获取其对应的方法、属性、构造函数等信息。
Class clazz = Person.class; // 获取Person类的Class对象
Class<?> clazz = Class.forName("com.example.Person"); // 通过类名获取Class对象
// 获取类名
String className = clazz.getName();
// 获取类的包信息
Package packageInfo = clazz.getPackage();
// 获取类的父类
Class superClass = clazz.getSuperclass();
// 获取类的接口列表
Class[] interfaces = clazz.getInterfaces();
- Method类:代表类中的方法,我们可以通过Method类进行方法的操作,例如调用、获取方法参数、获取方法修饰符等。
Method method = clazz.getMethod("methodName", parameterTypes); // 获取指定方法名和参数类型的Method对象
// 调用方法
Object result = method.invoke(obj, args); // obj为方法所属的对象,args为方法参数
// 获取方法的修饰符
int modifiers = method.getModifiers();
// 判断方法是否为静态方法
boolean isStatic = Modifier.isStatic(modifiers);
- Field类:代表类中的字段(属性),我们可以通过Field类进行字段的操作,例如获取字段值、设置字段值等。
Field field = clazz.getField("fieldName"); // 获取指定字段名的Field对象
// 获取字段的值
Object value = field.get(obj); // obj为字段所属的对象
// 设置字段的值
field.set(obj, value);
- Constructor类:代表类中的构造函数,我们可以通过Constructor类进行构造函数的操作,例如创建对象、获取构造函数参数等。
Constructor constructor = clazz.getConstructor(parameterTypes); // 获取指定参数类型的Constructor对象
// 创建对象
Object instance = constructor.newInstance(args); // args为构造函数参数
标签:Web,Java,JavaWeb,Servlet,方法,public,String
From: https://blog.51cto.com/LowellHere/8566614