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

news2024/9/20 22:39:55

文章目录

  • Servlet
    • 认识Tomcat服务器
    • 使用Maven创建Web项目
    • 创建Servlet
    • 探究Servlet的生命周期
    • 解读和使用HttpServlet
    • @WebServlet注解详解
    • 使用POST请求完成登陆
    • 上传和下载文件
      • 下载文件
      • 上传文件
    • 使用XHR请求数据
    • 重定向与请求转发
      • 重定向
      • 请求转发
    • ServletContext对象
    • 初始化参数

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"));

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/565007.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于自营配送模式的车辆路径规划设计与实现_kaic

摘要 近年来&#xff0c;随着我国消费水平逐渐提升&#xff0c;消费者在网上购物的频率也越来越高&#xff0c;电商发展速度迅猛&#xff0c;加大了物流配送的压力&#xff0c;促使物流企业以更大的运力&#xff0c;更短的时间将货物送达。在货品的运输过程中&#xff0c;成本居…

TCP 连接未必都是用TCP协议沟通

确实&#xff0c;一般来说 TCP 连接是标准的 TCP 三次握手完成的&#xff1a; 1.客户端发送 SYN&#xff1b; 2.服务端收到 SYN 后&#xff0c;回复 SYNACK&#xff1b; 3.客户端收到 SYNACK 后&#xff0c;回复 ACK。 SYN 会在两端各发送一次&#xff0c;表示“我准备好了&…

MicroBlaze系列教程(9):xilisf串行Flash驱动库的使用

文章目录 1. xilisf库简介2. xilisf库函数3. xilisf配置4. xilisf应用示例工程下载本文是Xilinx MicroBlaze系列教程的第9篇文章。 1. xilisf库简介 xilisf库(Xilinx In-system and Serial Flash Library) 是Xilinx 提供的一款串行Flash驱动库,支持常用的 Atmel 、Intel、S…

Linux基本指令介绍

目录 前言 指令操作与图形化界面的对比 adduser passwd whoami ls指令 pwd指令 cd指令 touch指令 mkdir指令 rmdir指令 && rm 指令 man指令 cp指令 mv指令 cat&#xff08;显示文件内容&#xff08;全部&#xff09;&#xff0c;且不可修改的&#xff09;…

【1++的C++初阶】之模板

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的C初阶】 文章目录 一&#xff0c;浅谈模板二&#xff0c;函数模板三&#xff0c;类模板 一&#xff0c;浅谈模板 在前面的文章【【1的C初阶】之C入门篇1】中我们对函数重载有了一定的认识&a…

Python-web开发学习笔记(3):Flask Demo,一个网站开发小案例

☕️ 推荐关注我的另一个专栏系列&#xff1a;大道至简&#xff0c;该系列收录了许多优质的人工智能算法博文&#xff0c;包括机器学习和自然语言处理等&#xff0c;持续更新中&#xff0c;欢迎各位关注~ 介绍完HTML标签&#xff0c;我们来搞一个简单的网站小案例&#xff0c;带…

一、CNNs网络架构-基础网络架构

目录 1.LeNet 2.AlexNet 2.1 激活函数&#xff1a;ReLU 2.2 随机失活&#xff1a;Droupout 2.3 数据扩充&#xff1a;Data augmentation 2.4 局部响应归一化&#xff1a;LRN 2.5 多GPU训练 2.6 论文 3.ZFNet 3.1 网络架构 3.2 反卷积 3.3 卷积可视化 3.4 ZFNet改…

6.4.6拓扑排序

用DAG&#xff08;有向无环图&#xff09;表示一个工程。顶点表示活动&#xff0c;有向边<Vi&#xff0c;Vj>表示活动Vi活动必须先与Vj活动进行。 所谓的拓扑排序&#xff1a;找到做事的先后顺序 以上根据拓扑排序的实现&#xff1a; 加入对有回路的图进行拓扑排序&#…

重磅,ChatGPT App 来了!(附保姆级教程)

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 日前&#xff0c;OpenAI宣布推出iOS移动版ChatGPT&#xff0c;为iPhone和iPad用户提供免费下载和使用机会。 总体而言&#xff0…

视频转换芯片MS7200概述 HDMI转数字RGB/YUV/HDMI RXReceive/替代IT66021FN

1. 基本介绍 MS7200 是一款 HDMI 接收芯片&#xff0c;兼容 HDMI1.4b 及 HDMI 1.4b 以下标准的视频 3D 传输格 式&#xff0c;最高分辨率可支持到 4K30Hz&#xff0c;最高采样率达到 300MHz。MS7200 支持 YUV 和 RGB 之 间的色彩空间转换&#xff0c;数字接口支持 YUV 及 RGB 格…

动态规划-完全平方数

动态规划-完全平方数 1 题目描述2 示例2.1 示例 1&#xff1a;2.2 示例 2&#xff1a;2.3 提示&#xff1a; 3 解题思路及方法3.1 解题思路3.1.1 确定状态3.1.2 转移方程3.1.3 初始条件和边界情况3.1.4 计算顺序 3.2 算法代码实现 跟着九章侯老师学习了动态规划专题之后根据学习…

联邦图神经网络:概述、技术和挑战

https://arxiv.org/pdf/2202.07256.pdf 摘要 图神经网络以其处理实际应用中广泛存在的图形数据的强大能力&#xff0c;受到了广泛的研究关注。然而&#xff0c;随着社会越来越关注数据隐私&#xff0c;GNN需要适应这种新常态。这导致了近年来联邦图神经网络&#xff08;FedGN…

一、阿里云oss

文章目录 一、阿里云oss1、开通“对象存储OSS”服务1.1创建Bucket1.2上传默认头像1.3获取用户acesskeys 2、使用SDK文档3、文件服务实现3.1搭建service-oss模块3.1.1 搭建service-oss模块3.1.2 修改配置3.1.3 启动类3.1.4配置网关 3.2 测试SDK3.3封装service接口3.4封装control…

【C++初阶】类和对象(下)构造函数(初始化列表) + explicit关键字 +static成员

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

如何下载旧版的Chrome

&#xff08;下面网址需要科学上网&#xff09; 1.获得浏览器版本号 访问 https://www.google.com 然后在搜索框中输入要搜索的浏览器版本号&#xff0c;例如 : site:chromereleases.googleblog.com 96.0。其中96.0是大版本号。 2.查询浏览器版本号的具体信息 访问 https://…

CANopenNode Master RPDO 配置

文章目录 CANopenNode 简介CANopenNode 主栈SDO ClientRPDORPDO 通讯参数RPDO 通信参数设置实例PDO 映射参数RPDO 映射参数设置实例 CANopenNode 简介 CANopenNode 是一个开源的免费的开源 CANopen 协议栈。 对象字典为任何变量提供清晰灵活的组织。可以直接或通过读/写函数…

电力系统的虚假数据注入攻击和MTD系统研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

代码随想录算法训练营第十五天|树的层序遍历 、226.翻转二叉树 、101.对称二叉树

层序遍历&#xff08;广度优先遍历&#xff09;&#xff1a; 遍历思路&#xff1a; 借用队列来实现。 若根节点不为空&#xff0c;则先将根节点放入队列&#xff0c; 随后&#xff0c;在while循环中&#xff0c;判断队列当前的size&#xff0c;队列的size就是树在该层中的节点…

ubuntu换镜像源(ubuntu换源)

ubuntu换镜像源&#xff08;ubuntu换源&#xff09; 文章目录 ubuntu换镜像源&#xff08;ubuntu换源&#xff09;1. 备份镜像源文件2. 根据不同 ubuntu 版本设置不同的镜像源2.1 focal 版本镜像源2.2 bionic 版本镜像源2.3 ubuntu 自带源&#xff1a; 参考文献 1. 备份镜像源文…

数据库连接 ---MySQL的总结(八)

数据库连接 —MySQL的总结&#xff08;八&#xff09; mysql使用在c编程之中使用&#xff0c;需要调用官方c库进行使用。 库的安装 库文件&#xff0c;安装mysql的c链接库 yum install mysql-server接口介绍 初始化 MYSQL *mysql mysql_init(nullptr);连接 mysql_real_conn…