目录
构建两个实体类。user存储用户信息,student学生类用于验证登录后的操作
一、uuid生成token存储到redis中
1.1、pojo
构建两个实体类。user存储用户信息,student学生类用于验证登录后的操作
student
@Data
public class Student implements Serializable {
private static final long serialVersionUID = 125236L;
private int id;
private String name;
private String sex;
}
user
@Data
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String username;
private String password;
}
1.2、控制层controller
@RestController
@RequestMapping("student")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("findAll")
public Result findAll(){
return Result.success(studentService.findAll());
}
@GetMapping("/{id}")
public Result findById(@PathVariable int id){
return Result.success(studentService.findById(id));
}
}
@RestController
@RequestMapping("student")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("findAll")
// 在过滤器或者拦截器验证是否带token(是否登录)登录后有token保存到请求头里面
// 这样直接在方法里面写,太麻烦耦合度高,不利于维护
public Result findAll(/*@RequestHeader(required = false) String token*/){
// if(token == null){
// return Result.failed("token不能为空");
// }
return Result.success(studentService.findAll());
}
@GetMapping("/{id}")
public Result findById(@PathVariable Integer id){
return Result.success(studentService.findById(id));
}
}
1.3、service和impl
user
public interface UserService {
String login(User user);
}
@Slf4j
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private JwtUtils jwtUtils;
@Override
public String login(User user) {
if (!StringUtils.hasText(user.getUsername())){
throw new RuntimeException("账号不能为空");
}
if (!StringUtils.hasText(user.getPassword())){
throw new RuntimeException("密码不能为空");
}
User user1 = userMapper.login(user.getUsername());
log.info(user1.toString());
if(ObjectUtils.isEmpty(user1) ){
throw new RuntimeException("账号不存在");
}
if (!user1.getPassword().equals(user.getPassword())){
throw new RuntimeException("密码错误");
}
// token 用户信息id
// token(key) id(value)
// key:123456
String token = "NEKOT"+UUID.randomUUID().toString().replace("-", "");
// 存到redis里面
redisTemplate.opsForValue().set(token,user1.getId());
redisTemplate.expire(token,30, TimeUnit.SECONDS);
}
}
student
public interface StudentService {
List<Student> findAll();
Student findById(Integer id);
}
@Service("studentService")
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public List<Student> findAll() {
List<Student> students = studentMapper.findAll();
if(students==null||students.size()==0){
throw new RuntimeException("暂无数据");
}
return students;
}
@Override
public Student findById(Integer id) {
return studentMapper.findById(id);
}
}
1.4、Mapper层
public interface StudentMapper {
@Select("select * from student ")
List<Student> findAll();
@Select("select * from student where id = #{id}")
Student findById(Integer id);
}
public interface UserMapper {
@Select("select * from user where username = #{userName}")
User login(String userName);
}
二、 过滤器的使用
概念:过滤器(Filter)主要用于拦截和处理进入Web应用程序的请求或从应用程序发出的响应。它们提供了一种灵活的、可插拔的方式来扩展或修改应用程序的行为,而无需修改应用程序的核心代码。
主要作用:
-
预处理请求:在请求到达目标资源(如Servlet或JSP)之前,过滤器可以对请求进行预处理。这包括验证用户身份(登录验证)、清理或修改请求参数、添加请求头、执行安全检查(如权限验证)、记录日志等。
-
后处理响应:过滤器可以在响应从服务器发回客户端之前对其进行处理。这可以涉及修改响应头、压缩响应体、记录响应时间、加密响应内容等操作。
-
统一处理:过滤器可以用来实现跨多个Servlet或JSP的通用行为,如统一设置字符编码、处理会话管理、实施站点范围内的错误处理策略等。
-
性能监控:通过测量请求和响应的时间,过滤器可以用于性能监控和分析,帮助识别和优化应用中的瓶颈。
-
安全控制:实现诸如跨站脚本(XSS)和SQL注入防护的安全措施,以及实施内容安全策略(CSP)等。
-
数据转换:对请求或响应的数据进行格式转换,比如将请求数据从一种格式转换为另一种格式,或者将响应数据压缩以减少网络传输量。
-
资源访问控制:根据请求的来源、用户角色或其他条件,控制对特定资源的访问权限。
-
日志和审计:记录详细的请求和响应日志,便于追踪和审计
在Java Web应用程序中,过滤器通过实现
javax.servlet.Filter
接口并配置在web.xml
文件中来定义和使用。从Java EE 7开始,还可以使用注解(如@WebFilter
)来声明过滤器,从而简化配置。过滤器的执行链由容器管理,开发者可以通过配置来决定过滤器应用的范围和顺序。
前文代码:检查用户是否登录,已登录的用户,请求头中带有token,token会保存在浏览器中,通过检验web请求是否带token就可以判断用户登录与否。但是web请求有很多,一个一个的写太麻烦,为了提升效率并减少代码重复,可以将用户登录状态的检查逻辑整合进过滤器(Filter)或拦截器(Interceptor)。这样,每次Web请求时,系统会自动通过这些组件来检验请求头中是否携带了合法的token,从而判断用户是否已经登录,而无需在每个单独的请求处理方法中重复此操作。这样做不仅让代码结构更清晰,也便于日后的维护与扩展。
对比
未使用过滤器或者拦截器----几个接口带几个String token参数
@GetMapping("findAll")
public Result findAll(@RequestHeader(required = false) String token){
if(token == null){
return Result.failed("token不能为空");
}
return Result.success(studentService.findAll());
}
使用过滤器--与springboot相整合
@Component
@WebFilter("/**")
public class TokenFilter implements Filter {
// 1. 游客接口:登录、注册
// 2. 认证接口:获取用户信息、修改用户信息、删除用户信息
// 3. 游客/认证接口: 获取课程信息、获取课程列表
// 4. 授权接口: 添加课程、删除课程、修改课程
private final RedisTemplate redisTemplate;
public TokenFilter(@Qualifier("redisTemplate") RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 将servletRequest转换为HttpServletRequest获取请求的信息
HttpServletRequest request = (HttpServletRequest) servletRequest;
request.setCharacterEncoding("utf-8");
//设置白名单
if(request.getRequestURI().contains("login")){
filterChain.doFilter(servletRequest, servletResponse);
return;
}
if (request.getHeader("token") == null) {
throw new RuntimeException("token is null");
}
// 获取请求头中的token
String token = request.getHeader("token");
// 在缓存中查询出userId
Integer userId = (Integer) redisTemplate.opsForValue().get(token);
System.out.println(userId+"filter-----");
if(userId == null){
throw new RuntimeException("token 已经过期--filter");
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
缺点:throw new RuntimeException("token is null");不能被errorController捕获,这与拦截器的实现机制有关。在springMVC核心控制器DispatcherServlet之前执行由于拦截器的执行时机位于DispatcherServlet分发请求之前,这意味着该异常不会被常规的Spring MVC异常处理器(如@ExceptionHandler
注解的方法或配置的ErrorController
)所捕获和处理。DispatcherServlet都没执行,没有分发请求到controller,所以不能捕获。
三、拦截器的使用(建议使用)
概念:拦截器(Interceptor)是一种设计模式,主要用于在业务处理过程中的特定点插入额外的操作,而无需修改核心业务逻辑。
拦截器的主要作用:
-
权限验证:在请求处理之前,拦截器可以检查用户是否拥有访问特定资源的权限,这是实现安全控制的重要手段。
-
日志记录:自动记录请求和响应的日志信息,帮助进行系统监控、故障排查和性能分析。
-
事务管理:自动开启和提交或回滚数据库事务,确保数据的一致性。
-
参数校验:在方法执行前对请求参数进行校验,确保数据的有效性和安全性。
-
性能监控:记录请求处理时间,监控系统性能,帮助识别性能瓶颈。
-
数据转换:在请求到达控制器之前或响应发送给客户端之前,转换数据格式,如将请求参数转换为业务对象,或把对象转换为JSON等。
-
重复提交预防:防止表单的重复提交,保证数据的唯一性和准确性。
-
异常处理:统一处理业务逻辑中的异常,提供一致的错误响应。
-
资源清理:在请求处理完毕后执行资源清理工作,如关闭数据库连接、释放资源等。
-
AOP(面向切面编程)实现:拦截器是实现AOP的一种方式,允许开发者定义横切关注点(如日志、安全等),并将这些关注点插入到核心业务逻辑中,而无需修改业务逻辑代码。
拦截器通常与框架如Spring MVC、Struts2等集成使用,通过配置可以灵活地指定哪些方法或类的调用需要经过拦截处理。拦截器的设计遵循“链条”模式,即可以有一系列的拦截器按顺序执行,每个拦截器都有机会在请求处理前后添加自己的处理逻辑。
四、JWT令牌生成token
4.1 介绍
JWT(json web token),它并不是一个具体的技术实现,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
JWT规定了数据传输的结构,一串完整的JWT由三段落组成,每个段落用英文句号连接(.)连接,他们分别是:Header、Payload、Signature,所以,常规的JWT内容格式是这样的:AAA.BBB.CCC
并且,这一串内容会base64加密;也就是说base64解码就可以看到实际传输的内容。接下来解释一下这些内容都有什么作用。
4.2 Jwt组成
头部(Header)(非敏感)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{"alg":"HS256"}
在头部指明了签名算法是HS256算法。 我们进行BASE64编码https://jwt.io/#debugger-io,编码后的字符串如下:
eyJhbGciOiJIUzI1NiJ9
载荷(playload)(非敏感数据)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
载荷就是存放有效信息的地方。该部分的信息是可以自定义的
定义一个payload:
{"jti":"ae412ba0-ac1a-426d-9460-0cee02bdd2b9","sub":"zhangsan","exp":1683354850,"iat":1683354250,"username":"zhangsan","uid":"1"}
然后将其进行base64编码,得到Jwt的第二部分。
eyJqdGkiOiJhZTQxMmJhMC1hYzFhLTQyNmQtOTQ2MC0wY2VlMDJiZGQyYjkiLCJzdWIiOiJ6aGFuZ3NhbiIsImV4cCI6MTY4MzM1NDg1MCwiaWF0IjoxNjgzMzU0MjUwLCJ1c2VybmFtZSI6InpoYW5nc2FuIiwidWlkIjoiMSJ9
签证(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
签名算法( header (base64后的).payload (base64后的) . secret )
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret秘钥组合加密,然后就构成了jwt的第三部分。
Elqv-RvSYH0W_KXhqWOmiQiXpDVMVcCu1Zi_SZs1qpI
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG7CoERvZSIsImFkbWluIjp0cnVlfQ==.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
4.3 入门
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
4.3.1 生成令牌
public void testGenerate(){
String token = Jwts.builder()
.setId(UUID.randomUUID().toString())
.setSubject(user.getUsername())
.setExpiration(expir)
.setIssuedAt(now)
.claim("username",user.getUsername())
.claim("uid",user.getId())
.signWith(SignatureAlgorithm.HS256,"ikun")
.compact();
System.out.println(token);
}
4.3.2 校验令牌
@Test
void m1(){
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMiIsInN1YiI6ImphdmEiLCJleHAiOjE3MTk5MTg2NzIsImlhdCI6MTcxOTkxNzY3Mn0.WaF9alWaLP_taGa2Czll1Y_4H3oSewFHsoDe5tMur9g";
//载荷playload--载荷就是存放有效信息的地方。该部分的信息是可以自定义的
Claims claims = Jwts.parser().setSigningKey("ikun").parseClaimsJws(token).getBody();
// 头部header---JWT的最基本的信息
JwsHeader header = Jwts.parser().setSigningKey("ikun").parseClaimsJws(token).getHeader();
// 签证signature---解析不了--安全的保证
String signature = Jwts.parser().setSigningKey("ikun").parseClaimsJws(token).getSignature();
System.out.println(header);//{alg=HS256}
System.out.println(claims);//{jti=12, sub=java, exp=1719918672, iat=1719917672}
System.out.println(signature);//WaF9alWaLP_taGa2Czll1Y_4H3oSewFHsoDe5tMur9g
}
当我们对令牌进行任何部分(header , payload , signature)任何部分进行篡改, 都会造成令牌解析失败 ;
4.4 普通令牌token和jwt令牌token区别
4.4.1 普通令牌token
普通令牌是SpringSecurityOauth2给客户端颁发的一个无含义的令牌,在令牌发布时,Oauth2将用户信息存储到程序指定的存储位置,并用普通令牌唯一标识这个存储信息,当用户再次携带令牌访问时,Oauth2会根据令牌查询用户信息,进而实现权限角色的限制。
普通令牌需要一个存储用户信息的地方,这个地方可以内存,也可以是数据库(Redis、Mysql)。
基于数据库存储(以Redis为例)
①基于Redis存储用户信息的方式,认证服务器将用户信息存储到指定的Redis数据库中 ②当资源服务获取到access_token时,会到Redis中获取用户信息 ③在微服务场景下适用
1.4.2 jwt令牌
jwt令牌的方式就无需数据库的介入,jwt令牌中就包含着用户的信息,Oauth在发布令牌时,会将用户信息放入JWT令牌中,用户拿着JWT令牌时,Oauth从中获取到用户信息,实现用户权限的控制。
jwt不需要后端进行存储。 ①基于JWT令牌的认证服务器,用户信息存储到令牌中 ②当资源服务获取到access_token后,会解析这个jwt类型的access_token,从中会获取到用户信息 ③微服务场景下也不适用
1.4.3 总结
流行的jwt有一个设计上的缺陷,他通过密文传输用户信息,那么服务器在这种基础结构下是无法做到关闭用户登陆授权的操作,如果用户的jwt密文被偷窃,那么黑客就能以用户身份登陆,并且即使知道密文丢失,也无法关闭被偷窃的jwt密文。
jwt,适合轻量的系统和权限不严格系统。 token,适合重量系统和权限有严格要求的系统。
五、JWT的应用--配合拦截器
5.1登录拦截器LoginInterception
使用拦截器不会造成throw new RuntimeException("token is null"),不能被自定义异常类errorController捕获的情况,与拦截器的实现机制有关,拦截器(Interceptor)的工作机制确实在一定程度上决定了异常处理的流程。拦截器的执行是在DispatcherServlet处理请求之前,但一旦拦截器通过了预处理(preHandle)阶段,即完成了对token的验证,后续的请求处理将按照正常流程进行,包括异常的捕获与处理。要拦截器不是简单地抛出异常而是将其适当地传递给后续处理流程,那么在拦截器中抛出的异常最终是可以被框架内的错误控制器捕获并处理的。这要求我们在编写拦截器逻辑时,要考虑到异常的正确处理,比如通过抛出自定义异常并确保有对应的异常处理器来捕获这些异常,从而提供更加友好的错误响应给客户端。
@Component
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {
// 拦截器和过滤器的区别
// 拦截器:在请求处理之前进行调用(Controller方法调用之前 DispatcherServlet之后)
// 过滤器:在web请求进入容器之前进行调用(Controller方法调用之前 DispatcherServlet之前)
// 过滤器:可以定义多个过滤器,但是拦截器只有一个
// 拦截器:可以获取到请求参数,但是过滤器不能获取到请求参数
// 拦截器Interceptor : springmvc
// 过滤器Filter : servlet/tomcat
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if(!StringUtils.hasText(token)){
throw new RuntimeException("token 不能为空");
}
// log.info(token);
// Integer userId = (Integer) redisTemplate.opsForValue().get(token);
// System.out.println(userId);
if(!jwtUtils.parseToken(token)){
throw new RuntimeException("token 已经失效");
}
// redisTemplate.opsForValue().set(token,userId,1, TimeUnit.HOURS);
return true;
}
}
5.2 拦截器配置类TokenConfig
@Configuration
public class TokenConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
// 创建一个拦截器对象,使用add的形式进行添加
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**") //拦截所有的请求
.excludePathPatterns("/user/login");//白名单,不需要拦截
}
}
5.3工具类-JwtUtils(重要)
@Component
public class JwtUtils {
//读取配置文件中的数据,方便管理
@Value("${jwt.salt}")
private String salt;//盐
@Value("${jwt.expiration}")
private Long expiration;//存在时间
// 生成jwt令牌--加密
public String createToken(Integer userId){
// 1000秒后的时间Date
// salt 盐
Date date = new Date(System.currentTimeMillis() + expiration);
String token = Jwts.builder()
.setId(userId+"")
.setSubject("java")
.setExpiration(date)
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "salt")
.compact();
return token;
}
// 解析令牌
public boolean parseToken(String token){
try{
Jwts.parser().setSigningKey("salt").parseClaimsJws(token);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public Integer getUserId(String token){
return Integer.parseInt(Jwts.parser().setSigningKey("salt").parseClaimsJws(token).getBody().getId());
}
}
5.4UserServiceImpl与前文项目相连接
UserServiceImpl代码改变
@Slf4j
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private JwtUtils jwtUtils;
@Override
public String login(User user) {
if (!StringUtils.hasText(user.getUsername())){
throw new RuntimeException("账号不能为空");
}
if (!StringUtils.hasText(user.getPassword())){
throw new RuntimeException("密码不能为空");
}
User user1 = userMapper.login(user.getUsername());
log.info(user1.toString());
if(ObjectUtils.isEmpty(user1) ){
throw new RuntimeException("账号不存在");
}
if (!user1.getPassword().equals(user.getPassword())){
throw new RuntimeException("密码错误");
}
// token 用户信息id
// token(key) id(value)
// key:123456
// String token = "NEKOT"+UUID.randomUUID().toString().replace("-", "");
存到redis里面
// redisTemplate.opsForValue().set(token,user1.getId());
// redisTemplate.expire(token,30, TimeUnit.SECONDS);
// 使用utils Jwt令牌生成token,有过期时间,不存到redis上
String token = jwtUtils.createToken(user1.getId());
System.out.println(token+"----------------");
return token;
}
}
其余代码不变。
六、总结
6.1、过滤器和拦截器的区别和联系?
在Java中,尤其是在Web应用开发中,拦截器(Interceptor)和过滤器(Filter)都是用来拦截请求和响应的机制,但它们之间存在一些关键的区别。下面是一些主要的不同点以及它们之间的联系:
区别
-
实现原理:
- 拦截器通常基于Java的反射机制或动态代理来实现,这使得它可以在运行时动态地拦截方法调用。
- 过滤器基于回调函数或事件监听器模式实现,它是由Servlet容器调用的。
-
依赖性:
- 拦截器不依赖于Servlet容器,而是由特定的框架(如Spring MVC)管理。
- 过滤器依赖于Servlet容器,如Tomcat、Jetty等,因为它是Servlet规范的一部分。
-
作用范围:
- 拦截器主要用于拦截和处理控制器方法调用前后的逻辑,它更专注于业务逻辑的控制。
- 过滤器可以对几乎所有进入容器的HTTP请求进行处理,包括静态资源、JSP页面等,它的作用更为广泛。
-
可访问性:
- 拦截器可以访问更多的上下文信息,如Spring MVC中的模型数据、异常信息等。
- 过滤器访问的是标准的
HttpServletRequest
和HttpServletResponse
对象,无法直接访问框架提供的额外上下文信息。
-
配置方式:
- 拦截器的配置通常是在框架层面进行的,比如在Spring中使用配置类或XML配置文件。
- 过滤器的配置是在web.xml文件中完成的,或者使用注解在Servlet容器中注册。
-
执行时机:
- 拦截器在请求到达控制器之前和之后执行,可以进行更细粒度的控制。
- 过滤器在请求到达目标资源(包括Servlet、JSP或其他过滤器)之前和之后执行,执行时机更早。
联系
- 两者都体现了AOP(面向切面编程)思想,即在不修改原有代码的情况下增加功能,如权限检查、日志记录、性能监控等。
- 都可以通过某种方式设置执行顺序,例如Spring框架允许通过
Ordered
接口或@Order
注解来指定拦截器的执行顺序,而过滤器可以通过web.xml中的<filter-mapping>
元素的顺序来决定。
总之,拦截器和过滤器都是Java Web应用中用于增强功能和控制流程的重要工具,选择使用哪一种取决于具体的应用场景和需求。拦截器更适合在框架层面进行更精细的控制,而过滤器则适用于更通用的请求处理任务。
6.2、过滤器和拦截器只能创建一次吗?
对于过滤器(Filter)和拦截器(Interceptor),它们的实例化和初始化的行为是有区别的,但表述“只能创建一次”需要进一步澄清:
过滤器(Filter)
过滤器的实例通常在Servlet容器启动时创建并且初始化。这意味着每个定义的过滤器配置只会创建一个过滤器实例。这个实例会驻留在容器中,对于每个进入的请求,容器会调用这个过滤器实例的方法,如doFilter()
,来处理请求和响应。因此,过滤器的init()
方法仅在容器启动时调用一次,而doFilter()
方法则会在每个请求通过该过滤器时被调用。
拦截器(Interceptor)
拦截器的实例化和生命周期管理则依赖于具体的框架。在Spring MVC中,拦截器的实例可以通过Spring的bean管理机制来创建,这意味着拦截器可能被定义为单例(Singleton)或者其他作用域的bean。如果定义为单例,那么在整个应用程序运行期间,确实只有一个拦截器实例会被创建。然而,与过滤器不同,拦截器的调用时机是在请求处理的特定阶段,如在请求处理之前或之后,具体取决于拦截器类型和配置。
在Spring MVC中,HandlerInterceptor
接口的实现(或其扩展)将在每个请求到达控制器之前和/或之后被调用,这允许在每个请求的基础上进行处理。因此,虽然拦截器的实例可能只创建一次,但其方法(如preHandle()
, postHandle()
, 和 afterCompletion()
)可以在每个请求上被多次调用。
总结来说,过滤器和拦截器的实例化次数主要由它们的生命周期管理决定。在典型的配置下,过滤器和作为单例的拦截器确实只会在应用启动时创建一次,但它们的方法会在每次请求通过时被调用。
6.3过滤器和拦截器在系统中只能有一个对象吗?
过滤器(Filter)和拦截器(Interceptor)在系统中并不限于只能有一个对象。实际上,你可以在系统中配置多个过滤器和多个拦截器,以便对请求和响应进行不同的预处理或后处理。
过滤器(Filter)
在Servlet容器中,你可以定义多个过滤器,并且这些过滤器将按照在web.xml(或等效的Java配置)中声明的顺序被调用。每个过滤器实例独立处理请求,可以执行如身份验证、日志记录、编码转换等不同的任务。过滤器链(Filter Chain)确保了请求按顺序经过所有相关的过滤器。
拦截器(Interceptor)
在像Spring MVC这样的框架中,你可以配置多个拦截器来处理请求。拦截器通常用于更精细的控制,如权限检查、请求前后的业务逻辑处理等。每个拦截器都会在其方法(如preHandle()
, postHandle()
, 和 afterCompletion()
)中被调用,根据配置的顺序进行。
多个过滤器和拦截器的配置
-
过滤器:在传统的Servlet容器中,过滤器在web.xml中通过
<filter>
和<filter-mapping>
元素进行配置。在现代的Spring Boot应用中,可以通过实现WebFilter
接口并使用@WebFilter
注解,或者通过WebFilterRegistrationBean
进行配置。 -
拦截器:在Spring MVC中,你可以通过实现
HandlerInterceptor
接口或继承HandlerInterceptorAdapter
类来创建拦截器。然后,通过在配置类中实现WebMvcConfigurer
接口的addInterceptors()
方法来注册这些拦截器。
总结
过滤器和拦截器的数量并不受限于只能有一个。你可以根据应用的需求配置任意数量的过滤器和拦截器,只要它们的实现和配置正确,就可以在请求和响应的处理过程中按需调用。
个人撰写,小白一枚,欢迎指正。