如何理解Servlet及Servlet容器的概念以及使用注意点,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
Servlet是一个服务器端编程的组件,主要能力产生动态的数据内容,和任何java组件一样,Servlet也是平台不相关的。 Servlet的运行依赖于一个容器 (或者叫servlet engine),容器在运行时会把Servlet字节码加载到虚拟机中生存一个servlet实例,并负责管理servlet实例的生命周期。
和servlet交互的对面可以是一个web浏览器,也可以是任何一个支持http协议的客户端程序,客户端和Servlet之间交互遵循request/response模式进行,实际上Servlet设计为并不仅仅局限于http协议,任何基于request/response的协议Servlet都支持。
Servlet容器为servlet提供运行环境,servlet容器通常实现为一个Web服务器或者应用服务器的一部分。
Servlet容器的核心功能是接受请求、解码请求内容,然后把解析后请求内容分发到相关的某个Servlet,Servlet处理后,容器会把Servlet返回的数据内容进行编码并发送给客户端。这个过程中容器可以在把请求分发给servlet之前修改请求内容,也可以在把servlet的返回内容发送到客户端之前修改响应内容。
Servlet容器必须要提供http 1.0/1.1的支持,也可以支持https。
一个servlet的生命周期阶段主要包括:加载并创建servlet实例、初始化servlet实例、处理请求以及销毁。
servlet的生命周期阶段完全由servlet容器来管理,从编程的角度看生命周期主要体现在Servlet接口中定义的三个方法:
init方法,让应用程序有机会执行初始化
service方法,应用程序处理请求并响应内容的地方
destroy方法,让应用程序有机会释放servlet持有的各种资源
任何一个servlet都必须直接或者间接实现这三个方法。 Servlet实例则由容器负责实例化,不需要应用程序参与,容器可以在启动完成后就开始加载servlet,也可以在第一次servlet被选中处理请求时才加载此servlet。
servlet容器完成servlet的实例化后就开始进行初始化,容器会调用 init 方法并传递一个 ServletConfig 对象的引用,通过ServletConfig引用可以在Servlet中感知到Servlet的一些详细配置参数和代表应用程序的ServletContext引用。一个servlet实例整个生命周期过程中init方法只会被容器调用一次。
只有初始化完成的servlet才能进一步提供请求服务,如果init方法中抛出ServletException异常或者没有在容器定义的时间内返回,则servlet容器认为初始化失败,此servlet不能提供服务。init方法抛出的可能是一个UnavailableException异常,这个异常暗示容器此servlet目前不可用:永久性不可用或者临时不可用,如果是临时性不可用则UnavailableException应该带上一个不可用预估时间秒数,这种情况下容器不会直接失败,而是会阻塞预估的时长然后重新创建一个servlet实例并再次执行初始化。
servlet成功初始化后就可以处理请求了,容器会调用service方法,并传递ServletRequest和ServletResponse两个引用,ServletRequest封装了客户端请求信息,用户应用程序的则负责填充ServletResponse对象,service方法返回后流程被容器接管。
service方法如果抛出ServletException异常代表失败,容器需要作出合适的下一步处理,sevlet规范定义如下过程:service方法如果抛出实际是一个UnavailableException异常则表示此servlet目前不可用,同样有永久性不可用和临时不可用两种情况,如果是永久性不可用,则容器会调用servlet的destroy方法清理然后释放此servlet,并给客户端响应SC_NOT_FOUND(404)状态;如果是临时性不可用,则servlet容器在预估的不可用时间内会直接拒绝所有的匹配到此servlet的请求而直接响应SC_SERVICE_UNAVAILABLE(503)状态,并响应一个 Retry-After header,容器也可以选择忽略这两种不可用的差别,直接当做是永久性不可用并清理释放servlet对象。
一个servlet实例正常情况下应该是永久驻留在容器中,直到关闭容器时才会销毁容器中的servlet实例。在清理一个servlet实例之前,容器总是会调用destroy方法,用户通常可以在destroy方法中做一些资源清理、持久化等工作,一旦一个servlet的destroy方法被调用后,容器就会完全释放它以便垃圾收集器回收。
通常我们要在应用程序中实现一个Servlet类的话,不需要直接实现Servlet接口,而是通过扩展GenericServlet,在http环境中的话就就可以扩展HttpServlet,GenericServlet对Servlet接口做了基本实现,只是预留了service方法需要用户实现, GenericServlet的实现没有绑定到任何具体的应用层协议,它是通用的。
在HTTP协议环境中,平台还提供了一个HttpServlet的实现类,HttpServlet就扩展自GenericServlet,HttpServlet完成实现了service方法,service方法中的实现方式是根据不同的http动词作了一些基本情况的判断处理,然后把正常情况下的过程委托到几个特定的http方法:doGet,doPost,doPut,...,绝大多数情况我们自己的Servlet实现类应该选择扩展HttpServlet,并且不应该直接重写service方法,而是应该重写doXXX方法。
public class RequestParameterServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); // … } }
Servlet接口中定义的init方法带有一个ServletConfig参数:
public void init(ServletConfig config) throws ServletException;
init方法在GenericServlet中做了一个基本实现:
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }
在GenericServlet版本的init实现中,先把容器传递过来的ServletConfig引用保存下来,然后调用了一个无参数的init方法,这个无参数的int方法正是预留给用户实现的,因此,不管我们的Servlet类是扩展了GenericServlet,还是扩展了HttpServlet,如果有初始化的需求,那么我们应该重写GenericServlet中的无参数init方法。
通常情况下,容器只会为web descriptor中声明的每个servlet创建一个实例,这意味着在多线程情况下,其service方法是并发状态下运行,因此我们在实现service方法时要自己保证其线程安全性,通常的做法是不在Servlet实现类中使用共享字段,或者对service方法中的相关代码段加锁。
另一种方式是我们的Servlet类实现SingleThreadModel接口,SingleThreadModel是一个标识接口,servlet容器保证实现SingleThreadModel的servlet实例的service方法在同一时间只有一个线程访问,容器的做法可能是在servlet实例上加锁,容器也可能会为同一个Servlet类创建出多个实例并在容器中缓存它们,然后对每一个请求分发到其中一个空闲的servlet实例。但是并不推荐使用SingleThreadModel接口,因为SingleThreadModel方式并不能完全解决线程安全问题,例如session中的数据并不能保证线程安全,并且在servlet 2.4版本中已经废弃。
客户端的所有请求数据,包括请求路径,协议,请求头以及请求内容等这些数据全部都被容器封装在一个ServletRequest对象中,从ServletRequest对象中读取请求内容时必须要知道数据是如何编码的,否则就不可能正确解析请求数据。
由于目前大多数浏览器客户端在发送请求时都没有在Content-Type域中提供字符编码的标记,因此servlet规范中明确要求所有servlet容器在读取请求数据时应该默认使用“ISO-8859-1”来解析数据。如果客户端没有发送请求数据的编码格式,规范定义调用getCharacterEncoding方法应该返回null。
如果客户端没有发送请求内容的编码格式,并且如果容器设定的默认编码和实际的请求内容编码又不一致时,就会解析得到错误的数据,为了纠正这种情况ServletRequest另外供了一个setCharacterEncoding方法,编程人员可以调用此方法覆盖容器提供的编码格式来读取数据,此方法的调用一定要在读取任何请求内容之前,否则不会有效果。
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。