过滤器监听器上下文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、生命周期相关的时间
容器创建或者销毁了request,session,servlet,上下文时产生的事件。
比如说现在有多少人在访问应用,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线程安全问题的主要内容,如果未能解决你的问题,请参考以下文章