一、什么是servlet
- servlet是java写的服务端程序,交互式的浏览修改数据,生成动态web内容。通俗讲,它就是java web开发的基础。
- Servlet API中定义了70中类型,可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
- 所有的Servlet就是直接或间接实现 javax.servlet接口。Servlet 接口定义了 Servlet与Servlet容器之间的契约:Servlet容器将Servlet载入内存,并在Servlet 实例上调用具体方法,在一个应用中,每种Servlet类型只能有一个实例(Servlet容器默认采用单实例多线程的方式来处理请求), Servlet 不是线程安全的,不能有成员变量,不要有synchorize 的方法(http://blog.chinaunix.net/uid-21209537-id-4304323.html)
- org.springframework.web.servlet.DispatcherServlet是SpringMVC的核心,SpringMVC请求的第一站是DispatcherServlet,充当前端控制器角色,DispatcherServlet会查询一个或多个处理器映射(handler mapping)并根据请求所携带的URL信息进行决策,将请求发送给哪个SpringMVC控制器(controller),controller 定义视图、打包数据返回给DispatcherServlet, DispatcherServlet 通过视图解析器(view resolver)来将逻辑视图名匹配为一个特定的视图实现,然后数据模型以视图形式响应给客户,完成整个请求流程。
- Spring boot自动注册DispatcherServlet。
二、Servlet生命周期
- 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。
- Servlet 容器加载并实例化 Servlet,调用init()方法初始化,仅执行一次。
- Servlet 容器创建每个线程都调用唯一Servlet 实例的 service() 方法(service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法)。
- Servlet实例被标记为垃圾回收,调用destroy(),结束生命周期。
三、 Servlet的线程工作模型
- Servlet收到客户端的具体请求,Servlet首先会把请求发送给调度器,由调度器进行统一的请求派发,调度器会从线程池中选取一个工作主线程;
- 调度器把请求派发给该线程,由该线程的执行servlet的service方法;
- 这个线程正在执行的时候,servlet容器又收到另一个请求,调度器会从线程池中选取另外一个工作主线程来服务这个新的请求,容器并不关心请求访问的是同一个servlet还是另外一个servlet;
- 容器收到同一个servlet的多个请求时,这个servlet的service方法将会在多线程中并发执行;
- 线程使用完之后,就会把线程放回线程池中,如果说线程池中的线程都在进行服务,有新的请求,一般情况下这些请求会做排队处理,servlet容器也能配置servlet的一个最大的请求排队数量,假如超过这个数量,servlet就会拒绝相应的servlet请求。
四、 Servlet并发处理的特点:
- 单实例:
从servlet生命周期中可以知道,对于具体的某个servlet来说,servlet只会初始化一次,调用一次init(),也就是说,在整个servlet容器中只会有一个servlet的实例对象,不管我们有多少请求都是针对同一个servlet实例对象。 - 多线程:
从线程的模型可以看到,请求的处理由多个工作线程来完成的,可以同时进行处理,同时处理的数量跟线程池的大小有关系。 - 线程不安全:
servlet是单个实例,但又是多个线程共用实例对象,意味着servlet容器在多个线程访问同一个servlet的实例对象是没有默认加锁操作的,照成线程不安全,因为我们可能出现某一个线程正在修改servlet的实例状态,但是另外一个线程又需要读取servlet的线程状态,这个时候就会出现数据不一致的情况。
四、Servlet线程安全注意要点
- 变量的线程安全:
A), 变量本地化:也就是说尽量使用局部变量,因为多线程并不共享局部变量。
B),使用同步块synchronized:就是对我们的一些代码进行加锁的处理,在做加锁处理的时候应该注意,尽可能的缩小synchronized的代码块的范围
C),最好不要再service方法上增加关键字,这样会对性能的损耗比较大。 - 属性的线程安全:
A),ServletContext线程不安全:因为ServletContext是可以多线程读取它的属性,所以线程不安全,那我们在对sevletContext属性进行读写的时候,就需要注意做一些同步的处理
B),HttpSession理论上线程安全:HttpSession属性是在用户会话期间存在的,那只能在处理同一个Session请求的线程中被访问,那Session对象的属性访问理论上是安全的,当用户打开多个属于同一个进程的浏览器窗口的时候,在这些窗口的访问属于同一个Session会出现多个请求,需要多个线程来进行处理,也会照成多个线程读写属性的问题,这个时候我们还会需要对Session的属性进行一个同步处理。所以是理论上的线程安全
C),ServletRequst线程安全:对于每一个请求只能由一个线程进行处理,所以ServletRequst对象只能在同一个线程中被访问。 - 避免在Servlet中创建线程
因为Servlet本身就是多线程进行处理的,如果在Servlet再创建一些线程,就会导致执行情况变得非常复杂,出现一些多线程安全的问题 - 多个Servlet访问外部对象加锁
五、Springboot中的DispatcherServlet
springboot在自动配置springMVC的时候,自动通过org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration并利用DispatherServletRegistrationBean和DisPatherservletPath来注册DispatcherServlet。
自定义mapping配置可:
1), 在application.properties 文件中增加 server.servlet-path= /api/*;
2),注入一个新的ServeletRegistryBean用于映射新的url;
//自动注入spring boot默认的上传配置
@Autowired
private MultipartConfigElement multipartConfigElement;
@Autowired
private DispatcherServlet dispatcherServlet;
@Bean
public ServletRegistrationBean apiServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(dispatcherServlet);
//注入上传配置到自己注册的ServletRegistrationBean
bean.addUrlMappings("/api/*");
bean.setMultipartConfig(multipartConfigElement);
bean.setName("apiServlet");
return bean;
}