Java Web学习笔记
预备知识
C/S架构
Client / Server(客户端 / 服务器)
C/S架构的特点:需要安装特定的客户端软件
B/S架构
Browser / Server(浏览器 / 服务器)
B/S结构的系统,其实就是开发网站,开发一个WEB系统
Tomcat服务器
安装
官网tomcat.apache.org
下载解压
配置环境变量CATALINA_HOME
和Path
启动和关闭
startup //启动服务器
shutdown.bat //关闭服务器(建议改为stop)
Servlet
Servlet规范
- 规范了哪些接口
- 规范了哪些类
- 规范了一个web应用中应该有哪些配置文件
- 规范了一个web应用中配置文件的名字
- 规范了一个web应用中配置文件存放的路径
- 规范了一个web应用中配置文件的内容
- 规范了一个合法有效的web应用它的目录结构应该是怎么样的。
- …
开发一个带有Servlet的Web App
在
webapps
目录下新建一个目录,起名crm
(webapp
的名字)在
webapp
的根下新建一个目录:WEB-INF
,必须一模一样,下同在
WEB-INF
目录下新建一个目录:classes
,存放的是java
程序编译之后的class文件(字节码文件)。在
WEB-INF
目录下新建一个目录:lib
,用来放第三方jar包(如果没有可以不创建)在
WEB-INF
目录下新建一个文件web.xml
,配置文件中描述了请求路径和Servlet类之间的对照关系,最好拷贝编写一个
java
程序,实现Servlet
接口编上一步的java程序
HelloServlet.java
,这里必须配置环境变量CLASSPATH=.;C:\Tomcat\apache-tomcat-10.0.27\lib\servlet-api.jar
将编译之后的
HelloServlet.class
文件拷贝到WEB-INF\classes
目录下在
web.xml
文件中编写配置信息,让“请求路径”和”Servlet类名”关联在一起(在web.xml文件中注册Servlet)<?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" metadata-complete="true"> <!--servlet描述信息--> <!--任何一个servlet都对应一个servlet-mapping--> <servlet> <servlet-name>helloworld</servlet-name> <!--这个位置必须带有包名的全限定类名--> <servlet-class>top.zhengru.servlet.HelloServlet</servlet-class> </servlet> <!--servlet映射信息--> <servlet-mapping> <!--写的内容要和上面的一样--> <servlet-name>helloworld</servlet-name> <!--这里需要一个以 / 开始的路径--> <url-pattern>/aaa/bbb</url-pattern> </servlet-mapping> </web-app>
启动
Tomcat
服务器,打开浏览器,在浏览器地址栏上输入与url.pattern
一致的请求路径(加项目名)Tomcat
服务器负责调用main
方法,Tomcat
服务器启动的时候执行的就是main
方法。我们只需要编写Servlet
接口的实现类,然后将其注册到web.xml
文件中即可
踩坑记录(?)
这里第八步的时候注意是把整个包复制到classes目录下而不是单单只复制一个文件,在这里卡500找了一个早上,换了好几次jdk和tom的版本都没解决。。。。。。。。(如图所示复制)
浏览器发送请求到最终服务器调用Servlet中的方法的过程
- 用户输入URL,或者直接点击超链接:127.0.0.1:8080/crm/aaa/bbb
- 然后Tomcat服务器接收到请求,截取路径:/aaa/bbb
- Tomcat服务器在web.xml文件中查找/aaa/bbb对应的Servlet是:top.zhengru.servlet.HelloServlet
- Tomcat服务器通过反射机制,创建top.zhengru.servlet.HelloServlet的对象
- Tomcat服务器调用top.zhengru.servlet.HelloServlet对象的Service方法
解决Tomcat服务器在DOS命令窗口中乱码的问题
在\Tomcat\apache-tomcat-10.0.27\conf
打开logging.properties文件
找到java.util.logging.ConsoleHandler.encoding = UTF-8
这一行,将UTF-8
改成GBK
java.util.logging.ConsoleHandler.encoding = GBK
向浏览器相应一段HTML代码
public void service(ServletRequest request, ServletResponse response){
// 向控制台输出hello, servlet
System.out.println("hello, servlet");
// 设置响应体的内容类型:普通文本或html
response.setContentType("text/html;charset=utf-8");
//将信息输出到浏览器上需要使用ServletResponse接口:response
//response表示从服务器向浏览器发送数据叫做响应
PrintWriter out = response.getWriter();
out.print("hello servlet, you are my first servlet!!!");
// 向浏览器中响应一段html代码
out.print("<h1>hello world!</h1><br><h1>这是我第一个webapp!</h1>");
}
Servlet编写JDBC连接数据库
编写程序StudentServlet.java
package top.zhengru.servlet;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;
public class StudentServlet implements Servlet{
public void init(ServletConfig config) throws ServletException{
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException{
//设置响应的内容的类型
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//JDBC
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/db1";
String user = "账号";
String password = "密码";
conn = DriverManager.getConnection(url, user, password);
//创建预编译数据库对象
String sql = "select * from emp";
ps = conn.prepareStatement(sql);
//执行sql
rs = ps.executeQuery();
//处理查询结果集
while (rs.next()) {
String id = rs.getString("id");
String name = rs.getString("ename");
out.print("id:" + id + " name:" + name +"<br>");
}
}catch (Exception e){
e.printStackTrace();
}finally{
//释放资源
if (rs!=null){
try{
rs.close();
}catch (Exception e){
e.printStackTrace();
}
}
if (ps!=null){
try{
ps.close();
}catch (Exception e){
e.printStackTrace();
}
}
if (conn!=null){
try{
conn.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public void destroy(){
}
public String getServletInfo(){
return "";
}
public ServletConfig getServletConfig(){
return null;
}
}
导入mysql驱动jar包
将驱动放在lib目录下
启动Tomcat
运行结果如图:
使用IDEA开发Servlet
新建一个空项目
new一个empty project
新建一个模块
找到File-New-Module
添加一个java se
的模块
给模块添加框架支持
右键项目文件选择Add Framwork Support
,勾选Web Applicantion
添加Servlet所需的jar包
找到File-Project Structure
,给模块添加依赖
这里添加了两个jar包
配置Tomcat服务器
进入Edit Configurations
,添加local Tomcat
,选择Tomcat
的根目录
开始编写Java程序
如果有需要可以手动在WEB-INF
目录下创建lib
文件夹
运行
Servlet对象的生命周期
Servlet对象更像一个人的一生
Servlet
的无参数构造方法执行:标志着出生Servlet
对象的init方法的执行:标志着正在接受教育Servlet
对象的service
方法的执行:标志着开始工作Servlet
对象的destroy
方法的执行:标志着临终
让服务器启动的时候创建Servlet对象
在servlet标签中添加子标签,在该子标签中填写整数,越小的整数优先级越高
<load-on-startup>1</load-on-startup>
Servlet对象谁来维护
Servlet
对象的创建,对象上方法的调用,对象最终的销毁,Javaweb
程序员无权干预Servlet
对象的生命周期是由Tomcat
服务器全权负责的Tomcat
服务器通常我们又称为:WEB容器(WEB Container
)- WEB容器来管理
Servlet
对象的死活 - 自己
new
的Servlet
对象不受WEB容器管理 - WEB容器创建的
Servlet
对象,这些Servlet对象都会被放到一个集合当中(HashMap
),能够被WEB容器管理 - 容器底层应该有一个
HashMap
这样的集合,在这个集合当中存储了Servlet
对象和请求路径之间的关系
结论
- 默认情况下,服务器在启动的时候
Servlet
对象并不会被实例化 - 用户在发送第一次请求的时候
Servlet
对象被实例化 - 对象被创建出来之后,先对象的init
方法
(仅仅调用一次),然后调用service
方法 Servlet
对象是单例的,Servlet
对象并没有新建,多次请求还是使用之前创建好的Servlet
对象,直接调用该对象的service
方法(Servlet
类并不符合单例模式,属于假单例,真单例构造器是私有化的)destroy
方法在服务器关闭的时候被调用(仅仅调用一次),在销毁AServlet
对象的内存之前,自动调用destroy
方法- 当我们
Servlet
类中编写一个有参数的构造方法,如果没有手动编写无参数构造方法会出现500错误,无法实例化Servlet对象(500错误一般情况下是因为服务器端的Java程序出现了异常)
适配器设计模式Adapter
编写GenericServlet类(适配器)
编写一个GenericServlet
类,实现Servlet接口,GenericServlet
是一个抽象类,其中有一个抽象方法service
,我们创建的类去继承GenericServlet
抽象类,并完成service
方法的编写
public class LoginServlet extends GenericServlet{
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("正在处理用户请求...");
}
}
优化GenericServlet类
主要优化了init
中的config
public abstract class GenericServlet implements Servlet {
private ServletConfig config; //让service方法也能够调用config
@Override
public final void init(ServletConfig servletConfig) throws ServletException {
this.config = servletConfig;
this.init(); //调用新写的init
} // 加入final修饰,防止子类重写破坏config
public void init(){} //写一个新的init给子类重写
@Override
public ServletConfig getServletConfig() {
return config; ////让service方法也能够调用config
}
@Override
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException; // 将service方法设置为抽象,子类就可以只完成service方法
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
自带的GenericServlet类
事实上GenericServlet
类已经存在不需要我们编写
ServletConfig接口
ServletConfig是什么?
ServletConfig
是Servlet规范中的一员,是一个接口,WEB
服务器实现了这个接口ServletConfig
对象中封装了web.xml
文件中servlet
的配置信息,一个Servlet
对应一个ServletConfig
对象- Servlet对象和ServletConfig对象都是Tomcat服务器创建的,默认情况下,都是在用户发送第一次请求的时候创建
配置优化
可以选择更改一些配置,这样可以避免每次更新代码都需要重启服务器
获取web.xml中的初始化参数信息
getInitParameterNames
获取所有初始化参数的name
getInitParameter
<servlet>
<servlet-name>configTest</servlet-name>
<servlet-class>top.zhengru.javaweb.servlet.ConfigTestServlet</servlet-class>
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/db1</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>密码</param-value>
</init-param>
</servlet>
Enumeration<String> initParameterNames = config.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
String parameterName = initParameterNames.nextElement();
out.print(parameterName);
out.print(" : ");
String parameter = config.getInitParameter(parameterName);
out.print(parameter);
out.print("<br>");
}
ServletContext接口
ServletContext是什么?
ServletContext
是接口,servlet
规范中的一员,WEB
服务器实现了这个接口
Tomcat
服务器启动webapp
的时候创建ServletContext
对象
只要在同一个webapp
当中,只要在同一个应用当中,所有的Servlet
对象都是共享同一个ServletContext
对象
servletContext
被称为Servlet
上下文对象(Servlet
对象的四周环境对象)
一个ServletContext
对象通常对应的是一个web.xml
文件
ServletContext对象的生命周期
ServletContext对象在服务器启动阶段创建,在服务器关闭的时候销毁,ServletContext对象是应用级对象
该接口中常用的方法
getInitParameter方法
一般一个项目中共享的配置信息会放到context-param
标签当中
public String getInitParameter(String name); // 通过初始化参数的name获取value
public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name
Enumeration<String> initParameterNames = application.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
String name = initParameterNames.nextElement();
String value = application.getInitParameter(name);
out.print(name+"="+value);
out.print("<br>");
}
<context-param>
<param-name>pageSize</param-name>
<param-value>10</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
getContextPath方法
// 获取应用的根路径(非常重要),因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径
public String getContextPath();
String contextPath = application.getContextPath();
getRealPath方法
// 获取文件的绝对路径(真实路径)
public String getRealPath(String path);
//在这个括号里面的path中,填写的路径可以不加“/”
String realPath = application.getRealPath("/index.html");
log方法
// 通过ServletContext对象也是可以记录日志的
public void log(String message);
public void log(String message, Throwable t);
application.log("用log方法来记日志");
ServletContext应用域
注意事项
- 如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到
ServletContext
这个应用域中,数据量比较大的话,太占用堆内存,并且这个对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能,占用内存较小的数据量可以考虑放进去 - 所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在
ServletContext
对象中的数据一般都是只读的 - 数据量小、所有用户共享、又不修改,这样的数据放到
ServletContext
这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次在用的时候,不需要从数据库中再次获取,大大提升执行效率
应用域的存、取、删(操作域)
// 存
public void setAttribute(String name, Object value);
// 取
public Object getAttribute(String name);
// 删
public void removeAttribute(String name);
HttpServlet类
B/S
结构的系统是基于HTTP
超文本传输协议的,在Servlet
规范当中,提供了一个类叫做HttpServlet
,它是专门为HTTP
协议准备的一个Servlet
类。我们编写的Servlet
类要继承HttpServlet
。(HttpServlet
是HTTP
协议专用的)使用HttpServlet
处理HTTP
协议更便捷
jakarta.servlet.Servlet//(接口)
jakarta.servlet.GenericServlet implements Servlet//(抽象类)
jakarta.servlet.http.HttpServlet extends GenericServlet//(抽象类)
目前为止接触过的缓存机制
堆内存当中的字符串常量池
堆内存当中的整数型常量池
连接池
这里所说的连接池中的连接是java语言连接数据库的连接对象:
java.sql.Connection
对象。JVM
是一个进程。MySQL
数据库是一个进程。进程和进程之间建立连接,打开通道是很费劲的。是很耗费资源的。可以提前先创建好N个Connection
连接对象,将连接对象放到一个集合当中,我们把这个放有Connection
对象的集合称为连接池。每一次用户连接的时候不需要再新建连接对象,省去了新建的环节,直接从连接池中获取连接对象,大大提升访问效率连接池
最小连接数
最大连接数
连接池可以提高用户的访问效率。当然也可以保证数据库的安全性
线程池
Tomcat
服务器本身就是支持多线程的Tomcat
服务器启动的时候,会先创建好N多个线程Thread
对象,然后将线程对象放到集合当中,称为线程池。用户发送请求过来之后需要有一个对应的线程来处理这个请求,这个时候线程对象就会直接从线程池中拿,效率比较高所有的WEB服务器,或者应用服务器,都是支持多线程的,都有线程池机制
redis
NoSQL
数据库,非关系型数据库,缓存数据库
向
ServletContext
应用域中存储数据,也等于是将数据存放到缓存cache
当中
HTTP协议
什么是HTTP协议?
W3C制定的一种超文本传输协议
游走在B和S之间。B向S发数据要遵循HTTP协议。S向B发数据同样需要遵循HTTP协议。这样B和S才能解耦合
什么是解耦合?
- B不依赖S
- S也不依赖B
浏览器向WEB服务器发送数据,叫做:请求(request)
WEB服务器向浏览器发送数据,叫做:响应(response)
HTTP协议包括请求协议、响应协议
HTTP协议就是提前制定好的一种消息模板
HTTP的请求协议(B–>S)
请求协议包括什么?
请求行
第一部分:请求方式(7种)
get
(常用)post
(常用)delete
put
head
options
trace
第二部分:
URI
代表网络中某个资源的名字。但是通过
URI
是无法定位资源的。URL
包括URI
http://localhost:8080/servlet05/index.html
这是URL
/servlet05/index.html
这是URI
第三部分:
HTTP
协议版本号
请求头
请求的主机
主机的端口
浏览器信息
平台信息
cookie
等信息…
空白行
- 空白行是用来区分“请求头”和“请求体”
请求体
- 向服务器发送的具体数据
具体报文
GET请求
GET /servlet05/getServlet?username=jack&userpwd=123 HTTP/1.1 //请求行
//请求头(本身没有这个换行)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: keep-alive
Cookie: Idea-75fd4672=73a4fda9-20c5-4f1a-9192-a68c6d64678a
Host: localhost:8080
Referer: http://localhost:8080/servlet05/index.html
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.55
sec-ch-ua: "Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
//空白行
username=jack&userpwd=123 //请求体
POST请求
POST /servlet05/postServlet HTTP/1.1 //请求行
//请求头(本身没有这个换行)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 25
Content-Type: application/x-www-form-urlencoded
Cookie: Idea-75fd4672=73a4fda9-20c5-4f1a-9192-a68c6d64678a
Host: localhost:8080
Origin: http://localhost:8080
Referer: http://localhost:8080/servlet05/index.html
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.55
sec-ch-ua: "Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
//空白行
username=lucy&userpwd=123 //请求体
怎么发送GET和POST请求?
到目前为止,只有一种情况可以发送
POST
请求:使用form
表单,并且form
标签中的method
属性值为:method=“post”
其他所有情况一律都是
get
请求:在浏览器地址栏上直接输入
URL
,回车,属于get
请求。在浏览器上直接点击超链接,属于
get
请求。使用
form
表单提交数据时,没写method
,默认就是get或者使用
form
的时候,form
标签中method
属性值为:method=“get”
…
GET请求和POST请求的区别
get
请求发送数据的时候,数据会挂在URI
的后面(请求行)post
请求发送数据的时候,在请求体当中发送(请求体)get
请求只能发送普通的字符串,且无法发送大数据量post
请求可以发送任何类型的数据,且可以发送大数据量get
请求比较适合从服务器端获取数据post
请求比较适合向服务器端传送数据get
请求是安全的,因为get
请求只是为了从服务器上获取数据。不会对服务器造成威胁post
请求是危险的,因为post
请求是向服务器提交数据,数据有可能通过后门进入到服务器post
是为了提交数据,一般情况下拦截请求的时候,大部分会选择拦截(监听)post
请求get
请求支持缓存,任何一个get
请求最终的“响应结果”都会被浏览器缓存起来- 只要发送
get
请求,浏览器先从本地缓存中找,找不到才会去服务器上获取 - 在路径的后面添加一个时间戳,可以避免
get
走缓存 post
请求不支持缓存(用来修改服务器端的资源的)post
请求之后,服务器“响应的结果”不会被浏览器缓存起来
GET请求和POST请求怎么选择?
get
请求比较适合从服务器端获取数据post
请求比较适合向服务器端传送数据- 大部分的
form
表单提交,都是post
方式 - 敏感信息用
post
请求 - 文件上传,一定是
post
请求 get
请求和post
请求发送的请求数据格式是统一的,只是位置不同
HTTP的响应协议(S–>B)
响应协议包括什么?
状态行
第一部分:协议版本号(HTTP/1.1)
第二部分:状态码(
HTTP
协议中规定的响应状态号。不同的响应结果对应不同的号码)200
表示请求响应成功,正常结束404
表示访问的资源不存在405
表示前端发送的请求方式与后端请求的处理方式不一致时发生:比如:前端是
POST
请求,后端的处理方式按照get
方式进行处理时,发生405
比如:前端是
GET
请求,后端的处理方式按照post
方式进行处理时,发生405
500
表示服务器端的程序出现了异常。一般会认为是服务器端的错误导致的以
4
开始的,一般是浏览器端的错误导致的以
5
开始的,一般是服务器端的错误导致的
第三部分:状态的描述信息
ok
表示正常成功结束not found
表示资源找不到
响应头
响应的内容类型
响应的内容长度
响应的时间
…
空白行
- 用来分隔“响应头”和“响应体”的
响应体
- 响应体就是响应的正文,这些内容是一个长的字符串
- 字符串被浏览器渲染,解释并执行,最终展示出效果
具体报文
HTTP/1.1 200 ok //状态行
Content-Type: text/html;charset=UTF-8 //响应头
Content-Length: 160
Date: Fri, 20 Jan 2023 13:29:18 GMT
Keep-Alive: timeout=20
Connection: keep-alive
//空白行
<!DOCTYPE html> //响应体
<html>
<head>
<title>from get servlet</title>
</head>
<body>
<h1>from get servlet</h1>
</body>
</html>
模板方法设计模式
设计模式的种类
- GoF设计模式(23种设计模式)
- 单例模式
- 工厂模式
- 代理模式
- 门面模式
- 责任链设计模式
- 观察者模式
- 模板方法设计模式
- …
- JavaEE设计模式
- DAO
- DTO
- VO
- PO
- pojo
- …
- …
什么是设计模式?
- 在模板类的模板方法当中定义核心算法骨架,具体的实现步骤可以延迟到子类当中完成
- 模板类通常是一个抽象类,模板类当中的模板方法定义核心算法,这个方法通常是
final
的(但也可以不是final
的) - 模板类当中的抽象方法就是不确定实现的方法,这个不确定怎么实现的事儿交给子类去做
- 方法添加
final
关键字后,这个方法无法被覆盖,这样核心的算法也能够得到保护。通常模板类中定义核心算法的方法基本上都需要添加final关键字
HttpServlet源码分析
源码
public class HelloServlet extends HttpServlet {
// 用户第一次请求,创建HelloServlet对象的时候,会执行这个无参数构造方法。
public HelloServlet() {
}
//override 重写 doGet方法
//override 重写 doPost方法
}
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
// 用户第一次请求的时候,HelloServlet对象第一次被创建之后,这个init方法会执行。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后,会执行这个没有参数的init()
public void init() throws ServletException {
// NOOP by default
}
}
// HttpServlet模板类。
public abstract class HttpServlet extends GenericServlet {
// 用户发送第一次请求的时候这个service会执行
// 用户发送第N次请求的时候,这个service方法还是会执行。
// 用户只要发送一次请求,这个service方法就会执行一次。
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
//这里的代码主要功能是:
//将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和 HttpServletResponse
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 调用重载的service方法。
service(request, response);
}
// 这个service方法的两个参数都是带有Http的。
// 这个service是一个模板方法。
// 在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求方式
// 这个请求方式最终可能是:""
// 注意:request.getMethod()方法获取的是请求方式,可能是七种之一:
// GET POST PUT DELETE HEAD OPTIONS TRACE
String method = req.getMethod();
// 如果请求方式是GET请求,则执行doGet方法。
//METHOD_GET这个就相当于“GET”,主要是用equals来进行判断
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
// 如果请求方式是POST请求,则执行doPost方法。
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
// 报405错误
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 报405错误
String msg = lStrings.getString("http.method_post_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
}
405错误
405表示前端的错误,发送的请求方式不对(和服务器不一致)
假设前端发送的请求是get请求,后端程序员重写的方法是doPost
假设前端发送的请求是post请求,后端程序员重写的方法是doGet
如何避免?
- 后端重写了doGet方法,前端一定要发get请求
- 后端重写了doPost方法,前端一定要发post请求
Servlet类的最终开发步骤
- 编写一个
Servlet
类,直接继承HttpServlet
- 重写
doGet
方法或者重写doPost
方法 - 将
Servlet
类配置到web.xml
文件当中 - 准备前端的页面(
form
表单),form
表单中(action
中指定)指定请求路径即可
WEB欢迎页
WEB欢迎页即访问http://localhost:8080/servlet07
默认访问设置的欢迎页面
添加方法
- 在
web.xml
文件中加入welcome-file-list
信息 - 路径不需要以“/”开始,并且这个路径默认是从webapp的根下开始查找
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
- 如果在新建的目录下则这样添加
<welcome-file-list>
<welcome-file>page1/page2/page.html</welcome-file>
</welcome-file-list>
- 一个
webapp
可以设置多个欢迎页面 - 越靠上的优先级越高,找不到的继续向下找
<welcome-file-list>
<welcome-file>login.html</welcome-file>
<welcome-file>page1/page2/page.html</welcome-file>
</welcome-file-list>
Tomcat
服务器已经提前配置欢迎页面了,如果没有设置局部的欢迎页面,则默认是index.html
有两个地方可以配置欢迎页面:
一个是在
webapp
内部的web.xml
文件中(局部配置)一个是在
CATALINA_HOME/conf/web.xml
文件中进行配置(全局配置)- ```xml
index.html index.htm index.jsp - 局部优先原则 - 欢迎页可以是一个`Servlet` - 在`web.xml`文件中配置`servlet`和欢迎页即可 ## 关于WEB-INF目录 - 放在`WEB-INF`目录下的资源是受保护的 - 在浏览器上不能够通过路径直接访问 - `HTML`、`CSS`、`JS`、`image`等静态资源一定要放到`WEB-INF`目录之外 # HttpServletRequest接口详解 `HttpServletRequest`是一个接口,继承了`ServletRequest` ```java public interface HttpServletRequest extends ServletRequest {}
- ```xml
HttpServletRequest
对象中封装了HTTP
的请求协议
request和response对象的生命周期
request
对象和response
对象,一个是请求对象,一个是响应对象- 这两个对象只在当前请求中有效
- 一次请求对应一个
request
- 两次请求则对应两个
request
HttpServletRequest接口中常用的方法
Map<String,String[]> getParameterMap() //获取Map
Enumeration<String> getParameterNames() //获取Map集合中所有的key
String[] getParameterValues(String name) //根据key获取Map集合的value
String getParameter(String name) //获取value这个一维数组当中的第一个元素
request请求域对象
request
对象实际上又称为“请求域”对象应用域:
ServletContext
(Servlet上下文对象)向应用域当中绑定数据,就相当于把数据放到了缓存当中,减少IO的操作,大大提升系统的性能
“请求域”对象要比“应用域”对象范围小很多,生命周期短很多
void setAttribute(String name, Object value);
Object getAttribute(String name);
void removeAttribute(String name);
请求域只在一次请求内有效,一个请求对象request对应一个请求域对象,一次请求结束之后,这个请求域就销毁了
如何选择请求域和应用域?
- 尽量使用小的域对象,因为小的域对象占用的资源较少
Request对象的常用方法
转发(一次请求)
写法一:
//第一步:获取请求转发器对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/b");
//第二步:调用转发器的forward方法完成跳转/转发
dispatcher.forward(request,response);
写法二:
request.getRequestDispatcher("/b").forward(request,response);
Servlet共享数据
ServletContext应用域当中,但是应用域范围太大,占用资源太多,不建议使用。
可以将数据放到request域当中,然后AServlet转发到BServlet
转发的下一个资源可以是Tomcat服务器当中的合法资源,例如:html…
- 转发的路径以“/”开始,不加项目名
HttpServletRequest接口的其他常用方法
// 获取客户端的IP地址
String remoteAddr = request.getRemoteAddr();
// 设置请求体的字符集(处理POST请求的乱码问题,不能解决get请求的乱码问题)
// Tomcat9和9以前,执行以下代码解决乱码
request.setCharacterEncoding("UTF-8");
// Tomcat9和9以前,解决响应乱码
response.setContentType("text/html;charset=UTF-8");
// get请求乱码怎么解决
// 方案:修改CATALINA_HOME/conf/server.xml配置文件
<Connector URIEncoding="UTF-8" />
// 注意:从Tomcat8之后,URIEncoding的默认值就是UTF-8,所以GET请求也没有乱码问题了。
// 获取应用的根路径
String contextPath = request.getContextPath();
// 获取请求方式
String method = request.getMethod();
// 获取请求的URI
String uri = request.getRequestURI(); // /aaa/testRequest
// 获取servlet path
String servletPath = request.getServletPath(); // /testRequest
Servlet实现单表CRUD
准备一张数据库表
# 部门表
drop table if exists dept;
create table dept(
deptno int primary key,
dname varchar(255),
loc varchar(255)
);
insert into dept(deptno, dname, loc) values(10, 'XiaoShouBu', 'BEIJING');
insert into dept(deptno, dname, loc) values(20, 'YanFaBu', 'SHANGHAI');
insert into dept(deptno, dname, loc) values(30, 'JiShuBu', 'GUANGZHOU');
insert into dept(deptno, dname, loc) values(40, 'MeiTiBu', 'SHENZHEN');
commit;
select * from dept;
准备一套HTML页面(项目原型)
- 欢迎页面:index.html
- 列表页面:list.html(核心)
- 新增页面:add.html
- 修改页面:edit.html
- 详情页面:detail.html
分析系统的功能
- 查看部门列表
- 新增部门
- 删除部门
- 查看部门详情
- 跳转到修改页面
- 修改部门
DBUtil工具类代码
在src
目录下创建resources
文件夹放置配置文件jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db2
user=root
password=密码
创建工具类DBUtil
public class DBUtil {
private static ResourceBundle bundle = ResourceBundle.getBundle("resources.jdbc");
private static String driver = bundle.getString("driver");
private static String url = bundle.getString("url");
private static String user = bundle.getString("user");
private static String password = bundle.getString("password");
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection GetConnection() throws SQLException {
Connection conn = DriverManager.getConnection(url,user,password);
return conn;
}
public static void close(Connection conn, Statement ps, ResultSet rs){
if (rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
查看部门列表以及详情页
修改前端超链接
<a href="/oa/dept/list.html">查看部门列表</a>
编写web.xml
<servlet>
<servlet-name>listDept</servlet-name>
<servlet-class>top.zhengru.oa.web.action.DeptListServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>listDept</servlet-name>
<url-pattern>/dept/list</url-pattern>
</servlet-mapping>
完成DeptListServlet
编写
public class DeptListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>部门列表</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1 align='center'>部门列表</h1>");
out.print(" <hr>");
out.print(" <table border='1px' align='center' width='50%'>");
out.print(" <tr>");
out.print(" <th>序号</th>");
out.print(" <th>部门编号</th>");
out.print(" <th>部门名称</th>");
out.print(" <th>操作</th>");
out.print(" </tr>");
String contextPath = request.getContextPath();
Connection conn = null;
PreparedStatement ps= null;
ResultSet rs = null;
try {
conn = DBUtil.GetConnection();
String sql = "select deptno,dname,loc from dept";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
int i=0;
while(rs.next()){
i++;
String deptno = rs.getString("deptno");
String dname = rs.getString("dname");
String loc = rs.getString("loc");
out.print(" <tr>");
out.print(" <td>"+i+"</td>");
out.print(" <td>"+deptno+"</td>");
out.print(" <td>"+dname+"</td>");
out.print(" <td>");
out.print(" <a href=''>删除</a>");
out.print(" <a href='edit.html'>修改</a>");
out.print(" <a href='"+contextPath+"/dept/detail?deptno="+deptno+"'>详情</a>");
out.print(" </td>");
out.print(" </tr>");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,rs);
}
out.print(" </table>");
out.print(" <hr>");
out.print(" <a href='add.html'>新增部门</a>");
out.print(" </body>");
out.print("</html>");
}
}
详情页的方法类似,获取get
请求然后进行输出
实现部门删除
在js
中返回部门编号给服务器,再执行删除的sql
语句,和上面大同小异
<script type='text/javascript'>
function del(dno){
if(window.confirm('确定删除此部门吗?删除后将无法恢复')){
window.location = '/oa/dept/delete?deptno='+dno
}
}
</script>
<a href="javascript:void(0)" onclick="del(30)">删除</a>
????魔幻的js
原来这个
del
是这样写的,结果方法死活不能生效??function del(dno){ if(window.confirm('确定删除此部门吗?删除后将无法恢复')){ alert('正在删除数据,请稍后...') window.location = '/oa/dept/delete?deptno='+dno } }
本来还以为是
a
标签的问题,后来把alert
给去掉了之后del
方法又生效了后来给两条语句都加上分号,发现又能执行了
alert('正在删除数据,请稍后...'); window.location = '/oa/dept/delete?deptno='+dno;
牛逼的是
本地打开这个没加分号的页面,是没有任何问题的
所以
js
到底要不要加分号?????
实现部门新增和修改
保存完转发回list
的时候,应该在DeptListServlet
里重写doPost
方法并且调用doGet
,否则会报错
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
修改分为查询和修改两个步骤,其实都差不多
最终项目结构
转发和重定向深度剖析
代码区别
//转发
request.getRequestDispatcher("/b").forward(request,response);
//重定向
response.sendRedirect(request.getContextPath()+"/b");
本质区别
- 转发:是由
WEB
服务器来控制的,Tomcat
服务器完成跳转动作 - 重定向:是浏览器完成的
如何选择?
如果在上一个
Servlet
当中向request
域当中绑定了数据,希望从下一个Servlet
当中把request
域里面的数据取出来,使用转发机制剩下所有的请求均使用重定向(重定向使用较多)
跳转的资源不一定是
Servlet
Servlet注解式开发
注解+配置文件的开发模式
- 有一些需要变化的信息,配置到
web.xml
文件中 - 一些不会经常变化修改的配置建议使用注解
使用方法
- @注解名称(属性名=属性值, 属性名=属性值, 属性名=属性值…)
- 如果数组中只有一个元素,可以省略大括号
@WebServlet(name = "helloServlet",urlPatterns = {"/hello1","/hello2","/hello3"},
//loadOnStartup = 0,
initParams = {@WebInitParam(name = "username",value = "root"),@WebInitParam(name = "password",value = "密码")})
public class HelloServlet extends HttpServlet {
}
使用模板方法设计模式优化oa项目
写法
一个请求对应一个方法,一个业务对应一个Servlet
类
@WebServlet({"/dept/list","/dept/save","/dept/edit","/dept/detail","/dept/delete","/dept/modify"})
//@WebServlet("/dept/*")
public class DeptServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String servletPath = request.getServletPath();
if ("/dept/list".equals(servletPath)){
doList(request,response);
} else if ("/dept/save".equals(servletPath)){
doSave(request,response);
} else if ("/dept/edit".equals(servletPath)){
doEdit(request,response);
} else if ("/dept/detail".equals(servletPath)){
doDetail(request,response);
} else if ("/dept/delete".equals(servletPath)){
doDel(request,response);
} else if ("/dept/modify".equals(servletPath)){
doModify(request,response);
}
}
private void doList(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
private void doSave(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
private void doEdit(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
private void doDetail(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
private void doDel(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
private void doModify(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
优化完后的效果
JSP
JSP是什么?
JSP
实际上就是一个Servlet
Servlet
的职责:收集数据JSP
的职责:展示数据
原理剖析
index.jsp
访问的时候,会自动翻译生成index_jsp.java
,会自动编译生成index_jsp.class
index_jsp
类继承HttpJspBase
,而HttpJspBase
类继承的是HttpServlet
。所以index_jsp
类就是一个Servlet
类jsp
的生命周期和Servlet
的生命周期完全相同jsp
和servlet
一样,都是单例的(假单例)
基础语法
在JSP中编写
的HTML CSS JS
代码,这些代码对于JSP来说只是一个普通的字符串。但是JSP把这个普通的字符串一旦输出到浏览器,浏览器就会对HTML CSS JS
进行解释执行。展现一个效果。通过page指令来设置响应的内容类型,在内容类型的最后面添加:charset=UTF-8
<%@page contentType=“text/html;charset=UTF-8”%>
,表示响应的内容类型是text/html
,采用的字符集UTF-8<%@page import=“java.util.List,java.util.ArrayList”%>
<% java语句; %>
在这个符号里面的
java
语句是可以拆分的被翻译到
Servlet
类的service方法内部方法体中不能编写静态代码块,不能用访问修饰符修饰定义变量,不能编写方法
service
方法当中不能写静态代码块,不能写方法,不能定义成员变量
注释
<%--jsp的注释,不会被翻译--%>
<%! %>
自动翻译到
service
方法之外不建议使用,因为在
service
方法外面写静态变量和实例变量,都会存在线程安全问题
JSP
的输出语句向浏览器上输出一个
java
变量<% String name = “jack”; **out.write("name = " + name);** %>
<%= %>
- 在
=
的后面编写要输出的内容 - 翻译到
service
方法当中,翻译成:out.print();
- 在
语法总结
JSP
中直接编写普通字符串- 翻译到
service
方法的out.write(“这里”)
- 翻译到
<%%>
- 翻译到
service
方法体内部,里面是一条一条的java
语句
- 翻译到
<%! %>
- 翻译到
service
方法之外
- 翻译到
<%= %>
- 翻译到
service
方法体内部,翻译为:out.print();
- 翻译到
<%@page contentType=“text/html;charset=UTF-8”%>
page
指令,通过contentType
属性用来设置响应的内容类型
使用JSP优化OA项目
以list.jsp
页面为例
<%@ page import="top.zhengru.oa.bean.Dept" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>部门列表</title>
<script type="text/javascript">
function del(dno){
if(window.confirm("确定删除此部门吗?删除后将无法恢复")){
window.location = "<%=request.getContextPath()%>/dept/del?deptno="+dno
}
}
</script>
</head>
<body>
<h1 align="center">部门列表</h1>
<hr>
<table border="1px" align="center" width="50%">
<tr>
<th>序号</th>
<th>部门编号</th>
<th>部门名称</th>
<th>操作</th>
</tr>
<%
List<Dept> deptList = (List<Dept>)request.getAttribute("deptList");
int i = 0;
for(Dept dept:deptList) {
%>
<tr>
<td><%=++i%></td>
<td><%=dept.getDeptno()%></td>
<td><%=dept.getDname()%></td>
<td>
<a href="javascript:void(0)" onclick="del(<%=dept.getDeptno()%>)">删除</a>
<a href="<%=request.getContextPath()%>/dept/edit?deptno=<%=dept.getDeptno()%>">修改</a>
<a href="<%=request.getContextPath()%>/dept/detail?deptno=<%=dept.getDeptno()%>">详情</a>
</td>
</tr>
<%
}
%>
</table>
<hr>
<a href="<%=request.getContextPath()%>/add.jsp">新增部门</a>
</body>
</html>
其余页面类似…
OA登录页面
没什么好说的,就是拿到post
过来的数据然后去数据库查一下
查到了则登陆成功
一般采用密文
session机制(会话机制)
session
对应的类名:HttpSession
- request < session < application
实现原理
JSESSIONID=xxxxxx
以Cookie
的形式保存在浏览器的内存中,浏览器只要关闭,cookie
就没了session
列表是一个Map
,map
的key
是sessionid
,map
的value
是session
对象- 用户第一次请求,服务器生成
session
对象,同时生成id
发送给浏览器 - 用户第二次请求,自动将浏览器内存中的
id
发送给服务器,服务器根据id查找``session
对象 - 关闭浏览器,内存消失,
cookie
消失,sessionid
消失,会话等同于结束 Cookie
禁用如何实现session
- 使用
URL
重写机制 http://localhost:8080/servlet12/test/session;jsessionid=19D1C99560DCBF84839FA43D58F56E16
jsessionid=19D1C99560DCBF84839FA43D58F56E16
这个就是获取到的session id
- 使用
用法
session.setAttribute(); //存
session.invalidate(); //删
JSP的指令
作用
指导JSP的翻译引擎如何工作(指导当前的JSP翻译引擎如何翻译JSP文件)
三大指令
- include指令:包含指令,在JSP中完成静态包含,很少用
- taglib指令:引入标签库的指令,JSTL标签库的时候再学习
- page指令:目前重点学 习一个page指令
使用语法
<%@指令名 属性名=属性值 属性名=属性值 属性名=属性值…%>
page指令常用属性
<%@page session="true|false" %>
true表示启用JSP的内置对象session,表示一定启动session对象。没有session对象会创建。
如果没有设置,默认值就是session="true"
session="false" 表示不启动内置对象session。当前JSP页面中无法使用内置对象session
<%@page contentType="text/json" %>
contentType属性用来设置响应的内容类型
但同时也可以设置字符集
<%@page contentType="text/json;charset=UTF-8" %>
<%@page pageEncoding="UTF-8" %>
pageEncoding="UTF-8" 表示设置响应时采用的字符集
<%@page import="java.util.List, java.util.Date, java.util.ArrayList" %>
<%@page import="java.util.*" %>
import语句,导包
<%@page errorPage="/error.jsp" %>
当前页面出现异常之后,跳转到error.jsp页面
errorPage属性用来指定出错之后的跳转位置
<%@page isErrorPage="true" %>
表示启用JSP九大内置对象之一:exception
默认值是false
JSP九大内置对象
jakarta.servlet.jsp.PageContext pageContext 页面作用域
jakarta.servlet.http.HttpServletRequest request 请求作用域
jakarta.servlet.http.HttpSession session 会话作用域
jakarta.servlet.ServletContext application 应用作用域
pageContext < request < session < application
以上四个作用域都有:setAttribute、getAttribute、removeAttribute方法
以上作用域的使用原则:尽可能使用小的域
java.lang.Throwable exception
jakarta.servlet.ServletConfig config
java.lang.Object page (其实是this,当前的servlet对象)
jakarta.servlet.jsp.JspWriter out (负责输出)
jakarta.servlet.http.HttpServletResponse response (负责响应)
Cookie
cookie最终是保存在浏览器客户端上的
- 可以保存在运行内存中(浏览器只要关闭cookie就消失了)
- 也可以保存在硬盘文件中(永久保存)
假设发送的请求路径是“http://localhost:8080/servlet13/cookie/generate”生成的cookie
- 默认的path是:http://localhost:8080/servlet13/cookie 以及它的子路径
用法
发送cookie
Cookie cookie = new Cookie("productid","13572468"); //创建cookie
cookie.setMaxAge(60*60); //设置cookie的有效时间(秒)
cookie.setPath(“/servlet13”); //手动设置cookie的path
response.addCookie(cookie); //向浏览器发送cookie
接收cookie
Cookie[] cookies = request.getCookies(); // 这个方法可能返回null
if(cookies != null){
for(Cookie cookie : cookies){
String name = cookie.getName();
String value = cookie.getValue();
}
}
EL表达式
作用
EL有预处理,当取不出数据的时候后会显示空白,不会显示null也不会报错
使用EL表达式不会出现数组下标越界的事情,这就是EL表达式的功效
作用:
从某个域中取数据
将取出的数据转成字符串,会自动调用java对象的
toString
方法将其转换成字符串
基本的语法格式:
${表达式}
表达式里面的内容:这里写的一定是存储到域对象当中时的
name
这个语法实际上调用了底层的
getXxx()
方法注意:如果没有对应的
get
方法,则报500
错误
在这里注意到了一个问题,比如一个方法是
getEmail
,那么EL表达式里应该是小写emai
而不是Emai
,否则会报异常。还有一个很有趣的事,如果是getage
和getAge
不会报异常,但getAGE
就会报异常,也许跟驼峰也有些关系(?
读取数据
没指定范围的前提下,EL表达式优先从小范围中读取数据
//范围指定 ${pageContext.data} ${requestScope.data} ${sessionScope.data} ${applicationScope.data}
EL表达式对
null
进行了预处理,向浏览器输出一个空字符串取数据的时候有两种形式:
第一种:
.
(大部分使用这种方式)第二种:
[ ]
(如果存储到域的时候,这个name中含有特殊字符,可以使用 [ ])request.setAttribute(“abc.def”, “zhangsan”);
应该是
${requestScope[“abc.def”]}
掌握使用EL表达式,怎么从Map集合中取数据:
${map.key}
掌握使用EL表达式,怎么从数组和List集合中取数据:
${数组[0]}
${数组[1]}
${list[0]}
其他语法
page指令当中,有一个属性,可以忽略EL表达式
<%@page contentType="text/html;charset=UTF-8" isELIgnored="true" %> 全局控制忽略EL表达式 局部控制:\${username} 也可以忽略EL表达式
EL表达式获取应用的根:
${pageContext.request.contextPath}
其他的隐式对象:
pageContext
:通过这个pageContext
可以在EL表达式中获取request
等其他jsp
的九大内置对象param
:取一个值,如果是多个值的话,只取第一个数值paramValues
:取多个值,用数组下标可以进行访问initParam
:获取Servlet上下文对象ServletContext中的属性内容
EL表达式的运算符
算术运算符
+、-、*、/、%
这里的“+”永远只能用于求和,非数字则转为数字
关系运算符
== eq != > >= < <=
== 和eq底层都是调用的equals方法
逻辑运算符
! && || not and or
条件运算符
? :
取值运算符
[ ]
和.
empty运算符
empty
运算符的结果是boolean
类型${empty param.username}
注意事项
ServletContext
对象是Servlet
上下文对象,对应的是JSP
九大内置对象的application
pageContext.getRequest()
获取的是SerevletRequest
getContextPath
只有HttpServlet
有
JSTL标签库
导入jar包
tomcat10之后引入的jar包是:
- jakarta.servlet.jsp.jstl-2.0.0.jar
- jakarta.servlet.jsp.jstl-api-2.0.0.jar
引入标签库
核心标签库
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
标签的原理
- uri后面的路径实际上指向了一个xxx.tld文件,实际上是一个xml配置文件
- 在tld文件中描述了“标签”和“java类”之间的关系
tld配置文件解析
<tag>
<description>对该标签的描述</description>
<name>catch</name> 标签的名字
<tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class> 标签对应的java类。
<body-content>JSP</body-content> 标签体当中可以出现的内容,如果是JSP,就表示标签体中可以出现符合JSP所有语法的代码。例如EL表达式。
<attribute>
<description>
对这个属性的描述
</description>
<name>var</name> 属性名
<required>false</required> false表示该属性不是必须的。true表示该属性是必须的。
<rtexprvalue>false</rtexprvalue> 这个描述说明了该属性是否支持EL表达式。false表示不支持。true表示支持EL表达式。
</attribute>
</tag>
<c:catch var="">
JSP....
</c:catch>
core标签库中常用的标签
c:if
- <c:if test=“boolean类型,支持EL表达式”></c: if>
c:forEach
- <c:forEach items=“集合,支持EL表达式” var=“集合中的元素” varStatus=“元素状态对象”> ${元素状态对象.count} </c: forEach>
- <c:forEach var=“i” begin=“1” end=“10” step=“2”> ${i} </c: forEach>
c:choose c:when c:otherwise
<c:choose> <c:when test="${param.age < 18}"> 青少年 </c:when> <c:when test="${param.age < 35}"> 青年 </c:when> <c:when test="${param.age < 55}"> 中年 </c:when> <c:otherwise> 老年 </c:otherwise> </c:choose>
Filter过滤器
作用
可以在Servlet这个目标程序执行之前添加代码,也可以在目标Servlet执行之后添加代码
之前之后都可以添加过滤规则,一般情况下,都是在过滤器当中编写公共代码
使用方法
实现
jarkata.servlet.Filter
接口init方法:在Filter对象第一次被创建之后调用,并且只调用一次
doFilter方法:只要用户发送一次请求,则执行一次。发送N次请求,则执行N次。在这个方法中编写过滤规则
destroy方法:在Filter对象被释放/销毁之前调用,并且只调用一次
在web.xml文件中对Filter进行配置,或者使用注解@WebFilter({“*.do”})
<filter> <filter-name>filter1</filter-name> <filter-class>top.zhengru.javaweb.servlet.Filter1</filter-class> </filter> <filter-mapping> <filter-name>filter1</filter-name> <url-pattern>/abc</url-pattern> </filter-mapping>
注意事项
Servlet
对象默认情况下,在服务器启动的时候是不会新建对象的Filter
对象默认情况下,在服务器启动的时候会新建对象Servlet
是单例的。Filter
也是单例的(单实例)目标
Servlet
是否执行,取决于两个条件:- 在过滤器当中是否编写了:
chain.doFilter(request, response);
- 用户发送的请求路径是否和
Servlet
的请求路径一致。
- 在过滤器当中是否编写了:
chain.doFilter(request, response);
的作用- 执行下一个过滤器,如果下面没有过滤器了,执行最终的
Servlet
- 执行下一个过滤器,如果下面没有过滤器了,执行最终的
Filter
的生命周期- 和
Servlet
对象生命周期一致 - 区别在
Filter
默认情况下,在服务器启动阶段就实例化
- 和
路径配置
关于
Filter
的配置路径:/a.do
、/b.do
、/dept/save
。这些配置方式都是精确匹配/*
匹配所有路径*.do
后缀匹配,不要以/
开始(模糊匹配的扩展匹配)/dept/*
前缀匹配
执行顺序
Filter
的优先级,天生的就比Servlet
优先级高/a.do
对应一个Filter
,也对应一个Servlet
。那么一定是先执行Filter
,然后再执行Servlet
在
web.xml
文件中进行配置的时候,Filter
的执行顺序- 依靠
filter-mapping
标签的配置位置,越靠上优先级越高 - 过滤器的调用顺序,遵循栈数据结构
- 依靠
使用
@WebFilter
的时候,Filter
的执行顺序- 在字典中比较
Filter
类名 - 但是一般情况都是使用
web.xml
进行配置
- 在字典中比较
责任链设计模式
Filter
过滤器这里有一个设计模式:- 责任链设计模式
- 过滤器最大的优点:
- 在程序编译阶段不会确定调用顺序。因为
Filter
的调用顺序是配置到web.xml
文件中的,只要修改web.xml
配置文件中filter-mapping
的顺序就可以调整Filter
的执行顺序。 Filter
的执行顺序是在程序运行阶段动态组合的。那么这种设计模式被称为责任链设计模式,符合开发的ocp
原则(开闭原则:对拓展开放,对修改关闭)
- 在程序编译阶段不会确定调用顺序。因为
- 责任链设计模式最大的核心思想:
- 在程序运行阶段,动态的组合程序的调用顺序。
- 使用过滤器改造
OA
项目- 注意在
Filter
中的req
和response
需要进行强制类型转换
- 注意在
Listener监听器
什么是监听器?
- 监听器是Servlet规范中的一员
- 在Servlet中,所有的监听器接口都是以“Listener”结尾
作用
- 特殊的时刻如果想执行这段代码,你需要想到使用对应的监听器
有哪些监听器
jakarta.servlet
包下:ServletContextListener
监听ServletContext的状态ServletContextAttributeListener
ServletRequestListener
ServletRequestAttributeListener
jakarta.servlet.http
包下:HttpSessionListener
HttpSessionAttributeListener
- 该监听器需要使用
@WebListener
注解进行标注 - 该监听器监听
session
域中数据的变化(添加、删除、替换)
- 该监听器需要使用
HttpSessionBindingListener
- 该监听器不需要使用
@WebListener
进行标注 - 假设
User
类实现了该监听器,那么User
对象在被放入session
的时候触发bind
事件,User
对象从session
中删除的时候,触发unbind
事件 - 假设
Customer
类没有实现该监听器,那么Customer
对象放入session
或者从session
删除的时候,不会触发bind
和unbind
事件
- 该监听器不需要使用
HttpSessionIdListener
session
的id
发生改变的时候,监听器被调用
HttpSessionActivationListener
- 监听
session
对象的钝化和活化 - 钝化:
session
对象从内存存储到硬盘文件 - 活化:从硬盘文件把
session
恢复到内存
- 监听
实现步骤
编写一个类实现ServletContextListener接口,并实现方法
void contextInitialized(ServletContextEvent event) void contextDestroyed(ServletContextEvent event)
在web.xml文件中对ServletContextListener进行配置
<listener> <listener-class>top.zhengru.javaweb.listener.MyServletContextListener</listener-class> </listener>
完结
完结撒花…