一、过滤器
1.过滤器介绍
过滤器的英文名称为Filter,是Servlet技术中最实用的技术。如同它的名字一样,过滤器是处于客户端与服务器资源文件之间的一道过滤网,帮助我们过滤一些不符合要求的请求。通常它被用作 Session校验,判断用户权限,如果不符合设定条件,就会被拦截到特殊的地址或者给予特殊的响应。
2.Filter生命周期
使用过滤器很简单,只需要实现Filter类,然后重写它的3个方法即可。
- init方法:程序启动调用Filter的init()方法(永远只调用一次);在容器中创建当前过滤器的时候自动调用这个方法。
- destory方法:程序停止调用Filter的destroy()方法(永远只调用一次);在容器中销毁当前过滤器的时候自动调用这个方法。
- doFilter方法:doFilter()方法每次的访问请求如果符合拦截条件都会调用(程序第一次运行,会在servlet调用init()方法以后调用;不管第几次,都在调用doGet(),doPost()方法之前)。这个方法有3个参数,分别是ServletRequest、ServletResponse和FilterChain可以从参数中获取HttpServletReguest和HttpServletResponse对象进行相应的处理操作。
4、注解方式实现过滤器(@WebFilter)
@WebFilter
@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性 ( 以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值 )
![在这里插入图片描述](https://img-blog.csdnimg.cn/da9a54dea1e144f794194e60f4ceddb7.png)
@Order(1)
标识当前过滤器的执行顺序,值越大越靠前执行;
@ServletComponentScan
只有在springboot 启动类添加该注解时@WebFilter注解才会生效
SpringBootApplication 上使用 @ServletComponentScan 注解后
- Servlet可以直接通过@WebServlet注解自动注册
- Filter可以直接通过@WebFilter注解自动注册
- Listener可以直接通过@WebListener 注解自动注册
启动类代码
@SpringBootApplication
@ServletComponentScan
public class ApplicationStarter {
public static void main(String[] args) {
SpringApplication.run(ApplicationStarter.class,args);
}
}
Filter代码
package com.buba.filter;
import com.alibaba.fastjson.JSONObject;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author qlx
*/
@WebFilter(urlPatterns = "/test/*", filterName = "testFilter")
@Order(1)
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter");
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader("token");
System.out.println("token");
//该方法执行后直接运行至下一个过滤器
if(token!=null){
filterChain.doFilter(servletRequest, servletResponse);
}else{
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("application/json; charset=utf-8");
PrintWriter out = servletResponse.getWriter();
JSONObject res = new JSONObject();
res.put("msg", "错误");
res.put("success", "false");
out.append(res.toString());
}
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
5、直接注入到spring中
Filter代码
package com.buba.filter;
import com.alibaba.fastjson.JSONObject;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author qlx
*/
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter");
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader("token");
System.out.println("token");
//该方法执行后直接运行至下一个过滤器
if(token!=null){
filterChain.doFilter(servletRequest, servletResponse);
}else{
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("application/json; charset=utf-8");
PrintWriter out = servletResponse.getWriter();
JSONObject res = new JSONObject();
res.put("msg", "错误");
res.put("success", "false");
out.append(res.toString());
}
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
Filter配置类代码
package com.buba.config;
import com.buba.filter.TestFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public TestFilter myFilter() {
return new TestFilter();
}
@Bean
public FilterRegistrationBean getFilterRegistrationBean(TestFilter myFilter) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(myFilter);
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/test/*");
filterRegistrationBean.setName("myFilter");
return filterRegistrationBean;
}
}
二、拦截器
1.拦截器介绍
拦截器(Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程——AOP 的具体实现(AOP切面编程只是一种编程思想而已)。
你可以使用 Interceptor 来执行某些任务,例如在 Controller 处理请求之前编写日志,添加或更新配置…
在 Spring中,当请求发送到 Controller 时,在被Controller处理之前,它必须经过 Interceptors(0或多个)。
Spring Interceptor是一个非常类似于Servlet Filter 的概念 。
Java中的拦截器是动态拦截 action调用的对象,然后提供了可以在action执行前后增加一些
操作,也可以在action执行前停止操作。其实拦截器也可以做和过滤器同样的操作,以下是拦截器的常用场景。
- 登录认证:在一些简单应用中,可能会通过拦截器来验证用户的登录状态,如果没有登录或者登录失效,就会给用户一个友好的提示或者返回登录页面。
- 记录系统日志:在Web应用中,通常需要记录用户的请求信息,比如请求的IP、方法执行时
常等,通过这些记录可以监控系统的状况,以便于对系统进行信息监控、信息统计、计算PV (PageView)和性能调优等。
- 通用处理:在应用程序中可能存在所有方法都要返回的信息,这时可以使用拦截器来实现 省去每个方法冗余重复的代码实现。
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
- 权限检查:如登录检测,进入处理器检测是否登录;
- 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如 Apache
也可以自动记录)
- 通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现。
2.拦截器生命周期
这里以使用Spring拦截器为例,在类上需要实现HandlerInterceptor类,并且重写类中的3个方法,分别是:
-
opreHandle在业务处理器处理请求之前被调用,方法在请求处理之前被调用。该方法在 Interceptor 类中最先执行,用来进行一些前置初始化操作或是对当前请求做预处理,也可以进行一些判断来决定请求是否要继续进行下去。该方法的返回至是 Boolean 类型,当它返回 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当它返回为 true 时会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会调用当前请求的 Controller 方法。
-
postHandle在业务处理器处理请求执行完成后、生成视图前执行。方法在当前请求处理完成之后,也就是 Controller 方法调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。
-
afterCompletion在DispatcherServlet完全处理请求后被调用,通常用于记录消耗时间,也可
以进行一些资源处理操作。方法需要在当前对应的 Interceptor 类的 postHandler 方法返回值为 true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。此方法主要用来进行资源清理。
3.自定义拦截器案例——性能监控
自定义拦截器有一下两种方式:
- 实现 org.springframework.web.servlet.HandlerInterceptor接口
- 继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类
如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如 apache 都具有这个功能,但此处我们演示一下使用拦截器怎么实现。
3.1实现分析:
1、在进入处理器之前记录开始时间,即在拦截器的 preHandle 记录开始时间;
2、在结束请求处理之后记录结束时间,即在拦截器的 afterCompletion 记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。
3、在测试时需要把 stopWatchHandlerInterceptor 放在拦截器链的第一个,这样得到的时间才是比较准确的。
3.2问题:
我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?
3.3解决方案
解决方案是使用 ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个 ThreadLocal,A线程的ThreadLocal 只能看到A线程的 ThreadLocal,不能看到B线程的 ThreadLocal)。
3.4拦截器代码实现:
package com.buba.intercepter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author qlx
*/
public class MyIntercepter implements HandlerInterceptor {
//NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。
private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime");
private Logger logger = LoggerFactory.getLogger(MyIntercepter.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long beginTime = System.currentTimeMillis();//1、开始时间
startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
return true;//继续流程
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long endTime = System.currentTimeMillis();//2、结束时间
long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
long consumeTime = endTime - beginTime;//3、消耗的时间
if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
//TODO 记录到日志文件
logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
} else {
// 测试的时候由于请求时间未超过500,所以启用该代码
logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
}
}
}
3.5拦截器配置类代码实现
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor();
registry.addInterceptor(new MyIntercepter()).addPathPatterns("/test/*");
}
}
三、拦截器和过滤器的区别
1、触发时机
过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/6622011abc0046568effc716429fcefb.png)
2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行
拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。
![在这里插入图片描述](https://img-blog.csdnimg.cn/cf2d478f54a34e60b14be640acfca8f2.png)
其中第2步,SpringMVC的机制是由DispaterServlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的.
3、底层实现原理
过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射,代理分静态代理和动态代理,动态代理是拦截器的简单实现。
4、何时使用拦截器?何时使用过滤器?
如果是非spring项目,那么拦截器不能用,只能使用过滤器。
如果是处理controller前后,既可以使用拦截器也可以使用过滤器。
四、监听器
1.监听器介绍
监听器通常用于监听Web应用中对象的创建、销毁等动作的发生,同时对监听的情况做出相应的处理,最常用于统计网站的在线人数、访问量等信息。
监听器大致分为以下几种。
- ServletContextListener:用来监听ServletContext属性的操作,比如新增、修改、删除。
- HttpSessionListener:用来监听Web应用中的Session对象,通常用于统计在线情况。
- ServletRequestListener:用来监听Request对象的属性操作。
2.使用监听器案例1——
使用监听器的话,只需要在类中实现对应功能的监听器对象,如本文使用的 HttpSessionListener。下面以监听Session信息为例统计在线人数。新建一个MyHttpSessionListener类,实现HttpSessionListener类,在类中定义一个全局变量online,当创建Session时,online的数量加1;当销毁Session时,online的数量减1。MyHttpSessionListener完整内容如下:
3.使用监听器案例2——自定义事件触发监听器
在实际项目中,我们往往需要自定义一些事件和监听器来满足业务场景,比如在微服务中会有这样的场景:微服务 A 在处理完某个逻辑之后,需要通知微服务 B 去处理另一个逻辑,或者微服务 A 处理完某个逻辑之后,需要将数据同步到微服务 B。这种场景非常普遍,这时我们可以自定义事件以及监听器来监听,一旦监听到微服务 A 中的某事件发生,就去通知微服务 B 处理对应的逻辑。
3.1自定义事件
自定义事件需要继承 ApplicationEvent 对象,在事件中定义一个 User 对象来模拟数据,构造方法中将 User 对象传进来初始化。如下:
package com.buba.event;
import com.buba.bean.User;
import lombok.Data;
import org.springframework.context.ApplicationEvent;
/**
* @author qlx
*/
public class UserEvent extends ApplicationEvent {
private User user;
public UserEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
3.2自定义监听器
package com.buba.listener;
import com.buba.bean.User;
import com.buba.event.UserEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* @author qlx
*/
@Component
public class MyEventListener implements ApplicationListener<UserEvent> {
@Override
public void onApplicationEvent(UserEvent event) {
// 把事件中的信息获取到
User user = event.getUser();
// 处理事件,实际项目中可以通知别的微服务或者处理其他逻辑等
System.out.println("用户名:" + user.getName());
System.out.println("age:" + user.getAge());
}
}
3.3触发逻辑
@Resource
private ApplicationContext applicationContext;
@GetMapping("/listener")
public void getUser2() {
User user = new User();
user.setName("zhangsan");
user.setAge(18);
// 发布事件
UserEvent event = new UserEvent(this, user);
applicationContext.publishEvent(event);
}