2【HowTomcatWork】如何自定义Servlet,为啥会有RequestFacade类型?

我的项目仓库:代码仓库
本节对应03-servlet模块和04-servletPro模块
04-servletPro模块对03-servlet模块进行了一些修改,加入了RequestFacade类型


在02-HttpServer模块中, 我们写了的Request,Response类
在03-servlet模块中,我们让Request,Response实现了ServletRequest, ServletResponse接口。我们需要导入javax.servlet.servlet-api依赖,里面有我们需要的ServletRequest,ServletResponse接口。

原来的02-HttpServer模块只能访问一些静态资源例如图片,html。现在的03-servlet模块中我们写了PrimitiveServlet类,PrimitiveServlet类实现javax.servlet.Servlet 接口。通过在浏览器访问url的方式,执行PrimitiveServlet类的特定方法。

首先理解Servlet接口,并自己写一个实现Servlet类。【我的项目中写了一个PrimitiveServlet】

Servlet接口

Servlet 编程是通过 javax.servlet 和 javax.servlet.http 这两个包的类和接口来实现的。 其中一个至关重要的就是 javax.servlet.Servlet 接口了。所有的 servlet 必须实现实现或者继 承实现该接口的类。

Servlet 接口有五个方法,其用法如下。

public void init(ServletConfig config) throws ServletException 

public void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException 

public void destroy() 

public ServletConfig getServletConfig() 

public java.lang.String getServletInfo() 

在 Servlet 的五个方法中,init,service 和 destroy 是 servlet 的生命周期方法。

在 servlet 类已经初始化之后,init 方法将会被 【servlet 容器】所调用。servlet 容器只调用一次init,以此表明 servlet 已经被加载进服务中。

init,service 和 destroy 是 servlet 的生命周期方法
在 servlet 的生命周期中,service 方法将会被调用多次。
但是init和destroy只执行一次

init 方法必须在 servlet 可以接受任何请求之前成功运行完毕。 程序员可以通过覆盖这个方法来写那些仅仅只要运行一次的初始化代码,例如加载 数据库驱动,值初始化等等。在其他情况下,这个方法通常是留空的。

servlet 容器为 servlet 请 求 调 用 它 的 service 方 法 。 servlet 容 器 传 递 一 个 【javax.servlet.ServletRequest 对象和javax.servlet.ServletResponse对象】

03-servlet模块中,我们直接new Request()和new Reasponse(),传给了service 方 法。
04-servletPro模块中,我们没有把new Request()和new Reasponse()传给了service 方
法。而是new RequestFacade()和new ReasponseFacade(),传给service 方 法

ServletRequest 对象能拿到客户端的 HTTP 请求信息,而 ServletResponse 对象封装 servlet 的响应。

当从服务中移除一个 servlet 实例的时候,servlet 容器调用 destroy 方法。这通常发生在 servlet 容器正在被关闭或者 servlet 容器需要一些空闲内存的时候。

仅仅在所有 servlet 线程 的 service 方法已经退出或者超时淘汰的时候,这个方法才被调用。在 servlet 容器已经调用完 destroy 方法之后,在同一个 servlet 里边将不会再调用 service 方法。destroy 方法提供了一 个机会来清理任何已经被占用的资源,例如内存,文件句柄和线程,并确保任何持久化状态和 servlet 的内存当前状态是同步的。

我自己写的PrimitiveServlet实现 javax.servlet.Servlet 接口

PrimitiveServlet 类实现了 javax.servlet.Servlet(所有的 servlet 都必须这样做),并为 Servlet 的这五个方法都提供了 实现。

service 方法从ServletResponse 对象获得 java.io.PrintWriter 实例,并发送Http响应包到浏览器去。我们在Http响应体写入Hello.Cy字符串,浏览器会显示该字符串。

public class PrimitiveServlet implements Servlet {

    public void init(ServletConfig config) throws ServletException {
        System.out.println("init");
    }

    public void service(ServletRequest request, ServletResponse response)
            throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = response.getWriter();
        String OkMessage = "HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html"  + "\r\n" +
                "\r\n";
        out.write(OkMessage);
        out.println("Hello. CY");
    }

    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
    }

    public ServletConfig getServletConfig() {
        return null;
    }

}
类加载

写好了自己的servlet, 我们想通过浏览器访问到它,并执行它的service方法,这如何实现?

03-servlet模块的HttpServer1类里面有这样的逻辑, 先得到浏览器的uri, 当uri起始部分是servlet,我们就执行ServletProcessor1类里面的处理逻辑,如果不是就交给静态资源处理器StaticResourceProcessor处理。

if (request.getUri().startsWith("/servlet/")) {
    ServletProcessor1 processor = new ServletProcessor1();
    processor.process(request, response);
} else {
    StaticResourceProcessor processor = new StaticResourceProcessor();
    processor.process(request, response);
}

当我们在浏览器访问http://localhost:8090/servlet/PrimitiveServlet,uri就是servlet/PrimitiveServlet,起始部分是servlet

在process方法中,先找到PrimitiveServlet.class,并用类加载器加载到JVM中,并通过反射得到实例对象,即创建一个PrimitiveServlet对象,运行它的service方法。

如何找到PrimitiveServlet.class并加载它
在03-servlet模块编译后,我会得到一个target目录,里面放置本模块编译后的class文件。
如果你不了解类加载,先看类加载
我会让AppClassLoader加载target/classes目录里面PrimitiveServlet.class文件

在这里插入图片描述

public void process(Request request, Response response) {
    String uri = request.getUri();
    String servletName = uri.substring(uri.lastIndexOf("/") + 1);
    ClassLoader loader = null;
    // 先得到当前类的加载器,是AppClassLoader
    loader = ServletProcessor1.class.getClassLoader();

    //读取class字节码,要保证当前项目的classpath路径下面有PrimitiveServlet.class
    Class myClass = null;
    try {
        //如果去掉"com.cy."会怎么样?
        myClass = loader.loadClass("com.cy."+servletName);
    } catch (ClassNotFoundException e) {
        System.out.println(e.toString());
    }

    Servlet servlet = null;
    try {
        servlet = (Servlet) myClass.newInstance();
        servlet.service((ServletRequest) request, (ServletResponse) response);
    } catch (Exception e) {
        System.out.println(e.toString());
    } catch (Throwable e) {
        System.out.println(e.toString());
    }

}
项目升级

知道这个 servlet 容器的内部运作的 Servlet 程序员可以分别把 ServletRequest 和 ServletResponse 实 例 向 下 转 换 为com.cy.Request 和 com.cy.Response,并调用他们的public方法。

拥有一个 Request实例,就可以调用 parse 方法。但是我不希望程序员在自定义的Servlet的service方法里面使用 parse 方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yaoAxf0K-1640435693932)(C:\Users\cy\AppData\Roaming\Typora\typora-user-images\image-20211225114732096.png)]

你不可以把 parse 和 sendStaticResource 方法设置为私有的,因为它们将会被其他的类调 用。我们希望两个方法是在个 servlet 内部是不可见的。这里有一 个更优雅的解决办法:通过使用 facade 类。

在04-servletPro中,我们增加了两个 façade 类: RequestFacade 和 ResponseFacade。 在ServletProcessor2类的process方法里,我把Request对象传入RequestFacade构造方法里面。

程序员仍然可以在service方法里面将 ServletRequest 类型转为 RequestFacade,但是在RequestFacade我们不提供parse 方法,现在 parse 方法就是安全的了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pz4r2QYh-1640435693935)(C:\Users\cy\AppData\Roaming\Typora\typora-user-images\image-20211225201519034.png)]

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

forgetable tree

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值