Web应用体系结构

Web应用体系结构

本篇对应《head first servlet and jsp》的第二章

什么是容器

Tomcat就是一个容器,在学习javaweb时最先接触到的容器就是Tomcat了。而servlet都是交给容器来管理的,也就是Tomcat 。当我们通过web应用程序向web服务器提交一个请求,不管他时GET还是POST都不会直接交给servlet本身,而是交给容器,然后容器向该servlet提供HTTP响应HttpServletRequest和HTTP请求HttpServletResponse

而且要由容器来调用doPostdoGet

容器帮助我们完成了很多事情让我们可以更专注的实现业务逻辑。

  • 通信支持:容器让我们不需要自己去关心servletweb服务器对话
  • 生命周期管理:容器替我们管理了这些servlet ,他们会在容器认为合适的时候被垃圾回收
  • 多线程支持:容器会自动地为他接收的每个servlet请求创建一个新的Java线程,并且会在servlet完成相应的http服务方法后死去。
  • 声明方式实现安全:我们可以通过写XML配置文件来完成
  • JSP支持:容器来翻译JSP代码为Java代码。

下面是一个空的servlet类,我们对应请求的处理就写在这2个方法里了。

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class servletStudy extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

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

容器如何处理请求

对于这一部分,我觉得如果先做过相应开发的人会容易理解一些。(^-^

  1. 我们点击一个链接,指向一个servlet而不是一个静态页面。当然所有的请求都不会走到servlet本身,而是交给容器,这里指的是Tomcat

  2. 容器发现了这个请求要一个servlet紧接着容器创建了HttpServletRequestHttpServletResponse这2个对象,当然我们知道这2个对象分别封装了请求和相应的相关信息。

  3. 容器通过请求中的URL找到了正确的servlet ,为这个servlet创建或分配一个Java线程,并把HttpServletRequestHttpServletResponse这2个对象传递给这个servlet线程

  4. 容器调用servletservice()方法。根据不同的请求会分别调用doPostdoGet ,本例中使用doGet

service()方法继承自HttpServlet

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        //这里调用doGet
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
            //这里调用doPost
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }
  1. doGet将生成的内容“塞到”响应对象HttpServletResponse里。
  2. 线程结束,容器把响应对象转换为一个HTTP响应,把它发回给客户,然后删除请求和响应对象。

一个servlet可以有3个名字

  1. 用户知道的URL名
  2. 部署人员知道的秘密内部名
  3. 开发人员知道的文件名

下面用我写的xml文件来说明吧,例子是上面的servletStudy

<servlet>
    <!-- servlet-name是部署人员知道的秘密内部名 -->
    <servlet-name>servletStudy</servlet-name>
    <!-- servlet-class是开发人员知道的文件名 -->
    <servlet-class>com.highway.servlet.user.servletStudy</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletStudy</servlet-name>
    <!-- servlet-pattern用户知道的URL名 -->
    <url-pattern>/testURL</url-pattern>
</servlet-mapping>

这是一个映射的关系,可以看到通过部署人员知道的秘密内部名 ,我们把URL名文件名进行了映射,当用户访问/testURL的时候容器会知道这个请求要的是servletStudy这个servlet ,而具体怎么实现让容器通过文件名找到servlet将在后面进行讲解。现在只要知道通过xml配置就能完成这样的一种映射关系。

MVC

MVC是一个非常经典的设计模式了,当然我认为只有写过一个MVC架构程序的程序员看到这里才会理解,至少我就是这样的,我最早接触MVC这个名词是去年的11月吧🐷🐷🐷 ,上个月迷迷糊糊的跟着视频写,也不过是知道这是MVC设计模式,但是对什么是MVC并没有特别深😥的看法。直到看到这个部分,突然有种醍醐灌顶💡的感觉,当然实际上我只是写过一个简简单单的web应用而已,如果有错误❌请各位看官指正。

但凡了解过MVC设计模式的盆友,一定已经听👂了无数遍“视图和业务逻辑分离”了吧。那么什么是分离呢?

重点关注doGet方法,想想这是分离吗?

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class servletStudy extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("servletStudy运行中");
		//getRequestDispatcher转发至名为hello.jsp的视图层
        req.getRequestDispatcher("hello.jsp").forward(req, resp);
    }

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

这是hello.jsp视图层

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
	<head lang="en">
    	<meta charset="UTF-8">
    	<title>主页</title>
	</head>
	<body>
		<h2>Hello World!</h2><br/>
	</body>
</html>

这是分离吗?或许这个业务逻辑的代码过于简单而难以看出,仅仅是打印一句话(⊙ˍ⊙)

实际上这做到了视图业务逻辑分离,可以看到我们的业务只关注于打印一句话,而剩下的事情就getRequestDispatcher转发到了hello页面。但是这并不是MVC 。因为如果另外一个servlet也要打印这句话呢?或许你会说,那我cv一下就好了,但是如果打印这句话的servlet很多而且需要稍稍调整一下打印的话呢?那么我们需要去每个打印这句话的servlet去修改,想想光是为了调整一行代码就要修改如此之多的servlet就非常可怕了

Σ(っ °Д °;)っ 再加上如果业务逻辑非常复杂呢?这简直就是维护地狱👹👹👹 而且这么多重复的代码也显得非常多余。

或许你可能注意到了这是视图业务逻辑分离,但是真正的业务逻辑分离是完全不知道有视图的存在,换句话说我们需要一个东西作为业务逻辑视图的中介,通过这个中介将2者联系起来。这个中介就是MVC中的C控制器。那么剩下的M是什么呢?就是我们的业务逻辑了。我们来写一个M看看,什么是M

public interface Person {
    public void speak(String str);
}

先写一个interface以后增加新的功能比如吃饭、睡觉、打豆豆就先在这个接口里增加然后继承他就行了。

public class PersonImpl implements Person{
    
    public PersonImpl() {}
    
    @Override
    public void speak() {
        System.out.println("servletStudy运行中");
    }
}

我们写好了实现类之后就可以在控制器中使用了。我们把目光放回servletStudy

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class servletStudy extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /* System.out.println("servletStudy运行中");
         被替换为下面这2行代码了 */
        Person person = new PersonImpl();
        person.speak();        
		//getRequestDispatcher转发至名为hello.jsp的视图层
        req.getRequestDispatcher("hello.jsp").forward(req, resp);
    }

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

看到这里应该非常明白这样设计的妙处在哪了吧?如果我们打印的话要调整,我们只需要去PersonImpl实现类里去修改speak方法就行了,相比之前的维护地狱👹👹👹 简直是 👏 VERY GOOD 👏

但是这样就是一个精妙的MVC设计了吗?显然不是的。可以预想到这样确实解决了业务逻辑带来的维护地狱但随着servlet的增加,servlet作为控制器只是负责驱动M然后更新V也就说servlet本身会出现大量重复的代码。那么新的维护地狱又来了இ௰இ 这可怎么办呢?

非常可惜本章到此结束,答案将会在《head first servlet and jsp》的最后给出。

(因为我也不会啊hh)

等我看到了再写出来吧ʕ•̫͡•ʔʕ•̫͡•ʔʕ•̫͡•ʔ

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值