JavaWeb(一:基础知识和环境搭建)
https://blog.csdn.net/xpy2428507302/article/details/140365130?spm=1001.2014.3001.5501
目录
九、监听器
五、Servlet
1.定义
Servlet 是Sun公司开发 动态web 的一门技术,负责与客户端进行通信。
Sun 在这些API中提供了一个接口叫做:Servlet
开发一个Servlet程序的步骤:
① 编写一个类实现Servlet接口
② 把开发好的Java类部署到web服务器中
Servlet:实现了Servlet接口的Java程序
2.创建一个HelloServlet
① 构建一个普通的Maven项目,删掉里面的src目录
这个空的工程就是Maven主工程,以后就在这个项目里面建立Module。
关于Maven父子工程的理解:
父项目中会有:
<modules>
<module>servlet-01</module>
</modules>
子项目中会有:
<parent>
<groupId>com.mihoyo</groupId>
<artifactId>javaweb</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
父项目中的 jar 包,子项目可以直接使用(类似于java中的继承)
② maven环境优化
Ⅰ. 修改web.xml为最新的。
Ⅱ. 将Maven的结构搭建完整。(建立 java 文件夹)
③ 编写一个Servlet程序
Ⅰ.编写一个普通类
Ⅱ.实现 Servlet 接口(Servlet接口Sun公司有两个默认的实现类:HttpServlet,GenericServlet)
import java.io.*;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Hello World!</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello World!</h1>");
out.println("</body>");
out.println("</html>");
}
}
注意:Tomcat10对servet依赖包级目录改变,maven导入依赖时应该导入:
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
所以,Servlet 程序中在导包时,也应导入 jakarta.servlet,而不是 javax.servlet。
④ 编写Servlet的映射
原因:
我们写的是 Java程序,浏览器不能直接访问 Servlet,只能通过映射的方式来间接访问 Servlet。
所有我们需要在web服务(web.xml)中注册我们写的Servlet,还需要给他一个浏览器能够访问的路径。
<!-- 注册Servlet -->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.mihoyo.serlvet.HelloServlet</servlet-class>
</servlet>
<!-- Servlet的请求路径 -->
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
⑤ 配置Tomcat
⑥ 启动测试
3.Servlet原理
Servlet是由Web服务器调用,Web服务器在收到浏览器请求
4.servlet生命周期
① 当浏览器访问 Servlet 的时候,Tomcat 容器会查询当前 Servlet 的对象是否存在:
如果不存在,则通过反射机制动态创建对象
如果存在,直接执行第 3 步
② 调用 init 方法完成初始化操作
③ 调用 service 方法完成业务逻辑操作
④ 当关闭 Tomcat 时,才会调用 destory 方法,释放当前对象所占用的资源。
Question:为什么在 web.xml 中配置servlet-class 的时候要给出全类名?
Tomcat 容器会根据全类名利用反射机制,创建该 servlet 的对象。
Servlet 的生命周期方法:
① 无参构造函数:只调用一次,创建对象
② init 方法:只调用一次,初始化对象
③ service 方法:调用多次,执行业务逻辑。
④ destory 方法:只调用一次,卸载对象。
5.Servlet体系结构
Servlet -- 接口
|| 继承
GenericServlet -- 抽象类
|| 继承
HttpServlet -- 抽象类
GenericServlet:
将Servlet接口中屏蔽了不常用的方法(做了默认空实现),只将service 方法作为抽象
使用定义Servlet类时,可以继承GenericServlet,子类只要重写 service 方法即可
HttpServlet:
根据请求类型进行分发处理,将 service 方法划分为 Get 和 Post 请求方法
使用时定义类继承HttpServlet,复写 doGet/doPost 方法
6.Mapping问题
一般情况下,一个Servlet 指定一个映射路径 。
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
一个Servlet可以指定多个映射路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello3</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello4</url-pattern>
</servlet-mapping>
一个Servlet可以指定通用映射路径
* :通配符,表示任意字符串
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>
默认的请求路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
指定一个后缀或者前缀等等……
<!-- 可以自定义后缀来实现请求映射
注意点:*前面不能加项目映射的路径(/hello/*.aaa ×)
-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>*.aaa</url-pattern>
</servlet-mapping>
优先级问题:指定了固有的映射路径优先级最高,如果找不到就会走默认的处理请求。
<!-- 404 -->
<servlet>
<servlet-name>error</servlet-name>
<servlet-class>com.mihoyo.servlet.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>error</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
7.ServletContext
Web容器在启动的时候,它会为每一个Web程序都创建一个对应的 ServletContext 对象。
它作用于整个Web应用,是一个全局对象。
(1)共享数据
在这个Servlet中保存的数据,可以在另外一个Servlet中拿到。
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取ServletContext对象
ServletContext context = this.getServletContext();
String username = "江江"; // 数据
// 将一个数据保存在了ServletContext中
// 名字:String字符串,值:任意对象(Object)
context.setAttribute("username", username);
}
}
public class GetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取ServletContext对象
ServletContext context = this.getServletContext();
//从ServletContext取出数据
String username = (String) context.getAttribute("username");
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
resp.getWriter().println("请叫我" + username);
}
}
<servlet>
<servlet-name>GetServlet</servlet-name>
<servlet-class>com.mihoyo.serlvet.GetServlet</servlet-class>
</servlet>
<!-- Servlet的请求路径 -->
<servlet-mapping>
<servlet-name>GetServlet</servlet-name>
<url-pattern>/get</url-pattern>
</servlet-mapping>
测试访问结果:
直接访问 /get,结果显示为 null;
先访问/hello,再访问 /get ,就能拿到结果为 “江江”。
(2)获取初始化参数
<!-- 配置一些web应用的初始化参数 -->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/mybatis</param-value>
</context-param>
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//获取初始化参数
String url = context.getInitParameter("url");
resp.getWriter().print(url);
}
(3)请求转发
public class ServletDemo extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
// 转发的请求路径
RequestDispatcher requestDispatcher = context.getRequestDispatcher("/hello");
// 调用forward实现请求转发
requestDispatcher.forward(req, resp);
//context.getRequestDispatcher("/SD").forward(req, resp);
}
}
<servlet>
<servlet-name>ServletDemo</servlet-name>
<servlet-class>com.mihoyo.serlvet.GetServlet</servlet-class>
</servlet>
<!-- Servlet的请求路径 -->
<servlet-mapping>
<servlet-name>ServletDemo</servlet-name>
<url-pattern>/SD</url-pattern>
</servlet-mapping>
转发和重定向的区别:
转发之后,路径不会变,仍是:/SD,只是转发到 /hello 这个路径下的 servelt 中。
重定向后,路径也会随之发生改变,变成 /hello。
(4)获取配置文件
分别在 Java目录下 和 resources目录下新建 .properties 配置文件。
发现:都被打包到了同一个路径下:WEB-INF/classes 下,我们俗称这个路径—classpath;
注意:如果资源无法导出,是因为:该 module 下的 pom.xml 中没有在build中配置 resources。
public class PropertiesServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取ServletContext对象
ServletContext context = this.getServletContext();
// 获取输入流
// 第一个 / 不能省略,代表当前 web 项目
InputStream is = context.getResourceAsStream("/WEB-INF/classes/db.properties");
Properties prop = new Properties();
//将配置文件中的数据,读取到集合中来
prop.load(is);
String user = prop.getProperty("username");
String pwd = prop.getProperty("password");
resp.getWriter().println(user + pwd);
}
}
8.HttpServletResponse
Web服务器接收到浏览器的http请求,针对这个请求,分别创建:
一个代表请求的 HttpServletRequest 对象;
一个代表响应的 HttpServletResponse 对象。
如果要获取浏览器请求过来的参数:找 HttpServletResuest
如果要给浏览器响应一些信息:找 HttpServletResponse
(1)简单分类
负责向浏览器发送数据的方法
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 setBufferSize(int 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 addIntHeader(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;
(2)常见应用
Ⅰ. 向浏览器输出消息 --> response.getWriter()
Ⅱ. 下载文件 --> response.getOutputStream()
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 要获取下载文件的路径
String realPath = this.getServletContext().getRealPath("/WEB-INF/classes/dog.jpg");
System.out.println("下载文件的路径:" + realPath);
// 2. 下载的文件名是啥?
String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
System.out.println(fileName);
// 3. 想办法设置让浏览器能够支持下载我们的东西,中文文件名URLEncoder.encode编码,否则有可能乱码
resp.setHeader("Content-Disposition","attachment;"+
" filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 4. 获取下载文件的输入流
FileInputStream in = new FileInputStream(realPath);
// 5. 创建缓冲区
int len = 0;
byte[] buffer = new byte[1024];
// 6. 获取OutoutStream对象
ServletOutputStream out = resp.getOutputStream();
// 7. 将FileOutputStream流写入到缓冲区, 使用OutputStream将缓冲区中的数据输出到客户端
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
out.close();
}
}
Ⅲ. 验证码功能
public class ImageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 如何让浏览器3秒刷新一次
resp.setHeader("refresh", "3");
// 在内存中创建一个图片
BufferedImage image = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
// 得到图片
Graphics2D g = (Graphics2D) image.getGraphics(); // 笔
// 设置图片的背景颜色
g.setColor(Color.white);
g.fillRect(0, 0, 80, 20);
// 给图片写数据
g.setColor(Color.blue);
g.setFont(new Font(null, Font.BOLD, 20));
g.drawString(getCode(), 0, 20);
// 告诉浏览器,这个请求用图片方式打开
resp.setContentType("image/png");
// 网站存在缓存,不让浏览器缓存
resp.setDateHeader("expires", -1);
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("Pragma", "no-cache");
// 图片IO流
ImageIO.write(image,"png", resp.getOutputStream());
}
// 生成验证码
private String getCode() {
ArrayList<Character> list=new ArrayList<>();
for (int i = 0; i < 26; i++) {
list.add((char)('a'+i));
list.add((char)('A'+i));
}
StringBuilder sb=new StringBuilder();
//随机抽取四次字母
Random r=new Random();
for (int i = 0; i < 4; i++) {
int idx=r.nextInt(list.size());
sb.append(list.get(idx));
}
int number=r.nextInt(10);
//StringBuilder的append方法可以接收任意数据类型
sb.append(number);
//如果需要修改字符串的内容-->先把字符串变成字符数组,再修改
char[] chs=sb.toString().toCharArray();
int randomIdx=r.nextInt(5);
char temp=chs[chs.length-1];
chs[chs.length-1]=chs[randomIdx];
chs[randomIdx]=temp;
String str=new String(chs);
System.out.println(str);
return sb.toString();
}
}
Ⅳ. 实现重定向
B收到A请求后,B会通知A去访问C,这个过程叫做重定向。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//路径要包含当前项目路径
resp.sendRedirect("/s1/hello");
//重定向包含以下两步
// resp.setHeader("Location", "/s1/hello");//设置重定向的路径
// resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);//设置状态码:302
}
(3)重定向和转发的区别:
相同点:页面都会实现跳转
不同点:
请求跳转的时候,url不会产生变化(状态码:307);
重定向的时候,url 地址栏会发生变化(状态码:302)。
转发:将同一个请求传给下一个页面。
重定向:创建一个新的请求传给下一个页面,之前的请求结束生命周期。
注:如果两个页面之间需要通过 request 来传值,则必须使用转发,不能使用重定向。
9.HttpServletRequest
HttpServletRequest 代表客户端的请求,用户通过 http 协议访问服务器,Http 请求中的所有信息会被封装到 HttpServletRequest 中。
获取前端传递的参数,请求转发
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
爱好:
<input type="checkbox" name="hobbys" value="代码"> 代码
<input type="checkbox" name="hobbys" value="唱歌"> 唱歌
<input type="checkbox" name="hobbys" value="女孩"> 女孩
<input type="checkbox" name="hobbys" value="电影"> 电影
<br>
<input type="submit" name="提交">
</form>
</body>
</html>`
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求中文乱码(后期可以使用过滤器来解决)
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobbys = req.getParameterValues("hobbys");
System.out.println(username);
System.out.println(password);
System.out.println(Arrays.toString(hobbys));
// 这里的 / 代表当前的web应用,所以不需要再加上/s1 这个上下文路径了,否则会出现404错误
req.getRequestDispatcher("/success.jsp").forward(req,resp);
}
}
六、Cookie 和 Session
1.会话
服务器无法识别每一次 Http 请求的出处(不知道来源于哪个终端),它只会接收到一个请求信号。
所以就存在一个问题:服务器可能将用户A的响应发送给其他人(用户B)。
必须有一种技术来让服务器知道请求来自哪,这就是会话技术。
会话:客户端和服务器之间发生的一系列连续的请求和响应的过程
(整个过程:打开浏览器 --> 进行操作 --> 关闭浏览器)。
会话状态:指服务器和浏览器在会话过程中产生的状态信息,借助于会话状态,服务器能够把属于同一次会话的一系列请求和响应关联起来。
2.实现会话的两种方式
cookie:客户端技术,(响应、请求)
session:服务端技术,利用这个技术,可以保存用户的会话信息,也可以把信息或者数据放在session中。
3.Cookie
① 从请求中拿到cookie
② 服务器响应给客户端cookie
Cookie[] cookies = req.getCookies();// 获得cookie
cookie.getName();// 获得cookie中的key
cookie.getValue();// 获得cookie中的value
new Cookie("username","zhangsan");// 新建一个cookie
cookie.setMaxAge(24*60*60);// 设置cookie的有效期,单位:秒
resp.addCookie(cookie);// 响应给客户端一个cookie
cookie 的保存位置:
一般情况下,会保存在本地的用户目录下的 AppData 文件夹中
除此之外,也会存放在浏览器的缓存目录中。
不论哪种方式,最终都是保存在本地硬盘当中。
Question:一个网站的 cookie 是否存在上限?
一个 cookie 只能保存一个信息;
但一个web站点(网站)可以给浏览器发送多个cookie,最多存放20个cookie;
单个 cookie 存储的数据大小有限制:4kb;
浏览器上限:300个cookie。
删除 cookie的两种方式:
① 不设置有效期,默认关闭浏览器,自动失效
② 设置有效期时间为0
解决 cookie 的中文乱码问题:
//编码
Cookie cookie = new Cookie("name",URLEncoder.encode("张三","UTF-8"))
//解码
URLDecoder.decode(cookie.getValue(),"UTF-8")
Test:保存用户上一次访问时间
public class CookieServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 解决中文乱码
resp.setContentType("text/html");
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
PrintWriter out = resp.getWriter();
// Cookie,服务器端从客户端获取cookie
Cookie[] cookies = req.getCookies();// 数组,说明cookie可以有多个
// 判断cookie是否
if (cookies != null) {
out.write("你上一次登录的时间是:");
for (int i = 0; i < cookies.length; i++) {
// 获取cookie的名字
if (cookies[i].getName().equals("lastLoginTime")) {
// 获取cookie的值
long l = Long.parseLong(cookies[i].getValue());
Date date = new Date(l);
out.write(date.toLocaleString());
}
}
} else {
//进不来-->浏览器会往cookie中添加一些其他的属性,第一次也不为空
out.write("你是第一次登录!");
}
Cookie cookie = new Cookie("lastLoginTime", System.currentTimeMillis() + "");
cookie.setMaxAge(24 * 60 * 60);// 设置cookie的有效期为一天,单位是:秒
resp.addCookie(cookie);
}
}
注意:即使没有手动往 cookie 中存储数据,cookie也不为空,会有一个自带的 JSESSIONID 属性(识别请求的来源)。
4.Session
定义:打开浏览器时,服务器就会给每一个用户(浏览器)创建一个Session对象。
一个session独占一个浏览器,只要浏览器没有关闭,这个session就存在。
// 得到session
HttpSession session = req.getSession();
// 给session中存东西
session.setAttribute("name", "张三");
// 获取session的id
String sessionId = session.getId();
// 从session中获取数据
session.getAttribute("name");
session和cookie的区别:
cookie:把用户的数据写给用户的浏览器
① 保存在浏览器
② 保存的数据是 String
③ 可以长期保存在浏览器中,与会话无关
④ 可以保存多个,保存不重要信息
Session:把用户的数据写到用户独占的 Session 对象中
① 保存在服务器
② 保存的数据是 Object
③ 随着会话结束而销毁
④ 保存相对少一点,只保存重要信息,以减少服务器资源的浪费
public class SessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 解决中文乱码
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
// 得到session
HttpSession session = req.getSession();
// 给session中存东西
session.setAttribute("name", "张三");
// 获取session的id
String sessionId = session.getId();
// 判断session是不是新创建
if (session.isNew()) {
resp.getWriter().write("session创建成功,ID:" + sessionId);
} else {
resp.getWriter().write("session已经存在了,ID:" + sessionId);
}
// session创建的时候做了什么事情
/*Cookie cookie = new Cookie("JSESSIONID", sessionId);
resp.addCookie(cookie);*/
//------------------
// 从session中获取数据
String name = (String) session.getAttribute("name");
//------------------
// 从session中删除指定name的数据
session.removeAttribute("name");
// 手动注销session
session.invalidate();
}
}
会话自动过期:
<!--设置session默认的失效时间-->
<session-config>
<!--15分钟后session自动失效,以分钟为单位-->
<session-timeout>15</session-timeout>
</session-config>
七、JSP
1.定义
Java Server Pages:Java服务端页面,和servlet一样,用于动态web技术
特点:写jsp就像在写html
区别:html只给用户提供静态的数据
jsp页面中可以嵌入Java代码,为用户提供动态数据
2.原理
代码层面:
服务器内部原理:
IDEA中使用 tomcat ,会在IDEA的 tomcat 的工作空间中产生一个work目录
在work 目录中,发现 jsp 页面变成了 java 程序,最终也会被转换成一个 Java 类。
注意:浏览器向服务器发送请求,不管访问什么资源,其实都是在访问Servlet!
打开 index_jsp.java,我们发现它继承了一个 HttpJspBase 类
而这个 HttpJspBase 类 ,正是继承了 HttpServlet 类,所以: jsp 本质上就是一个 Servlet!
// 初始化
public void _jspInit() {
}
// 销毁
public void _jspDestroy() {
}
// 请求服务
public void _jspService(HttpServletRequest request,HttpServletResponse response)
在 _jspService方法中:
① 请求判断
② 内置一些对象(9个)
final javax.servlet.jsp.PageContext pageContext; // 页面上下文
javax.servlet.http.HttpSession session = null; // session
final javax.servlet.ServletContext application; // applicationContext
final javax.servlet.ServletConfig config; // config:配置
javax.servlet.jsp.JspWriter out = null; // out:输出流
final java.lang.Object page = this; // page:当前
HttpServletRequest request; // 请求
HttpServletResponse response; // 响应
③ 输出页面前增加的代码
response.setContentType("text/html;charset=UTF-8");// 设置响应的页面类型
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
④ 以上的内置对象可以在jsp页面中直接使用
原理图:
在JSP页面中:
只要是JAVA代码就会原封不动的输出
如果是HTML代码,就会被转换为如下格式,输出到前端
out.write("<html>\r\n");
out.write("<head>\r\n");
3.基础语法
JSP作为java技术的一种应用,Java所有语法它都支持。但它同时拥有一些自己扩充的语法)。
(1)jsp表达式
格式:<%= 变量或表达式%>
<%--
作用:用来将程序的输出,输出到客户端
--%>
<%= new java.util.Date() %>
(2)jsp脚本片段
格式:<% %>
<%
int sum = 0;
for (int i = 0; i < 100; i++) {
sum+=i;
}
out.print("<h1>sum="+sum+"</h1>");
%>
在 jsp 脚本片段中,嵌入 html 代码
<%--EL表达式:${变量} --%>
<% for (int i = 0; i < 3; i++) { %>
<h1>Hello World! ${i}</h1>
<% } %>
(3)jsp声明
格式:<%! %>
<%!
static {
System.out.println("loading Servlet!");
}
private int globalVar =0;
public void method(){
System.out.println("进入了该方法!");
}
%>
区别:
JSP声明:会被编译到 jsp 生成 java 的类中!
其他的,就会被生成到 _jspService 方法中!
(4)jsp注释
格式:<%--注释--%>
注意:jsp 的注释不会在客户端(浏览器)显示,而 HTML 的注释会。
4.jsp指令
(1)定制错误页面
<%--当该页面出现错误(异常)之后,会跳转到500.jsp中--%>
<%@ page errorPage="error/500.jsp" %>
可以在web.xml定制全局的错误页面,产生对应的错误代码后,就会跳转到对应的 jsp 页面。
(2)包含页面头部和尾部
区别:
@include 会将包含的页面中的代码直接拷贝过来,合二为一
jsp:include 会将包含的页面进行引用,本质上还是两个互不影响的页面。
5.九大内置对象
① pageContext:页面上下文,获取页面信息,存东西。
② Request:表示⼀次请求,HttpServletRequest,存东西。
③ Response:表示⼀次响应,HttpServletResponse。
④ Session:表示⼀次会话,HttpSession,存东西:保存用户信息。
⑤ Aapplication:表示当前 Web 应用,全局对象,ServletContext,存东西:保存所有用户共享信息。
⑥ Config:当前 JSP 对应的 Servlet 的 ServletConfig 对象,获取当前 Servlet 的配置信息。
⑦ out:向浏览器输出数据,JspWriter。
⑧ page:当前 JSP 对应的 Servlet 对象,Servlet。
⑨ exception:表示 JSP ⻚⾯发⽣的异常,Exception。
其中,常用的是 request、response、session、application、pageContext
存东西的区别:
//保存的数据只在一个页面中有效
pageContext.setAttribute("name1","张三1");
//保存的数据只在一次请求中有效,请求转发会携带这个数据
request.setAttribute("name2","张三2");
//保存的数据只在一次会话中有效,从打开浏览器到关闭浏览器
session.setAttribute("name3","张三3");
//保存的数据只在服务器中有效,从打开服务器到关闭服务器
application.setAttribute("name4","张三4");
从底层到高层(作用域):page --> request --> session --> application
使用场景:
request:客户端向服务器发送请求,产生的数据,可能过会就用不上了,例:某个文章。
session:客户端向服务器发送请求,产生的数据,可能用完了还要用,例:用户信息。
application:客户端向服务器发送请求,产生的数据,一个用户用完了,其他用户还可能使用。例:全局参数。
6.JSP标签、JSTL标签、EL表达式
相关依赖:
<!-- JSTL表达式的依赖 -->
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>2.0.0</version>
</dependency>
<!-- standard标签库 -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
(1)EL表达式:${}
获取数据
执行运算
获取web开发的常用对象
(2)JSP标签:
<%-- 引用页面 --%>
<jsp:include page="header.jsp"></jsp:include>
<%--
请求转发,并携带参数
http://localhost:8080/s1/jsptag1.jsp?name=zhangsan&age=20
--%>
<jsp:forward page="jspTag2.jsp">
<jsp:param name="name" value="zhangsan"/>
<jsp:param name="age" value="20"/>
</jsp:forward>
(3)JSTL表达式:
JSTL 标签库的使用,就是为了弥补 HTML 标签的不足。
它定义了许多标签,标签的功能和 java 代码一样。
使用步骤:
① jsp文件中引入对应的 taglib
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
② 在 tomcat 安装目录下的 lib 文件夹中,也需要引入 jstl 的包,否则会报错:JSTL解析错误。
③ 使用其中的方法
Ⅰ. c:if
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>if测试</h3>
<hr>
<form action="core.jsp" method="get">
<%--
EL表达式获取表单中的数据
${param.参数名}
--%>
<input type="text" name="username" value="${param.username}">
<input type="submit" value="登录">
</form>
<%--判断如果提交的用户名是管理员,则登录成功--%>
<c:if test="${param.username=='admin'}" var="isAdmin">
<c:out value="管理员欢迎您!"/>
</c:if>
<%--自闭合标签--%>
<c:out value="${isAdmin}"/>
</body>
</html>
Ⅱ. c:choose 和 c:when
<%--定义一个变量score,值为85--%>
<c:set var="score" value="55"/>
<c:choose>
<c:when test="${score>=90}">
你的成绩为优秀
</c:when>
<c:when test="${score>=80}">
你的成绩为一般
</c:when>
<c:when test="${score>=70}">
你的成绩为良好
</c:when>
<c:when test="${score<=60}">
你的成绩为不及格
</c:when>
</c:choose>
Ⅲ. c:foreach
<%
List<String> nameLists = new ArrayList<>();
nameLists.add("张三");
nameLists.add("李四");
nameLists.add("王五");
nameLists.add("赵六");
nameLists.add("田六");
request.setAttribute("list",nameLists);
%>
<%--
var : 每一次遍历出来的变量
items: 要遍历的对象
begin: 起始索引
end : 结束索引
step : 步长
--%>
<c:forEach var="name" items="${list}" begin="1" end="3" step="1" >
<c:out value="${name}"/> <br>
</c:forEach>
八、Filter过滤器
Filter:过滤器 ,用来过滤请求中的数据(如:处理中文乱码,登录验证….)。
步骤:
① 导入 jakarta.servlet.Filter 类
② 实现 Filter 接口,重写方法
import jakarta.servlet.*;
import java.io.IOException;
public class FilterDemo implements Filter {
//web启动时,过滤器初始化,随时等待过滤对象出现
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter初始化=====》");
}
/**
* 1. 过滤器器中的所有代码在过滤特定请求(路径)时都会执行
* 2. 必须要让过滤器继续通行,把请求和响应继续传递给后续所有过滤器和servlet。
chain.doFilter(request,response);
*/
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
servletResponse.setContentType("text/html");
System.out.println("Filter执行前=====》");
//放行,如果不写,程序到这里就停止了
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("Filter执行后=====》");
}
// web服务器关闭时,过滤器才会销毁
public void destroy() {
System.out.println("Filter销毁=====》");
}
}
注意:
必须要让过滤器继续通行:chain.doFilter(request,response); 把请求和响应继续传递给后续所有过滤器和servlet。
如果没有调用这个方法,过滤器链就会被中断,后续的过滤器和目标资源将不会被执行。
请求不会继续传递下去,而是直接在当前过滤器中结束。
③ 在 web.xml 中 配置 filter,设置特定的请求(路径)
<filter>
<filter-name>FilterDemo</filter-name>
<filter-class>com.mihoyo.filter.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo</filter-name>
<!--只要是/fliter下的任何请求,都走这个过滤器-->
<url-pattern>/filter/*</url-pattern>
</filter-mapping>
应用:用户只有登录后,才能进入主页。用户没登录或者退出登录后,就不能进入主页了。
(1)用户登录之后,向 session 中存放数据
(如果用直接 sessionId 的话,会频繁涉及 session 的创建和销毁,造成资源浪费。
向 session 中存数据的话,session 还在,可以复用)
由于 session 中存入的数据的名字会频繁使用(存取),一旦发生变化,很多地方都要修改。
将该数据的名字以静态常量的形式,存储一个常量类中。写名字的地方,直接调用该常量即可。
如果名字发生变化,就只要修改该类即可。
(2)进入主页的时候,通过过滤器判断:用户是否已经登录(session中存的数据是否存在)
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (request.getSession().getAttribute(Constant.USER_SESSION) == null) {
response.sendRedirect("/error.jsp");
}
filterChain.doFilter(servletRequest,servletResponse);
}
九、监听器
步骤:
① 编写一个监听器:实现监听器的接口
import jakarta.servlet.*;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
public class OnlineCountListener implements HttpSessionListener {
//创建session监听:创建Session时会触发一次该事件。
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
ServletContext ctx = httpSessionEvent.getSession().getServletContext();
System.out.println(httpSessionEvent.getSession().getId());
Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");
if (onlineCount==null){
onlineCount = 1;
}else {
int count = onlineCount;
onlineCount = count + 1;
}
ctx.setAttribute("OnlineCount",onlineCount);
}
//销毁session监听:销毁session时,会触发一次该事件。
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
ServletContext ctx = httpSessionEvent.getSession().getServletContext();
Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");
if (onlineCount==null){
onlineCount = 0;
}else {
int count = onlineCount;
onlineCount = count - 1;
}
ctx.setAttribute("OnlineCount",onlineCount);
}
}
② 在 web.xml 中配置监听器
<!--注册监听器-->
<listener>
<listener-class>com.mihoyo.listener.OnlineCountListener</listener-class>
</listener>
③ 看情况是否使用
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>当前有<%=request.getServletContext().getAttribute("OnlineCount")%>人在线</h1>
</body>
</html>
注意:
tomcat 在启动时,会进行多次连接。因为可能会连接失败,但session已经创建了。
此时就会造成只有一个客户端(浏览器),但 session 数量不唯一的情况。
此时不重启服务器,而是重新发布项目, session 就会自动销毁了,显示就正常了。