Web架构演进历程
早期的Web技术主要用于浏览静态页面,首先浏览器发起HTTP请求,请求一些静态资源,这时候需要一个服务器来处理HTTP请求,并且将相应的静态资源返回。这个服务器叫HTTP服务器。
简单点说就是解析请求,然后得知需要服务器上面哪个文件夹下哪个名字的静态文件,找到返回即可。
而随着互联网的发展,交互越发得重要,用户已经不满足于仅浏览静态页面。用户需要一些交互操作,获取一些动态结果。业务变得复杂,需要我们编写代码来处理诸多业务,需要根据HTTP请求调用不同的业务逻辑来响应。如果基于HTTP协议实现服务器端软件增强功能太过复杂,所以需要一些扩展机制来实现用户想要的功能。
早期使用的Web服务器扩展机制是CGI(Common Gateway Interface,公共网关接口)。
CGI程序在一定程度上解决了用户需求。不过还存在一些不足之处,如CGI程序编写困难,响应时间较长,以进程方式运行导致性能受限。我们迫切需要一种新的抽象,在克服CGI程序缺点的同时,去实现业务代码跟HTTP服务器的解耦。
业务千千万,所以需要规定一个接口,所以业务类都实现这个接口这样才好对接,这就是接口的含义。这个接口就是Servlet规范,Servlet指代的是实现Servlet接口的那些业务类。而Servlet容器其实就是用来管理和加载这些Servlet类的,根据HTTP请求找到对应的Servlet类就是 Servlet容器要做的事情。
这里还能再抽一层?业务实现写在Servlet类中,而管理和加载Servlet类和具体的业务实现是没关系的,没必要把Servlet容器做的事情和具体的业务耦合起来,业务反正照着Servlet接口实现就行,这样Servlet容器就可以加载并管理它。
把HTTP请求和Servlet的对应关系也抽象出来,就是web.xml了,咱们在配置里面告诉Servlet容器对应关系即可,当HTTP请求进来时,Servlet容器会根据映射关系找到对应的Servlet类。
其实Servlet接口和Servlet容器这一整套包括目录命名合起来就叫Servlet 规范。所有相关的中间件按照Servlet规范实现,我们也按Servlet规范来实现业务代码,这样我们就能在不同场景选择不同的Web中间件。规范的目的就是为了对接方便,减少对接成本。
至此HTTP服务器、Servlet 、Servlet容器都划分清晰了。而Web容器其实就是HTTP 服务器 + Servlet容器,因为单单Servlet容器没有解析HTTP协议的能力。
所以把Tomcat、Jetty等实现了HTTP服务器和Servlet容器的功能,称之为Web容器。
架构的设计就是一系列相关的抽象。先是抽象出HTTP服务器,用来通信和解析协议。再因为业务的复杂,为了不和HTTP服务耦合又抽象了一层Servlet。由Servlet容器来专职负责加载和管理Servlet,将请求转发到指定的Servlet实现类。然后我们安心的开发业务即可。
因为抽象所以灵活易扩展,比如现在是HTTP1.1服务,可以换成HTTP 2。
现在用Tomcat来作为Servlet容器,也可以换成Jetty。
现在用原生的实现Servlet来做业务,也可以换成Spring MVC。
随意变更,因为都抽象出来了,就很好替换,只要遵循约定的接口实现即可。
框架设计的套路
从以上架构演进历程,也可以总结出框架设计的套路:接口和抽象类。
这是所有中间件设计必用的套路,当然我们自己的代码也可以这样用。
先定义一个接口来约定一些动作,能做什么。
然后再定义一个抽象类来实现这个接口,用来实现一些通用的逻辑,做到代码的复用。
然后再搞一些常用的实现类继承抽象类,方便开发者的使用。剩下的就留给开发者自行扩展即可。
然后抽象类都会使用模板方法,也就是定义执行的流程,流程中的具体实现逻辑由子类自行实现。
这就是必用的套路:接口约束、抽象类完成代码复用(提供通用实现)、常用实现类方便直接使用、模板方法定义执行流程、剩下的自行扩展。
拿Servlet举例,首先定义Servlet接口。
public interface Servlet {
void init(ServletConfig config) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
String getServletInfo();
void destroy();
}
然后搞了个通用抽象类GenericServlet,不过这个抽象类逻辑比较简单。
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
// 省略一些...
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 省略一些...
}
然后又搞了个常用的HttpServlet继承了GenericServlet。
public abstract class HttpServlet extends GenericServlet {
private static final long serialVersionUID = 1L;
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
// 省略一些...
}
套路就是这么个套路,计算机科学中的每个问题都可以用一间接层解决?是的,基本上所有问题抽象一层都能解决。如果一层不够,那就两层。