java框架 2 springboot 过滤器 拦截器 异常处理 事务管理 AOP

Filter 过滤器

在这里插入图片描述
对所有请求都可以过滤。
在这里插入图片描述
实现Filter接口,重写几个方法,加上@WebFilter注解,表示拦截哪些路由,如上是所有请求都会拦截。
然后还需要在入口处加上@SvlterComponentScan注解,因为Filter是javaweb三大组件之一,并不是springboot的内容。
在这里插入图片描述
chain.doFilter是放行该请求的意思,如果没有将会卡在当前过滤器上。

过滤器链

一个服务可以配置多个过滤器,多个过滤器形成过滤器链。有点像koa的洋葱模型。

过滤器1执行 -> 过滤器2执行 -> 执行主要逻辑 -> 过滤器2放行后的逻辑执行 -> 过滤器1放行后的逻辑执行

那么怎么区分那个过滤器先执行呢?通过过滤器首字母排序来决定。

Interceptor拦截器

在这里插入图片描述

拦截器是Spring框架提供的,跟filter不一样。
使用:
在这里插入图片描述
实现HandlerInterceptor接口,重写方法,其中preHanldel是在controller执行前执行,返回值作为放行的条件。
postHandle是在controller执行后执行。
实现拦截器之后,还需要配置才能生效。
在这里插入图片描述
实现WebMvcConfigurer接口,然后使用@Configuration,这样springboot启动的时候会自动扫描该注解,生效该配置。
如上就是将拦截器注册,并指定其拦截的接口。

拦截器-拦截路径

在这里插入图片描述
可以通过addPathPatterns指定哪些路径需要拦截,通过excludePathPatterns指定哪些路径不需要拦截。

执行时机

上面说过,拦截器是spring框架提供的,而过滤器是tomcat框架提供的,如图。
在这里插入图片描述
如果都存在的话,会先执行过滤器,再执行拦截器的逻辑。
过滤器会拦截所有的请求资源,而拦截器只会拦截Spring环境中的资源。

案例 实现jwt登陆验证

安装对应依赖

	<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>

实现jwtUtils类

package com.example.demo.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.Map;

public class JwtUtils {
    private static String singKey = "test1234"; // 密钥
    private static long expire = 432000L; //过期时间

    public static String generatorToken(Map<String, Object> data) {
        String jwt = Jwts.builder()
                .addClaims(data)
                .signWith(SignatureAlgorithm.HS256, JwtUtils.singKey)
                .setExpiration(new Date(System.currentTimeMillis() + JwtUtils.expire))
                .compact();
        return jwt;
    }


    public static Claims parseToken(String token) {
        Claims calims = Jwts.parser()
                .setSigningKey(singKey)
                .parseClaimsJws(token)
                .getBody();
        return calims;
    }

}

使用jwt生成token

@Slf4j
@RestController
public class LoginController {

    @PostMapping("/login")
    public Result login(@RequestBody LoginUser body){
        Map<String, Object> data = new HashMap<>();
        log.info("{},{}", body.getName(),body.getPassword());
        data.put("name", body.getName());
        data.put("password", body.getPassword());
        String token = JwtUtils.generatorToken(data);
        return Result.success(token);
    };
}

使用对应的拦截器进行校验

@Component("loginInterceptor")
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
        System.out.println("preHandle....." + req.getRequestURL());
        String url = req.getRequestURL().toString();
        if(url.contains("login")){
            // 登陆接口不需要校验
            return true;
        }
        String token = req.getHeader("token");
        try {
            if (token != null) {
                Claims data = JwtUtils.parseToken(token);
                req.setAttribute("user", data);
                return true;
            } else {
                throw new Exception("token不存在或者过期");
            }
        } catch (Exception e) {
            Result error = Result.error("token is not exists or expire");
            res.addHeader("Content-Type", "application/json");
            res.getWriter().write(JSONObject.toJSONString(error));
            return false;
        }
    }
}
@Configuration
public class SpringMvcConfigure implements WebMvcConfigurer {

    @Resource(name="loginInterceptor")
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**"); //该拦截器拦截所有请求
    }
}

登陆接口不需要验证,让该拦截器拦截对应请求,拿到token,进行解析,解析到用户数据就塞入req中,后续controller就可以拿到该用户信息。

异常处理

  • 程序开发过程中不可避免会碰到异常,有时候返回的信息并不是后端统一的信息。

在这里插入图片描述
像nest可以配置全局异常过滤器,会俘获所有的异常然后统一信息返回。

方案

java也可以定义全局异常处理器。
在这里插入图片描述

通过注解@RestControllerAdvice指定这是一个controller异常处理器,@RestControllerAdvicd中也包含@ResponseBody,表示所有的方法返回的值会被转为json传给前端(实际上所有的请求传输都是字符串,只不过设置了contentType为json,浏览器会自动识别contentType处理。)。通过@ExceptionHandler(Exception.class)指定拦截什么类型的异常,Exception.class就是指拦截所有的异常。

import com.example.demo.pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public Result ex(Exception ex){
        ex.printStackTrace();
        return Result.error("服务器报错: " + ex.getMessage());
    }
}

事务管理

在这里插入图片描述

在这里插入图片描述
springboot提供了@Transactional注解来开启事务。

回滚异常


@Transactional注解,可以传入值,可以控制出现什么异常的情况下,回滚事务。默认只有运行时异常才会处理。

事务传播行为

事务a方法调用事务b方法的时候
在这里插入图片描述
比如在a中调用b的方法,当a失败后,事务回滚,会导致b方法执行的逻辑也会回滚,事务传播行为默认是有则加入,也就是b方法会加入当前a方法的事务中。

将其改为REQUIRES_NEW,在a中调用b的时候,会先挂起a的事务,然后起一个b的事务,当a失败后,a执行的逻辑会回滚,但是b方法执行的逻辑如果没报错,会保留。

在这里插入图片描述

案例 删除一个部门,并将该部门下所有员工删除,不管删除失败成功都需要记录日志

如下,启用Propagation.REQUIRES_NEW,在调用创建日志方法的时候并不会受原本事务的影响

  @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public Result deleteDepts(Integer id) {
        try {
            this.dept.deleteDept(id);
            this.emp.deleteEmpByJobId(id); //删除员工表下该部门的员工
            return Result.success(0);
        } catch (Exception e) {
            return Result.error(e.getMessage());
        } finally {
            // 记录日志 也是一个事务方法,用REQUIRES_NEW则不会被上述逻辑影响,会正常记录日志
            logger.create(new Date(), "删除")
        }
    }

AOP

在这里插入图片描述
实现
在这里插入图片描述
编写AOP类,交给IOC容器管理。
通过joinPoint可以拿到原始方法。

AOP核心概念

在这里插入图片描述

  • 连接点,joinPoint,可以被AOP控制的方法,比如上图的list,delete,save等方法都属于连接点。
  • 通知,Advice,重复逻辑,比如上图的recordTime方法,共性逻辑(所有连接点都会执行)
  • 切入点:pointCut,顾名思义就是在哪里切入这个类,匹配连接点的条件。
  • 切面:描述通知与切入点的对应关系,如上图的@Around(切入点表达式)+recordTIme(通知)方法就是切面。
  • 目标对象:Target,AO类所应用的对象。
AOP的执行流程

AOP类是如何作用于目标对象的,
在这里插入图片描述
上述说过,AOP是通过动态代理实现的,如上,DeptServiceImpl类,是AOP的目标对象,他会根据通知,生成一个代理类,DeptServiceProxy,然后重新list方法,执行AOP类的逻辑,最后如上,通过@Autowired注入的deptService就不是DeptServiceImpl,而是DeptSerivceProxy这个代理对象,所以执行list方法的时候,就会执行代理对象list,从而执行通知的逻辑

通俗的说,AOP类会在不影响目标对象代码的基础上,基于目标对象,新增一些其他的逻辑,通过代理的形式生成一个新的类,交给IOC容器.

案例 记录每个controller的耗费时间

可以用拦截器做到,也可以用过滤器做到,也可以用AOP做到。

@Component
@Aspect
@Slf4j
public class TimeAspect {
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws  Throwable{
        long begin = System.currentTimeMillis();
        Object object = joinPoint.proceed(); //调用原方法
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature() + "执行耗时: {}ms", end- begin);
        return object; //返回
    }
}

在这里插入图片描述

AOP高阶
通知类型

上述我们使用的@Around就是通知类型。他的功能最强大,可以编写目标方法执行前的逻辑,也可以编写目标方法执行后的逻辑(可以拿到目标方法)

在这里插入图片描述
@Pointcut注解可以抽离公共的切入点表达式,服用切入点表达式
在这里插入图片描述

通知的执行顺序

当有多个通知都匹配到同一个切入点时,目标方法执行,多个通知方法都会执行。
跟过滤器一样,其实是根据AOP的类名字母排序有关,且@before和@after的执行顺序就跟洋葱模型一样,也是234 -> 432这样执行。
在这里插入图片描述

除此之外,还可以通过@Order注解来标记执行顺序。

切入点表达式

在这里插入图片描述
在这里插入图片描述

通配符号 * 和 …

在这里插入图片描述

 @Around("execution(* com.*.demo.controller.*.update*(*))")

* com表示 匹配任何返回值
com.*.demo表示二级包是任意的
controller.*表示controller下的类或者接口是任意的
update*表示以update开头的类或者接口
(*)表示匹配一个参数

上述表示 任何的返回值,com下任意的二级包里含有的demo三级包下的controller目录下的任意的以类或者接口下以update开头,且只能有一个参数的方法

如果换成…

 @Around("execution(* com..controller.*.update*(..))")

com…可以匹配任意层级的包
update*(…)表示匹配update开头的方法,且可以有任意参数。

上述表示 任何的返回值,com下任意的二级包里含有controller的目录,下的任意的以类或者接口下以update开头,且只能有一个参数的方法

多个切入点表达式可以用 || & 等组合。

@annotation

在这里插入图片描述
匹配标识有特定注解的方法
定义一个注解用来标识

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Mylog {
    
}

将execution切换成@annotation

public class TimeAspect {
    //@Around("execution(* com.example.demo.controller.*.*(..))")
    @Around("@annotation(com.example.demo.aop.Mylog)")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws  Throwable{
        long begin = System.currentTimeMillis();
        Object object = joinPoint.proceed(); //调用原方法
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature() + "执行耗时: {}ms", end- begin);
        return object; //返回
    }
}

连接点

对于@Around,只能通过ProceddingJoinPoint获取连接点信息,如下

在这里插入图片描述
对于其他四种通知
在这里插入图片描述
只能通过JoinPoint获取连接点信息。

@Aspect
@Component
@Slf4j
public class TimeAspect {
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws  Throwable{
        long begin = System.currentTimeMillis();
        String className = joinPoint.getTarget().getClass().getName();
        Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        Object[] args = joinPoint.getArgs();
        log.info("打印结果==={},{},{},{}", className, signature, methodName, Arrays.toString(args));
        Object object = joinPoint.proceed(); //调用原方法
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature() + "执行耗时: {}ms", end- begin);
        return object; //返回
    }
}
案例 对增删改操作进行日志写入操作

使用AOP完成该功能。
定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperatorLog {
}

在增删改方法上加上该注解
编写AOP代码

@Component
@Aspect
@Slf4j
public class OperatorLog {

    // 通过注解获取request对象
    @Autowired
    private HttpServletRequest request;

    @Around("@annotation(com.example.demo.aop.OperatorLog)")
    public Object createLog(ProceedingJoinPoint joinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        Claims user = (Claims) request.getAttribute("user");
        String className = joinPoint.getTarget().getClass().getName();
        Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        Object[] args = joinPoint.getArgs();
        Object object = joinPoint.proceed(); //调用原方法
        long time = begin -  System.currentTimeMillis();
        Log newLog = new Log((String) user.get("name"),time, methodName, Arrays.toString(args),className);
        log.info("插入一条日志: {}",newLog);
        Log.insertLogs(newLog);
        return object; //返回
    }
}

通过注解获取request对象,在拦截器的时候将user注入到req里面去,所以这里可以获取得到。然后插入一条日志。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/476145.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机二级C语言的注意事项及相应真题-6-程序设计

目录 51.将a所指数组主对角线上的元素分别乘以2;次对角线上的元素分别乘以3&#xff0c;依次放入指针p所指的数组中。计算过程中不得修改a所指数组中的数据52.将a、b中的两个两位正整数合并形成一个新的整数放在c中。合并的方式是:将a中的十位和个位数依次放在变量c的十位和千位…

面试算法-62-盛最多水的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。…

Java------数据结构之栈与队列(简单讲解)

本篇碎碎念&#xff1a;时隔n个月&#xff0c;继续写博客&#xff0c;假期落下的进度&#xff0c;在开学后努力追赶&#xff0c;假期不努力&#xff0c;开学徒伤悲啊&#xff0c;此时此刻真想对自己说一句&#xff0c;活该啊~~~~ 欠下的链表练习题讲解会在下次更新~~~~ 今日份励…

基于Springboot的在线投稿系统+数据库+免费远程调试

项目介绍: Javaee项目&#xff0c;springboot项目。采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringBoot Mybatis VueMavenLayui来实现。MySQL数据库作为系统数据储存平台&a…

第十二届蓝桥杯省赛CC++ 研究生组-砝码称重

solution1&#xff08;通过10%&#xff09; 写了几种可能的组合方式&#xff0c;骗到一丢丢分数 #include<iostream> #include<algorithm> #include<map> using namespace std; int main(){int n, a[110], count 0, sum[110] {0};map<int, int> mp…

Windows系统部署GoLand结合内网穿透实现SSH远程Linux服务器开发调试

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-HIOuHATnug3qMHzx {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

PTA L2-041 插松枝 代码附注释

人造松枝加工场的工人需要将各种尺寸的塑料松针插到松枝干上&#xff0c;做成大大小小的松枝。他们的工作流程&#xff08;并不&#xff09;是这样的&#xff1a; 每人手边有一只小盒子&#xff0c;初始状态为空。每人面前有用不完的松枝干和一个推送器&#xff0c;每次推送一…

本地项目文件夹创建python文件并配置conda环境的完整流程

1 在Pycharm中创建新项目 位置就是本地的项目文件夹 2 接着打开pycharm的终端 创建conda环境&#xff08;这个过程需要保证conda.exe能够被系统路径识别&#xff09; conda create --name my_environment&#xff08;my_environment取自己想要的环境名字&#xff09; 还可以指…

(附源码)基于Spring Boot + Vue的校园综合信息服务平台设计与实现

前言 &#x1f497;博主介绍&#xff1a;✌专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅&#x1f447;&#x1f3fb; 2024年Java精品实战案例《100套》 &#x1f345;文末获取源码联系&#x1f345; &#x1f31…

1.2 编译型语言和解释型语言的区别

编译型语言和解释型语言的区别 通过高级语言编写的源码&#xff0c;我们能够轻松理解&#xff0c;但对于计算机来说&#xff0c;它只认识二进制指令&#xff0c;源码就是天书&#xff0c;根本无法识别。源码要想执行&#xff0c;必须先转换成二进制指令。 所谓二进制指令&…

2024年上半年PETS5考试提醒/北语考前培训班(线上)招

PEST5考试每年进行两次&#xff0c;上半年和下半年各一次。目前尚未公布2024年的报考计划&#xff0c;但可以参考2023年度信息&#xff0c;上半年报名时间&#xff1a;4月11日-4月13日&#xff1b;考试时间&#xff1a;5月20日-5月21日。知识人网小编提醒拟申报者关注报考日期&…

聚焦两会:数字化再加速,VR全景助力制造业转型

近年来&#xff0c;随着信息技术、人工智能、VR虚拟现实等新兴技术的不断涌现&#xff0c;数字化正日益成为推动当今经济发展的新驱动力。在不久前的两会上&#xff0c;数字化经济和创新技术再度成为热门话题&#xff1a; 国务院总理李强作政府工作报告&#xff1a; 要深入推…

2024.3.21 QT

思维导图 自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面。&#xff08;不要使用课堂上的图片和代码&#xff0c;自己发挥&#xff0c;有利于后面项目的完成&#xff09; 要求&#xff1a; 1. 需要使用Ui界面文件进行界面设计 2. ui界面上的组件相关设置&…

如何设计一个安全的API接口详解

前言 在日常开发中&#xff0c;总会接触到各种接口。前后端数据传输接口&#xff0c;第三方业务平台接口。一个平台的前后端数据传输接口一般都会在内网环境下通信&#xff0c;而且会使用安全框架&#xff0c;所以安全性可以得到很好的保护。这篇文章重点讨论一下提供给第三方…

【ai技术】(3):树莓派4,成功安装ollama软件,内存4G,推荐使用命令行界面安装,使用raspi-config配置wifi,运行速度飞快

1&#xff0c;关于raspberrypi 4 项目 https://www.bilibili.com/video/BV1K2421P71h/ 【ai技术】&#xff08;3&#xff09;&#xff1a;树莓派4&#xff0c;成功安装ollama软件&#xff0c;内存4G&#xff0c;安装命令行版本&#xff0c;使用raspi-config配置wifi&#xff0…

MO尺度(大气边界层)

在大气表面层( atmospheric surface layer)中,MO参数是用来决定流动是中性或者非中性的一个重要参数。其定义是 z / L z/L z/L&#xff0c;其中 L L L为Obukhov长度&#xff0c;其含义是浮力产生的湍动能和剪切产生的湍动能之比(Hj h AIP 2023)(Monin IAS,1954)&#xff0c;具体…

C++初始化列表

本博客将讲述C初始化列表的相关内容 一.什么是初始化列表 图中红方框框的就是初始化列表 格式为&#xff1a; &#xff1a;成员变量1&#xff08;参数1&#xff09;&#xff0c;成员变量2&#xff08;参数2&#xff09; 编译器会将初始化列表一一转换成代码&#xff0c;并将…

G - Find a way

题目分析 1.双重bfs,遍历两个起点求最短路再计算总和即可 2.唯一的坑点在于对于一个KFC&#xff0c;两人中可能有一个到不了&#xff0c;所以还要对到不了的点距离做处理 #include <bits/stdc.h> using namespace std; using ll long long; const int N 220;struct pos…

英伟达GTC大会看点:Blackwell芯片、推理微服务NIM、人形机器人

北京时间3月19日&#xff0c;英伟达创始人兼首席执行官黄仁勋在美国加州圣何塞SAP中心拉开了GTC大会帷幕&#xff0c;这是时隔5年重回线下的会议&#xff0c;现场吸引了11000多名与会者。大会上黄仁勋演讲了长达120分钟的主题分享《见证AI的变革时刻》&#xff0c;并发布了最新…