Servlet详解(一)

什么是Servlet

Servlet(Server Applet),全称Java Servlet。一个Java Servlet就是一个小型Java应用程序,它可以继承HttpServlet实现,运行在Web服务器中。Servlet 会接收并响应来自浏览器的请求,通常是基于Http协议的请求。

如下图:
这里写图片描述

创建Servlet

实现Servlet或者继承Servlet的实现类:

方法一:实现Servlet接口

1> 实现接口Servlet并实现以下几个生命周期方法:

  • init(ServletConfig config):当Servlet被创建时,将会使用此方法对自己初始化
  • service(ServletRequest req,ServletResponse res):当浏览器对服务器发出请求后,servlet会使用该方法处理请求
  • destory():当Servlet处理完请求后,在销毁前会调用此方法,然后GC会将它回收掉
生命周期:

当client访问Servlet时,创建并初始化Servlet,在服务器中只有一个Servlet实例,当服务器被关闭时,Servlet才会被销毁。

package com.feathers.servletdemo;
import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet implements Servlet {
	
	private ServletConfig config;

	@Override
	public void init(ServletConfig config) throws ServletException {
		System.out.println("init(): servlet 初始化");
		this.config = config;
	}

	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		System.out.println("service():servlet 收到请求了,开始取出请求的数据(request)并作出处理,\n 并将处理后的结果放置到响应对象(response)中");
		// 响应对象向客户端写东西
		response.getWriter().write("Hello Client, I'm Servlet");
	}

	@Override
	public void destroy() {
		System.out.println("destory(): servlet被销毁");
	}
	

	@Override // 获取配置信息,键值对的方式
	public ServletConfig getServletConfig() { 
		return config;
	}

	@Override  // 返回servlet信息,比如servlet的author version copyright等
	public String getServletInfo() {
		return null;
		// return "Feather著作 Feathers 版权所有";
	}
}

2> 在web.xml 中注册Servlet,告诉服务器有这么一个Servlet

<!-- 注册servlet到服务器 -->
  <servlet>
  	<servlet-name>HelloServlet</servlet-name><!-- 注册名称 -->
  	<servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class><!-- java字节码路径 -->
  </servlet>
  <!-- 给注册过的Servlet配置路径 -->
  <servlet-mapping>
  	<servlet-name>HelloServlet</servlet-name><!-- 给哪一个Servlet配置路径 -->
	<url-pattern>/HelloServlet</url-pattern><!-- 要配置的路径,servle访问路径  / 代表项目路径 -->
  </servlet-mapping>

什么?太麻烦了?还好我们有注解@WebServlet

使用java注解注册Servlet
@WebServlet("/FirstServlet") // 后面是Servlet的访问路径

@WebServlet(  
    name="Hello",   // Servlet名称
    urlPatterns={"/hello.view"}, // 访问路径   
    loadOnStartup=1 // 加载级别
)  
public class HelloServlet extends HttpServlet {  

启动服务器,访问此Servlet:

这里写图片描述

在tomcat中的项目结构:
这里写图片描述
我们可以看到,Java编译的class文件全部被eclipse放置到WEB-INF—> classes目录下了。

输出:
这里写图片描述

生命周期时序图:
这里写图片描述

服务器是如何查找Servlet的?
这里写图片描述

我们看到,通过<url-pattern>访问servlet,最后访问到他的class文件。
首先根据<url-pattern>找到它的name,然后根据name找到<servlet> <name>相同的servlet标签,根据name找到<servlet-class>class路径,使用找到的class生成servlet实例到服务器内存中。

ServletConfig接口:

servlet容器使用servlet配置对象,该对象在初始化期间将信息传递给ServletConfig。

  • String getInitParamter(String name):根据初始化参数名称获取初始化参数的值,如果没有此参数,返回null
  • Enumeration getInitParamterNames():获取所有初始化参数名称,以枚举方式返回,如果没有参数,返回一个空的枚举

web.xml中初始化参数:

  <servlet>
  	<servlet-name>HelloServlet</servlet-name>
  	<servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class>
  	<!-- 配置两个初始化参数 -->
  	<init-param>
  		<param-name>name</param-name><!-- key -->
  		<param-value>Feathers</param-value><!-- value -->
  	</init-param>
 	<init-param>
  		<param-name>age</param-name>
  		<param-value>100</param-value>
  	</init-param>
  </servlet>
  <servlet-mapping>
  	<servlet-name>HelloServlet</servlet-name>
	<url-pattern>/HelloServlet</url-pattern>
  </servlet-mapping>

在Servlet service()中对初始化参数进行处理:

package com.feathers.servletdemo;
import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet implements Servlet {
	
	private ServletConfig config;
	@Override
	public void init(ServletConfig config) throws ServletException {
		this.config = config;
	}

	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		// 响应对象向客户端写东西
		response.setContentType("text/html;charset=utf-8");
		response.getWriter().write("Hello Client, I'm Servlet");
		// 获取初始化参数 作出处理
		Enumeration<String> enums = getServletConfig().getInitParameterNames();
		while (enums.hasMoreElements()){
			String key = enums.nextElement();
			String value = getServletConfig().getInitParameter(key);
			response.getWriter().write(key+":"+value + "<br/>");
		}
	}

	@Override
	public void destroy() {
	}
	

	@Override
	public ServletConfig getServletConfig() {
		return config;
	}

	@Override
	public String getServletInfo() {
		return null;
	}

}

请求结果如下:
这里写图片描述

  • getServletName():获取Servlet的名称<servlet-name>
  • getServletContext():获取ServletContext上下文对象
点击form表单提交,跳转servlet
<!-- 注意action, 这个是Servlet的访问路径,但是不能加/ 不能加/ 不能加/  -->
<form action="FirstServlet" method="get">
		<table>
			<tr>
				<td>用户名:</td>
				<td><input type="text" name="username" value=""/></td>
			</tr>
			<tr>
				<td>密码:</td>
				<td><input type="password" name="password" value=""/></td>
			</tr>
			<tr>
				<td><input type="checkbox" name="remember" value=“yes”/>记住用户名</td>
			</tr>
			<tr>
				<td><input type="submit" value="提交"/></td>
			</tr>
		</table>
	</form>

方法二、继承GenericServlet

所谓的GenericServlet只是一个Servlet的实现类,因为用户每次使用Servlet接口自定义一个Servlet都会进行许多不必要的操作,所以就封装了一个GenericServlet以方便程序员使用,

使用方法:在方法service()中进行请求的处理,具体请查看源码。

下面是它的源码:

    package javax.servlet;  
      
    import java.io.IOException;  
    import java.util.Enumeration;  
      
    //GenericServlet实现了Servlet、ServletConfig、Serializable三个接口
    public abstract class GenericServlet   
        implements Servlet, ServletConfig, java.io.Serializable  
    {  
        private transient ServletConfig config;  
          
        //无参的构造方法  
        public GenericServlet() { }  
          
        /* 
        实现接口Servlet接口生命周期初始化init(ServletConfig Config)方法,将ServletConfig对象保存到成员变量中,以扩展他的生命周期,让service方法也能访问 
        **/  
          
        public void init(ServletConfig config) throws ServletException {  
          this.config = config;  
          this.init(); // 用户的初始化方法
        }  
      
        public void init() throws ServletException {  
        }  
          
        public void destroy() { }      
              
           
        //返回ServletConfig对象  
        public ServletConfig getServletConfig(){  
           return config;  
        }      
      
        //该方法实现接口<Servlet>中的ServletInfo,默认返回空字符串  
        public String getServletInfo() {  
           return "";  
        }  
          
          
       // service方法没有去实现,而是丢给使用者去实现         
        public abstract void service(ServletRequest req, ServletResponse res)  
     throws ServletException, IOException;  
          
       
      // 为了方便用户调用,简化操作,实现了ServletConfig接口,这样就不用使用getServletConfig().getServletContext()获取对象了
      // 只需使用getServletContext即可
      // 即实现ServletConfig的目的
        public ServletContext getServletContext() {  
           return getServletConfig().getServletContext();  
        }  
          
        public String getInitParameter(String name) {  
         return getServletConfig().getInitParameter(name);  
        }  
         
        public Enumeration getInitParameterNames() {  
           return getServletConfig().getInitParameterNames();  
         
        public String getServletName() {  
            return config.getServletName();  
        }  
           
      
        public void log(String msg) {  
           getServletContext().log(getServletName() + ": "+ msg);  
        }    
         
         
        public void log(String message, Throwable t) {  
           getServletContext().log(getServletName() + ": " + message, t);  
        }  
    }  

方法三、继承HttpServlet

HttpServlet继承自GenericServlet,是针对Http协议的Servlet,是进一步的封装,
使用只需要继承HttpServlet,重写doGet或doPost方法即可,doGet用来处理get请求,而doPost方法用来进行post请求处理。

源码如下:

    package javax.servlet.http;  
    .....  
      
    public abstract class HttpServlet extends GenericServlet  
        implements java.io.Serializable   
    {  
      
        private static final String METHOD_GET = "GET";  
        private static final String METHOD_POST = "POST";  
        ......  
        public HttpServlet() { }  
        
        public void service(ServletRequest req, ServletResponse res)  
            throws ServletException, IOException  
        {  
	    // 1. 强转ServletRequest与ServletResponse
            HttpServletRequest        request;  
            HttpServletResponse        response;  
              
            try {  
                request = (HttpServletRequest) req;  
                response = (HttpServletResponse) res;   
            } catch (ClassCastException e) {  
                throw new ServletException("non-HTTP request or response");  
            }  
            //调用重载的service()方法  
            service(request, response);  
        }

  
        //重载的service方法  
        protected void service(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  
        {  
	        // 根据操作的不同,作出不同的处理
	        // 处理方法有 doGet,doPost,doHead,doDelete...
	        // 这些都是http协议中的方法,get请求,post修改,delete删除,put上传等。

			// 获取请求方式
            String method = req.getMethod();  
      
            if (method.equals(METHOD_GET)) {  // get请求
                long lastModified = getLastModified(req);  
                if (lastModified == -1) {  
                    doGet(req, resp);  
                } else {  
                    long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);  
                    if (ifModifiedSince < (lastModified / 1000 * 1000)){
                        maybeSetLastModified(resp, lastModified);  
                        doGet(req, resp);  
                    } else {  
                        resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);  
                    }  
                }  
      
            } else if (method.equals(METHOD_HEAD)) {   
                long lastModified = getLastModified(req);  
                maybeSetLastModified(resp, lastModified);  
                doHead(req, resp);  
      
            } else if (method.equals(METHOD_POST)) {   // post请求
                doPost(req, resp);  
                  
            } else if (method.equals(METHOD_PUT)) {  // put 增加操作
                doPut(req, resp);          
                  
            } else if (method.equals(METHOD_DELETE)) {   // 删除操作
                doDelete(req, resp);  
                  
            } else if (method.equals(METHOD_OPTIONS)) {  
                doOptions(req,resp);  
                  
            } else if (method.equals(METHOD_TRACE)) {  
                doTrace(req,resp);  
                  
            } else {
                String errMsg = lStrings.getString("http.method_not_implemented");  
                Object[] errArgs = new Object[1];  
                errArgs[0] = method;  
                errMsg = MessageFormat.format(errMsg, errArgs);  
                  
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);  
            }  
        }  
         
        ......

protected void doGet(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  
        {  
            String protocol = req.getProtocol();  
            String msg = lStrings.getString("http.method_get_not_supported");  
            if (protocol.endsWith("1.1")) {  
                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);  
            } else {  
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);  
            }  
        }  
          
      
        protected void doHead(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  
        {  
            .......  
        }    
        protected void doPost(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  
        {  
            String protocol = req.getProtocol();  
            String msg = lStrings.getString("http.method_post_not_supported");  
            if (protocol.endsWith("1.1")) {  
                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);  
            } else {  
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);  
            }  
        }     
        protected void doPut(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  {  
            //todo  
        }     
          
              
        protected void doOptions(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException {  
           //todo  
        }  
              
        protected void doTrace(HttpServletRequest req, HttpServletResponse resp)   
            throws ServletException, IOException   {         
          //todo  
        }     
       
        protected void doDelete(HttpServletRequest req,  
                                HttpServletResponse resp)  
            throws ServletException, IOException   {  
            //todo  
        }     

    }  

Servlet线程安全问题:

Servlet存在这线程安全的问题,
假设在Servlet中放置一个成员变量name,用来接受用户的表单信息。当多个用户访问Servlet时,会对name进行多次赋值,那么用户得到的结果就很有可能出错,因为多个用户共用一块内存。

解决办法也很简单,让每个用户都有一块内存保存自己的name值,只需要将name值保存在方法中,建立一个局部变量即可。因为java方法在被调用时,每次被调用,都会开辟一个新的方法区。

修改Servlet创建时机

通常情况下,servlet在被用户第一次调用时创建,如果servlet要进行非常耗时的创建操作,用户就会等待很久,影响用户体验,所以,我们可以将servlet设置在服务器启动之后,解决这类问题:
web.xml中,添加加载时间标签:

<servlet>
  	<servlet-name>HelloServlet</servlet-name>
  	<servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class>
  	<!-- 让servlet随项目的启动而启动 value: 0-5  数字越小,启动优先级越高       如果多个servlet优先级相同,则按照配置顺序启动-->
  	<load-on-startup>3</load-on-startup>
  </servlet>

Servlet访问路径配置

相对路径:
/ 代表 项目路径
/AServlet => http://localhost:8080/ServletDemo/Aservlet

/File/* => /File/ => http://localhost:8080/ServletDemo/File/sfsdfdsfsdfe 匹配任意

匹配范围越大,优先级越低:
两个配置/File/AServlet/File/*
访问:http://localhost:8080/ServletDemo/File/AServlet
则会访问AServlet,而不会访问第二个路径,因为第一个优先级高。

后缀名匹配:
*.dohttp://localhost:8080/ServletDemo/sfdsfds.do
该方式不常用,Filter过滤器常用
注意:不能同时使用后缀名匹配和相对路径。例如:/File/*.do

ServletContext

项目级别的对象,一个Web项目,有且只有一个ServletContext对象,在项目启动时创建,到项目关闭时销毁,可以理解为这个类集合了项目所有的功能方法,代表了项目,所以这个类非常强大。
我们使用ServletConfig.getServletContext()获得这个对象。

功能如下:

获取项目参数

配置项目初始化参数:

<webapp>
  <context-param>
  	<param-name>name</param-name>
  	<param-value>tom</param-value>
  </context-param>
</webapp>

获取项目初始化参数:
getServletContext().getInitParamter(String name)
getServletContext().getInitParamterNames()

ServletContext 域

域是服务器两个组件之间的通讯,比如两个Servlet之间通讯。

Application域

在ServletContext中,有一个Map<String,Object> 集合,用来保存信息,这个Map集合就是ServletContext的域。
AServlet获取ServletContext对象,向域中添加信息,BServlet也可以获取Servlet,然后从ServletContext域中获取AServlet填入的信息。
这样,每个Servlet之间就可以共享信息了。
同样存在线程不安全的问题。

/*AServlet*/
// 1. 获取ServletContext
ServletContex scontext = getServletContext();
// 2. 向域中存放键值对
scontext.setAttribute(key,object);

/*BServlet*/
// 3. 在另一个Servlet中从域中取出键值对
getServletContext().getAttribute(key);
// 4. 删除key
getServletContext().removeAttribute(key);
// 5. 获取所有建
getServletContext().getAttributeNames();//返回枚举

ServletContext获取项目内的资源

获取WebRoot/WebContent下的user.xml文件:

InputStream is = getServletContext().getResourceAsStream("/user.xml"); // 获得资源流
String path = getServletContext().getRealPath("/user.xml");// 获取资源绝对路径(tomcat webapp下 项目的部署路径)

// 获取WEB-INFO下的`user.xml`文件
getServletContext().getResourceAsStream("/WEB-INFO/user.xml");

获取class资源:

// 方法1:使用上面的方法
getServletContext().getResourceAsStream("/WEB-INFO/classes/com/feathers/servlet/AServlet.class"); // 麻烦

// 方法2:
// “/” 代表src目录,或者是classes目录,详见getResourceAsStream方法
InputStrean is2 = AServlet.class.getResourceAsStream("/user.xml");
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值