JSP与Servlet

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配置正确。

启动问题:

  1. 点击startup.bat后窗口一闪即消失:
  • 检查JAVA_HOME环境变量配置是否正确;
  1. 启动报错:
  • 暴力:找到占用的端口号,并且找到对应的进程,杀死该进程

    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 部署
  1. 直接将项目放到webapps目录下即可
  • 项目名就是我们该项目的访问路径

  • 简单部署:将项目打成一个war包,再将war包放置到webapps目录下。war会自动解压

    虚拟目录: 项目名或者war名字

  1. 配置conf/server.xml文件,在<Host>标签体中配置
<Context docBase="D:/hello" path="/hehe" />
  • docBase:项目存放的路径
  • path:虚拟目录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dFGOdJWQ-1675781506833)(assets/image-20220710234741903.png)]

  1. 在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项目
  1. 在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包,连接数据库的驱动等等很多
  1. 配置文件夹路径

    添加这两个文件夹之后,修改文件夹路径,不然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)]

  1. 添加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。它必须是以“/”开头!

  1. 可以在<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。

  1. 还可以在<url-pattern>中使用通配符,所谓通配符就是星号“ ”,星号可以匹配任何URL前缀或后缀,使用通配符可以命名一个Servlet绑定一组URL,例如:
  • <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的配置.

步骤:

  1. 创建JavaEE项目,选择Sevlet的版本3.0以上,可以不创建web.xml
  2. 定义一个Servlet类
  3. 复写请求的方法
  4. 在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>
  1. b.jsp为目标资源,当直接请求b.jsp时,会执行过滤器

  2. 当转发到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 过滤器的应用场景

过滤器的应用场景:

  1. 执行目标资源之前做预处理工作,例如设置编码,这种试通常都会放行,只是在目标资源执行之前做一些准备工作;

  2. 通过条件判断是否放行,例如校验当前用户是否已经登录,或者用户IP是否已经被禁用;

  3. 在目标资源执行后,做一些后续的特殊处理工作,例如把目标资源输出的数据进行处理;

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 文件上传对页面的要求

上传文件的要求比较多,需要记一下:

  1. 必须使用表单,而不能是超链接;

  2. 表单的method必须是POST,而不能是GET;

  3. 表单的enctype必须是multipart/form-data;

  4. 在表单中添加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组件的步骤如下:

  1. 创建工厂类DiskFileItemFactory对象:DiskFileItemFactory factory = new DiskFileItemFactory()

  2. 使用工厂创建解析器对象:ServletFileUpload fileUpload = new ServletFileUpload(factory)

  3. 使用解析器来解析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 简单上传示例

写一个简单的上传示例:

  1. 表单包含一个用户名字段,以及一个文件字段;

  2. 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)

  1. 使用解析器来解析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 简单上传示例

写一个简单的上传示例:

  1. 表单包含一个用户名字段,以及一个文件字段;

  2. 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);
		} 
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值