Servlet学习总结
之前做好几个项目,后端都直接用springBoot,真的是太方便了...大概知道点注解就能开始做项目了,结果今天想从底层造个轮子,光配置web项目配置了老半天...才发现自己Web这些基础都忘得差不离了,这两天再看看,把之前看的回忆一哈做个记录。目前想按照Servlet-->Tomcat-->SpringMVC的顺序来慢慢梳理梳理。先挖个坑,这几天有空就填。
一、基本概念
我们首先要区分一下几个概念,即web服务器、servlet容器以及servlet这三个概念。
web服务器,其主要的作用就是提供URL以便外部访问。我们在本地的各种文件、数据他人是看不到的,但是通过部署在web服务器上,我们提供接口使外界可以通过对应的ip和路径来访问我们的资源。
servlet容器,就是管理servlet资源的东西。我们一台机器只有一个ip地址,但为什么我们却可以在同一个ip下访问获得不同的资源呢,这就是servlet容器的功劳了,通过识别不同的URL,servlet容器可以将请求分发给不同的servlet进行处理,从而返回不同的资源。
servlet就是对于不同的请求,进行不同的逻辑处理的具体业务逻辑对象了。这也是我们最常打交道的东西了。
其中我们常用的tomcat就相当于web服务器和servlet的结合,通过在tomcat上进行部署我们的项目,他会自动根据我们的配置文件创建相应的servlet容器,因此我们在进行项目设计的时候,只需要关心servlet中具体的逻辑代码即可了。
二、Servlet的来龙去脉
2.1 Servlet接口
其实,Servlet指的就是javax.servlet.Servlet
这个简简单单的接口,它本身并没有什么很复杂的方法,当然它本身也不应该很复杂,毕竟作为供给我们这些底层码农直接操作的东西,怎么可能会很复杂呢(:
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
是不是很简单呢,一共只有五个方法,其中四个一看就知道是要干什么的:
init
方法就是在Servlet刚创建进行初始化的时候执行的,这里需要传递一个ServletConfig对象,看名字能猜到这是对Servlet的配置的对象,但是我们如何对Servlet进行配置呢,我们如何获得一个ServletConfig对象传入呢?这是在init
方法中,我们疑惑的问题。
第二个方法getServletConfig
就很明确了,就是获得我们的ServletConfig对象,看来以后实现这个Servlet
接口的对象内部应该会有一个ServletConfig对象,要不然不会设计有这样一个接口。
第三个方法service
方法是我们实际的业务逻辑处理要复写的方法。
在这里,我们获得了对应的请求对象,就可以获得请求对象里面的各种参数,然后进行处理,最后将处理结果设置在对应的响应对象中,传回给用户。
其中的ServletRequest
和ServletResponse
这两个兄弟是干什么的,看名称我们大概理解是一个是请求,一个是响应。但里面究竟写了什么呢?这是我们的第二个问题。
第四个方法getServletInfo
,就是获得Servlet的一些信息。
destory
方法就是在Servlet销毁的时候,执行的方法。
好的,现在我们来慢慢解决上面的两个问题,即1、我们怎么配置一个Servlet,又怎么得到一个ServletConfig的对象。2、service方法中其形参ServletRequest和ServletReponse又是什么样的对象。
2.2 ServletConfig接口
对于第一个问题,让我们看一下ServletConfig
接口。
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}
ServletConfig接口里面就对应的是一些Servlet的配置信息,其中较为关键的就是getServletContext
获得ServletContext对象,ServletContext是Servlet的上下文对象,于是....问题来了:那ServletContext又是什么啊!每打开一个新的对象,都会出来一堆新的对象ORZ,于是我们的第三个问题就来了:ServletContext又是什么东西。
除了这个ServletContext以外,其他的都看起来没有什么疑惑,就是获得Servlet名字以及获得初始化参数的信息。虽然不知道其实现类会怎么实现,但起码我们知道这个方法到底是干什么的。
2.3 GenericServlet 抽象类
刚刚我们写的两个都是接口,其声明了一些方法,但是还没有一个类对他们进行实现,而现在要说的GenericServlet就是他们两个的实现类,里面大部分的方法大概看一下就能了解内容和逻辑,我们下面对重点的方法进行分析。
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameter(name);
}
}
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameterNames();
}
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletName();
}
}
}
这里是对两个接口的方法的实现,其中需要关注的一个是init(ServletConfig config)
这个方法里面,我们可以看到Servlet的配置从外部传进来,其初始化的时候,又执行了init()
这个方法,而我们在复写方法的时候,只需要重新实现对应的init()
方法即可。
这好像还是没有解决,我们怎么获得一个ServletConfig对象的问题,但找完javax.servlet包的所有类,都没有ServletConfig接口的实现类啊,那是不是我们需要自己写一个ServletConfig的实现类,然后配置好对应的参数,才可以跑我们的程序呀!
现在看起来,好像是这样的。但转念一想,我们只是码农啊,怎么可能写这么复杂的东西,我之前调配置的时候,都是直接改一改一些配置文件就好了啊,怎么还会用自己实现配置类呢。别忘了,我们的servlet虽然是javax.servlet包下的内容,但我们还有tomcat呀。在tomcat的配置中,早就帮我们实现了对应的配置类了,我们要做的,只是按照规范进行项目的配置就好了。而配置方法就是我们在web项目里一直会用到的web.xml文件。tomcat会读取对应的web.xml配置文件,然后解析创建对应的实现了ServletConfig接口的配置类,载入到我们的Servlet类中。
这样,我们的第一个问题就解决啦:我们可以使用web.xml文件来进行我们Servlet的配置,并且在tomcat中,在servlet容器启动的过程中,会读取web.xml的配置文件,并且解析文件生成对应的配置类,载入到Servlet类中。
对应的配置文件注入的简略流程图如下图:
至于tomcat怎么进行的解析,生成了什么样的实现了ServletConfig的配置类,我会在这几天开始对Tomcat源码的分析,在那里我们就可以找到想要的答案啦。(正在更新ing)
这个解决以后,我们再来看看我们的第二个问题,ServeltRequest和ServletResponse接口。
2.4 ServletRequest和ServletReponse接口
public interface ServletRequest {
Object getAttribute(String var1);
Enumeration<String> getAttributeNames();
String getCharacterEncoding();
void setCharacterEncoding(String var1) throws UnsupportedEncodingException;
int getContentLength();
long getContentLengthLong();
String getContentType();
//方法很多,但意思都差不多,就不再展开了
}
这里面的方法有点多,就不再一一描述了,说到底就是处理我们在浏览器中经常会看到的response和resquest,但这些都是接口,里面各种获取方法都要我来实现,网络协议有好多种,而且我也不懂各个协议的内容,所以,这些东西肯定也不是我们写的啦。在Tomcat里,他早就已经封装处理了对请求和响应的方法和类,一旦有请求过来,在web服务器和Servlet容器两个地方就会自动把请求解析成对应的Request对象了,而我们对Reponse对象进行各种属性的设置以后,在返回给用户的过程中,tomcat也会变成符合对应协议的内容来返回。
至于tomcat是怎么做的,那当然就要看源码啦~
2.5 ServletContext
ServletContext是一个Web应用对应一个,而不是每一个Servlet对应一个,它包含了所有Servlet共享的信息,从而可以实现Servlet之间消息的共享、访问静态资源、请求转发等操作。
这里推荐一个文章:JavaWeb-ServletContext,里面对每个方法都有详细的讲解。
2.6 HTTP协议下对应的Servlet
因为现在我们的请求基本都是基于HTTP协议的,所以在javax.servlet中专门准备了对应与http协议下的Servlet类:HttpServlet
public abstract class HttpServlet extends GenericServlet{
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
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) {
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);
} 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);
}
}
// 还有对应很多doxx方法没有写,本质是一样的
}
在这里,因为初始的servlet接口中使用service的话,对于不同的请求,我们在service中需要判断然后执行相应的代码,这样会使得service中逻辑繁琐,因此在HttpServlet中,它对service进行了进一步的封装,我们只需要实现对应的doGet
和dopost
等对应请求方式的方法即可,而不用自己再在service中写相应请求方式的判断逻辑了。
三、Servlet实际使用
说了这么多,让我们看看怎么搭建一个Web应用,并且添加Servlet吧。
还是那句话,框架大牛们都已经搭建好了,其框架搭建的目的就是让我们不用了解太多东西就可以实现我们想要的业务逻辑,所以肯定不会太难啦。
首先,我们新建一个web项目,在这里我使用Maven来构建一个项目,在pom.xml中添加如下依赖:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
其次,我们将项目改为Web项目,我使用的是IDEA。
打开Project Structure,并且选中Modules选项,使用add创建一个Web的modules以及Artifacts:
我们就可以看到,在项目目录下会多一个web文件夹,且其中WEB-INF中有对应的web.xml文件。
然后,新建一个继承HttpServlet的类,来复写其中的方法:
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello?");
}
}
这样,在得到Get请求的时候,我们会返回一个"Hello?"
最后,我们配置web.xml文件,来配置对应的Servlet信息:
<servlet>
<servlet-name>ServletTest</servlet-name>
<servlet-class>com.xiaoxin.Servlet.ServletTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletTest</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
配置好后,我们启动tomcat,进入localhost:8080/test来看看我们的项目是否成功部署。
最后的项目结构为:
好啦,对于Servlet的相关内容就到这里啦。
近期会看一看Tomcat的源码,好好理解一下它的流程。(希望不会鸽)
参考文章:
JavaWeb——Servlet(全网最详细教程包括Servlet源码分析)
当然,这里只是对其源码进行了一些分析,对于其更多的应用,读者可以自己再学习,在菜鸟教程里也有很多详细的使用方法。