深入理解Servlet

一、Web项目结构

|- WebRoot : web应用的根目录
        |- 静态资源(html+css+js+image+vedio)
        |- WEB-INF : 固定写法。
          |-classes: (可选)固定写法。存放class字节码文件
          |-lib: (可选)固定写法。存放jar包文件。
          |-jsp:java服务器页面
          |-web.xml (必选)核心web配置,后端代码入口
 注意:1)WEB-INF目录里面的资源不能通过浏览器直接访问。
    2)如果希望访问到WEB-INF里面的资源,就必须把资源配置到web.xml的文件中。

二、动态资源开发技术

  1. 静态资源和动态资源的区别:
      静态资源: 当用户多次访问这个资源,资源的源代码永远不会改变的资源。
      动态资源:当用户多次访问这个资源,资源的源代码可能会发送改变。
  2. 动态资源的开发技术:
    1. Servlet : 用java语言来编写动态资源的开发技术,许多框架如:SpringMVC、Structs等都是servlet改造的。

    2. Servlet特点:

      1. 普通的java类,继承HttpServlet类,覆盖doGet、doPost等方法。
      2. Servlet类只能交给tomcat服务器运行。
    3. Servlet 注解版本:
      Servlet3.0以上使用注解自动映射@WebServlet

    4. Servlet代码实现步骤:

      1. 继承HttpServlet这个类
      2. 重写请求方法
      3. 在web.xml中使用<servlet><servlet-mapping>、使用注解@WebServlet定义servlet
    5. Sevlet的生命周期:
        Servlet程序的生命周期由tomcat服务器控制的.
        Servlet重要的四个生命周期方法:
         1. 构造方法:创建servlet对象的时候调用,默认情况下,第一次访问servlet的时候创建servlet对象,只调用1次,证明servlet对象在tomcat是单实例的。
         2. init方法:创建完servlet对象的时候调用,只调用1次。
         3. service方法:每次发出请求时调用,调用n次。
         4. destroy方法:销毁servlet对象的时候调用。停止服务器或者重新部署web应用时销毁servlet对象,只调用1次。
      代码实现:

      package chauncy.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;
      
      /** 
       * 多个请求同时访问servlet 会被实例化多少次? 1次   证明:通过反射创建对象会访问构造函数,只要证明构造函数执行一次
       * init() 初始化,只会执行一次
       * service() 处理请求,doGet、doPost、doPut、doDelete等 
       * destory() 销毁方法,只会执行一次,在容器停止的时候把对象从堆空间删除
       * 总结:Servlet默认是单例的且在第一次请求被执行的时候才创建,而且永远在jvm中只有一个实例。
       * 如何保证在容器启动的时候创建Servlet而不是第一次请求再创建?在web.xml中使用<load-on-startup>配置servlet的自动加载,数值越大优先级越低,设置成1当容器启动的时候就会创建并且执行init方法  
       * @classDesc: 功能描述(servlet生命周期)  
       * @author: ChauncyWang 
       * @version: 1.0  
       */
      @WebServlet("/ServletLifecycle")
      public class ServletLifecycle extends HttpServlet {
      	public ServletLifecycle() {
      		System.out.println("ServletLifecycle 构造函数被执行。。。");
      	}
      
      	@Override
      	public void init() throws ServletException {
      		System.out.println("执行Servlet初始化init方法");
      	}
      
      	/**
      	 * doGet是被service执行的
      	 */
      	@Override
      	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      		System.out.println("doGet方法被执行");
      	}
      
      	@Override
      	public void destroy() {
      		System.out.println("执行Servlet销毁destroy方法");
      	}
      
      }
      
    6. Servlet的自动加载:
        默认情况下,第一次访问servlet的时候创建servlet对象。如果servlet的构造方法或init方法中执行了比较多的逻辑代码,那么导致用户第一次访问sevrlet的时候比较慢。
        改变servlet创建对象的时机: 提前到加载web应用的时候!
        在servlet的配置信息中,加上一个<load-on-startup>即可,整数值越大,创建优先级越低。

    7. Servlet的多线程并发问题:
      servlet对象在tomcat服务器是单实例多线程的。
      因为servlet是多线程的,所以当多个servlet的线程同时访问了servlet的共享数据,如成员变量,可能会引发线程安全问题。
      解决办法:

      1. 把使用到共享数据的代码块进行同步(使用synchronized关键字进行同步)
      2. 建议在servlet类中尽量不要使用成员变量。如果确实要使用成员,必须同步。而且尽量缩小同步代码块的范围。(哪里使用到了成员变量,就同步哪里!!),以避免因为同步而导致并发效率降低。

      代码实现:

      package chauncy.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;
      
      /**
       * @classDesc: 功能描述:(证明servlet线程安不安全)
       * servlet加载流程:
       * 1.tomcat加载web.xml配置
       * 2.当发生请求来源时,先匹配<url-pattern>标签,然后寻找<servlet>配置
       * 3.解析<servlet>配置<servlet-class>
       * 4.通过java反射机制,调用Class.forName class.newInstance();无参构造函数创建对象 
       * 5.先执行servlet无参构造函数
       * 6.然后执行init方法
       * 7.之后访问service方法,进行判断请求类型,确定访问执行doGet、doPost等
       * 8.最后当服务器停止,执行销毁destroy方法
       * @author: ChauncyWang
       * @verssion: v1.0
       */
      @WebServlet("/ServletThreadSecurity")
      public class ServletThreadSecurity extends HttpServlet {
      	private int i=1;
      	
      	public ServletThreadSecurity(){
      		System.out.println("ServletThreadSecurity无参构造函数被执行");
      	}
      
      	/**
      	 * 当多个请求触发时,如果servlet的构造函数只执行一次,说明servlet时单例的。
      	 * 
      	 */
      	@Override
      	public void init() throws ServletException {
      		System.out.println("This is init()");
      	}
      	
      	/**
      	 * 因为servlet线程不安全,所以应该尽量避免使用全局变量。
      	 * 面试中如果问到哪里遇到线程安全,举例servlet默认是单例的线程不安全。HashMap和HashTable区别,HashMap是非线程安全、非synchronized可以接受null的键值,HashTable与之相反。
      	 * jdk1.5提供了ConcurrentHashMap替代HashTable,ConcurrentHashMap具有更强的可扩展性。
      	 * 因为HashMap是线程不安全非synchronized而HashTable是线程安全synchronized的,所以单线程下HashMap比HashTable快。
      	 * HashMap可以通过语句:Map m = Collections.synchronizeMap(hashMap);进行同步
      	 * StringBuilder是线程不安全的,而StringBuffer是线程安全的。
      	 * 如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全。
      	 * 但是如果在单线程情况下不保证线程安全,StringBuilder比StringBuffer快。
      	 */
      	@Override
      	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      		System.out.println("This is doGet()");
      		resp.setCharacterEncoding("utf-8");//内容编码,防止出现中文乱码
      		resp.setContentType("text/html;charset=utf-8");//向浏览器输出内容
      		synchronized (ServletThreadSecurity.class) {
      			resp.getWriter().write("第"+i+"次");
      			try {
      				Thread.sleep(1000);
      			} catch (InterruptedException e) {
      			}
      			i++;
      		}
      	}
      }
      
  3. ServletContext对象:
    1. 得到web应用上下文路径:
      java.lang.String getContextPath(),用在请求重定向的资源名称中。
      代码实现:
      package chauncy.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;
      
      /**
       * @classDesc: 功能描述(servlet上下文的使用)
       * @author: ChauncyWang
       * @version: 1.0
       */
      @WebServlet("/ServletContext")
      public class ServletContext extends HttpServlet {
      	@Override
      	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      		javax.servlet.ServletContext servletContext = this.getServletContext();
      		// 获取当前项目上下文路径
      		String contextPath = servletContext.getContextPath();
      		// 跳转网页使用:转发或者重定向
      		resp.sendRedirect(contextPath + "/ServletLifecycle");// 重定向
      	}
      }
      
    2. 域对象有关的方法
      域对象:作用是用于保存数据,获取数据,可以在不同的动态资源之间共享数据。
      可以通过传递参数的形式,共享数据,但是局限:只能传递字符串类型,也可以使用域对象共享数据,好处:可以共享任何类型的数据,ServletContext就是一个域对象,ServletContext域对象:作用范围在整个web应用中有效
      1. 保存数据:void setAttribute(java.lang.String name, java.lang.Object object)
      2. 获取数据: java.lang.Object getAttribute(java.lang.String name)
      3. 删除数据: void removeAttribute(java.lang.String name)
        ServletContext域对象的使用,代码实现:
        package chauncy.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;
        
        /**
         * 验证servlet上下文作用域 <br/>
         * servlet上下文作用域保存在服务器端,多用户共享使用,类似于全局变量,不建议使用。
         * @classDesc: 功能描述(创建servlet上下文属性值)
         * @author: ChauncyWang
         * @version: 1.0
         */
        @WebServlet("/ServletContextActionScope")
        public class ServletContextActionScope extends HttpServlet {
        
        	@Override
        	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        		this.getServletContext().setAttribute("userName", "ChauncyWang");
        	}
        }
        
        package chauncy.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;
        
        /**  
         * 验证servlet上下文作用域 
         * @classDesc: 功能描述(调用servlet上下文属性值)  
         * @author: ChauncyWang
         * @version: 1.0  
         */  
        @WebServlet("/ServletContextActionScopeCall")
        public class ServletContextActionScopeCall extends HttpServlet{
        	
        	@Override
        	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        		String userName = (String) this.getServletContext().getAttribute("userName"); 
        		System.out.println("userName:"+userName);
        	}
        }
        
        所有域对象:
        1. HttpServletRequet 域对象
        2. ServletContext域对象
        3. HttpSession 域对象
        4. PageContext域对象
    3. 转发与重定向:
      重定向非常耗费资源,会发送两次请求,且使用request域会失效。一般来说,转发在本服务器内部使用,重定向在外部服务器使用(跳转到外部服务器)。
      通用获取数据的类:
      package chauncy.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;
      
      /**   
       * @classDesc: 功能描述(获取参数)  
       * @author: ChauncyWang  
       * @version: 1.0  
       */  
      @WebServlet("/GetDataServlet")
      public class GetDataServlet extends HttpServlet{
      	@Override
      	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      		String userName = (String) req.getAttribute("userName");
      		System.out.println("userName:"+userName);
      	}
      }
      
      1. 转发:
        1. 地址栏不会改变
        2. 转发只能转发到当前web应用内的资源
        3. 可以在转发过程中,把数据保存到request域对象中。
        4. 效率高,不需要等待服务器返回302状态后再request。
          代码实现:
          package chauncy.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;
          
          /**   
           * @classDesc: 功能描述(转发请求servlet)  
           * @author: ChauncyWang
           * @version: 1.0  
           */ 
          @WebServlet("/ForwardServlet")
          public class ForwardServlet extends HttpServlet{
          	@Override
          	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          		req.setAttribute("userName", "ChauncWang");
          		//跳转有两种方式,一种是转发一种是重定向,此处使用转发。
          		req.getRequestDispatcher("/GetDataServlet").forward(req, resp);
          	}
          }
          
      2. 重定向:
        1. 地址栏会改变,变成重定向到地址。
        2. 重定向可以跳转到当前web应用,或其他web应用,甚至是外部域名网站。
        3. 不能在重定向的过程,把数据保存到request中。
        4. 效率低,需要当客户端request给服务器,服务器返回302状态给客户端后,客户端收到状态为302,本地解析Location值,后再次request服务器的Location值地址。
          代码实现:
          package chauncy.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;
          
          /**   
           * @classDesc: 功能描述(重定向请求servlet)  
           * @author: ChauncyWang
           * @version: 1.0  
           */ 
          @WebServlet("/RedirectServlet")
          public class RedirectServlet extends HttpServlet{
          	@Override
          	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          		req.setAttribute("userName", "ChauncWang");
          		//跳转有两种方式,一种是转发一种是重定向,此处使用重定向。
          		resp.sendRedirect(this.getServletContext().getContextPath()+"/GetDataServlet");
          	}
          }
          
          手动实现Servlet的Redirect:
          package chauncy.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;
          
          /**   
           * @classDesc: 功能描述(手写实现Servlet的Redirect)  
           * @author: ChauncyWang
           * @version: 1.0  
           */ 
          @WebServlet("/RedirectImplement")
          public class RedirectImplement extends HttpServlet {
          	@Override
          	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          		resp.setStatus(302);
          		resp.setHeader("Location", this.getServletContext().getContextPath()+"/ServletLifecycle");
          	}
          }
          
    总结:如果要使用request域对象进行数据共享,只能用转发技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值