Servlet、Filter、Listener深入理解

Servlet

Servlet接口

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res)
	throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}

Init()

在Servlet实例化之后,Servlet容器会调用init()方法,来初始化该对象,主要是为了让Servlet对象在处理客户端请求前可以完成一些初始化的工作,例如:建立数据库连接、获取配置信息等。对于每一个Servlet实例,init()方法只能被调用一次。init()方法有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用Servlet对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。另外,在Servlet中,还可以通过ServletConfig对行啊获取描述Servlet运行环境的ServletContext对象,使用该对象,Servlet可以和它的Servlet容器进行通信。

service()

容器调用service()方法来处理客户端请求。要注意,在Servlet方法被容器调用之前,必须确保init()方法正确完成。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()方法。在service方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。

destory()

当容器检测到一个Servlet对象应该从服务中被移除的时候,容器会调用该对象的destory()方法,以便让Servlet对象可以释放它所使用的资源,同时保存数据到持久存储设备中,例如将内存中的数据保存到数据库中,关闭数据库连接等。当需要释放内存或者容器关闭时,容器就会调用Servlet对象的destory()方法。在Servlet容器调用destory()方法之前,如果还有其他线程正在service()方法中执行,容器将会等待这些线程执行完毕或等待服务器设定的超时时间到达。一旦Servlet对象的destory()方法被调用,容器不会再把其他的请求发送给该对象。如果需要该Servlet再次为客户端服务,容器将会重新产生一个Servlet对象来处理客户端请求。在destory()方法调用之后,容器会释放这个Servlet对象,在随后的时间内,该对象会被Java垃圾收集器所回收。

getServletConfig()

该方法返回容器调用init()方法时传递给Servlet对象的ServletConfig对象,ServletConfig对象包含了Servlet的初始化参数。

getServletInfo()

返回一个String类型的字符串,其中包括了关于Servlet的信息,例如,作者、版本和版权。

ServletConfig接口

public interface ServletConfig {

    public String getServletName();//返回Servlet实例的名字

    public ServletContext getServletContext();//返回Servlet上下文对象
    
    public String getInitParameter(String name);//返回名称为name的初始化参数的值

    public Enumeration getInitParameterNames();//返回Servlet所有初始化参数的名字和枚举集合
}

GenericServlet抽象类

public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable
{//GenericServlet抽象类定义了一个通用的、不依赖于具体协议的Servlet,简化子类的实现。

    private transient ServletConfig config;//config为transient,不参与序列化

    public GenericServlet() { }
    
    public void destroy() {
    }
    
    public String getInitParameter(String name) {
	return getServletConfig().getInitParameter(name);
    }
    
    public Enumeration getInitParameterNames() {
	return getServletConfig().getInitParameterNames();
    }   
    
    public ServletConfig getServletConfig() {
	return config;
    }
    
    public ServletContext getServletContext() {
	return getServletConfig().getServletContext();
    }

    public String getServletInfo() {
	return "";
    }
    //对Servlet接口中init()方法的实现。其中调用了不带参数的的init()方法。
    public void init(ServletConfig config) throws ServletException {
	this.config = config;
	this.init();
    }
    //不带参数的init()方法。通常我们在编写继承自GenericServlet的Servlet类时,只需重写该方法。

    public void init() throws ServletException {

    }
    public void log(String msg) {
	getServletContext().log(getServletName() + ": "+ msg);
    }
    public void log(String message, Throwable t) {
	getServletContext().log(getServletName() + ": " + message, t);
    }
    
    public abstract void service(ServletRequest req, ServletResponse res)
	throws ServletException, IOException;


    public String getServletName() {
        return config.getServletName();
    }
}

 

HTTPServlet抽象类

public abstract class HttpServlet extends GenericServlet
    implements java.io.Serializable
{
//绝大多数的网络应用中,都是客户端(浏览器)通过HTTP协议去访问服务器端的资源。
//而我们所编写的Servle也主要是应用于HTTP协议的请求和响应。该抽象类简化了开发应用于HTTP协议的Servlet。
......
        //对GenericServlet类中service()方法的实现。
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		HttpServletRequest request;
		HttpServletResponse response;
                //首先进行显示类型转换
		try {
			request = (HttpServletRequest) req;
			response = (HttpServletResponse) res;
		} catch (ClassCastException e) {
			throw new ServletException("non-HTTP request or response");
		}
		service(request, response);//调用下一个service方法
	}
        //在编写HTTPServlet子类时,通常不需要覆盖该方法,而只需要重写相应的doXXX()方法。
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String method = req.getMethod();//获取HTTP请求方法的类型
                //HTTP1.1中定义了7种请求方法:Get、Post、Head、Put、Delete、Trace和Options
                //然后根据请求方法的类型,调用相应的doXXX()方法

		if (method.equals(METHOD_GET)) {
			long lastModified = getLastModified(req);
			if (lastModified == -1) {
				// servlet doesn't support if-modified-since, no reason
				// to go through further expensive logic
				doGet(req, resp);
			} else {
				long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
				if (ifModifiedSince < (lastModified / 1000 * 1000)) {
					// If the servlet mod time is later, call doGet()
					// Round down to the nearest second for a proper compare
					// A ifModifiedSince of -1 will always be less
					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)) {
			doPost(req, resp);

		} else if (method.equals(METHOD_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 {
			//不支持的请求方法,提示错误
			// Note that this means NO servlet supports whatever
			// method was requested, anywhere on this server.
			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);
		}
	}

......}

 

Servlet生命周期

Servlet运行在Servlet容器中,其生命周期由容器来管理。Servlet的生命周期通过Servlet接口中的init()、service()和destory()方法来表示。

注:如果需要让Servlet容器在启动时自动加载Servlet,可以在web.xml文件中配置。

单实例多线程的Servlet模型

Servlet规范中定义,默认情况下(Servlet不是在分布式的环境中部署),Servlet容器对声明的每一个Servlet,只创建一个实例。如果有多个客户端请求同时访问这个Servlet,Servlet容器如何处理多个请求呢?答案是采用多线程,Servlet容器维护一个线程池来服务请求。当容器接收到一个访问Servlet的请求,调度者线程从线程池中选取一个工作线程,将请求传递给该线程,然后由这个线程执行Servlet的service()方法。

线程安全的Servlet

变量的线程安全

因为Servlet是单实例多线程模型,多个线程共享一个Servlet实例,因此对于实例变量的访问是非线程安全的。

建议:在Servlet中尽可能的使用局部变量,应该只使用只读的实例变量和静态变量。如果非得使用共享的实例变量或静态变量,在修改共享变量时应该注意线程同步。

属性的线程安全

在Servlet中,可以访问保存在ServletContext、HttpSession和ServletRequest对象中的属性。那么这三种不同范围的对象,属性访问是否是线程安全的呢?

ServletContext:该对象被Web应用程序的所有Servlet共享,多线程环境下肯定是非线程安全的。

HttpSession:HttpSession对象只能在同属于一个Session的请求线程中共享。对于同一个Session,我们可能会认为在同一时刻只有一个用户请求,因此,Session对象的属性访问是线程安全的。但是,如果用户打开多个同属于一个进程的浏览器窗口,在这些窗口中的访问请求同属于一个Session,对于多个线程的并发修改显然不是线程安全的。

ServletRequest:因为Servlet容器对它所接收到的每一个请求,都创建一个新的ServletRequest对象,所以ServletRequest对象只在一个线程中被访问,因此对ServletRequest的属性访问是线程安全的。但是,如果在Servlet中创建了自己的线程,那么对ServletRequest的属性访问的线程安全性就得自己去保证。此外,如果作死的将当前请求的Servlet通过HttpSession或者ServletContext共享,那当然也是非线程安全的。

相关问题

1、重定向和转发的区别

JSP

JSP是一种建立在Servlet规范提供的功能之上的动态网页技术,它通过在网页文件中嵌入脚本代码,用于产生动态内容。

JSP文件在用户第一次请求时,会被编译成Servlet,然后由这个Servlet处理用户请求,所以JSP也可以看成是运行时的Servlet。

相关问题

JSP和Servlet的区别与联系?

JSP在本质上就是SERVLET,但是两者的创建方式不一样.Servlet完全是JAVA程序代码构成,擅长于流程控制和事务处理,通过Servlet来生成动态网页很不直观.JSP由HTML代码和JSP标签构成,可以方便地编写动态网页.因此在实际应用中采用Servlet来控制业务流程,而采用JSP来生成动态网页.在struts框架中,JSP位于MVC设计模式的视图层,而Servlet位于控制层.JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP是Java和HTML组合成一个扩展名为.jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑。

Filter

Filter接口

public interface Filter {
        //用于完成Filter的初始化
	public void init(FilterConfig filterConfig) throws ServletException;
	//实现过滤功能
        public void doFilter ( ServletRequest request, ServletResponse response, 
FilterChain chain ) throws IOException, ServletException;
        //用于销毁Filter前,完成某些资源的回收
	public void destroy();
}

Filter生命周期

web.xml 中声明的每个 filter 在每个虚拟机中仅仅只有一个实例。
        (1) 加载和实例化
        Web 容器启动时,即会根据 web.xml 中声明的 filter 顺序依次实例化这些 filter。
        (2) 初始化
        Web 容器调用 init(FilterConfig) 来初始化过滤器。容器在调用该方法时,向过滤器传递 FilterConfig 对象,FilterConfig 的用法和 ServletConfig 类似。利用 FilterConfig 对象可以得到 ServletContext 对象,以及在 web.xml 中配置的过滤器的初始化参数。在这个方法中,可以抛出 ServletException 异常,通知容器该过滤器不能正常工作。此时的 Web 容器启动失败,整个应用程序不能够被访问。实例化和初始化的操作只会在容器启动时执行,而且只会执行一次。
        (3) doFilter
        doFilter 方法类似于 Servlet 接口的 service 方法。当客户端请求目标资源的时候,容器会筛选出符合 filter-mapping 中的 url-pattern 的 filter,并按照声明 filter-mapping 的顺序依次调用这些 filter 的 doFilter 方法。在这个链式调用过程中,可以调用 chain.doFilter(ServletRequest, ServletResponse) 将请求传给下一个过滤器(或目标资源),也可以直接向客户端返回响应信息,或者利用 RequestDispatcher 的 forward 和 include 方法,以及 HttpServletResponse 的 sendRedirect 方法将请求转向到其它资源。需要注意的是,这个方法的请求和响应参数的类型是 ServletRequest  和 ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。
        (4) 销毁
        Web 容器调用 destroy 方法指示过滤器的生命周期结束。在这个方法中,可以释放过滤器使用的资源。
       

Filter运行原理



Filter应用场景

1、统一POST请求中文字符编码的过滤器
2、控制浏览器缓存页面中的静态资源的过滤器

有些动态页面中引用了一些图片或css文件以修饰页面效果,这些图片和css文件经常是不变化的,所以为减轻服务器的压力,可以使用filter控制浏览器缓存这些文件,以提升服务器的性能。

3、使用Filter实现URL级别的权限认证
在实际开发中我们经常把一些执行敏感操作的servlet映射到一些特殊目录中,并用filter把这些特殊目录保护起来,限制只能拥有相应访问权限的用户才能访问这些目录下的资源。从而在我们系统中实现一种URL级别的权限功能。

4、实现用户自动登陆
首先,在用户登陆成功后,发送一个名称为user的cookie给客户端,cookie的值为用户名和md5加密后的密码。编写一个AutoLoginFilter,这个filter检查用户是否带有名称为user的cookie,如果有,则调用dao查询cookie的用户名和密码是否和数据库匹配,匹配则向session中存入user对象(即用户登陆标记),以实现程序完成自动登陆。

Filter应用实例

与开发 Servlet 不同的是,Filter 接口并没有相应的实现类可供继承,要开发过滤器,只能直接实现 Filer 接口。

此过滤器用来解决全站中文乱码问题:设置统一的字符编码集

public class CharacterEncodingFilter implements Filter {
 
     private FilterConfig filterConfig = null;
     //设置默认的字符编码
     private String defaultCharset = "UTF-";
 
     public void doFilter(ServletRequest req, ServletResponse resp,
             FilterChain chain) throws IOException, ServletException {
         
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) resp;
         String charset = filterConfig.getInitParameter("charset");
         if(charset==null){
             charset = defaultCharset;
         }
         request.setCharacterEncoding(charset);
         response.setCharacterEncoding(charset);
         response.setContentType("text/html;charset="+charset);
         
         MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
         chain.doFilter(requestWrapper, response);
     }
 
     public void init(FilterConfig filterConfig) throws ServletException {
         //得到过滤器的初始化配置信息
         this.filterConfig = filterConfig;
     }
     
     public void destroy() {
 
     }
 }

Listener

当Web应用在Web容器中运行时,Web应用内部会不断地发生各种事件:如Web应用的启动和停止、用户Session的开始和结束等,通常这些Web事件对开发者是透明的。Listener(监听器)是观察者模式的应用,通过方法回调来实现。

Listener生命周期

Listener在当web容器启动的时候,去读取每个web应用的web.xml配置文件,当配置文件中配有filter和listener时,web容器实例化listener,listener是当某个事件发生时,调用它特定方法,如HttpSessionListener,当创建一个session时会调用它的sessionCreated()方法,当servlet容器关闭或者重新加载web应用时lister对象被销毁。

Listener分类

不同功能的Listener 需要实现不同的 Listener  接口,一个Listener也可以实现多个接口,这样就可以多种功能的监听器一起工作。常用监听器:

1)监听 Session、request、context 的创建于销毁,分别为  

HttpSessionLister、ServletContextListener、ServletRequestListener

2)监听对象属性变化,分别为:

HttpSessionAttributeLister、ServletContextAttributeListener、ServletRequestAttributeListener

Listener应用

1、利用HttpSessionLister,统计当前在线人数。

public class OnLineCountListener implements HttpSessionListener {

	@Override
	public void sessionCreated(HttpSessionEvent se) {
		ServletContext context = se.getSession().getServletContext();
		Integer onLineCount = (Integer) context.getAttribute("onLineCount");
		if (onLineCount == null) {
			context.setAttribute("onLineCount", 1);
		} else {
			onLineCount++;
			context.setAttribute("onLineCount", onLineCount);
		}
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent se) {
		ServletContext context = se.getSession().getServletContext();
		Integer onLineCount = (Integer) context.getAttribute("onLineCount");
		if (onLineCount == null) {
			context.setAttribute("onLineCount", 1);
		} else {
			onLineCount--;
			context.setAttribute("onLineCount", onLineCount);
		}
	}
}

2、自定义Session扫描器

当一个Web应用创建的Session很多时,为了避免Session占用太多的内存,我们可以选择手动将这些内存中的session销毁,那么此时也可以借助监听器技术来实现。

参考

http://zachary-guo.iteye.com/blog/640889

http://www.cnblogs.com/xdp-gacl/p/3965508.html


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值