Spring MVC源码分析—Servlet解析
2017-09-28 00:07
513 查看
Servlet解析
Servlet是Server + Applet的缩写,表示一个服务器应用。(Servlet其实就是一套规范,我们按照这套规范写的代码就可以直接在Java的服务器上面运行。)
0. Servlet 3.1 中的Servlet结构图
1. Servlet接口
针对Servlet接口内的抽象方法,我们先看一段配置文件再做详细分析:
init方法:
在容器启动时被容器调用(当load-on-startup设置为负数或者不设置时会在Servlet第一次用到时才被调用),而且只会调用一次;
init方法被调用时会接收到一个ServletConfig类型的参数,是容器传进去的。而如上图中通过init-param标签配置的参数就是通过ServletConfig来保存的。
getServletConfig方法:
用于获取ServletConfig。
service方法:
用于具体处理一个请求。
getServletInfo方法:
用于获取一些servlet相关的信息,如作者、版权等,这个方法需要自己实现,默认返回空字符串。
destroy方法:
主要用于在Servlet销毁(一般指关闭服务器)时释放一些资源,也只会调用一次。
注:
Tomcat中Servlet的init方法是在org.apache.catalina.core.StandardWrapper的initServlet方法中调用的,ServletConfig传入的是StandardWrapper(里面封装着Servlet)自身的门面类StandardWrapperFacade。
Servlet是通过xml文件配置的,在解析xml时就会把配置参数给设置进去,这样StandardWrapper本身就包含配置项了,当然,并不是StandardWrapper的所有的内容都是Config相关的,所以就用了其门面Facade类。
2. ServletConfig接口
getServletName方法:
用于获取Servlet的名字,也就是在web.xml中定义的servlet-name。
getServletContext方法:
该方法非常重要,它的返回值ServletContext代表的是我们这个应用本身,回顾上面针对Tomcat中init方法调用的分析,你应该会想到,ServletContext其实就是Tomcat中Context的门面类ApplicationContextFacade。
既然ServletContext代表应用本身,那么ServletContext里面设置的参数就可以被当前应用的所有Servlet共享了。
我们做项目的时候都知道参数可以保存在Session中,也可以保存在Application中,而后者很多时候就是保存在ServletContext中。
getInitParameter方法:
用于获取init-param配置的参数。
getInitParameterNames方法:
用于获取配置的所有init-param的名字集合。
注:
ServletConfig是Servlet级的,而ServletContext是Context(也就是Application)级的。
ServletContext的功能要强大的多,并不只是保存一下配置参数,否则就叫ServletContextConfig了。
上图中,通过context-param配置的contextConfigLocation配置到了ServletContext中,而通过servlet下的init-param配置的contextConfigLocation配置到了ServletConfig中。在Servlet中可以分别通过它们的getInitParameter方法进行获取。
当然,为了操作方便,GenericServlet定义了getInitParameter方法,内部返回getServletConfig().getInitParameter的返回值,因此,我们如果需要获取ServletConfig中的参数,可以不再调用getServletConfig(),而直接调用getInitParameter。
另外,ServletContext中非常常用的用法就是保存Application级的属性,这个可以使用setAttribute来完成。比如:
需要注意的是,这里设置的同名Attribute并不会覆盖initParameter中的参数值,它们是两套数据,互不干扰。ServletConfig不可以设置属性。
补充:
Q: Servlet级和Context级都可以操作,那有没有更高一层的站点级,也就是Tomcat中的Host级的相应操作呢?
A: 在Servlet的标准里其实还真有,在ServletContext接口中有这么一个方法:public ServletContext getContext(String uripath), 它可以根据路径获取到同一个站点下的别的应用的ServletContext。当然,由于处于安全的原因,一般会返回null,如果想使用需要进行一些设置。
3. GenericServlet
如上图所示,GenericServlet是Servlet的默认实现,主要做了三件事:
1)实现了ServletConfig接口(我们可以直接调用ServletConfig里面的方法)
当我们需要调用ServletConfig中方法的时候可以直接调用,而不再需要先获取ServletConfig。
比如:获取ServletContext的时候可以直接调用getServletContext,而无需调用getServletConfig().getServletContext(),不过其底层实现其实是在内部调用了。
2)提供了无参的init方法
GenericServlet实现了Servlet的init(ServletConfig config)方法,在里面将config设置给了内部变量config,然后调用了无参的init()方法,这个方法是个模板方法,在子类中可以通过覆盖它来完成自己的初始化工作。
Q: 如此做法的三个作用?
a) 首先,将参数config设置给了内部属性config,这样就可以在ServletConfig的接口方法中直接调用config的相应方法来执行;
b) 其次,我们在写Servlet的时候就可以只处理自己的初始化逻辑,而不需要关心config了;
c) 再重写init方法时也不需要再调用super.init(config)了。如果在自己的Servlet中重写了带参数的init方法,那么一定要记着调用super.init(config),否则这里的config属性就接受不到值,相应的ServletConfig接口方法也就不能执行了。
3)提供了log方法
GenericServlet提供了两个log方法,一个记录日志,一个记录异常。具体实现是通过传给ServletContext的日志实现的。
注:
一般我们都有自己的日志处理方式,所以log这个方法用得不是很多。
GenericServlet是与具体协议无关的。
4. HttpServlet
HttpServlet是用HTTP协议实现的Servlet的基类,写Servlet时直接继承它就可以了,不需要再从头实现Servlet接口,而Spring MVC的DispatcherServlet就是继承的HttpServlet。
既然HttpServlet是和协议相关的,当然主要关心的就是如何处理请求了——>
HttpServlet主要是重写了service方法。
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException源码:
doGet方法:
注:
doGet、doPost、doPut、doDelete方法都是模板方法,而且如果子类没有实现将抛出异常;
在调用doGet方法前还对是否过期做了检查,如果没有过期则直接返回304状态码使用缓存;
doHead调用了doGet的请求,然后返回空body的Response;
doOptionos和doTrace正常不需要使用,主要是用来做一些调试工作,doOptions返回所有支持的处理类型的集合,正常情况下可以禁用,doTrace是用来远程诊断服务器的,它会将接收到的header原封不动地返回,这种做法很可能会被黑客利用,存在安全漏洞,所以如果不是必须使用,最好禁用;
HttpServlet对doOptions和doTrace做了默认实现。
Summary
1 了解Servlet的类层级关系(Servlet, ServletConfig, GenericServlet, HttpServlet)
2 HttpServlet主要是将不同的请求方法路由到不同的处理方法。
3 Spring MVC的处理思路则不一样,又将所有请求合并到了统一的一个方法进行处理。
好了,Servlet详解到此为止。如果想做更深入的学习,可以自己开发源码学习!
Servlet是Server + Applet的缩写,表示一个服务器应用。(Servlet其实就是一套规范,我们按照这套规范写的代码就可以直接在Java的服务器上面运行。)
0. Servlet 3.1 中的Servlet结构图
1. Servlet接口
针对Servlet接口内的抽象方法,我们先看一段配置文件再做详细分析:
init方法:
在容器启动时被容器调用(当load-on-startup设置为负数或者不设置时会在Servlet第一次用到时才被调用),而且只会调用一次;
init方法被调用时会接收到一个ServletConfig类型的参数,是容器传进去的。而如上图中通过init-param标签配置的参数就是通过ServletConfig来保存的。
getServletConfig方法:
用于获取ServletConfig。
service方法:
用于具体处理一个请求。
getServletInfo方法:
用于获取一些servlet相关的信息,如作者、版权等,这个方法需要自己实现,默认返回空字符串。
destroy方法:
主要用于在Servlet销毁(一般指关闭服务器)时释放一些资源,也只会调用一次。
注:
Tomcat中Servlet的init方法是在org.apache.catalina.core.StandardWrapper的initServlet方法中调用的,ServletConfig传入的是StandardWrapper(里面封装着Servlet)自身的门面类StandardWrapperFacade。
Servlet是通过xml文件配置的,在解析xml时就会把配置参数给设置进去,这样StandardWrapper本身就包含配置项了,当然,并不是StandardWrapper的所有的内容都是Config相关的,所以就用了其门面Facade类。
2. ServletConfig接口
getServletName方法:
用于获取Servlet的名字,也就是在web.xml中定义的servlet-name。
getServletContext方法:
该方法非常重要,它的返回值ServletContext代表的是我们这个应用本身,回顾上面针对Tomcat中init方法调用的分析,你应该会想到,ServletContext其实就是Tomcat中Context的门面类ApplicationContextFacade。
既然ServletContext代表应用本身,那么ServletContext里面设置的参数就可以被当前应用的所有Servlet共享了。
我们做项目的时候都知道参数可以保存在Session中,也可以保存在Application中,而后者很多时候就是保存在ServletContext中。
getInitParameter方法:
用于获取init-param配置的参数。
getInitParameterNames方法:
用于获取配置的所有init-param的名字集合。
注:
ServletConfig是Servlet级的,而ServletContext是Context(也就是Application)级的。
ServletContext的功能要强大的多,并不只是保存一下配置参数,否则就叫ServletContextConfig了。
上图中,通过context-param配置的contextConfigLocation配置到了ServletContext中,而通过servlet下的init-param配置的contextConfigLocation配置到了ServletConfig中。在Servlet中可以分别通过它们的getInitParameter方法进行获取。
String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation") String servletLocation = getServletConfig().getInitParameter("contextConfigLocation")
当然,为了操作方便,GenericServlet定义了getInitParameter方法,内部返回getServletConfig().getInitParameter的返回值,因此,我们如果需要获取ServletConfig中的参数,可以不再调用getServletConfig(),而直接调用getInitParameter。
另外,ServletContext中非常常用的用法就是保存Application级的属性,这个可以使用setAttribute来完成。比如:
getServletContext().setAttribute("contextConfigLocation", "new path")
需要注意的是,这里设置的同名Attribute并不会覆盖initParameter中的参数值,它们是两套数据,互不干扰。ServletConfig不可以设置属性。
补充:
Q: Servlet级和Context级都可以操作,那有没有更高一层的站点级,也就是Tomcat中的Host级的相应操作呢?
A: 在Servlet的标准里其实还真有,在ServletContext接口中有这么一个方法:public ServletContext getContext(String uripath), 它可以根据路径获取到同一个站点下的别的应用的ServletContext。当然,由于处于安全的原因,一般会返回null,如果想使用需要进行一些设置。
3. GenericServlet
如上图所示,GenericServlet是Servlet的默认实现,主要做了三件事:
1)实现了ServletConfig接口(我们可以直接调用ServletConfig里面的方法)
当我们需要调用ServletConfig中方法的时候可以直接调用,而不再需要先获取ServletConfig。
比如:获取ServletContext的时候可以直接调用getServletContext,而无需调用getServletConfig().getServletContext(),不过其底层实现其实是在内部调用了。
2)提供了无参的init方法
GenericServlet实现了Servlet的init(ServletConfig config)方法,在里面将config设置给了内部变量config,然后调用了无参的init()方法,这个方法是个模板方法,在子类中可以通过覆盖它来完成自己的初始化工作。
Q: 如此做法的三个作用?
a) 首先,将参数config设置给了内部属性config,这样就可以在ServletConfig的接口方法中直接调用config的相应方法来执行;
b) 其次,我们在写Servlet的时候就可以只处理自己的初始化逻辑,而不需要关心config了;
c) 再重写init方法时也不需要再调用super.init(config)了。如果在自己的Servlet中重写了带参数的init方法,那么一定要记着调用super.init(config),否则这里的config属性就接受不到值,相应的ServletConfig接口方法也就不能执行了。
3)提供了log方法
GenericServlet提供了两个log方法,一个记录日志,一个记录异常。具体实现是通过传给ServletContext的日志实现的。
注:
一般我们都有自己的日志处理方式,所以log这个方法用得不是很多。
GenericServlet是与具体协议无关的。
4. HttpServlet
HttpServlet是用HTTP协议实现的Servlet的基类,写Servlet时直接继承它就可以了,不需要再从头实现Servlet接口,而Spring MVC的DispatcherServlet就是继承的HttpServlet。
既然HttpServlet是和协议相关的,当然主要关心的就是如何处理请求了——>
HttpServlet主要是重写了service方法。
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException源码:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
doGet方法:
注:
doGet、doPost、doPut、doDelete方法都是模板方法,而且如果子类没有实现将抛出异常;
在调用doGet方法前还对是否过期做了检查,如果没有过期则直接返回304状态码使用缓存;
doHead调用了doGet的请求,然后返回空body的Response;
doOptionos和doTrace正常不需要使用,主要是用来做一些调试工作,doOptions返回所有支持的处理类型的集合,正常情况下可以禁用,doTrace是用来远程诊断服务器的,它会将接收到的header原封不动地返回,这种做法很可能会被黑客利用,存在安全漏洞,所以如果不是必须使用,最好禁用;
HttpServlet对doOptions和doTrace做了默认实现。
Summary
1 了解Servlet的类层级关系(Servlet, ServletConfig, GenericServlet, HttpServlet)
2 HttpServlet主要是将不同的请求方法路由到不同的处理方法。
3 Spring MVC的处理思路则不一样,又将所有请求合并到了统一的一个方法进行处理。
好了,Servlet详解到此为止。如果想做更深入的学习,可以自己开发源码学习!
相关文章推荐
- spring mvc源码分析(续)——视图解析过程
- spring mvc框架源码分析(一)-封装请求拦截器DispatchServlet
- Spring MVC源码分析(续)——视图解析过程
- Spring MVC源码分析(续)——视图解析过程
- spring mvc json及各类视图解析 源码分析
- spring mvc json及各类视图解析 源码分析
- servlet中Response输出源码解析
- Spring MVC 4使用Servlet 3 MultiPartConfigElement实现文件上传(带源码)
- Mybatis3源码分析(11)-Sql解析执行-BoundSql的加载-1
- Spring mvc 视图解析器 ContentNegotiatingViewResolver 源码分析
- Tomcat源码分析——SERVER.XML文件的加载与解析
- smack源码分析——接收消息以及如何解析消息
- Android源码解析四大组件系列(一)---Service的启动过程分析
- Servlet基本结构的源码解析
- libdvbpsi源码分析(四)PAT表解析/重建
- Spring对注解(Annotation)处理源码分析2——解析和注入注解配置的资源
- List源码解析之LinkedList 源码分析
- 数据库中间件 Sharding-JDBC 源码分析 —— SQL 解析(二)之SQL解析
- 对Xabber源码解析的过程(1)工程目录分析