JavaWeb_Servlet开发

来源:互联网 发布:拉里·伯德数据 编辑:程序博客网 时间:2024/05/17 23:40

Servlet简介

Servlet是sun公司提供的一门用于开发动态web资源的技术。
  • 还有一种动态web资源开发技术是Jsp,jsp说白了就是Servlet
Sun公司在其API中提供了一个servlet接口,用户若想开发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
  1. 编写一个Java类,实现servlet接口。
  2. 把开发好的Java类部署到web服务器中。
快速入门,用servlet向浏览器输出“hello servlet”。
  • 阅读Servlet API,解决两个问题:
    • J2EE有13门技术,Servlet是其中一个。
    • Life cycle methods:生命周期相关的方法,就是在特定时刻执行的方法。包括:init,service,destroy
  • 输出hello servlet的java代码应该写在servlet的哪个方法内?
    • service方法中
  • 如何向IE浏览器输出数据?
    • Service方法由服务器调用,会传入请求和响应对象,通过响应对象拿到与浏览器相关的输出流对象
    • 手写servlet步骤:
      1. 在tomat中建立一个web应用,然后在web应用中新建一个\WEB-INF\classes目录,
      2. 在classes目录中新建一个FirstServlet的java类,注意需要抛出ServletException不能简写为只抛出Exception
      3. 编译servlet,想成功需要做到:1.将servlet相关API所需的jar包加入classpath中(可在tomcat中找,因为它可以运行servlet:lib中的servlet-api.jar),在dos中:set classpath=%classpath%;“该jar包的路径名”2.在DOS中编译的时候先进入servlet类所在的目录,然后带包名编译(这样会生成包文件夹):javac –d . FirstServlet.java,点代表把编译后的结果保存在当前目录中
      4. 在WEB-INFI目录中新建一个web.xml,为该servlet配置一个对外访问路径。可以照着tomcat的同名文件抄头抄屁股
      5. 启动服务器访问
    • 手动给服务器做开发,类要有包名,并且是public修饰,因为是给服务器调用的。

Servlet在web应用中的位置


提示:按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet。

Servlet的运行过程


UML图描述调用过程
  • 图中没有destroy方法,因为在servlet的生命周期中,用户访问完servlet之后该对象不会被摧毁,所以destroy方法不会被执行
  • 面试时问对象的生命周期就答:何时生,何时死,中间有什么方法会被执行:
    • 生:servlet对象是用户第一次访问的时候被创建,之后会驻留在服务器里面响应后续的请求,
    • 生命周期中方法的执行:一旦创建,init方法会被执行,对于客户端的每次请求,service方法会被执行
    • 死:Servlet被摧毁的时候destroy方法会被执行。web服务器停止(关掉web服务器或者删除web应用)的时候会被摧毁
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
  1. Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
  2. 装载并创建该Servlet的一个实例对象。(使用了反射) 
  3. 调用Servlet实例对象的init()方法。
  4. 创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
  5. WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。 

在Eclipse中开发Servlet

在eclipse中新建一个web project工程,eclipse会自动创建下图所示目录结构,也会自动导入将J2SE和J2EE相关API,不用自己添加jar包。

  • 一般选择J2EE1.4,纯洁些,避免第三方jar包冲突,注意:此时为了使用java的泛型等技术,编译器的版本还是要高于1.4
  • 可以导入tomcat源码,使参数名易看
点击配置文件(比如web.xml),选择source,进入编辑页面
获取该java文件完整路径名:右键单击一个java文件,选择copy qulified name,然后在配置文件中(比如web.xml)粘贴,就可以获取该java文件完整路径名。(注意:servlet-class标签中一定是包名.类名,类名不加后缀的.java,注意格式!)
在eclipse中集成一台tomcat服务器:
  • windows->preferences->myeclipse enterprise workbench->servers->tomcat->选择需要版本->tomcat home directory选择tomcat所在目录,点确定注意一定要选择左上角的enable
  • 因为tomcat是一个java程序,所以还需要指定tomcat运行的java虚拟机jdk,在选择的tomcat版本下点JDK,一般不用改,使用默认即可,Launch中选择debug模式,方便调试程序
部署web应用至服务器:
  • 点击deploy按钮进入管理部署界面,选择工程名,然后增加对应服务器,如果原服务器中有重名的工程,就会提示备份、删除或覆盖。
  • 当修改了源程序后,可以点deploy按钮选择服务器进行重新部署redeploy,这样就不用重启服务器了
启动Tomcat:
  • 选择run servers按钮的下拉列表,点run。
  • 如果在console中出现UnsupportedClassVersionError:是编译版本高,运行的虚拟机版本低,可以将运行的版本配置高

Servlet接口实现类

Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。
HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
  • 客户机请求方式有很多种,但是常用的就get和post两种
  • 默认是get请求,所以为了省事,通常在doPost方法中调用doGet方法。
阅读HttpServlet API文档
  • 快捷键Alt+<-:返回上一查看位置
新建Servlet模板操作:
  • Eclipse可以直接新建Servlet模板,并在web.xml文件中自动完成部署。
  • 除了继承抽象方法以外,只需勾选doGet和doPost两种方法即可。点击next,配置web.xml映射(display name和description是注释,可以去掉)。最后点击完成
  • 技巧:如果一个类的类名写错了,需要修改,必须在重构中重命名,这样被别人调用的地方也会随之一起更改。但是还要注意修改web.xml文件!
  • 修改Servlet模板:进入MyEclipse根目录,搜索Servlet.java文件,修改里面的doGet和doPost方法,可能找不到该文件,需要解压缩jar包进行更改。

Servlet的一些细节

技巧:当复制一个工程,然后更改了工程名,此时需要使发布的工程名也要一致,就需要进行配置:点一下工程的属性->MyEclipse->Web(有的版本web在project facets下)->在Web Context-root中更改。

Servlet的映射

由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名(有包名,无.java)。 
一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。例如:
<web-app><servlet><servlet-name>AnyName</servlet-name><servlet-class>HelloServlet</servlet-class></servlet><servlet-mapping><servlet-name>AnyName</servlet-name><url-pattern>/demo/hello.html</url-pattern></servlet-mapping></web-app>
同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个Servlet的注册名。
  • 伪静态:可以将一个动态web资源映射成为一个静态web资源名称 
在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是“*.扩展名”,另一种格式是以正斜杠(/)开头并以“/*”结尾。
<servlet-mapping><servlet-name>AnyName</servlet-name><url-pattern>*.do</url-pattern></servlet-mapping>
<servlet-mapping><servlet-name>AnyName</servlet-name><url-pattern>/action/*</url-pattern></servlet-mapping>
改动了web.xml文件之后不需要重新发布,因为服务器会自动监测到改动并加载,这是由于在conf目录下的context.xml文件中的配置了web.xml文件,所以就会被所有的web应用所加载

地址冲突

对于如下的一些映射关系:
  • Servlet1 映射到 /abc/* 
  • Servlet2 映射到 /* 
  • Servlet3 映射到 /abc 
  • Servlet4 映射到 *.do 
问题:
  • 当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,哪个servlet响应
    • Servlet引擎将调用Servlet1。因为长得像
  • 当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,哪个servlet响应
    • Servlet引擎将调用Servlet3。因为长得像
  • 当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,哪个servlet响应
    • Servlet引擎将调用Servlet1。因为*为首的优先级最低
  • 当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
    • Servlet引擎将调用Servlet2。因为*为首的优先级最低
  • 当请求URL为“/xxx/yyy/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
    • Servlet引擎将调用Servlet2。因为*为首的优先级最低

Servlet的运行原理解释

Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
  • Web服务器里用来调用servlet的程序称之为servlet引擎
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
  • 证明只有一个servlet对象:分别覆盖init和destroy方法,加入输出语句,并两次访问该servlet并观察init执行。再停掉服务器(注意不是停掉虚拟机)观察destroy的执行
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象(请求结束,这两个对象就摧毁了,生命周期短,所以只要不是并发访问,服务器就不会崩溃),然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。 

load-on-startup元素

如果在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
  • 举例:
            <servlet><servlet-name>invoker</servlet-name><servlet-class>org.apache.catalina.servlets.InvokerServlet</servlet-class><load-on-startup>2</load-on-startup>//数字越小,启动优先级越高</servlet>
用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
应用:Struts框架
  • Struts框架的核心类ActionServlet就是一个大的servlet,它就被加入了<load-on-startup>元素
  • 还有可以提前创建该Servlet对象,这样用户访问时更快响应

缺省Servlet

如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。 
凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求。 
当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问服务器默认的缺省Servlet,并由它读取静态资源并回送给客户端。当在web应用的web.xml文件中手动覆盖了这个缺省servlet之后,该web应用的静态的资源就访问不了了,就只能访问指定的servlet了。所以开发时不要把缺省的servlet覆盖!
配置服务器默认的缺省servlet:在<tomcat的安装目录>\conf\web.xml文件中(该文件的配置会被服务器中所有的web应用共享),注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。

线程安全

当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。
  • 同一个资源:如果在doGet方法中,int i = 0;i++ 每个线程都有各自的i,因为此时i是局部变量,所以这是没有线程安全问题的。如果对变量的声明在Servlet的成员位置,就会有安全问题,因为servlet对象只有一个。或者是声明为static的变量
  • 提示:如果线程向Person对象的的静态集合中加入了数据,数据使用完以后,在Person对象摧毁之前,一般要移除静态集合中的数据,否则集合中的数据越来越多,就会导致内存溢出,所以开发中对于静态的集合和容器要特别小心
  • 实际开发不能使用同步代码块synchronized(this)方式解决,因为此时的资源只能被单线程访问,其他线程需要等待,就会导致用户长时间等待
如果某个Servlet实现了SingleThreadModel接口(Servlet API提供),那么Servlet引擎将以单线程模式来调用其service方法。
SingleThreadModel接口中没有定义任何方法,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。
  • 它是一个标记接口,同样,Serializable接口也是,所标记的类的实例对象可以被序列化  
对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。 
注意:对于Servlet中出现异常的代码,不能抛出该异常,因为是子类覆写的父类HttpServlet的方法,比如doGet。子类在覆写父类的方法时,不能抛出比父类更多的异常,因为子类要比父类好。所以只能捕获异常。而且对于网页来说,异常需要被处理,给用户提示,而不是抛给用户。 

服务器传递给Servlet的对象

服务器在调用Servlet对象时会传递给Servlet很多对象,见下图:
每一套技术就是学习它的API,比如JDBC,Android,Hibernate等

ServletConfig对象

有些数据在开发时不适合在程序里写死,就可以通过配置配进去。在Servlet的配置文件(web.xml)中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。
<servlet><servlet-name>ServletDemo1</servlet-name><servlet-class>cn.itcast.ServletDemo1</servlet-class><init-param><param-name>charset</param-name><param-value>UTF-8</param-value></init-param></servlet>
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象作为init方法的参数传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
阅读ServletConfig API,并举例说明该对象的作用:
  • 获取ServletConfig对象:
    • 第一种方式:首先声明一个ServletConfig类型成员变量:private ServletConfig config;  再在传入了Servlet对象的init方法中this.config = config; 这样就可以在doGet方法中使用该对象了:String value = config.getInitParameter(“charset”);
    • 实际开发中在doGet方法中:this.getServletConfig(); 就可以获取该对象了。因为父类HttpServlet的父类GenericServlet已经写好了,第一种方式就是原理
  • 得到所有的参数值:
    Enumeration e = this.getServletConfig().getInitParameterNames(); while(e.hasMoreElements()){ String name = (String)e.nextElement(); String value = this.getServletConfig().getInitParameter(name);}
实际开发中那些数据不适合在程序中写死,而应该以配置信息的方式写入呢?
  • Servlet使用哪种码表输出?获得字符集编码。
    <init-param><param-name>charset</param-name><param-value>UTF-8</param-value></init-param>
  • Servlet链接那个数据库?获得数据库连接信息
    <init-param><param-name>url</param-name><param-value>jdbc:mysql://localhost:3306/test</param-value></init-param><init-param><param-name>username</param-name><param-value>root</param-value></init-param><init-param><param-name>password</param-name><param-value>123456</param-value></init-param>
  • Servlet读取那个配置文件?获得配置文件,查看struts案例的web.xml文件
    <init-param><param-name>config</param-name><param-value>/WEB-INF/struts-config.xml</param-value></init-param>

ServletContext对象

WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用,它提供了管理应用中所有资源的方法。
ServletContext对象的生命周期
  • 服务器启动针对每一个web应用创建对应的ServletContext对象,停掉服务器或者删除某个web应用会销毁该ServletContext对象
获得ServletContext对象
  • 方式一:ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
  • 方式二:在doGet方法中直接this.getServletContext();当前的Servlet直接从父类中继承了该方法
由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。
查看ServletContext API文档,了解ServletContext对象的功能。
  • setAttribute和removeAttribute:在ServletContext对象中存入删除信息
  • getContext:获得其他的web应用的ServletContext对象
  • getInitParameter:得到整个web应用的初始化配置信息
  • getMajorVersion和getMinorVersion:得到servlet版本号
  • getMimeType:返回指定文件的mime类型:jpg的mime类型text/jpeg
  • getNamedDispatcher:得到一个转发对象,并在参数中指定了转发地址
  • getRealPath:得到某一个资源的实际路径
  • getResource:将资源当做URL对象返回。getResourceAsStream:把资源作为流返回
  • getResourcePaths:得到指定地址的所有的资源,返回一个set集合
  • getServletContextName:返回web应用的名称

ServletContext方法的功能

多个Servlet通过ServletContext对象实现数据共享。
  • web开发中有四个域:context域、request域、session域、page域
  • 域就是范围,context域就是应用程序范围,即这个对象作用的范围是整个应用程序范围。ServletContext域是一个容器,同时说明了该容器的作用范围,也就是应用程序范围
  • 应用:聊天室
获取WEB应用的初始化参数。
  • 使用<context-param>标签为整个web应用配置初始化参数(init-param是为单个的servlet配置)。Web服务器创建servletContext对象时自动将web应用的初始化参数封装到该对象中。
  • 使用ServletContext的getInitParameter和getInitParameterNames方法获取单个和所有的参数值
  • 应用:数据库参数配置应用于web应用中所有的servlet
实现Servlet的转发。
  • 转发:你找我,我帮你去找他(客户机只有一次请求);重定向:你找我,我要你自己去找他(客户机有两次请求)
  • 实际开发中servlet不做输出,因为需要页面美化。通常servlet接受请求,产生数据,然后把请求转发给jsp,由jsp专门负责输出数据。
  • 在Servlet中,首先要将数据带给1.jsp(实际开发中不采用ServletContext对象,而是采用request对象):this.getServletContext().setAtrribute(“data”,data); 然后获取转发对象:RequestDispatcher rd = This.getServletContext().getRequestDispatcher(“/1.jsp”),最后调用转发对象的forward方法将请求转发过去:rd.forward(request, response);
  • 在jsp页面中,与HTML唯一不同就是可以嵌入java代码,通过<% 代码 %>实现。Jsp中application就是servlet中的ServletContext对象。所以代码为:<% String data = (String) application.getAttribute(“data”); out.write(data); %>
  • 为什么不把数据存在Context域中?因为该数据会被共享,出现多线程问题。比如在转发前另一个访问线程覆盖了需要被转发的数据。实际开发中通过request域将数据带给jsp文件。
利用ServletContext对象读取资源文件(管理web资源)。
  • 注意:软件开发中用作资源配置文件的文件类型通常有两种:一种是properties,一种是xml。如果配置文件中数据之间没有关系,就用前者,反之用后者。比如,数据库的连接信息是没有关系的,所以创建db.properties文件
    url=jdbc:mysql://localhost:3306/testusername=rootpassword=root
  • 得到文件路径:getRealPath得到某一个资源的实际路径
  • 读取资源文件的三种方式
    • 方式一:通过servletContext的getResourceAsStream获取资源文件流对象():
      InputStream in = this.getServletContext().getResourceAsStream(“/WEB-INF/classes/db.properties”); //获取资源文件的流对象,斜杠就代表web应用,此时eclipse中properties文件创建在src目录下。注意路径不是src而是classes。//以下是读取properties文件模板代码,要记住!Properties props = new Properties();//只要是properties文件,都可以通过java的Properties对象读取。内部使用map对象props.load(in);//load方法自动从流中加载数据String url = props.getProperty(“url”);
      • 如果properties文件放在包中,则文件路径应写为:/WEB-INF/classes/cn/itcast/db.properties
      • 如果资源文件放在WebRoot下,则写为:/db.properties
      • 注意:web应用开发完成会发布到服务器,服务器里没有src目录!src变成了classes。
      • 读取资源文件需要注意的问题:如果采用FileInputStream  f = new FileInputStream(“classes/db.properties”)采用的是相对路径,相对的是Java虚拟机的启动目录,对于web开发即tomcat的bin目录。所以web应用的资源文件切忌不要采用传统的方式读取
    • 方式二:通过ServletContext的getRealPath方法获取资源文件的绝对路径,再通过传统流读取资源文件
      String path = this.getServletContext().getRealPath(“/WEB-INF/classes/db.properties”)FileInputStream  f = new FileInputStream(path)//传统方式
      • 这种方式主要是可以得到资源的名称 :String fileName = path.subString(path.lastIndexOf(“\\”)+1);
      • 应用:下载
    • 方式三:Web应用中普通Java程序(非Servlet)读取配置文件,只能通过类加载器读取
      • Servlet收到请求后需要调用专门操作数据库的DAO类(DAO类一般在单独的dao包中创建),DAO类需要读取配置文件中的信息。此时DAO类中拿不到ServletContext对象了。
      • 如果把ServletContext对象以参数的形式传入DAO类的方法,然后在DAO中获取ServletContext对象的方式不可取,因为web层就侵入了数据访问层,层和层之间就耦合在一起了,不符合软件设计思想。
      • 需要通过类加载器读取。因为类加载器可以加载类,也可以加载资源文件。而一般资源文件只需要读取一次,通常使用静态代码块读取,而不是在每个方法中分别读取。
        public class UserDao {private static Properties dbconfig = new Properties();static{try {//得到加载该类的类加载器对象,再获取资源文件流对象。此时db.properties文件就在src下,采用相对路径InputStream in = UserDao.class.getClassLoader().getResourceAsStream("db.properties");//String path = UserDao.class.getClassLoader().getResource("db.properties").getPath();dbconfig.load(in);} catch (Exception e) {//如果数据库无法连接,是一个致命的问题,通常跑一个错误出去throw new ExceptionInInitializerError(e);}}public void update(){dbconfig.getProperty("url");}}
      • 注意:
        类加载器读取资源文件该文件不能太大,因为类加载器读取文件和装载类都会装载进内存,如果文件太大,就会内存溢出。
        由于类加载器只加载类一次,所以也只加载资源文件一次,所以是无法读到资源文件的更改的。
        如果一定要读到更新后的数据,就需要:String path = UserDao.class.getClassLoader().getResource("db.properties").getPath();
        getResource方法把资源当做URL对象返回,然后调用getPath方法得到资源的路径,再通过传统的流方式读取。关键是通过类加载器得到文件的位置!

在客户端缓存Servlet的输出

对于不经常变化的数据,在servlet中可以为其设置合理的缓存时间值,以避免浏览器频繁向服务器发送请求,提升服务器的性能。
 
0 0