Chapter 12. 过滤器 Filter 与监听器(Listener)
12.1 过滤器概述
过滤器,能够对一部分客户请求先进行预处理操作,然后再把请求转发给响应的 Web 组件,等到 Web 组件生成了响应结果后,过滤器还能对响应结果进行检查和修改,然后再把修改后的响应结果发送给客户。
各个 Web 组件中的相同操作可放到同一个过滤器中来完成,这样即能减少重复编码。
具体来说,过滤器为 Web 组件(如 Servlet、JSP 或 HTML)提供如下过滤功能:
- 在 Web 组件被调用之前检查 ServletRequest 对象,修改请求头和请求正文的内容,或者对请求进行预处理操作。
- 在 Web 组件被调用之后检查 ServletResponse 对象,修改响应头和响应正文。
12.2 Filter 快速入门
所有自定义的过滤器必须实现 javax.servlet.Filter 接口,该接口含三个过滤器类必须实现的方法,见下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @WebFilter("/*") public class FilterDemo1 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { }
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } }
|
注意,两次启动原因:Tomcat 配置里面勾选了 “after launch” 选项,导致在服务器启动,自动访问了一次 index.jsp
当然,过滤器映射的 URL ,可在 web\WEB-INF 目录下的 web.xml 文档中配置:
在 web.xml 文件中,必须先配置所有过滤器,再配置 Servlet 。
1 2 3 4 5 6 7 8 9
| <filter> <filter-name>demo1</filter-name> <filter-class>cn.itcast.web.filter.FilterDemo1</filter-class> </filter> <filter-mapping> <filter-name>demo1</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
12.3 过滤器生命周期
过滤器由 Servlet 容器创建,在它的生命周期中包含以下三个阶段:
- 初始化阶段:当 Web 应用启动时,Servlet 容器会加载过滤器类,创建过滤器配置对象(
FilterConfig)和过滤器对象,并调用过滤器对象的 init(FilterConfig config) 方法 - 运行时阶段:当客户请求访问的 URL 与为过滤器映射的 URL 匹配时,Servlet 容器将先调用过滤器的
doFilter 方法。 - 销毁阶段:当 Web 应用终止时,Servlet 容器将先调用过滤器对象的
destroy() 方法,然后销毁过滤器对象。
12.4 过滤器的相关拦截配置
12.4.1 拦截路径配置
- 具体资源路径:
/index.jsp ,只有访问 index.jsp 资源时,过滤器才会被执行。 - 拦截目录:
/user/* ,访问 /user 下的所有资源时,过滤器都会被执行。 - 后缀名拦截:
*.jsp,访问所有后缀名为 jsp 资源时,过滤器都会被执行。 - 拦截所有资源:
/*,访问所有资源时,过滤器都会被执行。
12.4.2 拦截方式配置
即以什么方式访问资源时会拦截。
- 注解配置:设置
dispatcherTypes 属性:REQUEST:默认值,浏览器直接请求资源FORWARD:转发访问资源INCLUDE:包含访问资源ERROR:错误跳转资源ASYNC:异步访问资源
web.xml 配置:设置 <dispatcher></dispatcher> 标签即可。
12.5 过滤器链
过滤器优先级问题:
- 注解配置:按照类名的字符串字典序比较,字典序小的先执行。
web.xml 配置:<filter-mapping> 谁定义在上边,谁先执行。
假设过滤器 A的优先级高于过滤器 B,则执行顺序如下,类似于栈的后进先出
1 2 3 4 5
| 1. 过滤器A 2. 过滤器B 3. 资源执行 4. 过滤器B 5. 过滤器A
|
案例演示:登陆验证
需求
必须要确保用户已经登陆,才能访问项目的其他资源。
若不进行验证,但用户知道某些资源的路径,便能够直接越过登陆进行访问了。
若用户:
- 已经登陆,则直接放行。
- 没有登陆,强制跳转到登陆页面,并提示“您尚未登陆,请先登陆”
代码示例
通过下面的过滤器代码,能够体现过滤器的作用。同时,也进一步体会 Session 跟踪客户状态的作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @WebFilter("/*") public class LoginFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) req; String uri = request.getRequestURI(); if(uri.contains("/login.jsp") || uri.contains("/loginServlet") || uri.contains("/css/") || uri.contains("/js/") || uri.contains("/fonts/") || uri.contains("/checkCodeServlet")){ chain.doFilter(req, resp); } else { Object user = request.getSession().getAttribute("user"); if(user != null){ chain.doFilter(req, resp); } else { request.setAttribute("login_msg", "您尚未登陆,请先登陆!"); request.getRequestDispatcher("/login.jsp").forward(request, resp); } } } public void init(FilterConfig config) throws ServletException { } }
|
12.6 动态代理
12.6.1 相关概念
- 设计模式:一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,是对面向对象设计中反复出现的问题的解决方案。
- 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作,达到增强真实角色功能的目的。
关于代理,分两种实现方式:
- 静态代理:有一个类文件描述代理模式
- 动态代理:在内存中形成代理类
12.6.2 动态代理的实现步骤
- 代理对象和真实对象实现相同的接口;
- 代理对象 =
Proxy.newProxyInstance() - 使用代理对象调用真实对象的方法
- 增强真实对象的方法,分三种增强:
具体见下面代码。
案例演示:敏感词屏蔽
需求:对新录入的数据进行敏感词过滤,使得敏感词替换为 *** 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| @WebFilter("/*") public class SensitiveWordsFilter implements Filter { public List<String> list = new ArrayList<String>();
@Override public void init(FilterConfig filterConfig) throws ServletException { try { ServletContext servletContext = filterConfig.getServletContext(); String realPath = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt"); BufferedReader br = new BufferedReader(new FileReader(realPath)); String line = null; while((line = br.readLine()) != null){ list.add(line); } br.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
@Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("getParameter")){ String parameter = (String) method.invoke(req, args); if(parameter != null){ for(String sensitive_str : list){ if(parameter.contains(sensitive_str)) parameter = parameter.replaceAll(sensitive_str, "***"); } } return parameter; } return method.invoke(req, args); } }); chain.doFilter(proxy_req, resp); }
@Override public void destroy() { } }
|
12.7 监听器 Listener
12.7.1 事件监听机制
- 事件
- 事件源:事件发生的地方
- 监听器:一个对象
- 注册监听:将事件、事件源、监听器绑定在一起。当事件源上发生某个事件后,执行监听器代码。
12.7.2 ServletContextListener
ServletContextListener 监听 ServletContext 对象的创建和销毁
方法
void contextDestroyed(ServletContextEvent sce):ServletContext 对象被销毁之前会调用该方法void contextInitialized(ServletContextEvent sce):ServletContext 对象创建后会调用该方法
步骤
定义一个类,实现 ServletContextListener 接口
覆写上述两个方法
配置方式:
方式一——web.xml 配置
1 2 3 4 5
| <listener> <listener-class> cn.itcast.web.listener.ContextLoaderListener </listener-class> </listener>
|
方式二——注解:@WebListener