Filter&Listener
目录
Filter:过滤器
概念:
- 生活中的过滤器:净水器,空气净化器,土匪
- web 中的过滤器,当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能.
- 过滤器的作用:
一般用于完成通用的操作.如:登陆验证,统一编码处理,敏感字符过滤...
快速入门
步骤
- 定义一个类,实现接口 filter
- 复写方法
- 配置拦截路径
- web.xml
- 注解
@WebFilter("/*") //访问所有资源之前,都会执行该过滤器
public class FilterDemo1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("doFilter");
//是否放行 --放行
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
过滤器细节
web.xml 配置
<filter>
<filter-name>demo2</filter-name>
<filter-class>com.zhiyou100.filter.FilterDemo2</filter-class>
</filter>
<filter-mapping>
<filter-name>demo2</filter-name>
<!--访问所有的资源都会执行 demo2的 filter-->
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器的执行流程
- 执行过滤器
- 执行放行后的资源
- 回来执行过滤器放行代码下边的代码
过滤器声明周期方法
-
init(FilterConfig); 在服务器启动后,会创建 filter对象,然后调用 init方法。只执行一次。用于加载资源
-
doFilter(SerlvetRequest, ServletResponse, FilterChain); 每个请求进来的时候这个方法都会被调用,并在Servlet的service方法执行之前。而FilterChain就代表当前的整个请求链,所以通过调用FilterChain的doFilter方法可以继续将请求传递下去。如果想拦截这个请求,可以不调用FilterChain的doFilter,那么这个请求就返回了,所以Filter是一种责任链模式。执行多次、
-
destroy(); 在服务器关闭后,filter对象被销毁,如果服务器时正常关闭,则会执行 destroy方法。只执行一次。用于释放资源
- Filter类的核心还是doFilter中传递的FilterChain对象,这个对象保存了到最终的Servlet对象的所有Filter对象,这些对象都保存在ApplicationFilterChain对象的filter数组中。在FilterChain链上每执行一个Filter对象,数组的当前计数都会加1,直到计数等于数组的长度,当FilterChain上的所有Filter对象都执行完之后,就会执行最终的Servlet。所以在ApplicationFilterChain对象中会持有Servlet对象的引用。
过滤器配置详解
- 拦截路径配置:
- 具体资源路径:/index.jsp 只有访问 index.jsp资源时,过滤器才会被执行
- 拦截目录:/user/* 访问 /user下的所有资源时,过滤器都会被执行
- 后缀名拦截器: *.jsp 访问所有后缀名为 jsp资源时,过滤器都会被执行
- 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
- 拦截方式配置:资源被访问的方式
- 注解配置:
- 设置 dispatcherTypes 属性 (他是个数组)
request:默认值.浏览器直接请去资源
forward:转发访问资源
include:包含访问资源
error:错误跳转资源
async:异步访问资源
- 设置 dispatcherTypes 属性 (他是个数组)
- web.xml 配置:
在 file-mapping标签中配置
<dispatcher></dispatcher>属性还是上面那五个
- 注解配置:
过滤器链(配置多个过滤器)
- 执行顺序:如果有两个过滤器:过滤器1,和过滤器2
过滤器1先执行
过滤器2
资源执行
过滤器2
过滤器1 - 过滤器先后顺序问题
- 注解配置:按照类名的字符串比较规则比较,较小的先执行
如:AFilter 和 BFilter,AFilter 先执行 - web.xml配置的:谁定义在上面,谁先执行
- 注解配置:按照类名的字符串比较规则比较,较小的先执行
案例
登陆验证
需求
- 访问 该项目的资源时,验证是否登陆
- 如果登陆了,则直接放行
- 如果没有登陆,则跳转到登陆页面,提示"你尚未登陆,请先登录"
步骤
- 判断是否是登陆相关的资源
是:直接放行
不是:判断是否登陆 - 判断当前用户是否登陆,判断 Session中是否有 User
有:已经登陆,放行
没有,没有登陆,跳转到登陆页面
@WebFilter("/*")
public class FilterLogin implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//强制转换
HttpServletRequest request = (HttpServletRequest) req;
//1. 获取资源请求路径
String uri = request.getRequestURI();
//2. 判断是否包含登陆相关资源路径,要注意排除掉 css、js、图片、验证码等资源
if (uri.contains("/login.jsp") || uri.contains("/loginServlet")
|| uri.contains("/css/") || uri.contains("/js/")
|| uri.contains("/fonts") || uri.contains("/checkServlet")) {
//包含,用户就是先登录,放行
chain.doFilter(req, resp);
} else {
// 不包含,需要验证用户是否登陆
//3. 从session中获取 user
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);
}
}
}
@Override
public void init(FilterConfig config) throws ServletException {
}
}
敏感词汇过滤
需求
- 对项目录入的数据进行敏感词汇过滤
- 敏感词汇:笨蛋 坏蛋
- 如果是敏感词汇,替换 **
步骤:
- filter 和 servlet 中的 request和response对象都是一样的
- 对 request对象的 getParameter方法进行增强.产生一个新的 request对象.
- 放行.将新的 request对象传入
chain.doFilter(req,resp) - 增强对象的功能:
设计模式:一些通用的解决问题的方式- 装饰模式
- 代理模式
- 概念:
真实对象:被代理的对象
代理对象:
代理模式:代理对象代理真实对象,达到增强真实对象功能的目的 - 实现方式
-
静态代理:
-
动态代理
实现步骤:- 代理对象和真实对象实现相同的接口
- 代理对象 = newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h);
- 使用代理对象调用方法.
- 增强方法
增强方法
- 增强参数列表
- 增强返回值类型
- 增强方法体执行逻辑
public interface SaleComputer { public String sale(double money); public void show(); }
public class Lenovo implements SaleComputer { @Override public String sale(double money) { System.out.println("花了" + money + "元,买了一台联想电脑"); return "联想电脑"; } @Override public void show() { System.out.println("展示电脑"); } }
public class ProxyTest { public static void main(String[] args) { SaleComputer lenovo = new Lenovo(); System.out.println(lenovo.sale(800)); System.out.println("~~~~~~~~~~~~~~~~~~~~"); /* * Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) * 三个参数: * 1. 类加载器,真实对象.getClass.getClassLoader() * 2. 接口数组:真实对象.getClass.getInterfaces() * 3. 处理器:new InvocationHandler() ==>重写 invoke(Object proxy,Method method,Object[] args) * */ SaleComputer proxyLenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader() , lenovo.getClass().getInterfaces(), (proxy, method, arguments) -> { /* * 代理逻辑编写的方法:代理对象调用的所有都会触发该方法执行 * invoke(Object proxy,Method method,Object[] args) * 参数 * 1. proxy 代理对象 * 2. method 代理对象调用的方法,被封装为的对象 * 3. args 代理对象调用方法时,传递的实际参数 * */ //判断是否是 sale方法 if (method.getName().equals("sale")) { //1. 增强参数 double money = (double) arguments[0]; money = money * 0.85; System.out.println("专车接你"); //使用真实对象调用该方法 //增强返回值类型 obj==字符串 Object obj = method.invoke(lenovo, money); System.out.println("免费送货"); return obj + "_鼠标垫"; } //使用真实对象调用该方法 return method.invoke(lenovo, arguments); }); System.out.println(proxyLenovo.sale(800)); } }
-
- 概念:
过滤器
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
//敏感词汇集合
private List<String> list = new ArrayList<>();
@Override
public void init(FilterConfig config) throws ServletException {
//1. 加载文件,->获取文件真实路径
ServletContext servletContext = config.getServletContext();
//敏感词汇.txt 在 idea中的是 src下的,但在 idea部署的是这个路径下
String path = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt");
//2. 读取文件
try (
BufferedReader bfr = new BufferedReader(new FileReader(path));
) {
//3. 将文件的每一行添加到list中
String line = null;
while ((line = bfr.readLine()) != null) {
list.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(list);
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//1. 创建代理对象,增强 getParameter 方法
ServletRequest proxyRequest = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(),
(proxy, method, args) -> {
//增强 getParameter方法
//判断是否是 getParameter方法
if (method.getName().equals("getParameter")) {
//增强返回值
//获取返回值
String value = (String) method.invoke(req, args);
//替换敏感词汇
if (value != null) {
for (String str : list) {
if (str.contains(value)) {
value = value.replaceAll(str, "**");
}
}
}
return value;
}
return method.invoke(req, args);
});
// 2. 放行
chain.doFilter(proxyRequest, resp);
}
@Override
public void destroy() {
}
}
控制器
@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String text = request.getParameter("text");
System.out.println(text);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
Listener:监听器
概念
- web的三大组件之一,
- 事件监听机制
事件:一件事情
事件源:事件发生的地方
监听器:一个对象
注册监听器:将事件,事件源,监听器绑定在一起,当事件源上发生某个事件后,执行监听器代码
- 事件监听机制
Listener的分类与使用
1. ServletContext监听器
- ServletContextListener:用于对Servlet整个上下文进行监听(创建,销毁)
public interface ServletContextListener extends EventListener { //上下文初始化 public default void contextInitialized(ServletContextEvent sce) { } //上下文销毁 public default void contextDestroyed(ServletContextEvent sce) { } }
- ServletContextEvent事件主要 api
public class ServletRequestEvent extends java.util.EventObject public ServletRequest getServletRequest() public ServletContext getServletContext()
- ServletContextEvent事件主要 api
- ServletContextAttributeListener,对Servlet上下文属性的监听(增删改查)
public interface ServletContextAttributeListener extends EventListener { //增加属性 public default void attributeAdded(ServletContextAttributeEvent scae) { } //属性删除 public default void attributeRemoved(ServletContextAttributeEvent scae) { } //属性替换(第二次设置同一个属性) public default void attributeReplaced(ServletContextAttributeEvent scae) { } }
- ServletContextAttributeEvent 常用方法
public String getName() 得到属性名称 public Object getValue() 取得属性的值
- ServletContextAttributeEvent 常用方法
2. Session监听
- Session属于 http协议的内容,接口位于 javax.sevlet.http.*包下。
- HttpSessionListener 接口,对 Session的整体状态的监听。
public interface HttpSessionListener extends EventListener { //session创建 public default void sessionCreated(HttpSessionEvent se) { } //session 销毁 public default void sessionDestroyed(HttpSessionEvent se) { } }
- HttpSessionEvent
public HttpSession getSession() 取得当前操作的 session
- HttpSessionEvent
- HttpSessionAttributeListener 接口,对 session的属性监听
public interface HttpSessionAttributeListener extends EventListener { //增加属性 public default void attributeAdded(HttpSessionBindingEvent se) { } //删除属性 public default void attributeRemoved(HttpSessionBindingEvent se) { } //替换属性 public default void attributeReplaced(HttpSessionBindingEvent se) { } }
- HttpSessionBindingEvent
public String getName();//取得属性的名称 public Object getValue();//取得属性的值 public HttpSession getSession();//取得当前的session
- HttpSessionBindingEvent
- session 的销毁有两种情况
- session超时,web.xml 配置
<session-config> <session-timeout>120</session-timeout><!--session120分钟后超时销毁--> </session-config>
- 手工使 session失效
public void invalidate();//使session失效方法。session.invalidate();
- session超时,web.xml 配置
3. Request 监听
- ServletRequestListener,用于对 Request请求进行监听(创建、销毁)
public interface ServletRequestListener extends EventListener { // request 初始化 public default void requestDestroyed (ServletRequestEvent sre) { } // request 销毁 public default void requestInitialized (ServletRequestEvent sre) { } }
- ServletRequestEvent事件
public ServletRequest getServletRequest();//取得一个ServletRequest对象 public ServletContext getServletContext();//取得一个ServletContext(application)对象
- ServletRequestEvent事件
- ServletRequestAttributeListener:对 request属性的监听(增删改查)
public interface ServletRequestAttributeListener extends EventListener { //增加属性 public default void attributeAdded(ServletRequestAttributeEvent srae) { } //属性删除 public default void attributeRemoved(ServletRequestAttributeEvent srae) { } 属性替换(第二次设置统一属性) public default void attributeReplaced(ServletRequestAttributeEvent srae) { } }
- ServletRequestAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称 public Object getValue();//取得属性的值
- ServletRequestAttributeEvent事件:能取得设置属性的名称与内容
4. 配置 监听器
- 在 web.xml 中配置
- Listener配置信息必须在 filter和 servlet 配置之前,listener的初始化(ServletContentListener初始化)比servlet和 filter都有限,而销毁比 servlet和 filter都慢
<listener> <listener-class>路径</listener-class> </listener>
- 注解
@WebListener()
案例HttpSessionAttributeListener 实现统计会员在线人数
@WebListener
public class Demo01AttributeListener implements HttpSessionAttributeListener {
@Override
//HttpSessionAttributeListener 实现 统计在线会员人数
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("Demo01SessionAttributeListener:::attributeAdded");
System.out.println("session对象:" + se.getSession().getId() + ":::添加属性:" + se.getName() + "=" + se.getValue());
//统计人数:需要被整个项目共享 装在servletcontext域中:::要求会员登录 在seesion域中添加属性user
if (se.getName().equals("user")) {//会员+1
Object count = se.getSession().getServletContext().getAttribute("count");//获取servletcontext域中的count属性
if (count == null) {//你是第一个登录者
se.getSession().getServletContext().setAttribute("count", 1);
} else {//原来的值+1
se.getSession().getServletContext().setAttribute("count", ((Integer) count) + 1);
}
}
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("Demo01SessionAttributeListener:::attributeRemoved");
System.out.println("session对象:" + se.getSession().getId() + ":::删除属性:" + se.getName() + "=" + se.getValue());
//统计人数:需要被整个项目共享 装在servletcontext域中:::要求会员登出:删除session中的域属性user
if (se.getName().equals("user")) {//会员+1
Object count = se.getSession().getServletContext().getAttribute("count");//获取servletcontext域中的count属性
//原来的值-1
se.getSession().getServletContext().setAttribute("count", ((Integer) count) - 1);
}
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("Demo01SessionAttributeListener:::attributeReplaced");
System.out.println("session对象:" + se.getSession().getId() + ":::修改属性:" + se.getName());
System.out.println("删除属性" + se.getName() + "旧值=" + se.getValue() + ",新值=" + se.getSession().getAttribute(se.getName()));
}
}
案例 ServletContextListerner 初始化指定文件
-
定义一个类,实现 ServletContextListener 接口
-
复写方法
-
配置
public class ListenerDemo2 implements ServletContextListener { /** * 监听 ServletContextListener 对象创建的。 * ServletContextListener 对象服务器启动后自动创建 * 在服务器启动后自动调用 * * @param sce */ @Override public void contextInitialized(ServletContextEvent sce) { //加载资源文件 // //1. 获取 ServletContext对象 ServletContext servletContext = sce.getServletContext(); //2. 加载资源文件 String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation"); //3. 获取真实路径 String realPath = servletContext.getRealPath(contextConfigLocation); //4. 加载进内存 try { FileInputStream fis = new FileInputStream(realPath); System.out.println("fis = " + fis); } catch (IOException e) { e.printStackTrace(); } System.out.println("ServletContextListener 对象被创建了"); } /** * 在服务器关闭后,ServletContextListener 对象被销毁。当服务器正常关闭后该方法被调用 * * @param sce */ @Override public void contextDestroyed(ServletContextEvent sce) { // System.out.println("ServletContextListener 被销毁了"); } }
web.xml 下 <listener> <listener-class>com.cainiao.Listener.ListenerDemo2</listener-class> </listener> <!--指定初始化参数--> <context-param> <param-name>contextConfigLocation</param-name> <!--因为创建的是普通的 web项目,在idea中的位置是 src下的, 其实看的是idea的out下的目录 --> <param-value>WEB-INF/classes/applicationContext.xml</param-value> </context-param>