Servlet

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Servlet

一 什么是Servlet

Servlet 是一种动态页面的技术,是一组 Tomcat 提供给程序员的API, 帮助程序员简单高效的开发一个web app.

二 第一个 Servlet程序

2.1 创建项目

使用 IDEA 创建一个 MAVEN 项目.

(1) New Project->Maven

(2) 选择要存放的目录

点击finish,创建项目

2.2 引入依赖

Maven 项目创建完毕后,会自动生成一个 pom.xml 文件.

我们需要在 pom.xml 中引入 Servlet API 依赖的 jar 包.

(1) 在中央仓库: https://mvnrepository.com/ 中搜索"servlet", 一般第一个结果就是.

(2) 选择版本. 一般我们使用 3.1.0 版本

Servlet 的版本要和Tomcat 匹配

可以在 http://tomcat.apache.org/whichversion.html 查询版本对应关系.


(3) 把中央仓库中提供的 xml 赋值到项目的 pom.xml 中

修改后的 pom.xml 形如

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>org.example</groupId>
    <artifactId>Servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
 
    <dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
 
    </dependencies>
</project>

<dependencies>标签内部放置项目依赖的 jar 包, maven 会自动下载依赖到本地.

2.3 创建目录

这些目录中:


•  src 表示源代码所在的目录


•  main/java 表示代码的根目录,后续 .Java 文件就放到这个目录中.


•  main/resources 表示项目的一些资源文件所在的目录


•  test/java 表示测试代码的根目录.


这些目录还不够,我们还需要创建一些新的目录/文件.


(1) 创建 webapp 目录

在 main 目录下,和 java 目录并列, 创建一个 webapp 目录(注意, 不是webapps)

(2) 创建 web.xml

然后再 webapp 目录内部创建一个 WEB-INF 目录

并在 WEB-INF 目录下创建一个 web.xml 文件

(3) 编写 web.xml

往 web.xml 中拷贝以下代码.

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/w eb-app_2_3.dtd" >
 
<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>

webapp 目录就是未来部署到 Tomcat 中的一个重要的目录,当前我们可以往 webapp 中放一些静态资源,比如 html, css 等.

在这个目录中还有一个重要的文件 web.xml. Tomcat 找到这个文件才能正确处理 webapp 中的动态资源.


2.4 编写代码

在 java 目录中创建一个类 HelloServlet ,代码如下:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("hello");
        resp.getWriter().write("hello");
    }
}

•  创建一个 HelloServlet, 继承字 HttpServlet


•  在这个类上方加上 @WebServlet("/hello") 注解,表示 Tomcat 收到的请求中,路径为 /hello 的请求才会调用 HelloServlet 这个类的代码.(这个路径为不包含 Context Path)


•  重写 doGet 方法. doGet 的参数有两个,分别表示收到的HTTP请求 和要构造的HTTP响应,这个方法会在Tomcat 收到 GET 请求时触发.


•  HttpServletRequest 表示 HTTP 请求. Tomcat 按照  HTTP 请求的格式 把 字符串格式的请求 转成了一个HttpServletRequest 对象. 后续想获取请求中的信息(方法,URL, header, body等)都是通过这个对象来获取


•  HttpServletResponse 表示 HTTP 响应. 代码中把响应对象构造好(构造响应的状态码, header, body 等),只要把这些属性设置到这个 resp 对象中, Tomcat 就会自动根据响应对象构造出一个HTTP 响应字符串,通过 socket 返回给客户端


•  resp.getWriter() 会获取到一个流对象,通过这个流对象就可以写入一些数据,写入的数据会被构造成一个HTTP 响应的 body 部分, Tomcat 会把整个响应转成字符串,通过 socket 写回给浏览器.


这个代码虽然只有寥寥几行,但是包含的信息量巨大


(1) 我们的代码不是通过 main 方法作为入口了. main 方法已经被包含在 Tomcat 里,我们写的代码会被 Tomcat 在合适的时机调用起来.此时我们写的代码并不是一个完整的程序.而是 Tomcat 这个程序的一小部分逻辑


(2)  我们随便写个类就能被 Tomcat 调用吗? 满足什么样的条件才能被调用呢?


主要满足三个条件:


 •  创建的类需要继承自 HttpServlet  


 •  这个类需要使用 @WebServlet 注解关联上一个 HTTP的路径


 •  这个类需要实现 doxxx 方法.


当满足以上三个条件后, Tomcat 就可以找到这个类,并且在合适的时机进行调用

2.5 打包程序

使用 maven 进行打包,打开 maven 窗口(如果看不到,可以通过 菜单-> View-> Tool Windows-> Maven 打开)

然后展开 Lifecycle, 双击 package(或者右键运行) 即可进行打包.

如果比较顺利的话,能够看到 SUCCESS 这样的字样.

打包成功后,可以看到在target 目录下,生成了一个 jar 包

但这个 jar 包并不是我们想要的, Tomcat 需要识别的是另外一种 war 包格式.另外这个 jar 包的名字太复杂了,我们也希望这个名字能简单一点.


war 包和 jar 包的区别


jar 包是普通的 Java 程序打包的结果,里面会包含一些 .class 文件.


war 包是 java web 的程序,里面处理会包含 .class 文件之外, 还会包含 HTML ,CSS ,JavaScript,图片,以及其他的 jar 包. 打成 war 包格式才能被Tomcat 识别.

如何打 war 包?

在 pom.xml 中新增一个 packing 标签,表示打包的方式是打一个 war 包.

<packaging>war</packaging>

在 pom.xml 中新增一个 build 标签,内置一个 finalName 标签, 表示 war 包的名字是 HelloServlet

 <build>
        <finalName>HelloServlet</finalName>
    </build>

重新使用 maven 打包, 可以看到生成的新的 war 包的结果

2.6 部署程序

先打开 文件所在位置, 将war 包拷贝到 Tomcat 的 webapps 目录下

启动 Tomcat,Tomcat 就会自动把 war 包解压缩.

然后双击 bin 目录下的 start.bat 运行.

此时通过浏览器访问  localhost:8080/HelloServlet/hello 就可以看到结果了.

注意: URL 中的 PATH 分成两个部分,其中的 HelloServlet 为 Context Path,hello 为 Servlet Path.


可以通过 Tomcat 插件 简化操作,不过多叙述.

四 Servlet API 详解

4.1 HttpServlet 核心方法

我们写 Servlet 代码的时候,首先第一步就是先创建类,继承自 HttpServlet ,并重写其中的某些方法.

4.1.1 init

该方法只有首次收到匹配的请求时才会被调用,当再次刷新时,不会被调用.

4.1.2 destroy

这个方法时该 webapp 被卸载(被销毁之前)执行一次

但destroy 能否真的执行,不太靠谱

1.如果通过8005 管理端口,来停止服务器,此时 destroy 能执行

2.如果是直接杀死进程的方式停止服务器,此时 destroy 执行不了


4.1.3 service

每次收到路径匹配的请求,都会执行.

doGet/doPost 其实是咋 service 中被调用的,一般不会重写 service,只是重写 doXXX

总结: Servlet 的声明周期


  1. 当一个请求从HTTP服务器转发给Servlet容器时,容器检查对应的Servlet是否创建,没有创建就实例化该Servlet,并调用init()方法,init()方法只调用一次,之后的请求都从第二步开始执行;
  2. 请求进入service()方法,根据请求类型转发给对应的方法处理,如doGet, doPost, 等等
  3. 容器停止前,调用destory()方法,进行清理操作,该方法只调用一次,随后JVM回收资源。

4.2 HttpServletRequest 的核心方法

当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串),并且按照 HTTP 协议的格式把字符串解析成HttpServletRequest 对象

使用示例:

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StringBuilder result=new StringBuilder();
        result.append(req.getProtocol()+"<br>");
        result.append(req.getMethod()+"<br>");
        result.append(req.getContextPath()+"<br>");
        result.append(req.getRequestURI()+"<br>");
        result.append(req.getQueryString()+"<br>");
 
        Enumeration<String> headerNames=req.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String headerName= headerNames.nextElement();
            String headerValue=req.getHeader(headerName);
            resp.getWriter().write(headerName+":"+headerValue+"<br>");
        }
        resp.getWriter().write("<hr>");
//      此处设置响应body的类型,方便浏览器解析
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write(result.toString());
    }

结果示例:

前段给后端传递数据,是非常常见的需求~~

4.2.1 通过 query string 传递

http://127.0.0.1:8080/Servlet/getServlet?userName=%E5%BC%A0%E4%B8%89&password=123
 
  @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        String userName=req.getParameter("userName");
        if(userName==null){
            System.out.println("userName是null");
        }
        String password=req.getParameter("password");
        if(password==null){
            System.out.println("password 是null");
        }
        System.out.println("userName="+userName+"&password="+password);
        resp.getWriter().write("ok");
    }

 


注意:在 URL 中! query string 如果是包含中文/特殊字符,务必使用 urlencode 的方式进行转码!!! 如果直接写中文/特殊字符,会存在非常大的风险(不同的服务器,对中文的支持是不一样的.)


最好使用 urlencode 进行编码.

4.2.2 通过body(form) 表单的形式)传递

 @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //若没有给请求设置,请求的body中含有中文时,所获得的value值会出现乱码
        //req.setCharacterEncoding("utf-8");
        String userName=req.getParameter("userName");
        if(userName==null){
            System.out.println("userName是null");
        }
 
        String password=req.getParameter("password");
        if(password==null){
            System.out.println("password 是null");
        }
        System.out.println("userName="+userName+"&password="+password);
        resp.getWriter().write("ok");
    }

此时我们在body中的value值设为中文时,发现控制台上的是乱码.

此时需要显示的告诉后端代码,请求的编码方式是 utf-8.

4.2.3 通过 body (json) 传递

json 也是键值对格式的数据,但是 Servlet 自身没有内置 json 解析功能,因此就需要借助其他的第三方库了(fastjson, gson, jackson),此处使用 Jackson.

在maven 中直接下载即可使用

class User{
    public String username;
    public String password;
}
 
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    //使用 Jackson,最核心的对象就是 ObjictMapper
    //通过这个对象, 就可以把 json 字符串解析成 Java 对象;也可以吧一个 java 对象解析成一个 json 格式的字符串
    private ObjectMapper objectMapper=new ObjectMapper();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //readValue 先解析json 字符串,转换成若干个键值对
        //根据第二个参数 User.class,去找到 User 里所有的public 的属性(或者有 public getter setter 的属性)
        //遍历属性,根据属性的名字,去上述准备好的键值对里查询,看看这个属性名字是否存在,如果存在,就把 value 赋值到属性中
        User user=objectMapper.readValue(req.getInputStream(),User.class);
        System.out.println("username="+user.username+",password="+user.password);
        resp.getWriter().write("ok");
    }
}

通过 postman 发送post 请求 (注意body 的格式,为 json 格式)

4.3 HttpServletResponse

Servlet 中的 doxxx 方法的目的就是根据计算得到响应,然后把响应的数据设置到 HttpServletResponser 对象中.


然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式,转成一个字符串,并通过 Socket 写回浏览器.

设置状态码:

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(200);
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("返回200响应!!");
 
    }
}

刷新:

@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Refresh","1");
        resp.getWriter().write("time="+System.currentTimeMillis());
    }
}

重定向:

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        resp.setStatus(302);
//        resp.setHeader("Location","https://www.sogou.com/");
        resp.sendRedirect("https://www.sogou.com/");
    }
}

五 使用实例:

5.1 服务器版表白墙

前段代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
    </script>
    <title>lovewall</title>
</head>
<body>
    <div style="width: 100%;text-align: center;">
        <h3>表白墙</h3>
        谁:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input id="from"><p></p>
        对谁:&nbsp;&nbsp;<input id="to"><p></p>
        说: &nbsp;&nbsp;&nbsp;&nbsp;<input id="mes"><p></p>
        <input type="button" value="提交" onclick="check()">
        <div id="wall">
        </div>
    </div>
    <script>
 
        function check(){
            let fromM=$("#from");
            let toM=$("#to");
            let mesM=$("#mes");
            if(fromM.val().trim()==""){
                alert("请先输入你的名字");
                fromM.focus();
                return;
            }
            if(toM.val().trim()==""){
                alert("请先输入对方的名字");
                toM.focus();
                return;
            }
            if(mesM.val().trim()==""){
                alert("请先输入要发送的信息");
                mesM.focus();
                return;
            }
            // 创建body对象,用于发送post请求
            let body={
                from:fromM.val(),
                to:toM.val(),
                message:mesM.val(),
            };
            console.log(JSON.stringify(body));
            $.ajax({
                url:'message',
                type:'post',
                contentType:"application/json;charset=utf8",
                data:JSON.stringify(body),
                // java 中使用 jackson 完成对象和 json 字符串的转换
                // objectMapper.writeValue 用来把 java对象转成 json 格式字符串
                // objectMapper.readValue 用来把 json 格式字符串转换成 json 格式字符串
                // JS 中使用 JSON 这个特殊对象, 完成对象和 json 字符串的转换
                // JSON.stringify 把js对象转成 json 格式字符串
                // JSON.parse 把js格式字符串转换成 js对象
                success:function(body){
                    
                }
            });
 
            $("#wall").append(fromM.val()+"对"+toM.val()+"说"+mesM.val()+"<p></p>");
            // 将表格数据清空
            fromM.val("");
            toM.val("");
            mesM.val("");
 
         
 
        }
        $.ajax({
            type:'get',
            url:'message',
            success:function(body){
                let containerDiv=document.querySelector('#wall');
                for(let message of body){
                    let rowDiv=document.createElement('div');
                    rowDiv.className='row.message';
                    rowDiv.innerHTML=message.from+'对'+message.to+'说'+message.message;
                    containerDiv.appendChild(rowDiv);
                }
            }
        });
    </script>
</body>

后端代码:

class Message{
    public String from;
    public String to;
    public String message;
}
@WebServlet("/message")
public class WallServlet extends HttpServlet {
    ObjectMapper objectMapper=new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List<Message> list=load();
        resp.setContentType("application/json;charset=utf8");
        //将java对象转成 json 格式字符串
        String message=objectMapper.writeValueAsString(list);
        resp.getWriter().write(message);
    }
 
    private List<Message> load() {
        PreparedStatement preparedStatement=null;
        Connection connection=null;
        ResultSet resultSet=null;
        List<Message> list=new ArrayList<>();
        MysqlDataSource dataSource=new MysqlDataSource();
        DButil.getConnection(dataSource);
        String sql="select * from message";
        try {
            connection= dataSource.getConnection();
            preparedStatement=connection.prepareStatement(sql);
            resultSet=preparedStatement.executeQuery();
            while (resultSet.next()){
                Message message=new Message();
                message.from=resultSet.getString("from");
                message.to=resultSet.getString("to");
                message.message=resultSet.getString("message");
                list.add(message);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.close(connection,preparedStatement,resultSet);
        }
        return list;
    }
 
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 
        Message message=objectMapper.readValue(req.getInputStream(),Message.class);
        save(message);
    }
 
    private void save(Message message){
        PreparedStatement preparedStatement=null;
        Connection connection=null;
 
        MysqlDataSource dataSource=new MysqlDataSource();
        DButil.getConnection(dataSource);
        String sql="insert into message value(?,?,?)";
        try {
            connection=dataSource.getConnection();
            preparedStatement=connection.prepareStatement(sql);
            preparedStatement.setString(1,message.from);
            preparedStatement.setString(2,message.to);
            preparedStatement.setString(3,message.message);
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.close(connection,preparedStatement,null);
        }
    }
}

六 session 与 cookie

6.1 HttpSession getSession()

getSession 有一个参数,boolean

• 如果参数为false ,getSession 的行为是:

 1. 读取请求中的cookie 里的 sessionId,


  2. 在服务器这边根据 sessionId 来查询对应的 Session 对象


  3. 如果查到了,就会直接返回这个 session 对象, 如果没有查到, 返回 null


• 如果参数为 true , getSession 的行为是:

1. 读取请求中 cookie 里的 sessionId,


 2. 在服务器这边根据 sessionId 来查询对应的 Session 对象


 3. 如果查到了, 就会直接返回这个session 对象


 4. 如果没查到, 就会创建一个 Session 对象, 同时生成一个 sessionId 为 key, Session 对象为              value 的键值对,把这个键值对存储到服务器里的一个哈希表中,同时把 sessionId 以 Set-                  Cookie  的方式返回给浏览器

6.2 简单实现登录页面

前端代码:

<body>
    <form action="login" style="text-align: center;" method="post">
        <input type="text" name="userName"><br>
        <input type="password" name="password"><br>
        <input type="submit" value="提交" ><br>
    </form>
 
</body>

后端代码:

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        String userName=req.getParameter("userName");
        String password =req.getParameter("password");
        resp.setContentType("text/html;charset=utf8");
        if(userName==null||userName.equals("")||password==null||password.equals("")){
            resp.getWriter().write("密码或用户名不能为空");
            return;
        }
        //此处不进行是否存在用户的判定
        HttpSession session=req.getSession(true);
        //此时获取的session 对象也相当于是一个哈希表,key是 String 类型,value 是object 类型
        //后续可以通过 getAttribute 根据key 再取到 value
        session.setAttribute("userName",userName);
        resp.sendRedirect("index");
    }
}
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //此时是false,禁止创建会话,如果没找到,润微用户是未登录的状态
        //如果找到了才认为是登录状态
        HttpSession session=req.getSession(false);
        if(session==null){
            //未登录状态
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(("当前用户未登录"));
            return;
        }
        //此处查询到的 session对象,就应该和刚才登录成功后创建的session 对象是同一个对象
        //因为是同一个 sessionid, 因为刚才登录成功, sessionid 会通过 Set-Cookie 返回给浏览器.
        //浏览器下次访问 IndexServlet 的时候,就会带上这个同一个 sessionid, 拿着 sessionid 再一查,就查到了同一个 session 对象
        //可以简单认为 在服务器这边,Servlet 内部维护了一个全局的哈希表,key 就是sessionid, value 就是 session 对象,通过getSession方法就是操作全局哈希表
        String username=(String) session.getAttribute("userName");
        if(username==null){
            //虽然有会话,但是里面没有必要的属性,也认为是登录状态异常
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(("当前用户未登录"));
            return;
        }
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("欢迎你"+username);
        //
        ServletContext servletContext=this.getServletContext();
        Integer count =(Integer) servletContext.getAttribute("count");
        if(count==null){
            count=1;
            servletContext.setAttribute("count",1);
        }else {
            servletContext.setAttribute("count",count+1);
        }
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("你是第"+servletContext.getAttribute("count")+"位访客");
    }
}
相关文章
|
XML Java 应用服务中间件
Servlet详解(上)
Servlet详解
84 0
|
6月前
|
Java 应用服务中间件 数据库连接
Servlet是什么?
Servlet(Server Applet)是Java Servlet的简称,通常被称为小服务程序或服务连接器。它是一个用Java编写的服务器端程序,具有独立于平台和协议的特性。Servlet的主要功能在于交互式地浏览和生成数据,进而生成动态Web内容。
52 3
|
IDE Java 应用服务中间件
Servlet3.0
Servlet3.0
|
Java 应用服务中间件 容器
|
6月前
|
Oracle Java 关系型数据库
浅谈Servlet
浅谈Servlet
30 0
|
Java 应用服务中间件
Servlet2(1)
Servlet2(1)
67 0
|
JSON 前端开发 Java
Servlet详解(下)
Servlet详解
75 0
|
前端开发 JavaScript 应用服务中间件
Servlet1(2)
Servlet1(2)
69 0
|
XML JavaScript 前端开发
servlet详解
servlet详解
|
小程序 Java 应用服务中间件
Servlet1(1)
Servlet1(1)
79 0