1、监听器
详见《Tomcat与Java Web开发技术详解》4.4.2
在Servlet API中有一个ServletContextListener接口,它能够监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。
当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理。在ServletContextListener接口中定义了处理ServletContextEvent事件的两个方法。
- contextInitialized(ServletContextEvent sce):当Servlet容器启动Web应用时调用该方法。在调用完该方法之后,容器再对Filter初始化,并且对那些在Web应用启动时就需要被初始化的Servlet进行初始化。
- contextDestroyed(ServletContextEvent sce):当Servlet容器终止Web应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet和Filiter过滤器。
应用举例:
记录当Web应用启动后,网页被客户端访问的次数。
- 在应用启动时,从文件中读取计数器的数值,并把表示计数器的Counter对象存到Web应用范围内。存放计数器的文件的路径为helloapp/count/count.txt。
- 在应用终止时把Web应用范围内的计数器数值保存在count.txt文件中。
public class MyServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
System.out.println("application is initialized.");
ServletContext context = sce.getServletContext();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResourceAsStream("/count/count.txt")));
int count = Integer.parseInt(reader.readLine());
reader.close();
Counter counter = new Counter(counter);
context.setAttribute("counter",counter);
} catch(IOException e) {
e.printStackTrace();
}
}
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("application is destyoyed.");
ServletContext context = sce.getServletContext();
Counter counter = (Counter)context.getAttribute("counter");
if (counter != null) {
try {
String filepath = context.getRealPath("/count");
filepath = filepath + "/count.txt";
PrintWriter pw = new PrintWriter(filepath);
pw.println(counter.getCounter());
pw.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
注册:
用户自定义的MyServletContextListener监听器只有先向Servlet容器注册,Servlet容器在启动或终止Web应用时,才会调用该监听器的相关方法。在web.xml中,<listener>元素用于向容器注册监听器。
<listener>
<listener-class>mypack.MyServletContextListener<listener-class/>
</listener>
2、拦截器
详见《JavaEE的颠覆者 SpringBoot实战》4.4.2
拦截器(Interceptor)实现对每一个请求处理前后进行相关的业务处理,类似于Servlet的Filter。
可以让普通的Bean实现HandlerInterceptor接口或继承HandlerInterceptorAdapter类来实现自定义拦截器。
应用举例:
计算每一次请求的处理时间
public class DemoInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {
Long startTime = System.currentTimeMillis();
request.setSttribute("startTime",startTime);
return true;
}
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView) throws Exception {
Long startTime = (Long) request.getAttribute("startTime");
request.removeAttribute("startTime");
Long endTime = System.currentTimeMillis();
System.out.println("本次请求处理时间为:"+new Long(endTime-startTime)+"ms");
request.setSttribute("handlingTime",endTime-startTime);
}
}
注册:
@Configuration
@EnableWebMvc
@ComponentScan("com.wisely.highlight")
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public DemoOnterceptor demoOnterceptor() {
return new DemoInterceptor();
}
@Overrice
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoOnterceptor());
}
}
3、过滤器
详见《Tomcat与Java Web开发技术详解》第20章
在一个Web应用中,每个Web组件都用于响应特定的客户请求。在这些Web组件响应客户请求的过程中,可能都会完成一些相同的操作,如检测客户IP地址是否位于预定义的拒绝IP地址范围内。
为了避免重复编码,各个Web组件中的相同操作可以放到同一个过滤器中来完成。
- 过滤器能够在Web组件被调用前检查ServletRequest对象,修改请求头和请求正文的内容,或对请求进行预处理
- 过滤器能够在Web组件被调用之后检查ServletResponse对象,修改响应头和响应正文。
过滤器负责过滤的Web组件可以是Servlet、JSP或HTML文件。其过滤过程如图:
过滤器具有以下特点:
- 可以检查ServletRequest和ServletResponse对象,并利用ServletRequestWrapper和ServletResponseWrapper包装类来修改ServletRequest和ServletResponse对象
- 可以在web.xml中为过滤器映射特定的URL。当客户请求访问此URL时,Servlet容器就会先触发过滤器工作。
- 所有实现Java Servlet 2.3规范及其以上版本的Servlet容器都支持过滤器。
- 多个过滤器可以被串联在一起,协同为Web组件过滤请求对象和响应对象。
过滤器创建:
所有自定义实现的过滤器类都必须实现javax.servlet.Filter接口,该接口中含有以下3个过滤器类必须实现的方法:
- init(FilterConfig config) : 过滤器的初始化方法。在Web应用启动时,Servlet容器先加载过滤器类,创建包含了过滤器配置信息的FilterConfig对象,然后创建Filter对象,接着调用Filter对象的 init(FilterConfig config)方法,在该方法中可通过config参数读取web.xml文件中为过滤器配置的初始化参数。
- doFilter(ServletRequest req,ServletResponse res,FilterChain chain):实际的过来操作。当客户请求访问的URL与为过滤器映射的URL匹配时,Servlet容器将先调用过滤器的 doFilter()方法。FilterChain参数用于访问后续过滤器或者Web组件。
- destroy():Servlet容器在销毁过滤器对象前调用该方法,在该方法中可以释放过滤器占用的资源。
应用举例:
NoteFilter过滤器为NoteServlet(表示留言簿)提供以下过滤功能:
- 判断客户IP地址是否在预定义的拒绝IP地址范围内,若在,直接返回拒绝信息
- 判断username请求参数表示的姓名是否位于预定义的黑名单中,若在,直接返回拒绝信息
- 将NoteServlet响应客户请求所花的时间写入日志
public class NoteFilter implements Filter {
private FilterConfig config = null;
private String blackList = null;
private String ipblock = null;
public void init(FilterConfig config) throws ServletException {
System.out.println("NoteFilter:init()");
this.config = config;
ipblock = config.getInitParameter("ipblock");
blackList = config.getInitParameter("blackList");
}
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException {
System.out.println("NoteFilter:doFilter()");
if (!checkRemoteIP(request,response)) return;
if (!checkUsername(request,response)) return;
long before = System.currentTimeMillis();
config.getServletContext().log("NoteFilter:before call chain.doFilter()");
chain.doFilter(request,response);
config.getServletContext().log("NoteFilter:after call chain.doFilter()");
String name = "";
if(request instanceof HttpServletRequest) {
name = ((HttpServletRequest)request).getRequestURI();
}
config.getServletContext().log("NoteFilter:"+name+":"+(after-before)+"ms");
}
private boolean checkRemote(ServletRequest request,ServletResponse response) throws IOException,ServletException{
String addr = request.getRemoteAddr();
if (addr.indexOf(ipblock)==0) {
response.setContentType("text/html;charset=GB2312");
PrintWriter out = response.getWriter();
out.println("<h1>对不起,服务器无法为你提供服务。</h1>");
out.flush();
return false;
} else {
return true;
}
}
private boolean checkUsername(ServletRequest request,ServletResponse response) throws IOException,ServletException{
String username = ((HttpServletRequest)request).getParameter("username");
if (username != null)
username = new String(username.getBytes("ISO-8859-1"),"GB2312");
if (username != null && username.indexOf(blackList)!=-1) {
response.setContentType("text/html;charset=GB2312");
PrintWriter out = response.getWriter();
out.println("<h1>对不起,"+username+",你没有权限留言</h1>");
out.flush();
return false;
} else {
return true;
}
}
public void destroy() {
System.out.println("NoteFilter:destroy()");
config = null;
}
}
工作流程:
发布
在发布过滤器时,必须在web.xml文件中加入<filter>元素和<filter-mapping>元素。
<filter>
<filter-name>NoteFilter</filter-name>
<filter-class>mypack.NoteFilter</filter-class>
<init-param>
<param-name>ipblock</param-name>
<param-value>221.45<param-value>
</init-param>
<init-param>
<param-name>blacklist</param-name>
<param-value>捣蛋鬼<param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>NoteFilter</filter-name>
<url-pattern>/note</url-pattern>
</filter-mapping>
注意:
1、如果希望过滤器能为所有的URL过滤,可以把<url-pattern>的值设为“/*”
2、在web.xml文件中,必须先配置所有过滤器,再配置Servlet
串联过滤器!!
详见《Tomcat与Java Web开发技术详解》20.4节!!
4、过滤器,拦截器,监听器的区别
https://www.cnblogs.com/lukelook/p/11079113.html
另附待看:过滤器、监听器、拦截器的区别