过滤器监听器上下文servlet线程安全问题

Posted Recar

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了过滤器监听器上下文servlet线程安全问题相关的知识,希望对你有一定的参考价值。

就像名字写那样,过滤器可以过滤请求,比如对一些评论进行过滤。又不改原有代码的基础上可以加上一个过滤器,或者是登陆验证。集中在一个过滤器中处理。写一个类实现接口 Filter 之后一定要在配置文件中配置!!!监听器可以监听,上下文的概念。

过滤器:

什么是过滤器:
servlet规范当中定义的一种特殊的组件,用来拦截servlet容器的调用过程。
会先调过过滤器的方法,过滤器决定是否向后继续调用就是调用servlet容器
容器收到请求之后 通常情况下会调用servlet的service方法来处理请求。如果有过滤器,则容器先调用过滤器的方法

如何写一个过滤器:

1、写一个java类,实现接口Filter
2、在doFilter方法里面,编写拦截处理逻辑
3、配置过滤器(web.xml) 让容器知道哪些请求需要拦截
比如写一个评论,然后显示出来。但是说一些敏感字。就不允许其评论
但是已经写完了的话,评论与 后端的 servlet的话。可以直接加个过滤器
容器只要一启动,就会立即创建过滤对象。只会创建一个。
容器在创建过滤器对象之后会调用该对象的init方法。该方法只会执行一次。
容器调用doFilter方法来处理请求
FilterChain(过滤器链)

过滤器

CommentFilterA

package web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CommentFilterA implements Filter {

    public void destroy() {
        // TODO Auto-generated method stub 

    }
        //容器会将request和response作为参数传递过来。
        //下面两个arg0和arg1就是 但是是Servlet的。用的是其子类HttpServlet。那么就强制转换成其子类
    //如果调用了FilterChain的doFilter方法,则容器会继续向后调用,如果没有调用doFilter就爱不会继续向后调用

    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {
        // TODO Auto-generated method stub
        HttpServletRequest request = (HttpServletRequest) arg0;

        HttpServletResponse response = (HttpServletResponse) arg1;

        request.setCharacterEncoding("utf-8");
        String content = request.getParameter("content");
        if(content.indexOf("日")!=-1){
            //包含了敏感字
            PrintWriter out = response.getWriter();
            out.print("!!! your commnet is error");
            return ;
        }else{
            //没有,继续向后调用
            arg2.doFilter(arg0, arg1);
        }

    }

    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

}

一定要记得配置!! web.xml

<!-- 配置过滤器 -->
    <filter>
    <filter-name>filterA</filter-name>
    <filter-class>web.CommentFilterA</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>filterA</filter-name>
/*这个就是访问什么url就会通过过滤器写的评论是将评论内容发向process的,所以~我写了个*.jsp 就根本不能访问了!!!哈哈哈 */
    <url-pattern>/process</url-pattern>
    </filter-mapping>

提交敏感字符
这里写图片描述

过滤器拦截,发现敏感字符。不继续执行。
这里写图片描述

练习: 评论的字符的个数有限制

写一个过滤器,检测评论的字符的个数,如果超过10个,则提示评论的字数过多。

CommentFilterB.java
package web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CommentFilterB implements Filter{

    public void destroy() {
        // TODO Auto-generated method stub

    }

    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {
        // TODO Auto-generated method stub

        HttpServletRequest request = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;
        request.setCharacterEncoding("utf-8");
        String message = request.getParameter("content");
        PrintWriter out = response.getWriter();
        if(message.length()>10){
            out.print("<h1>评论字数不得超过10个!</h1>");
            return ;
        }else{
            arg2.doFilter(arg0, arg1);
        }

    }

    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

}

过滤器的优先级

当有多个过滤器都满足过滤的条件,则容器会依据的先后顺序来调用。

但是这么的就固定限制了,只能是日和10个。
那么就~

<!-- 初始化参数 -->
    <init-param>
    <param-name>illegalStr</param-name>
    <param-value>日你</param-value>
    </init-param>

这里写图片描述

在配置文件中先配置好,然后去读取初始化参数

更改CommentFilterA.java过滤器
就是给init增加一个属性,因为init执行一次就没了。
那么变量也没了。创建一个全局变量来存储容器传进来
的FilterConfig对象
然后在方法中读取配置文件中对应名字的内容

package web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CommentFilterA implements Filter {
    private FilterConfig config;
    //用config来存储init中的读取初始化参数的对象。

    public void destroy() {
    }
        //容器会将request和response作为参数传递过来。
        //下面两个arg0和arg1就是 但是是Servlet的。用的是其子类HttpServlet。那么就强制转换成其子类
    //如果调用了FilterChain的doFilter方法,则容器会继续向后调用,如果没有调用doFilter就爱不会继续向后调用

    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {
        // TODO Auto-generated method stub

        HttpServletRequest request = (HttpServletRequest) arg0;

        HttpServletResponse response = (HttpServletResponse) arg1;

        request.setCharacterEncoding("utf-8");
        String content = request.getParameter("content");
        String mingan = config.getInitParameter("illegalStr");
        if(content.indexOf(mingan)!=-1){
            //包含了敏感字
            PrintWriter out = response.getWriter();
            out.print("!!! your commnet is error");
            return ;
        }else{
            //没有,继续向后调用
            arg2.doFilter(arg0, arg1);
        }

    }

    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub
        //将容器传递过来的FilterConfig对象保存下来
        config=arg0;
    }

}

过滤器优点

1、不用修改原有程序,在原有程序上增加一些新的功能。
2、将多个组件相同的处理逻辑集中写在过滤器里面,方便代码的维护。(比如登陆,可以把验证写在过滤器里)

监听器

什么是监听器:
servlet规范当中定义的一种特殊的组件,用来监听容器产生的事件的。

容器会产生什么事件:

主要有两大类:
1、生命周期相关的时间
容器创建或者销毁了requestsessionservlet上下文时产生的事件。
比如说现在有多少人在访问应用,session是用来保存状态的。只用统计session的个数。他的创建和销毁,做个监听器进行监听
2、绑定数据相关的事件
调用了request,session,servlet上下文的setAttribute,removeAttribute时产生的事件。

servlet上下文

什么是servlet上下文
容器启动之后,会为每一个web应用创建唯一的一个符合ServletContext接口要求的对象
特点:
一个web应用对应一个唯一的上下文
只要容器没有关闭,并且应用没有被删除,则上下文会一直存在。

如何获得上下文

4种方式
GenericServlet,ServletConfig,FilterConfig,HttpSession提供了一个getServletContext方法来获得上下文

作用:
1、绑定数据

setAttribute,getAttribute,removeAttribute

注:将数据绑定到上下文上面,可以随时访问。因为他一直在。
转发
request.setAttribute
绑定到session上
session.setAttrobute
也可以 ServletContext.setAttribute
绑定的不同上面不同,ServletContext时间最长,但是占用内存。
所有优先使用保存时间短的即request
在满足使用条件的情况下,优先使用生命周期短的
(request<session<上下文)

例子: 先写一个ServletA 来在上下文中绑定数据,然后在ServletB中读取数据。

这个数据是随web应用的结束而结束,就算浏览器关闭了也还在。

这里写图片描述

ServletA:


package web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class A extends HttpServlet {

    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        //先获得上下文
        ServletContext ctx = getServletContext();
        //将一些数据绑定到上下文
        ctx.setAttribute("userlist", "ddd,qqq,lihaile");

        out.close();
    }

}

ServletB:

package web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class B extends HttpServlet {

    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        //获得上下文
        ServletContext ctx = getServletContext();
        String userlist = (String) ctx.getAttribute("userlist");
        out.print("<h1>"+userlist+"</h1>");
        out.close();
    }

}

2、访问全局的初始化参数

<!-- 全局的初始化参数 -->
    <context-param>
    <param-name>company</param-name>
    <param-value>Recar</param-value>
    </context-param>

这里写图片描述

然后读取全局的初始化参数:

String getInitParamenter(String paramName);

如何写一个监听器:

1、写一个java类实现相应的接口
要根据监听的事件类型来选择合适的接口。
比如,要监听session的创建和销毁,需要实现HttpSessionListener接口
2、在这个接口方法当中,实现监听处理逻辑
3、配置web.xml

例子:实现统计在线人数

这里写图片描述

把count这个数据绑定到山下文中。通过session事件的创建于销毁来检测当前在线人数,创建则加,销毁则减

用一个浏览器看就是在线人数1,我用了3个浏览器看就变成3个了。一个浏览器在开新窗口还是人数不会变的,因为无论开多少窗口都会用那个session的
sessionId会保存在内存中。

增加登出。就是销毁session
这里写图片描述

再写一个servlet来登出,就是删除其session。触发session销毁事件,减少一个在线人数

监听器:
Countlister.java

package web;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class Countlistener implements HttpSessionListener{

    /*
     * session对象创建之后,容器会调用此方法
     */

    public void sessionCreated(HttpSessionEvent arg0) {
        // TODO Auto-generated method stub
        //获得上下文,先通过session事件对象来获取session
        HttpSession session=arg0.getSession();
        ServletContext ctx = session.getServletContext();
        Integer count = (Integer) ctx.getAttribute("count");
        if(count==null){
            count=1;
        }else{
            count++;
        }

        ctx.setAttribute("count", count);


    }

    /*
     * session对象销毁之后,容器会调用此方法。
     */
    public void sessionDestroyed(HttpSessionEvent arg0) {


        HttpSession session=arg0.getSession();
        ServletContext ctx = session.getServletContext();
        Integer count = (Integer) ctx.getAttribute("count");
        count--;

        ctx.setAttribute("count", count);

    }

}

显示当前在线人数:

<h1>当前在线人数:<%=application.getAttribute("count") %></h1>

<h2><a href="logout">登出!</a></h2>

登出,销毁session类
LogoutServlet.java

package web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LogoutServlet extends HttpServlet {


    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        HttpSession session = request.getSession();
        session.invalidate();

    }

}

servlet线程安全问题

为什么说servlet会存在线程安全问题?
容器收到请求会启动一个线程
当有多个线程访问一个方法。

package web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SomeServlet extends HttpServlet {
    private int count ;
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        count++;
        try {
            Thread.sleep(2000);
            //刻意造成线程安全问题
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+count);
    }

}

这里写图片描述

我访问了好几次。因为设置了sleep。所以访问的线程会sleep。然后是每个访问的线程都对count进行了操作。产生了线程安全问题

容器在默认情况下,只会创建一个servlet实例(对象)
容器收到一个请求,就会启动一个线程来处理
如果有多个请求同时访问某个servlet,就有可能产生线程安全问题
(比如,这些线程要修改servlet的属性)

解决:

可以使用 synchronized对有可能产生线程安全问题的代码块加锁
但是加锁会影响性能

package web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SomeServlet extends HttpServlet {
    private int count ;
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        synchronized(this){
        count++;

        try {
            Thread.sleep(2000);
            //刻意造成线程安全问题
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+count);
        }
    }

}

这里写图片描述

以上是关于过滤器监听器上下文servlet线程安全问题的主要内容,如果未能解决你的问题,请参考以下文章

servlet 过滤器中的 StringBuffer 与 StringBuilder

过滤器,拦截器,监听器的区别

过滤器,拦截器,监听器的区别

过滤器,拦截器,监听器的区别

面試就业题库-04

java web过滤器拦截器监听器的区别