Web应用体系结构
本篇对应《head first servlet and jsp》的第二章
什么是容器
Tomcat就是一个容器,在学习javaweb时最先接触到的容器就是Tomcat了。而servlet都是交给容器来管理的,也就是Tomcat 。当我们通过web应用程序向web服务器提交一个请求,不管他时GET还是POST都不会直接交给servlet本身,而是交给容器,然后容器向该servlet提供HTTP响应HttpServletRequest和HTTP请求HttpServletResponse
而且要由容器来调用doPost和doGet
容器帮助我们完成了很多事情让我们可以更专注的实现业务逻辑。
- 通信支持:容器让我们不需要自己去关心servlet和web服务器对话
- 生命周期管理:容器替我们管理了这些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);
}
}
容器如何处理请求
对于这一部分,我觉得如果先做过相应开发的人会容易理解一些。(^-^)
-
我们点击一个链接,指向一个servlet而不是一个静态页面。当然所有的请求都不会走到servlet本身,而是交给容器,这里指的是Tomcat
-
容器发现了这个请求要一个servlet紧接着容器创建了HttpServletRequest和HttpServletResponse这2个对象,当然我们知道这2个对象分别封装了请求和相应的相关信息。
-
容器通过请求中的URL找到了正确的servlet ,为这个servlet创建或分配一个Java线程,并把HttpServletRequest和HttpServletResponse这2个对象传递给这个servlet线程
-
容器调用servlet的service()方法。根据不同的请求会分别调用doPost和doGet ,本例中使用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);
}
}
- doGet将生成的内容“塞到”响应对象HttpServletResponse里。
- 线程结束,容器把响应对象转换为一个HTTP响应,把它发回给客户,然后删除请求和响应对象。
一个servlet可以有3个名字
- 用户知道的URL名
- 部署人员知道的秘密内部名
- 开发人员知道的文件名
下面用我写的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)
等我看到了再写出来吧ʕ•̫͡•ʔʕ•̫͡•ʔʕ•̫͡•ʔ