JavaWeb学习笔记
一.Servlet入门
1.HelloServlet项目
1).创建一个Servlet父项目的步骤
在此处为了方便我们之后其余学习项目的创建,我们将创建一个父项目,这个父项目中导入servlet所需的jar包。
之后我们将在这个父项目中创建诸多子项目。
这么做的优点便是:
1.在之后创建新的子项目时,我们不必每次都new一个Project。只需要在对应的父项目中new一个moudle即可。
2.在之后创建的子项目中,我们不需要每次都在pom文件中添加父项目中已有的依赖。
3.可以方便我们日后对学习笔记的整理。
可能这么做不利于新手对项目创建的理解,此点见仁见智。
1.创建一个普通的Maven文件
2.删除其中的src目录
3.在pom.xml
中导入java.servlet以及java.servlet.jsp的依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
2).关于Maven父子项目
1.在创建完成父子项目之后
父项目会有:
<modules>
<module>servlet-01</module>
</modules>
子项目会有:
<parent>
<artifactId>javaweb-02-servlet</artifactId>
<groupId>com.kuang</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
父项目的jar文件子项目可以直接使用
父子项目之间的关系类似于java中的继承关系:
son extends father
注意:web.xml文件中的内容需要换成新的(在旧版本的IDEA中生成的web.xml可能不同,尽量修改为下面内容):
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
</web-app>
3).编写一个servlet项目
编写一个Servlet程序需要两个步骤:
- 编写一个普通类
- 实现servlet接口,我们这里继承的是HttpServlet
Tips:Servlet接口在sun公司有两个实现类:HttpServlet
和GenericServlet
我们在点开HttpServlet的源码时可以发现其实HttpServlet是作为一个抽象类
继承
的GebericServlet
之后在我们点开GenericServlet的源码之后,我们可以发现GenericServlet是作为一个抽象类
实现
的Servlet接口
.
由此我们可以知道,在我们编写一个类并且将它继承Httpservlet时,我们可以得到以下关系:
我们在Servlet接口
可以看到一个名为service
的方法
之后我们在GenericServlet
类中可以发现GenericServlet
并没有将service方法
进行实现。
最后,在HttpServlet
中我们可以发现,HttpServlet
是对service方法
进行了实现。实现的代码如下:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{
method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
此段代码的功能为获取request(请求)中的method,对其进行分析,针对不同的method调用不同的方法。其中我们需要重点掌握的便是Get
以及Post
方法。
结合以上对于Servlet接口以及其相关的HttpServlet和GenericServlet的解析。我们就可以知道在我们创建一个Servlet项目,想要Servlet在面对不同请求完成我们所需要的功能时,我们只需要继承
HttpServlet的类,并且将所需要用到的请求方法进行重写
(@override)即可。
我们现在要创建一个最简单的Servlet项目,其只需要输出"Hello,Servlet!"即可,所以我们创建HelloServlet类:
package com.kuang;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();//输出流 response用于响应所以有输出流
// ServletInputStream inputStream = req.getInputStream(); 输入流 request用于请求,所以有输入流
writer.print("Hello,Servlet!");
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
这个类中的doGet
方法的流程步骤便是:获取response的输出流–将输出内容设置为"Hello,Servlet"
在我们编写好HttpServlet之后,我们需要给这个类添加一个Servlet映射
。
为什么要添加映射:我们编写是一个JAVA类,而且这个JAVA类最后是要给浏览器访问的,那么浏览器在访问这个JAVA类时,需要通过web服务器。当我们在web服务器中将这个Servlet注册好以后,浏览器才可以通过web服务器得知这个Servlet的具体地址以此来访问。
Servlet映射的添加方法:
在web.xml
中添加映射即可:
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
<!-- 注册Servlet-->
<servlet>
<!-- servlet名字-->
<servlet-name>Hello</servlet-name>
<!-- 此servlet对应的servlet类-->
<servlet-class>com.kuang.HelloServlet</servlet-class>
</servlet>
<!-- 注册Servlet mapping-->
<servlet-mapping>
<!-- servlet名字-->
<servlet-name>Hello</servlet-name>
<!-- 此servlet对应的访问路径-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
我们可以看出,添加一个Servlet映射是需要在web.xml中配置两个标签:<servlet>
和<servlet-mapping>
。
其中我们在<servlet>
中设置的是这个Servlet的名字以及它对应的JAVA类。
在<servlet-mapping>
中设置的是这个Servlet的名字以及它对应的访问路径。
我们可以这么理解,<servlet>
中我们是‘告诉’web服务器有一个名字为hello的servlet,以及这个名字为hello的servlet对应的java类。在<servlet-mapping>
中我们‘告诉’了web服务器这个名字为hello的servlet对应的访问路径。
简单来说,<servlet>
是注册一个servlet,<servlet-mapping>
是设置一个servlet的访问路径。
之后我们配置好Tomcat启动之后,访问tomcat对应设置的url+'/hello’即可成功运行HelloServlet项目。
2.Servlet原理
Servlet是由web服务器
调用的,web服务器在接收到浏览器请求之后,会进行以下操作:
浏览器向web容器发送Http请求
。web容器在收到请求之后,如果这个请求时第一次访问的话,web容器会生成一个servlet(只有首次访问才会产生)。web容器会产生一个请求(request)
和一个响应(response)
(请求有请求头
和请求体
,响应也有响应头
和响应体
)。Servlet接口中的service方法便是用来处理请求(Request)
,并且将请求处理的结果封装到响应(Response)
中。之后将响应放到web容器中。
大致流程如下图所示:
我们可以在此做一个总结:
1.请求(request)
中封装的是浏览器向web容器发送来的请求数据。响应(response)
是用来封装Servlet.service()
方法处理请求之后的所产生的结果数据。
2.Servlet.service()
方法的作用其实就是获取request中的数据,根据request中的数据做出处理,并且将处理之后的结果封装到response中,之后将response返回给web容器,web容器读取到响应的信息,之后web容器将数据响应给浏览器。
3.Servlet.service()方法是由我们(web开发人员)
来进行重写的。
3.Mapping问题(< servlet-mapping >)
在web.xml中我们添加了一个<servlet-mapping>
标签:
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
其中的<url-pattern>/hello</url-pattern>
是对对应servlet的映射路径进行设置。
servlet指定映射规则:
1.一个servlet可以指定一个映射路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
2.一个servlet可以指定多个映射路径
<!-- 我们可以将hello这个servlet指定hello1 hello2...等多个映射路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello1</url-pattern>
</servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
3.一个servlet可以指定通用映射路径
<!-- 比如我们可以使用通配符'*',来让hello这个servlet指定在hello路径下的任意请求-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello/*</url-pattern>
<!-- '*'为通配符,可以匹配所有路径-->
</servlet-mapping>
值得注意的是,当我们在将一个servlet的映射设置为'/*'
,即:
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
此时当我们访问项目路径时,我们访问到的是配置的servlet,并不会访问到web项目中的index.jsp
。可以理解为,我们设置映射路径为/*
的servlet将index.jsp
的映射路径给顶替了。也可以理解为我们配置的servlet优先级高于默认的index。
4.一个servlet可以指定有前缀或者后缀的映射路径
映射路径后缀设置:
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>*.hhh</url-pattern>
</servlet-mapping>
注意:后缀前面不可添加路径。可以理解为**添加了后缀映射之后,只要是以此后缀结尾的请求路径都会映射到配置这个Servlet。**比如我们在配置了*.hhh
之后,我们访问的路径为locahost:8080/servlet01/jskaldjaskld/123.hhh
,那么此路径依然映射的是我们配置的servlet。
4.<servlet-mapping>
优先级问题
指定了固定的映射路径的优先级最高,如果找不到对应的固定优先级配置路径才会走带有通配符的映射路径。
应用案例:改变404页面的显示
我们可以根据<servlet-mapping>
优先级规则,来设置servlet的映射,使得我们在访问路径错误找不到页面时使路径映射到我们自己编写的servlet。
操作步骤:
- 编写一个ErrorServlet类,此类便是URL路径错误时映射的Servlet
package com.kuang;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置resp响应的输出格式以及字符编码
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
// 设置resp的输出内容
resp.getWriter().print("<h1>自己编写的404页面</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
- 给ErrorServlet类编写Servlet映射路径
<servlet>
<servlet-name>ErrorPage</servlet-name>
<servlet-class>com.kuang.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ErrorPage</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
我们可以看到,此案例中我们就是运用的<servlet-mapping>
的优先级,在我们输入一个无法匹配到的路径时,我们就使用’/*'映射到所有路径来对无法映射的路径进行一个“拦截”。
效果:
当我们输入错误的路径时:
当我们访问HelloServlet时:
我们可以看到,有映射的路径并没有收到影响。
5.Servlet类的学习
创建一个新的子项目,在子项目中新建一个"HelloServlet"类,并且配置对应的映射。
代码如下:
HelloServlt.java
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.print("Hello");
resp.getWriter().print("Hello,Servlet");
}
@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="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.kuang.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
项目结构如下:
1)对HelloServlet中继承方法解析
之后我们便可以在HelloServlet
类中使用this.XXX
来查看HelloServlet都继承了那些方法,以及这些方法的用处。
首先我们可以先确定do…()的方法,这些方法都是Servlet.service中根据请求方法来选择运行的方法,具体有以下几个:
// 这些都是service方法中根据req请求方法而选择运行的方法
this.doPost();
this.doGet();
this.doDelete();
this.doHead();
this.doOptions();
this.doPut();
this.doTrace();
这些方法我们会在之后的学习中使用到。以后使用到时可以详细的学习。
之后我们可以来看看get开头的方法。这些方法我们可以根据名字轻松的看出来它们获取的信息。这些方法如下:
this.getInitParameter();
this.getLastModified();
this.getServletConfig();
this.getServletInfo();
this.getServletName();
this.getInitParameterNames();
this.getServletContext();
this.getClass();
2)this.getInitParameter() 方法
其中我们需要了解this.getInitParameter()
方法。根据方法名我们不能看出它是获取servlet初始化参数的方法,那么servlet初始化参数是什么?它又是怎么设置的呢?
我们在web.xml
中的<servlet>
标签中,我们发现它有一个<init-paramter>
,我们可以在这个标签中设置初始化参数的名称以及值。
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.kuang.servlet.HelloServlet</servlet-class>
<!-- 设置此servlet的初始化参数-->
<init-param>
<!-- 初始化参数名称-->
<param-name>MyName</param-name>
<!-- 初始化参数的值-->
<param-value>BugMaker2000</param-value>
</init-param>
</servlet>
如上我们设置了一个名称是"MyName",值是"BugMaker2000"的初始化参数。
现在我们在HelloServlet中进行测试。
在HelloServlet
中添加如下代码:
resp.getWriter().println("MyName : " + this.getInitParameter("MyName"));
之后我们访问’…/hello’,结果如下:
初始化参数被成功获取。
其余的方法有:
this.clone();
this.equals();
this.hashCode();
this.toString();
this.destroy();
this.init();
this.log();
...
这些方法我们可以根据字面意思快速了解其含义,在此就不一一赘述。感兴趣的小伙伴可以自己去阅读源码。
6.ServletContext对象
在刚刚我们对this.XX方法的分析中,我们不难发现其中有一个this.getServletContext()
的方法。
看到本章节的标题,想必大家也能猜到,这个ServletContext
便是本章的主角。
那么我们不禁想问:什么是ServletContext?它有什么作用?
1)什么是ServletContext?
ServletContext是一个全局的储存信息的空间,服务器开始就存在,服务器关闭才释放。
推荐阅读本篇博客:ServletContext详解
我们可以做一个总结:ServletContext
是一个可以被所有客户端访问、被所有servlet共享的一个公共空间。web容器
启动时,会为每个web应用
创建一个ServletContext
。
重点是共享数据以及与web应用在web容器中存在时间相同。
每个web应用有且仅有一个ServletContext
.
2)ServletContext的作用
由于ServletContext
作为一个共享的空间,所以servlet
之间可以通过ServletContext
通讯。
使用ServletContext
的案例有:公共聊天室。
3)ServletContext使用案例
在本案例中,我们会对ServletContext
的共享数据功能做出一个基本的使用。
测试方法:
- 创建一个SetServletContext类,此类的作用是向
ServletContext
中放入一条数据。 - 创建一个GetServletContext类,此类的作用是向
ServletContext
中获取SerServlet放入的数据。 - 配置两个类的映射
- 启动服务器测试。
1.创建SetServletContext类。
package com.kuang.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SetServletContext extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 向ServletContext中放入一条数据
String message = "我的名字是BugMaker2000";
ServletContext servletContext = this.getServletContext();
servletContext.setAttribute("Message",message);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
2.创建GetServletContext类。
package com.kuang.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class GetServletContext extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取servletContext中的数据
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
// 这两句话可以用 resp.setContentType("text/html;charset=utf-8"); 代替
ServletContext servletContext = this.getServletContext();
PrintWriter writer = resp.getWriter();
writer.print("信息 : " + servletContext.getAttribute("Message"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
3.设置两个Servlet的映射。
<servlet>
<servlet-name>setServletContext</servlet-name>
<servlet-class>com.kuang.servlet.SetServletContext</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>setServletContext</servlet-name>
<url-pattern>/setServletContext</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>getServletContext</servlet-name>
<servlet-class>com.kuang.servlet.GetServletContext</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getServletContext</servlet-name>
<url-pattern>/getServletContext</url-pattern>
</servlet-mapping>
4.测试结果:
我们先访问’/setServletContext’
此时ServletContext
中已经被插入了我们设置好的数据。
之后我们再访问’/getServletContext’:
我们可以看到数据别成功获取并且输出。
小知识:
在GetServletContext中,我们添加了两行代码:
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
这两行的代码的作用是:设置响应输出的内容类型以及设置响应的编码格式。如果我们响应中输出的内容带有中文,那么resp.setCharacterEncoding("utf-8");
此行代码必不可缺,否则页面输出的中文将会是乱码。resp.setContentType("text/html");
这行代码的作用是设置响应输出的文本内容的类型,此处设置的参数为text/html
是的页面响应输出的内容中可以带有HTML
格式,并且可以被页面识别。
此外,这两行代码可以用一行代码代替:
resp.setContentType("text/html;charset=utf-8");
4)getInitParameter()方法
此方法和上一章我们遇到的Serlvet.getInitParameter
相似,都是获取初始化参数。不同的是,Serlvet.getInitParameter
方法获取的是使用此方法的servlet的初始化参数,配置设置在<servlet>
标签中;而SerlvetContext.getInitParameter
获取的是整个web应用的初始化参数,配置设置在web.xml
中并且不在别的标签里面。
其实ServletContext
的初始化参数相当于web应用
的初始化参数。
接下来为了方便我们对于ServletContext.getInitParamter
方法的理解,我们做一个小小的案例。
在此案例中我们需要完成以下步骤:
- 在
web.xml
中设置ServletContext
的初始化参数。 - 编写一个
servlet
类,使用它获取上一步设置的初始化参数。 - 启动web应用,查看servlet类获取的初始化参数。
1.在web.xml中设置SerlvetContext的初始化参数。
<context-param>
<param-name>InitParamterTest</param-name>
<param-value>This is a test for ServetContext.getInitParameter !</param-value>
</context-param>
2.编写一个获取ServletContext初始化参数的servlet类,给它配置映射路径。
package com.kuang.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class ServletContextGetInitParameterTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
// 获取初始化参数
String message = servletContext.getInitParameter("InitParamterTest");
// 将获取的初始化参数输出
PrintWriter writer = resp.getWriter();
writer.println("IninParameter test : " + message);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
配置映射路径:
<servlet>
<servlet-name>initParameterTest</servlet-name>
<servlet-class>com.kuang.servlet.ServletContextGetInitParameterTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>initParameterTest</servlet-name>
<url-pattern>/initParameterTest</url-pattern>
</servlet-mapping>
(PS:配置映射路径的步骤就不在此处展示了。)
3.测试结果。
5)getRequestDispatcher()方法
我们只需要知道ServletContext中有请求转发的功能。其相关的知识我们将会在后面的学习中与重定向一同学习。
6)ServletContext读取资源文件
Properties
类介绍
1.什么是Properties
类?
Properties类是用于处理属性文件的类(一般来说,属性文件是使用键值对保存一些程序运行所需要的配置数据,例如jdbc的url等。这类文件的后缀一般是.properties
)。Properties类继承自Hashtable
类,在Hashtable
类的基础之上增加了一些与配置文件相关的方法。
2.Properties类中常用的方法
Properties
类中常用的方法有:load()
,getProperty()
,setProperty
,store()
load()
:加载properties文件。一般使用流
的方式来加载。
getProperty()
:根据参数获取Property类中对应的数据信息。
setProperty()
:根据参数设置Property类中的信息。
store()
:将Property中的信息存储会properties文件之中。
3.使用ServletContex获取properties文件的方法
- 创建一个Properties文件
username = BugMaker2000
password = 123456
- 创建一个用于读取配置文件的
Serlvet
,使用ServletContext
读取上一步设置的配置文件(作为流读取)
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Properties;
import java.util.Set;
public class PropertiesTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.使用Servlet读取配置文件
InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/Test.properties");
// 2.创建一个Properties类,并且使用load()方法加载获取的输入流
Properties prop = new Properties();
prop.load(is);
// 3.读取Properties中的数据
String username = prop.getProperty("username");
String password = prop.getProperty("password");
// 4.使用Servlet输出
PrintWriter writer = resp.getWriter();
writer.print("username : " + username + " password : " + password);
// 测试properties的修改功能
// 1.再properties中设置属性
prop.setProperty("url","www.baidu.com");
// 2.创建文件的输出流(输出流要使用绝对路径)
FileOutputStream fos = new FileOutputStream("D:\\IDEAWorkPlace\\javaweb-02-servlet\\servlet-02\\src\\main\\resources\\Test.properties");
// 3.使用store文件对数据进行保存
prop.store(fos,"文件修改");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
映射路径的操作在此省略。
- 启动服务器,查看读取的内容。
properties读取结果:
properties修改结果:
小知识:
在我们使用servlet读取数据时,我们使用的文件路径是:/WEB-INF/classes/Test.properties
。这个路径是根据文件生成的target
文件中的路径获取的。
其中我们可以看到’servlet-02’这个目录,在web应用中我们文件的目录便是从这个目录开始的。其中’class’目录我们称为classpath
目录,我们在’java’以及’resource’目录下的创建的目录会生成在本目录之下。在没有进行其余操作时,我们是不可以在除了resource目录之外的地方创建.properties文件的(因为maven的特性:约定大于配置)。
如果需要在别的地方生成.properties并且被导出的话,我们需要在pom文件的<build>
标签中添加下面标签:
<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>
这个是固定格式。
在我们添加了<resources>
标签之后我们便可以在别的目录之下创建.properties文件,并且可以被导出了。
7.HttpServletResponse
在web服务器
接收到客户端的http请求之后,针对于这个请求,web服务器
会创建两个类:HttpServletRequest
以及HtppServletResponse
,其中HttpServletRequest
来代表请求,HttpServletResponse
用来代表响应。
对于这两个类我们要记住:
- 如果我们想要获取客户端传来的请求参数,我们需要找的类是
HttpServletRequest
。 - 如果我们想要给客户端响应信息,我们需要找的类是
HttpServletResponse
。
对于HttpServletResponse
类,我们习惯于称之为Response,也即响应类。
我们学习一个类最直接有效的方法就是先看他的代码,查看其中具体都有哪些方法,搞明白这些方法的功能都是什么。
首先我们看HttpServletResponse
类,我们可以发现它继承的是ServletResponse
接口。
我们可以发现,这些方法大多都是get、set以及add方法。那么我们可以将这些方法做一个简单的归类。
1)负责向服务器发送数据的方法
// 只可以输出字符流数据,即只可以输出字符串
PrintWriter getWriter() throws IOException;
// 可以输出字符流数据和字节流数据,即字符串和二进制字节流
ServletOutputStream getOutputStream() throws IOException;
这两个类都是用于获取向服务器输出数据的输出流。
我们需要注意的是:
1.getWriter()
方法用于输出字符流数据,getOutputStream()
方法用于输出字节流数据。
在这里和大家解释一下什么是字节流数据和字符流数据。
字节流数据其实就是二进制数据,它是以字节为单位,即在很多类中都有的getBytes()
方法便是将此类转换为字节流。
字符流数据其实就是我们常用的字符串,它是以字符为单位,例如我们经常使用的String
类。
为了更好地理解这两个类,我们可以编写一个案例。
测试getWriter()
的Servlet:
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
public class WriterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 由于我们的message中有中文,为了防止乱码,所以要对ContentType进行设置
resp.setContentType("text/html;charset=UTF-8");
// 获取Writer输出流
PrintWriter writer = resp.getWriter();
String message = "你好,我是BugMaker2000";
// 将数据使用输出流输出
writer.write(message);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
测试getOutputStream()
的Servlet:
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class OutputStreamServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 对resp的ContextType设置
resp.setContentType("text/html;charset=UTF-8");
// 获取输出流
ServletOutputStream os = resp.getOutputStream();
String message = "大家好,我是BugMaker2000 ";
// 这里在测试时,我发现resp中设置的字符字符标准对outputStream无效
os.write(message.getBytes());
// 当我们需要outputsStream输出数据时候,我们需要在此设置字节流的标准
os.write(message.getBytes(StandardCharsets.UTF_8));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
测试结果:
getWriter()
的测试结果
getOutputStream()
的测试结果
通过本次案例我们可以更加直观的了解到两个输出流之间功能上的差别:Writer
更加注重字符的输出,OutputStream
更加注重对字节的输出。
在OutputStreamServlet
类中尽管我们在输出流输出之前对response的ContextType中的字符进行了设置,但是outputStream输出的字节流依然是乱码。只用我们在os.write()
中设置好字节流的编码格式输出的中文字符才不会变成乱码。
- 一个
serlvet
中的输出流只可以选择Writer
或者outputStream
两者其中一种。
在一个输出流创建完毕之后,它就会随着response发送到客户端,在客户端接收之后,服务器就会自动关闭response中没有关闭的输出流。
2)负责向浏览器发送响应头的方法
//这些是 SerlvetResponse的方法
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
//这些是HttpServletResponse的方法
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);
3)设置响应的状态码
void setStatus(int var1);
关于状态码,我们只需要记住:
200
表示响应成功3XX
表示请求重定向4XX
表示找不到资源5XX
表示服务器代码错误
4)Response常见的应用——下载文件
一般来说我们使用response应用为:向客户端输出消息,客户端下载文件。其实这两个应用的本质都是客户端通过response类获取服务器传来的数据。
我们此处详细讲解一下response完成下载文件的功能。
客户端下载文件,我们需要完成一下几步:
1.设置响应头,使得客户端可以接收下载文件
2.获取文件输入流
3.获取servlet的输出流
4.创建缓冲区,通过缓冲区将文件输出流写入输出流(返回给客户端)
(5.关闭流,我们十分提倡我们自己关闭输入/输出流)
完成的类:
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileDownServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.设置浏览器可以接收下载的文件
resp.setHeader("Content-Disposition","attachment;filename=1.png");
// 2.获取文件作为输入流
InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/1.jpeg");
// 3.获取输出流
ServletOutputStream os = resp.getOutputStream();
// 4.创建缓冲区,通过缓冲区将文件输入流写入响应输出
byte[] buffer = new byte[1024];
int len = 0;
while((len = is.read(buffer)) > 0){
// 将文件输出给客户端
os.write(buffer,0,len);
}
// 关闭流
is.close();
os.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
实现结果:
当我们运行下载文件的serlvet之后,浏览器就会新建下载任务
关于这个案例,我们需要知道这些知识点:
1.关于设置浏览器下载文件的语句
resp.setHeader("Content-Disposition","attachment;filename=1.png");
只有在我们设置了响应头之后,浏览器才会出现新建下载任务的窗口,否则浏览器只会展示图片。要注意其中的filename=1.png
,filename=
之后的内容是根据我们需要下载的文件名而改变的。
2.获取文件作为输入流
我们需要知道,获取文件作为输入流不仅仅可以通过ServletContext.getResourceAsStream
来完成。我们也可通过new一个FileInputStream
来完成。
FileInputStream fis = new FileInputStream(filepath);//参数是文件路径
3.关于文件输入流的输入方法inputStream.read()
我们把文件输入流通过缓冲区写入输出流的代码:
int len = 0;
while((len = is.read(buffer)) > 0){
// 将文件输出给客户端
os.write(buffer,0,len);
}
这段代码可以说是将文件输入流转换为输出流的固定格式,在服务器文件下载中经常使用,需要我们记住。
其中的is.read()
方法的作用是:如果参数是byte[]
,那么将从输入流中获取下一段最多长度是byte[].length
的字节(也有可能不够这么多),将其存放在byte[]
中,将实际读取的字节数作为int
类型返回,如果输入流之后没有字节,那么返回值为-1。
4.我们一定要养成在使用完流之后手动将它关闭的好习惯。
5.如果我们想要下载的文件名称是中文的话,我们下载的文件名会是乱码,这个时候需要我们将响应头的设置修改一下:
String filename = "中文名字.jpeg";
resp.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(filename,"UTF-8"));
需要注意的是,SerlvetContext.getResource()
中的路径是可以带有中文的,所以在将中文名称的文件作为文件流导入时直接将filename输入进去就可以了。
InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/" + filename);
而且我们需要知道,就算我们没有对response的响应头做处理中文乱码的设置,文件也是可以正常下载的,仅仅只是名字是乱码。
5)Response验证码案例实现。
本案例重点实现验证码的生成以及响应将验证码返回给客户端。
步骤如下:
1.在内存中创建一个图片,即创建一个图片类
2.生成一个具有N位的随机数
3.将随机数写入到图片之中
4.将图片返回给servlet
案例代码
package com.kuang.servlet;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
public class RandNumServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("refresh","3");
// 1.创建一个图片类
BufferedImage image = new BufferedImage(100,30,BufferedImage.TYPE_INT_RGB);//参数分别是 宽度 高度 以及图片类型
// 获取图片的笔,笔用来设置图片的内容,例如背景颜色等
Graphics2D g = (Graphics2D) image.getGraphics();
// 设置图片背景颜色
/*
* 设置背景颜色的方法是,先将笔设置一个颜色,然后将范围内的区域全都染成设置好的颜色。
* 我们在涂染区域时,设置区域的大小是图片大小,所以就将整个图片的背景设置成了对应的颜色
* */
g.setColor(Color.BLUE);
g.fillRect(0,0,100,30);
// 在图片中绘制生成的随机数
g.setColor(Color.WHITE);
g.setFont(new Font(null,Font.ITALIC,20));
g.drawString(createRandNum2(),5,25);//参数:要绘制的字符串,字符串左下角的坐标
// 告诉浏览器使用图片格式打开
resp.setContentType("image/jpeg");
// 不让浏览器缓存
resp.setDateHeader("expires",-1);
resp.setHeader("Cache-Control","no-cache");
resp.setHeader("Pragma","no-cache");
// 把图片写给浏览器
boolean write = ImageIO.write(image, "jpg",resp.getOutputStream());
}
// 生成随机数方法
private String createRandNum(){
/*
* 这个方法的原理是,根据我们想要生成随机数的位数,在这个范围内生成一个随机数,
* 但是考虑到生成的随机数可能不满足位数大小(例如我们生成的随机数可能是个位数,远远小于想要生成的位数),
* 所以我们在后面添加了一个for循环,用于创建一个缺少位数大小的String,在缺少的地方添加“0”作为补充。
* 这样,我们就生成了一个规定位数的随机数*/
Random random = new Random();
String r = random.nextInt(999999999) + "";
StringBuffer sb = new StringBuffer();
for (int i = 0;i < (8-r.length()) ; i++){
sb.append("0");
}
return sb + r;
}
private String createRandNum2(){
// 这个方法的思想是每次生成一个位的随机数,循环N次,将每次生成的随机数放在字符缓冲区中
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0 ; i < 8 ; i++){
sb.append(random.nextInt(10));
}
System.out.println("得到的随机数 :" + sb);
return sb.toString();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
我们需要注意:
1.创建随机数的方法最广为流传的是createRandNum()
,这个方法可以说是固定的。我们应该记住。
2.本案例中大部分代码都是固定的格式,而且在如今不需要我们掌握,大家做个了解就可以了。
6)response重定向
重定向:客户端向web资源A发送请求之后,web资源A通知客户端去访问web资源B,这个过程就是重定向。
我们需要注意的是:重定向过程中客户端向服务器发送了两次请求,所以重定向之后的请求路径会发生改变,第一次请求发送给web资源B,在收到了B返回的响应之后,得知需要访问web资源C,之后又向web资源C发送请求。
重定向常见的应用场景:用户登录等等。
为了更加直观的了解重定向,我们可以做一个简单的案例。
案例步骤:
- 创建一个请求转发的servlet,将请求转发给我们上一章做的验证码案例
- 启动服务器测试
请求转发案例代码:
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RedirectServlet extends HttpServlet {
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("radom");//我们如果输入的参数之前没有加‘/’,那么重定向的路径会是对应servlet路径
// 如果我们在输入参数之前加‘/’,那么我们重定向的路径是 localhost+输入的参数
// resp.sendRedirect("/servlet_03_war/radom");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
案例结果:
我们需要注意的是,resp.sendRedirect()
这个方法的参数可以是对应重定向servlet的相对于localhost的URL路径,也可以是对应servlet在项目内的URL路径,他们的区别是参数字符串是否以“/”开头。
重定向本质做的操作:
resp.setHeader("Location","/r/img");
resp.setStatus(HttpServletResponse.SC_ACCEPTED);
面试题:请聊聊重定向和请求转发的区别?
相同点:
- 页面都会实现跳转
不同点:
- 重定向的地址栏会发生变化,请求转发的地址栏不会发生变化
- 重定向是请求发送给web资源A后,客户端接受到了返回的响应,并且根据获得的响应去向web资源B发送响应,整个过程中发送了两次请求。
- 请求转发是客户端发送给web资源A,web资源A在接受到了请求之后,将请求转发给web资源B,web资源B处理完毕之后将响应返回给web资源A,web资源A在接受响应之后再将响应返回给客户端,整个过程中仅仅发送了一个请求。
重定向:
请求转发:
8.HttpServletRequest
HttpServletRequest
代表客户端的请求,用户使用HTTP协议访问服务器,其中的HTTP请求新消息会被封装到HttpServletRequest
中,所以通过HttpServletRequest
,我们可以获取客户端请求的信息。
请求主要有两个应用场景:获取前端传递的参数和请求转发。
1)获取前端传递的参数
我主要需要掌握两个方法:req.getParameter();
和req.getParameterValues();
req.getParameter();
req.getParameterValues();
我们主要就是靠req.getParameter()
来获取前端传来的参数,而getParameter()
和getParameterValues()
之间的区别就是一个是获取一个参数,一个是获取多个参数。
2)请求转发
关于请求转发,我们使用的语句是:
req.getRequestDispatcher("/success.jsp").forward(req,resp);
其中getRequestDispatcher()
中的参数是请求转发的路径。关于这个路径我们需要知道的是:请求转发的路径在加了"/"之后就默认为项目当前路径,这点和重定向不同。
为了方便我们理解Request的常用方法,我们接下来做一个案例。
在本次案例中,我们需要做一个简单的前端登录页面,此页面将用户填写的数据通过request传给servlet,我们在接受的servlet中获取前段传来的参数并且使用请求转发将路径转发到登录成功页面。
我们需要做:
- 编写一个简单的登录页面和登录成功页面。
- 编写一个serlvet获取request之中的参数,并且使用请求转发将请求转发登录成功页面。
登录页面:
<%@page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<h2>Hello World!</h2>
<form action="${pageContext.request.contextPath}/dispatcher" method="post">
Username : <input name="username" type="text"> <br>
Password : <input name="password" type="text"> <br>
爱好 : <input type="checkbox" name="hobbies" value="代码">代码
<input type="checkbox" name="hobbies" value="游戏">游戏
<input type="checkbox" name="hobbies" value="运动">运动
<input type="checkbox" name="hobbies" value="电影">电影
<br>
<input type="submit">
</form>
</body>
</html>
其中我们需要注意:<%@page contentType="text/html;charset=UTF-8" language="java" %>
这句话是将页面的内容格式设置为UTF-8,以此保证出现中文字符不会乱码。
登录成功页面:
<%--
Created by IntelliJ IDEA.
User: 10437
Date: 2023/9/25
Time: 20:35
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Success</title>
</head>
<body>
<h1>登录成功!!!</h1>
</body>
</html>
接受Servlet:
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
public class RequestDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.设置req和resp的编码类型
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
// 2.获取前端参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobbies = req.getParameterValues("hobbies");
// 3.输出获取的参数
System.out.println("username : " + username);
System.out.println("password : " + password);
System.out.println(Arrays.toString(hobbies));
// 4.请求转发
req.getRequestDispatcher("/success.jsp").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
我们需要注意在请求或者响应的数据中如果有中文字符的话,我们需要添加.setCharacterEncoding("utf-8")
来预防乱码。
在这个案例中,我们使用request获取username
和password
这两个单体的输入框类型的数据时,我们只需要使用getParameter()
即可。但是在我们获取hobbies
这种多个复选框数据时,就需要使用到getParameterValues()
来获取一个字符数组。
请求转发和重定向
那么在我们学习完请求转发以及重定向之后,我们再次总结一下请求转发以及重定向的区别:
相同点:请求转发和重定向都可以实现页面跳转。
不同点:
- 请求转发在页面跳转之后页面的URL不会改变;重定向在实现页面跳转之后页面URL会改变。造成这个原因是:请求转发实现页面跳转的方式是
客户端
访问的Serlvet A
将请求转发给另一个Servlet B
,B
在处理完转发过来的请求之后,将响应返回给A
,A
之后再把响应转发给客户端
,从始至终只有一个请求;重定向实现页面跳转的方式是客户端
在将请求发送给A
之后,A
将需要访问B
的响应返回给客户端
,客户端
在接受到请求之后,再发一份请求给B
,B
在接受请求并且处理完请求之后,将响应返回给客户端,从始至终有两个请求。 - 请求转发是通过
request
来实现的,重定向是通过response
来实现的。 - 请求转发访问页面的路径是从当前项目路径开始访问的,重定向访问页面路径是从localhost来开始访问的。
- 请求转发的代码是
307
,重定向是302
。
重定向:
请求转发:
9.Session和Cookie
1)会话
会话:用户点开浏览器,访问了多个web资源,之后关闭浏览器,这一整个过程我们称之为会话。也就是说,一次会话是指从用户打开浏览器到完成使用关闭浏览器。
会话分为有状态会话和无状态会话。
有状态会话:有状态Session则是一种将用户会话状态保存在持久化资源中的对象,例如数据库中。每次请求时,会从持久化资源中获取最新的会话状态,并将会话状态与客户端的Session对象进行关联。
无状态会话:无状态Session是一种不会持久保存用户会话状态的对象,每次请求结束都会重新创建一个Session对象,因此无状态Session的会话状态是临时的。
总而言之,有状态会话是指服务器会存储客户端的信息,以便下次客户端访问时识别。无状态会话不会存储客户端的信息。
2)保存会话的两种技术
保存会话主要有两种技术:cookie
和session
。保存会话的技术我们也称之为会话跟踪。(我们需要知道的是:Http是一种无状态的协议)
cookie
和session
的主要区别是:cookie
是被存放在客户端(即浏览器)中的,session
是存储在服务器之中的。
session和cookie常见的应用:我们在bilibili中登录了一次之后,在我们关闭了浏览器结束会话后,下次我们打开bilibili时,不需要再次登录。
3)关于Cookie
案例:通过cookie获取客户端上次开始会话的时间
为了方便我们的理解,我们接下来针对cookie做一个简单的案例
在此案例中我们将使用cookie完成客户端在访问服务器的时候,服务器将客户端上次访问的日期返回给客户端。
在此案例中,我们做的servlet需要完成的功能:
- 获取客户端传来的
cookie
- 如果客户端传来的
cookie
中有数据,那么获取cookie
中数据,将其输出;如果客户端传来的cookie
中没有数据,那么就提示用户是第一次访问。 - 将用户此次的访问时间封装到
cookie
当中 - 将
cookie
存放在response
之中返回给客户端
案例代码:
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CookieDemo01 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");
// 获取输出字符
PrintWriter out = resp.getWriter();
// 1.获取cookie
Cookie[] cookies = req.getCookies();
// 2.检查请求中的cookie中是否有数据
boolean hasCookie = false;
if (cookies != null){
for (Cookie cookie : cookies){
if ("loginTime".equals(cookie.getName())){
// cookie中如果有数据,那么就将数据输出
out.write("您上次访问的时间是:" + cookie.getValue());
hasCookie = true;
}
}
}
if (!hasCookie){
// cookie中没有数据那么就提示用户第一次访问
out.write("这是您第一次访问本网站!");
}
// 3.将用户此刻访问的时间田间到cookie之中
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd-HH:mm:ss");//需要注意cookie字符串中不可存放空格和一些特殊字符
String dateFormat = simpleDateFormat.format(date);
Cookie cookie1 = new Cookie("loginTime",dateFormat);
// 设置cookie的最大存在时间,注意单位是秒
cookie1.setMaxAge(24*60*60);
// 4.将cookie放入响应之中
resp.addCookie(cookie1);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
实现结果:
第一次访问:
非第一次访问:
在此次案例之中我们需要注意:cookie
中对它存放的字符有限制,其中的字符不可以有空格和方括号等一系列特殊字符。
从上面这个案例中我们可以知道cookie是客户端传给服务器的,在服务器处理完客户端请求之后,再由客户端传送给服务器。因此,在请求中只有获取cookie
的方法,在响应中只有设置cookie
的方法。
cookie是保存在客户端本地的。一般是保存在客户端本地的AppData 目录下。
关于cookie的一些需要知道的知识
- 一个
cookie
只能保存一个信息(只能有一对键值对) - 一个web站点可以向客户端发送多个
cookie
,一般来说客户端最多存放这个站点20个cookie
(这个根据浏览器来设置),当存放的cookie
超出浏览器限制,那么浏览器会将新的cookie
来替换掉最老的cookie
;有些浏览器会限制接受该站点的cookie
数量,一般是300个。 - Cookie的大小有限制,最大为4kb(也就是4096个字节)
关于删除cookie的方法
一般来说我们删除cookie的方法是创建一个和我们要删除的cookie同名的cookie,并且将它的生存周期设置为0,这么一来,新建的cookie就会替代我想要删除的cookie,而新建的cookie在刚替换完之后就会因为生存周期为0而被删除。
删除代码:
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CookieDemo02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 我们删除名字为loginTime的cookie
Cookie cookie = new Cookie("loginTime","");
cookie.setMaxAge(0);//设置生存周期为0
resp.addCookie(cookie);//将cookie添加到响应之中
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
4)关于Session
关于Session:服务器会给每个用户(浏览器)创建一个Session对象,一个Session独占一个会话,浏览器没有关闭(会话没有结束),Session就会一直存在。
Session
和Cookie
目前来说,Cookie
我们可以把它理解为一个标记,服务器给客户端Cookie
作为标记,方便以后的操作中服务器进行辨别。Session
我们可以理解为一个数据结构,或者一个数据载体,它可以承载本次会话中需要用到的数据。
一般来说,Cookie
中会带有一个Session ID
,方便服务器根据传来的Cookie
来找到对应的Session
,以此来更加方便的辨认出用户。
Session
和Cookie
的关系我们可以通过一个案例来理解:
我们可以用银行卡的例子来更加方便我们来理解。
我们可以把Cookie
理解为银行卡,把客户端
理解为客户,把服务器
理解为银行,把Session
理解为此张银行卡在银行中的金额等信息。
当用户带着银行卡到银行中时,银行才可以通过银行卡来了解到这个用户的一些信息(比如信用情况,可以取出多少金额);同理,当客户端带着Cookie
来访问服务器
的时候,服务器
才可以根据Cookie
来得知客户端
的在本网站的一些基本信息。
Session
中常用的方法
1.在Session
中存储以及读取数据
关于Session
中存放数据,我们可以清楚Session
中不仅可以存放字符串(String
)类型的数据,同时也可以存放类(一般为实体类)。
Session
中存放String
类型的代码:
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class SessionDemo01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 在此条Demo中我们将会获取Session并且在其中存贮一个字符串
// 1.解决乱码问题
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// 2.获取Session
HttpSession session = req.getSession();
// 3.在Session中写入信息
session.setAttribute("name","一个写Bug的");
// 4.获取SessionID
String id = session.getId();
// 5.判断Session是否是新生成的
if (session.isNew()){
resp.getWriter().write("这个Session是新生成的,ID : " + id);
}else{
resp.getWriter().write("这个Session不是新生成的,ID : " + id);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
在Session
中存放类:
新建一个实体类User
package com.kuang.pojo;
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public User() {
}
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 "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
在Session
中存放此类
package com.kuang.servlet;
import com.kuang.pojo.User;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class SessionDemo03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 在本Demo中,我们将在Session中存储一个实体类
// 1.解决乱码问题
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// 2.获取Session
HttpSession session = req.getSession();
// 3.创建实体类
User user = new User("一个写Bug的", 23);
// 4.将实体类存入Session
session.setAttribute("user",user);
// 5.提示用户已经完成
resp.getWriter().write("已经将user类存入Session");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
关于读取的Session
中的数据的方法也很简单,在我们获取Session
之后,只需要使用getAttribute
获取对应存储数据的名称即可。我们需要知道的是,Session之中存储的键值对类型是<String,Object>,也就是说在我们获取数据时,一般会同时将获取的数据进行一个类型转化。
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class SessionDemo02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 本条Demo中,我们将获取Session中的数据并且将其输出
// 1.解决乱码问题
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// 2.获取Session,并且获取Session里面的数据
HttpSession session = req.getSession();
String name = (String) session.getAttribute("name");
// 3.将获取的数据输出
resp.getWriter().write("获取到的数据为 : " + name);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
2.使Session失效
我们一般将Session
失效的方法用两种,一种是在代码中直接使用invalidate()
方法将Session失效,另外一种则是在web.xml
中设置Session
的过期时间。
使用invalidate()
方法:
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class SessionDemo05 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 用来使Session失效
// 1.获取Session
HttpSession session = req.getSession();
// 2.使Session中的数据失效
session.removeAttribute("name");
session.invalidate();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
在web.xml中设置Session的过期时间:
<session-config>
<!-- 以分钟来设置Session的失效时间,单位为分钟,也就是说此处设置Session会在15分钟之后失效-->
<session-timeout>15</session-timeout>
</session-config>
Session的使用场景
- 保存用户的登录信息
- 购物车信息
- 在网站中会经常被使用到的信息,我们一般也会把它存储在Session之中
5)总结
我们现在已经学习了ServletContext
、Cookie
以及Session
这三个在网络应用中常见的存放数据的技术。关于这三个技术之间的关系,我们可以用一张图直观的表述:
二.JSP学习
1.什么是JSP
JSP
的全称是Java Server Pages
,意为Java服务器端页面,和Servlet
一样,用于开发动态Web技术。
其实JSP
的语法和HTML
非常相似,标签也大多都一样,但是JSP中可以嵌入JAVA代码并且也可以为用户提供动态的数据。
2.JSP原理
首先我们需要知道的是:不管客户端向服务器发送请求,不管是访问的是什么资源,其实都是在访问Servlet!
所以JSP也会被转换成一个Java类。
我们可以在项目的文件中可以找到JSP
转换之后的Java类。(一般在....\Catalina\localhost\项目名\org\apache\jsp
目录之下)
我们之前所说,客户端给服务器发送请求,访问到的资源都是Servlet,在我们打开index_jsp.java
之后,却发现这个Java类并没有直观的继承Servlet相关的类:
但是如果我们继续查看它继承的org.apache.jasper.runtime.HttpJspBase
类,我们可以发现,HttpJspBase
类是继承的HttpServlet
类,也就是说其实JSP转换之后的Java类也是Servlet类
。
(需要知道的是,我们在IDEA中不能直接访问HttpJspBase
的代码,需要首先导入对应的依赖:
<!-- https://mvnrepository.com/artifact/tomcat/jasper-runtime -->
<dependency>
<groupId>tomcat</groupId>
<artifactId>jasper-runtime</artifactId>
<version>5.5.23</version>
</dependency>
)
那么接下来我们继续看index_jsp.java
类之中的代码,其中有三个方法需要我们来注意一下:
// jsp初始化
public void _jspInit() {
}
// jsp销毁
public void _jspDestroy() {
}
// jsp服务
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException
其中的_jspService我们可以简化一下:
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException
我们可以发现,在把参数的包名简化之后,这其实就是Servlet中的service
方法。
这个方法中主要实现以下功能:
- 判断请求类型(JSP页面请求只允许GET、POST、HEAD、METHOD这四种请求类型)
- 设置内置对象(JSP页面中有九大内置对象)
final javax.servlet.jsp.PageContext 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 当前页面对象
HttpServletReqeust request; //请求对象
HttpServletResponse response; //响应对象
- 为输出页面内容做准备
response.setContentType("text/html");
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;
这些代码的功能是获取各个内置对象的内容,为接下来输出页面内容做准备
- 输出页面内容
out.write("<html>\n");
out.write("<body>\n");
out.write("<h2>Hello World!</h2>\n");
out.write("</body>\n");
out.write("</ht。ml>\n");
我们在JSP中编写的HTML代码,都会放到out.write()
中。(需要注意的是,我们在JSP页面中编写的JAVA代码是原封不动的编译过来的)
其实客户端访问JSP的流程我们可以用一张图来很直观的描写出来:
在本章中我们将学习JSP的一些基础语法。
在我们正式开始之前,我们需要先在原来的基础之上再导入两个依赖:
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl-api -->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/taglibs/standard -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
其中JSTL
全称为Java Server pages Standarded Tag Library
,意为JSP标准标签库,主要是给我提供一个标准通用的标签库。其中包含了一系列的标签和函数。至于后面的taglibs依赖一般是和JSTL依赖一起导入,Taglibs Standard是一个标准的JSP标签集合,其中包含了一系列常用的JSP标签。
这两个标签我们做一个了解即可。
3.JSP中的基础语法
JSP表达式
JSP表达式的基本语法
<%= 变量或者表达式%>
作用:在JSP页面上输出数据
常见用法:
1.直接输出字符串
<%--1.输出字符串--%>
<%= "直接通过JSP表达式输出的对象"%>
JSP转换结果:
out.print( "直接通过JSP表达式输出的对象");
2.输出对象
<%--2.输出字符串对象--%>
<%= new String("先创建String类型后,输出此字符串")%>
<br>
<%--3.获取前端数据输出--%>
获取request中的username:
<%= request.getAttribute("username")%>
<br>
JSP转换结果:
out.print( new String("先创建String类型后,输出此字符串"));
out.write("\r\n");
out.write("<br>\r\n");
out.write("\r\n");
out.write("获取request中的username:\r\n");
out.print( request.getAttribute("username"));
out.write("\r\n");
3.通过表达式输出对象
<%--4.表达式输出--%>
根据username的值来输出不同的内容:
<%= "123".equals(request.getAttribute("username")) ? "用户名正确" : "用户名错误" %>
JSP转换结果:
out.write("根据username的值来输出不同的内容:\r\n");
out.print( "123".equals(request.getAttribute("username")) ? "用户名正确" : "用户名错误" );
我在此编写了一个简单的Servlet,其主要的功能是在request中添加参数,并且请求转发给JSP页面。
Servlet代码:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 此Servlet主要是在req中添加username的值来传递给JSP页面
req.setAttribute("username","123");
// 使用请求转发来访问页面
req.getRequestDispatcher("/JSTLLearn.jsp").forward(req,resp);
}
测试结果:
JSP表达式对Java代码的转换是使用out.print()
直接输出,而对于字符串是使用out.write("")
作为字符串输出。
JSP脚本片段(不能编写全局变量和方法)
JSP脚本片段的基本语法
<% Java代码 %>
我们可以直接在脚本片段中填写Java语言,这段语言可以在页面中被执行。在这段java代码中我们可以声明变量、调用方法、进行表达式运算。
我们可以写一段简单的JSP脚本片段来理解:
<%-- JSP脚本片段 --%>
<%
int x = 0;//1.声明变量
for (x = 0;x < 20 ; x++ ){
if (x % 2 == 0){
//2.进行表达式运算
x +=x;
}
}
out.write("x :" + x); //3.调用方法
%>
运行结果:
JSP转换结果:
int x = 0;//声明变量
for (x = 0;x < 20 ; x++ ){
if (x % 2 == 0){
//进行表达式运算
x +=x;
}
}
out.write("x :" + x); //调用方法
我们可以看到其中的代码在转换时没有任何改变。
JSP声明(可以编写全局变量和方法)
语法:
<%! 全局变量和方法%>
之前我们使用<% %>
在JSP页面中嵌入Java代码,来完成一些HTML很难完成的逻辑运算,但是我们发现其中不能编写方法,而且我们编写的代码都只会被转换到_jspService()
方法之中,对我们的使用还时有些不便。因此我们可以使用<%! %>
来在JSP页面中嵌入全局变量和方法(以及包含静态的代码块),我们称之为JSP声明。
比如:
<%!
public String name = "BugMaker2000";
static {
System.out.println("!!!");
}
public void fun(){
System.out.println("这是通过JSP定义的方法");
}
%>
JSP转换结果:
public String name = "BugMaker2000";
static {
System.out.println("!!!");
}
public void fun(){
System.out.println("这是通过JSP定义的方法");
}
目前为止我们学习了JSP中的三个基础语法:表达式<%= %>
,脚本片段<% %>
,声明<%! %>
。通过这三个语法我们可以在JSP中编写各样的Java代码,以方便我们的动态页面编写。
4.JSP指令
JSP指令
一般用于设置整个JSP页面的属性。
JSP指令格式:
<%@ %>
此处仅介绍两个使用频率比较高的JSP指令
,其余指令我们会在日后学习。
自定义异常页面
语法:
<%@page errorPage="自定义页面目录" %>
我们首先编写一个简单的页面:
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%@page errorPage="error/500.jsp" %>
<html>
<body>
<h2>Hello World!</h2>
<%;
int i = 1/0;
%>
</body>
</html>
很明显,页面中的int i = 1/0
这句程序是错误的,我们运行时会触发500(服务器程序错误)
的异常。此外,我们也可以使用JSP指令设置如果页面错误时显示的页面。
我们只需要添加<%@page errorPage="error/500.jsp" %>
,这个指令便是设置如果页面错误的话,错误页面是**error目录下的500.jsp页面。**而error目录下的500.jsp页面便是我们自己编写的页面,我们可以任意编辑:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>500错误页面</title>
</head>
<body>
<h1>服务器代码错误...</h1>
</body>
</html>
那么我们现在尝试访问之前的错误页面就可以得到以下结果:
我们可以看到页面自动跳转到了我们设置的错误页面了。
那么以上就是针对一个页面的错误页面自定义显示,如果我们需要修改整个服务器显示的错误页面,使用这个方法就需要在每个页面中添加这句话,这样就非常不利于日后的维护和管理,针对这个情况更好的办法便是在web.xml
中添加错误异常页面的设置。
在web.xml
中添加:
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
这个便是设置整个浏览器中遇到错误异常时显示的页面,此处为了和上面异常区分开来,我们设置404
页面无法访问时的异常。
我们编写的404.jsp
页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>404</title>
</head>
<body>
<h1>404</h1>
<hr>
<h2>您访问的页面没有找到...</h2>
</body>
</html>
测试结果:
通用页面组件设置
语法:
<%@ include file="模块页面目录" %>
在我们访问的大部分网站中,页面的导航部分和底部部分都是一样,也就是说我们可以把导航和底部作为组件,给网站的每个页面使用。
我们先编写头部以及底部页面的模块:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>
页面Header通用模块
</h1>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>页面底部组件</h1>
编写一个页面引用模块:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--引用页面头部模块--%>
<%@ include file="common/Header.jsp" %>
<h1>页面主题</h1><%-- 页面内容--%>
<%--引用页面底部模块--%>
<%@include file="common/Bottomer.jsp"%>
</body>
</html>
结果:
另外除了这个方法,我们也可以使用JSP标签
来实现这个功能。
语法:
<jsp:include page="引用页面目录">
我们可以将上述代码修改为:
<jsp:include page="common/Header.jsp"/><%--引用页面头部模块--%>
<h1>页面主题</h1><%-- 页面内容--%>
<jsp:include page="common/Bottomer.jsp"/><%--引用页面头部模块--%>
结果:
我们可以看到,<@page include="">
和<jsp:include page="">
实现出来的效果是一致的,但是实现的方法不同。
<@page include="">
是将引用的页面组合在一起合并成为一个页面,本质是一个页面,其被JSP转换之后的结果是:
out.write("<h1>\r\n");
out.write(" 页面Header通用模块\r\n");
out.write("</h1>\r\n");
out.write("\r\n");
out.write("<h1>页面主题</h1>");
out.write('\r');
out.write('\n');
out.write('\r');
out.write('\n');
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("<h1>页面底部组件</h1>");
<jsp:include page="">
是直接将页面拼接,本质上还是三个页面,JSP转换的结果:
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "common/Header.jsp", out, false);
out.write("\r\n");
out.write("<h1>页面主题</h1>");
out.write('\r');
out.write('\n');
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "common/Bottomer.jsp", out, false);
我们可以看到<@page include="">
直接将页面的代码拼接过来而<jsp:include page="">
则是将页面通过include()
使用。
5.九大内置对象
在我们之前查看JSP页面转换的Java类中,我们曾看到过其中设置的八个个内置对象
final javax.servlet.jsp.PageContext 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 当前页面对象
HttpServletReqeust request; //请求对象
HttpServletResponse response; //响应对象
//还有一个Exception对象
由此我们可以罗列出九大内置对象:
1.PageContext : 用于获取页面相关的信息,比如使用getAttribute()获取页面范围内的属性值,或者使用getOut()
方法获取JspWriter
对象。
2.Reqeust : 用于存储用户传来的请求参数以及响应头信息和请求属性。例如可以使用getRequestParameter()
方法获取请求参数的值,或者使用getAttribute()方法获取请求属性的值。
3.Response : 用于响应客户端到的请求。一般用来输出响应的正文以及响应头等。一般是使用write方法将字符串写入响应的正文之中。
4.Session : 用于存储一次会话中需要使用到的数据。可以使用getAttribute()方法获取session中的属性值。
5.Config : 提供了一个接口允许获取JSP页面的配置信息。
6.Application(ServletContext) : 存放整个网络应用中需要使用到的数据。可以使用setAttribute方法设置application中的属性值,也可以使用getAttribute()方法获取application中的属性值。
7.out : 是一个JspWriter
类型的变量,向客户端发送消息。其中最常见的用法是在将JSP页面转换为Java类时,jsp页面中的java脚本片段会通过 out.write()
或者out.print()
转换到Java类中。
8.page : 表示当前正在访问的JSP页面。可以通过此方法获取页面的其他方法和属性。
9.exception : 用于捕获JSP页面中的异常。
我们可以发现,在这几个内中对象中,有几个对象的作用是存放数据,并且都会使用getAttribute()方法来获取对象中存放的数据,我们把这几个对象称之为作用域。JSP中有四大作用域:pageContext、request、session和application(ServletContext)。
三.JavaWeb基础知识学习
1.javabean
JavaBean
也可以称之为实体类。它是一种遵顼特定约束的java类。
一个合格的JavaBean需要满足一下三个条件:
- 该类是具体的
- 该类必须有无参构造方法
- 该类的属性必须是私有的且属性都是封装的(有get和set方法)
JavaBean一般用于和数据库中的字段作为映射。它们的关系一般为:
- 一个表 -> 一个JavaBean的类
- 表中的字段 -> 类中的属性
- 表中的一条记录 -> 类的一个对象
比如说我们有这么一个人员基础信息表:
id | name | age | address |
---|---|---|---|
1 | BugMaker | 24 | 中国 |
2 | Bug | 2 | 美国 |
那么我们就可以针对这个表创建一个实体类:
public class People {
private int id;
private String name;
private String age;
private String address;
......
...get 和 set 方法...
......
}
MVC三层架构
什么是MVC : Model(模型) View(视图) Controller(控制层)
MVC三层架构之间的关系如下:
- View(JSP)
- 展示数据
- 提供发起Servlet请求的链接
- Controller (Servlet)
- 接收用户的请求
- 交给业务层(Service)处理对应业务
- 控制视图跳转
- Model
- 业务处理 : 业务逻辑 (业务层 Service)
- 数据持久层 (Dao)
过滤器
过滤器基本介绍
过滤器的大致功能如下所示
一般来说我们使用过滤器来拦截请求,并且对拦截的请求进行特别处理。
Servlet中的过滤器接口
我们可以看到Filter类的结构如下:
Filter过滤器的简单使用案例
在本个案例中,我们将创建一个简单的过滤器,其主要功能为拦截/servlet/*
URL路径下的请求,并且设置拦截到请求的字符格式。
1.导入对应的jar包
我们使用的Filter接口需要导入javax.servlet.Filter
包,一般来说,我们可以直接导入javax.servlet
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
2.创建一个拦截器
创建一个类,实现Filter接口,并且在doFilter()方法中编写对应的拦截方法
package com.kuang.filter;
import javax.servlet.*;
import java.io.IOException;
public class CharacterEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter创建完成...");
}
@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");
// 继续完成链后面的过滤器类
filterChain.doFilter(servletRequest,servletResponse);//如果不加这个那么请求就不会往下运行
}
@Override
public void destroy() {
System.out.println("Filter销毁完成...");
}
}
3.添加对应的Filter映射
在web.xml中添加我们编写的Filter对应的映射<ilter和filter-mapping
<filter>
<filter-name>characterEncoding</filter-name>
<filter-class>com.kuang.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>characterEncoding</filter-name>
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
4.编写一个简单的Servlet,用于测试Filter
我们可以编写一个简单的Servlet类,它只用于输出一串简单的中文字符,并且给这个Servlet设置两个对应的servlet-mapping,一个URL路径在’/servlet/*'路径下,另外一个不在
package com.kuang.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FilterTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("你好啊!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
添加Servlet类的映射(两个)
<servlet>
<servlet-name>filterTest</servlet-name>
<servlet-class>com.kuang.servlet.FilterTestServlet</servlet-class>
</servlet>
<!-- 添加两个映射路径-->
<servlet-mapping>
<servlet-name>filterTest</servlet-name>
<url-pattern>/servlet/show</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>filterTest</servlet-name>
<url-pattern>/show</url-pattern>
</servlet-mapping>
5.启动服务器,进行测试
我们可以看到当我们发出在’/servlet’目录下的请求时,请求会被过滤器拦截并且被过滤器进行了字符格式设置的处理所以页面输出的字符不是乱码;而当我们访问不是‘/servlet’目录下的请求时,请求没有被拦截,所以字符格式没有被设置,页面输出的字符为乱码
另外我们可以看到,当服务器启动时,过滤器被创建,调用init()方法;当服务器终结时,过滤器被销毁,调用destroy()方法。
Filter过滤器实用案例 : 过滤未登录用户
在此案例中我们将编写一个只用当用户登录之后才可以访问的页面,当用户没有登录时想要访问此页面会被过滤器拦截。
在此案例中我们判断用户是否登录的方法是在用户登录之后在Session中添加一个USER_ID属性,其内容就是此Session的ID。
1.创建简单的JSP
我们主要创建登录页面、登录成功页面、登录失败页面、未登录页面这四个JSP。
它们代码如下:
登录页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form method="post" action="/servlet/login">
<input type="text" name="username"/>
<input type="submit">
</form>
</body>
</html>
登录成功页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录成功</title>
</head>
<body>
登录成功!!!
<a href="/servlet/logout">注销</a>
</body>
</html>
登录失败页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录失败</title>
</head>
<body>
登录失败
<a href="Login.jsp">再次登录</a>
</body>
</html>
未登录页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
未登录,请先<a href="Login.jsp">登录</a>
</body>
</html>
2.编写登录Servelt已经注销Servlet
登录Servetl:
package com.kuang.servlet;
import com.kuang.utils.Constant;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.获取前端输入数据
String username = (String) req.getParameter("username");
// 2.根据前端输入数据进行判断
if ( "BugMaker".equals(username) ){
// 2.1 用户名输入正确,在session中添加一个字段以便’标记‘此Session登录成功
req.getSession().setAttribute(Constant.USER_ID,req.getSession().getId());
resp.sendRedirect("/sys/LoginSuccess.jsp");
}else{
// 2.2 用户名输入错误,返回登录失败页面
resp.sendRedirect("/LoginFailed.jsp");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
注销Servlet
package com.kuang.servlet;
import com.kuang.utils.Constant;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.获取Session
HttpSession session = req.getSession();
String userId = (String) session.getAttribute(Constant.USER_ID);
// 2,如果Session中有USER_ID或者USER_ID异常那么就将此属性移除
if(userId != null || "".equals(userId)){
session.removeAttribute(Constant.USER_ID);
}
// 3.重定向到登录页面
resp.sendRedirect("/Login.jsp");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
3.编写过滤器
package com.kuang.filter;
import com.kuang.utils.Constant;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class LoginFilter 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;
HttpSession session = request.getSession();
if (session.getAttribute(Constant.USER_ID) == null){
response.sendRedirect("/Error.jsp");
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
运行结果:
当我们没有登录就访问登录成功页面时: