Tomcat服务器、Servlet生命周期、上传下载文件、使用XHR请求数据、注解使用

Servlet

认识Tomcat服务器

在学习之前,应该先了解Tomcat服务器的基本知识,才能学习之后内容

Tomcat Home

Tomcat(汤姆猫)就是一个典型的Web应用服务器软件,通过运行Tomcat服务器,我们就可以快速部署我们的Web项目,并交由Tomcat进行管理,我们只需要直接通过浏览器访问我们的项目即可。

那么首先,我们需要进行一个简单的环境搭建,我们需要在Tomcat官网下载最新的Tomcat服务端程序:https://tomcat.apache.org/download-10.cgi(下载速度可能有点慢)

  • 下载:64-bit Windows zip

下载完成后,解压,并放入桌面,接下来需要配置一下环境变量,打开高级系统设置,打开环境变量,添加一个新的系统变量,变量名称为JRE_HOME,填写JDK的安装目录+/jre,比如Zulujdk默认就是:C:\Program Files\Zulu\zulu-8\jre

如果找不到jre文件,那么就直接设定jdk所在的位置为环境变量也行

image-20230521151422862

设置完成后,我们进入tomcat文件夹bin目录下,并在当前位置打开CMD窗口,将startup.sh拖入窗口按回车运行,如果环境变量配置有误,会提示,若没问题,服务器则正常启动。

如果出现乱码,说明编码格式配置有问题,我们修改一下服务器的配置文件,打开conf文件夹,找到logging.properties文件,这就是日志的配置文件(我们在前面已经给大家讲解过了)将ConsoleHandler的默认编码格式修改为GBK编码格式:

image-20230521145452288

java.util.logging.ConsoleHandler.encoding = GBK

现在重新启动服务器,就可以正常显示中文了

image-20230521151340062

服务器启动成功之后,不要关闭,我们打开浏览器,在浏览器中访问:http://localhost:8080/,Tomcat服务器默认是使用8080端口(可以在配置文件中修改),访问成功说明我们的Tomcat环境已经部署成功了

image-20230521151507657

整个Tomcat目录下,已经认识了bin目录(所有可执行文件,包括启动和关闭服务器的脚本)以及conf目录(服务器配置文件目录),接着来看其他的文件夹:

  • lib目录:Tomcat服务端运行的一些依赖,不用关心。
  • logs目录:所有的日志信息都在这里。
  • temp目录:存放运行时产生的一些临时文件,不用关心。
  • work目录:工作目录,Tomcat会将jsp文件转换为java文件(我们后面会讲到,这里暂时不提及)
  • webapp目录:所有的Web项目都在这里,每个文件夹都是一个Web应用程序:

我们发现,官方已经给我们预设了一些项目了,访问后默认使用的项目为ROOT项目,也就是我们默认打开的网站。

我们也可以访问example项目,只需要在后面填写路径即可:http://localhost:8080/examples/,或是docs项目(这个是Tomcat的一些文档)http://localhost:8080/docs/

Tomcat还自带管理页面,我们打开:http://localhost:8080/manager,提示需要用户名和密码,由于不知道是什么,我们先点击取消,页面中出现如下内容:

You are not authorized to view this page. If you have not changed any configuration files, please examine the file conf/tomcat-users.xml in your installation. That file must contain the credentials to let you use this webapp.

For example, to add the manager-gui role to a user named tomcat with a password of s3cret, add the following to the config file listed above.

<role rolename="manager-gui"/>
<user username="tomcat" password="s3cret" roles="manager-gui"/>

Note that for Tomcat 7 onwards, the roles required to use the manager application were changed from the single manager role to the following four roles. You will need to assign the role(s) required for the functionality you wish to access.

  • manager-gui - allows access to the HTML GUI and the status pages
  • manager-script - allows access to the text interface and the status pages
  • manager-jmx - allows access to the JMX proxy and the status pages
  • manager-status - allows access to the status pages only

The HTML interface is protected against CSRF but the text and JMX interfaces are not. To maintain the CSRF protection:

  • Users with the manager-gui role should not be granted either the manager-script or manager-jmx roles.
  • If the text or jmx interfaces are accessed through a browser (e.g. for testing since these interfaces are intended for tools not humans) then the browser must be closed afterwards to terminate the session.

For more information - please see the Manager App How-To.

现在我们按照上面的提示,去配置文件中进行修改:

  <role rolename="manager-gui"/>
  <user username="admin" password="admin" roles="manager-gui"/>

现在再次打开管理页面,已经可以成功使用此用户进行登陆了。登录后,展示给我们的是一个图形化界面,我们可以快速预览当前服务器的一些信息,包括已经在运行的Web应用程序,甚至还可以查看当前的Web应用程序有没有出现内存泄露

image-20230521153037470

同样的,还有一个虚拟主机管理页面,用于一台主机搭建多个Web站点,一般情况下使用不到,这里就不做演示了。

我们可以将我们自己的项目也放到webapp文件夹中,这样就可以直接访问到了,我们在webapp目录下新建test文件夹,将我们之前编写的前端代码全部放入其中(包括html文件、js、css、icon等),重启服务器。

可以直接通过 http://localhost:8080/test/ 来进行访问

使用Maven创建Web项目

虽然我们已经可以在Tomcat上部署我们的前端页面了,但是依然只是一个静态页面(每次访问都是同样的样子),那么如何向服务器请求一个动态的页面呢(比如显示我们访问当前页面的时间)这时就需要我们编写一个Web应用程序来实现了,我们需要在用户向服务器发起页面请求时,进行一些处理,再将结果发送给用户的浏览器

我们打开IDEA,新建一个项目,选择Java Enterprise(社区版没有此选项!)项目名称随便,项目模板选择Web应用程序,然后我们需要配置Web应用程序服务器,将我们的Tomcat服务器集成到IDEA中。配置很简单,首先点击新建,然后设置Tomcat主目录即可,配置完成后,点击下一步即可,依赖项使用默认即可,然后点击完成,之后IDEA会自动帮助我们创建Maven项目。

创建完成后,直接点击右上角即可运行此项目了

image-20230521155358531

可以将项目直接打包为war包(默认),打包好之后,放入webapp文件夹,就可以直接运行我们通过Java编写的Web应用程序了,访问路径为文件的名称

创建Servlet

它是Java EE的一个标准,大部分的Web服务器都支持此标准,包括Tomcat,就像之前的JDBC一样,由官方定义了一系列接口,而具体实现由我们来编写,最后交给Web服务器(如Tomcat)来运行我们编写的Servlet。

我们可以通过实现Servlet来进行动态网页响应,使用Servlet,不再是直接由Tomcat服务器发送我们编写好的静态网页内容(HTML文件),而是由我们通过Java代码进行动态拼接的结果,它能够很好地实现动态网页的返回。

当然,Servlet并不是专用于HTTP协议通信,也可以用于其他的通信,但是一般都是用于HTTP。

那么如何创建一个Servlet呢,非常简单,我们只需要实现Servlet类即可,并添加注解@WebServlet来进行注册。

@WebServlet("/test")
public class TestServlet implements Servlet {
		...实现接口方法
}

当对打开浏览器的URL进行修改时,会发现以下情况

image-20230521193154870

注意此处的链接,应该直接在打开的页面之后加上/test

我们发现,直接访问此页面是没有任何内容的,这是因为我们还没有为该请求方法编写实现,这里先不做讲解,后面我们会对浏览器的请求处理做详细的介绍。

除了直接编写一个类,我们也可以在web.xml中进行注册,现将类上@WebServlet的注解去掉:

<servlet>
    <servlet-name>test</servlet-name>
    <servlet-class>com.example.webtest.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>test</servlet-name>
    <url-pattern>/test</url-pattern>
</servlet-mapping>

这样的方式也能注册Servlet,但是显然直接使用注解更加方便,因此之后我们一律使用注解进行开发。只有比较新的版本才支持此注解,老的版本是不支持的哦。

实际上,Tomcat服务器会为我们提供一些默认的Servlet,也就是说在服务器启动后,即使我们什么都不编写,Tomcat也自带了几个默认的Servlet,他们编写在conf目录下的web.xml中:

<!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- The mappings for the JSP servlet -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

我们发现,默认的Servlet实际上可以帮助我们去访问一些静态资源,这也是为什么我们启动Tomcat服务器之后,能够直接访问webapp目录下的静态页面。

探究Servlet的生命周期

image-20230522200739055

首先我们需要了解,Servlet中的方法各自是在什么时候被调用的,我们先编写一个打印语句来看看:

public class TestServlet implements Servlet {

    public TestServlet(){
        System.out.println("我是构造方法!");
    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("我是init");
    }

    @Override
    public ServletConfig getServletConfig() {
        System.out.println("我是getServletConfig");
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("我是service");
    }

    @Override
    public String getServletInfo() {
        System.out.println("我是getServletInfo");
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("我是destroy");
    }
}

启动一次服务器,然后访问定义的页面,然后再关闭服务器,得到如下的顺序:

我是构造方法!
我是init
我是service
我是service(出现两次是因为浏览器请求了2次,是因为有一次是请求favicon.ico,浏览器通病)

我是destroy

image-20230522205517957

可以多次尝试去访问此页面,但是init和构造方法只会执行一次,而每次访问都会执行的是service方法,因此,一个Servlet的生命周期为:

  • 首先执行构造方法完成 Servlet 初始化
  • Servlet 初始化后调用 init () 方法。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 销毁前调用 destroy() 方法。
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

实际上在Web应用程序运行时,每当浏览器向服务器发起一个请求时,都会创建一个线程执行一次service方法,来让我们处理用户的请求,并将结果响应给用户。

service方法中,还有两个参数,ServletRequestServletResponse,实际上,用户发起的HTTP请求,就被Tomcat服务器封装为了一个ServletRequest对象,我们得到是其实是Tomcat服务器帮助我们创建的一个实现类,HTTP请求报文中的所有内容,都可以从ServletRequest对象中获取,同理,ServletResponse就是我们需要返回给浏览器的HTTP响应报文实体类封装。

那么我们来看看ServletRequest中有哪些内容,我们可以获取请求的一些信息:

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    //首先将其转换为HttpServletRequest(继承自ServletRequest,一般是此接口实现)
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        
        System.out.println(request.getProtocol());  //获取协议版本
        System.out.println(request.getRemoteAddr());  //获取访问者的IP地址
  		  System.out.println(request.getMethod());   //获取请求方法
        //获取头部信息
        Enumeration<String> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()){
            String name = enumeration.nextElement();
            System.out.println(name + ": " + request.getHeader(name));
        }
}

我们发现,整个HTTP请求报文中的所有内容,都可以通过HttpServletRequest对象来获取,当然,它的作用肯定不仅仅是获取头部信息,我们还可以使用它来完成更多操作,后面会一一讲解。

ServletResponse,这个是服务端的响应内容,可以在这里填写我们想要发送给浏览器显示的内容:

//转换为HttpServletResponse(同上)
HttpServletResponse response = (HttpServletResponse) servletResponse;
//设定内容类型以及编码格式(普通HTML文本使用text/html,之后会讲解文件传输)
response.setHeader("Content-type", "text/html;charset=UTF-8");
//获取Writer直接写入内容
response.getWriter().write("I'm the content");
//所有内容写入完成之后,再发送给浏览器

image-20230522205155611

现在在浏览器中打开此页面,就能够收到服务器发来的响应内容了。其中,响应头部分,是由Tomcat帮助生成的一个默认响应头。

image-20230521153238176

因此,实际上整个流程就已经很清晰明了了。

解读和使用HttpServlet

继续来深入学习Servlet接口的一些实现类

首先Servlet有一个直接实现抽象类GenericServlet,那么我们来看看此类做了什么事情

我们发现,这个类完善了配置文件读取和Servlet信息相关的的操作,但是依然没有去实现service方法,因此此类仅仅是用于完善一个Servlet的基本操作,那么我们接着来看HttpServlet,它是遵循HTTP协议的一种Servlet,继承自GenericServlet,它根据HTTP协议的规则,完善了service方法。

在阅读了HttpServlet源码之后,我们发现,其实我们只需要继承HttpServlet来编写我们的Servlet就可以了,并且它已经帮助我们提前实现了一些操作,这样就会给我们省去很多的时间。

@Log
@WebServlet("/test")
public class TestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("<h1>恭喜你解锁了全新玩法</h1>");
    }
}

现在,我们只需要重写对应的请求方式,就可以快速完成Servlet的编写

image-20230522210744044

@WebServlet注解详解

我们接着来看WebServlet注解,我们前面已经得知,可以直接使用此注解来快速注册一个Servlet,那么我们来想细看看此注解还有什么其他的玩法。

首先name属性就是Servlet名称,而urlPatterns和value实际上是同样功能,就是代表当前Servlet的访问路径,它不仅仅可以是一个固定值,还可以进行通配符匹配:

@WebServlet("/test/*")

上面的路径表示,所有匹配/test/随便什么的路径名称,都可以访问此Servlet,我们可以在浏览器中尝试一下

也可以进行某个扩展名称的匹配:

@WebServlet("*.js")

这样的话,获取任何以js结尾的文件,都会由我们自己定义的Servlet处理。

那么如果我们的路径为/呢?

@WebServlet("/")

此路径和Tomcat默认为我们提供的Servlet冲突,会直接替换掉默认的,而使用我们的,此路径的意思为,如果没有找到匹配当前访问路径的Servlet,那么久会使用此Servlet进行处理。

我们还可以为一个Servlet配置多个访问路径:

@WebServlet({"/test1", "/test2"})

我们接着来看loadOnStartup属性,此属性决定了是否在Tomcat启动时就加载此Servlet,默认情况下,Servlet只有在被访问时才会加载,它的默认值为-1,表示不在启动时加载,我们可以将其修改为大于等于0的数,来开启启动时加载。并且数字的大小决定了此Servlet的启动优先级。

@Log
@WebServlet(value = "/test", loadOnStartup = 1)
public class TestServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        super.init();
        log.info("我被初始化了!");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("<h1>恭喜你解锁了全新玩法</h1>");
    }
}

其他内容都是Servlet的一些基本配置,这里就不详细讲解了。

使用POST请求完成登陆

我们前面已经了解了如何使用Servlet来处理HTTP请求,那么现在,我们就结合前端,来实现一下登陆操作。

我们需要修改一下我们的Servlet,现在我们要让其能够接收一个POST请求:

@Log
@WebServlet("/login") 
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getParameterMap().forEach((k, v) -> {
            System.out.println(k + ": " + Arrays.toString(v));
        });
    }
}

ParameterMap存储了我们发送的POST请求所携带的表单数据,我们可以直接将其遍历查看,浏览器发送了什么数据

image-20230522214501200

现在再来修改一下前端:

<body>
    <h1>登录到系统</h1>
    <form method="post" action="login"> <!--不要加/否则会从根目录开始改-->
        <hr>
        <div>
            <label>
                <input type="text" placeholder="用户名" name="username">
            </label>
        </div>
        <div>
            <label>
                <input type="password" placeholder="密码" name="password">
            </label>
        </div>
        <div>
            <button>登录</button>
        </div>
    </form>
</body>

通过修改form标签的属性,现在我们点击登录按钮,会自动向后台发送一个POST请求,请求地址为当前地址+/login(注意不同路径的写法),也就是我们上面编写的Servlet路径。

运行服务器,测试后发现,在点击按钮后,确实向服务器发起了一个POST请求,并且携带了表单中文本框的数据。

现在,我们根据已有的基础,将其与数据库打通,我们进行一个真正的用户登录操作,首先修改一下Servlet的逻辑:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //首先设置一下响应类型
    resp.setContentType("text/html;charset=UTF-8");
    //获取POST请求携带的表单数据
    Map<String, String[]> map = req.getParameterMap();
    //判断表单是否完整
    if(map.containsKey("username") && map.containsKey("password")) {
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        //权限校验(待完善)
    }else {
        resp.getWriter().write("错误,您的表单数据不完整!");
    }
}

接下来我们再去编写Mybatis的依赖和配置文件,创建一个表,用于存放我们用户的账号和密码
如果对于这个Mybatias配置不甚了解,点击链接跳转

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${驱动类(含包名)}"/>
                <property name="url" value="${数据库连接URL}"/>
                <property name="username" value="${用户名}"/>
                <property name="password" value="${密码}"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

配置完成后,在我们的Servlet的init方法中编写Mybatis初始化代码,因为它只需要初始化一次。

SqlSessionFactory factory;
@SneakyThrows
@Override
public void init() throws ServletException {
    factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
}

现在创建一个实体类以及Mapper来进行用户信息查询:

@Data
public class User {
    String name;
    String password;
}
public interface UserMapper {
    @Select("select * from users where name = #{name} and password = #{password}")
    User getUser(@Param("username") String name, @Param("password") String password);
}
<mappers>
    <mapper class="com.example.Mapper.UserMapper"/>
</mappers>

好了,现在完事具备,只欠东风了,我们来完善一下登陆验证逻辑:

//登陆校验(待完善)
try (SqlSession sqlSession = factory.openSession(true)){
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUser(username, password);
    //判断用户是否登陆成功,若查询到信息则表示存在此用户
    if(user != null){
        resp.getWriter().write("登陆成功!");
    }else {
        resp.getWriter().write("登陆失败,请验证您的用户名或密码!");
    }
}

现在再去浏览器上进行测试吧!

image-20230522225657811

上传和下载文件

下载文件

首先我们来看看比较简单的下载文件,首先png放入到resource文件夹中,接着我们编写一个Servlet用于处理文件下载:

@WebServlet("/file")
public class FileServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      resp.setContentType("image/png");  
      OutputStream outputStream = resp.getOutputStream();
      InputStream inputStream = Resources.getResourceAsStream("haha.png");

    }
}

为了更加快速地编写IO代码,我们可以引入一个工具库:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.7</version>
</dependency>

使用此类库可以快速完成IO操作:

resp.setContentType("image/png");
OutputStream outputStream = resp.getOutputStream();
InputStream inputStream = Resources.getResourceAsStream("haha.png");
//直接使用copy方法完成转换
IOUtils.copy(inputStream, outputStream);

现在我们在前端页面添加一个链接,用于下载此文件:

<hr>
<a href="file" download="icon.png">点我下载超清资源</a>

image-20230523191810275

上传文件

HTML部分:

<form method="post" action="file" enctype="multipart/form-data">
    <div>
        <input type="file" name="test-file">
    </div>
    <div>
        <button>上传文件</button>
    </div>
</form>

注意必须添加enctype="multipart/form-data",来表示此表单用于文件传输

Servlet代码:

@MultipartConfig
@WebServlet("/file")
public class FileServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try(FileOutputStream stream = new FileOutputStream("/Users/nagocoler/Documents/IdeaProjects/WebTest/test.png")){
            Part part = req.getPart("test-file");
            IOUtils.copy(part.getInputStream(), stream);
            resp.setContentType("text/html;charset=UTF-8");
            resp.getWriter().write("文件上传成功!");
        }
    }
}

注意,必须添加@MultipartConfig注解来表示此Servlet用于处理文件上传请求

使用XHR请求数据

现在希望,网页中的部分内容,可以动态显示,比如网页上有一个时间,旁边有一个按钮,点击按钮就可以刷新当前时间。

这个时候就需要在网页展示时向后端发起请求了,并根据后端响应的结果,动态地更新页面中的内容,要实现此功能,就需要用到JavaScript来帮助我们,首先在js中编写我们的XHR请求,并在请求中完成动态更新:

function updateTime() {
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            document.getElementById("time").innerText = xhr.responseText
        }
    };
    xhr.open('GET', 'time', true);
    xhr.send();
}

接着修改一下前端页面,添加一个时间显示区域:

<hr>
<div id="time"></div>
<br>
<button onclick="updateTime()">更新数据</button>
<script>
    updateTime()
</script>

最后创建一个Servlet用于处理时间更新请求:

@WebServlet("/time")
public class TimeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String date = dateFormat.format(new Date());
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write(date);
    }
}

现在点击按钮就可以更新了

重定向与请求转发

当我们希望用户登录完成之后,直接跳转到网站的首页,那么这个时候,我们就可以使用重定向来完成。当浏览器收到一个重定向的响应时,会按照重定向响应给出的地址,再次向此地址发出请求

重定向

实现重定向很简单,只需要调用一个方法即可,我们修改一下登陆成功后执行的代码:

resp.sendRedirect("time");

调用后,响应的状态码会被设置为302,并且响应头中添加了一个Location属性,此属性表示,需要重定向到哪一个网址

另一种方法

resp.setStatus(302);
resp.setHeader("Location",URL);

如果成功登陆,那么服务器会发送给我们一个重定向响应,浏览器会去重新请求另一个网址。在登陆成功之后,就可以直接帮助用户跳转到用户首页了

请求转发

那么接着来看请求转发,请求转发其实是一种服务器内部的跳转机制,重定向会使得浏览器去重新请求一个页面,而请求转发则是服务器内部进行跳转,它的目的是,直接将本次请求转发给其他Servlet进行处理,并由其他Servlet来返回结果,因此它是在进行内部的转发。

req.getRequestDispatcher("/time").forward(req, resp);

现在,在登陆成功的时候,将请求转发给处理时间的Servlet,注意这里的路径规则和之前的不同,需要填写Servlet上指明的路径,并且请求转发只能转发到此应用程序内部的Servlet,不能转发给其他站点或是其他Web应用程序

image-20230523200834119

现在再次进行登陆操作,返回结果为一个405页面,说明请求现在是被另一个Servlet进行处理,并且请求的信息全部被转交给另一个Servlet,由于此Servlet不支持POST请求,因此返回405状态码

在TimeServlet.class下重写dopost

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("登陆成功!");
    }

image-20230523202033127

那么也就是说,该请求包括请求参数也一起被传递了,那么我们可以尝试获取以下POST请求的参数

   //login.class
   req.setAttribute("user",user);
   req.getRequestDispatcher("/time").forward(req, resp);
   //TimeServlet.class
   @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Users user = (Users) req.getAttribute("user");
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write(user.getUsername() + "登陆成功!");
    }

image-20230523202441400

现在给此Servlet添加POST请求处理,直接转交给Get请求处理:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doGet(req, resp);
}

再次访问,成功得到结果,发现浏览器只发起了一次请求,并没有再次请求新的URL,也就是说,这一次请求直接返回了请求转发后的处理结果。

那么,请求转发的好处:携带数据

req.setAttribute("test", "我是请求转发前的数据");
req.getRequestDispatcher("/time").forward(req, resp);
System.out.println(req.getAttribute("test"));

通过setAttribute方法来给当前请求添加一个附加数据,在请求转发后,可以直接获取到该数据。

重定向属于2次请求,因此无法使用这种方式来传递数据

最后总结,两者的区别为:

  • 请求转发是一次请求,重定向是两次请求
  • 请求转发地址栏不会发生改变, 重定向地址栏会发生改变
  • 请求转发可以共享请求参数 ,重定向之后,就获取不了共享参数了
  • 请求转发只能转发给内部的Servlet

ServletContext对象

ServletContext全局唯一,它是属于整个Web应用程序的,可以通过getServletContext()来获取到此对象。

此对象也能设置附加值:

ServletContext context = getServletContext();
context.setAttribute("test", "我是重定向之前的数据");
resp.sendRedirect("time");
System.out.println(getServletContext().getAttribute("test"));

因为无论在哪里,无论什么时间,获取到的ServletContext始终是同一个对象,因此可以随时随地获取添加的属性

获取index.html的内容

ServletContext context = getServletContext();
System.out.println(IOUtils.toString(context.getResourceAsStream("index.html"), StandardCharsets.UTF_8));
req.getRequestDispatcher("/time").forward(req, resp);

请求转发:

context.getRequestDispatcher("/time").forward(req, resp);

它还可以获取根目录下的资源文件(注意是webapp根目录下的,不是resource中的资源)

初始化参数

初始化参数类似于初始化配置需要的一些值,比如的数据库连接相关信息,就可以通过初始化参数来给予Servlet,或是一些其他的配置项,也可以使用初始化参数来实现

可以给Servlet添加一些初始化参数:

@WebServlet(value = "/login", initParams = {
        @WebInitParam(name = "test", value = "我是一个默认的初始化参数")
})

它也是以键值对形式保存的,可以直接通过Servlet的getInitParameter方法获取:

System.out.println(getInitParameter("test"));

但是,这里的初始化参数仅仅是针对于此Servlet,也可以定义全局初始化参数,只需要在web.xml编写即可:

<context-param>
    <param-name>lbwnb</param-name>
    <param-value>我是全局初始化参数</param-value>
</context-param>

需要使用ServletContext来读取全局初始化参数:

ServletContext context = getServletContext();
System.out.println(context.getInitParameter("haha"));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值