Servlet基本认识
Servlet的本质就是一个接口,有的人往往以为就是Servlet直接处理客户端的http请求,其实并不是这样,servlet并不会去监听端口;直接与客户端打交道是我们前面已经学过的Tomcat服务器。
客户端发送请求到tomcat,它监听端口,请求到达tomcat,根据url等信息,确定要将请求交给哪个servlet去处理,然后调用那个servlet的service方法,service方法返回一个response对象,tomcat再把这个response返回给客户端。如下图就是整个执行过程。
servlet_demo1 : 是一个moudle即一个项目
ServletDemo01 : 是一个servlet
Servlet生命周期
- 调用 init() 方法初始化
- 调用 service() 方法来处理客户端的请求
- 调用 destroy() 方法释放资源,标记自身为可回收
- 被垃圾回收器回收
package com.wunan.servlet;
import javax.servlet.*;
import java.io.IOException;
public class StudentServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 所有的客户端请求都会经过这个service方法
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("这是一个servlet入门工具类");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
Servlet继承关系
- 第一种:
- 实现Servlet接口,实现所有的抽象方法,该方法支持最大程度的自定义。
- 第二种:
- 继承GenericServlet抽象类,必须重写service方法,其他方法可以选择重写,该方式可以让开发Servlet变得简单,但是和Http协议没有关系,比如不能指定请求方法。
- 第三种:
- 继承HttpServlet抽象类,需要重写doGet和doPost方法,这也是最常用的方法。
@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);
}
快速入门
首先创建一个java web项目,配置好tomcat,配置前需要检查tomcat的lib目录下是否有servlet的jar,并且检查jar不为空(有一次下载的tomcat jar里的javax类空,导致无法实现servlet接口)。
- 在src下新建一个类叫ServletDemo,然后继承HttpServlet。
package com.wunan.servlet;
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 ServletDemo02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("方法执行了。。。");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
- 然后在web.xml中声明Servlet,其中servlet中的name与servlet-mapping中的name要保持一致,url即访问时要输入的地址。
<servlet>
<servlet-name>servletDemo01</servlet-name>
<servlet-class>com.wunan.servlet.ServletDemo01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletDemo01</servlet-name>
<url-pattern>/servletDemo01</url-pattern>
</servlet-mapping>
- 检查tomcat的war包。
- 打开浏览器访问,就可以在控制台中看见输出的语句 。(http://localhost:8080/Servlet_demo3_war_exploded/servletDemo01)
注意:当有多个浏览器的请求同时打到同一个Servlet时,容器会起多个线程同时访问一个servlet的service()方法,有可能会出现线程安全的问题,因此能使用局部变量尽量使用局部变量,否则需要对成员变量进行加锁处理。
面试问题:Servlet如何同时处理多个请求访问?
单实例多线程: 主要是请求来时,会由线程调度者从线程池李取出来一个线程,来作为响应线程。这个线程可能是已经实例化的,也可能是新创建的。
Servlet容器默认是采用单实例多线程的方式处理多个请求的:
1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);
2.容器初始化化Servlet主要就是读取配置文件(例如tomcat,可以通过servlet.xml的设置线程池中线程数目,初始化线程池通过web.xml,初始化每个参数值等等。
3.当请求到达时,Servlet容器通过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者;
4.线程执行Servlet的service方法;
5.请求结束,放回线程池,等待被调用;
(注意:避免使用实例变量(成员变量),因为如果存在成员变量,可能发生多线程同时访问该资源时,都来操作它,照成数据的不一致,因此产生线程安全问题)
从上面可以看出:
第一:Servlet单实例,减少了产生servlet的开销;
第二:通过线程池来响应多个请求,提高了请求的响应时间;
第三:Servlet容器并不关心到达的Servlet请求访问的是否是同一个Servlet还是另一个Servlet,直接分配给它一个新的线程;如果是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行;
第四:每一个请求由ServletRequest对象来接受请求,由ServletResponse对象来响应该请求;
Servlet的不同映射方式
<!-- Servlet不同的映射方式-->
<!-- 指定名称的方式-->
<!-- <servlet>-->
<!-- <servlet-name>servletDemo05</servlet-name>-->
<!-- <servlet-class>com.wunan.servlet.ServletDemo05</servlet-class>-->
<!-- </servlet>-->
<!-- <servlet-mapping>-->
<!-- <servlet-name>servletDemo05</servlet-name>-->
<!-- <url-pattern>/servletDemo05</url-pattern>-->
<!-- </servlet-mapping>-->
<!-- <servlet>-->
<!-- <servlet-name>servletDemo05</servlet-name>-->
<!-- <servlet-class>com.wunan.servlet.ServletDemo05</servlet-class>-->
<!-- </servlet>-->
<!-- 不管servlet后面写什么都能匹配到-->
<!-- <servlet-mapping>-->
<!-- <servlet-name>servletDemo05</servlet-name>-->
<!-- <url-pattern>/servlet/*</url-pattern>-->
<!-- </servlet-mapping>-->
<!-- 匹配到 .hahah 结尾的地址,注意*号前面没有 / -->
<!-- <servlet-mapping>-->
<!-- <servlet-name>servletDemo05</servlet-name>-->
<!-- <url-pattern>*.hahah</url-pattern>-->
<!-- </servlet-mapping>-->
Servlet注解开发
在JavaWeb开发中, 每次编写一个Servlet都需要在web.xml文件中进行配置,如下所示:
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.wunan.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
每开发一个Servlet,都要在web.xml中配置Servlet才能够使用,这实在是很头疼的事情,所以Servlet3.0之后提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署描述,简化开发流程。
Servlet基于注解的配置方式
在直接在servlet类名上面写以下注解,能达到同样的效果:
@WebServlet("/login")
package com.sponge.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost");
}
}