Servlet

Servlet 是什么

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

Servlet 主要做的工作

  • 允许程序猿注册一个类, Tomcat 收到某个特定的 HTTP 请求的时候, 执行这个类中的一些代码.
  • 帮助程序猿解析 HTTP 请求, HTTP 请求从一个字符串解析成一个 HttpRequest 对象.
  • 帮助程序猿构造 HTTP 响应. 程序猿只要给指定的 HttpResponse 对象填写一些属性字段, Servlet
  • 就会自动的安装 HTTP 协议的方式构造出一个 HTTP 响应字符串, 并通过 Socket 写回给客户端.

 第一个 Servlet 程序

1. 创建项目 

 创建一个maven项目, 名称目录自己设定

2. 引入依赖 

Maven 项目创建完毕后, 会自动生成一个 pom.xml 文件.
我们需要在 pom.xml 中引入 Servlet API 依赖的 jar 包.

Servlet 的版本要和 Tomcat 匹配.
如果我们使用 Tomcat 8.5, 那么就需要使用 Servlet 3.1.0

把中央仓库中提供的 xml 复制到项目的 pom.xml 中


    
    
        javax.servlet
        javax.servlet-api
        3.1.0
        provided
    

中央仓库提供的xml

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

3. 创建目录

 Servlet_第1张图片

1) 创建 webapp 目录

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

 2) 创建WEB-INF 目录与web.xml文件

然后在 webapp 目录内部创建一个 WEB - INF 目录 , 并创建一个 web.xml 文件

3) 编写 web.xml

web.xml 中拷贝以下代码 . 具体细节内容我们暂时不关注



    Archetype Created Web Application

4. 编写代码

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

@WebServlet("/hello")
  public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);
        System.out.println("hello world");
        resp.getWriter().println("hello world");
    } 
}
  • 创建一个类 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.getWriter() 会获取到一个流对象, 通过这个流对象就可以写入一些数据, 写入的数据会被 构造成一个 HTTP 响应的 body 部分, Tomcat 会把整个响应转成字符串, 通过 socket 写回给浏览.

5. 打包程序

使用 maven 进行打包 . 打开 maven 窗口 ( 一般在 IDEA 右侧就可以看到 Maven 窗口 , 如果看不到的话 , 可以通过 菜单 -> View -> Tool Window -> Maven 打开 ) 然后展开 Lifecycle , 双击 package 即可进行打包 .
但这样生成的包是.jar 格式, tomcat 需要识别的是另外一种.war包格式
war 包和 jar 包的区别
jar 包是普通的 java 程序打包的结果. 里面会包含一些 .class 文件.
war 包是 java web 的程序, 里面除了会包含 .class 文件之外, 还会包含 HTML, CSS, JavaScript, 图片, 以及其他的 jar 包. 打成 war 包格式才能被 Tomcat 识别

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

war
pom.xml 中再新增一个 build 标签 , 内置一个 finalName 标签 , 表示打出的 war 包的名字是
java108_hello

    java108_hello

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

6. 部署程序

war 包拷贝到 Tomcat webapps 目录下 .
启动 Tomcat , Tomcat 就会自动把 war 包解压缩 .

 Servlet_第2张图片

7. 验证程序

此时通过浏览器访问 http://127.0.0.1:8080/java108_hello/helloServlet_第3张图片

注意: URL 中的 PATH 分成两个部分, 其中 java108_helloContext Path, hello Servlet Path

更方便的部署方式 

手动拷贝 war 包到 Tomcat 的过程比较麻烦 .
我们使用 IDEA 中的 Smart Tomcat 插件完成这个工作

安装 Smart Tomcat 插件 Servlet_第4张图片

   一个项目中, 第一次使用smart tomcat 需要简单配置一下

Servlet_第5张图片Servlet_第6张图片 

点击运行发现:

Servlet_第7张图片 因为之前已经打开了tomcat ,占用了8080端口, 只要管关掉tomcat就可以了

实验:改动输出值和返回值

Servlet_第8张图片

Servlet_第9张图片

 学习Servlet API

API就是一组类和方法 我们只需要学习3各类就可以

(1)HttpServlet

这是编写Servlet 代码用到的核心类, 通过继承这个类, 并重写其中的方法, 让tomcat去调用到这里的逻辑

核心方法

Servlet_第10张图片

init 在 webapp 被加载的时候, 执行init (理论)

但是 tomcat 是可以配置webapp为 "懒加载" 的状态的

会使webapp在真正被访问到的时候才加载,(首次访问的时候, 才会触发 init)

destory webapp在被销毁的时候(tomcat结束)执行, 进行收尾工作Servlet_第11张图片

service 每次收到请求,都会执行Service 处理每个请求

我们实际开发的时候主要重写 doXXX 方法 , 很少会重写 init / destory / service .
这些方法的调用时机, 就称为 "Servlet 生命周期". (也就是描述了一个 Servlet 实例从生到死的过
程).
Servlet_第12张图片
  1. webapp 刚被加载的时候, 调用 servlet 的init 方法
  2. 每次收到请求的时候, 调用service方法
  3. webapp要结束的时候, 调用destroy方法
代码示例 : 处理 不同  请求
@WebServlet("/method")
public class MethodServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doGet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doPost");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doPut");
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doDelete");
    }
}

针对上述方法, 浏览器只能比较方便的构造get请求, 不太方便构造其他的

针对其他的方法要想构造, 使用Ajax或者postman

GET

Servlet_第13张图片Servlet_第14张图片

POST

 Servlet_第15张图片

(2)HttpServletRequest

Tomcat 通过 Socket API 读取 HTTP 请求 ( 字符串 ), 并且按照 HTTP 协议的格式把字符串解析成
HttpServletRequest 对象 .
URI 唯一资源标识符  URL 唯一资源定位符, 描述网络上的一个资源
URL 也可以理解URI的一种实现方式吧 

核心方法

Servlet_第16张图片
@WebServlet("/request")
public class RequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 上面这个操作也是必要的, 显示告诉浏览器, 你拿到的数据是html
        resp.setContentType("text/html");
        // 调用req的各个方法, 把得到的结果汇总到一个字符串中, 统一返回到页面上
        StringBuilder respBody = new StringBuilder();

        // 下列内容实在浏览器上按照html的方式来展示, 此时 \n 在html中并不是换行
        // 而是用
标签表示换行 respBody.append(req.getProtocol()); respBody.append("
"); respBody.append(req.getMethod()); respBody.append("
"); respBody.append(req.getRequestURI()); respBody.append("
"); respBody.append(req.getContextPath()); respBody.append("
"); respBody.append(req.getQueryString()); respBody.append("
"); //拼接header Enumeration headers = req.getHeaderNames(); while(headers.hasMoreElements()) { String header = headers.nextElement(); respBody.append(header + ":" + req.getHeader(header)); respBody.append("
"); } //统一返回结果 resp.getWriter().write(respBody.toString()); } }

Servlet_第17张图片

当然更多时候, 是希望获取到query string 或者 body 中的内容(用户自定义) 

1 下面代码就是直接获取query string 

@WebServlet("/parameter")
public class ParameterServlet extends HttpServlet {
    // 约定, 客户端使用 query string 传递数据
    // query string 形如: username=zhangsan&password=123
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("username=" + username);
        System.out.println("password=" + password);
        resp.getWriter().write("ok");
    }
}

Servlet_第18张图片

2 下面代码就是直接获取body

@WebServlet("/parameter2")
public class Parameter2Servlet extends HttpServlet {
    // 预期让客户端发送一个POST请求, 同时使用form 格式的数据, 在body中把数据传递过来
    // body 形如:
    // username=zhangsan&password=123
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("username" + username);
        System.out.println("password" + password);
        resp.getWriter().write("OK");
    }
}

Servlet_第19张图片

Servlet_第20张图片

 3 获取body (考虑body为json格式)

引入jackson库:

Servlet_第21张图片

Jackson也是第三方库, 也需要通过maven从中央仓库把这个库下载来了导入到项目中Servlet_第22张图片Servlet_第23张图片

 使用这个库的核心类就是ObjectMapper : 可以把一个对象映射到 JSON 字符串 也可以把 JSON 字符串映射到 对象

代码实现:
class User {
    public String username;
    public String password;
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    // 此处约定客户端 body 按照 json 格式来进行传输
    //其格式想入:
    // {
    //     username : zhangsan
    //     password : 123
    // }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        // json => java对象
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        System.out.println("username=" + user.username + ", password=" + user.password);
        // Java对象 => json
        String userString = objectMapper.writeValueAsString(user);
        System.out.println("userJson=" + userString);
        resp.getWriter().write("okk");
    }
}

这个映射方法有许多版本, 作用就是把json字符串解析成Java对象, 其中这里的第一个参数, 是一个流对象, 也就是表示json从哪里来读.

第一个参数: http请求中的body(是通过 getInputStream() 方法得到流对象, 进一步读取出来的)

第二个参数, 则是指定的类型, 当我得到json字符串(第一个参数) 需要转成一个啥样的Java对象, 需要指定一下对象的类型Servlet_第24张图片Servlet_第25张图片

实验 :Servlet_第26张图片Servlet_第27张图片
 能否使用 private 修饰变量

Servlet_第28张图片

要想写作private 也可以, 但是必须要提供对应的getter和setter方法

class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    // 此处约定客户端 body 按照 json 格式来进行传输
    //其格式想入:
    // {
    //     username : zhangsan
    //     password : 123
    // }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        // json => java对象
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        System.out.println("username=" + user.getUsername() + ", password=" + user.getPassword());
        // Java对象 => json
        String userString = objectMapper.writeValueAsString(user);
        System.out.println("userJson=" + userString);
        resp.getWriter().write("oKK");
    }
}

 如果不提供getter() 和 setter()方法, 就会出现状态500报错

获取和匹配的过程都发生在Jackson 的 readValue 工作过程中:

  •  就是会先把json字符串(第一个参数)解析成键值对, 先放到Map中,
  •  再根据参数填入的类对象(第二个参数) , 通过反射api就可以知道, 这个类里面有哪些public 修饰的属性, 每个属性的名字和类型
  • 一次把这里的每个属性都取出来, 通过属性名字查询上述的Map, 把得到的值, 赋值给这个类的属性  

由于把username 改成 private了, 而Jackson 并不会直接针对private 属性进行扫描, username就不认识了

(3)HttpServletResponse

同样也是和HTTP响应数据, 是匹配的

响应数据中的状态码, 各种header, body 针对这些属性, 就可以进行设置

请求对象, 我们拿到之后的母的, 是为了获取里面的属性(读)

响应对象, 我们拿到之后的目的, 是为了设置里  面的属性(写)

        对于 doXXX这样的方法来说, 本身要做的事情就是"根据请求 计算响应"

        请求对象, 是Tomcat 收到请求之后,  对http 协议解析得到的对象

        响应对象, 是Tomcat 创建的空的对象, 我们在代码中把 响应对象的属性设置好

 Servlet_第29张图片

(1) 状态码
@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(404);
    }
}

 

其是是可以在返回  状态码的同时, 给body也写入数据的, 就可以得到一些"个性化的错误页面"

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

refresh : 2  ==> 浏览器就会每隔两秒自动刷新一次

设置这个属性之后, 此时, 每隔2s就会自动刷新一次, 但是这里的刷新间隔也不是精确的2000ms, 会比2000稍微长点, 毕竟, 浏览器发起请求,  服务器响应, 再到页面被解析出来, 都是需要消耗一定的时间 

(3) 重定向 : 请求会返回重定向位置的页面
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 让页面被重定向 搜狗主页
        resp.setStatus(302);
        // 重定向响应,一定要带有 Location , 表示要重定向到哪里
        resp.setHeader("Location", "https://www.bilibili.com");
    }
}

Servlet_第30张图片

(4)  设置resp body
@WebServlet("/body")
public class BodyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 让服务器返回一个 html 数据
        resp.getWriter().write("
你好
"); } }

Servlet_第31张图片

Servlet_第32张图片

l浏览器吗默认会跟随系统的编码

windows简体中文版, 默认的编码是gbk

拿着utf8的数据, 浏览器按照gbk的方式来解析, 势必就会出现乱码

解决乱码的原则, 就是编码方式匹配~~

utf8是更主流的编码方式, 而gbk这种编码只能表示简体中文

@WebServlet("/body")
public class BodyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        // 让服务器返回一个 html 数据
        resp.getWriter().write("
你好
"); } }

Servlet_第33张图片

 Servlet_第34张图片

你可能感兴趣的:(网络协议,servlet,http)