JSP与Servlet
1. 软件系统体系结构
1.1常见软件系统体系结构B/S、C/S
1.1.1 C/S
-
C/S结构即客户端/服务器(Client/Server),例如QQ;
-
需要编写服务器端程序,以及客户端程序,例如我们安装的就是QQ的客户端程序;
-
缺点:软件更新时需要同时更新客户端和服务器端两端,比较麻烦;
-
优点:安全性比较好。
1.1.2 B/S
-
B/S结构即浏览器/服务器(Browser/Server);
-
优点:只需要编写服务器端程序;
-
缺点:安全性较差。
1.2 WEB资源
1.2.1 Web资源介绍
-
html/CSS/JS/图片…:静态资源;
-
JSP/Servlet:动态资源。
当然,除了JavaWeb程序,还有其他Web程序,例如:ASP、PHP等。
1.2.2 静态资源和静态资源区别
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sF7VEG96-1675781506828)(assets/wps1.jpg)]
1.2.3 访问Web资源
打开浏览器,输入URL:
协议名://域名:端口/路径,例如:http://www.baidu.com:80/index.html
1.3 Web服务器
Web服务器的作用是接收客户端的请求,给客户端作出响应。
对于JavaWeb程序而已,还需要有JSP/Servlet容器,JSP/Servlet容器的基本功能是把动态资源转换成静态资源,当然JSP/Servlet容器不只这些功能,我们会在后面一点一点学习。
我们需要使用的是Web服务器和JSP/Servlet容器,通常这两者会集于一身。下面是对JavaWeb服务器:
-
Tomcat(Apache):Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范servlet/jsp。开源的,免费的
-
JBoss(JBOSS):大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
-
Weblogic(Orcale):大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
-
Websphere(IBM):IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
JavaEE:Java语言在企业级开发中使用的技术规范的总和,一共规定了13项大的规范
1.4 Tomcat
1.4.1 Tomcat概述
Tomcat服务器由Apache提供,开源免费。由于Sun和其他公司参与到了Tomcat的开发中,所以最新的JSP/Servlet规范总是能在Tomcat中体现出来。当前最新版本是Tomcat10,我们课程中使用Tomcat8.5。Tomcat7支持Servlet3.0,而Tomcat6只支持Servlet2.5!
1.4.2 安装、启动、配置Tomcat
下载Tomcat可以到http://tomcat.apache.org下载。
Tomcat分为安装版和解压版:
-
安装版:一台电脑上只能安装一个Tomcat;
-
解压版:无需安装,解压即可用,解压多少份都可以,所以我们选择解压版。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mXexhHnr-1675781506830)(assets/image-20220710170549766.png)]
1.4.2.1 Tomcat目录结构
把解压版Tomcat解压到一个没有中文,没有空格的路径中即可,建议路径不要太深,因为我们需要经常进入Tomcat安装目录。例如:D: tomcat apache-tomcat-8.5.59
1.4.2.2 启动和关闭Tomcat
在启动Tomcat之前,我们必须要配置环境变量 :
-
JAVA_HOME:必须先配置JAVA_HOME,因为Tomcat启动需要使用JDK;
-
CATALANA_HOME:如果是安装版,那么还需要配置这个变量,这个变量用来指定Tomcat的安装路径,例如:D: tomcat apache-tomcat-8.5.59。
启动:进入%CATALANA_HOME% bin目录,找到startup.bat,双击即可;
关闭:进入%CATALANA_HOME% bin目录,找到shutdown.bat,双击即可;
startup.bat会调用catalina.bat,而catalina.bat会调用setclasspath.bat,setclasspath.bat会使用JAVA_HOME环境变量,所以我们必须在启动Tomcat之前把JAVA_HOME配置正确。
启动问题:
- 点击startup.bat后窗口一闪即消失:
- 检查JAVA_HOME环境变量配置是否正确;
- 启动报错:
暴力:找到占用的端口号,并且找到对应的进程,杀死该进程
netstat -ano
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZeFIeaWB-1675781506831)(assets/image-20220710232836863.png)]
温柔:修改自身的端口号conf/server.xml
<Connector port="8888" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8445" />
1.4.2.3 进入Tomcat主页
访问:http://localhost:8080
1.4.2.4 配置端口号
打开%CATALANA_HOME% conf server.xml文件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IyeltIwm-1675781506831)(assets/wps2.jpg)]
http默认端口号为80,也就是说在URL中不给出端口号时就表示使用80端口。当然你也可以修改为其它端口号。
当把端口号修改为80后,在浏览器中只需要输入:http://localhost就可以访问Tomcat主页了。
1.4.2.5 Tomcat的目录结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I7KrgBSU-1675781506832)(assets/wps3.jpg)]
-
bin:该目录下存放的是二进制可执行文件,如果是安装版,那么这个目录下会有两个exe文件:tomcat6.exe、tomcat6w.exe,前者是在控制台下启动Tomcat,后者是弹出UGI窗口启动Tomcat;如果是解压版,那么会有startup.bat和shutdown.bat文件,startup.bat用来启动Tomcat,但需要先配置JAVA_HOME环境变量才能启动,shutdawn.bat用来停止Tomcat;
-
conf:这是一个非常非常重要的目录,这个目录下有四个最为重要的文件:
- server.xml:配置整个服务器信息。例如修改端口号,添加虚拟主机等; tomcat-users.xml:存储tomcat用户的文件,这里保存的是tomcat的用户名及密码,以及用户的角色信息。可以按着该文件中的注释信息添加tomcat用户,然后就可以在Tomcat主页中进入Tomcat Manager页面了;
- web.xml:部署描述符文件,这个文件中注册了很多MIME类型,即文档类型。这些MIME类型是客户端与服务器之间说明文档类型的,如用户请求一个html网页,那么服务器还会告诉客户端浏览器响应的文档是text/html类型的,这就是一个MIME类型。客户端浏览器通过这个MIME类型就知道如何处理它了。当然是在浏览器中显示这个html文件了。但如果服务器响应的是一个exe文件,那么浏览器就不可能显示它,而是应该弹出下载窗口才对。MIME就是用来说明文档的内容是什么类型的!
- context.xml:对所有应用的统一配置,通常我们不会去配置它。
-
lib:Tomcat的类库,里面是一大堆jar文件。如果需要添加Tomcat依赖的jar文件,可以把它放到这个目录中,当然也可以把应用依赖的jar文件放到这个目录中,这个目录中的jar所有项目都可以共享之,但这样你的应用放到其他Tomcat下时就不能再共享这个目录下的Jar包了,所以建议只把Tomcat需要的Jar包放到这个目录下;
-
logs:这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常也会记录在日志文件中。
-
temp:存放Tomcat的临时文件,这个目录下的东西可以在停止Tomcat后删除!
-
webapps:存放web项目的目录,其中每个文件夹都是一个项目;如果这个目录下已经存在了目录,那么都是tomcat自带的。项目。其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目。http://localhost:8080/examples,进入示例项目。其中examples就是项目名,即文件夹的名字。
-
work:运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的!可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。
-
LICENSE:许可证。
-
NOTICE:说明文件。
1.4.2.6 部署
- 直接将项目放到webapps目录下即可
项目名就是我们该项目的访问路径
简单部署:将项目打成一个war包,再将war包放置到webapps目录下。war会自动解压
虚拟目录: 项目名或者war名字
- 配置conf/server.xml文件,在
<Host>
标签体中配置<Context docBase="D:/hello" path="/hehe" />
- docBase:项目存放的路径
- path:虚拟目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dFGOdJWQ-1675781506833)(assets/image-20220710234741903.png)]
- 在conf/Catalina/localhost创建任意名称的xml文件。在文件中编写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zwb65aS6-1675781506833)(assets/image-20220710235253567.png)]
<Context docBase="D:/hello" />
虚拟目录:xml文件的名称
推荐使用第三种: 因为它支持热部署,当我们不想部署项目,那我们就把hello.xml修改为hello.xml.back,那我们不需要重新启动tomcat,再访问,就无法访问到hello项目了
1.5 Web应用(重点)
1.5.1 创建静态应用
-
在webapps下创建一个hello目录;
-
在webapps/hello 下创建index.html;
-
启动tomcat;
-
打开浏览器访问http://localhost:8080/hello/index.html
index.html
<html>
<head>
<title>hello</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
1.5.2 创建动态应用
-
在webapps下创建hello1目录;
-
在webapps/hello1 下创建WEB-INF目录;
-
在webapps/hello1 WEB-INF 下创建web.xml;
-
在webapps/hello1 下创建index.html。
-
打开浏览器访问http://localhost:8080/hello/index.html
web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
</web-app>
完整的Web应用还需要在WEB-INF目录下创建:
-
classes;
-
lib目录;
webapps
|- hello
|-index.html
|-WEB-INF
|-web.xml
|-classes
|-lib
-
hello:应用目录,hello就是应用的名称;
-
index.html:应用资源。应用下可以有多个资源,例如css、js、html、jsp等,也可以把资源放到文件夹中,例如:hello html index.html,这时访问URL为:http://localhost:8080/hello/html/index.html;
-
WEB-INF:这个目录名称必须是大写,这个目录下的东西是无法通过浏览器直接访问的,也就是说放到这里的东西是安全的;
-
web.xml:应用程序的部署描述符文件,可以在该文件中对应用进行配置,例如配置应用的首页:
<welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list>
-
classes:存放class文件的目录;
-
lib:存放jar包的目录;
-
1.6 IDEA创建Web项目,以及配置tomcat
1.6.1 创建web项目,配置Tomcat
1.新建一个项目
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhO7PXZ8-1675781506834)(assets/1590549787507.png)]
2.我们先以创建一个空项目:
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pps6eNDz-1675781506835)(assets/1590550051629.png)]
3.选择好web服务器,再弹出的对话框,选择你Tomcat服务器的路径
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x5r8iQdb-1675781506835)(assets/1590550186867.png)]
4.选择Web Application
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ce091M68-1675781506835)(assets/1590561430894.png)]
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a8MpPumO-1675781506836)(assets/1590561495369.png)]
5.创建好项目之前,进入到IDEA中,我们在软件的顶部可以看到如下内容,对Tomcat进行操作:
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0jMahek7-1675781506837)(assets/1590561762669.png)]
通过上面的配置我们以及创建了一个web项目, 配置为这个web项目配置了一个tomcat,但是我们发现这个web项目与我们之前的Eclipse的web项目是不一样的.
并且没有web.xml文件,接下来我们来修改这个web项目:
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AsWhLzDc-1675781506838)(assets/1590564269665.png)]
1.6.2 修改Web项目
- 在web目录手动创建WEB-INF,目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fCjkPNnl-1675781506838)(assets/1590565873845.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SlhgislU-1675781506839)(assets/1590565985229.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oI6iXPyX-1675781506839)(assets/1590566062213.png)]
按照上面创建WEB-INF的方式,在WEB-INF目录下创建classes和lib目录
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtWQpL1k-1675781506840)(assets/1590566145843.png)]
- classes文件夹:存放编译后输出的class文件
- lib文件夹;存放第三方jar包,比如servlet用的jar包,连接数据库的驱动等等很多
-
配置文件夹路径
添加这两个文件夹之后,修改文件夹路径,不然i知道你这里两个文件夹。
点击File>Project Structure(或者快捷键Ctrl+AIT+shift+s)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pwtlOn2S-1675781506841)(assets/1590564440412.png)]
在如图页面点击modules>Paths>Use module…把output path和test output path改成你刚刚创建的那个classes文件路径,这样module就知道这个文件是存放编译后输出的class文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-asW4pWeo-1675781506842)(assets/1590566527518.png)]
通过上一步操作,我们已经把字节码文件输出目录设置为WEB-INF/classes目录下,那接下来我们需要依赖jar包选择
File–>Project Structure–>modules --> 选中项目“JavaWeb” --> 切换到 Dependencies 选项卡 --> 点击右边的“+”,选择 “JARs or directories…”,选择创建的lib目录;选择Jar Directory
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g8l4FFk4-1675781506842)(assets/1590566658057.png)]
选择lib目录
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VT5eXl9i-1675781506843)(assets/1590566843565.png)]
选择:Jar Directory:
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U44ZH0FL-1675781506844)(assets/1590566907726.png)]
最终结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2YJ6Z7Oj-1675781506844)(assets/1590566969444.png)]
-
添加jar包
把你项目需要的jar包拷贝到lib目录, 比如我们需要jstl的jar,那我们就把jstl的jar拷贝到lib目录:
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4zMkPnf1-1675781506845)(assets/1590567552598.png)]
这时候需要把jar包导入,类似于Eclipse的build Path
点击File>Project Structure(或者快捷键Ctrl+AIT+shift+s)
点击Libraries>绿色+>java,选择你lib文件夹的jar包,OK,就导入了
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49qerX3m-1675781506845)(assets/1590567730655.png)]
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JtYQHYhe-1675781506846)(assets/1590567772239.png)]
.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ay1M9eBU-1675781506847)(assets/1590567816441.png)]
到这个时候,我们才把一个web项目完成了.大家动手试一下
2.Servlet
2.1 Servlet概述
2.1.1什么是Servlet
Servlet是JavaWeb的 三大组件之一 ,它属于动态资源。Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:
-
接收请求数据;
-
处理请求;
-
完成响应。
例如客户端发出登录请求,或者输出注册请求,这些请求都应该由Servlet来完成处理!Servlet需要我们自己来编写,每个Servlet必须实现javax.servlet.Servlet接口。
2.1.2 实现Servlet的方式(由我们自己来写)
实现Servlet有三种方式:
-
实现javax.servlet.Servlet接口;
-
继承javax.servlet.GenericServlet类;
-
继承javax.servlet.http.HttpServlet类;
通常我们会去继承HttpServlet类来完成我们的Servlet,但学习Servlet还要从javax.servlet.Servlet接口开始学习。
Servlet.java
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
Servlet中的方法大多数不由我们来调用,而是由Tomcat来调用。并且Servlet的对象也不由我们来创建,由Tomcat来创建!
2.1.3 创建helloservlet应用
我们开始第一个Servlet应用吧!首先我们使用idea创建一个web项目
接下来我们开始准备完成Servlet,完成Servlet需要分为两步:
-
编写Servlet类;
-
在web.xml文件中配置Servlet;
HelloServlet.java
public class HelloServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {}
public ServletConfig getServletConfig() {
return null ;
}
public void destroy() {}
public String getServletInfo() {
return null;
}
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System. out .println("hello servlet!");
}
}
我们暂时忽略Servlet中其他四个方法,只关心service()方法,因为它是用来处理请求的方法。我们在该方法内给出一条输出语句!
web.xml(下面内容不需要背下来)
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.csmf.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/helloworld</url-pattern>
</servlet-mapping>
在web.xml中配置Servlet的目的其实只有一个,就是把访问路径与一个Servlet绑定到一起,上面配置是把访问路径:“/helloworld”与“org.csmf.servlet.HelloServlet”绑定到一起。
-
<servlet>
:指定HelloServlet这个Servlet的名称为hello; -
<servlet-mapping>
:指定/helloworld访问路径所以访问的Servlet名为hello。<servlet>
和<servlet-mapping>
通过<servlet-name>
这个元素关联在一起了!
接下来,我们编译HelloServlet,注意,编译HelloServlet时需要导入servlet-api.jar,因为Servlet.class等类都在servlet-api.jar中。
然后把HelloServlet.class放到/helloworld/WEB-INF/classes/目录下,然后启动Tomcat,在浏览器中访问:http://localhost:8080/helloservlet/helloworld即可在控制台上看到输出!
2.2 Servlet接口
2.2.1 Servlet的生命周期
所谓xxx的生命周期,就是说xxx的出生、服务,以及死亡。Servlet生命周期也是如此!与Servlet的生命周期相关的方法有:
-
void init(ServletConfig);
-
void service(ServletRequest,ServletResponse);
-
void destroy();
2.2.1.1 Servlet的出生
服务器会在Servlet 第一次被访问时创建Servlet ,或者是在 服务器启动时创建Servlet 。 如果服务器启动时就创建Servlet,那么还需要在web.xml文件中配置。也就是说默认情况下,Servlet是在第一次被访问时由服务器创建的。
而且 一个Servlet类型,服务器只创建一个实例对象 ,例如在我们首次访问http://localhost:8080/helloservlet/helloworld时,服务器通过“/helloworld”找到了绑定的Servlet名称为org.csmf.servlet.HelloServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器才会通过反射来创建HelloServlet的实例。当我们再次访问http://localhost:8080/helloservlet/helloworld时,服务器就不会再次创建HelloServlet实例了,而是直接使用上次创建的实例。
在Servlet被创建后,服务器会马上调用Servlet的void init(ServletConfig)方法。请记住, Servlet出生后马上就会调用init()方法,而且一个Servlet的一生 。 这个方法只会被调用一次。这好比小孩子出生后马上就要去剪脐带一样,而且剪脐带一生只有一次。
我们可以把一些对Servlet的初始化工作放到init方法中!
2.2.1.2 Servlet服务
当服务器每次接收到请求时,都会去调用Servlet的service()方法来处理请求。 服务器接收到一次请求,就会调用service() 方法一次 ,所以service()方法是会被调用多次的。正因为如此,所以我们才需要把处理请求的代码写到service()方法中!
2.2.1.3 Servlet的离去
Servlet是不会轻易离去的,通常都是在服务器关闭时Servlet才会离去!在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法,我们可以把Servlet的临终遗言放到destroy()方法中,例如对某些资源的释放等代码放到destroy()方法中。
2.2.1.4 测试生命周期方法
修改HelloServlet如下,然后再去访问http://localhost:8080/helloservlet/helloworld
public class HelloServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
System.out.println("Servlet出生了!");
}
public ServletConfig getServletConfig() {
return null;
}
public void destroy() {
System.out.println("Servlet要死了!");
}
public String getServletInfo() {
return null;
}
public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException {
System.out.println("hello servlet!");
}
}
在首次访问HelloServlet时,init方法会被执行,而且也会执行service方法。再次访问时,只会执行service方法,不再执行init方法。在关闭Tomcat时会调用destroy方法。
2.2.2 Servlet接口相关类型
在Servlet接口中还存在三个我们不熟悉的类型:
-
ServletRequest:service() 方法的参数,它表示请求对象,它封装了所有与请求相关的数据,它是由服务器创建的;
-
ServletResponse:service()方法的参数,它表示响应对象,在service()方法中完成对客户端的响应需要使用这个对象;
-
ServletConfig:init()方法的参数,它表示Servlet配置对象,它对应Servlet的配置信息,那对应web.xml文件中的
<servlet>
元素。
2.2.2.1 ServletRequest和ServletResponse
ServletRequest和ServletResponse是Servlet#service() 方法的两个参数,一个是请求对象,一个是响应对象,可以从ServletRequest对象中获取请求数据,可以使用ServletResponse对象完成响应。你以后会发现,这两个对象就像是一对恩爱的夫妻,永远不分离,总是成对出现。
ServletRequest和ServletResponse的实例由服务器创建,然后传递给service()方法。如果在service() 方法中希望使用HTTP相关的功能,那么可以把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse。这也说明我们经常需要在service()方法中对ServletRequest和ServletResponse进行强转,这是很心烦的事情。不过后面会有一个类来帮我们解决这一问题的。
HttpServletRequest方法:
-
String getParameter(String paramName):获取指定请求参数的值;
-
String getMethod():获取请求方法,例如GET或POST;
-
String getHeader(String name):获取指定请求头的值;
-
void setCharacterEncoding(String encoding):设置请求体的编码!因为GET请求没有请求体,所以这个方法只只对POST请求有效。当调用request.setCharacterEncoding(“utf-8”)之后,再通过getParameter()方法获取参数值时,那么参数值都已经通过了转码,即转换成了UTF-8编码。所以,这个方法必须在调用getParameter()方法之前调用!
HttpServletResponse方法:
-
PrintWriter getWriter():获取字符响应流,使用该流可以向客户端输出响应信息。例如response.getWriter().print(“
Hello JavaWeb!
”); -
ServletOutputStream getOutputStream():获取字节响应流,当需要向客户端响应字节数据时,需要使用这个流,例如要向客户端响应图片;
-
void setCharacterEncoding(String encoding):用来设置字符响应流的编码,例如在调用setCharacterEncoding(“utf-8”);之后,再response.getWriter()获取字符响应流对象,这时的响应流的编码为utf-8,使用response.getWriter()输出的中文都会转换成utf-8编码后发送给客户端;
-
void setHeader(String name, String value):向客户端添加响应头信息,例如setHeader(“Refresh”, “3;url=http://www.baidu.com”),表示3秒后自动刷新到http://www.baidu.com;
-
void setContentType(String contentType):该方法是setHeader(“content-type”, “xxx”)的简便方法,即用来添加名为content-type响应头的方法。content-type响应头用来设置响应数据的MIME类型,例如要向客户端响应jpg的图片,那么可以setContentType(“image/jepg”),如果响应数据为文本类型,那么还要台同时设置编码,例如setContentType(“text/html;chartset=utf-8”)表示响应数据类型为文本类型中的html类型,并且该方法会调用setCharacterEncoding(“utf-8”)方法;
-
void sendError(int code, String errorMsg):向客户端发送状态码,以及错误消息。例如给客户端发送404:response(404, “您要查找的资源不存在!”)。
2.2.2.2 ServletConfig
ServletConfig对象对应web.xml文件中的<servlet>
元素。例如你想获取当前Servlet在web.xml文件中的配置名,那么可以使用servletConfig.getServletName()方法获取!
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.csmf.servlet.HelloServlet</servlet-class>
</servlet>
ServletConfig对象是由服务器创建的,然后传递给Servlet的init()方法,你可以在init()方法中使用它!
- String getServletName():获取Servlet在web.xml文件中的配置名称,即指定的名称;
- ServletContext getServletContext():用来获取ServletContext对象,ServletContext会在后面讲解;
- String getInitParameter(String name):用来获取在web.xml中配置的初始化参数,通过参数名来获取参数值;
- Enumeration getInitParameterNames():用来获取在web.xml中配置的所有初始化参数名称;
在<servlet>
元素中还可以配置初始化参数:
<servlet>
<servlet-name>One</servlet-name>
<servlet-class>org.csmf.servlet.OneServlet</servlet-class>
<init-param>
<param-name>paramName1</param-name>
<param-value>paramValue1</param-value>
</init-param>
<init-param>
<param-name>paramName2</param-name>
<param-value>paramValue2</param-value>
</init-param>
</servlet>
在OneServlet中,可以使用ServletConfig对象的getInitParameter()方法来获取初始化参数,例如:
String value1 = servletConfig.getInitParameter(“paramName1”);//获取到paramValue1
2.3 GenericServlet
2.3.1 GenericServlet概述
GenericServlet是Servlet接口的实现类,我们可以通过继承GenericServlet来编写自己的Servlet。下面是GenericServlet类的源代码:
GenericServlet.java
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {}
@Override
public void destroy() {}
@Override
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void init(ServletConfig config) throws ServletException { this.config = config;
this.init();
}
public void init()throws ServletException {}
public void log(String msg) {
getServletContext().log(getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
@Override
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
@Override
public String getServletName() {
return config.getServletName();
}
}
实现了Servlet的init(ServletConfig)方法,把参数config赋给了本类的成员config,然后再调用本类自己的无参的init()方法。
init()
这个方法是GenericServlet自己的方法,而不是从Servlet继承下来的。当我们自定义Servlet时,如果想完成初始化作用就不要再重复init(ServletConfig)方法了,而是应该去重写init()方法。因为在GenericServlet中的init(ServletConfig)方法中保存了ServletConfig对象,如果覆盖了保存ServletConfig的代码,那么就不能再使用ServletConfig了。
2.3.2 GenericServlet的init()方法
在GenericServlet中,定义了一个ServletConfig config实例变量,并在init(ServletConfig)方法中把参数ServletConfig赋给了实例变量。然后在该类的很多方法中使用了实例变量config。
如果子类覆盖了GenericServlet的init(StringConfig)方法,那么this.config=config这一条语句就会被覆盖了,也就是说GenericServlet的实例变量config的值为null,那么所有依赖config的方法都不能使用了。如果真的希望完成一些初始化操作,那么去覆盖GenericServlet提供的init()方法,它是没有参数的init()方法,它会在init(ServletConfig)方法中被调用。
2.3.3 实现了ServletConfig接口
GenericServlet还实现了ServletConfig接口,所以可以直接调用getInitParameter()、getServletContext()等ServletConfig的方法。
2.4 HttpServlet
2.4.1 HttpServlet概述
HttpServlet类是GenericServlet的子类,它提供了对HTTP请求的特殊支持,所以通常我们都会通过继承HttpServlet来完成自定义的Servlet。
2.4.2 HttpServlet覆盖了service()方法
HttpServlet类中提供了service(HttpServletRequest,HttpServletResponse)方法,这个方法是HttpServlet自己的方法,不是从Servlet继承来的。在HttpServlet的service(ServletRequest,ServletResponse)方法中会把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse,然后调用service(HttpServletRequest,HttpServletResponse)方法,这说明子类可以去覆盖service(HttpServletRequest,HttpServletResponse)方法即可,这就不用自己去强转请求和响应对象了。
其实子类也不用去覆盖service(HttpServletRequest,HttpServletResponse)方法,因为HttpServlet还要做另一步简化操作,下面会介绍。
HttpServlet.java
public abstract class HttpServlet extends GenericServlet {
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//...代码省略
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try{
//强转
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
}catch(ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
//调用service(HttpServletRequest,HttpServletResponse)方法
service(request, response);
}
//其他方法省略……
}
2.4.3 doGet()和doPost()
在HttpServlet的service(HttpServletRequest,HttpServletResponse)方法会去判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的doGet()方法,如果是POST请求会去调用doPost()方法,这说明我们在子类中去覆盖doGet()或doPost()方法即可。
public class AServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("hello doGet()...");
}
}
public class BServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("hello doPost()...");
}
}
2.5 Servlet细节
2.5.1 Servlet与线程安全
因为一个类型的Servlet只有一个实例对象,那么就有可能会现时出一个Servlet同时处理多个请求,那么Servlet是否为线程安全的呢?答案是:“ 不是线程安全的 ”。这说明Servlet的工作效率很高,但也存在线程安全问题!
所以我们不应该在Servlet中便宜创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。
2.5.2 让服务器在启动时就创建Servlet
默认情况下,服务器会在某个Servlet第一次收到请求时创建它。也可以在web.xml中对Servlet进行配置,使服务器启动时就创建Servlet。
<servlet>
<servlet-name>hello1</servlet-name>
<servlet-class>com.suke.servlet.Hello1Servlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hello1</servlet-name>
<url-pattern>/hello1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>hello2</servlet-name>
<servlet-class>com.suke.servlet.Hello2Servlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hello2</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>hello3</servlet-name>
<servlet-class>com.suke.servlet.Hello3Servlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hello3</servlet-name>
<url-pattern>/hello3</url-pattern>
</servlet-mapping>
在
<servlet>
元素中配置<load-on-startup>
元素可以让服务器在启动时就创建该Servlet,其中<load-on-startup>
元素的值必须是大于等于0的整数,它的使用是服务器启动时创建Servlet的顺序。上例中,根据<load-on-startup>
的值可以得知服务器创建Servlet的顺序为Hello1Servlet、Hello2Servlet、Hello3Servlet。
2.5.3 <url-pattern>
<url-pattern>
是<servlet-mapping>
的子元素,用来指定Servlet的访问路径,即URL。它必须是以“/”开头!
- 可以在
<servlet-mapping>
中给出多个<url-pattern>
,例如:
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/AServlet</url-pattern>
<url-pattern>/BServlet</url-pattern>
</servlet-mapping>
那么这说明一个Servlet绑定了两个URL,无论访问/AServlet还是/BServlet,访问的都是AServlet。
- 还可以在
<url-pattern>
中使用通配符,所谓通配符就是星号“
<url-pattern>/servlet/ <url-patter>
:/servlet/a、/servlet/b,都匹配/servlet/ ;<url-pattern>*.do</url-pattern>
:/abc/def/ghi.do、/a.do,都匹配 .do;<url-pattern>/*<url-pattern>
:匹配所有URL;
请注意,通配符要么为前缀,要么为后缀,不能出现在URL中间位置,也不能只有通配符。例如:
/*.do
就是错误的,因为星号出现在URL的中间位置上了。*.*
也是不对的,因为一个URL中最多只能出现一个通配符。
注意,通配符是一种模糊匹配URL的方式,如果存在更具体的
<url-pattern>
,那么访问路径会去匹配具体的<url-pattern>
。
例如:
<servlet>
<servlet-name>hello1</servlet-name>
<servlet-class>org.csmf.servlet.Hello1Servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello1</servlet-name>
<url-pattern>/servlet/hello1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>hello2</servlet-name>
<servlet-class>org.csmf.servlet.Hello2Servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello2</servlet-name>
<url-pattern>/servlet/ </url-pattern>
</servlet-mapping>
当访问路径为http://localhost:8080/hello/servlet/hello1时,因为访问路径即匹配hello1的<url-pattern>
,又匹配hello2的<url-pattern>
,但因为hello1的<url-pattern>
中没有通配符,所以优先匹配,即设置hello1。
2.6 ServletContext
2.6.1 ServletContext概述
服务器会为每个应用创建一个ServletContext对象:
- ServletContext对象的创建是在服务器启动时完成的;
- ServletContext对象的销毁是在服务器关闭时完成的。
ServletContext对象的作用是在整个Web应用的动态资源之间共享数据!例如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中就可以获取这个值,这就是共享数据了。
2.6.2 获取ServletContext
在Servlet中获取ServletContext对象:
- 在void init(ServletConfig config)中:ServletContext context = config.getServletContext();,ServletConfig类的getServletContext()方法可以用来获取ServletContext对象;
在GenericeServlet或HttpServlet中获取ServletContext对象:
- GenericServlet类有getServletContext()方法,所以可以直接使用this.getServletContext()来获取;
public class MyServlet implements Servlet {
public void init(ServletConfig config) {
ServletContext context = config.getServletContext();
}
//…
}
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) { ServletContext context = this.getServletContext();
}
}
2.6.3 域对象的功能
ServletContext是JavaWeb四大域对象之一:
-
PageContext;
-
ServletRequest;
-
HttpSession;
-
ServletContext;
所有域对象都有存取数据的功能,因为域对象内部有一个Map,用来存储数据,下面是ServletContext对象用来操作数据的方法:
-
void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:servletContext.setAttribute(“xxx”, “XXX”),在ServletContext中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
-
Object getAttribute(String name):用来获取ServletContext中的数据,当前在获取之前需要先去存储才行,例如:String value = (String)servletContext.getAttribute(“xxx”);,获取名为xxx的域属性;
-
void removeAttribute(String name):用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
-
Enumeration getAttributeNames():获取所有域属性的名称;
2.6.4 获取应用初始化参数
还可以使用ServletContext来获取在web.xml文件中配置的应用初始化参数!注意,应用初始化参数与Servlet初始化参数不同:
web.xml
<web-app>
<context-param>
<param-name>paramName1</param-name>
<param-value>paramValue1</param-value>
</context-param>
<context-param>
<param-name>paramName2</param-name>
<param-value>paramValue2</param-value>
</context-param>
</web-app>
ServletContext context =this.getServletContext();
String value1 = context.getInitParameter("paramName1");
String value2 = context.getInitParameter("paramName2");
System.out.println(value1 + ", " + value2);
Enumeration names = context.getInitParameterNames();
while(names.hasMoreElements()) {
System.out.println(names.nextElement());
}
2.6.5 获取资源相关方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9FP7A3MR-1675781506849)(assets/wps4.jpg)]
获取真实路径
还可以使用ServletContext对象来获取Web应用下的资源,例如在hello应用的根目录下创建a.txt文件,现在想在Servlet中获取这个资源,就可以使用ServletContext来获取。
-
获取a.txt的真实路径:String realPath = servletContext.getRealPath(“/a.txt”),realPath的值为a.txt文件的绝对路径:F: tomcat7 webapps hello a.txt;
-
获取b.txt的真实路径:String realPath = servletContext.getRealPath(“/WEB-INF/b.txt”);
获取资源流
不只可以获取资源的路径,还可以通过ServletContext获取资源流,即把资源以输入流的方式获取:
-
获取a.txt资源流:InputStream in = servletContext.getResourceAsStream(“/a.txt”);
-
获取b.txt资源流:InputStream in = servletContext.getResourceAsStream(“/WEB-INF/b.txt”);
获取指定目录下所有资源路径
还可以使用ServletContext获取指定目录下所有资源路径,例如获取/WEB-INF下所有资源的路径:
Set set = context.getResourcePaths("/WEB-INF");
System.out.println(set);
[/WEB-INF/lib/, /WEB-INF/classes/, /WEB-INF/b.txt, /WEB-INF/web.xml]
注意,本方法必须以“/”开头!!!
2.6.6 练习访问量统计
相信各位一定见过很多访问量统计的网站,即“本页面被访问过XXX次”。因为无论是哪个用户访问指定页面,都会累计访问量,所以这个访问量统计应该是整个项目共享的!很明显,这需要使用ServletContext来保存访问量。
ServletContext application = this.getServletContext();
Integer count = (Integer)application.getAttribute("count");
if(count ==null){
count = 1;
}else{
count++;
}
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("<h1>本页面一共被访问" + count + "次!</h1>"); application.setAttribute("count", count);
2.7 Servlet3.0 注解配置
通过上面的学习,我们已经学会怎么开发Servlet了,但是大家有没有发现,我们编写一个Servlet,就需要到web.xml文件中对Servlet相关的配置,如果我们的项目是一个大型项目,可以要写成千上百的Servlet,那我们还得在web.xml文件中进行大量配置,这也太繁琐了,所以在Servlet3.0之后,提供了注解的方式,来替换xml的配置.
步骤:
- 创建JavaEE项目,选择Sevlet的版本3.0以上,可以不创建web.xml
- 定义一个Servlet类
- 复写请求的方法
- 在Servlet类使用@WebServlet注解,进行配置
@WebServlet("资源路径")
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rCtSuL7l-1675781506849)(assets/image-20220711001502758.png)]
2.8 IDEA与tomcat相关配置
IDEA会为每一个tomcat部署的项目单独创建一份配置文件,配置文件在哪里呢?我们启动tomcat的时候,在控制台可以看到如下信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHE1c3c1-1675781506850)(assets/image-20220711001942412.png)]
其他有个目录:Using CATALINA_BASE: "C:\Users\suke\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat\_web"
这就是我们该项目的配置文件目录
工作空间项目和tomcat部署的web项目
- tomcat真正访问的是“tomcat部署的web项目”,“tomcat部署的web项目"对应着"工作空间项目” 的web目录下的所有资源
- WEB-INF目录下的资源不能被浏览器直接访问。
3.请求(request)与响应(response)
3.1请求响应流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOCkW6p0-1675781506851)(assets/wps1-1657548585006.jpg)]
3.2 request
3.2.1 request概述
request是Servlet.service()方法的一个参数,类型为javax.servlet.http.HttpServletRequest。在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()方法时传递给service()方法,这说明在service()方法中可以通过request对象来获取请求数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QRF4yycq-1675781506852)(assets/wps2-1657548585007.jpg)]
request的功能可以分为以下几种:
-
封装了请求头数据;
-
封装了请求正文数据,如果是GET请求,那么就没有正文;
-
request是一个域对象,可以把它当成Map来添加获取数据;
-
request提供了请求转发和请求包含功能。
3.2.2 request域方法
request是域对象!在JavaWeb中一共四个域对象,其中ServletContext就是域对象,它在整个应用中只创建一个ServletContext对象。request其中一个,request可以在一个请求中共享数据。
一个请求会创建一个request对象,如果在一个请求中经历了多个Servlet,那么多个Servlet就可以使用request来共享数据。现在我们还不知道如何在一个请求中经历之个Servlet,后面在学习请求转发和请求包含后就知道了。
下面是request的域方法:
-
void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:servletContext.setAttribute(“xxx”, “XXX”),在request中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
-
Object getAttribute(String name):用来获取request中的数据,当前在获取之前需要先去存储才行,例如:String value = (String)request.getAttribute(“xxx”);,获取名为xxx的域属性;
-
void removeAttribute(String name):用来移除request中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
-
Enumeration getAttributeNames():获取所有域属性的名称;
3.2.3 request获取请求头数据
request与请求头相关的方法有:
-
String getHeader(String name):获取指定名称的请求头;
-
Enumeration getHeaderNames():获取所有请求头名称;
-
int getIntHeader(String name):获取值为int类型的请求头。
3.2.4 request获取请求相关的其它方法
request中还提供了与请求相关的其他方法,有些方法是为了我们更加便捷的方法请求头数据而设计,有些是与请求URL相关的方法。
-
int getContentLength():获取请求体的字节数,GET请求没有请求体,没有请求体返回-1;
-
String getContentType():获取请求类型,如果请求是GET,那么这个方法返回null;如果是POST请求,那么默认为application/x-www-form-urlencoded,表示请求体内容使用了URL编码;
-
String getMethod():返回请求方法,例如:GET
-
Locale getLocale():返回当前客户端浏览器的Locale。java.util.Locale表示国家和言语,这个东西在国际化中很有用;
-
String getCharacterEncoding():获取请求编码,如果没有setCharacterEncoding(),那么返回null,表示使用ISO-8859-1编码;
-
void setCharacterEncoding(String code):设置请求编码,只对请求体有效!注意,对于GET而言,没有请求体!!!所以此方法只能对POST请求中的参数有效!
-
String getContextPath():返回上下文路径,例如:/hello
-
String getQueryString():返回请求URL中的参数,例如:name=zhangSan
-
String getRequestURI():返回请求URI路径,例如:/hello/oneServlet
-
StringBuffer getRequestURL():返回请求URL路径,例如:http://localhost/hello/oneServlet,即返回除了参数以外的路径信息;
-
String getServletPath():返回Servlet路径,例如:/oneServlet
-
String getRemoteAddr():返回当前客户端的IP地址;
-
String getRemoteHost():返回当前客户端的主机名,但这个方法的实现还是获取IP地址;
-
String getScheme():返回请求协议,例如:http;
-
String getServerName():返回主机名,例如:localhost
-
int getServerPort():返回服务器端口号,例如:8080
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pREaezGU-1675781506852)(assets/wps3-1657548585008.jpg)]
System.out.println("request.getContentLength(): " + request.getContentLength()); System.out.println("request.getContentType(): " + request.getContentType()); System.out.println("request.getContextPath(): " + request.getContextPath()); System.out.println("request.getMethod(): " + request.getMethod());
System.out.println("request.getLocale(): " + request.getLocale());
System.out.println("request.getQueryString(): " + request.getQueryString()); System.out.println("request.getRequestURI(): " + request.getRequestURI());
System.out.println("request.getRequestURL(): " + request.getRequestURL());
System.out.println("request.getServletPath(): " + request.getServletPath()); System.out.println("request.getRemoteAddr(): " + request.getRemoteAddr());
System.out.println("request.getRemoteHost(): " + request.getRemoteHost());
System.out.println("request.getRemotePort(): " + request.getRemotePort());
System.out.println("request.getScheme(): " + request.getScheme());
System.out.println("request.getServerName(): " + request.getServerName());
System.out.println("request.getServerPort(): " + request.getServerPort());
3.2.4.1案例:request.getRemoteAddr():封IP
可以使用request.getRemoteAddr()方法获取客户端的IP地址,然后判断IP是否为禁用IP。
String ip = request.getRemoteAddr();
System.out.println(ip);
if(ip.equals("127.0.0.1")) {
response. getWriter().print("您的IP已被禁止!");
}else{
response.getWriter().print("Hello!");
}
3.2.5 request获取请求参数
最为常见的客户端传递参数方式有两种:
-
浏览器地址栏直接输入:一定是GET请求;
-
超链接:一定是GET请求;
-
表单:可以是GET,也可以是POST,这取决与的method属性值;
GET请求和POST请求的区别:
-
GET请求:
-
请求参数会在浏览器的地址栏中显示,所以不安全;
-
请求参数长度限制长度在1K之内;
-
GET请求没有请求体,无法通过request.setCharacterEncoding()来设置参数的编码;
-
POST请求:
- 请求参数不会显示浏览器的地址栏,相对安全;
- 请求参数长度没有限制;
<a href="/hello/ParamServlet?p1=v1&p2=v2">超链接</a>
<hr/>
<form action="/hello/ParamServlet" method="post">
参数1:<input type="text" name="p1"/><br/>
参数2:<input type="text" name="p2"/><br/>
<input type="submit" value="提交"/>
</form>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JMOKDxh2-1675781506853)(assets/wps4-1657548585008.jpg)]
下面是使用request获取请求参数的API:
String getParameter(String name):通过指定名称获取参数值;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String v1 = request.getParameter("p1");
String v2 = request.getParameter("p2");
System.out.println("p1=" + v1);
System.out.println("p2=" + v2);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String v1 = request.getParameter("p1");
String v2 = request.getParameter("p2");
System.out.println("p1=" + v1);
System.out.println("p2=" + v2);
}
String[] getParameterValues(String name):当多个参数名称相同时,可以使用方法来获取;
<a href="/hello/ParamServlet?name=zhangSan&name=liSi">超链接</a>
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String[] names = request.getParameterValues("name");
System.out.println(Arrays. toString (names));
}
Enumeration getParameterNames():获取所有参数的名字;
<form action="/hello/ParamServlet" method="post">
参数1:<input type="text" name="p1"/><br/>
参数2:<input type="text" name="p2"/><br/>
<input type="submit" value="提交" />
</form>
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Enumeration names = request.getParameterNames();
while (names.hasMoreElements()) {
System.out.println(names.nextElement());
}
}
Map getParameterMap():获取所有参数封装到Map中,其中key为参数名,value为参数值,因为一个参数名称可能有多个值,所以参数值是String[],而不是String。
<a href="/ParamServlet?p1=v1&p1=vv1&p2=v2&p2=vv2" >超链接</a>
Map<String,String[]> paramMap = request.getParameterMap();
for(String name : paramMap.keySet()) {
String[] values = paramMap.get(name);
System.out.println(name + ": " + Arrays. toString (values));
}
3.2.6 请求转发
客户一个请求,都表示由多个Servlet共同来处理一个请求。例如Servlet1来处理请求,然后Servlet1又转发给Servlet2来继续处理这个请求。
3.2.6.1 请求转发
在AServlet中,把请求转发到BServlet:
public class AServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("AServlet");
RequestDispatcher rd = request.getRequestDispatcher("/BServlet");
rd.forward(request, response);
}
}
public class BServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("BServlet");
}
}
3.2.6.2总结
- 如果在AServlet中请求转发到BServlet,那么在AServlet中就不允许再输出响应体,即不能再使用response.getWriter()和response.getOutputStream()向客户端输出,这一工作应该由BServlet来完成;如果是使用请求包含,那么没有这个限制;
- 请求转发虽然不能输出响应体,但还是可以设置响应头的,例如:response.setContentType(”text/html;charset=utf-8”);
- 请求请求大多是应用在Servlet中,转发目标大多是JSP页面;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RTL5s1l-1675781506854)(assets/wps5.jpg)]
3.3 response
3.3.1 response概述
response是Servlet.service方法的一个参数,类型为javax.servlet.http.HttpServletResponse。在客户端发出每个请求时,服务器都会创建一个response对象,并传入给Servlet.service()方法。response对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作。
response对象的功能分为以下四种:
-
设置响应头信息;
-
发送状态码;
-
设置响应正文;
-
重定向;
3.3.2 response响应正文
response是响应对象,向客户端输出响应正文(响应体)可以使用response的响应流,repsonse一共提供了两个响应流对象:
-
PrintWriter out = response.getWriter():获取字符流;
-
ServletOutputStream out = response.getOutputStream():获取字节流;
当然,如果响应正文内容为字符,那么使用response.getWriter(),如果响应内容是字节,例如下载时,那么可以使用response.getOutputStream()。
注意,在一个请求中,不能同时使用这两个流!也就是说,要么你使用repsonse.getWriter(),要么使用response.getOutputStream(),但不能同时使用这两个流。不然会抛出IllegalStateException异常。
-
字符编码
在使用response.getWriter()时需要注意默认字符编码为ISO-8859-1,如果希望设置字符流的字符编码为utf-8,可以使用response.setCharaceterEncoding(“utf-8”)来设置。这样可以保证输出给客户端的字符都是使用UTF-8编码的!
但客户端浏览器并不知道响应数据是什么编码的!如果希望通知客户端使用UTF-8来解读响应数据,那么还是使用response.setContentType(“text/html;charset=utf-8”)方法比较好,因为这个方法不只会调用response.setCharaceterEncoding(“utf-8”),还会设置content-type响应头,客户端浏览器会使用content-type头来解读响应数据。
-
缓冲区
response.getWriter()是PrintWriter类型,所以它有缓冲区,缓冲区的默认大小为8KB。也就是说,在响应数据没有输出8KB之前,数据都是存放在缓冲区中,而不会立刻发送到客户端。当Servlet执行结束后,服务器才会去刷新流,使缓冲区中的数据发送到客户端。
如果希望响应数据马上发送给客户端:
- 向流中写入大于8KB的数据;
- 调用response.flushBuffer()方法来手动刷新缓冲区;
3.3.3 设置响应头信息
可以使用response对象的setHeader()方法来设置响应头!使用该方法设置的响应头最终会发送给客户端浏览器!
-
response.setHeader(“content-type”, “text/html;charset=utf-8”):设置content-type响应头,该头的作用是告诉浏览器响应内容为html类型,编码为utf-8。而且同时会设置response的字符流编码为utf-8,即response.setCharaceterEncoding(“utf-8”);
-
response.setHeader(“Refresh”,“5; URL=http://www.baidu.com”):5秒后自动跳转到百度主页。
3.3.4 设置状态码及其他方法
-
response.setContentType(“text/html;charset=utf-8”):等同与调用response.setHeader(“content-type”, “text/html;charset=utf-8”);
-
response.setCharacterEncoding(“utf-8”):设置字符响应流的字符编码为utf-8;
-
response.setStatus(200):设置状态码;
-
response.sendError(404, “您要查找的资源不存在”):当发送错误状态码时,Tomcat会跳转到固定的错误页面去,但可以显示错误信息。
3.3.5 重定向
3.3.5.1 什么是重定向
当你访问http://www.sun.com时,你会发现浏览器地址栏中的URL会变成http://www.oracle.com/us/sun/index.htm,这就是重定向了。重定向是服务器通知浏览器去访问另一个地址,即再发出另一个请求。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g4IdKoyE-1675781506855)(assets/wps6.jpg)]
3.3.5.2 完成重定向
响应码为200表示响应成功,而响应码为302表示重定向。
public class AServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.sendRedirect("http://www.baidu.cn");
}
}
response.sendRedirect()方法会设置响应头为302,以设置Location响应头。
如果要重定向的URL是在同一个服务器内,那么可以使用相对路径,例如:
public class AServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.sendRedirect("/hello/BServlet");
}
}
重定向的URL地址为:http://localhost:8080/hello/BServlet。
3.3.5.3 重定向小结
重定向是两次请求;
重定向的URL可以是其他应用,不局限于当前应用;
重定向的响应头为302,并且必须要有Location响应头;
重定向就不要再使用response.getWriter()或response.getOutputStream()输出数据,不然可能会出现异常;
3.3.6 请求转发与重定向比较
请求转发是一个请求,而重定向是两个请求;
请求转发后浏览器地址栏不会有变化,而重定向会有变化,因为重定向是两个请求;
请求转发的目标只能是本应用中的资源,重定向的目标可以是其他应用;
请求转发对AServlet和BServlet的请求方法是相同的,即要么都是GET,要么都是POST,因为请求转发是一个请求;
重定向的第二个请求一定是GET;
请求转发是在服务端内部执行的,而重定向是在客户端执行的.
3.4 编码
3.4.1 请求编码
3.4.1.1 在页面中发出请求
通常向服务器发送请求数据都需要先请求一个页面,然后用户在页面中输入数据。页面中有超链接和表单,通过超链接和表单就可以向服务器发送数据了。
因为页面是服务器发送到客户端浏览器的,所以这个页面本身的编码由服务器决定。而用户在页面中输入的数据也是由页面本身的编码决定的。
index.html
<!DOCTYPE html>
<html>
<head>
<title>index.html</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<form action="/hello/servlet/AServlet">
名称:<input type="text" name="name" />
<input type="submit" value="提交" />
</form>
<a href="/hello/servlet/AServlet?name=传智">链接</a>
</body>
</html>
当用户在index.html页面中输入数据时,都是UTF-8列表的。因为这个页面本身就是UTF-8编码的!
3.4.1.2 GET请求解读编码
当客户端通过GET请求发送数据给服务器时,使用request.getParameter()获取的数据是被服务器误认为ISO-8859-1编码的,也就是说客户端发送过来的数据无论是UTF-8还是GBK,服务器都认为是ISO-8859-1,这就说明我们需要在使用request.getParameter()获取数据后,再转发成正确的编码。
例如客户端以UTF-8发送的数据,使用如下转码方式:
String name = request.getParameter(“name”);
name = new String(name.getBytes(“iso-8859-1”), “utf-8”);
第二种方式: 可以修改Tomcat的配置文件, 让所有发布到Tomcat的web项目都对Get请求进行编码:
<Connector URIEncoding= "UTF-8" connectionTimeout= "20000" port= "8888" protocol= "HTTP/1.1" redirectPort= "8443" />
注意: 在Tomcat8以上的版本,默认对get请求的编码设置为UTF-8,所以不需要再到配置文件去设置了
3.4.1.3 POST请求解读编码
当客户端通过POST请求发送数据给服务器时,可以在使用request.getParameter()获取请求参数之前先通过request.setCharacterEncoding()来指定编码,然后再使用reuqest.getParameter()方法来获取请求参数,那么就是用指定的编码来读取了。
也就是说,如果是POST请求,服务器可以指定编码!但如果没有指定编码,那么默认还是使用ISO-8859-1来解读。
request.setCharacterEncoding(“utf-8”);
String name = request.getParameter(“name”);
3.4.2 响应编码
响应:服务器发送给客户端数据!响应是由response对象来完成,如果响应的数据不是字符数据,那么就无需去考虑编码问题。当然,如果响应的数据是字符数据,那么就一定要考虑编码的问题了。
response.getWriter().print(“小明”);
上面代码因为没有设置repsonse.getWriter()字符流的编码,所以服务器使用默认的编码(ISO-8859-1)来处理,因为ISO-8859-1不支持中文,所以一定会出现乱码的。
所以在使用response.getWriter()发送数据之前,一定要设置response.getWriter()的编码,这需要使用
//response.setCharacterEncoding()方法:
response.setCharacterEncoding(“utf-8”);
response.getWriter().print(“小明”);
上面代码因为在使用response.getWriter()输出之前已经设置了编码,所以输出的数据为utf-8编码。但是,因为没有告诉浏览器使用什么编码来读取响应数据,所以很可能浏览器会出现错误的解读,那么还是会出现乱码的。当然,通常浏览器都支持来设置当前页面的编码,如果用户在看到编码时,去设置浏览器的编码,如果设置的正确那么乱码就会消失。但是我们不能让用户总去自己设置编码,而且应该直接通知浏览器,服务器发送过来的数据是什么编码,这样浏览器就直接使用服务器告诉他的编码来解读!这需要使用content-type响应头。
response.setContentType(“text/html;charset=utf-8”);
response.getWriter().print(“小明”);
上面代码使用setContentType()方法设置了响应头content-type编码为utf-8,这不只是在响应中添加了响应头,还等于调用了一次response.setCharacterEncoding(“utf-8”)
,也就是说,通过我们只需要调用一次response.setContentType(“text/html;charset=utf-8”)
即可,而无需再去调用response.setCharacterEncoding(“utf-8”)
了。
在静态页面中,使用来设置content-type响应头,例如:
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
4.JSP
4.1 JSP概述
4.1.1 什么是JSP
JSP(Java Server Pages)是JavaWeb服务器端的 动态资源 。它与html页面的作用是相同的, 显示数据和获取数据 。
4.1.2 JSP的组成
JSP = html + Java脚本(代码片段) + JSP动态标签
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-19FZABPq-1675781506856)(assets/wps7.jpg)]
4.2 JSP语法
4.2.1 JSP脚本
JSP脚本就是Java代码片段,它分为三种:
<%…%>:Java语句;
<%=…%>:Java表达式;
<%!..%>:Java定义类成员;
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<!DOCTYPE HTML>
<html>
<head>
<title>JSP演示</title>
</head>
<body>
<h1>JSP演示</h1>
<%
// Java语句
String s1 = "hello jsp";
// 不会输出到客户端,而是在服务器端的控制台打印
System.out.println(s1);
%>
<!-- 输出到客户端浏览器上 -->
输出变量:<%=s1 %><br/>
输出int类型常量:<%=100 %><br/>
输出String类型常量:<%="你好" %><br/> <br/>
</body>
</html>
4.2.2 内置对象out
out对象在JSP页面中无需创建就可以使用,它的作用是用来向客户端输出。
<body>
<h1>out.jsp</h1>
<%
//向客户端输出
out.print("你好!");
%>
</body>
其中
<%=…%>
与out.print()功能是相同的!它们都是向客户端输出,例如:
<%=s1%>
等同于<% out.print(s1); %>
<%=”hello”%>
等同于<% out.print(“hello”); %>
,也等同于直接在页面中写hello一样。
4.2.3 多个<%…%>可以通用
在一个JSP中多个<%…%>是相通的。例如:
<body>
<h1>out.jsp</h1>
<%
String s = "hello";
%>
<%
out.print(s);
%>
</body>
循环打印表格:
<body>
<h1>表格</h1>
<table border="1" width="50%">
<tr>
<th>序号</th>
<th>用户名</th>
<th>密码</th>
</tr>
<%
for(int i = 0; i < 10; i++) {
%>
<tr>
<td><%=i+1 %></td>
<td>user<%=i %></td>
<td><%=100 + 1 %></td>
</tr>
<%
}
%>
</table>
</body>
4.3 JSP九大内置对象
在JSP中无需创建就可以使用的9个对象,它们是:
-
out(JspWriter):等同与response.getWriter(),用来向客户端发送文本数据;
-
config(ServletConfig):对应“真身”中的ServletConfig;
-
page(当前JSP的真身类型):当前JSP页面的“this”,即当前对象;
-
pageContext(PageContext):页面上下文对象,它是最后一个没讲的域对象;
-
exception(Throwable):只有在错误页面中可以使用这个对象;
-
request(HttpServletRequest):即HttpServletRequest类的对象;
-
response(HttpServletResponse):即HttpServletResponse类的对象;
-
application(ServletContext):即ServletContext类的对象;
-
session(HttpSession):即HttpSession类的对象,不是每个JSP页面中都可以使用,如果在某个JSP页面中设置<%@page session=”false”%>,说明这个页面不能使用session。
在这9个对象中有很多是极少会被使用的,例如:config、page、exception基本不会使用。
5会话跟踪技术
5.1 什么是会话跟踪技术
我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器了。从双方接通电话那一刻起,会话就开始了,到某一方挂断电话表示会话结束。在通话过程中,你会向10086发出多个请求,那么这多个请求都在一个会话中。
在JavaWeb中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。
在一个会话的多个请求中共享数据,这就是会话跟踪技术。例如在一个会话中的请求如下:
-
请求银行主页;
-
请求登录(请求参数是用户名和密码);
-
请求转账(请求参数与转账相关的数据);
-
请求信誉卡还款(请求参数与还款相关的数据)。
在这上会话中当前用户信息必须在这个会话中共享的,因为登录的是张三,那么在转账和还款时一定是相对张三的转账和还款!这就说明我们必须在一个会话过程中有共享数据的能力。
5.2 会话路径技术使用Cookie或session完成
我们知道HTTP协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态。但HTTP协议中可以使用Cookie来完成会话跟踪!
在JavaWeb中,使用session来完成会话跟踪,session底层依赖Cookie技术。
5.3 Cookie
5.3.1 Cookie概述
5.3.1.1 什么叫Cookie
Cookie翻译成中文是小甜点,小饼干的意思。在HTTP中它表示服务器送给客户端浏览器的小甜点。其实Cookie就是一个键和一个值构成的,随着服务器端的响应发送给客户端浏览器。然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UsX8GNEV-1675781506864)(assets/wps1-1665588742249.jpg)]
Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!
5.3.1.2 Cookie规范
- Cookie大小上限为4KB;
- 一个服务器最多在客户端浏览器上保存20个Cookie;
- 一个浏览器最多保存300个Cookie;
上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。
5.3.1.3 Cookie的覆盖
如果服务器端发送重复的Cookie那么会覆盖原有的Cookie,例如客户端的第一个请求服务器端发送的Cookie是:Set-Cookie: a=A;第二请求服务器端发送的是:Set-Cookie: a=AA,那么客户端只留下一个Cookie,即:a=AA。
5.3.1.4 Cookie第一例
我们这个案例是,客户端访问AServlet,AServlet在响应中添加Cookie,浏览器会自动保存Cookie。然后客户端访问BServlet,这时浏览器会自动在请求中带上Cookie,BServlet获取请求中的Cookie打印出来。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BPSZ08ko-1675781506865)(assets/wps2-1665588742250.jpg)]
AServlet.java
/**
* 给客户端发送Cookie
* @author Administrator
*
*/
public class AServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
String id = UUID.randomUUID().toString();//生成一个随机字符串
Cookie cookie = new Cookie("id", id);//创建Cookie对象,指定名字和值
response.addCookie(cookie);//在响应中添加Cookie对象
response.getWriter().print("已经给你发送了ID");
}
}
BServlet.java
/**
* 获取客户端请求中的Cookie
* @author Administrator
*
*/
public class BServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
Cookie[] cs = request.getCookies();//获取请求中的Cookie
if(cs != null) {//如果请求中存在Cookie
for(Cookie c : cs) {//遍历所有Cookie
if(c.getName().equals("id")) {//获取Cookie名字,如果Cookie名字是id
response.getWriter().print("您的ID是:" + c.getValue());//打印Cookie值
}
}
}
}
}
5.3.2 Cookie的生命
5.3.2.1 什么是Cookie的生命
Cookie不只是有name和value,Cookie还是生命。所谓生命就是Cookie在客户端的有效时间,可以通过setMaxAge(int)来设置Cookie的有效时间。
cookie.setMaxAge(-1):cookie的maxAge属性的默认值就是-1,表示只在浏览器内存中存活。一旦关闭浏览器窗口,那么cookie就会消失。
cookie.setMaxAge(60*60):表示cookie对象可存活1小时。当生命大于0时,浏览器会把Cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie也会存活1小时;
cookie.setMaxAge(0):cookie生命等于0是一个特殊的值,它表示cookie被作废!也就是说,如果原来浏览器已经保存了这个Cookie,那么可以通过Cookie的setMaxAge(0)来删除这个Cookie。无论是在浏览器内存中,还是在客户端硬盘上都会删除这个Cookie。
5.3.2.2 浏览器查看Cookie
下面是浏览器查看Cookie的方式:
1. IE edge查看Cookie:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IhbekbO3-1675781506865)(assets/image-20220713213216053.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nxPK2maq-1675781506866)(assets/image-20220713213311634.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KWaY8jIk-1675781506866)(assets/image-20220713213337199.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lTJBivSX-1675781506867)(assets/image-20220713213357888.png)]
2. Google查看Cookie:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Z6d4KFd-1675781506868)(assets/image-20220713213521265.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dIwoP56n-1675781506868)(assets/image-20220713213624254.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7wgK2zJh-1675781506868)(assets/image-20220713213713373.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t1HpXz4m-1675781506869)(assets/image-20220713213726305.png)]
案例:显示上次访问时间
创建Cookie,名为lasttime,值为当前时间,添加到response中;
在AServlet中获取请求中名为lasttime的Cookie;
如果不存在输出“您是第一次访问本站”,如果存在输出“您上一次访问本站的时间是xxx”;
AServlet.java
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//创建Cookie对象,名为lasttime,值为当前时间
Cookie cookie = new Cookie("lasttime", new Date().toString());
//设置Cookie在客户端的有效时间为1小时
cookie.setMaxAge(60 * 60);
//添加Cookie到response中,保存到客户端
response.addCookie(cookie);
//获取请求中的Cookie
Cookie[] cs = request.getCookies();
String s = "您是首次访问本站!";
//如果请求中存在Cookie
if(cs != null) {
//循环遍历请求中的所有的Cookie
for(Cookie c : cs) {
//如果Cookie名为lasttime
if(c.getName().equals("lasttime")) {
//获取cookie中的值
s = "您上次的访问时间是:" + c.getValue();
}
}
}
response.getWriter().print(s);
}
5.3.2.3 Cookie的path
5.3.2.3.1 什么是Cookie的路径
现在有WEB应用A,向客户端发送了10个Cookie,这就说明客户端无论访问应用A的哪个Servlet都会把这10个Cookie包含在请求中!但是也许只有AServlet需要读取请求中的Cookie,而其他Servlet根本就不会获取请求中的Cookie。这说明客户端浏览器有时发送这些Cookie是多余的!
可以通过设置Cookie的path来指定浏览器,在访问什么样的路径时,包含什么样的Cookie。
5.3.2.3.2 Cookie路径与请求路径的关系
下面我们来看看Cookie路径的作用:
下面是客户端浏览器保存的3个Cookie的路径:
a: /cookietest;
b: /cookietest/servlet;
c: /cookietest/jsp;
下面是浏览器请求的URL:
A: http://localhost:8080/cookietest/AServlet;
B: http://localhost:8080/cookietest/servlet/BServlet;
C: http://localhost:8080/cookietest/jsp/CServlet;
请求A时,会在请求中包含a;
请求B时,会在请求中包含a、b;
请求C时,会在请求中包含a、c;
也就是说,请求路径如果包含了Cookie路径,那么会在请求中包含这个Cookie,否则不会请求中不会包含这个Cookie。
A请求的URL包含了“/cookietest”,所以会在请求中包含路径为“/cookietest”的Cookie;
B请求的URL包含了“/cookietest”,以及“/cookietest/servlet”,所以请求中包含路径为“/cookietest”和“/cookietest/servlet”两个Cookie;
C请求的URL包含了“/cookietest”,以及“/cookietest/jsp”,所以请求中包含路径为“/cookietest”和“/cookietest/jsp”两个Cookie;
5.3.2.3.3 设置Cookie的路径
设置Cookie的路径需要使用setPath()方法,例如:
cookie.setPath(“/cookietest/servlet”);
如果没有设置Cookie的路径,那么Cookie路径的默认值当前访问资源所在路径,例如:
访问http://localhost:8080/cookietest/AServlet时添加的Cookie默认路径为/cookietest;
访问http://localhost:8080/cookietest/servlet/BServlet时添加的Cookie默认路径为/cookietest/servlet;
访问http://localhost:8080/cookietest/jsp/BServlet时添加的Cookie默认路径为/cookietest/jsp;
5.3.2.4 案例:显示曾经浏览过的商品
index.jsp
<body>
<h1>商品列表</h1>
<a href="<%=path %>/GoodServlet?name=ThinkPad">ThinkPad</a><br/>
<a href="<%=path %>/GoodServlet?name=Lenovo">Lenovo</a><br/>
<a href="<%=path %>/GoodServlet?name=Apple">Apple</a><br/>
<a href="<%=path %>/GoodServlet?name=HP">HP</a><br/>
<a href="<%=path %>/GoodServlet?name=SONY">SONY</a><br/>
<a href="<%=path %>/GoodServlet?name=ACER">ACER</a><br/>
<a href="<%=path %>/GoodServlet?name=DELL">DELL</a><br/>
<hr/>
您浏览过的商品:
<%
Cookie[] cs = request.getCookies();
if(cs != null) {
for(Cookie c : cs) {
if(c.getName().equals("goods")) {
out.print(c.getValue());
}
}
}
%>
</body>
GoodServlet
public class GoodServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String goodName = request.getParameter("name");
String goods = CookieUtils.getCookValue(request, "goods");
if(goods != null) {
String[] arr = goods.split(", ");
Set<String> goodSet = new LinkedHashSet(Arrays.asList(arr));
goodSet.add(goodName);
goods = goodSet.toString();
goods = goods.substring(1, goods.length() - 1);
} else {
goods = goodName;
}
Cookie cookie = new Cookie("goods", goods);
cookie.setMaxAge(1 * 60 * 60 * 24);
response.addCookie(cookie);
response.sendRedirect("index.jsp");
}
}
CookieUtils
public class CookieUtils {
public static String getCookValue(HttpServletRequest request, String name) {
Cookie[] cs = request.getCookies();
if(cs == null) {
return null;
}
for(Cookie c : cs) {
if(c.getName().equals(name)) {
return c.getValue();
}
}
return null;
}
}
5.4 HttpSession
5.4.1 HttpSession概述
5.4.1.1 什么是HttpSesssion
javax.servlet.http.HttpSession
接口表示一个会话,我们可以把一个会话内需要共享的数据保存到HttSession对象中!
5.4.1.2 获取HttpSession对象
HttpSession request.getSesssion():如果当前会话已经有了session对象那么直接返回,如果当前会话还不存在会话,那么创建session并返回;
HttpSession request.getSession(boolean):当参数为true时,与requeset.getSession()相同。如果参数为false,那么如果当前会话中存在session则返回,不存在返回null;
5.4.1.3 HttpSession是域对象
我们已经学习过HttpServletRequest、ServletContext,它们都是域对象,现在我们又学习了一个HttpSession,它也是域对象。它们三个是Servlet中可以使用的域对象,而JSP中可以使用一个Page域对象
HttpServletRequest:一个请求创建一个request对象,所以在同一个请求中可以共享request,例如一个请求从AServlet转发到BServlet,那么AServlet和BServlet可以共享request域中的数据;
ServletContext:一个应用只创建一个ServletContext对象,所以在ServletContext中的数据可以在整个应用中共享,只要不启动服务器,那么ServletContext中的数据就可以共享;
HttpSession:一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享session中的数据;
下面是session的域方法:
void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:session.setAttribute(“xxx”, “XXX”),在session中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
Object getAttribute(String name):用来获取session中的数据,当前在获取之前需要先去存储才行,例如:String value = (String) session.getAttribute(“xxx”);,获取名为xxx的域属性;
void removeAttribute(String name):用来移除HttpSession中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
Enumeration getAttributeNames():获取所有域属性的名称;
5.4.2 登录案例
需要的页面:
login.jsp:登录页面,提供登录表单;
index1.jsp:主页,显示当前用户名称,如果没有登录,显示您还没登录;
index2.jsp:主页,显示当前用户名称,如果没有登录,显示您还没登录;
Servlet:
LoginServlet:在login.jsp页面提交表单时,请求本Servlet。在本Servlet中获取用户名、密码进行校验,如果用户名、密码错误,显示“用户名或密码错误”,如果正确保存用户名session中,然后重定向到index1.jsp;
当用户没有登录时访问index1.jsp或index2.jsp,显示“您还没有登录”。如果用户在login.jsp登录成功后到达index1.jsp页面会显示当前用户名,而且不用再次登录去访问index2.jsp也会显示用户名。因为多次请求在一个会话范围,index1.jsp和index2.jsp都会到session中获取用户名,session对象在一个会话中是相同的,所以都可以获取到用户名!
login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>login.jsp</title>
</head>
<body>
<h1>login.jsp</h1>
<hr/>
<form action="/LoginServlet" method="post">
用户名:<input type="text" name="username" /><br/>
<input type="submit" value="Submit"/>
</form>
</body>
</html>
index1.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>index1.jsp</title>
</head>
<body>
<h1>index1.jsp</h1>
<%
String username = (String)session.getAttribute("username");
if(username == null) {
out.print("您还没有登录!");
} else {
out.print("用户名:" + username);
}
%>
<hr/>
<a href="/index2.jsp">index2</a>
</body>
</html>
index2.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>index2.jsp</title>
</head>
<body>
<h1>index2.jsp</h1>
<%
String username = (String)session.getAttribute("username");
if(username == null) {
out.print("您还没有登录!");
} else {
out.print("用户名:" + username);
}
%>
<hr/>
<a href="/index1.jsp">index1</a>
</body>
</html>
LoginServlet
public class LoginServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
if(username.equalsIgnoreCase("admin")) {
response.getWriter().print("用户名或密码错误!");
} else {
HttpSession session = request.getSession();
//在session中保存用户名
session.setAttribute("username", username);
//重定向到index1.jsp
response.sendRedirect("/index1.jsp");
}
}
}
5.4.3 session的实现原理
session底层是依赖Cookie的!我们来理解一下session的原理吧!
当我首次去银行时,因为还没有账号,所以需要开一个账号,我获得的是银行卡,而银行这边的数据库中留下了我的账号,我的钱是保存在银行的账号中,而我带走的是我的卡号。
当我再次去银行时,只需要带上我的卡,而无需再次开一个账号了。只要带上我的卡,那么我在银行操作的一定是我的账号!
当首次使用session时,服务器端要创建session,session是保存在服务器端,而给客户端的session的id(一个cookie中保存了sessionId)。客户端带走的是sessionId,而数据是保存在session中。
当客户端再次访问服务器时,在请求中会带上sessionId,而服务器会通过sessionId找到对应的session,而无需再创建新的session。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ThjjSVTq-1675781506869)(assets/wps8-1665588799705.jpg)]
5.4.4 session与浏览器
session保存在服务器,而sessionId通过Cookie发送给客户端,但这个Cookie的生命不-1,即只在浏览器内存中存在,也就是说如果用户关闭了浏览器,那么这个Cookie就丢失了。
当用户再次打开浏览器访问服务器时,就不会有sessionId发送给服务器,那么服务器会认为你没有session,所以服务器会创建一个session,并在响应中把sessionId中到Cookie中发送给客户端。
你可能会说,那原来的session对象会怎样?当一个session长时间没人使用的话,服务器会把session删除了!这个时长在Tomcat中配置是30分钟,可以在${CATALANA}/conf/web.xml找到这个配置,当然你也可以在自己的web.xml中覆盖这个配置!
web.xml
<session-config>
<session-timeout>30</session-timeout>
</session-config>
session失效时间也说明一个问题!如果你打开网站的一个页面开始长时间不动,超出了30分钟后,再去点击链接或提交表单时你会发现,你的session已经丢失了!
5.4.5 session其他常用API
String getId():获取sessionId;
int getMaxInactiveInterval():获取session可以的最大不活动时间(秒),默认为30分钟。当session在30分钟内没有使用,那么Tomcat会在session池中移除这个session;
void setMaxInactiveInterval(int interval):设置session允许的最大不活动时间(秒),如果设置为1秒,那么只要session在1秒内不被使用,那么session就会被移除;
long getCreationTime():返回session的创建时间,返回值为当前时间的毫秒值;
long getLastAccessedTime():返回session的最后活动时间,返回值为当前时间的毫秒值;
void invalidate():让session失效!调用这个方法会被session失效,当session失效后,客户端再次请求,服务器会给客户端创建一个新的session,并在响应中给客户端新session的sessionId;
boolean isNew():查看session是否为新。当客户端第一次请求时,服务器为客户端创建session,但这时服务器还没有响应客户端,也就是还没有把sessionId响应给客户端时,这时session的状态为新。
6. MVC
6.1 MVC设计模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0sqt3Vsg-1675781506870)(assets/wps16.png)]
MVC设计模式
MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
MVC模式最早为Trygve Reenskaug提出,为施乐帕罗奥多研究中心(Xerox PARC)的Smalltalk语言发明的一种软件设计模式。
MVC可对程序的后期维护和扩展提供了方便,并且使程序某些部分的重用提供了方便。而且MVC也使程序简化,更加直观。
控制器Controller:对请求进行处理,负责请求转发;
视图View:界面设计人员进行图形界面设计;
模型Model:程序编写程序应用的功能(实现算法等等)、数据库管理;
注意,MVC不是Java的东西,几乎现在所有B/S结构的软件都采用了MVC设计模式。但是要注意,MVC在B/S结构软件并没有完全实现,例如在我们今后的B/S软件中并不会有事件驱动!
6.2 JavaWeb经典三层框架
我们常说的三层框架是由JavaWeb提出的,也就是说这是JavaWeb独有的!
所谓三层是表述层(WEB层)、业务逻辑层(service),以及数据访问层(dao)。
WEB层:包含JSP和Servlet等与WEB相关的内容;
业务层:业务层中不包含JavaWeb API,它只关心业务逻辑;
数据层:封装了对数据库的访问细节;
注意,在业务层中不能出现JavaWeb API,例如request、response等。也就是说,业务层代码是可重用的,甚至可以应用到非Web环境中。业务层的每个方法可以理解成一个万能,例如转账业务方法。业务层依赖数据层,而Web层依赖业务层!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Qfoy1EQ-1675781506871)(assets/wps20.jpg)]
7. 过滤器(Filter)
7.1过滤器概述
1 什么是过滤器
过滤器JavaWeb三大组件之一,它与Servlet很相似!不它过滤器是用来拦截请求的,而不是处理请求的。
当用户请求某个Servlet时,会先执行部署在这个请求上的Filter,如果Filter“放行”,那么会继承执行用户请求的Servlet;如果Filter不“放行”,那么就不会执行用户请求的Servlet。
其实可以这样理解,当用户请求某个Servlet时,Tomcat会去执行注册在这个请求上的Filter,然后是否“放行”由Filter来决定。可以理解为,Filter来决定是否能调用Servlet!当执行完成Servlet的代码后,还会执行Filter后面的代码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4JghSrP-1675781506871)(assets/wps21.jpg)]
2 过滤器之hello world
其实过滤器与Servlet很相似,我们回忆一下如果写的第一个Servlet应用!写一个类,实现Servlet接口!没错,写过滤器就是写一个类,实现Filter接口。
public class HelloFilter implements Filter {
//过滤器创建之后马上调用
public void init(FilterConfig filterConfig) throws ServletException {}
//当访问被拦截资源时,doFilter()方法会被调用!我们先不去管它的参数是什么作用!
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("Hello Filter");
}
//过滤器销毁之前调用
public void destroy() {}
}
第二步也与Servlet一样,在web.xml文件中部署Filter:
<filter>
<filter-name>helloFilter</filter-name>
<filter-class>cn.itcast.filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>helloFilter</filter-name>
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
应该没有问题吧,都可以看懂吧!
OK了,现在可以尝试去访问index.jsp页面了,看看是什么效果!
当用户访问index.jsp页面时,会执行HelloFilter的doFilter()方法!在我们的示例中,index.jsp页面是不会被执行的,如果想执行index.jsp页面,那么我们需要放行!
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//执行index.jsp之前执行
System.out.println("filter start...");
//放行,去执行index.jsp
chain.doFilter(request, response);
//在执行index.jsp后执行这一句
System.out.println("filter end...");
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xQeoFipm-1675781506872)(assets/wps22.jpg)]
有很多同学总是错误的认为,一个请求在给客户端输出之后就算是结束了,这是不对的!其实很多事情都需要在给客户端响应之后才能完成!
7.2 过滤器详细
1 过滤器的生命周期
我们已经学习过Servlet的生命周期,那么Filter的生命周期也就没有什么难度了!
init(FilterConfig):在服务器启动时会创建Filter实例,并且每个类型的Filter只创建一个实例,从此不再创建!在创建完Filter实例后,会马上调用init()方法完成初始化工作,这个方法只会被执行一次;
doFilter(ServletRequest req,ServletResponse res,FilterChain chain):这个方法会在用户每次访问“目标资源(pattern>index.jsp)”时执行,如果需要“放行”,那么需要调用FilterChain的doFilter(ServletRequest,ServletResponse)方法,如果不调用FilterChain的doFilter()方法,那么目标资源将无法执行;
destroy():服务器会在创建Filter对象之后,把Filter放到缓存中一直使用,通常不会销毁它。一般会在服务器关闭时销毁Filter对象,在销毁Filter对象之前,服务器会调用Filter对象的destory()方法。
2 FilterConfig
你已经看到了吧,Filter接口中的init()方法的参数类型为FilterConfig类型。它的功能与ServletConfig相似,与web.xml文件中的配置信息对应。下面是FilterConfig的功能介绍:
ServletContext getServletContext():获取ServletContext的方法;
String getFilterName():获取Filter的配置名称;与元素对应;
String getInitParameter(String name):获取Filter的初始化配置,与元素对应;
Enumeration getInitParameterNames():获取所有初始化参数的名称。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NcjZk25E-1675781506872)(assets/wps23.jpg)]
3 FilterChain
doFilter()方法的参数中有一个类型为FilterChain的参数,它只有一个方法:doFilter(ServletRequest,ServletResponse)
。
前面我们说doFilter()方法的放行,让请求流访问目标资源!但这么说不严密,其实调用该方法的意思是,“我(当前Filter)”放行了,但不代表其他人(其他过滤器)也放行。
也就是说,一个目标资源上,可能部署了多个过滤器,就好比在你去北京的路上有多个打劫的匪人(过滤器),而其中第一伙匪人放行了,但不代表第二伙匪人也放行了,所以调用FilterChain类的doFilter()方法表示的是执行下一个过滤器的doFilter()方法,或者是执行目标资源!
如果当前过滤器是最后一个过滤器,那么调用chain.doFilter()方法表示执行目标资源,而不是最后一个过滤器,那么chain.doFilter()表示执行下一个过滤器的doFilter()方法。
4 多个过滤器执行顺序
一个目标资源可以指定多个过滤器,过滤器的执行顺序是在web.xml文件中的部署顺序:
<filter>
<filter-name>myFilter1</filter-name>
<filter-class>org.csmf.filter.MyFilter1</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter1</filter-name>
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>myFilter2</filter-name>
<filter-class>org.csmf.filter.MyFilter2</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter2</filter-name>
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
因为MyFilter1配置在前面,所以先执行MyFilter1的doFilter()方法。
public class MyFilter1 extends HttpFilter {
public void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("filter1 start...");
chain.doFilter(request, response);//放行,执行MyFilter2的doFilter()方法
System.out.println("filter1 end...");
}
}
public class MyFilter2 extends HttpFilter {
public void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("filter2 start...");
chain.doFilter(request, response);//放行,执行目标资源
System.out.println("filter2 end...");
}
}
当有用户访问index.jsp页面时,输出结果如下:
filter1 start…
filter2 start…
index.jsp
filter2 end…
filter1 end…
5 四种拦截方式
我们来做个测试,写一个过滤器,指定过滤的资源为b.jsp,然后我们在浏览器中直接访问b.jsp,你会发现过滤器执行了!
但是,当我们在a.jsp中request.getRequestDispathcer(“/b.jsp”).forward(request,response)时,就不会再执行过滤器了!也就是说,默认情况下,只能直接访问目标资源才会执行过滤器,而forward执行目标资源,不会执行过滤器!
public class MyFilter extends HttpFilter {
public void doFilter(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("myfilter...");
chain.doFilter(request, response);
}
}
<filter>
<filter-name>myfilter</filter-name>
<filter-class>org.csmf.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myfilter</filter-name>
<url-pattern>/b.jsp</url-pattern>
</filter-mapping>
<body>
<h1>b.jsp</h1>
</body>
<h1>a.jsp</h1>
<%
request.getRequestDispatcher("/b.jsp").forward(request, response);
%>
</body>
http://localhost:8080/filtertest/b.jsp -->直接访问b.jsp时,会执行过滤器内容;
http://localhost:8080/filtertest/a.jsp --> 访问a.jsp,但a.jsp会forward到b.jsp,这时就不会执行过滤器!
其实过滤器有四种拦截方式!分别是:REQUEST、FORWARD、INCLUDE、ERROR。
REQUEST:直接访问目标资源时执行过滤器。包括:在地址栏中直接访问、表单提交、超链接、重定向,只要在地址栏中可以看到目标资源的路径,就是REQUEST;
FORWARD:转发访问执行过滤器。包括RequestDispatcher#forward()方法、jsp:forward标签都是转发访问;
INCLUDE:包含访问执行过滤器。包括RequestDispatcher#include()方法、jsp:include标签都是包含访问;
ERROR:当目标资源在web.xml中配置为中时,并且真的出现了异常,转发到目标资源时,会执行过滤器。
可以在<filter-mapping>
中添加0~n个<dispatcher>
子元素,来说明当前访问的拦截方式。
<filter-mapping>
<filter-name>myfilter</filter-name>
<url-pattern>/b.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
b.jsp为目标资源,当直接请求b.jsp时,会执行过滤器
当转发到b.jsp页面时,会执行过滤器
<filter-mapping>
<filter-name>myfilter</filter-name>
<url-pattern>/b.jsp</url-pattern>
</filter-mapping>
当没有给出拦截方式时,那么默认为REQUEST
<filter-mapping>
<filter-name>myfilter</filter-name>
<url-pattern>/b.jsp</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
当转发到b.jsp页面时,会执行过滤器!因为已经给出了
<dispatcher>FORWARD</dispatcher>
了,那么就没有默认的REQUEST了!所以只有在转发到b.jsp时才会执行过滤,而转发到b.jsp时,不会执行b.jsp 其实最为常用的就是REQUEST和FORWARD两种拦截方式,而INCLUDE和ERROR都比较少用!
6 过滤器的应用场景
过滤器的应用场景:
执行目标资源之前做预处理工作,例如设置编码,这种试通常都会放行,只是在目标资源执行之前做一些准备工作;
通过条件判断是否放行,例如校验当前用户是否已经登录,或者用户IP是否已经被禁用;
在目标资源执行后,做一些后续的特殊处理工作,例如把目标资源输出的数据进行处理;
7 设置目标资源
在web.xml文件中部署Filter时,可以通过“*”
来执行目标资源:
<filter-mapping>
<filter-name>myfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这一特性与Servlet完全相同!通过这一特性,我们可以在用户访问敏感资源时,执行过滤器,例如:
<url-pattern>/admin/*<url-pattern>
,可以把所有管理员才能访问的资源放到/admin路径下,这时可以通过过滤器来校验用户身份。
还可以为<filter-mapping>
指定目标资源为某个Servlet,例如:
<servlet>
<servlet-name>myservlet</servlet-name>
<servlet-class>com.suke.servlet.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myservlet</servlet-name>
<url-pattern>/abc</url-pattern>
</servlet-mapping>
<filter>
<filter-name>myfilter</filter-name>
<filter-class>cn.itcast.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myfilter</filter-name>
<servlet-name>myservlet</servlet-name>
</filter-mapping>
当用户访问http://localhost:8080/filtertest/abc时,会执行名字为myservlet的Servlet,这时会执行过滤器。
7.3 案例
粗粒度权限控制(拦截是否登录、拦截用户名admin权限)
1 说明
我们给出三个页面:index.jsp、user.jsp、admin.jsp。
index.jsp:谁都可以访问,没有限制;
user.jsp:只有登录用户才能访问;
admin.jsp:只有管理员才能访问。
2 分析
设计User类:username、password、grade,其中grade表示用户等级,1表示普通用户,2表示管理员用户。
当用户登录成功后,把user保存到session中。
创建LoginFilter,它有两种过滤方式:
-
如果访问的是user.jsp,查看session中是否存在user;
-
如果访问的是admin.jsp,查看session中是否存在user,并且user的grade等于2。
3 代码
User.java
public class User {
private String username;
private String password;
private int grade;
//省略get/set
}
为了方便,这里就不使用数据库了,所以我们需要在UserService中创建一个Map,用来保存所有用户。Map中的key中用户名,value为User对象。
UserService.java
public class UserService {
private static Map<String,User> users = new HashMap<String,User>();
static {
users.put("zhangSan", new User("zhangSan", "123", 1));
users.put("liSi", new User("liSi", "123", 2));
}
public User login(String username, String password) {
User user = users.get(username);
if(user == null) return null;
return user.getPassword().equals(password) ? user : null;
}
}
login.jsp
<body>
<h1>登录</h1>
<p style="font-weight: 900; color: red">${msg }</p>
<form action="<c:url value='/LoginServlet'/>" method="post">
用户名:<input type="text" name="username"/><br/>
密 码:<input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
index.jsp
<body>
<h1>主页</h1>
<h3>${user.username }</h3>
<hr/>
<a href="<c:url value='/login.jsp'/>">登录</a><br/>
<a href="<c:url value='/user/user.jsp'/>">用户页面</a><br/>
<a href="<c:url value='/admin/admin.jsp'/>">管理员页面</a>
</body>
/user/user.jsp
<body>
<h1>用户页面</h1>
<h3>${user.username }</h3>
<hr/>
</body>
/admin/admin.jsp
<body>
<h1>管理员页面</h1>
<h3>${user.username }</h3>
<hr/>
</body>
LoginServlet
public class LoginServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
UserService userService = new UserService();
User user = userService.login(username, password);
if(user == null) {
request.setAttribute("msg", "用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request, response);
} else {
request.getSession().setAttribute("user", user);
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
}
}
LoginUserFilter.java
public class LoginUserFilter implements Filter {
public void destroy() {}
public void init(FilterConfig fConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
HttpServletRequest req = (HttpServletRequest) request;
User user = (User) req.getSession().getAttribute("user");
if(user == null) {
response.getWriter().print("您还没有登录");
return;
}
chain.doFilter(request, response);
}
}
<filter>
<display-name>LoginUserFilter</display-name>
<filter-name>LoginUserFilter</filter-name>
<filter-class>org.csmf.filter.LoginUserFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginUserFilter</filter-name>
<url-pattern>/user/*</url-pattern>
</filter-mapping>
LoginAdminFilter.java
public class LoginAdminFilter implements Filter {
public void destroy() {}
public void init(FilterConfig fConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
HttpServletRequest req = (HttpServletRequest) request;
User user = (User) req.getSession().getAttribute("user");
if(user == null) {
response.getWriter().print("您还没有登录!");
return;
}
if(user.getGrade() < 2) {
response.getWriter().print("您的等级不够!");
return;
}
chain.doFilter(request, response);
}
}
<filter>
<display-name>LoginAdminFilter</display-name>
<filter-name>LoginAdminFilter</filter-name>
<filter-class>org.csmf.filter.LoginAdminFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginAdminFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
8. 文件上传概述
1 文件上传的作用
在现实中,我们经常需要客户端向服务器上传一些资源,比如,我们使用QQ时,可以设置头像,那我们就需要从我们本地电脑(客户端)向QQ服务器来上传我们喜欢的头像.还比如现在很流行的百度云盘,也需要我们往百度云盘的服务器上传资源,比如小电影,小图片等等.
2 文件上传对页面的要求
上传文件的要求比较多,需要记一下:
必须使用表单,而不能是超链接;
表单的method必须是POST,而不能是GET;
表单的enctype必须是multipart/form-data;
在表单中添加file表单字段,即<input type="file”…/>
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
文件1:<input type="file" name="file1"/><br/>
文件2:<input type="file" name="file2"/><br/>
<input type="submit" value="提交"/>
</form>
“文件上传表单”和“普通文本表单”的区别:
文件上传表单的enctype=”multipart/form-data”,表示多部件表单数据;
普通文本表单可以不设置enctype属性:
当method=”post”时,enctype的默认值为application/x-www-form-urlencoded,表示使用url编码正文;
当method=”get”时,enctype的默认值为null,没有正文,所以就不需要enctype了。
3 文件上传对Servlet的要求
当提交的表单是文件上传表单时,那么对Servlet也是有要求的。
首先我们要肯定一点,文件上传表单的数据也是被封装到request对象中的。
request.getParameter(String)方法获取指定的表单字段字符内容,但文件上传表单已经不在是字符内容,而是字节内容,所以失效。
这时可以使用request的getInputStream()方法获取ServletInputStream对象,它是InputStream的子类,这个ServletInputStream对象对应整个表单的正文部分(从第一个分隔线开始,到最后),这说明我们需要的解析流中的数据。当然解析它是很麻烦的一件事情,而Apache已经帮我们提供了解析它的工具**:commons-fileupload。**
1 fileupload概述
fileupload是由apache的commons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream()。
fileupload组件需要的JAR包有:
commons-fileupload.jar,核心包;
commons-io.jar,依赖包。
2 fileupload简单应用
fileupload的核心类有:DiskFileItemFactory、ServletFileUpload、FileItem。
使用fileupload组件的步骤如下:
创建工厂类DiskFileItemFactory对象:DiskFileItemFactory factory = new DiskFileItemFactory()
使用工厂创建解析器对象:ServletFileUpload fileUpload = new ServletFileUpload(factory)
使用解析器来解析request对象:List list = fileUpload.parseRequest(request)
隆重介绍FileItem类,它才是我们最终要的结果。一个FileItem对象对应一个表单项(表单字段)。一个表单中存在文件字段和普通字段,可以使用FileItem类的isFormField()方法来判断表单字段是否为普通字段,如果不是普通字段,那么就是文件字段了。
String getName():获取文件字段的文件名称;
String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;
String getFieldName():获取字段名称,例如:,返回的是username;
String getContentType():获取上传的文件的类型,例如:text/plain。
int getSize():获取上传文件的大小;
boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;
InputStream getInputStream():获取上传文件对应的输入流;
void write(File):把上传的文件保存到指定文件中。
3 简单上传示例
写一个简单的上传示例:
表单包含一个用户名字段,以及一个文件字段;
Servlet保存上传的文件到uploads目录,显示用户名,文件名,文件大小,文件类型。
第一步:
完成index.jsp,只需要一个表单。注意表单必须是post的,而且enctype必须是mulitpart/form-data的。
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
文件1:<input type="file" name="file1"/><br/>
<input type="submit" value="提交"/>
</form>
第二步:
完成FileUploadServlet
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 因为要使用response打印,所以设置其编码
response.setContentType("text/html;charset=utf-8");
// 创建工厂
DiskFileItemFactory dfif = new DiskFileItemFactory();
// 使用工厂创建解析器对象
ServletFileUpload fileUpload = new ServletFileUpload(dfif);
try {
// 使用解析器对象解析request,得到FileItem列表
List<FileItem> list = fileUpload.parseRequest(request);
// 遍历所有表单项
for(FileItem fileItem : list) {
// 如果当前表单项为普通表单项
if(fileItem.isFormField()) {
// 获取当前表单项的字段名称
String fieldName = fileItem.getFieldName();
// 如果当前表单项的字段名为username
if(fieldName.equals("username")) {
// 打印当前表单项的内容,即用户在username表单项中输入的内容
response.getWriter().print("用户名:" + fileItem.getString() + "<br/>");
}
} else {//如果当前表单项不是普通表单项,说明就是文件字段
String name = fileItem.getName();//获取上传文件的名称
// 如果上传的文件名称为空,即没有指定上传文件
if(name == null || name.isEmpty()) {
continue;
}
// 获取真实路径,对应${项目目录}/uploads,当然,这个目录必须存在
String savepath = this.getServletContext().getRealPath("/uploads");
// 通过uploads目录和文件名称来创建File对象
File file = new File(savepath, name);
// 把上传文件保存到指定位置
fileItem.write(file);
// 打印上传文件的名称
response.getWriter().print("上传文件名:" + name + "<br/>");
// 打印上传文件的大小
response.getWriter().print("上传文件大小:" + fileItem.getSize() + "<br/>");
// 打印上传文件的类型
response.getWriter().print("上传文件类型:" + fileItem.getContentType() + "<br/>");
}
}
} catch (Exception e) {
throw new ServletException(e);
}
}
leUpload(factory)
- 使用解析器来解析request对象:List list = fileUpload.parseRequest(request)
隆重介绍FileItem类,它才是我们最终要的结果。一个FileItem对象对应一个表单项(表单字段)。一个表单中存在文件字段和普通字段,可以使用FileItem类的isFormField()方法来判断表单字段是否为普通字段,如果不是普通字段,那么就是文件字段了。
String getName():获取文件字段的文件名称;
String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;
String getFieldName():获取字段名称,例如:,返回的是username;
String getContentType():获取上传的文件的类型,例如:text/plain。
int getSize():获取上传文件的大小;
boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;
InputStream getInputStream():获取上传文件对应的输入流;
void write(File):把上传的文件保存到指定文件中。
3 简单上传示例
写一个简单的上传示例:
表单包含一个用户名字段,以及一个文件字段;
Servlet保存上传的文件到uploads目录,显示用户名,文件名,文件大小,文件类型。
第一步:
完成index.jsp,只需要一个表单。注意表单必须是post的,而且enctype必须是mulitpart/form-data的。
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
文件1:<input type="file" name="file1"/><br/>
<input type="submit" value="提交"/>
</form>
第二步:
完成FileUploadServlet
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 因为要使用response打印,所以设置其编码
response.setContentType("text/html;charset=utf-8");
// 创建工厂
DiskFileItemFactory dfif = new DiskFileItemFactory();
// 使用工厂创建解析器对象
ServletFileUpload fileUpload = new ServletFileUpload(dfif);
try {
// 使用解析器对象解析request,得到FileItem列表
List<FileItem> list = fileUpload.parseRequest(request);
// 遍历所有表单项
for(FileItem fileItem : list) {
// 如果当前表单项为普通表单项
if(fileItem.isFormField()) {
// 获取当前表单项的字段名称
String fieldName = fileItem.getFieldName();
// 如果当前表单项的字段名为username
if(fieldName.equals("username")) {
// 打印当前表单项的内容,即用户在username表单项中输入的内容
response.getWriter().print("用户名:" + fileItem.getString() + "<br/>");
}
} else {//如果当前表单项不是普通表单项,说明就是文件字段
String name = fileItem.getName();//获取上传文件的名称
// 如果上传的文件名称为空,即没有指定上传文件
if(name == null || name.isEmpty()) {
continue;
}
// 获取真实路径,对应${项目目录}/uploads,当然,这个目录必须存在
String savepath = this.getServletContext().getRealPath("/uploads");
// 通过uploads目录和文件名称来创建File对象
File file = new File(savepath, name);
// 把上传文件保存到指定位置
fileItem.write(file);
// 打印上传文件的名称
response.getWriter().print("上传文件名:" + name + "<br/>");
// 打印上传文件的大小
response.getWriter().print("上传文件大小:" + fileItem.getSize() + "<br/>");
// 打印上传文件的类型
response.getWriter().print("上传文件类型:" + fileItem.getContentType() + "<br/>");
}
}
} catch (Exception e) {
throw new ServletException(e);
}
}