关于Servlet一些东西
---- 概念
Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。 它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器响应(HTTP服务器上的数据库或应用程序)的中间层。 Servlet是位于Web 服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序不同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机。
Servlet 容器是 web server 或 application server 的一部分,提供基于请求/响应发送模型的网络服务,解码基于 MIME 的请求,并且格式化基于 MIME 的响应。Servlet 容器也包含了管理 Servlet 生命周期。
附加:文中参考代码
---- 生命周期
说明:
- init():
- 在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。
- service():
- 它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。
- destroy():
- 仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。
Servlet在Tomcat的工作流程
说明:
1)web client向Tomcat发起Http请求
2)Servlet容器接收到该Http请求
3)Servelt容器创建一个HttpRequest对象,将web client的请求信息封装到这个对象中去
4)Servelt容器创建一个HttpResponse对象
5)Servelt容器调用HttpServlet对象中的service()方法,将HttpRequest对象和HttpResponse对象作为参数传给HttpServlet对象
6)HttpServlet对象调用HttpRequest对象的相关方法,获取http请求信息
7)HttpServlet对象调用HttpResponse对象的相关方法,返回响应数据
8)Servlet容器把HttpServlet的响应结果传给Web Client
---- Servlet的对象
HttpServletRequest 请求对象:获取请求信息
HttpServletResponse 响应对象: 设置响应对象
ServletConfig对象: servlet配置对象
ServletContext对象: servlet的上下文对象
--- ServletConfig对象
1) 作用
主要是用于加载servlet的初始化参数。在一个web应用可以存在多个ServletConfig对象(一个Servlet对应一个ServletConfig对象)
2)创建
在创建完servlet对象之后,在调用init方法之前创建,直接从有参数的init方法中得到
3)举个栗子
servlet文件
//ServletConfig用于封装servlet的配置 public class ServletDemo1 extends HttpServlet { private ServletConfig config; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //返回此上下文中支持servlet API级别的最大和最小版本号 int m = this.getServletContext().getMinorVersion(); int n = this.getServletContext().getMajorVersion(); System.out.println("m="+m+",n="+n); System.out.println(this.getServletConfig().getServletName()); System.out.println(this.getServletConfig().getInitParameter("data")); //得到所有的值 Enumeration<String> initParameterNames = getServletConfig().getInitParameterNames(); while(initParameterNames.hasMoreElements()){ String name = initParameterNames.nextElement(); System.out.println(name); System.out.println(this.getServletConfig().getInitParameter(name)); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }
web.xml文件
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>ServletTest</display-name> <servlet> <servlet-name>ServletDemo1</servlet-name> <servlet-class>com.xxg.web.ServletDemo1</servlet-class> <init-param> <param-name>data</param-name> <param-value>xxxx</param-value> </init-param> <init-param> <param-name>data2</param-name> <param-value>yyyy</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/ServletDemo1</url-pattern> </servlet-mapping> </web-app>
运行结果:
说明:其实可以不用先获得ServletConfig,然后在获取其各种参数,可以直接使用其方法,比如上面我们用的ServletConfig().getServletName();可以直接写成getServletName();而不用在先获取ServletConfig();了,原因就是在GenericServlet中,已经帮我们获取了这些数据,我们只需要直接拿就行。
--- ServletContext对象
1)作用
ServletContext对象 ,叫做Servlet的上下文对象。tomcat为每个web项目都创建一个ServletContext实例,tomcat在启动时创建,服务器关闭时销毁,在一个web项目中共享数据,管理web项目资源,为整个web配置公共信息等,通俗点讲,就是一个web项目,就存在一个ServletContext实例,每个Servlet读可以访问到它。一个web应用中只有一 个ServletContext对象。
2)创建
加载web应用时创建ServletContext对象,加载web应用时创建ServletContext对象
3)举个栗子
在上边的ServletDemo1文件中
//在公共区域存放了一个key值为key1,value值为xiaomi的值 getServletContext().setAttribute("key1", "xiaomi"); String value = (String) this.getServletContext().getAttribute("key1"); System.out.println("key1: "+value);
在里面的一个ServletDemo2文件中
/* ServletContext域:1,是一个容器 2。作用范围是应用程序范围 */ /*@WebServlet("/ServletDemo2")*/ public class ServletDemo2 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //在ServletDemo2通过ServletContext获取在ServletDemo1中设置的值 String value = (String) this.getServletContext().getAttribute("key1"); System.out.println("key1: "+value); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
运行结果:(注意需要先运行ServletDemo1,让ServletContext区域存储值,再运行ServletDemo2)
--- 其他两个对象补充
1)请求转发
request.getRequestDispatcher(String path).forward(request,response); //path:转发后跳转的页面,这里不管用不用"/"开头,都是以web项目根开始,因为这是请求转发,请求转发只局限与在同一个web项目下使用,所以这里一直都是从web项目根下开始的,
web项目根:
开发:G:\Workspaces\test01\WebRoot\..
运行时:D:\java\tomcat\apache-tomcat-7.0.53\webapps\test01\..
web站点根:
运行时:D:\java\tomcat\apache-tomcat-7.0.53\webapps\..
从这里可以看出,web项目根就是从该web项目名开始,所以我们请求转发时,只需要接着项目名后面需要访问的路径写就行了,
特点:浏览器中url不会改变,也就是浏览器不知道服务器做了什么,是服务器帮我们跳转页面的,并且在转发后的页面,能够继续使用原先的request,因为是原先的request,所以request域中的属性都可以继续获取到。
2)重定向
方式一:手动方案
response.setStatus(302); //状态码302就代表重定向
response.setHeader("location","http://www.baidu.com");
方式二:使用封装好的,通过response.sendRedirect("http://www.baidu.com");
特点:服务器告诉浏览器要跳转的页面,是浏览器主动去跳转的页面,浏览器知道,也浏览器的地址栏中url会变,是浏览器重新发起一个请求到另外一个页面,所以request是重新发起的,跟请求转发不一样。
注意:response.sendRedirect(path); //
第一种:response.sendRedirect("/test01/MyServlet01"); //使用了"/"开头,说明是从web站点根开始,所以需要写test01/MyServlet01
第二种:response.sendRedirect("MyServlet01"); //没有使用"/"开头,说明是从web项目根开始,那么就无需写test01了。
重定向没有任何局限,可以重定向web项目内的任何路径,也可以访问别的web项目中的路径,并且这里就用"/"区分开来,如果使用了"/"开头,就说明我要重新开始定位了,不访问刚才的web项目,自己写项目名,如果没有使用"/"开始,那么就知道是访问刚才那个web项目下的servlet,就可以省略项目名了。就是这样来区别。
----Servlet与多线程
?-Servlet容器默认是采用单实例多线程的方式处理多个请求的
1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);
2.容器初始化化Servlet主要就是读取配置文件(例如tomcat,可以通过servlet.xml的<Connector>设置线程池中线程数目,初始化线程池通过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是默认采用单实例,多线程的方式进行。只要webapp被发布到web容器中的时候,servlet只会在发布的时候实例化一次,servlet在其生命周期中只有在将项目给移除或服务器stop的时候才会销毁,那么一个web项目从发布到运行只存在一个servlet的实例。
当容器收到一个Servlet请求,调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程,然后由该线程来执行Servlet的service方法。当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候,那么这个Servlet的service()方法将在多线程中并发执行。
在Tomcat7.0.3中的server.xml里面的代码:
<!--The connectors can use a shared executor, you can define one or more named thread pools--> <!-- <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/> --> <!-- <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> -->
?-Servlet如何实现线程安全
1、实现 SingleThreadModel 接口
该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。(但是Servlet2.4中已不再提倡使用)
2、同步对共享数据的操作
使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,但是在JMM内存模型中可以知道,使用synchronized为了保证主内存和工作内存一致性,会来回切换内核线程与用户线程,会造成一定的资源浪费,使得系统性能下降,所以在实际的开发中也应避免或最小化 Servlet 中的同步代码
3、避免使用实例变量
局部变量是在堆栈中运行。每个运行的线程都有自己的堆栈。
别的线程无法访问得到,因此我们说,局部变量是“安全”的。
全局变量在堆中,堆是对所有的线程都可见的。
因此在两个以上的线程访问全局变量时,就会出现所谓的“不安全”,a线程访问全局变量,赋值为a,然后中间睡眠了0.001秒,在此期间b进程访问了全局变量,赋值为b了,此时a线程醒来了,抢了处理机制,发现全局变量是b,显然不是我们a线程所要到的值,这时就要加入同步机制或者定义为局部变量,比如如果是方法的话就加同步方法,代码块就加同步代码块。
举个栗子
public class Test1{ ... public void fun1(){ String s = ""; System.out.print(s); } } public class Test2{ ... String s ; public void fun1(){ System.out.print(s); } }
说明:第一种Test1 如果Test1只有一个实例化对象,那么不同的用户访问他的话,每一个用户执行的fun1方法都是由自身的线程单独开辟的空间的。
第二种Test2 如果Test2只有一个实例化对象,那么不同的用户访问他的话,那每一个用户访问的s都是同一个变量,那么线程安全性就很难保证。所以建议第一种方法。
参考资料:Servlet其实是多线程