JavaWeb-教你如何实现交互
Srvlet主要内容
Web的概念
静态web
动态web
面试题
发布一个网站(本机Tomcat)
HTTP协议
浏览器和服务器的交互模式
HTTP的两个时代
浏览器中的书写格式
HTTP协议的特点
HTTP之URL
HTTP请求
HTTP响应
消息头
例:访问百度
TomCat服务器
什么是TomCat
安装配置与启动TomCat(基于Deepin20.2)
Tomcat的目录结构
使用IDEA配置Tomcat
Servlet的实现
Servlet实现(使用Maven创建Web项目)
使用Idea社区版+Tomcat+maven实现Servlet访问
使用idea付费版+Tomcat+maven实现Servlet
扩展实现:更改404页面
Servlet的生命周期
Servlet中的父项目与子项目(基于maven)
servlet程序web.xml中的Mapping映射问题
路径访问的优先级问题
Servlet程序中的上下文对象(ServletContext)
HttpServletRequest对象
接收请求
请求乱码问题
request作用域
HttpServletResponse对象
简单分类
常见应用
响应数据乱码
Cookie和Session
Cookie
Session(重点)
文件的上传与下载(Servlet应用)
文件的上传
文件下载
JSP和JSTL
什么是JSP
JSP原理
JSP的注释
JSP执行流程图
JSP的基础语法
JSP指令
九大内置对象
JSP标签、JSTL标签、EL表达式
JaveBean
三层架构(MVC)
早些年的架构
MVC三层架构
Filter过滤器
Filter开发步骤
监听器
过滤器的常见应用
JavaEE
Srvlet主要内容
Web的概念
Web即表示网页应用程序,一般是分静态web和动态web
静态web
表示客户端发送请求之后,服务端通过webserver返回静态的index.html文件。是静态的web页面。
「不足:」
web页面无法动态更新,所有的用户看到的都是同一个页面。 它无法和数据库交互(index.html是写好的,数据无法持久化,用户无法交互)
动态web
通过客户端发出请求之后,在服务端进行过滤请求之后,再通过webserver分别获取动态和静态的web资源。
「不足」:
加入服务器的动态web资源出现错误,我们需要重新编写我们的「后台程序」。
「优势」:
web页面可以动态更新,所有用户看到的页面都是不一样的 它可以与数据库进行交互(数据持久化),因为服务端存在一个数据库,java通过JDBC完成与数据库的连接。
面试题
网站是如何进行访问的?
输入一个域名:回车 检查本机的/etc/hosts文件(主机文件)下有没有域名映射
如果有,直接返回域名对应的ip地址(即输入localhost,返回的ip为127.0.0.1)127.0.0.1———>localhost 如果没有,去DNS服务器上去寻找
发布一个网站(本机Tomcat)
到本机的tomcat文件夹中,找到webapp目录(存放所有的web文件),复制一下ROOT根项目,新建一个Demo的文件夹
进入Demo文件,删掉所有文件,只留下一个WEB-INF文件夹,并创建一个index.html的静态网页,注意要与WEB-INF并列
之后启动Tomcat服务器,访问该页面localhost:8080/Demo即可访问
web项目的结构
---webapps :Tomcat的web目录
-Root
-自定义的项目 网站的目录名
- WEB-INF
- web.xml 存放网站配置文件
- classes java程序
- lib web应用所依赖的jar包
- index.html 默认的首页
- static 静态资源文件夹
- css
- style.css
- js
- img
- ...........
HTTP协议
HTTP协议(Hypertext Transfer Protocol超文本传输协议),是一个客户端请求和响应的标准协议,这个协议详细规定了浏览器和万维网服务器之间互相通信的规则,用户输入地址和端口号之后就可以从服务器上取得所需要的网页信息。
通信规则规定了客户端发送给服务器的内容格式,也规定了服务器发送给客户端的内容格式。客户端发送给服务器的格式叫「请求协议」,服务器发送给客户端的格式叫「响应协议」。
在浏览器中F12可以查看
浏览器和服务器的交互模式
HTTP的两个时代
HTTP/1.0:客户端可以和web服务器连接后,只能获得一个web资源,之后就断开了连接。 HTTP/1.1:客户端可以与web服务器建立连接之后,可以获得多个web资源
浏览器中的书写格式
服务端资源需要通过浏览器执行,此时由浏览器将我们给出的请求解析为满足HTTP协议的格式并发出,我们发出的请求格式需要按照浏览器规定的格式来书写,在浏览器中的书写格式如下:
当浏览器获取到信息以后,按照特定格式解析并发送即可。接收到服务端给出的响应时,也按照HTTP协议进行解析获取到的各个数据,最后按照特定的格式展示给用户。
HTTP协议的特点
支持客户/服务器模式
简单快速:客户向服务器请求服务时,只需传送请求方法和路径,请求方法常用的有:GET、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
灵活:HTTP允许传输任意类型的数据对象,传输的类型由Content-Type加以标记。
无连接:无连接是表示每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答之后,即断开连接,采用这种方式来节省传输时间。
HTTP1.1版本后支持可持续连接,通过这种连接,就有可能建立TCP连接后,发送请求并得到回应,然后发送更多的请求并得到更多的回应,通过把建立和释放TCP连接的开销分摊到多个请求上,则对于每个请求而言,由于TCP而造成的相对开销被大大地降低了,而且,还可以发送流水线请求,也就是说在发送请求1之后的回应到来之前就可以发送请求2,也可以认为,一次连接发送多次请求,由客户机确认是否关闭连接,而服务器会认为这些请求分别来自不同的客户端。
无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,而服务器不需要先前信息时,它的应答就比较快。
HTTP之URL
HTTP(超文本传输协议)是一个基于请求和响应模式的、应用层的协议,常基于TCP的连接方式,绝大多数的Web开发,都是构建在HTTP协议之上的Web应用。
HTTP URL(URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息)的格式如下:
❝http: // host [:port] / [abs_path]
❞
❝http:// IP(主机名/域名) :端口 /访问的资源路径
❞
http表示要通过HTTP协议来定位网络资源 host表示合法的Internet主机域名或者IP地址 port表示指定一个端口号,为空则使用缺省端口80 abs_path指定请求资源的URI,如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,通常这个工作浏览器自动帮助我们完成。
HTTP请求
HTTP请求通常由3部分组成:「请求行、请求头、请求正文」
通过chrome浏览器,F12–>NetWork查看
GET请求(没有请求正文)
GET请求的请求正文被加在了请求后面
当访问一个网址之后(如上图所示),GET表示请求方式,之后的一串字符表示访问的地址(请求正文),最后的HTTP1.1表示请求协议版本。注意?之后的都表示请求的参数,也就是请求体。
POST请求
POST请求之后跟着请求的路径(不带参数,参数存储在一个from data中),后面再跟请求协议版本。
格式
❝请求行
请求头1
请求头2
…..
请求空行
请求体
❞
请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本。
格式如下:Method Request-URI HTTP-Version CRLF
Method:表示请求方法
Request-URI:是一个统一资源标识符
HTTP-Version:表示请求的HTTP协议版本
CRLF表示回车和换行
HTTP响应
在接收和解释请求消息之后,服务器会返回一个HTTP响应消息。HTTP响应也是由3个部分组成的:「状态行、消息报头(响应头)、响应正文」
格式
❝状态行
响应头1
响应头2
…..
响应空行
响应体
❞
消息头
HTTP消息由客户端到服务端的请求和服务器到客户端的响应组成。请求消息和响应消息都是由开始行(对于请求消息,开始行就是请求行,对于响应消息,开始行就是状态行),消息报头(可选),空行(只有CRLF的行),消息正文(可选)组成。
每一个报头域都是由名字+“:”+空格+值组成,消息报头域的名字是大小写无关的。
请求头
请求报允许客户端向服务端传递请求的附加信息以及客户端自身的信息。
Referer:该请求头指明请求从哪里来。
如果是地址栏中输入地址访问的都没有请求头,地址栏输入地址,通过请求可以看到,此时多了一个Referer的请求头,并且后面的值,为该请求从哪里发出。比如:百度竞价,只能从百度来的才有效果,否则不算;通常用来做统计工作、防盗链。(因为它的Referer保存的始终是上一个页面的相关参数)
响应头
响应报头,允许服务器传递不能放在状态行中的附加响应信息,以及关于服务器的信息和对Request-URI所标识的资源进行下一步访问的信息。
Location:Location响应报头域用于重定向接收者到一个新的位置,Location响应报头域常用在更换域名的时候。
❝response.sendRedirect(“http://www.baidu.com”);
❞
Resfresh:自动跳转(单位是秒),可以在页面通过meta标签实现,也可以在后台实现
❝;❞
以上标签表示当前页面将于3秒之后跳转至url指定的域名。
例:访问百度
「通用」
Request URL: https://www.baidu.com/ 请求地址
Request Method: GET get请求方法
Status Code: 200 OK 状态码:200
Remote Address: 183.232.231.174:443 远程地址:ip
Referrer Policy: strict-origin-when-cross-origin
「百度响应」
Cache-Control: private 缓存控制
Connection: keep-alive 保持连接
Content-Encoding: gzip 编码
Content-Type: text/html;charset=utf-8 类型
「百度请求」
Accept:text/html 支持的数据类型
Accept-Encoding: gzip, deflate, br 编码格式
Accept-Language: zh-CN,zh;q=0.9 语言
Cache-Control: max-age=0 缓存控制
Connection: keep-alive 连接状态
Refresh: 告诉客户端多久刷新一次
Location 让网页重新定位
「状态码」
200 表示访问正常
3xx 请求重定向
4xx 表示找不到资源(404 表示资源不存在)
5xx 服务器代码错误(502 表示网关错误)
TomCat服务器
服务器本质上就是一种高性能的计算机,通常有文件服务器、数据库服务器、程序服务器等。相对于普通的pc而言,在稳定性、安全性、性能等方面有更高的要求。
什么是TomCat
TomCat是一个符合JavaEE WEB标准的最小的「WEB容器」,所有的JSP程序一定要有WEB容器的支持才能运行。而且在给定的WEB容器里面都会支持事务处理操作。
TomCat是由Apache提供的可用的安装版和解压版,安装版可以在服务中出现一个TomCat服务器,免安装没有,开发中使用免安装版,TomCat的简单说就是一个运行Java的网络服务器,**底层是Socket的一个程序,**它也是JSP和Servlet的一个容器,TomCat是Apache软件基金会的Jakarta项目中的一个核心项目。由Apache、Sun和其他一些公司及个人共同开发而成。
由于有了Sun的参与和支持,最新的Servlet和JSP规范总是能在TomCat中得到体现,因为TomCat技术先进、性能稳定,而且免费,因而深受java爱好者的喜爱并得到部分软件开发商的认可,成为目前比较流行的「Web应用服务器。」
TomCat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合被普遍使用,是开发和调适JSP程序的首选,对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache服务器,可以利用它响应HTML(标准通用标记语言下的一个应用)页面的访问请求,实际上TomCat部分是Apache服务器的扩展,但它是独立运行的,所以当你运行TomCat时,它实际上作为一个与Apache独立的进程单独运行。
当配置正确时,Apache为HTML页面服务,而TomCat实际上是运行JSP页面和Servlet,另外,TomCat和IIS等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是TomCat的默认模式,不过TomCat处理静态HTML的能力不如Apache服务器,目前TomCat的最新版本为10.0
安装配置与启动TomCat(基于Deepin20.2)
安装之前首先需要确认是否安装了JDK
运行java -version
得到JDK的相关参数
首先去Apache的TomCat官网下载Tomcat安装包(tar.gz格式)
解压至Tomcat的目录(/data/home/lambda/TomCat本机目录)
进入bin目录
启动TomCat服务(./startup.sh命令)
访问TomCat服务器(查看是否启动)
使用本地地址http://localhost:8080访问成功
需要注意的是TomCat的默认端口是8080,如果之前安装过其他应用占用了8080端口,则需要进行相应的处理。
关闭TomCat服务器
至此表明Tomcat服务器已经安装完成。
Tomcat的目录结构
Tomcat解压之后的目录示意图如下
bin:启动和关闭tomcat的sh文件(shell脚本文件),常用的有startup.sh和shutdown.sh文件
conf:配置文件server.xml,该文件用于配置server相关的信息,比如tomcat启动的端口号,配置主机(Host),web.xml文件配置与web应用(web应用相当于一个web站点),tomcat-user.xml配置用户名密码和相关权限。
lib该目录放置运行tomcat需要运行的jar包
logs存放日志,当我们需要查看日志时候,就可以查询信息
webapps放置web应用(相当于一个web站点)
work工作目录,该目录用于存放.jsp被访问后生成对应的server文件和.class文件。
使用IDEA配置Tomcat
首先选择settings——>othersettings 添加配置tomcat,选择tomcat的安装路径
Servlet的实现
Servlet是Server和Applet的缩写,是服务端小程序的意思,使用Java语言编写的服务端程序,可以像生成动态的WEB页一样,Servlet主要运行在服务器端,并由服务器调用执行,是一种按照Servlet标准来开发的类。是SUN公司提供的一门用于开发动态web资源的技术。(言外之意,要实现web开发,需要实现servlet1标准)
「Servlet本质上也是Java类」,但是要遵循Servlet规范进行编写,没有main方法,它的创建、使用、销毁都由Servlet容器进行管理(如tomcat)(即写自己的类,不用写main方法,别人自动调用)
Servlet是和HTTP协议是紧密联系的,其可以处理HTTP协议相关的内容,这也是Servlet应用广泛的原因之一
提供了Servlet功能的服务器,叫做Servlet容器,其中常见的容器有很多,例如Tomcat、jetty、WebLogic Server、WebSphere、JBoss等等。
Servlet实现(使用Maven创建Web项目)
新建类
在src目录下的java文件夹中创建一个包,在该包下创建一个类。
如下所示
package com.alibaba.servlet;
/**
* @author: 彬彬
* @Date: 2021/12/12 - 12 - 12 - 上午12:12
* @Description: com.alibaba.servlet
* @Version: JDK17
*/
public class Servlet01 {
}
实现Servlet规范
实现Servlet规范,即继承HttpServlet类,并得到响应的jar包,该类中已经完成了通信的规则,只需要进行业务即可。(如果将Tomcat集成到项目中就可以直接使用jar包的工具,如果没有需要将Tomcat的相关工具类lib中的jar包进行引入)
package com.alibaba.servlet;
import javax.servlet.http.HttpServlet;
/**
* @author: 彬彬
* @Date: 2021/12/12 - 12 - 12 - 上午12:12
* @Description: com.alibaba.servlet
* @Version: JDK17
*/
public class Servlet01 extends HttpServlet {
}
由于本机使用maven项目结构,未集成Tomcat,因此需要手动引入tomcat的依赖与对应的Servlet依赖。(以此使该类能够实现HttpServlet类)
重写Service方法
满足Servlet规范只是让我们的类能够满足接受请求的要求,接收到请求后需要对请求进行分析,以及进行业务逻辑处理,计算出结果。则需要添加代码,在规范中有一个service的方法,专门用来做请求处理的操作,业务代码则可以写在该方法中。
package com.alibaba.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: 彬彬
* @Date: 2021/12/12 - 12 - 12 - 上午12:12
* @Description: com.alibaba.servlet
* @Version: JDK17
*/
public class Servlet01 extends HttpServlet {
/**
* @author 彬彬
* @date 2021/12/12 上午12:34
* @param req
* @param resp
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("Hello Servlet!");
resp.getWriter().write("Hello Servlet!");
}
}
设置注解
在完成一切代码的编写之后,还需要向服务器说明,特定说明特定请求对应特定的资源
在开发servlet项目,使用**@WebServlet「将一个继承于javax.servlet.http.HttpServlet的类定义为Servlet组件,在Servlet3.0中,可以使用」@WebServlet**注解将一个继承于javax.servlet.http.HttpServlet的类标注为可以处理用户请求的Servlet
package com.alibaba.servlet;
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;
/**
* @author: 彬彬
* @Date: 2021/12/12 - 12 - 12 - 上午12:12
* @Description: com.alibaba.servlet
* @Version: JDK17
*/
@WebServlet("/servlet01")
public class Servlet01 extends HttpServlet {
/**
* @author 彬彬
* @date 2021/12/12 上午12:34
* @param req
* @param resp
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//打印内容
System.out.println("Hello Servlet!");
//通过流输出数据到浏览器
resp.getWriter().write("Hello Servlet!");
}
}
总结:
首先创建一个普通的java类 实现Servlet的规范,继承HttpServlet类 重写service方法,用来处理请求 设置注解,指定访问的路径(@WebServlet)
使用Idea社区版+Tomcat+maven实现Servlet访问
首先选择Project新建项目,选择maven项目,选择默认的JDK(本机为JDK17),选择模板(带有webapp的)
等待Maven下载相应的依赖,创建成功
配置Tomcat(File——>settings——>Tomcat Server)
添加Tomcat的配置,其中Tomcat Server设置服务器类型,Deployment设置当前web项目的webapp目录,Context Path路径一般无需更改,一般利用此路径访问webapp的jsp文件。
如图所示 启动tomcat10,在浏览器访问localhost:8080/ServletDemo01可以得到Hello World的页面。
接下来在idea中创建maven项目的基本结构(main和test目录),在main创建java目录,并创建相应的包xxx.xxx.xxx,同时创建resources目录和test目录。并在新建的包下创建一个新类
由于实现Servlet的项目需要继承HttpServlet类并实现Servlet所实现的规范,并且由于Tomcat10的变化,使得我们需要为项目添加不同的项目依赖。
<!--添加Servlet的依赖包,-->
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
</dependency>
并在web.xml文件中设置相应的依赖(此时在实现servlet规范的类中就不需要设置相应的注解,设置了会冲突)
<servlet>
<servlet-name>MyServletDemo</servlet-name> <!--继承HttpServlet的类(想运行的类)-->
<servlet-class>org.binbin.MyServlet.MyServletDemo</servlet-class> <!--Servlet类的完整路径-->
</servlet>
<servlet-mapping>
<servlet-name>MyServletDemo</servlet-name> <!--继承HttpServlet的类-->
<url-pattern>/MyServlet</url-pattern> <!--映射的url路径 -->
</servlet-mapping>
Tomcat9是添加javax.servlet.jsp,所以如果用tomcat10去运行带有javax.servlet.jsp依赖的项目会报错。
在相应的包下编写相应的类并实现HttpServlet接口
之后点击运行tomcat命令即可。并通过localhost:8080/项目名/servlet自定义的路径即可在浏览器上访问。
项目启动成功
使用idea付费版+Tomcat+maven实现Servlet
File——>new ——>Project选择相应的参数
需要依据Tomcat的版本选择相应的Java EE版本,由于本次使用的是Tomcat10.0.14,所以需要重新设置EE版本
最后设置项目的名称和项目的位置,点击Finish即可
点击之后需要下载相应的Maven依赖,完成之后目录结构如图所示
点击右上方的Tomcat10.0.14旁的下拉框,选择Edit Configuration,设置项目的启动等相关信息(勾选After launch即表示服务器启动之后会自动打开浏览器访问设置的对外访问路径。)
并且Deployment可以设置项目的对外访问路径
启动Tomcat10.0.14,并且查看访问路径
表明项目创建成功,除此之外,我们还可以新建自己的类实现Servlet规范(建立流程详情见Servlet的实现,不过此处是集成了Tomcat服务器,无需再次更改依赖信息)
此时访问路径(localhost:8080/MyServlet/ser01)显示中文乱码。这是因为servlet的编码规范的原因引起的。
为访问到正常的页面,我们需要在服务端的响应对象设置相应的字符编码和项目的格式
再次访问,页面就显示正常了。
扩展实现:更改404页面
首先在对应的包中创建一个对应的404错误类,并继承HttpServlet,重写doGet和doPost方法
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;
/**
* @author lambda
*/
public class ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter writer = resp.getWriter();
writer.println("<h1>404 NOT Found!!!</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
在web.xml文件中添加对应的servlet的映射
web.xml文件中的映射设置
<servlet>
<servlet-name>errors</servlet-name>
<servlet-class>com.apache.servlet1.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>errors</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
由于在添加的servlet映射中直接使用了 /*
的访问路径,所以web容器开启之后会首先默认去访问/*
所对应的内容,并不会跳到index.jsp文件
但是值得注意的是:在当前默认访问路径下,添加指定的另一个servlet访问路径,却可以访问到另一个servlet程序的内容
这主要是因为路径存在着一定的优先级。
Servlet的生命周期
Servlet没有main方法,不能够独立运行,它的运行完全由Servlet引擎来控制和调度。所谓生命周期,就是指的Servlet容器何时创建servlet实例,何时调用其方法进行请求的处理,何时销毁其实例的整个过程。
实例和初始化时机
当请求到达容器时,容器查找该servlet对象是否存在,如果不存在,则会创建其实例并进行初始化
就绪/调用/服务阶段
有请求到达容器的时候,容器调用servlet对象的service方法,处理请求的方法在整个生命周期中可以被多次调用,HttpServlet的service方法会依据请求的方式来调用doGet和doPost方法,但是这两个do方法默认情况下会抛出异常,需要子类去override
销毁时机
当容器关闭时(应用程序停止时),会将程序中的Servlet实例进行销毁。
上述的生命周期可以通过Servlet中的生命周期方法来观察,在Servlet中有三个生命周期方法,不由用户手动调用,而是在特定的时期内有容器自动调用,观察这三个生命周期方法,即可观察到Servlet的生命周期了。
init方法,在Servlet实例创建之后执行(「证明Servlet有实例创建了」)
/**
初始化方法
系统方法,服务器自动调用
当请求到达Servlet容器时,Servlet容器会判断该Servlet对象是否存在,如果不存在,则创建实例并初始化
该方法只执行一次。
*/
@Override
public void init() {
message = "Hello World!";
}
service方法,每次有请求到达某个servlet方法时执行,用来处理请求(证明Servlet进行服务了)
/**
就绪服务方法(处理请求数据)
系统方法,服务器自动调用
当有请求到达servlet时,就会调用该方法
该方法可以被多次调用
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("Hello Servlet!");
resp.setContentType("text/html; charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.write("这是我的第一个servlet程序");
}
destory方法,Servlet实例销毁时执行,证明Servlet真的被销毁了。
/**
销毁方法
系统方法,也是服务器自动调用的
当我们的服务器关闭或者应用程序停止时,就会调用该方法
该方法也只会执行一次。
*/
@Override
public void destroy() {
System.out.println("Servlet实例被销毁了.....");
}
总的来说,Servlet的加载一共可以分为四步:servlet类加载——>实例化——>服务——>销毁
Tomcat和Servlet的工作原理与工作流程如下
Web Client向Servlet容器(Tomcat)发出Http请求 Servlet容器接收Web Client的请求 Servlet容器创建一个HttpServletRequest对象,将Web Client请求的信息封装到这个对象中 Servlet容器创建一个HttpServletResponse对象 Servlet容器调用HttpServlet对象的service方法,将Resquest和Resopnse作为参数传递给HttpServlet HttpServlet调用HttpServletResuqest对象的有关方法,获取Http请求信息 HttpServlet调用HttpServletResponse对象的有关方法,生成响应数据 Servlet容器把HttpServlet的响应结果传给Web Client
Servlet中的父项目与子项目(基于maven)
maven中的父项目(包含子项目的引用)
<!--子项目-->
<modules>
<module>servlet1</module>
</modules>
maven中的子项目(包含父项目)
<!--父项目-->
<parent>
<artifactId>javaweb1</artifactId>
<groupId>com.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
由于父项目打包需要pom,因此需要在父项目的pom文件中添加<packing>pom</packing>
由于我们写的程序是Java程序,浏览器的不能直接访问,需要通过服务器来完成对指定路径下的内容进行访问,因此需要对写好的servlet程序进行映射,映射的方式有两种,一种是通过注解来实现**@webservlet(/path)
**和配置xml文件的形式。
xml文件配置
<!--注册servlet程序-->
<servlet>
<servlet-name>hello</servlet-name> //此处的内容可以自定义,只要与下面的servlet-name一致即可,
<servlet-class>com.apache.servlet1.HelloServlet</servlet-class> //servlet-class要求写全类的路径
</servlet>
<!--注册Servlet的访问路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet</url-pattern> //表示浏览器访问的路径
</servlet-mapping>
servlet程序web.xml中的Mapping映射问题
对应一个映射路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
此处表示一个Servlet程序指定了一个映射为/servlet
的路径
成功访问!
对应多个映射路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet2</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet3</url-pattern>
</servlet-mapping>
对应的访问路径分别为/servlet、 /servlet1、 /servlet2、 /servlet3
同理不同的路径仍旧可以映射到servlet程序之中去。
可以指定通用路径(采用通配符)
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
对应的访问路径为/servlet或者/servlet/xxxx均可以访问该页面
成功访问!
默认请求路径
<!--表示默认的请求路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
此时启动web容器直接是进入了servlet程序,没有访问index.jsp这个页面,因为/*表示默认访问路径
成功访问!
指定一些前缀或者后缀来访问
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>*.edu</url-pattern>
</servlet-mapping>
指定的后缀为.edu,前面的*表示通配符,表示任何字符均可,只需要保证后缀为.edu即可。
需要注意的是*之前是不能够加项目映射路径的(/),这样会报错
成功访问!
路径访问的优先级问题
指定了固有路径的优先级是最高的,如果找不到,就会走默认的处理请求。
Servlet程序中的上下文对象(ServletContext)
ServletContext也可以当作域对象来使用,通过向servletcontext中存取数据,可以使得整个应用程序共享某些数据,当然不建议存放过多数据,因为servletcontext对象中的数据一旦存储进去,没有手动移除就会一直存在。
常用方法
主要是指继承了HttpServlet的类的响应方法
方法名 | 作用 |
---|---|
getInitParameter() | 主要用于获取servlet初始化参数(可以在web.xml文件中设置) |
getServletConfig() | 主要用于获取servlet的配置 |
getServletContext() | 主要用于获取servlet的上下文环境(比较重要) |
在xml文件中的表示
<?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_5_0.xsd"
version="5.0">
<!--设置全局参数-->
<context-param>
<param-name>namespace</param-name>
<param-value>sds</param-value>
</context-param>
<!--对新建的类进行映射-->
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.alibaba.servlet2.HelloServlet</servlet-class>
<!--设置servlet的初始化参数-->
<init-param>
<param-name>namespace</param-name>
<param-value>sds</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
Servlet的三大域对象
request域对象
在一次请求中有效,请求转发有效,重定向失效
session域对象
在一次会话中有效,请求转发和重定向都有效,session销毁后失效
servletcontext对象
在整个应用程序中有效,服务器关闭后失效
ServletContext对象(全局唯一)及应用
web容器在启动的时候,它会为每个web程序都创建一个对应的ServletContext对象,「它代表了当前的web应用」
「共享数据」
我在当前servlet保存的数据,可以在另一个servlet中拿到。
实现两个servlet之间的通信(通过ServletContext对象)
//在helloservlet类中为其servletcontext设置属性值
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("hello!");
//得到初始化参数(了解即可)
String initParameter = this.getInitParameter("");
//得到servlet的配置(了解即可)
ServletConfig servletConfig = this.getServletConfig();
//得到servlet的上下文(重点掌握)
ServletContext context = this.getServletContext();
//添加一个数据对象
String userName="李白";
//将一个数据对象保存在servletContext中,名字为username,值为userName对象(Object对象)
context.setAttribute("username",userName);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
//在GetServlet类中获取helloServlet类中设置的ServletContext对象的属性值
public class GetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取ServletContext对象,此对象全局唯一,表示该对象与HelloServlet中获取的是同一个对象
ServletContext context = this.getServletContext();
String username = (String) context.getAttribute("username");
//得到相应的属性值之后将其输出到页面上
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
//表示在html页面中实现输出在HelloServlet中为ServletConetxt这个共享对象所建立的属性值。
resp.getWriter().println(username);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
<!--设置对应的web.xml-->
<?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_5_0.xsd"
version="5.0">
<!--对新建的类进行映射-->
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.alibaba.servlet2.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>get</servlet-name>
<servlet-class>com.alibaba.servlet2.GetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>get</servlet-name>
<url-pattern>/get</url-pattern>
</servlet-mapping>
</web-app>
「测试结果:」
「获取初始化参数」
在web.xml文件中设置对应的servletcontext初始化的参数
<!--配置一些web应用的初始化参数-->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/MyBatis</param-value>
</context-param>
设置相应的ServletDome类,设置相应的servlet映射,并运行该类,得到输出结果
<servlet>
<servlet-name>demo</servlet-name>
<servlet-class>com.alibaba.servlet2.ServletDemo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo</servlet-name>
<url-pattern>/demo</url-pattern>
</servlet-mapping>
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
//对应的初始化参数可以在web.xml文件中设定
String initParameter = servletContext.getInitParameter("url");
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().println("<h1>"+initParameter+"</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
「最终结果」
请求转发
请求转发执行过程中,访问的路径不会发生变化,但是内容却与本程序不同。
「这是一种服务器行为」,当客户端请求到达以后,服务器进行转发,此时会将请求对象进行保存,**地址栏中的URL地址不会发送改变。**得到响应之后,服务器端再响应发送给客户端,**从开始到结束都只有一个请求发出。**可以达到多个资源协同响应的效果。
「实现请求转发」
创建一个专门处理请求转发的类,并实现HttpServlet类。此处需要注意的是:请求转发的getRequestDispatcher方法传入的是转发的目的地的路径(本例中是转发至当前目录下的/demo路径,该路径在web.xml文件中已经设置。)并且转发之后项目的路径不会发生改变,这点区别于重定向。
//创建新类
public class ServletRePost extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了ServletRePost程序");
ServletContext servletContext = this.getServletContext();
//上下文得到请求转发,转发的路径为/demo
RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/demo");
//调用forward方法进行请求的转发
requestDispatcher.forward(req,resp);
/**连接起来,无返回值
servletContext.getRequestDispatcher("/demo").forward(req,resp);
*/
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
为新建里的类设置相应的Servlet映射,设置相应的访问地址。
<!--设置请求转发的servlet程序的访问地址-->
<servlet>
<servlet-name>repost</servlet-name>
<servlet-class>com.alibaba.servlet2.ServletRePost</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>repost</servlet-name>
<url-pattern>/repost</url-pattern>
</servlet-mapping>
「测试结果」(启动服务器,进行访问)
读取资源文件
读取资源文件功能需要Properties类。
在resources中创建的properties文件,在启动项目之后会创建一个新的target目录,而在target目录的classes文件夹中也存在properties文件。并且两个地方都存在properties文件
在com.xxx.Servlet包下另建一个新的配置文件properties,并重新启动项目,可以发现,对应的properties并没有生成在WEB-INF下的classes中。
这与maven的导出配置有关,需要在maven项目的pom.xml配置文件中添加相应的resource标签导出。
「为相应的pom文件添加导出依赖参数」
<!--maven项目中添加导出的配置-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
「导出成功」
「总结:」
在java目录下新建properties文件 在resource目录下新建properties文件
两者都会被打包到classes目录下,俗称类路径(ClassPath)。
创建对应的类,并通过流的方式去读取配置文件的内容(需要注意的是配置文件的路径可以采用相对路径的方式来读取,在target目录下进行读取。)注意如果是绝对路径读取的话会出现服务器500的错误。因为绝对路径从磁盘开始读取。而web页面是从对应的打包目录开始读取内容。
public class ServletProperties extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
//传入资源路径:/表示当前项目,也就是servlet2-1.0-SNAPSHOT,依次找到db.properties文件即可
InputStream resourceAsStream = servletContext.getResourceAsStream("/WEB-INF/classes/db.properties");
//读取配置文件的内容并输出
Properties properties = new Properties();
properties.load(resourceAsStream);
String username = properties.getProperty("username");
String password = properties.getProperty("password");
//将读取到的内容展示到前端页面中
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
if (username==null || password==null) {
resp.getWriter().println(username + ":"+password);
}else {
resp.getWriter().println("<h1>对不起,这个地方貌似出了点问题.......</h1>");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
并为对应的类创建对应的Servlet映射。
<servlet>
<servlet-name>properties</servlet-name>
<servlet-class>com.alibaba.servlet2.ServletProperties</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>properties</servlet-name>
<url-pattern>/pro</url-pattern>
</servlet-mapping>
HttpServletRequest对象
HttpServletRequest对象:主要是用来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等都属于客户端发送来的信息,service()方法中形参接收的是HttpServletRequest「接口的实例化对象」,表示该对象主要应用在http协议上,该对象是由Tomcat封装好传递过来的。
HttpServletRequest是ServletRequest的子接口,ServletRequest只有一个子接口,就是HttpServletRequest。
既然只有一个子接口为什么不将两个接口合并为一个?
长远来讲,现在主要的协议是http协议,但以后可能会出现更多的协议,若想要以后支持这种新协议,只需要直接继承ServletRequest接口就行。
在HttpServletRequest接口中,定义的方法很多,但是都是围绕客户端参数的,但是怎么拿到该对象呢?不需要。直接在service方法中由容器传入过来,而我们需要做的就是取出对象中的数据,进行分析、处理。
接收请求
常用方法
方法名 | 作用 |
---|---|
getRequestURL() | 获取客户端发出请求时的完整URL |
getRequestURI() | 获取请求行中的资源名称部分(项目名称开始) |
getQueryString() | 获取请求行中的参数部分 |
getMethod() | 获取客户端的请求方式 |
getProtocol() | 获取HTTP的版本号 |
getContextPath() | 获取webapp的名字 |
新建一个项目来测试
/**
* 常用方法如下;
* */
// 获取请求时候的完整路径(从http开始,到?之前结束)
String requestUrlFull=request.getRequestURL().toString();
System.out.println("获取请求时候的完整路径:"+requestUrlFull);
//获取请求时的部分路径(从项目的站点名开始,到?之前结束)
String requestUri=request.getRequestURI();
System.out.println("获取请求时的部分路径:"+requestUri);
//获取请求时的参数字符串(从?开始,到最后的字符串)
String queryString=request.getQueryString();
System.out.println("获取请求时的参数字符串:"+queryString);
//获取请求的方式(get和post)
String requestMethod=request.getMethod();
System.out.println("获取请求的方式:"+requestMethod);
//获取协议版本(http1.1)
String requestProtocol=request.getProtocol();
System.out.println("获取协议版本:"+requestProtocol);
//获取项目的站点名(项目对外访问路径)
String requestSite=request.getContextPath();
System.out.println("获取项目的站点名:"+requestSite);
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.println("<h1>请问1+1等于多少?</h1>");
获取请求参数
方法名 | 作用 |
---|---|
getParameter(String name) | 获取指定名称的参数 |
getParameterValues(String name) | 获取指定名称参数的所有值 |
「示例」:
/**获取请求的参数*/
//获取指定名称的参数值,并且所有返回的都是字符串
String userName=request.getParameter("username");
String userPassword=request.getParameter("userpassword");
System.out.println("userName="+userName);
System.out.println("userPassword"+userPassword);
//获取指定名称参数的所有值(用于复选框传值),所以返回的是字符串数组
String[] values=request.getParameterValues("hobby");
//判断数组是否为空
if (values!=null && values.length>0){
for (String value : values) {
System.out.println("hobby="+value);
}
}
请求乱码问题
由于现在的request属于接收客户端的参数,所以必然要其默认的语言编码。主要是由于在解析过程中默认使用的编码方式为ISO-8559-1(此编码不支持中文)。所以解析时一定会出现乱码。要想解决这种问题,需要设置request中的编码方式,告诉服务器以何种方式来解析数据,或者在接收到乱码数据以后,再通过相应的编码格式还原。
需要注意的是:在Tomcat8以上的GET请求中文不会乱码,但POST请求会乱码。
示例
新建一个login.jsp,创建表单提交页面,此处可以指定POST请求
之后在浏览器中提交中文内容之后就会出现乱码。
总结:
请求方式 | Tomcat8及以上 | Tomcat7及以下 |
---|---|---|
GET请求 | 不会乱码,不需要处理 | 会乱码,需要设置:new String(requset.getParameter("xxx").getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8); |
POST请求 | 会乱码,需要通过设置request.setCharacterEncoding("UTF-8")解决 | 会乱码,需要通过设置request.setCharacterEncoding("UTF-8")解决 |
「解决」
设置服务器解析编码的格式
「方式一:」
request.setCharacterEncoding("UTF-8");
这句话request.setCharacterEncoding("UTF-8");只针对post请求
「方式二:」
new String(requset.getParameter("xxx").getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
这句话针对任何请求的方式,但是需要注意的是如果本身是UTF-8的格式,再转为UTF-8,会出现另一种乱码。
request作用域
通过该对象可以在一个请求中传递数据,作用范围:「在一次请求中有效,即服务跳转有效。」
请求转发到Servlet程序
//Servlet5中设置相关请求的域内容,并跳转到Servlet6,并将设置的内容展示到页面中
/**
* @author lambda
* request的作用域
*/
@WebServlet("/s5")
public class Servlet5 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了Servlet05这个类");
//设置域对象的内容
req.setAttribute("name","admin");
req.setAttribute("age",18);
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
req.setAttribute("list",list);
//通过ServletContext跳转至Servlet06
ServletContext servletContext = req.getServletContext();
servletContext.getRequestDispatcher("/s6").forward(req,resp);
// req.getRequestDispatcher("/s6").forward(req,resp);
}
}
/**
* @author lambda
* 设置servlet06获取servlet05请求跳转后的参数及参数值
对应的参数名要匹配。
*/
@WebServlet("/s6")
public class Servlet6 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了Servlet6");
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
//此处需要注意的是我们设置的是request的域的属性值,而不是参数值Parameter
String name = (String) req.getAttribute("name");
Integer age = (Integer) req.getAttribute("age");
List<String> lists= (List<String>) req.getAttribute("list");
System.out.println(name);
System.out.println(age);
System.out.println(lists.get(0));
PrintWriter writer = resp.getWriter();
writer.println("<h1>得到Servlet5传过来的姓名参数:"+name+"</h1><br>");
writer.println("<h1>得到Servlet5传过来的年龄参数:"+age+"</h1><br>");
for (String list : lists) {
writer.println("<h1>得到Servlet5传过来的list参数:"+list+"</h1>");
}
}
}
需要注意的是在请求跳转的目的地程序中获取之前设置的属性值需要注意类型转换问题,需要与之前所设置的匹配,不然会出现500的错误,类型转换失败。同时还需要注意属性与参数值的区别,属性是请求所有的,参数值是页面提交表单之后或者人为输入之后才有的。
结果展示:
请求转发到JSP页面中
请求转发到JSP页面中,「并通过域对象传递数据」
Servlet5通过请求转发将数据共享给index.jsp页面。
<%@ page import="java.util.List" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1>这是一个index.jsp页面</h1>
<%-- 如果要在jsp中写java代码,需要写在脚本段中--%>
<%
//获取域对象的相关属性内容
String name = (String) request.getAttribute("name");
Integer age= (Integer) request.getAttribute("age");
List<String> list=(List<String>)request.getAttribute("list");
System.out.println(name);
System.out.println(age);
for (String s : list) {
System.out.println(s);
}
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.println("从Servlet5传过来的姓名:"+name);
writer.println("从Servlet5传过来的年龄:"+age);
for (String s : list) {
writer.println("从Servlet5传过来的list内容:"+s);
}
%>
</body>
</html>
/**
* @author lambda
* request的作用域
*/
@WebServlet("/s5")
public class Servlet5 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了Servlet05这个类");
//设置域对象的内容
req.setAttribute("name","admin");
req.setAttribute("age",18);
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
req.setAttribute("list",list);
/* //通过ServletContext跳转至Servlet06
ServletContext servletContext = req.getServletContext();
servletContext.getRequestDispatcher("/s6").forward(req,resp);*/
// req.getRequestDispatcher("/s6").forward(req,resp);
//跳转到jsp页面
req.getRequestDispatcher("index.jsp").forward(req,resp);
}
}
「结果展示」
request域对象中的数据在「一次请求中有效」,则经过请求转发,request域中的数据依然存在,则在请求转发的过程中可以通过request来传输或共享数据。
HttpServletResponse对象
web服务器接收到客户端的http请求,针对这个请求。分别创建一个代表请求的HttpServletRequest对象和一个代表响应的HttpServletResponse对象。
如果要获取客户端请求的参数,使用HttpServletRequest 如果要给客户端响应一些信息,使用HttpServletResponse
HttpServletResponse对象的主要功能用于服务器对客户端的请求进行响应,将web服务器处理后的结果返回给客户端,service方法中形参接收到的是HttpServletResponse接口的实例化对象,这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。
简单分类
发送数据方法
接到客户端请求后,可以通过HttpServletResponse对象直接进行响应,响应时需要获取输出流,有两种形式:不能同时使用
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
发送响应头方法
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
void setDateHeader(String var1, long var2);
void addDateHeader(String var1, long var2);
void setHeader(String var1, String var2);
void addHeader(String var1, String var2);
void setIntHeader(String var1, int var2);
void setStatus(int var1);
响应状态常量
int SC_CONTINUE = 100;
int SC_SWITCHING_PROTOCOLS = 101;
int SC_OK = 200;
int SC_CREATED = 201;
int SC_ACCEPTED = 202;
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
int SC_NO_CONTENT = 204;
int SC_RESET_CONTENT = 205;
int SC_PARTIAL_CONTENT = 206;
int SC_MULTIPLE_CHOICES = 300;
int SC_MOVED_PERMANENTLY = 301;
int SC_MOVED_TEMPORARILY = 302;
int SC_FOUND = 302;
int SC_SEE_OTHER = 303;
int SC_NOT_MODIFIED = 304;
int SC_USE_PROXY = 305;
int SC_TEMPORARY_REDIRECT = 307;
int SC_BAD_REQUEST = 400;
int SC_UNAUTHORIZED = 401;
int SC_PAYMENT_REQUIRED = 402;
int SC_FORBIDDEN = 403;
int SC_NOT_FOUND = 404;
int SC_METHOD_NOT_ALLOWED = 405;
int SC_NOT_ACCEPTABLE = 406;
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
int SC_REQUEST_TIMEOUT = 408;
int SC_CONFLICT = 409;
int SC_GONE = 410;
int SC_LENGTH_REQUIRED = 411;
int SC_PRECONDITION_FAILED = 412;
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
int SC_REQUEST_URI_TOO_LONG = 414;
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
int SC_EXPECTATION_FAILED = 417;
int SC_INTERNAL_SERVER_ERROR = 500;
int SC_NOT_IMPLEMENTED = 501;
int SC_BAD_GATEWAY = 502;
int SC_SERVICE_UNAVAILABLE = 503;
int SC_GATEWAY_TIMEOUT = 504;
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
常见应用
向浏览器输出消息
利用getWriter和getOutputStream来向浏览器输出信息。
下载文件
获取文件的下载路径——获取下载文件的文件名——使浏览器支持我们所需要下载的东西——获取文件下载的输入流——创建文件缓冲区——获取OutputStream——将文件输出流写入到缓冲区——使用OutputStream将缓冲区的内容输出到客户端
创建新的类,并设置相应的参数,利用浏览器下载
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取下载文件的路径(可以使用上下文路径)
//String realPath = this.getServletContext().getRealPath("/1.png");
String realPath="/home/lambda/Java—Servlet/ServletDemo02/httpresponse/target/classes/1.png";
System.out.println("获取文件的绝对下载路径"+realPath);
//表示获取绝对路径的倒数第二个字符再加1个字符,//表示转义/
String fileName= realPath.substring(realPath.lastIndexOf("/") + 1);
//让浏览器支持下载我们需要的东西(需要设置response对象,设置他们的头),并设置文件的编码格式
resp.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8"));
//获取下载文件的输入流
FileInputStream fileInputStream = new FileInputStream(realPath);
//创建缓冲区
byte[] buffer = new byte[1024];
int len=0;
//获取文件输出流(向浏览器输出)
ServletOutputStream outputStream = resp.getOutputStream();
//将fileInputStream写入到buffer缓冲区中,输出到客户端中
while ((len=fileInputStream.read(buffer))!=-1){
outputStream.write(buffer,0,len);
}
fileInputStream.close();
outputStream.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
设置对应项目的访问路径
<?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_5_0.xsd"
version="5.0">
<servlet>
<servlet-name>filedown</servlet-name>
<servlet-class>com.gpnu.httpresponse.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>filedown</servlet-name>
<url-pattern>/f</url-pattern>
</servlet-mapping>
</web-app>
访问结果
注意事项:
下载文件的绝对路径不能写错,是在 /home/lambda/Java—Servlet/ServletDemo02/httpresponse/target/classes/1.png
路径下,target目录下的内容在截取路径最后的文件名时需要截取倒数第二个内容再加1 注意关闭输入与输出流
验证码功能
验证码怎么来?
前端由JS实现 后端由Java的图片类来实现,生成一个图片
首先在后端编写生成图片验证码的类,并设置相应的参数
public class ImageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//让浏览器实现3秒刷新一次
resp.setHeader("refresh","3");
//在内存中创建一个图片,使用BufferedImage,设置长宽及类型
BufferedImage image = new BufferedImage(80,20,BufferedImage.TYPE_INT_RGB);
//得到这张图,并写内容
//得到一只画笔
Graphics2D graphics =(Graphics2D) image.getGraphics();
//设置图片的背景颜色
graphics.setColor(Color.white);
//填充矩形(白色)
graphics.fillRect(0,0,80,20);
//给图片写数据
//首先给画笔换个颜色
graphics.setColor(Color.BLUE);
//为画笔预先设置字体
graphics.setFont(new Font(null,Font.BOLD,20));
graphics.drawString(getRandomNumber(),0,20);
//告诉浏览器这个请求用图片形式打开(image/png)
resp.setContentType("image/png");
//网站是有缓存的,但是不让它缓存
resp.setDateHeader("expires",-1);
resp.setHeader("Cache-Control","no-cache");
resp.setHeader("Pragma","no-cache");
//把图片写给浏览器
ImageIO.write(image,"png",resp.getOutputStream());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
public String getRandomNumber(){
Random random = new Random();
String number = random.nextInt(99999999)+ "";
//由于必须要保证数字验证码必须是7位,所以要使用StringBuffer来填充
StringBuffer sb = new StringBuffer();
//此处遍历表示,如果number的位数小于8位,则需要在sb后面添加0,缺几位填几位。
for (int i=0;i<8-number.length();i++){
sb.append("0");
}
String numbers=number+sb.toString();
return numbers;
}
}
设置xml配置属性
<servlet>
<servlet-name>image</servlet-name>
<servlet-class>com.gpnu.httpresponse.ImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>image</servlet-name>
<url-pattern>/i</url-pattern>
</servlet-mapping>
结果展示
重定向
重定向是一种服务端指导客户端的行为。客户端发送第一个请求,被服务端接收处理之后,服务端会进行响应,在响应的同时,服务端会给客户端一个新的地址(下次请求的地址response.sendRedirect(url)),当客户端接收到响应之后,会立刻,马上自动根据服务器给的地址发起第二个请求,服务器接收请求并作出响应,重定向完成。
总的来说,重定向有两个请求存在,并且属于客户端行为。
一个web资源收到客户端的请求之后。它会通知客户端去访问另一个web资源,「这样的过程就叫做重定向。」
常见场景:
用户登录界面
void sendRedirect(String var1) throws IOException;
编写Java重定向源码
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//此时的路径存在问题,重定向时会出现404
//resp.sendRedirect("/i");
//此时表示跳转到当前项目的i路径下的页面中去。
resp.sendRedirect("/h/i");
//h表示当前项目的对外访问路径
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
为程序添加Servlet映射
<servlet>
<servlet-name>redirect</servlet-name>
<servlet-class>com.gpnu.httpresponse.RedirectServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>redirect</servlet-name>
<url-pattern>/r</url-pattern>
</servlet-mapping>
重定向执行结果
注意:
重定向的路径必须要写全,不然会出现404错误
面试题
相同点:
页面都会实现跳转
不同点:
请求转发的时候,URL不会发生变化。 重定向的时候,URL会发生变化。.
请求转发(request.getRequestDispatcher().forward()) | 重定向(response.sendRedirect) |
---|---|
一次请求,数据在request域中共享 | 两次请求,数据在request域中不共享 |
服务器端行为 | 客户端行为 |
地址栏不会发生变化 | 地址栏会发生变化 |
绝对地址定位到站点后 | 绝对地址可以写到http:// |
响应数据乱码
由于客户端与服务器端采用的编码格式往往不一致,常常会导致响应乱码问题。
字符流乱码
解决:
创建一个Servlet类,获取一个输出字符流
@WebServlet("/s7")
public class Servlet7 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/** //设置服务端的编码格式
resp.setCharacterEncoding("UTF-8");
//设置客户端的编码格式,两种格式一定要一致为UTF-8(通过响应头的形式)
// resp.setHeader("content-type","text/html;charset=utf-8");*/
//同时设置服务端和客户端的编码格式
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.println("<h1>这是中文的字符,希望是UTF-8格式</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
结果
如果客户端与服务器端的编码不一致,就会出现乱码问题:
字节流乱码(可能)
不论客户端与服务端的编码格式是否一致,都一致给他们双方设定一个编码格式,保持统一。
@WebServlet("/s8")
public class Servlet8 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//需要同时设置客户端与服务器端的编码格式,要求一致
resp.setContentType("text/html;charset=utf-8");
ServletOutputStream outputStream = resp.getOutputStream();
outputStream.write("<h1>你好,这是我用字节流输出的内容</h1>".getBytes(StandardCharsets.UTF_8));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
这样就总是能保证响应始终不会乱码。
Cookie和Session
「会话」:用户打开一个浏览器,点击了很多链接,访问了多个web资源,关闭浏览器,这个过程可以被称为会话。
Cookie
Cookie是浏览器提供的一种技术,通过服务器将一些只需要保存在客户端,或者在客户端进行处理的数据,放在本地的计算机上,不需要通过网络传输,因而提高网页处理的效率。并且能够减少服务器的负载,但是由于Cookie是服务器端保存在客户端的信息,所以其安全性很差,例如常见的记住密码则可以通过Cookie实现。
Cookie的格式是:键值对用=链接,多个键值对间通过分号隔开。
这是一种客户端技术,(服务端给客户端的)
「从请求中拿到cookie信息,服务器响应给客户端cookie」
例1:设置和获取cookie
1.首先编写获取和设置客户端的代码(实现获取用户上次登录的时间)
public class CookieDemo01 extends HttpServlet {
private static String LogInTime="logTime";
private static final int MAX_AGE =60*60;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//服务器记录访问时间,将记录封装成一个令牌,给客户端,这就cookie
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
//服务端需要从客户端获取cookie,因为cookie是服务端给客户端的一个令牌(因此需要从请求获取)
//这里是数组,说明可能有多个cookie
Cookie[] cookies = req.getCookies();
out.println("<h1>您第一次访问该网站的时间是:</h1>");
//判断cookie是否存在
if (cookies!=null){
//如果存在,需要取出想要的内容
for (int i=0;i<cookies.length;i++){
Cookie cookie = cookies[i];
//如果当前的cookie的名字等于LogInTime,执行操作
if (LogInTime.equals(cookie.getName())){
//获取对应name的cookie的值
String time = cookie.getValue();
//由于是日期,所以需要date类来表示
Date date=new Date(Long.parseLong(time));
out.println(date);
}
}
}else {
out.println("这是您第一次访问本站");
}
//服务端给客户端响应(发送)一个Cookie,由于响应对象添加的内容是一个Cookie对象,所以需要创建一个对象
Cookie cookie = new Cookie(LogInTime,System.currentTimeMillis()+"");
//可以给cookie设置有效期1分钟
cookie.setMaxAge(MAX_AGE);
resp.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
编写对应xml映射Servlet
<servlet>
<servlet-name>cookoiedemo01</servlet-name>
<servlet-class>com.bytedance.javaweb2_cookie_session.CookieDemo01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cookoiedemo01</servlet-name>
<url-pattern>/c1</url-pattern>
</servlet-mapping>
访问网站,获取上次登录时间()
需要注意的是设置cookie的名字与获取cookie的名字要一致,否则会报500的错误。
例2:删除一个cookie
编写删除例1cookie的方法(只需要将cookie的名字和值设置和例1中的一致即可,再将存活时间设置为0)
public class CookieDemo02 extends HttpServlet {
private static String logInTime="logTime";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//创建一个与例1一致的cookie
Cookie cookie = new Cookie(logInTime, String.valueOf(System.currentTimeMillis()));
//设置cookie的有效期
cookie.setMaxAge(0);
//将该cookie响应给客户端
resp.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
为对应的例子设置servlet映射(使用xml文件的形式)
<servlet>
<servlet-name>cookiedemo02</servlet-name>
<servlet-class>com.bytedance.javaweb2_cookie_session.CookieDemo02</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cookiedemo02</servlet-name>
<url-pattern>/c2</url-pattern>
</servlet-mapping>
首先访问例1,再访问例2,看看网站中cookie的变化
例3:cookie保存中文并输出
可以运用编码
首先编写一个可以添加cookie的Servlet程序
public class CookieDemo03 extends HttpServlet {
private static String name="name";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
Cookie[] cookies = req.getCookies();
PrintWriter out = resp.getWriter();
if (cookies!=null) {
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())){
out.println(cookie.getValue());
//对获取的中文信息进行解码
// String value = URLDecoder.decode(cookie.getValue(), "UTF-8");
}
}
}else {
out.println("这是你第一次访问该网站!");
}
Cookie cookie = new Cookie(name, "谢谢");
//另一种形式编码
Cookie cookie1 = new Cookie("hello", URLEncoder.encode("你好", "UTF-8"));
resp.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
//此处需要注意的是应该使用URLEncoder.encode("你好", "UTF-8")和URLDecoder.decode(cookie.getValue(), "UTF-8");保证安全性。这是两个编码转换的类(传输中文时可以使用)
为该类编写对应的Servlet映射
<servlet>
<servlet-name>cookiedemo03</servlet-name>
<servlet-class>com.bytedance.javaweb2_cookie_session.CookieDemo03</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cookiedemo03</servlet-name>
<url-pattern>/c3</url-pattern>
</servlet-mapping>
访问该网站,可以看到在页面中输出了对应设置的中文信息
需要注意中文编码的转换
Cookie到期时间
到期时间可以用来指定cookie何时失效,默认为当前浏览器关闭即失效,我们可以手动设定cookie的有效时间(通过到期时间计算),通过setMaxAge(int time)设定cookie的最大有效时间,以秒为单位。
「到期时间的取值:」
负整数
若为负数,则表示不存储该cookie
cookie的maxAge默认值是-1,表示只在浏览器内存中存活,一旦关闭浏览器窗口,那么cookie就会消失。
正整数
若大于0的数整数,表示存储的秒数
表示cookie对象可存活到指定的秒数,当生命大于0时,浏览器会把cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie也会存活相应的时间
零
若为0,则表示删除该cookie
cookie的声明等于0是一个特殊的值,它表示cookie被作废,也就是说如果原来浏览器已经保存了这个cookie,那么可以通过cookie的setMaxAge(0)来删除这个cookie,无论是在浏览器内存中,还是在客户端硬盘上都会删除这个cookie。
cookie方法
Cookie[] cookies = req.getCookies();//获取cookie
cookie.getName()//获取cookie中的key
cookie.getValue()//获取cookie中的值
Cookie cookie = new Cookie(LogInTime,System.currentTimeMillis()+"");//新建一个cookie
cookie.setMaxAge(MAX_AGE);//设置cookie的有效期
resp.addCookie(cookie);//响应给客户端一个cookie
cookie一般会保存在本地的用户目录下。
cookie上限
一个网站cookie是否存在上限?不同的浏览器有着不同的限制。
一个cookie只能保存一个信息 一个web站点可以给浏览器发送多个cookie,最多存放20个cookie cookie大小有限制4kb 300个cookie浏览器上限
删除cookie
不设置有效期,关闭浏览器,自动失效。 设置有效期时间为0
编码与解码(cookie)
URLEncoder.encode("String", "UTF-8")//对信息进行编码
URLDecoder.decode(cookie.getValue(), "UTF-8")//对信息进行解码
Cookie的路径问题
Cookie的setPath设置cookie的路径,这个路径直接决定服务器的请求是否从浏览器中加载某些cookie
情景一
「当前服务器下的任何项目的任意资源都可以获取Cookie对象」
/*当前项目的路径为s01*/
Cookie cookie = new Cookie("xxx","xxx");
//设置路径为“/”,表示在当前服务器下任何项目都可以访问到Cookie对象
cookie.setPath(“/”);
resp.addCookie(cookie);
情景二
「当前项目下的资源可获取cookie对象(默认不设置cookie的path)」
/*当前项目的路径为:s01*/
Cookie cookie=new Cookie(“xxx”,“xxx”);
//设置路径为“/s01”,表示在当前项目下的任何项目都可以访问到Cookie对象
cookie.setPath(“/s01”)//默认情况下可以不设置cookie的路径path
resp.addCookie(cookie);
情景三
指定项目下的资源可获取Cookie对象
/*当前项目路径为s01*/
Cookie cookie=new Cookie("xxx","xxx");
//设置路径为“/s02”,表示只有在s02项目下才可以访问到Cookie对象
cookie.setPath("/s02");//只能在s02项目下获取cookie,就算cookie是s01产生的,s01也不能获取它
情景四
指定目录下的资源可以获取Cookie对象
/*当前项目路径为s01*/
Cookie cookie=new Cookie("xxx","xxx");
//设置路径为“/s01/cook”,表示s01/cook目录下才可以访问到cookie对象
cookie.setPath("/s01/cook");
resp.addCookie(cookie);
如果我们设置path,如果当前访问的路径包含了cookie的路径(当前访问路径在cookie路径基础上要比cookie的范围小),cookie就会加载到request对象之中
cookie的路径指的是可以访问该cookie的顶层目录,该路径的子路径也可以访问该cookie
当访问的路径包含cookie的路径时,则该请求上将带上该cookie,如果该路径不含cookie路径,则将不会带上该路径。
注意事项
cookie保存在当前浏览器中,在一般站点中常常有记住用户名这个操作,该操作只是将信息保存在本机上。cookie不能跨浏览器。 cookie的存中文问题,需要编码和解码 同名Cookie问题,如果服务器发送重复的Cookie,那么会覆盖原有的Cookie 浏览器存放cookie的数量,不同的浏览器对Cookie也有限定,Cookie的存储数量是有上限的,Cookie是存储在客户端(浏览器)的,而且一般是由服务端创建和设定的,后期结合session来实现会话跟踪。
Session(重点)
HttpSession对象是javax.servlet.http.HttpSession的实例,该接口并不像HttpServletRequest或HttpServletResponse还存在一个父接口,该接口是一个纯粹的接口,这是因为session本身就属于HTTP协议范畴。
对于服务器而言,每一个连接到它的客户端都是一个session,servlet容器使用此接口创建HTTP客户端和HTTP服务器之间的会话,会话将保留指定的时间段,跨多个连接或者来自用户的页面请求,一个会话通常用对应于一个用户,该用户可能多次访问一个站点,可以通过此接口查看和操作有关某个会话的信息,比如会话标识符、创建时间和最后一次访问时间,在整个session中,「最重要的就是属性的操作」。
session无论是客户端还是服务端都可以感知到,若重新打开一个新的浏览器,则无法取得之前设置的session,因为每一个session只保存在当前的浏览器中,并在相关的页面取得。
session的作用就是为了标示一次会话,或者说确认一个用户,并且在一次会话(一个用户的多次请求)期间共享数据,我们可以通过request.getSession()方法,获取当前会话的session对象
Session即是「会话」。
服务器会给每一个用户(浏览器)创建一个Session对象 一个Session独占一个浏览器,只要浏览器没有关闭,这个Session就会一直存在。
例如:用户在某个网站登录之后,整个网站就都可以访问了。
标识符JSESSIONID
session既然是为了标识一次会话,那么这次会话就应该有一个唯一的标志,这个标志就是sessionid
每当一次请求达到服务器,如果开启了会话(访问了session),服务器第一步会查看是否从客户端回传一个名为JSESSIONID的cookie,
如果没有则认为这是一次新的会话,会创建一个新的session对象,并用唯一的sessionid为此次会话做一个标识。 如果有JSESSIONID这个cookie回传,服务器则会根据JSESSIONID这个值去查看是否含有id为JSESSION值的session对象, 如果没有,则认为是一个新的会话,重新创建一个新的session对象,并标志此次会话 如果有,则认为是之前标志的一次会话,返回该session对象,数据达到共享。
这里的JSESSIONID的cookie是一个比较特殊的cookie,当用户请求服务器时,如果访问了session,则服务器会创建一个名为JSESSIONID,值为获取到的session(无论是新获取到的还是新创建的)sessionid的cookie对象,并添加到response对象中,响应给客户端,「有效时间为关闭浏览器」。
所以session的底层依赖cookie来实现。
Session和Cookie的区别
Cookie是将用户的数据写给用户的浏览器,浏览器缓存(可以保存多个) Session是把用户的数据写到用户独占Session中。服务器端保存(保存重要的信息,减少服务器资源浪费) Session对象由服务器创建。
Session的使用场景
保存一个登录用户的信息 购物车信息 在整个项目(网站)中经常会使用的数据将其保存在Session中。
Session常用方法
Session使用
由于Session是服务端给予客户端的,并且一旦打开浏览器就会存在session。所以我们可以设置session相关的存储的内容
例1:向session中存入类对象
首先创建一个新的包,并创建新的实体类Person
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
编写Servlet代码内容(即为Session赋值,同时获取sessionId并输出)
public class SessionDemo01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决乱码问题
resp.setContentType("text/html;charset=utf-8");
//得到session(从请求中获取Session)
HttpSession session = req.getSession();
//给session中存入东西
session.setAttribute("name",new Person("马云",65));
//获取session的id
String id = session.getId();
//判断是不是新的session
if (session.isNew()){
resp.getWriter().write("这是新创建的session,id为"+id);
}else {
resp.getWriter().write("session已经在服务器中创建了"+id);
}
}
}
为对应的Servlet设置相应的映射内容
<servlet>
<servlet-name>sessiondemo01</servlet-name>
<servlet-class>com.bytedance.javaweb2_cookie_session.SessionDemo01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sessiondemo01</servlet-name>
<url-pattern>/s1</url-pattern>
</servlet-mapping>
运行程序测试结果
例2:获取session中设置的值
编写Servlet程序,获取session中的值并输出到页面中
public class SessionDemo02 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
HttpSession session = req.getSession();
Person person =(Person) session.getAttribute("name");
resp.getWriter().write(String.valueOf(person));
}
}
编写xml文件设置servlet映射
<servlet>
<servlet-name>cookiedemo02</servlet-name>
<servlet-class>com.bytedance.javaweb2_cookie_session.CookieDemo02</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cookiedemo02</servlet-name>
<url-pattern>/c2</url-pattern>
</servlet-mapping>
访问s2路径,获取输出结果
例3:手动和自动取消session
可以手动或自动取消session,但是一旦浏览器开着就会立即获得一个新的session
编写相关的取消程序(首先移除掉对应的值,之后取消掉session)
public class SessionDemo03 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
//取消掉session的节点
session.removeAttribute("name");
//此方法用于注销session(手动注销)
session.invalidate();
}
}
设置相关的xml文件(可以映射servlet和自动取消session)
<servlet>
<servlet-name>sessiondemo03</servlet-name>
<servlet-class>com.bytedance.javaweb2_cookie_session.SessionDemo03</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sessiondemo03</servlet-name>
<url-pattern>/s3</url-pattern>
</servlet-mapping>
<!--设置session默认的失效时间-->
<session-config>
<!--设置session的过期时间为1分钟,以分钟为单位-->
<session-timeout>1</session-timeout>
</session-config>
运行程序即可看到session过了一分钟之后就获取到新的sessionID。
例4:自己设定到期时间
我们可以设置和获取最大不活动时间,此时设置的时间单位为秒
编写相应的类文件(实现设置和获取最大不活动时间)
@WebServlet("/sd4")
public class SessionDemo04 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
session.setAttribute("username","hello");
//获取session的最大不活动时间,就是存活时间
System.out.println("session的最大不活动时间为;"+session.getMaxInactiveInterval());
//重新设置session的最大不活动时间(单位为秒)
session.setMaxInactiveInterval(10);
}
}
此时访问浏览器显示JSESSIONID每隔十秒钟刷新一次。
文件的上传与下载(Servlet应用)
文件的上传
文件上传涉及到前台页面的编写和后台服务器的编写,前台发送文件后,后台接收并保存文件,这是一个完整的文件上传。
首先编写前台页面,在做文件上传的时候,会有一个上传文件的界面,首先我们需要一个表单,并且表单的请求方式为post,其次我们的form表单的enctype必须设置为“multipart/form-data”,「即:enctype=“multipart/form-data”」,意思是设置表单的类型为文件上传表单,默认情况下这个表单类型是application/x-www-form-urlencoded,不能用与文件上传,只有使用了multipart/form-data才能完整地传递文件数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<!--文件上传
1. 准备表单
2. 设置表单的提交类型method="post"
3. 设置表单类型为文件上传表单enctype="multipart/form-data"
4. 设置文件提交的地址UploadServlet
5.准备表单元素,普通的表单项目和文件项目
6.设置表单元素的name属性值(必须要设置的,否则后台无法接收数据)
-->
<form method="post" action="UploadServlet" enctype="multipart/form-data">
姓名:<input type="text" name="username"><br>
文件:<input type="file" name="myfile"><br>
<input type="submit"><br>
</form>
</body>
</html>
后台实现
使用注解@MultipartConfig将一个Servlet标识为支持文件上传。Servlet将multipart/form-data的POST请求封装成Part,通过Part对上传的文件进行操作。
@WebServlet("/UploadServlet")
@MultipartConfig //如果是文件上传必须要加注解,将其封装成Part对象
public class UploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("文件上传");
//设置编码
resp.setContentType("text/html;charset=utf-8");
//获取表单项目的属性值
String username = req.getParameter("username");
System.out.println("username:"+username);
//由于post请求被封装成Part,所以需要获取part对象
Part myfile = req.getPart("myfile");
//此处的参数填写的文件域的name属性名(需要获取属性值)
//通过Part得到上传的文件名(提交的文件名)
String name = myfile.getSubmittedFileName();
System.out.println("上传的文件名"+name);
//得到文件存放的路径("/"表示当前项目,此举获取了当前项目的路径)
String realPath = req.getServletContext().getRealPath("/");
System.out.println("文件存放的路径"+realPath);
//上传文件到指定目录
myfile.write(realPath+"/"+name);
}
}
测试成功,提交成功
文件下载
文件下载,即将服务器上的资源(拷贝)到本地,我们可以通过两种方式下载,一种是通过超链接本身的特性来下载,第二种是通过代码来下载。
超链接下载
当我们在HTML或者JSP页面中使用a标签时,原意是希望能够进行跳转,但当超链接遇到浏览器不识别的资源的时候会自动下载,当遇到浏览器能够直接显示的资源的时候,浏览器会默认显示出来,比如:txt、png、jpg等。
当然我们也可以通过设置download属性来规定浏览器进行下载,但有些浏览器并不支持。
<!--浏览器不能够识别的资源,点击会自动下载-->
<a href="download/java.txt.zip">压缩文件</a>
download属性可以不写任何信息, 会自动默认文件名,如果设置了download属性的值,则使用设置的值作为文件名,当用户打开浏览器点击链接的时候,会直接下载文件。
<a href="download/java.txt" download>文本文件</a>
<a href="download/近况.png" download="表情包">图片文件</a>
<!--此处的download表示对浏览器可以识别的内容进行下载操作,如果download有属性值,下载的文件名即为属性值,如果下载的download没有属性值,那么使用默认的文件名-->
需要注意的是:在从项目中下载文件的时候,需要在webapp当前项目的目录下设置一个文件夹放置需要在网页上被下载的资源文件,并且同时设置该资源文件的路径。
默认下载(后台实现下载)
需要通过response.setContentType方法设置Content-type头字段的值,为浏览器无法使用某种方式或者激活某个过程来处理的MIME程序,例如;“application/octeet-stream”或者“application/x-msdownload”等 需要通过response.setHeader方法设置Content-Disposition头的值为“attachment;filename=文件名” 读取下载文件,调用response.getOutputStream方法向客户端写入附件内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件下载页面</title>
</head>
<body>
<!--a标签,超链接下载
1. 如果遇到浏览器能够识别的资源,浏览器会显示内容
2.如果遇到浏览器不能够识别的资源,则会进行下载
如果遇到不能下载的资源,我们可以设置download属性,
-->
<!--浏览器可以识别的资源-->
<a href="download/java.txt">文本文件</a>
<a href="download/近况.png">图片文件</a>
<!--浏览器不能够识别的资源-->
<a href="download/java.txt.zip">压缩文件</a>
<hr>
<a href="download/java.txt" download>文本文件</a>
<a href="download/近况.png" download="表情包">图片文件</a>
<hr>
<form action="DownloadServlet" method="post">
<!--此处的name表示的是参数名,filename,其值是我们在页面中输入的内容,
后面的servlet程序中的参数名就是filename-->
<input name="filename" type="text" placeholder="请输入您要下载的内容">
<button>下载</button>
</form>
</body>
</html>
@WebServlet("/DownloadServlet")
public class DownLoadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("文件下载......");
//设置一个请求的文件编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
//获取参数,得到要下载的文件名
String filename = req.getParameter("filename");
//后端判断文件名非空和非空格
//trim表示去除字符串的前后空格
if (filename==null || "".equals(filename.trim())){
PrintWriter writer = resp.getWriter();
//由于此处在浏览器上显示乱码,需要在此处设置响应的编码格式(也可以在方法开始处设置格式)
resp.setContentType("text/html;charset=utf-8");
writer.write("请输入正确的下载文件名......");
//关闭输出流
writer.close();
}
//得到想要下载文件的路径(由于将资源目录放在了download目录下,所以需要在/当前目录,即webapp目录下添加目录download)
String realPath = req.getServletContext().getRealPath("/download");
//由于需要通过该路径,获取想要下载的文件的内容。所以需要获取该文件的file对象,以便后续的流的操作
File file = new File(realPath + "/" + filename);
//filename不能够保证一定存在download目录下,因此需要对文件对象进行非空判断
if (file.exists() && file.isFile()){
//此时证明浏览器可以下载文件了
/*1. 需要通过response.setContentType方法设置Content-type头字段的值,
为浏览器无法使用某种方式或者激活某个过程来处理的MIME程序,
例如;“application/octeet-stream”或者“application/x-msdownload”等*/
resp.setContentType("application/x-msdownload");
//设置响应头(使得浏览器能够下载)
resp.setHeader("Content-Disposition","attachment;filename="+filename);
/*由于是下载文件,所以我们此时需要获取输入流,将项目中要下载的文件,加载至输入流中(是file文件的输入流)*/
FileInputStream fileInputStream = new FileInputStream(file);
//此时还需要字节输出流(向外输出给客户端去下载)
ServletOutputStream outputStream = resp.getOutputStream();
//此时需要定义一个字节数组的形式来向外输出
byte[] buff = new byte[1024];
int len=0;
while ((len=fileInputStream.read(buff))!=-1){
//之后向外客户端输出
outputStream.write(buff,0,len);
}
//关闭相应的资源
outputStream.close();
fileInputStream.close();
}else {
PrintWriter writer = resp.getWriter();
writer.write("文件不存在,请重新输入下载的文件名。");
writer.close();
}
}
}
JSP和JSTL
什么是JSP
Java Server Pages:Java服务器端页面,也和Servlet一样,用于开发动态web
它相比HTML而言,HTML只能够为用户提供静态数据,而JSP技术允许在页面中嵌套java代码,为用户提供动态数据。
相比Servlet而言,Servlet很难对数据进行排版,而JSP除了可以用java代码产生动态数据的同时,也很容易对数据进行排版。
不管是JSP还是Servlet,虽然都可以用于开发动态web资源,但是由于这两门技术各自的特点,在长期的软件实践中,人们逐渐把Servlet作为web应用中的控制器组件来使用,而把JSP这门技术作为数据显示模板来使用。
「其实JSP就是一个Servlet,当我们第一次访问JSP的时候,JSP引擎都会将这个JSP翻译成一个Servlet,这个文件存放在tomcat(源码目录)中的work目录中。」
最大特点;
写JSP就像是在写HTML 区别在于:HTML只给用户提供静态数据,JSP页面中可以嵌入Java代码,为用户提供动态数据
JSP原理
思路:JSP是如何执行的?
代码层面没有任何问题
服务器内部工作
tomcat中有一个work目录 IDEA使用Tomcat的会在IDEA的Tomcat中产生一个work目录
「浏览器向服务器发送请求,不管访问什么资源,其实都是在访问Servlet」
JSP最终也会被转换成Java文件
可以看到JSP是继承了HttpJspBase类,翻看HttpJspBase的源码,发现HttpJspBase也是继承了HttpServlet,因此,JSP本质上也是一个Servlet
JSP源码中的三个重要方法:
public void _jspInit() {
//初始化
}
public void _jspDestroy() {
//销毁
}
public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response)
throws java.io.IOException, jakarta.servlet.ServletException {
//JSP的服务
}
「步骤」
判断请求 内置了一些对象
final jakarta.servlet.jsp.PageContext pageContext;//页面上下文
final jakarta.servlet.ServletContext application;//applicationContext
final jakarta.servlet.ServletConfig config;//配置
jakarta.servlet.jsp.JspWriter out = null;//输出对象
final java.lang.Object page = this;//当前页面
jakarta.servlet.jsp.JspWriter _jspx_out = null;
jakarta.servlet.jsp.PageContext _jspx_page_context = null;
HttpServletRequest request //请求
HttpServletResponse response //响应
输出页面前增加的代码
response.setContentType("text/html; charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,null, false, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
out = pageContext.getOut();
_jspx_out = out;
以上对象我们可以直接在jsp页面中直接使用。 Tomcat10中需要导入的JSP依赖
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-2.1-glassfish</artifactId>
<version>2.1.v20091210</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
</dependency>
JSP的注释
在JSP中支持两种注释的语法操作:
一种是显式注释,这种注释允许客户端看见,另一种是隐式注释,此种注释是客户端无法看见的。
显式注释语法:从HTML风格继承而来 隐式注释语法:从Java风格继承,JSP自己的注释。
1)//注释,单行注释 /*多行注释*/
2)<!--HTML风格的注释-->
3)<%--JSP在--%>
JSP执行流程图
只要是Java代码就会原封不动地输出
如果是HTML代码,就会被转换成out.println(xxx)的形式来输出。
JSP的基础语法
任何语言都有自己的语法,Java中有,JSP作为Java技术的一种应用, 它拥有一些自己的扩充语法,Java所有的语法都支持
JSP表达式
用来将程序输出到客户端。
<%= 变量或者变量表达式%>
<%--Jsp表达式--%>
<%= new Date()%>
JSP脚本片段
<%--jsp书写脚本片段--%>
<%
int sum=0;
for (int i=1;i<=100;i++){
sum+=i;
}
out.println("<h1>结果是:"+sum+"</h1>");
System.out.println(sum);
%>
「再实现」
<%--脚本片段的再实现--%>
<%
int x=10;
out.println(x);
%>
<p>这是一个JSP的文档</p>
<%
// int x=10,此时是无法被定义的,因为本质上jsp页面实际上是在一个java程序中的,不重复定义一样的内容,但是可以输出。
int y=20;
out.println(x);
out.println(y);
%>
「脚本片段中嵌套HTML标签」
<%--在Java源码中嵌入HTML元素--%>
<%
for (int i=0;i<10;i++){
//此处将for循环进行分段显示,脚本段之间书写HTML代码
%>
<%--此处在HTML元素中添加脚本变量表达式--%>
<h1>Java是世界上最好的语言!<%= i%></h1>
<%
}
%>
这里通常出现了out对象无法调用方法的情况:
主要有以下原因;
新建工程时没有选择Java Enterprise,而选择了Java里的webapplication(Java Enterprise会自动关联Tomcat到Dependencies) Tomcat没有关联到依赖中
1和2的解决方案:
使用Java Enterprise 将Tomcat的依赖添加到项目中
上述1和2步骤都未解决,则需要在WEB-INF目录下创建lib目录,从Tomcat的lib目录中拷贝jsp-api.jar和servlet-api.jar
此时的out.println不会出现爆红出错,说找不到依赖了。
JSP声明
会被编译到JSP生成大的java的类中
其他的(表达式或者脚本片段)会被生成到service方法中。
<%--JSP声明,里面定义的内容可以提升作用域,在类下,而不是service方法中--%>
<%!
static {
System.out.println("进入了静态代码块");
}
//定义一个全局变量
private int globalVar=0;
//定义一个方法
public void method(){
System.out.println("进入了method方法.....");
}
%>
JSP的注释不会在客户端显示,但是HTML的注释会在客户端显示
JSP指令
使用包含操作,可以将一些重复的代码包含进来继续使用,从正常的页面组成来看,有时可能会分成几个区域,而其中的一些区域可能是一直不需要改变的,改变的就是其中的一个具体内容区域,现在有两种方式可以实现上述功能。
在每个JSP页面(HTML)都包含工具栏,头部信息,尾部信息,具体内容 将工具栏,头部信息,尾部信息都分成各个独立的文件,使用的时候直接导入。
很明显第二种方式比第一种方式好,第一种会存在很多重复的代码,并且修改很不方便。在JSP中如果想要实现包含操作,有两种操作,「静态包含、动态包含」,静态包含使用include指令即可,动态包含则需要使用到include动作标签。
include静态包含
静态包含
<%@ include file="common/header.jsp"%>
<h1>网页主体</h1>
<%@ include file="common/footer.jsp"%>
静态包含其实就相当于将内容进行了直接的替换,就好比程序中定义的变量一样是在Servlet引擎转译时。就把此文件的内容包含了进去(两个文件的内容会整合一起,全部放到jspService方法中去),所以只生成了一个servlet,所以两个页面不能有同名的变量,运行效率高一点点,耦合性较高,不够灵活。
include动态包含
动态包含在代码的编译阶段,包含和被包含部分是两个独立的部分,只有当运行时,才会动态包含进来,「好比方法调用。」
动态包含
<jsp:include page="common/header.jsp"/>
<h1>网页主体</h1>
<jsp:include page="common/footer.jsp"/>
需要注意的是,当动态包含不包含任何参数时,include双标签之间不许出现任何内容(包括空格),因为有了内容它会去寻找对应的name和value,找不到就会报错(500)
使用动态包含传递参数的情况如下:
<jsp:include page="common/header.jsp"/>
<h1>这是演示JSP的动态包含内容</h1>
<%String str="helloWorld";
%>
<!--这里在include标签中设置了参数-->
<jsp:include page="common/footer.jsp">
<jsp:param name="uname" value="admin"/>
<jsp:param name="msg" value="<%= str %>"/>
</jsp:include>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>我是底部</h1>
<%
//可以在此处获取动态包含的参数
String name = request.getParameter("uname");
String msg = request.getParameter("msg");
out.println(name+":"+msg);
%>
此处的包含页面可以传入参数,包括参数名和对应的参数值,参数名无法使用表达式,只能以字符串的形式传入,参数值可以传入字符串,也可以是表达式,可以在相应的动态包含页面中获取相应的参数键值对。
JSP设置页面编码指令
<%--这是设置页面的编码--%>
<%@page pageEncoding="UTF-8" %>
JSP设置错误页面的指令
<%--使用JSP定制错误的页面,一般是使用指令来操作--%>
<%--这里表示将该错误页面转到到500.jsp页面中--%>
<%@ page errorPage="error/500.jsp" %>
JSP显示设置页面属性
<%--显式地声明这是一个错误的页面--%>
<%@page isErrorPage="true" %>
JSP设置包含其他页面(合并为一个页面,incloude属性)-静态包含
<%--使用incloude包含其他页面,表示公共页面,表示会将两个页面合二为一--%>
<%@ include file="common/header.jsp"%>
<h1>网页主体</h1>
<%@ include file="common/footer.jsp"%>
JSP包含多个页面(使用jsp标签)-动态包含
<%--JSP标签,相当于是拼接页面,本质上是三个页面---%>
<jsp:include page="common/header.jsp"/>
<h1>网页主体</h1>
<jsp:include page="common/footer.jsp"/>
代码示例:
jsp2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--使用JSP定制错误的页面,一般是使用指令来操作--%>
<%--这里表示将该错误页面转到到500.jsp页面中--%>
<%--<%@ page errorPage="error/500.jsp" %>--%>
<%--显式地声明这是一个错误的页面--%>
<%@page isErrorPage="true" %>
<%--这是设置页面的编码--%>
<%@page pageEncoding="UTF-8" %>
<html>
<head>
<title>JSP指令使用</title>
</head>
<body>
<% int i=1/0;
%>
</body>
</html>
500.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>内部服务器出错了</title>
</head>
<body>
<h1>自定义500的错误的界面</h1>
<%--这里使用图片标签表示错误
--%>
<img src="img/500错误页面.png" alt="500内部服务器错误">
</body>
</html>
404.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--配置404可以从web.xml中进行配置--%>
<html>
<head>
<title>404NOT FOUND</title>
</head>
<body>
<h1>404页面走丢了</h1>
<img src="img/404错误.png">
</body>
</html>
<!--设置相应的页面跳转-->
<?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_5_0.xsd"
version="5.0">
<error-page>
<error-code>404</error-code>
<location> /error/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
</web-app>
jsp3.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>JSP其他指令</title>
</head>
<body>
<%--使用incloude包含其他页面,表示公共页面,表示会将两个页面合二为一--%>
<%@ include file="common/header.jsp"%>
<h1>网页主体</h1>
<%@include file="common/footer.jsp"%>
<hr>
<%--JSP标签,相当于是拼接页面,本质上是三个页面---%>
<jsp:include page="common/header.jsp"/>
<h1>网页主体</h1>
<jsp:include page="common/footer.jsp"/>
</body>
</html>
header.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>我是头部</h1>
footer.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>我是底部</h1>
目录结构如下:
九大内置对象
PageContext 存东西 Request 存东西 Response Session 存东西 Application(ServletContext)存东西 config(ServletConfig) out page 不用了解 exception 同Java的异常
request:客户端向服务器发送请求产生的数据,用户看完就没有了,如新闻。
session:客户端向服务端发送请求,产生的数据,用户用完一会还有用。如购物车
application:客户端向服务端发送请求,产生的数据,一个用户用完了,可能还有其他用户需要使用,比如统计网站的人数。
用的比较多的就是PageContext、Request、Session、Application这四个。
PageContext:保存的数据在一个页面中有效。 Request:保存的数据在一个请求中有效,请求转发会携带内容 Session:保存的数据在一次会话中有效 Application:保持的内容在服务器中有效
例1:在一个页面中
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>PageContextDemo01</title>
</head>
<body>
<%--内置对象,可以用来存内容--%>
<%
pageContext.setAttribute("name1","小明");//保存的数据在一个页面中有效
application.setAttribute("name2","小华");//在服务器中有效
request.setAttribute("name3","小李");//保存的数据只在一个请求中有效,请求转发会携带
request.getSession().setAttribute("name4","小王");//保存的数据在一次会话中有效(从打开到关闭浏览器)
%>
<%--可以用来取内容--%>
<%
//通过pageContext取出我们保存的值(通过寻找的方式)
//作用域从底层到高层有效(page-request-session-application)
//这是使用了双亲委派机制,从底层向高层找
String name1 =(String) pageContext.findAttribute("name1");
String name2 =(String) pageContext.findAttribute("name2");
String name3 =(String) pageContext.findAttribute("name3");
String name4 =(String) pageContext.findAttribute("name4");
String name5 =(String) pageContext.findAttribute("name5");
%>
<%--使用EL表达式来取出所有的值--%>
<h1>取出的值为:</h1>
<h2>${name1}</h2>
<h2>${name2}</h2>
<h2>${name3}</h2>
<h2>${name4}</h2>
<h2>${name5}</h2>
</body>
</html>
「测试结果」
例2:另一个页面访问
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Demo2</title>
</head>
<body>
<%--可以用来取内容,在另一个页面中去取,即下所有的值都在另一个
页面中定义,在本页面上获取内容。--%>
<%
//通过pageContext取出我们保存的值(通过寻找的方式)
//作用域从底层到高层有效
String name1 =(String) pageContext.findAttribute("name1");
String name2 =(String) pageContext.findAttribute("name2");
String name3 =(String) pageContext.findAttribute("name3");
String name4 =(String) pageContext.findAttribute("name4");
String name5 =(String) pageContext.findAttribute("name5");
%>
<h1>取出的值为:</h1>
<h2><%= name1%></h2>
<h2><%= name2%></h2>
<h2><%= name3%></h2>
<h2><%= name4%></h2>
<h2><%= name5%></h2>
</body>
</html>
「测试结果」
这次未使用EL表达式,并且在另一个页面中访问,但是只有Application和Session的值没有丢失,这是因为:Application的作用域是整个服务器,Session的作用域是一个会话(从浏览器的开启到关闭这段时间)。
其他的诸如:Request或者PageConetxt都已经失效了。
不存在的值为null是因为使用了JSP的标签语法,将内容直接输出到页面中。
例3:设置作用域
public static final int PAGE_SCOPE = 1;
public static final int REQUEST_SCOPE = 2;
public static final int SESSION_SCOPE = 3;
public static final int APPLICATION_SCOPE = 4;
public void setAttribute(String name, Object attribute, int scope) {
switch(scope) {
case 1:
this.mPage.put(name, attribute);
break;
case 2:
this.mRequest.put(name, attribute);
break;
case 3:
this.mSession.put(name, attribute);
break;
case 4:
this.mApp.put(name, attribute);
break;
default:
throw new IllegalArgumentException("Bad scope " + scope);
}
}
<%
//设置的语句中第三个参数可以设置相应的作用域(即在Session中有效)
pageContext.setAttribute("name","Jack",PageContext.SESSION_SCOPE);
//等价于 request.getSession().setAttribute();
%>
设置setAttribute方法的第三个参数可以实现其他对象的作用域,效果是等价的。
例4:实现转发
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>PageContextDemo04</title>
</head>
<body>
<%
//使用转发来实现
pageContext.forward("/index.jsp");
%>
</body>
</html>
JSP标签、JSTL标签、EL表达式
JSP标签
<%--<jsp:include page=""/>--%>.
<%--forward表示转发到相应的页面并同时携带参数--%>
<jsp:forward page="jsptag2.jsp">
<jsp:param name="name" value="黎明"/>
<jsp:param name="age" value="18"/>
</jsp:forward>
JSTL标签
JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能。
JSTL支持通用的、结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签,SQL标签。除了这些,它还提供了一个框架来使用集成JSTL的自定义标签。
根据JSTL标签所提供的功能,可以将其分为5个类别。
核心标签
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
格式化标签
<%@ taglib prefix="fmt"
uri="http://java.sun.com/jsp/jstl/fmt" %>
SQL标签
<%@ taglib prefix="sql"
uri="http://java.sun.com/jsp/jstl/sql" %>
XML标签
<%@ taglib prefix="x"
uri="http://java.sun.com/jsp/jstl/xml" %>
JSTL函数
<%@ taglib prefix="fn"
uri="http://java.sun.com/jsp/jstl/functions" %>
JSTL标签的使用就是为了弥补HTML标签的不足,它自定义了许多标签,可以供我们使用,标签的功能和Java代码一样。
核心标签
常用核心标签如下:
JSTL标签的使用步骤
引入相应的taglib 使用其中的方法 在Tomcat中也需要导入相应的jstl的包,否则会报jstl解析错误。 Tomcat10由于兼容性会出现500的错误情况。
「使用if条件语句」
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--导入相应的标签库,我们才能使用JSTL标签--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>if测试</title>
</head>
<body>
<form action="coreif.jsp" method="get">
<%--使用EL表达式获取表单中的数据
${param.参数名}
--%>
<input name="username" type="text" value="${param.username}"/>
<br>
<input type="submit" value="登录">
</form>
<!--判断如果是管理员那么就登录成功
test是必须要写的,表示判断的条件
var表示判断的结果,如果是真就会输出下面的内容
var表示一个变量,用来接受test判断的结果。
-->
<c:if test="${param.username=='admin'}" var="isadmin">
<c:out value="管理员欢迎您!"/>
</c:if>
<c:out value="isadmin"/>
</body>
</html>
「使用choose判断」
<%--定义一个变量,值为85--%>
<c:set var="score" value="85"/>
<%--使用判断语句--%>
<c:choose >
<c:when test="${score>=90}">
您的成绩为优秀
</c:when>
<c:when test="${score<90}">
您的成绩不是优秀
</c:when>
</c:choose>
「使用foreach遍历」
<%--测试foreach循环遍历--%>
<%
ArrayList<String> strings = new ArrayList<>();
strings.add(0,"张三");
strings.add(1,"李四");
strings.add(2,"王五");
//将集合放入请求中进行相应的传输。
request.setAttribute("list",strings);
%>
<%--
这里的var表示的是每次遍历出来的变量(类似于java中的增强for循环),
for (String string : strings) {
}
就像是strings中的string
items表示的是要遍历的对象
--%>
<c:forEach var="string" items="${list}">
<c:out value="${string}"/><br>
</c:forEach>
<%--
此处的内容类似于java中的for循环遍历
for (int i = 0; i < strings.size(); i++) {
}
这种形式的循环遍历。
下面表示从1序号开始,到3序号结束,每次遍历显示一个。
--%>
<c:forEach var="string" items="${list}" begin="1" end="3" step="1" >
<c:out value="${string}"/><br>
</c:forEach>
EL表达式 ${}
<!--需要导入相应的包-->
<!--导入JSTL表达式的依赖-->
<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--导入standard的标签库-->
<!-- https://mvnrepository.com/artifact/taglibs/standard -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
主要作用:
获取数据 执行运算 获取web开发的常用对象 调用java方法(不常用)
使用JSP操作对象
首先创建一个类People
public class People {
private int id;
private String name;
private int age;
private String address;
public People(int id, String name, int age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
public People() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "People{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
编写相应的jsp页面,来实现对象的创建与属性值的设置获取
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>使用JSP操作对象</title>
</head>
<body>
<%--这里使用useBean来创建对象
id为类名,class表示对应类的完全限定名。scope表示该类作用的范围。
使用setProperty来为这个对象设置相应的属性值
name表示的是具体的类名,property表示属性名,value表示属性值
--%>
<jsp:useBean id="People" class="com.jd.pojo.People" scope="page">
<jsp:setProperty name="People" property="name" value="小明"/>
<jsp:setProperty name="People" property="id" value="1"/>
<jsp:setProperty name="People" property="age" value="18"/>
<jsp:setProperty name="People" property="address" value="北京"/>
</jsp:useBean>
<%--jsp:getProperty表示取出该对象的属性值
--%>
姓名:<jsp:getProperty name="People" property="name"/>
id :<jsp:getProperty name="People" property="id"/>
年龄:<jsp:getProperty name="People" property="age"/>
地址:<jsp:getProperty name="People" property="address"/>
</body>
</html>
测试如下:
JaveBean
实体类
JavaBean有特定的写法:
必须要有一个无参数构造 属性必须要私有化 必须要有对应的get/set方法
一般用来和数据库的字段做映射。
==ORM(对象关系映射)==
表——>类 字段——>属性 行记录——>类的对象
来看个例子:
People表
id | name | age | address |
---|---|---|---|
1 | 小明 | 18 | 北京 |
2 | 小红 | 17 | 上海 |
3 | 小华 | 20 | 广州 |
将表格映射成Java类
class People{
private int id;
private String name;
private int age;
private String address;
public People(){
}
/**建立相应的get/set方法*/
}
// 对应的行记录
class Test{
People p1=new People(1,"小明","18","北京");
}
三层架构(MVC)
什么是MVC?
Model、View、Controller:模型、视图、控制器
早些年的架构
用户直接访问控制层,控制层可以直接操作数据库。
Servlet——CRUD——数据库
弊端:程序异常臃肿,不利于维护。此时Servlet中,处理请求,处理响应、视图跳转、处理JDBC、处理业务代码、处理逻辑代码。
MVC三层架构
Model模型层
业务处理:产生业务逻辑(Service) 数据持久层:CRUD(Dao)
View视图层
展示数据 提供链接发起Servlet请求(a、form、img…)
Controller(以Servlet为例)
接收用户的请求(req:请求参数,Session) 交给业务层对应的代码 控制视图跳转
例:以用户登录为例
登录——>接收用户的登录请求——>处理用户的请求(获取用户登录的参数,username password)——>交给业务层去处理登录业务(判断用户名和密码是否正确)——>Dao层查询用户名和密码是否正确——>数据库查询
Filter过滤器
Filter:过滤器,用来过滤网站的数据。
Filter开发步骤
导入相应的依赖(例如:Servlet、JSP、JSTL、MySQL等) 编写过滤器
在对应的包下创建相应的实现类(以字符过滤器实现类为例),需要实现Filter接口,如果是Tomcat10以下版本需要导入 Javax.Servlet.*
,如果是Tomcat10,则需要导入jakarta.servlet.*
编写相应过滤器的代码
public class CharacterEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//初始化:Web服务器启动的时候就进行了初始化
System.out.println("CharacterEncodingFilter已经初始化了......");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//此处的过滤器处理乱码问题
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
//同时设置响应的格式
servletResponse.setContentType("text/html;charset=utf-8");
System.out.println(" CharacterEncodingFilter执行前......");
//filterChain是过滤链的含义,调用方法处理
//此处让我们的程序继续走,如果不写,程序到此处就立即停止了。
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("CharacterEncodingFilter执行之后........");
}
/**总结:
* 1.过滤器中的所有代码,过滤特定的请求之后都会执行
* 2.必须要让所有的过滤器继续执行(doFilter方法调用)
* */
@Override
public void destroy() {
//销毁:web服务器关闭的时候会销毁过滤器
System.out.println("CharacterEncodingFilter已经被销毁了........");
}
}
编写相应的Servlet代码
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("你好,世界!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
编写相应的xml文件(配置filter过滤器和servlet映射)
<servlet>
<servlet-name>showServlet</servlet-name>
<servlet-class>com.alibaba.Servlet.ServletDemo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>showServlet</servlet-name>
<url-pattern>/servlet/show</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Servlet</servlet-name>
<servlet-class>com.alibaba.Servlet.ServletDemo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/show</url-pattern>
</servlet-mapping>
<!--以下代码注册过滤器-->
<filter>
<filter-name>charset</filter-name>
<filter-class>com.alibaba.Web_Filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>charset</filter-name>
<!--此处表示只要是/servlet的请求都需要走这个过滤器-->
<url-pattern>/servlet/*</url-pattern>
<!--/*表示整个网站的所有内容都需要走这个过滤器
<url-pattern>/*</url-pattern>-->
</filter-mapping>
监听器
实现一个监听器的接口(有很多种)
首先编写一个类实现监听器接口
public class OnLineCountListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
//创建session监听,一旦创建session,就会触发这个事件
//获取session的id
System.out.println(se.getSession().getId());
//通过se参数我们可以获取到网站的上下文对象
ServletContext context = se.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("OnLineCount");
if (onLineCount==null){
onLineCount=new Integer(1);
}else {
int count=onLineCount.intValue();
onLineCount=new Integer(count+1);
}
//更新计数
context.setAttribute("OnLineCount",onLineCount);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
//销毁session 一旦销毁session,就会触发这个事件
//通过se参数我们可以获取到网站的上下文对象
ServletContext context = se.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("OnLineCount");
if (onLineCount==null){
onLineCount=new Integer(0);
}else {
int count=onLineCount.intValue();
onLineCount=new Integer(count-1);
}
//更新计数
context.setAttribute("OnLineCount",onLineCount);
}
}
session的销毁主要两种方式,一种手动调用方法销毁,一种是在web.xml文件中设置session的过期时间(以分钟为单位)
在JSP页面中获取内容(以application为例)
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1>当前在线人数<%= application.getAttribute("OnLineCount")%>
</h1>
<br/>
</body>
</html>
在web.xml文件中注册监听事件
<!--注册监听器-->
<listener>
<listener-class>com.alibaba.Listener.OnLineCountListener</listener-class>
</listener>
测试结果
过滤器的常见应用
需求:用户登录界面,如果登录成功,则进行页面的跳转,如果登录失败,则跳转到失败的页面,如果是直接以路径的形式访问主页,则会自动跳转到登录失败的页面。
首先编写登录前端的代码(需要编写相应的form表单),请求格式是POST,跳转到路径为 /s
的Servlet中去。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录界面</title>
</head>
<body>
<form method="post" action="/s">
帐号:<input type="number" name="account"><br>
密码:<input type="password" name="password"><br>
<input type="submit" name="登录">
</form>
</body>
</html>
编写用户登录成功的界面和用户登录失败的界面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<%--<%
//由于需要防止用户直接登录页面,需要做拦截
Object session1 = request.getSession().getAttribute("SESSION");
if (session1==null){
response.sendRedirect("/Login.jsp ");
}
%>--%>
<head>
<title>欢迎登录!</title>
</head>
<body>
<h1>这是已经登录的主页!</h1>
<h2>
<a href="/logout">注销</a>
</h2>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录出现了错误!</title>
</head>
<body>
<h1>登录出现了错误!!!!!</h1>
<img src="img/404错误.png">
</body>
<a href="../Login.jsp">返回到登录页面</a>
</html>
在java文件夹中编写处理用户登录的业务逻辑(此时首先需要获取用户的各种参数值,进行校验,如果满足条件,那么为当前用户创建一个session,属性名为SESSION,属性值为对应的sessionID,与此同时跳转到相应的页面中去)
public class LogInServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取前端请求的参数
String account = req.getParameter("account");
String password = req.getParameter("password");
if ("123".equals(account) && "123456".equals(password)){
req.getSession().setAttribute("SESSION",req.getSession().getId());
resp.sendRedirect("/sys/HomePage.jsp");
}else {
resp.sendRedirect("/error/Error.jsp");
}
}
}
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.alibaba.Servlet.LogInServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/s</url-pattern>
</servlet-mapping>
在登录主页面上编写注销的请求(超链接)
编写注销的servlet程序(如果有用户请求注销,那么只需要将用户所对应的session移除即可,不必关闭session,如果当时有用户登录,注销后页面跳转到登录页面,如果没有用户登录,那么注销后也依旧跳转到登录页面)
public class LogOutServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//注销方法
//1.首先获取到session的值
Object session = req.getSession().getAttribute("SESSION");
if (session!=null){
//如果存在用户登录,只需要将session的属性值移除即可
req.getSession().removeAttribute("SESSION");
//之后再重定向至登录页面
resp.sendRedirect("/Login.jsp");
}else {
resp.sendRedirect("/Login.jsp");
}
}
}
<servlet>
<servlet-name>logout</servlet-name>
<servlet-class>com.alibaba.Servlet.LogOutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>logout</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
为了防止用户能够从对应的地址直接访问到主页,需要加一层过滤器,如果是从指定路径下访问资源的,则会被直接过滤请求,并且直接重定向至错误页面。(由于过滤器中的对象与之前的登录注销的请求响应对象不同,需要强制转换,之后再获取session,如果没有用户登录,对应的session即为空,那么该程序会直接重定向至错误页面,实现过滤。此外还需要将过滤链传递下去)
public class SysFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//由于与登录注销的请求与转发的类型不同,需要强制转
HttpServletRequest request= (HttpServletRequest)servletRequest;
HttpServletResponse response=(HttpServletResponse)servletResponse;
Object session = request.getSession().getAttribute("SESSION");
if (session==null){
response.sendRedirect("/error/Error.jsp");
}
//无论写不写内容,需要让链子往下走
filterChain.doFilter(servletRequest,servletResponse);
//注册过滤器
}
@Override
public void destroy() {
}
}
<!--注册过滤器-->
<filter>
<filter-name>sys</filter-name>
<filter-class>com.alibaba.Web_Filter.SysFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sys</filter-name>
<!--此处表示过滤sys下的所有内容-->
<url-pattern>/sys/*</url-pattern>
</filter-mapping>
往期内容回顾