目录
(8) IDEA使用Tomcat运行JavaWeb项目的原理
3. Servlet_jar包导入和Content-Type问题
(3)HttpServlet 抽象类(侧重于service方法的处理)
③sout乱码问题,设置JVM加载.class文件时使用UTF-8字符集
一. Tomcat
1. WEB服务器
Web服务器通常由硬件和软件共同构成。
硬件:电脑,提供服务供其它客户电脑访问
软件:电脑上安装的服务器软件,安装后能提供服务给网络中的其他计算机,将本地文件映射成一个虚拟的url地址供网络中的其他人访问。
常见的JavaWeb服务器:
Tomcat(Apache):当前应用最广的JavaWeb服务器
Jetty:更轻量级、更灵活的servlet容器
JBoss(Redhat红帽):支持JavaEE,应用比较广EJB容器 –> SSH轻量级的框架代替
GlassFish(Orcale):Oracle开发JavaWeb服务器,应用不是很广
Resin(Caucho):支持JavaEE,应用越来越广
Weblogic(Orcale):要钱的!支持JavaEE,适合大型项目
Websphere(IBM):要钱的!支持JavaEE,适合大型项目
2. Tomcat服务器
(1)简介
Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web应用服务器。
(2)安装
版本:企业用的比较广泛的是8.0和9.0,目前比较新正式发布版本是Tomcat10.0, Tomcat11仍然处于测试阶段。
JAVAEE 版本和Servlet版本号对应关系:Jakarta EE Releases | Download the Latest Version of Jakarta EE (Java EE) | The Eclipse Foundation
Servlet Version EE Version 6.1 Jakarta EE ? 6.0 Jakarta EE 10 5.0 Jakarta EE 9/9.1 4.0 JAVA EE 8 3.1 JAVA EE 7 3.1 JAVA EE 7 3.0 JAVAEE 6 Tomcat 版本和Servlet版本之间的对应关系:
Servlet Version Tomcat Version JDK Version 6.1 11.0.x 17 and later 6.0 10.1.x 11 and later 5.0 10.0.x (superseded) 8 and later 4.0 9.0.x 8 and later 3.1 8.5.x 7 and later 3.1 8.0.x (superseded) 7 and later 3.0 7.0.x (archived) 6 and later (7 and later for WebSocket) Tomcat官方网站:Apache Tomcat® - Welcome!
安装版:需要安装,一般不考虑使用。
解压版: 直接解压缩使用,我们使用的版本。
1. 打开官网
2. 配置JAVA_HOME
3. 将下载好的tomcat解压,点击进入文件夹
上面的命令窗口不关tomcat默认就是运行状态,关闭窗口就停止运行
解决中文乱码问题:
(3)Tomcat常见目录:
bin:该目录下存放的是二进制可执行文件,如果是安装版,那么这个目录下会有两个exe文件:
tomcat10.exe、tomcat10w\.exe,前者是在控制台下启动Tomcat,后者是弹出GUI窗口
启动Tomcat;如果是解压版,那么会有startup.bat和shutdown.bat文件,startup.bat
用来启动Tomcat,但需要先配置JAVA\_HOME环境变量才能启动,shutdawn.bat用来停止
Tomcat;
conf:这是一个非常非常重要的目录,这个目录下有四个最为重要的文件:
server.xml:配置整个服务器信息。例如修改端口号。默认HTTP请求的端口号是:8080
tomcat-users.xml:存储tomcat用户的文件,这里保存的是tomcat的用户名及密码,以及用
户的角色信息。可以按着该文件中的注释信息添加tomcat用户,然后就可以在Tomcat主页中进
入Tomcat Manager页面了
web.xml:部署描述符文件,这个文件中注册了很多MIME类型,即文档类型。这些MIME类型是客户端
与服务器之间说明文档类型的,如用户请求一个html网页,那么服务器还会告诉客户端浏览器响应的
文档是text/html类型的,这就是一个MIME类型。客户端浏览器通过这个MIME类型就知道如何处理它
了当然是在浏览器中显示这个html文件了。但如果服务器响应的是一个exe文件,那么浏览器就不可
能显示它,而是应该弹出下载窗口才对。MIME就是用来说明文档的内容是什么类型的!
context.xml:对所有应用的统一配置,通常我们不会去配置它。
lib:Tomcat的类库,里面是一大堆jar文件。如果需要添加Tomcat依赖的jar文件,可以把它放到这个目
录中,当然也可以把应用依赖的jar文件放到这个目录中,这个目录中的jar所有项目都可以共享之,
但这样你的应用放到其他Tomcat下时就不能再共享这个目录下的jar包了,所以建议只把Tomcat需要
的jar包放到这个目录下
logs:这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常
也会记录在日志文件中。
temp:存放Tomcat的临时文件,这个目录下的东西可以在停止Tomcat后删除!
webapps:存放web项目的目录,其中每个文件夹都是一个项目**;如果这个目录下已经存在了目录,那么
都是tomcat自带的项目。其中ROOT是一个特殊的项目,在地址栏中访问:
http://127.0.0.1:8080,没有给出项目目录时,对应的就是ROOT项
目.http://localhost:8080/examples,进入示例项目。其中examples"就是项目名,即文件
夹的名字。
work:运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的!可以把这个目录下的
内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过
JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这
个目录下。
LICENSE:许可证。
NOTICE:说明文件。
(4)WEB项目的标准结构:
一个标准的可以用于发布的WEB项目标准结构如下:
app 本应用根目录
static 非必要目录,约定俗成的名字,一般在此处放静态资源 ( css js img)
WEB-INF 必要目录,必须叫WEB-INF,受保护的资源目录,浏览器通过url不可以直接访问的目录
classes 必要目录,src下源代码,配置文件,编译后会在该目录下,web项目中如果没有源码,则该目录不会出现
lib 必要目录,项目依赖的jar编译后会出现在该目录下,web项目要是没有依赖任何jar,则该目录不会出现
web.xml 必要文件,web项目的基本配置文件. 较新的版本中可以没有该文件,但是学习过程中还是需要该文件
index.html 非必要文件,index.html/index.htm/index.jsp为默认的欢迎页
url的组成部分和项目中资源的对应关系:
(5) WEB项目部署的方式
方式1: 直接将编译好的项目放在webapps目录下
方式2: 将编译好的项目打成war包放在webapps目录下,tomcat启动后会自动解压war包(其
实和第一种一样)
方式3: 可以将项目放在非webapps的其他目录下,在tomcat中通过配置文件指向app的实际
磁盘路径
在磁盘的自定义目录上准备一个app
在tomcat的conf下的Catalina/localhost目录下准备一个app.xml文件
<!--
path: 项目的访问路径,也是项目的上下文路径,就是在浏览器中,输入的项目名称
docBase: 项目在磁盘中的实际路径
-->启动tomcat访问测试即可
例如:在磁盘的自定义目录上准备一个app
在tomcat的conf下的Catalina/localhost目录中准备一个app.xml文件
启动tomcat访问测试即可
(6) IDEA关联本地Tomcat
可以在创建项目前设置本地tomcat,也可以在打开某个项目的状态下找到settings
选择tomcat服务器
选择tomcat的安装目录并点击确定
(7)IDEA创建web工程并部署运行
推荐先创建一个空项目,这样可以在一个空项目下同时存在多个modules,不用后续来回切换之前的项目,当然也可以忽略此步直接创建web项目
检查项目的SDK,语法版本,以及项目编译后的输出目录:
为项目添加Tomcat依赖
添加 framework support
选择Web Application 注意Version,勾选 Create web.xml
IDEA部署-运行web项目
点击+号,添加本地tomcat服务器
绿色箭头是正常运行模式,"小虫子"是debug运行模式
解决查看日志状态乱码问题:
打开tomcat的conf/logging.properties文件修改UTF-8为GDK
(8) IDEA使用Tomcat运行JavaWeb项目的原理
idea并没有直接进将编译好的项目放入tomcat的webapps中
idea根据关联的tomcat,创建了一个tomcat副本,将项目部署到了这个副本中
idea的tomcat副本在C:\用户\当前用户\AppData\Local\JetBrains\IntelliJIdea2022.2\tomcat\中
idea的tomcat副本并不是一个完整的tomcat,副本里只是准备了和当前项目相关的配置文件而已
idea启动tomcat时,是让本地tomcat程序按照tomcat副本里的配置文件运行
idea的tomcat副本部署项目的模式是通过conf/Catalina/localhost/*.xml配置文件的形式实现项目部署的
二. Servlet
1. Servlet简介
动态资源和静态资源
静态资源:
无需在程序运行时通过代码运行生成的资源,在程序运行之前就写好的资源. 例如:html css
js img ,音频文件和视频文件
动态资源:
需要在程序运行时通过代码运行生成的资源,在程序运行之前无法确定的数据,运行时动态
生成,例如Servlet,Thymeleaf ... ...
动态资源指的不是视图上的动画效果或者是简单的人机交互效果
生活举例:
去蛋糕店买蛋糕
直接买柜台上已经做好的 : 静态资源
和柜员说要求后现场制作 : 动态资源
Servlet简介:
Servlet (server applet) 是运行在服务端(tomcat)的Java小程序,是sun公司提供一套定义动态资源规范; 从代码层面上来讲Servlet就是一个接口
用来接收、处理客户端请求、响应给浏览器的动态资源。在整个Web应用中,Servlet主要负责接收处理请求、协同调度功能以及响应数据。我们可以把Servlet称为Web应用中的控制器
不是所有的JAVA类都能用于处理客户端请求,能处理客户端请求并做出响应的一套技术标准就是Servlet
Servlet是运行在服务端的,所以 Servlet必须在WEB项目中开发且在Tomcat这样的服务容器中运行
请求响应与HttpServletRequest和HttpServletResponse之间的对应关系:
2. Servlet开发流程
目标:校验注册时,用户名是否被占用. 通过客户端向一个Servlet发送请求,携带username,如果用户名是'atguigu',则向客户端响应 NO,如果是其他,响应YES
开发过程:
步骤1: 开发一个web类型的module,过程参照之前
步骤2: 开发一个UserServlet
package com.atguigu.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * servlet开发流程 * 1. 创建javaWEB项目,同时将tomcat添加为当前项目的依赖 * 2. 重写service方法 service(HttpServletRequest req,HttpServletResponse resp) * 3. 在service方法中定义业务处理代码 * 4. 在web.xml中,配置Servlet对应的请求映射路径 */ public class UserServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 从request对象获取请求中的任何信息(username参数) String username=req.getParameter("username"); //根据参数名称获取参数值,无论参数是在url还是在请求体中 //2. 处理业务代码 String info="Yes"; if("atguigu".equals(username)){ info="No"; } //3. 将要响应的数据放入response //设置Content-Type响应头 resp.setContentType("text/html"); PrintWriter writer=resp.getWriter(); //该方法返回的是一个向响应体中打印字符串的打印流 writer.write(info); } }
自定义一个类,要继承HttpServlet类
重写service方法,该方法主要就是用于处理用户请求的服务方法
HttpServletRequest 代表请求对象,是有请求报文经过tomcat转换而来的,通过该对象可以获取请求中的信息
HttpServletResponse 代表响应对象,该对象会被tomcat转换为响应的报文,通过该对象可以设置响应中的信息
Servlet对象的生命周期(创建,初始化,处理服务,销毁)是由tomcat管理的,无需我们自己new
HttpServletRequest HttpServletResponse 两个对象也是有tomcat负责转换,在调用service方法时传入给我们用的
步骤3: 在web.xml为UseServlet配置请求的映射路径
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version="6.0"> <!--配置Servlet类,并起一个别名--> <!--servlet-class 告诉Tomcat对应的要实例化的Servlet类--> <!--servlet-name 用于关联请求的映射路径--> <servlet> <servlet-name>userServlet</servlet-name> <!--给UserServlet起一个别名--> <servlet-class>com.atguigu.servlet.UserServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>userServlet</servlet-name> <!--关联别名--> <url-pattern>/userServlet</url-pattern> <!--映射路径--> </servlet-mapping> </web-app>
Servlet并不是文件系统中实际存在的文件或者目录,所以为了能够请求到该资源,我们需要为其配置映射路径
servlet的请求映射路径配置在web.xml中
servlet-name作为servlet的别名,可以自己随意定义,见名知意就好
url-pattern标签用于定义Servlet的请求映射路径
一个servlet可以对应多个不同的url-pattern
多个servlet不能使用相同的url-pattern
url-pattern中可以使用一些通配写法
/ 表示通配所有资源,不包括jsp文件
/* 表示通配所有资源,包括jsp文件
/a/* 匹配所有以a前缀的映射路径
*.action 匹配所有以action为后缀的映射路径
步骤4: 开发一个form表单,向servlet发送一个get请求并携带username参数
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="get" action="userServlet"> 用户名:<input type="text" name="username"> <br> <input type="submit" value="校验"> </form> </body> </html>
启动项目,访问index.html ,提交表单测试
3. Servlet_jar包导入和Content-Type问题
servlet-api.jar导入问题:
servlet-api编码的时候需要,运行的时候,在服务器的环境中,由服务软件(Tomcat)提供因此,我们的JAVAWEB项目中,在打包/构建的时候,是无需携带servlet-api的jar包 Content-Type响应头的问题:
MIME类型响应头 媒体类型,文件类型,响应的数据类型
MIME类型用于告诉客户端响应的数据是什么类型的数据,客户端以此类型决定用什么方式解析响应体
设置Content-Type响应头:response.setContentType("text/html")
4. 注解方式配置Servlet
使用@WebServlet注解替换Servlet配置
web.xml中的这些配置可以省略 <servlet> <servlet-name>userServlet</servlet-name> <servlet-class>com.atguigu.servlet.UserServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>userServlet</servlet-name> <url-pattern>/userServlet</url-pattern> </servlet-mapping> 直接在这用@WebServlet("/xxx")代替 @WebServlet("/userServlet") public class UserServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 从request对象获取请求中的任何信息(username参数) String username=req.getParameter("username"); //根据参数名称获取参数值,无论参数是在url还是在请求体中 //2. 处理业务代码 String info="<h1>Yes</h1>"; if("atguigu".equals(username)){ info="No"; } //3. 将要响应的数据放入response //设置Content-Type响应头 resp.setContentType("text/html"); PrintWriter writer=resp.getWriter(); //该方法返回的是一个向响应体中打印字符串的打印流 writer.write(info); } }
5. Servlet生命周期
什么是Servlet的生命周期:
应用程序中的对象不仅在空间上有层次结构的关系,在时间上也会因为处于程序运行过程中的不同阶段而表现出不同状态和不同行为——这就是对象的生命周期。
简单的叙述生命周期,就是对象在容器中从开始创建到销毁的过程。
Servlet容器:
Servlet对象是Servlet容器创建的,生命周期方法都是由容器(目前我们使用的是Tomcat)调用的。这一点和我们之前所编写的代码有很大不同。在今后的学习中我们会看到,越来越多的对象交给容器或框架来创建,越来越多的方法由容器或框架来调用,开发人员要尽可能多的将精力放在业务逻辑的实现上。
Servlet主要的生命周期执行特点:
生命周期 对应方法 执行时机 执行次数 构造对象 构造器 第一次请求或者容器启动 1 初始化 init() 构造完毕后 1 处理服务 service(HttpServletRequest req,HttpServletResponse resp) 每次请求 多次 销毁 destory() 容器关闭 1 生命周期测试:
开发servlet代码: package com.atguigu.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; //注解方法配置Servlet //@WebServlet(value = "/ServletLifeCycle",loadOnStartup = 100) public class ServletLifeCycle extends HttpServlet { public ServletLifeCycle(){ System.out.println("构造器"); } @Override public void init() throws ServletException { System.out.println("初始化"); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("服务"); } @Override public void destroy() { System.out.println("destory"); } } 配置Servlet: <servlet> <servlet-name>ServletLifeCycle</servlet-name> <servlet-class>com.atguigu.servlet.ServletLifeCycle</servlet-class> <load-on-startup>1</load-on-startup> <!--默认值是-1 含义是tomcat启动时不会实例化该servlet--> <!--其他正整数 含义是tomcat在启动时,实例化该servlet的顺序--> <!--如果序号冲突了tomcat会自动协调启动顺序--> </servlet> <servlet-mapping> <servlet-name>ServletLifeCycle</servlet-name> <url-pattern>/ServletLifeCycle</url-pattern> </servlet-mapping>
生命周期总结:
Servlet对象在容器中是单例的
容器是可以处理并发的用户请求的,每个请求在容器中都会开启一个线程
多个线程可能会使用相同的Servlet对象,Servlet的成员变量在多个线程栈之中是共享的,所以不建议在service方法中修改成员变量,在并发请求时,会引发线程安全问题
load-on-startup中定义的正整数表示实例化顺序,如果数字重复了,容器会自行解决实例化顺序问题,但是应该避免重复
Tomcat容器中,已经定义了一些随系统启动实例化的servlet,我们自定义的servlet的load-on-startup尽量不要占用数字1-5
default-servlet 用于加载静态资源的servlet,默认随服务启动,默认启动序号为1
6. Servlet继承结构
(1)Servlet 接口
public interface Servlet {
//初始化方法,构造完毕后,由tomcat自身调用完成初始化功能的方法
void init(ServletConfig var1) throws ServletException;
//获得 ServletConfig对象的方法
ServletConfig getServletConfig();
//接收用户请求,向用于响应信息的方法
void service(ServletRequest var1, ServletResponse var2) throws ServletException,
IOException;
返回Servlet宇符串形式描述信息的方法
String getServletInfo();
//Servlet在回收前,由tomcat调用的销毁方法,往往用于做资源的释放工作
void destroy();
}
(2) GenericServlet 抽象类
ublic abstract class GenericServlet implements Servlet{
private transient ServletConfig config;
public void destroy() {
//将抽象方法,重写为普通方法,在方法内部没有任何的实现代码
//平庸实现
}
//tomcat在调用init方法时,会读取配置信息进入一个ServletConfig对象并将该对象传入init方法
public void init(ServletConfig config) throws ServletException {
this.config = config; //将config对象存储为当前的属性
this.init(); //调用了重载的无参的init
}
//重载的初始化方法,我们重写初始化方法时对应的方法
public void init() throws ServletException {}
//再次抽象声明service方法
public abstract void service(ServletRequest var1, ServletResponse var2)
throws ServletException, IOException;
//返回ServletConfig的方法
public ServletConfig getServletConfig() {
return this.config;
}
}
(3)HttpServlet 抽象类(侧重于service方法的处理)
//参数的父传子,调用重载service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException,
IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
//参数的父传子
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException(lStrings.getString("http.non_http"));
}
//调用重载service方法
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
//获取请求的方式
String method = req.getMethod();
long lastModified;
//根据请求方式,调用对应do...方法
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
//故意响应405
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_get_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
//故意响应405
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_post_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
(4)自定义Servlet
继承关系图解:
1. 部分程员推荐在servlet中重写do***方法处理请求
理由:service方法中可能做了一些处理,如果我们直接重写servicel的话,
父类中service方法处理的功能则失效
2. 目前直按重写service也没有什么问题
3. 后续使用了SpringMVC框架后,我们则无需继承HttpServlet,处理访求的方法也无需是do***service
4. 如果doGet和doPost方法中,我们定义的代码都一样,可以让一个方法直按调用另一个方法
继承HttpServlet后,要么重写service方法要么重写doGet/doPost
(5) ServletConfig的使用
ServletConfig是什么:
为Servlet提供初始配置参数的一种对象,每个Servlet都有自己独立唯一的ServletConfig对象
容器会为每个Servlet实例化一个ServletConfig对象,并通过Servlet生命周期的init方法传入给Servlet作为属性
ServletConfig是一个接口,定义了如下API:
package jakarta.servlet; import java.util.Enumeration; public interface ServletConfig { String getServletName(); ServletContext getServletContext(); String getInitParameter(String var1); Enumeration<String> getInitParameterNames(); }
方法名 作用 getServletName() 获取<servlet-name>HelloServlet</servlet-name>定义的Servlet名称 getServletContext() 获取ServletContext对象 getInitParameter() 获取配置Servlet时设置的『初始化参数』,根据名字获取值 getInitParameterNames() 获取所有初始化参数名组成的Enumeration对象 测试代码:
1. 定义Servlet1: //注解方式配置Servlet1 @WebServlet( // urlPatterns = "/serlvet1", // initParams = {@WebInitParam(name="keya",value = // "valuea"),@WebInitParam(name="keyb",value = "valueb")} //) public class Servlet1 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletConfig config = getServletConfig(); //获取初始配置信息即可 //根据参数名获取参数值 String keya=config.getInitParameter("keya"); System.out.println("keya:"+keya); //获取所有的初始参数的名字 //hasMoreElements 判断有没有下一个参数,如果有返回true没有返回false //nextElement 取出下一个元素,向下移动游标 Enumeration<String> initParameterNames = config.getInitParameterNames(); while (initParameterNames.hasMoreElements()){ String pname=initParameterNames.nextElement(); System.out.println(pname+"="+getInitParameter(pname)); } } } 定义Servlet2: //注解方式配置Servlet2 @WebServlet( // urlPatterns = "/serlvet2", // initParams = {@WebInitParam(name="keya",value = // "value2a"),@WebInitParam(name="keyb",value = "value2b")} //) public class Servlet2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletConfig config = getServletConfig(); //获取初始配置信息即可 //根据参数名获取参数值 String keya=config.getInitParameter("keya"); System.out.println("keya:"+keya); //获取所有的初始参数的名字 //hasMoreElements 判断有没有下一个参数,如果有返回true没有返回false //nextElement 取出下一个元素,向下移动游标 Enumeration<String> initParameterNames = config.getInitParameterNames(); while (initParameterNames.hasMoreElements()){ String pname=initParameterNames.nextElement(); System.out.println(pname+"="+getInitParameter(pname)); } } } 配置Servlet: <servlet> <servlet-name>servlet1</servlet-name> <servlet-class>com.atguigu.servlet.Servlet1</servlet-class> <!--配置Servlet1的初始参数--> <init-param> <param-name>keya</param-name> <param-value>valuea</param-value> </init-param> <init-param> <param-name>keyb</param-name> <param-value>valueb</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>servlet1</servlet-name> <url-pattern>/servlet1</url-pattern> </servlet-mapping> <servlet> <servlet-name>servlet2</servlet-name> <servlet-class>com.atguigu.servlet.Servlet2</servlet-class> <!--配置Servlet2的初始参数--> <init-param> <param-name>key2a</param-name> <param-value>value2a</param-value> </init-param> <init-param> <param-name>key2b</param-name> <param-value>value2b</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>servlet2</servlet-name> <url-pattern>/servlet2</url-pattern> </servlet-mapping>
(6) ServletContext的使用
ServletContext是什么:
ServletContext对象有称呼为上下文对象,或者叫应用域对象(后面统一讲解域对象)
容器会为每个app创建一个独立的唯一的ServletContext对象
ServletContext对象为所有的Servlet所共享
ServletContext可以为所有的Servlet提供初始配置参数
配置ServletContext参数:
<context-param> <param-name>paramA</param-name> <param-value>valueA</param-value> </context-param> <context-param> <param-name>paramB</param-name> <param-value>valueB</param-value> </context-param>
在Servlet中获取ServletContext并获取参数:
public class Servlet1 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("------------ServletConfig获取参数--------------"); ServletConfig config = getServletConfig(); //获取初始配置信息即可 //根据参数名获取参数值 String keya=config.getInitParameter("keya"); System.out.println("keya:"+keya); //获取所有的初始参数的名字 //hasMoreElements 判断有没有下一个参数,如果有返回true没有返回false //nextElement 取出下一个元素,向下移动游标 Enumeration<String> initParameterNames = config.getInitParameterNames(); while (initParameterNames.hasMoreElements()){ String pname=initParameterNames.nextElement(); System.out.println(pname+"="+getInitParameter(pname)); } System.out.println("------------ServletContext获取参数--------------"); // 从ServletContext中获取为所有的Servlet准备的参数 ServletContext servletContext1=getServletContext(); ServletContext servletContext2=config.getServletContext(); ServletContext servletContext3=req.getServletContext(); System.out.println(servletContext3==servletContext2); //true System.out.println(servletContext3==servletContext1); //true String encoding=servletContext1.getInitParameter("encoding"); System.out.println("encoding:"+encoding); // 获取所有参数名 Enumeration<String> parameterNames=servletContext1.getInitParameterNames(); // 迭代并获取参数名 while (parameterNames.hasMoreElements()){ String pname=parameterNames.nextElement(); System.out.println(pname+"="+servletContext1.getInitParameter(pname)); } } }
获取文件路径和上下文:
ServletContext servletContext = getServletContext(); //获得一个指向项目部署位置下的某个文件/目录的磁盘真实路径的API String path=servletContext.getRealPath("upload"); System.out.println(path); //获得项目部署的上下文跻径项目的访问路径 String contextPath=servletContext.getContextPath(); System.out.println(contextPath);
域对象的相关API:
域对象: 一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同
ServletContext代表应用,所以ServletContext域也叫作应用域,是webapp中最大的域,可以在本应用内实现数据的共享和传递
webapp中的三大域对象,分别是应用域,会话域,请求域
后续我们会将三大域对象统一进行讲解和演示
,三大域对象都具有的API如下
API 功能解释 void setAttribute(String key,Object value); 向域中存储/修改数据 Object getAttribute(String key); 获得域中的数据 void removeAttribute(String key); 移除域中的数据 ServletContext servletContext = getServletContext(); servletContext.setAttribute("name","liming"); //向域中添加数据 String name=(String)servletContext1.getAttribute("name"); //获取域中的数据 System.out.println(name); servletContext.removeAttribute("name"); //删除域中的数据
(7)HttpServletRequest
HttpServletRequest是什么:
HttpServletRequest是一个接口,其父接口是ServletRequest
HttpServletRequest是Tomcat将请求报文转换封装而来的对象,在Tomcat调用service方法时传入
HttpServletRequest代表客户端发来的请求,所有请求中的信息都可以通过该对象获得
HttpServletRequest常见API:
获取请求行信息相关(方式,请求的url,协议及版本):
API 功能解释 StringBuffer getRequestURL(); 获取客户端请求的url String getRequestURI(); 获取客户端请求项目中的具体资源 int getServerPort(); 获取客户端发送请求时的端口 int getLocalPort(); 获取本应用在所在容器的端口 int getRemotePort(); 获取客户端程序的端口 String getScheme(); 获取请求协议 String getProtocol(); 获取请求协议及版本号 String getMethod(); 获取请求方式 获得请求头信息相关:
API 功能解释 String getHeader(String headerName); 根据头名称获取请求头 Enumeration<String> getHeaderNames(); 获取所有的请求头名字 String getContentType(); 获取content-type请求头 获得请求参数相关:
API 功能解释 String getParameter(String parameterName); 根据请求参数名获取请求单个参数值 String[] getParameterValues(String parameterName); 根据请求参数名获取请求多个参数值数组 Enumeration<String> getParameterNames(); 获取所有请求参数名 Map<String, String[]> getParameterMap(); 获取所有请求参数的键值对集合 BufferedReader getReader() throws IOException; 获取读取请求体的字符输入流 ServletInputStream getInputStream() throws IOException; 获取读取请求体的字节输入流 int getContentLength(); 获得请求体长度的字节数 其他API:
API 功能解释 String getServletPath(); 获取请求的Servlet的映射路径 ServletContext getServletContext(); 获取ServletContext对象 Cookie[] getCookies(); 获取请求中的所有cookie HttpSession getSession(); 获取Session对象 void setCharacterEncoding(String encoding) ; 设置请求体字符集 例如:
//行相关 get/post url http/1.1 System.out.println(req.getMethod()); //获取请求方式 System.out.println(req.getProtocol());//获取请求协议及版本 System.out.println(req.getScheme());//获取请求协议 System.out.println(req.getRequestURI());//获取请求的url 项目内的资源路径 System.out.println(req.getRequestURL());//获取请求的url 项目内资源的完整路径 /* URI 统一资源标识符 例如:/demo03/servlet2 资源定位的要求和规范 URL 统一资源定位符 例如:http://localhost:8080/demo03/servlet2 一个具体的资源路径 */ System.out.println(req.getLocalPort());//本应用容器的端口号 System.out.println(req.getRemoteHost());//客户端发请求时使用的端口号 System.out.println(req.getServerPort());//客户端软件的端口号 //头相关 key:value key:value... ... //根据名字单独获取某个请求头 String accept= req.getHeader("Accept"); System.out.println(accept); //获取本次请求中所有的请求头的名字 Enumeration<String> headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); System.out.println("headerName: "+req.getHeader(headerName)); }
(8)HttpServletResponse
HttpServletResponse是什么:
HttpServletResponse是一个接口,其父接口是ServletResponse
HttpServletResponse是Tomcat预先创建的,在Tomcat调用service方法时传入
HttpServletResponse代表对客户端的响应,该对象会被转换成响应的报文发送给客户端,通过该对象我们可以设置响应信息
HttpServletResponse的常见API:
设置响应行相关:
API 功能解释 void setStatus(int code); 设置响应状态码 设置响应头相关:
API 功能解释 void setHeader(String headerName, String headerValue); 设置/修改响应头键值对 void setContentType(String contentType); 设置content-type响应头及响应字符集(设置MIME类型) 设置响应体相关:
API 功能解释 PrintWriter getWriter() throws IOException; 获得向响应体放入信息的字符输出流 ServletOutputStream getOutputStream() throws IOException; 获得向响应体放入信息的字节输出流 void setContentLength(int length); 设置响应体的字节长度,其实就是在设置content-length响应头 其他API:
API 功能解释 void sendError(int code, String message) throws IOException; 向客户端响应错误信息的方法,需要指定响应码和响应信息 void addCookie(Cookie cookie); 向响应体中增加cookie void setCharacterEncoding(String encoding); 设置响应体字符集 MIME类型:
MIME类型,可以理解为文档类型,用户表示传递的数据是属于什么类型的文档
浏览器可以根据MIME类型决定该用什么样的方式解析接收到的响应体数据
可以这样理解: 前后端交互数据时,告诉对方发给对方的是 html/css/js/图片/声音/视频/... ...
tomcat/conf/web.xml中配置了常见文件的拓展名和MIMIE类型的对应关系
常见的MIME类型举例如下
文件拓展名 MIME类型 .html text/html .css text/css .js application/javascript .png /.jpeg/.jpg/... ... image/jpeg .mp3/.mpe/.mpeg/ ... ... audio/mpeg .mp4 video/mp4 .m1v/.m1v/.m2v/.mpe/... ... video/mpeg 例如:
//设置响应行相关的api resp.setStatus(405); //设置响应头相关的api resp.setHeader("AAA","value1"); //获取一个响应体中输入文本字符输出流 PrintWriter writer=resp.getWriter(); writer.write("<h1>hello</h1>");
(9)请求转发和响应重定向
什么是请求转发和响应重定向:
请求转发和响应重定向是web应用中间接访问项目资源的两种手段,也是Servlet控制页面跳转的两种手段
请求转发通过HttpServletRequest实现,响应重定向通过HttpServletResponse实现
请求转发生活举例: 张三找李四借钱,李四没有,李四找王五,让王五借给张三
响应重定向生活举例:张三找李四借钱,李四没有,李四让张三去找王五,张三自己再去找王五借钱
请求转发:
请求转发运行逻辑图
请求转发特点:
请求转发通过HttpServletRequest对象获取请求转发器实现
请求转发是服务器内部的行为,对客户端是屏蔽的
客户端只发送了一次请求,客户端地址栏不变
服务端只产生了一对请求和响应对象,这一对请求和响应对象会继续传递给下一个资源
因为全程只有一个HttpServletRequset对象,所以请求参数可以传递,请求域中的数据也可以传递
请求转发可以转发给其他Servlet动态资源,也可以转发给一些静态资源以实现页面跳转
请求转发可以转发给WEB-INF下受保护的资源
请求转发不能转发到本项目以外的外部资源
测试代码:
ServletA: @WebServlet("/servletA") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("ServletA执行了"); //获取请求参数 String money=req.getParameter("MONEY"); System.out.println("servletA获取参数:MONEY="+money); //请求转发给ServletB //获取请求转发器 RequestDispatcher requestDispatcher = req.getRequestDispatcher("servletB"); //转发给一个视图资源 //RequestDispatcher requestDispatcher = req.getRequestDispatcher("a.html"); //转发给WEB-INF下的资源 //RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB- //INF/a.html"); //让请求转发器做出转发动作 requestDispatcher.forward(req, resp); } } ServletB: @WebServlet("/servletB") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("ServletB执行了"); // 获取请求参数 String money=req.getParameter("MONEY"); System.out.println("servletB获取参数:MONEY="+money); } }
响应重定向:
响应重定向运行逻辑图
测试代码:
@WebServlet("/servletA") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //接受用户请求 System.out.println("ServletA执行了"); //响应重定向 设置响应状态码为302,同时设置Location响应头 resp.sendRedirect("servletB"); } } @WebServlet("/servletB") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("servlet2执行了"); } }
响应重定向特点:
响应重定向通过HttpServletResponse对象的sendRedirect方法实现
响应重定向是服务端通过302响应码和路径,告诉客户端自己去找其他资源,是在服务端提示下的客户端的行为
客户端至少发送了两次请求,客户端地址栏是要变化的
服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
因为全程产生了多个HttpServletRequset对象,所以请求参数不可以传递,请求域中的数据也不可以传递
重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转
重定向不可以到给WEB-INF下受保护的资源
重定向可以到本项目以外的外部资源
重点:同样能够实现页面跳转,优先使用响应重定向
(10)乱码问题
乱码问题产生的根本原因是什么?
数据的编码和解码使用的不是同一个字符集
使用了不支持某个语言文字的字符集
各个字符集的兼容性:
由上图得知,上述字符集都兼容了ASCII
ASCII中有什么?
英文字母和一些通常使用的符号,所以这些东西无论使用什么字符集都不会乱码
①HTML乱码问题:
设置项目文件的字符集要使用一个支持中文的字符集
查看当前文件的字符集:
查看项目字符集配置,打开设置—>编译器—>文件编码,将全局编码,项目编码, 属性文件的默认编码设为UTF-8
②Tomcat控制台乱码:
修改 tomcat/conf/logging.properties中,乱码的地方将UTF-8为GBK或将GBK改为UTF-8即可
③sout乱码问题,设置JVM加载.class文件时使用UTF-8字符集
如果没有乱码不设置,乱码了才设置
④get请求参数乱码
GET请求方式乱码分析:
GET方式提交参数的方式是将参数放到URL后面,如果使用的不是UTF-8,那么会对参数进行URL编码处理
HTML中的 <meta charset='字符集'/> 影响了GET方式提交参数的URL编码
tomcat10.1.7的URI编码默认为 UTF-8
当GET方式提交的参数URL编码和tomcat默认的URI编码不一致时,就会出现乱码
GET请求方式乱码演示:
浏览器解析的文档的<meta charset="GBK" />
GET方式提交时,会对数据进行URL编码处理 ,是将GBK 转码为 "百分号码"
tomcat默认使用UTF-8对URI进行解析,造成前后端使用的字符集不一致,出现乱码
GET请求方式乱码解决:
方式1 :设置GET方式提交的编码和Tomcat的URI默认解析编码一致即可 (推荐)
方式2 : 设置Tomcat的URI解析字符集和GET请求发送时所使用URL转码时的字符集一致即可,修改conf/server.xml中 Connecter 添加 URIEncoding="GBK" (不推荐)
⑤POST方式请求乱码:
POST请求方式乱码分析:
POST请求将参数放在请求体中进行发送
请求体使用的字符集受到了<meta charset="字符集"/> 的影响
Tomcat10.1.7 默认使用UTF-8字符集对请求体进行解析
如果请求体的URL转码和Tomcat的请求体解析编码不一致,就容易出现乱码
POST方式乱码演示:
POST请求请求体受到了<meta charset="字符集"/> 的影响
请求体中,将GBK数据进行 URL编码
后端默认使用UTF-8解析请求体,出现字符集不一致,导致乱码
POST请求方式乱码解决:
方式1 : 请求时,使用UTF-8字符集提交请求体 (推荐)
方式2 : 后端在获取参数前,设置解析请求体使用的字符集和请求发送时使用的字符集一致 (不
推荐)
⑥响应乱码问题:
响应乱码分析:
在Tomcat10.1.7中,向响应体中放入的数据默认使用了工程编码 UTF-8
浏览器在接收响应信息时,使用了不同的字符集或者是不支持中文的字符集就会出现乱码
响应乱码演示:
服务端通过response对象向响应体添加数据:
浏览器接收数据解析乱码:
响应乱码解决:
方式1: tomcat10中,响应体默认的编码字符集使用的是UTF-8,可以设置响应体的编码字符集和客户端的 保持一致 resp.setCharacterEncoding("utf-8"); 不推荐,因为客户端解析的宇符集是无法预测 方式2: 可以告诉客户端使用指定的字符集进行解码通过设置content-Type响应头 注意的是:明确响应体的编码,然后再设置Content-Type resp.setContentType("text/html;charset=UTF-8"); 方法3:两种同时设置 resp.setCharacterEncoding("utf-8"); 设置响应体使用UTF-8编码 //设置Content-Type响应头,告诉客户端用UTF-8解码 resp.setContentType("text/html;charset=UTF-8");
(11) 路径问题
①前端路径问题:
相对路径:
以当前资源的所在路径为出发点去找目际资源
语法:不以/开头
./表示当前资源的路径
../表示当前资源的上一层路径
缺点:目标资源路径受到当前资源路径的影响,不同的位置相对路径写法不同绝对路径:
始终以固定的路径作为出发点去找目标资源和当前资源的所在路径没有关系语法:以/开头
不同的项目中,固定的路径出发点可能不一致优点:目标资源路径的写法不会受到当前资源路径的影响,不同的位置,绝对路径写法一
致
缺点:绝对路径要补充项目的上下文,项目上下文是可以改变的
base标签的使用:
base标签定义页面相对路径公共前缀
base 标签定义在head标签中,用于定义相对路径的公共前缀
base 标签定义的公共前缀只在相对路径上有效,绝对路径中无效
如果相对路径开头有 ./ 或者../修饰,则base标签对该路径同样无效
例如: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!--定义相对路径的公共前缀,将相对路径转化成了绝对路径--> <base href="/web03_war_exploded/"> </head> <body> <img src="static/img/logo.png"> </body> </html>
②后端路径问题
重定向的路径问题:
相对路径写法和前端的相对路径规则一致
绝对路径写法http://Localhost:8080/
请求转发的路径问题:
请求转发的绝对路径是不需要添加项目上下文的
不设置项目上下文路径路径:
三. MVC架构模式
MVC(Model View Controller)是软件工程中的一种
软件架构模式
,它把软件系统分为模型
、视图
和控制器
三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
M:Model 模型层,具体功能如下
存放和数据库对象的实体类以及一些用于存储非数据库表完整相关的VO对象
存放一些对数据进行逻辑运算操作的的一些业务处理代码
V:View 视图层,具体功能如下
存放一些视图文件相关的代码 html css js等
在前后端分离的项目中,后端已经没有视图文件,该层次已经衍化成独立的前端项目
C:Controller 控制层,具体功能如下
接收客户端请求,获得请求数据
将准备好的数据响应给客户端
MVC模式下,项目中的常见包:
M:
实体类包(pojo /entity /bean) 专门存放和数据库对应的实体类和一些VO对象
数据库访问包(dao/mapper) 专门存放对数据库不同表格CURD方法封装的一些类
服务包(service) 专门存放对数据进行业务逻辑运算的一些类
C:
控制层包(controller)
V:
web目录下的视图资源 html css js img 等
前端工程化后,在后端项目中已经不存在了
非前后端分离的MVC:
前后端分离的MVC:
例如:
四. 会话
1. 会话管理概述
为什么需要会话管理:
HTTP是无状态协议
无状态就是不保存状态,即无状态协议(stateless),HTTP协议自身不对请求和响应之间的通信状态进行保存,也就是说,在HTTP协议这个级别,协议对于发送过的请求或者响应都不做持久化处理
简单理解:浏览器发送请求,服务器接收并响应,但是服务器不记录请求是否来自哪个浏览器,服务器没记录浏览器的特征,就是客户端的状态
举例: 张三去一家饭馆点了几道菜,觉得味道不错,第二天又去了,对老板说,还点上次的那几道菜
无状态: 老板没有记录张三是否来过,更没有记录上次他点了那些菜,张三只能重新再点一遍
有状态: 老板把每次来吃饭的用户都做好记录,查阅一下之前的记录,查到了张三之前的菜单,直接下单
会话管理实现的手段:
Cookie和Session配合解决
cookie是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息
session是在服务端保留更多数据的技术,主要通过HttpSession对象保存一些和客户端相关的信息
cookie和session配合记录请求状态
举例: 张三去银行办业务
张三第一次去某个银行办业务,银行会为张三开户(Session),并向张三发放一张银行卡(cookie)
张三后面每次去银行,就可以携带之间的银行卡(cookie),银行根据银行卡找到之前张三的账户(session)
2. Cookie
Cookie概述:
cookie是一种客户端会话技术,cookie由服务端产生,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去。
服务端创建cookie,将cookie放入响应对象中,Tomcat容器将cookie转化为set-cookie响应头,响应给客户端
客户端在收到cookie的响应头时,在下次请求该服务的资源时,会以cookie请求头的形式携带之前收到的Cookie
cookie是一种键值对格式的数据,从tomcat8.5开始可以保存中文,但是不推荐
由于cookie是存储于客户端的数据,比较容易暴露,一般不存储一些敏感或者影响安全的数据
原理图:
应用场景举例:
记录用户名
当我们在用户名的输入框中输入完用户名后,浏览器记录用户名,下一次再访问登录页面时,用户名自动填充到用户名的输入框.
保存电影播放进度
在网页上播放电影的时候,如果中途退出浏览器了,下载再打开浏览器播放同一部电影的时候,会自动跳转到上次退出时候的进度,因为在播放的时候会将播放进度保存到cookie中
Cookie的使用:
servletA向响应中增加Cookie: @WebServlet("/servletA") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 创建Cookie Cookie cookie1 =new Cookie("c1","c1_message"); Cookie cookie2 =new Cookie("c2","c2_message"); // 将cookie放入响应对象 resp.addCookie(cookie1); resp.addCookie(cookie2); } } servletB从请求中读取Cookie: @WebServlet("/servletB") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求中的cookie Cookie[] cookies = req.getCookies(); //迭代cookies数组 if (null != cookies && cookies.length!= 0) { for (Cookie cookie : cookies) { System.out.println(cookie.getName()+":"+cookie.getValue()); } } } }
Cookie的时效性:
默认情况下Cookie的有效期是一次会话范围内,我们可以通过cookie的setMaxAge()方法让Cookie持久化保存到浏览器上
会话级Cookie
服务器端并没有明确指定Cookie的存在时间
在浏览器端,Cookie数据存在于内存中
只要浏览器还开着,Cookie数据就一直都在
浏览器关闭,内存中的Cookie数据就会被释放
持久化Cookie
服务器端明确设置了Cookie的存在时间
在浏览器端,Cookie数据会被保存到硬盘上
Cookie在硬盘上存在的时间根据服务器端限定的时间来管控,不受浏览器关闭的影响
持久化Cookie到达了预设的时间会被释放
servletA设置一个Cookie为持久化cookie: @WebServlet("/servletA") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 创建Cookie Cookie cookie1 =new Cookie("c1","c1_message"); cookie1.setMaxAge(60); Cookie cookie2 =new Cookie("c2","c2_message"); // 将cookie放入响应对象 resp.addCookie(cookie1); resp.addCookie(cookie2); } } servletB接收Cookie,浏览器中间发生一次重启再请求servletB测试: @WebServlet("/servletB") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求中的cookie Cookie[] cookies = req.getCookies(); //迭代cookies数组 if (null != cookies && cookies.length!= 0) { for (Cookie cookie : cookies) { System.out.println(cookie.getName()+":"+cookie.getValue()); } } } }
cookie.setMaxAge(int expiry)参数单位是秒,表示cookie的持久化时间,如果设置参数为0,表示将浏览器中保存的该cookie删除
Cookie的提交路径:
访问互联网资源时不能每次都需要把所有Cookie带上。访问不同的资源时,可以携带不同的cookie,我们可以通过cookie的setPath(String path) 对cookie的路径进行设置
public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 创建Cookie Cookie cookie1 =new Cookie("c1","c1_message"); // 设置cookie的提交路径 cookie1.setPath("/web03_war_exploded/servletB"); Cookie cookie2 =new Cookie("c2","c2_message"); // 将cookie放入响应对象 resp.addCookie(cookie1); resp.addCookie(cookie2); } }
3. Session
HttpSession概述:
HttpSession是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即session对象. 客户端在发送请求时,都可以使用自己的session. 这样服务端就可以通过session来记录某个客户端的状态了
服务端在为客户端创建session时,会同时将session对象的id,即JSESSIONID以cookie的形式放入响应对象
后端创建完session后,客户端会收到一个特殊的cookie,叫做JSESSIONID
客户端下一次请求时携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的session对象
通过该机制,服务端通过session就可以存储一些专门针对某个客户端的信息了
session也是域对象(后续详细讲解)
原理图如下:
应用场景
记录用户的登录状态
用户登录后,将用户的账号等敏感信息存入session
记录用户操作的历史
例如记录用户的访问痕迹,用户的购物车信息等临时性的信息
HttpSession的使用:
用户提交form表单到ServletA,携带用户名,ServletA获取session 将用户名存到Session,用户再请求其他任意Servlet,获取之间存储的用户
定义表单页,提交用户名,提交后 <form action="servletA" method="post"> 用户名: <input type="text" name="username"> <input type="submit" value="提交"> </form> 定义ServletA,将用户名存入session: @WebServlet("/servletA") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求中的参数 String username = req.getParameter("username"); // 获取session对象 HttpSession session = req.getSession(); // 获取Session的ID String jSessionId = session.getId(); System.out.println(jSessionId); // 判断session是不是新创建的session boolean isNew = session.isNew(); System.out.println(isNew); // 向session对象中存入数据 session.setAttribute("username",username); } } 定义其他Servlet,从session中读取用户名: @WebServlet("/servletB") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取session对象 HttpSession session = req.getSession(); // 获取Session的ID String jSessionId = session.getId(); System.out.println(jSessionId); // 判断session是不是新创建的session boolean isNew = session.isNew(); System.out.println(isNew); // 从session中取出数据 String username = (String)session.getAttribute("username"); System.out.println(username); } }
getSession方法的处理逻辑:
HttpSession时效性:
为什么要设置session的时效?
用户量很大之后,Session对象相应的也要创建很多。如果一味创建不释放,那么服务器端的内存迟早要被耗尽。
客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况,就需要对session的时限进行设置了
默认的session最大闲置时间(两次使用同一个session中的间隔时间) 在tomcat/conf/web.xml配置为30分钟
我们可以自己在当前项目的web.xml对最大闲置时间进行重新设定
也可以通过HttpSession的API 对最大闲置时间进行设定或可以直接让session失效 :
// 设置最大闲置时间 session.setMaxInactiveInterval(60); // 直接让session失效 session.invalidate();
4. 三大域对象
域对象概述:
域对象: 一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同
web项目中,我们一定要熟练使用的域对象分别是 请求域,会话域,应用域
请求域对象是HttpServletRequest ,传递数据的范围是一次请求之内及请求转发
会话域对象是HttpSession,传递数据的范围是一次会话之内,可以跨多个请求
应用域对象是ServletContext,传递数据的范围是本应用之内,可以跨多个会话
生活举例: 热水器摆放位置不同,使用的范围就不同
摆在张三工位下,就只有张三一个人能用
摆在办公室的公共区,办公室内的所有人都可以用
摆在楼层的走廊区,该楼层的所有人都可以用
三大域对象的数据作用范围图解:
请求域:
会话域:
应用域 :
所有域在一起:
域对象的使用:
API 功能 void setAttribute(String name,String value) 向域对象中添加/修改数据 Object getAttribute(String name); 从域对象中获取数据 removeAttribute(String name); 移除域对象中的数据 API测试:
ServletA向三大域中放入数据: @WebServlet("/servletA") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 向请求域中放入数据 req.setAttribute("request","request-message"); //req.getRequestDispatcher("servletB").forward(req,resp); // 向会话域中放入数据 HttpSession session = req.getSession(); session.setAttribute("session","session-message"); // 向应用域中放入数据 ServletContext application = getServletContext(); application.setAttribute("application","application-message"); } } ServletB从三大于中取出数据: @WebServlet("/servletB") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 从请求域中获取数据 String reqMessage =(String)req.getAttribute("request"); System.out.println(reqMessage); // 从会话域中获取数据 HttpSession session = req.getSession(); String sessionMessage =(String)session.getAttribute("session"); System.out.println(sessionMessage); // 从应用域中获取数据 ServletContext application = getServletContext(); String applicationMessage =(String)application.getAttribute("application"); System.out.println(applicationMessage); } }
三大域对象分别适合放什么类型数据:
请求转发时,请求域可以传递数据
请求域内一般放本次请求业务有关的数据,如:查询到的所有的部门信息
同一个会话内,不用请求转发,会话域可以传递数据
会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户
同一个APP内,不同的客户端,应用域可以传递数据
应用域内一般放本程序应用有关的数据 如:Spring框架的IOC容器
五. 过滤器
1. 过滤器概述
Filter,即过滤器,是JAVAEE技术规范之一,作用目标资源的请求进行过滤的一套技术规范,是Java Web项目中
最为实用的技术之一
Filter接口定义了过滤器的开发规范,所有的过滤器都要实现该接口
Filter的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequest和HttpServletResponse对象后,会先调用Filter的doFilter方法
Filter的doFilter方法可以控制请求是否继续,如果放行,则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应
Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理
Filter是GOF中责任链模式的典型案例
Filter的常用应用包括但不限于: 登录权限检查,解决网站乱码,过滤敏感字符,日志记录,性能分析... ...
生活举例: 公司前台,停车场安保,地铁验票闸机
公司前台对来访人员进行审核,如果是游客则拒绝进入公司,如果是客户则放行 . 客户离开时提醒客户不要遗忘物品
停车场保安对来访车辆进行控制,如果没有车位拒绝进入,如果有车位,发放停车卡并放行,车辆离开时收取请车费
地铁验票闸机在人员进入之前检查票,没票拒绝进入,有票验票后放行,人员离开时同样验票
过滤器开发中应用的场景
日志的记录
性能的分析
乱码的处理
事务的控制
登录的控制
跨域的处理
过滤器工作位置图解:
2. 过滤器使用
过滤器图解:
1. 实现Filter接口 2. 重写过滤方法 3. 配置过滤器 过滤请求的和响应的方法 1. 请求到达目标资源之前,先经过该方法 2. 该方法有能力控制请求是否继续向后到达目际资源,可以在该方法内直按向客户端做响应处理 3. 请求到达目标资源后,响应之前,还会经过该方法 定义一个过滤器类,编写功能代码 public class LoggingFilter implements Filter{ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //1. 请求到达目标资源之前的功能代码 // 判断是否登录 // 校验权限是否满足 //2. 放行代码 //3. 响应之前 HttpServletResponse 转换为响应报文之前的功能代码 // 请求到达目标资源之前的代码 System.out.println("AA"); //放行 filterChain.doFilter(servletRequest,servletResponse); //响应之前的功能代码 System.out.println("BB"); } } } 配置过滤器以及过滤器的过滤范围: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version="6.0"> //配置过滤器,并为filter起别名 <filter> <filter-name>loggingFilter</filter-name> <filter-class>com.atguigu.filters.LoggingFilter</filter-class> </filter> //为别名对应的filter配置要过滤的目标资源 <filter-mapping> <filter-name>loggingFilter</filter-name> url-pattern 根据访求的资源路径 对指定的请求进行过滤 /* 过滤全部资源 /a/* 过滤以a开头的资源 *.html 过滤以html为后缀的资源 /servlet1 对servlet1请求进行过滤 servlet-name 根据请求的servlet的别名,最指定的servlet资源进行过滤 一个filter-mapping中可以同时存在多个url-pattern和servlet-name <servlet-name>servlet1</servlet-name> 通过servlet别名确定过滤资源 <url-pattern>/servletA</url-pattern> 通过映射路径确定过滤资源 </filter-mapping> </web-app>
3. 过滤器生命周期
过滤器作为web项目的组件之一,和Servlet的生命周期类似,略有不同,没有servlet的load-on-startup的配置,默认就是系统启动立刻构造
阶段 对应方法 执行时机 执行次数 创建对象 构造器 web应用启动时 1 初始化方法 void init(FilterConfig filterConfig) 构造完毕 1 过滤请求 void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 每次请求 多次 销毁 default void destroy() web应用关闭时 1次 测试代码:
public class LifeCycleFilter implements Filter { public LifeCycleFilter(){ System.out.println("构造"); } @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("初始化"); System.out.println(filterConfig.getInitParameter(("dateTimePattern"))); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("过滤方法"); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { System.out.println("销毁方法"); } } 配置: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version="6.0"> <filter> <filter-name>lifeCycleFilter</filter-name> <filter-class>com.atguigu.filters.LifeCycleFilter</filter-class> <init-param> <param-name>dateTimePattern</param-name> <param-value>yyy-MM-dd HH:mm:ss</param-value> </init-param> </filter> <filter-mapping> <filter-name>lifeCycleFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
4. 过滤器链及执行顺序问题
一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链
过滤器链中的过滤器的顺序由filter-mapping顺序决定
每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
如果某个Filter是使用ServletName进行匹配规则的配置,那么这个Filter执行的优先级要更低
图解过滤器链:
过滤器链功能测试:
定义三个过滤器,对目标资源Servlet的请求进行过滤: //@WebFilter("/*") 注解方式配置 public class Filter1 implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Filter1 before doFilter invoked"); filterChain.doFilter(servletRequest,servletResponse); System.out.println("Filter1 after doFilter invoked"); } } //@WebFilter("/*") 注解方式配置 public class Filter2 implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Filter2 before doFilter invoked"); filterChain.doFilter(servletRequest,servletResponse); System.out.println("Filter2 after doFilter invoked"); } } //@WebFilter("/*") 注解方式配置 public class Filter3 implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Filter3 before doFilter invoked"); filterChain.doFilter(servletRequest,servletResponse); System.out.println("Filter3 after doFilter invoked"); } } 过滤器配置代码: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version="6.0"> <filter> <filter-name>filter1</filter-name> <filter-class>com.atguigu.filters.Filter1</filter-class> </filter> <filter-mapping> <filter-name>filter1</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>filter2</filter-name> <filter-class>com.atguigu.filters.Filter2</filter-class> </filter> <filter-mapping> <filter-name>filter2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>filter3</filter-name> <filter-class>com.atguigu.filters.Filter3</filter-class> </filter> <filter-mapping> <filter-name>filter3</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> 目标Servlet资源代码: @WebServlet(value = "/servlet1",name = "servlet1") public class Servlet1 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("servlet1"); resp.getWriter().write("servlet1"); } }
工作流程图解:
过滤器链的执行顺序:
如果用过滤器配置的话,按 </filter-mapping>的位置顺序执行
如果使用注解方式配置,按包中类名的字典顺序执行
六. Listener监听器
1. 监听器概述:
监听器:专门用于对域对象对象身上发生的事件或状态改变进行监听和相应处理的对象
监听器是GOF设计模式中,观察者模式的典型案例
观察者模式: 当被观察的对象发生某些改变时, 观察者自动采取对应的行动的一种设计模式
监听器使用的感受类似JS中的事件,被观察的对象发生某些情况时,自动触发代码的执行
监听器并不监听web项目中的所有组件,仅仅是对三大域对象做相关的事件监听
监听器的分类:
web中定义八个监听器接口作为监听器的规范,这八个接口按照不同的标准可以形成不同的分类
按监听的对象划分
application域监听器 ServletContextListener ServletContextAttributeListener
session域监听器 HttpSessionListener HttpSessionAttributeListener HttpSessionBindingListener HttpSessionActivationListener
request域监听器 ServletRequestListener ServletRequestAttributeListener
按监听的事件分
域对象的创建和销毁监听器 ServletContextListener HttpSessionListener ServletRequestListener
域对象数据增删改事件监听器 ServletContextAttributeListener HttpSessionAttributeListener ServletRequestAttributeListener
其他监听器 HttpSessionBindingListener HttpSessionActivationListener
2. 监听器的六个主要接口
(1)application域监听器:
ServletContextListener 监听ServletContext对象的创建与销毁
ServletContextEvent对象代表从ServletContext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象。
方法名 作用 contextInitialized(ServletContextEvent sce) ServletContext创建时调用 contextDestroyed(ServletContextEvent sce) ServletContext销毁时调用 ServletContextAttributeListener 监听ServletContext中属性的添加、移除和修改
ServletContextAttributeEvent对象代表属性变化事件,它包含的方法如下:
方法名 作用 attributeAdded(ServletContextAttributeEvent scab) 向ServletContext中添加属性时调用 attributeRemoved(ServletContextAttributeEvent scab) 从ServletContext中移除属性时调用 attributeReplaced(ServletContextAttributeEvent scab) 当ServletContext中的属性被修改时调用
方法名 作用 getName() 获取修改或添加的属性名 getValue() 获取被修改或添加的属性值 getServletContext() 获取ServletContext对象 测试代码:
定义监听器: @WebListener public class MyAppListener implements ServletContextListener, ServletContextAttributeListener { @Override public void contextInitialized(ServletContextEvent sce) { ServletContext application = sce.getServletContext(); System.out.println(application.hashCode()+"应用域初始化了"); } @Override public void contextDestroyed(ServletContextEvent sce) { ServletContext application = sce.getServletContext(); System.out.println(application.hashCode()+"应用域销毁了"); } @Override public void attributeAdded(ServletContextAttributeEvent scae) { ServletContext application = scae.getServletContext(); String key = scae.getName(); Object value = scae.getValue(); System.out.println(application.hashCode()+"应用域增加了"+key+":"+value); } @Override public void attributeRemoved(ServletContextAttributeEvent scae) { ServletContext application = scae.getServletContext(); String key = scae.getName(); Object value = scae.getValue(); System.out.println(application.hashCode()+"应用域移除了"+key+":"+value); } @Override public void attributeReplaced(ServletContextAttributeEvent scae) { ServletContext application = scae.getServletContext(); String key = scae.getName(); Object value = scae.getValue();//获取的是旧的值 Object newValue = application.getAttribute(key);//获取最新的值 System.out.println(application.hashCode()+"应用域修改 了"+key+":"+value+"为"+newValue); } } 定义触发监听器的代码: @WebServlet("/Servlet1") public class Servlet1 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 向应用域中嵌入数据 ServletContext application= this.getServletContext(); application.setAttribute("keya","valuea"); } } @WebServlet("/Servlet2") public class Servlet2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 修改应用域中的数据 ServletContext application= this.getServletContext(); application.setAttribute("keya","valuexx"); } } @WebServlet("/Servlet3") public class Servlet3 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 删除应用域的数据 ServletContext application= this.getServletContext(); application.removeAttribute("keya"); } }
(2)session域监听器
HttpSessionListener 监听HttpSession对象的创建与销毁
方法名 作用 sessionCreated(HttpSessionEvent hse) HttpSession对象创建时调用 sessionDestroyed(HttpSessionEvent hse) HttpSession对象销毁时调用 HttpSessionEvent对象代表从HttpSession对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpSession对象。
HttpSessionAttributeListener 监听HttpSession中属性的添加、移除和修改
方法名 作用 attributeAdded(HttpSessionBindingEvent se) 向HttpSession中添加属性时调用 attributeRemoved(HttpSessionBindingEvent se) 从HttpSession中移除属性时调用 attributeReplaced(HttpSessionBindingEvent se) 当HttpSession中的属性被修改时调用 HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:
方法名 作用 getName() 获取修改或添加的属性名 getValue() 获取被修改或添加的属性值 getSession() 获取触发事件的HttpSession对象 测试代码:
定义监听器: @WebListener public class MySessionListener implements HttpSessionListener, HttpSessionAttributeListener { @Override public void sessionCreated(HttpSessionEvent se) { //任何一个session域对象的创建都会触发该方法的执行 } @Override public void sessionDestroyed(HttpSessionEvent se) { //任何一个session域对象的销毁都会触发该方法的执行 } @Override public void attributeAdded(HttpSessionBindingEvent se) { //任何一个session域中增加了数据都会触发该方法的执行 } @Override public void attributeRemoved(HttpSessionBindingEvent se) { //任何一个session域中移除了数据都会触发该方法的执行 } @Override public void attributeReplaced(HttpSessionBindingEvent se) { //任何一个session域中修改了数据都会触发该方法的执行 } } 定义触发监听器的代码: // servletA用于创建session并向session中放数据 @WebServlet(urlPatterns = "/servletA",name = "servletAName") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 创建session,并向session中放入数据 HttpSession session = req.getSession(); session.setAttribute("k1","v1"); session.setAttribute("k2","v2"); } } // servletB用于修改删除session中的数据并手动让session不可用 @WebServlet(urlPatterns = "/servletB", name = "servletBName") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); // 修改session域中的数据 session.setAttribute("k1","value1"); // 删除session域中的数据 session.removeAttribute("k2"); // 手动让session不可用 session.invalidate(); } }
(3)request域监听器
ServletRequestListener 监听ServletRequest对象的创建与销毁
方法名 作用 requestInitialized(ServletRequestEvent sre) ServletRequest对象创建时调用 requestDestroyed(ServletRequestEvent sre) ServletRequest对象销毁时调用 ServletRequestEvent对象代表从HttpServletRequest对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpServletRequest对象。另外还有一个方法可以获取到当前Web应用的ServletContext对象。
ServletRequestAttributeListener 监听ServletRequest中属性的添加、移除和修改
方法名 作用 attributeAdded(ServletRequestAttributeEvent srae) 向ServletRequest中添加属性时调用 attributeRemoved(ServletRequestAttributeEvent srae) 从ServletRequest中移除属性时调用 attributeReplaced(ServletRequestAttributeEvent srae) 当ServletRequest中的属性被修改时调用 ServletRequestAttributeEvent对象代表属性变化事件,它包含的方法如下:
方法名 作用 getName() 获取修改或添加的属性名 getValue() 获取被修改或添加的属性值 getServletRequest () 获取触发事件的ServletRequest对象 测试代码:
定义监听器: @WebListener public class MyRequestListener implements ServletRequestListener, ServletRequestAttributeListener { @Override public void requestInitialized(ServletRequestEvent sre) { //任何一个请求域对象的初始化都会触发该方法的执行 } @Override public void requestDestroyed(ServletRequestEvent sre) { //任何一个请求域对象的销毁都会触发该方法的执行 } @Override public void attributeRemoved(ServletRequestAttributeEvent srae) { //任何一个请求域中移除了数据都会触发该方法的执行 } @Override public void attributeReplaced(ServletRequestAttributeEvent srae) { //任何一个请求域中修改了数据都会触发该方法的执行 } @Override public void attributeAdded(ServletRequestAttributeEvent srae) { //任何一个请求域中增加了数据都会触发该方法的执行 } } 定义触发监听器的代码: // servletA向请求域中放数据 @WebServlet(urlPatterns = "/servletA",name = "servletAName") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 向request中增加数据 req.setAttribute("k1","v1"); req.setAttribute("k2","v2"); // 请求转发 req.getRequestDispatcher("servletB").forward(req,resp); } } // servletB修改删除域中的数据 @WebServlet(urlPatterns = "/servletB", name = "servletBName") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 修改request域中的数据 req.setAttribute("k1","value1"); // 删除session域中的数据 req.removeAttribute("k2"); } }
3. session域的两个特殊监听器
(1)session绑定监听器
HttpSessionBindingListener 监听当前监听器对象在Session域中的增加与移除
方法名 作用 valueBound(HttpSessionBindingEvent event) 该类的实例被放到Session域中时调用 valueUnbound(HttpSessionBindingEvent event) 该类的实例从Session中移除时调用 HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:
方法名 作用 getName() 获取当前事件涉及的属性名 getValue() 获取当前事件涉及的属性值 getSession() 获取触发事件的HttpSession对象 测试代码:
定义监听器: public class MySessionBindingListener implements HttpSessionBindingListener { // 监听绑定 @Override public void valueBound(HttpSessionBindingEvent event) { HttpSession session = event.getSession(); String name = event.getName(); System.out.println("MySessionBindingListener"+this.hashCode()+" binding into session"+session.hashCode()+" with name "+name); } // 监听解除绑定 @Override public void valueUnbound(HttpSessionBindingEvent event) { HttpSession session = event.getSession(); String name = event.getName(); System.out.println("MySessionBindingListener"+this.hashCode()+" unbond outof session"+session.hashCode()+" with name "+name); } } 定义触发监听器的代码: @WebServlet(urlPatterns = "/servletA",name = "servletAName") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); // 绑定监听器 session.setAttribute("bindingListener",new MySessionBindingListener()); // 解除绑定监听器 session.removeAttribute("bindingListener"); } }
(2)钝化活化监听器
HttpSessionActivationListener 监听某个对象在Session中的序列化与反序列化
方法名 作用 sessionWillPassivate(HttpSessionEvent se) 该类实例和Session一起钝化到硬盘时调用 sessionDidActivate(HttpSessionEvent se) 该类实例和Session一起活化到内存时调用 HttpSessionEvent对象代表事件对象,通过getSession()方法获取事件涉及的HttpSession对象。
什么是钝化活化:
session对象在服务端是以对象的形式存储于内存的,session过多,服务器的内存也是吃不消的
而且一旦服务器发生重启,所有的session对象都将被清除,也就意味着session中存储的不同客户端的登录状态丢失
为了分摊内存 压力并且为了保证session重启不丢失,我们可以设置将session进行钝化处理
在关闭服务器前或者到达了设定时间时,对session进行序列化到磁盘,这种情况叫做session的钝化
在服务器启动后或者再次获取某个session时,将磁盘上的session进行反序列化到内存,这种情况叫做session的活化
如何配置钝化活化:
在web目录下,添加 META-INF下创建Context.xml
文件中配置钝化:
<?xml version="1.0" encoding="UTF-8"?> <Context> <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1"> <Store className="org.apache.catalina.session.FileStore" directory="d:\mysession"> </Store> </Manager> </Context>
请求servletA,获得session,并存入数据,然后重启服务器:
@WebServlet(urlPatterns = "/servletA",name = "servletAName") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); // 添加数据 session.setAttribute("k1","v1"); } }
请求servletB获取session,获取重启前存入的数据:
@WebServlet(urlPatterns = "/servletB", name = "servletBName") public class ServletB extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Object v1 = session.getAttribute("k1"); System.out.println(v1); } }
如何监听钝化活化:
如何监听钝化活化: public class ActivationListener implements HttpSessionActivationListener, Serializable { // 监听钝化 @Override public void sessionWillPassivate(HttpSessionEvent se) { HttpSession session = se.getSession(); System.out.println("session with JSESSIONID "+ session.getId()+" will passivate"); } // 监听活化 @Override public void sessionDidActivate(HttpSessionEvent se) { HttpSession session = se.getSession(); System.out.println("session with JSESSIONID "+ session.getId()+" did activate"); } } 定义触发监听器的代码: @WebServlet(urlPatterns = "/servletA",name = "servletAName") public class ServletA extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); // 添加数据 session.setAttribute("k1","v1"); // 添加钝化活化监听器 session.setAttribute("activationListener",new ActivationListener()); } }
七. Maven
1. 初识Maven
什么是Maven:
Maven是Apache旗下的一个开源项目,是一款用于管理和构建java项目的工具。
官网:Maven – Welcome to Apache Maven
Maven的作用:
1. 依赖管理
方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题
当使用maven进行项目依赖(jar包)管理,则很方便的可以解决这个问题。 我们只需要在
maven项目的pom.xml文件中,添加一段如下图所示的配置即可实现。
2. 统一项目结构
提供标准、统一的项目结构
在项目开发中,当使用不同的开发工具 (如:Eclipse、Idea),创建项目工程时:
若我们创建的是一个maven工程,是可以帮我们自动生成统一、标准的项目目录结构:
具体的统一结构如下:
目录说明:
3. 项目构建:
maven提供了标准的、跨平台(Linux、Windows、MacOS) 的自动化项目构建方式
如上图所示我们开发了一套系统,代码需要进行编译、测试、打包、发布,这些操作如果
需要反复进行就显得特别麻烦,而Maven提供了一套简单的命令来完成项目构建。
综上所述,可以得到一个结论:Maven是一款管理和构建java项目的工具
2. Maven概述
Maven介绍:
Apache Maven是一个项目管理和构建工具,它基于项目对象模型(Project Object Model , 简称: POM)的概念,通过一小段描述信息来管理项目的构建、报告和文档。
官网:Maven – Welcome to Apache Maven
Maven的作用:
方便的依赖管理
统一的项目结构
标准的项目构建流程
Maven模型:
1). 构建生命周期/阶段(Build lifecycle & phases)
以上图中紫色框起来的部分,就是用来完成标准化构建流程 。当我们需要编译,Maven
提供了一个编译插件供我们使用;当我们需要打包,Maven就提供了一个打包插件供我们
使用等。
2). 项目对象模型 (Project Object Model)
以上图中紫色框起来的部分属于项目对象模型,就是将我们自己的项目抽象成一个对象模
型,有自己专属的坐标,如下图所示是一个Maven项目:
坐标,就是资源(jar包)的唯一标识,通过坐标可以定位到所需资源(jar包)位置
3). 依赖管理模型(Dependency)
以上图中紫色框起来的部分属于依赖管理模型,是使用坐标来描述当前项目依赖哪些第
三方jar包
Maven仓库:
仓库:用于存储资源,管理各种jar包
仓库的本质就是一个目录(文件夹),这个目录被用来存储开发中所有依赖(就是jar包)和插件
Maven仓库分为:
本地仓库:自己计算机上的一个目录(用来存储jar包)
中央仓库:由Maven团队维护的全球唯一的。仓库地址:Central Repository:
远程仓库(私服):一般由公司团队搭建的私有仓库
当项目中使用坐标引入对应依赖jar包后,首先会查找本地仓库中是否有对应的jar包
如果有,则在项目直接引用
如果没有,则去中央仓库中下载对应的jar包到本地仓库
如果还可以搭建远程仓库(私服),将来jar包的查找顺序则变为: 本地仓库 --> 远程仓库--> 中央仓库
Maven安装:
1. 下载
下载地址:Maven – Download Apache Maven
2. 解压(解压即安装)
建议解压到没有中文、特殊字符的路径下。
我解压到了d盘
解压缩后的目录结构如下:
bin目录 : 存放的是可执行命令。(mvn 命令重点关注)
conf目录 :存放Maven的配置文件。(settings.xml配置文件后期需要修改)
lib目录 :存放Maven依赖的jar包。(Maven也是使用java开发的,所以它也依赖其他的jar包)
3. 配置本地仓库
在自己计算机上新一个目录(本地仓库,用来存储jar包)
进入到conf目录下修改settings.xml配置文件
打开settings.xml文件,定位到53行
复制<localRepository>标签,粘贴到注释的外面(55行)
复制之前新建的用来存储jar包的路径,替换掉<localRepository>标签体内容
<localRepository>D:\apache-maven-3.9.9\mvn_repo</localRepository>
4. 配置阿里云私服
由于中央仓库在国外,所以下载jar包速度可能比较慢,而阿里公司提供了一个远程仓库,
里面基本也都有开源项目的jar包。
进入到conf目录下修改settings.xml配置文件
打开settings.xml文件,定位到160行左右
在<mirrors>标签下为其添加子标签<mirror>,内容如下:
<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror>
注: 只可配置一个<mirror>(另一个要注释!) ,不然两个可能发生冲突,导致jar包无法下载
5. 配置环境变量
Maven环境变量的配置类似于JDK环境变量配置一样
在系统变量处新建一个变量MAVEN_HOME
MAVEN_HOME环境变量的值,设置为maven的解压安装目录
在Path中进行配置
PATH环境变量的值,设置为:%MAVEN_HOME%\bin
6. 打开DOS命令提示符进行验证,出现如图所示表示安装成功
3. 配置maven环境
4. 创建maven项目
Maven项目的目录结构:
maven-project01
|--- src (源代码目录和测试代码目录)
|--- main (源代码目录)
|--- java (源代码java文件目录)
|--- resources (源代码配置文件目录)
|--- test (测试代码目录)
|--- java (测试代码java目录)
|--- resources (测试代码配置文件目录)
|--- target (编译、打包生成文件存放目录)
POM配置详解:
POM (Project Object Model) :指的是项目对象模型,用来描述当前的maven项目。 使用pom.xml文件来实现 pom.xml文件: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <!-- POM模型版本 --> <modelVersion>4.0.0</modelVersion> <!-- 当前项目坐标 --> <groupId>com.itheima</groupId> <artifactId>maven_project1</artifactId> <version>1.0-SNAPSHOT</version> <!-- 打包方式 --> <packaging>jar</packaging> </project> pom文件详解: - <project> :pom文件的根标签,表示当前maven项目 - <modelVersion> :声明项目描述遵循哪一个POM模型版本 - 虽然模型本身的版本很少改变,但它仍然是必不可少的。目前POM模型版本是4.0.0 - 坐标 :<groupId>、<artifactId>、<version> - 定位项目在本地仓库中的位置,由以上三个标签组成一个坐标 - <packaging> :maven项目的打包方式,通常设置为jar或war(默认值:jar)
Maven坐标详解:
什么是坐标?
Maven中的坐标是,资源的唯一标识, 通过该坐标可以唯一定位资源位置
使用坐标来定义项目或引入项目中需要的依赖
Maven坐标主要组成
groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.itheima)
artifactId:定义当前Maven项目名称(通常是模块名称,例如 order-service、goods-service)
version:定义当前项目版本号
如下图就是使用坐标表示一个项目:
注意:
上面所说的资源可以是插件、依赖、当前项目。
我们的项目如果被其他的项目依赖时,也是需要坐标来引入的。
5. idea导入maven项目
1. 使用Maven面板,快速导入项目
打开IDEA,选择右侧Maven面板,点击 + 号,选中对应项目的pom.xml文件,双击即可
2. 使用idea导入模块项目
6. 依赖管理
(1)依赖配置
依赖:指当前项目运行所需要的jar包。一个项目中可以引入多个依赖:
例如:在当前工程中,我们需要用到logback来记录日志,此时就可以在maven工程的pom.xml文件中,引入logback的依赖。具体步骤如下:
在pom.xml中编写<dependencies>标签
在<dependencies>标签中使用<dependency>引入坐标
定义坐标的 groupId、artifactId、version
<dependencies> <!-- 第1个依赖 : logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> <!-- 第2个依赖 : junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
4. 点击刷新按钮,引入最新加入的坐标
刷新依赖:保证每一次引入新的依赖,或者修改现有的依赖配置,都可以加入最新的坐标
注意事项:
如果引入的依赖,在本地仓库中不存在,将会连接远程仓库 / 中央仓库,然后下载依赖(这个过程会比较耗时,耐心等待)
如果不知道依赖的坐标信息,可以到mvn的中央仓库(https://mvnrepository.com/)中搜索
(2)依赖传递
早期我们没有使用maven时,向项目中添加依赖的jar包,需要把所有的jar包都复制到项目工程下
我们现在使用了maven,当项目中需要使用logback-classic时,只需要在pom.xml配置文件中,添加logback-classic的依赖坐标即可。
在pom.xml文件中只添加了logback-classic依赖,但由于maven的依赖具有传递性,所以会自动把所依赖的其他jar包也一起导入。
依赖传递可以分为:
直接依赖:在当前项目中通过依赖配置建立的依赖关系
间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源
比如以上图中:
projectA依赖了projectB。对于projectA 来说,projectB 就是直接依赖。
而projectB依赖了projectC及其他jar包。 那么此时,在projectA中也会将projectC的依赖传递下来。对于projectA 来说,projectC就是间接依赖。
排除依赖:
之前我们讲了依赖具有传递性。那么A依赖B,B依赖C,如果A不想将C依赖进来,是否可以做到?
答案:在maven项目中,我们可以通过排除依赖来实现。
排除依赖:指主动断开依赖的资源。(被排除的资源无需指定版本)
<dependency> <groupId>com.itheima</groupId> <artifactId>maven-projectB</artifactId> <version>1.0-SNAPSHOT</version> <!--排除依赖, 主动断开依赖的资源--> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency>
(3)依赖范围
在项目中导入依赖的jar包后,默认情况下,可以在任何地方使用。
作用范围:
主程序范围有效(main文件夹范围内)
测试程序范围有效(test文件夹范围内)
是否参与打包运行(package指令范围内)
如果希望限制依赖的使用范围,可以通过<scope>标签设置其作用范围。
scope标签的取值范围:
scope值 主程序 测试程序 打包(运行) 范例 compile(默认) Y Y Y log4j test - Y - junit provided Y Y - servlet-api runtime - Y Y jdbc驱动 如上图所示,给junit依赖通过scope标签指定依赖的作用范围。 那么这个依赖就只能作用在测试环境,其他环境下不能使用。
(4) 生命周期
Maven的生命周期就是为了对所有的构建过程进行抽象和统一。 描述了一次项目构建,经历哪些阶段。
Maven对项目构建的生命周期划分为3套(相互独立):
clean:清理工作。
default:核心工作。如:编译、测试、打包、安装、部署等。
site:生成报告、发布站点等。
三套生命周期又包含哪些具体的阶段呢, 我们来看下面这幅图:
我们看到这三套生命周期,里面有很多很多的阶段,这么多生命周期阶段,其实我们常用的并不多,主要关注以下几个:
• clean:移除上一次构建生成的文件
• compile:编译项目源代码
• test:使用合适的单元测试框架运行测试(junit)
• package:将编译后的文件打包,如:jar、war等
• install:安装项目到本地仓库
说明:在同一套生命周期中,我们在执行后面的生命周期时,前面的生命周期都会执行。
思考:当运行package生命周期时,clean、compile生命周期会不会运行?
clean不会运行,compile会运行。 因为compile与package属于同一套生命周期,而clean与package不属于同一套生命周期。
Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际工作。在Maven的设计中,实际任务(如源代码编译)都交由插件来完成。
执行:
在日常开发中,当我们要执行指定的生命周期时,有两种执行方式:
1. 在idea工具右侧的maven工具栏中,选择对应的生命周期,双击执行
2. 在DOS命令行中,通过maven命令执行,进入项目所在路径并cmd,进入后输入mvn 生命周期即可执行