##什么是servlet

Servlet是sun公司提供的一种用于开发动态web资源的技术

  • 其实就是一个java程序,运行在服务器上,用来接收和响应客户端的HTTP请求
  • 更多的是配合动态资源来使用,当然静态资源也用到servlet,只不过是Tomcat里面已经定义好了,一个DefaultServlet(优先级为1)

##tmocat和servlet的关系

  • tomcat是web应用服务器,是一个servlet/jsp的容器

  • servlet是一种运行在支持java语言例如tomcat服务器上的组件

##servlet的创建和servlet的web.xml配置

  • 创建一个简单的servlet
    • 实现servlet接口,或者继承httpservlet
  • servlet的web.xml配置
    • 在webContent/WEB-INF/web.xml中写入下面的内容
    <!--告诉tomca,应用里有这个servlet,名字叫做xxxx
    具体的路径是xxxx-->
     <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.Servlet.Servletdemo</servlet-class>
    </servlet>
    <!-- 注册servlet的映射 url-pattern地址栏上地址 -->
    <servlet-mapping>
    	<servlet-name>hello</servlet-name>
    	<url-pattern>/hello</url-pattern>
    </servlet-mapping>
    
      + 第一个servlet-name的名字可以随便写,不过一般与类名相同
      + 第二个servlet-name与第一个的相同
      + servlet-class包名.类名.class	 
      + url-pattern 以"/"开头
    

###url-paren的配置(通配符)

  • 全路径匹配
    • 以/开头 例如
      • /a
      • /a/b
  • 路径匹配 前半段或者后半段
      • 通配符,匹配任意的文字
        • /a/*
        • *.do

###缺省servlet

  • url-pattern仅为一个/
  • 凡是在web.xml中没有匹配的servlet-mapping元素的url,都通过servlet来处理
  • 在tomcat的安装目录\config\web.xml文件中,注册了一个org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。我们在访问静态资源就是在使用这个缺省的servlet
<servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

 <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

##servlet的访问过程运行过程

servlet程序是由web服务器调用,web服务器接收到客户端的servlet请求后

  • web服务器检查是否已经装载并创建了该servlet对象,如果是就执行第4步,否则执行第二部
  • 装载并创建一个该servlet对象
  • 调用servlet实例对象的init()方法
  • 创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
  • web应用被重启或者重新启动之前,将卸载servlet,在卸载之前调用servlet的destory方法

servlet的访问过程

  • 根据要早的servlet的url 在web.xml中找到相匹配的url-pattern
  • 根据找到的路径在对应的servlet-mapping中找到servlet-name
  • 根据servlet-name去找到servlet中的servlet-name
  • 最后找到了对应的servlet.class文件

##servlet接口及其实现类

  • Servlet接口定义了两个默认实现类,分别是GenericServlet,HttpServlet
  • HttpServlet在实现Servlet接口时,覆盖了service方法,该方法体内的代码会自动判断用户的请求方式,因此只需要重写doGet方法或者doPost方法
  • HttpServlet指能够处理HTTP请求的servlet,在原有的servlet接口上添加了一些HTTP协议的处理方法,比Servlet接口更加强大,因此在编写Servlet时,通常继承这个类,而避免直接去实现Servlet接口

###为什么要有两个接口

  • GenericServlet虽然实现了Servlet接口,但是没有指定所实用的协议,而HttpServlet指定了所使用的HTTP协议,SUN公司处于维护的目的,一旦出现了别的协议,就可以直接添加新的类似HttpServlet的类继承GenericServlet

servlet(接口) | | GenericServlet(重写了5个接口) | | httpservlet(用于处理http的请求)

##servlet的生命周期 ###生命周期

  • 从创建到销毁的时间

###生命周期方法

  • 从创建到销毁所调用的方法,都叫生命周期方法

  • inti()方法

    • 在创建servlet的实例时会调用init方法
    • 声明时候创建实例
      • 默认情况下,初次方法是时候会创建实例
      • 一个servlet只会初始化一次,也就是说init只会执行一次,servlet是一个单实例,多线程的类,只会创建一次
  • service 方法

    • 只要客户端来了一个请求,就会调用这个方法
    • 该方法可以被执行多次,来一次请求就调用service方法
  • destory方法

    • 销毁的时候就执行该方法
    • 什么时候销毁
      • 该项目从tomcat中移除
      • 正常的关闭服务器

doGet和doPost不算生命周期方法,生命周期方法只指,从对象创建到销毁一定会执行的方法,但这两个方法不一定会执行

###创建的时机,前提

  1. 默认情况下,只有在初次访问servlet的时候,才会执行init方法,有的时候,我们需要在这个方法里面执行一些初始化工作,甚至做一些比较耗时的逻辑
  2. 那么这个时候,初次访问,就可能在init方法中逗留太久时间,那么有没有方法让初始化的时间提前一点
  3. 在配置的时候,使用load-on-starup来指定,给定的数字越小,启动的时间就越早,一般不写负数,从2开始,因为1已经被tomcat里的东西所占用
  		<servlet>
  			<servlet-name>demo</servlet-name>
  			<servlet-class>com.Servlet.Demo</servlet-class>
  			<load-on-startup>2</load-on-startup>
  		</servlet>
  • load-on-startup
    • 标记容器是否在启动的时候就加载这个servlet
    • 当值为非负数的时候就在启动时就加载servlet
    • 当时一个负数或者没有指定时,该servlet在选择时加载
    • 正数的值越小,启动servlet的优先级越高
    • 0比1的优先级高
    • 我们一般从2开始,1是tomcat的servlet的优先级

##servlet的线程安全问题

当多个tomcat并发的访问同一个servlet时,web服务器为每一个客户端的访问请求创建一个线程,并在这个线程上调用servlet的service方法,如果service方法访问了同一个资源的话就看你引发多线程安全问题

  • (假)解决方法,加锁保证任何时候都只有一个线程在方法servlet中的资源
    • 这样虽然解决了线程安全问题,但是很不现实,因为这样就变成了轮流访问了
  • (过时)sun公司提供的解决方案,让Servlet去实现一个SingleThreadModel接口,如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
    • 对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
    • 实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。
    • 创建多个对象会占用服务器资源

##servletConfig ###配置servletconfig

  • 在web.xml中配置
<servlet>
    <servlet-name>ServletConfigDemo1</servlet-name>
    <servlet-class>study.ServletConfigDemo1</servlet-class>
    <!--配置ServletConfigDemo1的初始化参数 -->
    <init-param>
        <param-name>name</param-name>
        <param-value>gacl</param-value>
    </init-param>
     <init-param>
        <param-name>password</param-name>
        <param-value>123</param-value>
    </init-param>
    <init-param>
        <param-name>charset</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</servlet>

###获取servletconfig

  • 通过初始化的方法获得servletconfig对象
public void init(ServletConfig config) throws ServletException {
	this.config = config;
}
  • 通过父类GenericServlet的方法获得一个ServletConfig对象
ServletConfig config = getServletConfig()

###获取配置的初始化参数

servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将初始化参数封装到servletConfig对象中,在调用init方法时,会将servletConfig对象传递给servlet

  • 例子
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //获取在web.xml中配置的初始化参数
        String paramVal = this.config.getInitParameter("name");//获取指定的初始化参数
        response.getWriter().print(paramVal);
        
        response.getWriter().print("<hr/>");
        //获取所有的初始化参数
        Enumeration<String> e = config.getInitParameterNames();
        while(e.hasMoreElements()){
            String name = e.nextElement();
            String value = config.getInitParameter(name);
            response.getWriter().print(name + "=" + value + "<br/>");
        }
    }

###servletchongfig的方法

  • getServletName() 获取servlet的名称,就是在web.xml文件中配置的
  • getservletContext() 获取servletcontext对象
  • getInitParameter(String) 获取在Servlet中初始化的参数的值,只能获取该Servlet下面的值
  • getInitParameterNames() 获取该servlet中所有初始化参数的名字,根据名字就可以获得各个初始化参数的value值,返回的类型是枚举类型 值的注意的是我们可以直接调用getServletName()等方法来获取个种参数,因为在父类GenericServlet中,已经帮我们获取了这些数据,不在需要我们手动调用了

###servletconfig的作用

  1. 未来我们自己开发的一些应用,使用了一些技术,或者一些代码,我们不会,但有人写出来了,天的代码放置在了自己的servlet类中 刚好这个servlet里面需要一个数字或者变量值,但这个值不能是固定的,所以要求使用这个servlet的公司,在注册servlet的时候,必须在web.xml里面,声明init-params
  2. 存放一些数据库的连接啊,用户名密码啊,修改的时候就可以不修改源代码,只修改配置文件就够了

##servletcontext ###什么是servletcongtext

有叫上下文,域对象,简单的说一个web项目有一个servletcontext实例,每个servlet都可以访问他

  • web容器在启动时,会为每个web应用都创建对应的servlet实例,在服务器启动时创建,在关闭时销毁,在一个web项目中共享数据,管理web项目资源
  • 由于一个web应用中所有servlet共享了一个servletcontext对象,因此servlet对象之间可以通过servletcontext来实现通讯,因此有被称为域对象

###获取servletcontext

  • 直接用父类的方法getServletContext()
  • 通过servletconfig来获取servletcontextgetServletConfig().getServletContext();

###配置参数

  • 必须在web.xml的最上方
<contxet-param>
	<param-name>add</param-name>
	<param-value>123</param-value>
</context-param>
  • 是一组键值对 name是键,value是值
  • 当服务启动的时候,会读取web.xml文件,当读到<context-param>这个节点,就set到我们的servletcontext中,我们就可以通过servletcontext来获取全局配置信息

###作用 ####实现数据共享

  • 在当前应用,是servlet共享数据
    • setAttribute(String name,Object obj) 在servletcontext中存入内容
    • getAttribute(String name) 通过名称得到内容
    • removeAttribute(String name) 移除名称的内容

####获取配置的参数

  • 通过方法getInitParameter("add")获取
    String add = servletContext.getInitParameter("add");
    
  • 通过getInitParameters() 获取枚举类型

####实现请求转发

ServletContext context = this.getServletContext();//获取ServletContext对象
RequestDispatcher rd = context.getRequestDispatcher("要去的界面");//获取请求转发对象(RequestDispatcher)
rd.forward(request, response);//调用forward方法实现请求转发

####读取资源文件--获得路径

  • 获取web项目下指定资源的路径 直接调用getServltContext().getRealPath("");获得到 D:\apache-tomcat-9.0.12\wtpwebapps\项目名\
  • 想获得webcontent的下文件直接加文件名就好了
getServltContext().getRealPath("文件名")
  • 获得Java Resoureces --> src下的文件
getServltContext().getRealPath("/WEB-INF/classes/文件名")
  • 获得src--->db.config包中的文件
getServletContext().getRealPath("/WEB-INF/classes/db/config/文件名");

为什么在classes下呢

  • tomcat会把web工程下的java文件编译后的字节码文件.class放到classes下,会把其他的资源也存放到classes下

####读取资源文件--获得流

  • 将获得路径的方法替换为获得流的方法
  • getResourceAsStream 获取资源的流对象

####读取资源文件--class类加载器

  • 注意事项:不适合装载大文件,否则会导致jvm内存溢出
  • 类加载器得到的路径D:\apache-tomcat-8.5.33\wtpwebapps\ServletRegister\WEB-INF\classes
  • 得到src下的文件
        //获取到装载当前类的类装载器
        ClassLoader loader = ServletContextDemo1.class.getClassLoader();
        //用类装载器读取src目录下的文件
        InputStream in = loader.getResourceAsStream("文件名");
  • 得到包下的资源文件
        //获取到装载当前类的类装载器
        ClassLoader loader = ServletContextDemo1.class.getClassLoader();
        //用类装载器读取src目录下的servlet.study包中的文件
        InputStream in = loader.getResourceAsStream("servlet/study/db4.properties");
  • 得到webcontent下的资源文件,先向上返回两层到工程目录下,在进去想得到的文件
        //获取到装载当前类的类装载器
        ClassLoader loader = ServletContextDemo1.class.getClassLoader();
		//用类装载器读取webContent下的文件
		classLoader.getResourceAsStream("../../Config.properties");

###控制浏览器缓存

添加Expires头可以有效减少Http请求次数,只要浏览器中有缓存,并且相应的资源未过期,均可以使用缓存中的资源,无须再发送请求到服务器

public class ResponseDemo6 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         * 设置数据合理的缓存时间值,以避免浏览器频繁向服务器发送请求,提升服务器的性能
         * 这里是将数据的缓存时间设置为1小时
         */
        response.setDateHeader("expires", System.currentTimeMillis()+1000*3600);
        String data = "aaaaaaaaaa";
        response.getWriter().write(data);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}