AOP切面

什么是Spring的AOP

AOP在spring中又叫“面向切面编程”,它可以说是对传统我们面向对象编程的一个补充,从字面上顾名思义就可以知道,它的主要操作对象就是“切面”,所以我们就可以简单的理解它是贯穿于方法之中,在方法执行前、执行时、执行后、返回值后、异常后要执行的操作。相当于是将我们原本一条线执行的程序在中间切开加入了一些其他操作一样。

在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常就称之为“切面”。

下面这个图就是一个AOP切面的模型图,是在某一个方法执行前后执行的一些操作,并且这些操作不会影响程序本身的运行。

AOP切面编程中有一个比较专业的术语:

基于AspectJ注解的AOP开发

1、五种通知注解

首先要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例。

当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创建代理。

在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知。通知是标注有某种注解的简单的Java方法。

AspectJ支持5种类型的通知注解:

  1. @Before:前置通知,在方法执行之前执行
  2. @After:后置通知,在方法执行之后执行
  3. @AfterRunning:返回通知,在方法返回结果之后执行
  4. @AfterThrowing:异常通知,在方法抛出异常之后执行(主动抛出的异常)
  5. @Around:环绕通知,围绕着方法执行

2、切入点表达式规范

这五种通知注解后面还可以跟特定的参数,来指定哪一个切面方法在哪一个方法执行时触发。那么具体操作是怎么样的呢?

这里就需要和大家介绍一个名词:“切入点表达式”,通过在注解中加入该表达式参数,我们就可以通过表达式的方式定位一个或多个具体的连接点,

切入点表达式的语法格式规范是:

execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]))

其中在表达式中有两个常用的特殊符号:

星号“ * ”代表所有的意思,星号还可以表示任意的数值类型

“.”号:“…”表示任意类型,或任意路径下的文件,

在这里举出几个例子:

表达式:

execution(* com.atguigu.spring.ArithmeticCalculator.*(…))

含义:

ArithmeticCalculator接口中声明的所有方法。第一个“*”代表任意修饰符及任意返回值。第二个“*”代表任意方法。“…”匹配任意数量、任意类型的参数。若目标类、接口与该切面类在同一个包中可以省略包名。

表达式:

execution(public * ArithmeticCalculator.*(…))

含义:

ArithmeticCalculator接口的所有公有方法

表达式:

execution(public double ArithmeticCalculator.*(…))

含义:

ArithmeticCalculator接口中返回double类型数值的方法

表达式:

execution(public double ArithmeticCalculator.*(double, …))

含义:

第一个参数为double类型的方法。“…” 匹配任意数量、任意类型的参数。

表达式:

execution(public double ArithmeticCalculator.*(double, double))

含义:

参数类型为double,double类型的方法

这里还有一个定位最模糊的表达式:

execution("* *(…)")

表示任意包下任意类的任意方法,但是这个表达式千万别写,哈哈,不然你每一个执行的方法都会有通知方法执行的!

同时,在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。

如:

execution (* .add(int,…)) || execution( *.sub(int,…))

表示任意类中第一个参数为int类型的add方法或sub方法

3、注解实践

对于切入点表达式,我们可以直接在注解中使用“”写在其中,还可以在@AfterReturning注解和@AfterThrowing注解中将切入点赋值给pointcut属性,但是在其他的注解中没有pointcut这个参数。

将切入点表达式应用到实际的切面类中如下:

@Aspect //切面注解
@Component  //其他业务层
public class LogUtli {

//  方法执行开始,表示目标方法是com.spring.inpl包下的任意类的任意以两个int为参数,返回int类型参数的方法
    @Before("execution(public int com.spring.inpl.*.*(int, int))")
    public static void LogStart(JoinPoint joinPoint) {
        System.out.println("通知记录开始...");
    }

//  方法正常执行完之后

    /**
     * 在程序正常执行完之后如果有返回值,我们可以对这个返回值进行接收
     * returning用来接收方法的返回值
     * */
    @AfterReturning(pointcut="public int com.spring.inpl.*.*(int, int)",returning="result")
    public static void LogReturn(JoinPoint joinPoint,Object result) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
    }
}

以上只是一个最简单的通知方法,但是在实际的使用过程中我们可能会将多个通知方法切入到同一个目标方法上去,比如同一个目标方法上既有前置通知、又有异常通知和后置通知。

但是这样我们也只是在目标方法执行时切入了一些通知方法,那么我们能不能在通知方法中获取到执行的目标方法的一些信息呢?当然是可以的。

4、JoinPoint获取方法信息

在这里我们就可以使用JoinPoint接口来获取到目标方法的信息,如方法的返回值、方法名、参数类型等。

如我们在方法执行开始前,获取到该目标方法的方法名和输入的参数并输出。

//  方法执行开始
    @Before("execution(public int com.spring.inpl.*.*(int, int))")
    public static void LogStart(JoinPoint joinPoint) {
            Object[] args = joinPoint.getArgs();    //获取到参数信息
            Signature signature = joinPoint.getSignature(); //获取到方法签名
            String name = signature.getName();  //获取到方法名
            System.out.println("【" + name + "】记录开始...执行参数:" + Arrays.asList(args));
    }

5、接收方法的返回值和异常信息

对于有些目标方法在执行完之后可能会有返回值,或者方法中途异常抛出,那么对于这些情况,我们应该如何获取到这些信息呢?

首先我们来获取当方法执行完之后获取返回值,

在这里我们可以使用@AfterReturning注解,该注解表示的通知方法是在目标方法正常执行完之后执行的。

在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。

该属性的值即为用来传入返回值的参数名称,但是注意必须在通知方法的签名中添加一个同名参数。

在运行时Spring AOP会通过这个参数传递返回值,由于我们可能不知道返回值的类型,所以一般将返回值的类型设置为Object型。

与此同时,原始的切点表达式需要出现在pointcut属性中,如下所示:

//  方法正常执行完之后
    /**
     * 在程序正常执行完之后如果有返回值,我们可以对这个返回值进行接收
     * returning用来接收方法的返回值
     * */
    @AfterReturning(pointcut="public int com.spring.inpl.*.*(int, int)",returning="result")
    public static void LogReturn(JoinPoint joinPoint,Object result) {
            System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
    }

对于接收异常信息,方法其实是一样的。

我们需要将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。

如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行。

实例如下:

//  异常抛出时
    /**
     * 在执行方法想要抛出异常的时候,可以使用throwing在注解中进行接收,
     * 其中value指明执行的全方法名
     * throwing指明返回的错误信息
     * */
    @AfterThrowing(pointcut="public int com.spring.inpl.*.*(int, int)",throwing="e")
    public static void LogThowing(JoinPoint joinPoint,Object e) {
        System.out.println("【" + joinPoint.getSignature().getName() +"】发现异常信息...,异常信息是:" + e);
    }

6、环绕通知

我们在上面介绍通知注解的时候,大家应该也看到了其实还有一个很重要的通知——环绕通知

环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。

对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。这就意味着我们需要在方法中传入参数ProceedingJoinPoint来接收方法的各种信息。

注意:
环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

具体使用可以看下面这个实例:

/**
	 * 环绕通知方法
	 * 使用注解@Around()
	 * 需要在方法中传入参数proceedingJoinPoint 来接收方法的各种信息
	 * 使用环绕通知时需要使用proceed方法来执行方法
	 * 同时需要将值进行返回,环绕方法会将需要执行的方法进行放行
	 * *********************************************
	 * @throws Throwable 
	 * */
	@Around("public int com.spring.inpl.*.*(int, int)")
	public Object MyAround(ProceedingJoinPoint pjp) throws Throwable {
 
//		获取到目标方法内部的参数
		Object[] args = pjp.getArgs();
 
		System.out.println("【方法执行前】");
//		获取到目标方法的签名
		Signature signature = pjp.getSignature();
		String name = signature.getName();
		Object proceed = null;
		try {
//			进行方法的执行
			proceed = pjp.proceed();
			System.out.println("方法返回时");
		} catch (Exception e) {
			System.out.println("方法异常时" + e);
		}finally{
			System.out.println("后置方法");
		}
 
		//将方法执行的返回值返回
		return proceed;
	}

7、通知注解的执行顺序

那么现在这五种通知注解的使用方法都已经介绍完了,我们来总结一下这几个通知注解都在同一个目标方法中时的一个执行顺序。

在正常情况下执行:

@Before(前置通知)—>@After(后置通知)---->@AfterReturning(返回通知)

在异常情况下执行:

@Before(前置通知)—>@After(后置通知)---->@AfterThrowing(异常通知)

当普通通知和环绕通知同时执行时:

执行顺序是:

环绕前置----普通前置----环绕返回/异常----环绕后置----普通后置----普通返回/异常

8、重用切入点定义

对于上面的通知注解,我们都是在每一个通知注解上都定义了一遍切入点表达式,

但是试想一个问题,如果我们不想给这个方法设置通知方法了,或者我们想要将这些通知方法切入到另一个目标方法,那么我们岂不是要一个一个的更改注解中的切入点表达式吗?这样也太麻烦了吧?

所以spring就想到了一个办法,重用切入点表达式

也就是说将这些会重复使用的切入点表达式用一个方法来表示,那么我们的通知注解只需要调用这个使用了该切入点表达式的方法即可实现和之前一样的效果,这样的话,我们即使想要更改切入点表达式的接入方法,也不用一个一个的去通知注解上修改了。

获取可重用的切入点表达式的方法是:

  1. 随便定义一个void的无实现的方法
  2. 为方法添加注解@Pointcut()
  3. 在注解中加入抽取出来的可重用的切入点表达式
  4. 使用value属性将方法加入到对应的切面函数的注解中
@Aspect
@Component
@Slf4j
public class SystemLogAspect {

    /**
     * SPEL表达式解析器
     */
    private final SpelExpressionParser parser = new SpelExpressionParser();

    /**
     * 默认参数名称探测器
     */
    private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

    @Autowired
    private SysLogService sysLogService;

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(com.xxx.util.log.OperationLog)")
    public void operationLogPointCut() {
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution(* com.xxx.controller..*.*(..))")
    public void operationExceptionLogPointCut() {
    }

    /**
     * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
     *
     * @param joinPoint 切入点
     */
    @AfterReturning(value = "operationLogPointCut()", returning = "keys")
    public void saveOperationLog(JoinPoint joinPoint, Object keys) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            OperationLog annotation = method.getAnnotation(OperationLog.class);
            String operator = annotation.operator();
            String operationType = annotation.operationType();
            Object[] arguments = joinPoint.getArgs();
            if (StringUtils.isNotBlank(operator)) {
                operator = parseParamToString(operator, joinPoint);
            }
            if (StringUtils.isNotBlank(operationType)) {
                operationType = parseParamToString(operationType, joinPoint);
            }
            UserInfo userByUuid = userInfoService.getUserByUuid(operator);
            String createBy = StringUtils.isNotBlank(operator) ? Optional.ofNullable(userByUuid.getName()).orElse(operator) :
                    (ContextUtil.getCurrentUserDto() != null ? ContextUtil.getCurrentUserDto().getUcn() : CommonConstants.USER_SYSADMIN);
            SysLog logDTO = new SysLog();
            logDTO.setUsername(createBy);
            logDTO.setMethod(joinPoint.getTarget().getClass().getName() + "." + method.getName());
            logDTO.setIpAddress(ContextUtil.getIp());
            logDTO.setCreatedBy(createBy);
            logDTO.setCreatedDate(new Date());
            logDTO.setOperation(operationType);
            logDTO.setParams(JSON.toJSONString(arguments) + "\n\n" + JSON.toJSONString(keys));
            sysLogService.add(logDTO);
        } catch (Exception exception) {
            log.error("saveOperationLog error: ", exception);
        }
    }

    /**
     * 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
     *
     * @param joinPoint 切入点
     * @param exception 异常信息
     */
    @AfterThrowing(pointcut = "operationExceptionLogPointCut()", throwing = "exception")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable exception) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Object[] arguments = joinPoint.getArgs();
            String operator = ContextUtil.getCurrentUserDto() != null ? ContextUtil.getCurrentUserDto().getUcn() : CommonConstants.USER_SYSADMIN;
            SysLog logDTO = new SysLog();
            logDTO.setUsername(operator);
            logDTO.setMethod(joinPoint.getTarget().getClass().getName() + "." + method.getName());
            logDTO.setIpAddress(ContextUtil.getIp());
            logDTO.setCreatedBy(operator);
            logDTO.setCreatedDate(new Date());
            logDTO.setOperation("Exception");
            logDTO.setParams(JSON.toJSONString(arguments) + "\n\n" + stackTraceToString(exception.getClass().getName(), exception.getMessage(), exception.getStackTrace()));
            sysLogService.add(logDTO);
        } catch (Exception saveExceptionLog) {
            log.error("saveExceptionLog error: ", saveExceptionLog);
        }
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuilder builder = new StringBuilder();
        for (StackTraceElement stet : elements) {
            builder.append(stet).append("\n");
        }
        return exceptionName + ":" + exceptionMessage + "\n\t" + builder;
    }

    private String parseParamToString(String spel, JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String[] parameterNames = discoverer.getParameterNames(method);
        if (parameterNames != null && parameterNames.length > 0) {
            EvaluationContext context = new StandardEvaluationContext();
            //获取方法参数值
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
            return parser.parseExpression(spel).getValue(context, String.class);
        }
        return "";
    }
}

以上就是使用AspectJ注解实现AOP切面的全部过程了,

在这里还有一点特别有意思的规定提醒大家,就是当你有多个切面类时,切面类的执行顺序是按照类名的首字符先后来执行的(不区分大小写)。

在切面中,辅助获取参数信息

JSONPath

JSONPath是一种类XPath的语言,用于在JSON结构中提取数据,方便数据的获取和转换。

基本语法

举例

获取 APPID 信息进行校验

path = "$.edaInfos.resource.appId"

private static void getBodyAppId(String path, JoinPoint joinPoint) throws BusinessException {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            Object read = JSONPath.read(JSONObject.toJSONString(arg), path);
            if (read instanceof JSONArray) {
                JSONArray jsonArray = (JSONArray) read;
                for (int i = 0; i < jsonArray.size(); i++) {
                    log.info("appID=",jsonArray.getString(i));
            }
        }
    }

SpEL

Spring Expression Language(简称 SpEL,Sp:Spring,EL:Expression Language)是一个支持运行时查询和操作对象图的强大的表达式语言。

SpEL常见用法

SpEL的语法类似于JSP中EL表达式,使用#{…} 作为定界符,所有在大框号中的字符都将被认为是SpEL。

SpEL支持如下表达式:

    • SpEL 字面量:
      • 整数:#{8}
      • 小数:#{8.8}
      • 科学计数法:#{1e4}
      • String:#{'string'}
      • Boolean:#{true}
  • SpEL引用bean,属性和方法:
    • 引用其他对象:#{car}
    • 引用其他对象的属性:#{car.brand}
    • 调用其它方法 , 还可以链式操作:#{car.toString()}
    • 调用静态方法静态属性:#{T(java.lang.Math).PI}
  • SpEL支持的运算符号:
    • 算术运算符:+,-,*,/,%,^(加号还可以用作字符串连接)
    • 比较运算符:< , > , == , >= , <= , lt , gt , eg , le , ge
    • 逻辑运算符:and , or , not , |
    • if-else 运算符(类似三目运算符):?:(temary), ?:(Elvis)
    • 正则表达式:#{admin.email matches ‘[a-zA-Z0-9._%±]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}’}

 举例

判断操作类型,以及获取操作人信息

@OperationLog(operationType = "#dto.operationType == 0 ? 'addOrUpdate' : (#dto.operationType == 1 ? 'update' : 'delete')", operator = "#dto.user[0].resourceInfoDTO.createdBy")
public RestResponse managingInfo(@RequestBody UserApiDTO.UserApi dto) throws BusinessException {
return userApiService.managingInfo(dto);
}
private String parseParamToString(String spel, JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String[] parameterNames = discoverer.getParameterNames(method);
        if (parameterNames != null && parameterNames.length > 0) {
            EvaluationContext context = new StandardEvaluationContext();
            //获取方法参数值
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
            return parser.parseExpression(spel).getValue(context, String.class);
        }
        return "";
    }

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

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

相关文章

springboot家乡特色推荐系统源码和论文

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括家乡特色推荐的网络应用&#xff0c;在外国家乡特色推荐系统已经是很普遍的方式&#xff0c;不过国内的管理网站可能还处于起步阶段。家乡特色推荐系统采用java技术&#xff0…

E3 基于Mysql的SQL应用和存储过程

一、实验目的: Mysql平台要求你熟练使用MySQL基本指令&#xff0c;完成对程序的控制与管理&#xff0c;并根据要求写存储过程。 二、实验要求: 1、基本硬件配置:英特尔Pentium III 以上,大于4G内存&#xff1b; 2、软件要求:Mysql&#xff1b; 3、时间:1小时&#xff1b; …

ggplot2 -- x轴相关操作

文章目录 刻度标签倾斜替换x轴刻度标签改变X刻度标签大小及颜色 演示数据集 library(ggplot2)# 示例数据 data <- data.frame(x 1:5,y c(3, 5, 2, 7, 4) ) data # x y #1 1 3 #2 2 5 #3 3 2 #4 4 7 #5 5 4刻度标签倾斜 p1 <- ggplot(data, aes(x x, y y)) geom_bar…

产品解读 | 新一代湖仓集存储,多模型统一架构,高效挖掘数据价值

星环科技TDH一直致力于给用户带来高性能、高可靠的一站式大数据基础平台&#xff0c;满足对海量数据的存储和复杂业务的处理需求。 同时在易用性方面持续深耕&#xff0c;降低用户开发和运维成本&#xff0c;让数据处理平民化&#xff0c;助力用户以更便捷、高效的方式去挖掘数…

【Kafka】Kafka安装:Linux本地和Docker

目录 Linux本地安装kafkajava环境配置Zookeeper的安装配置Kafka的安装与配置生产与消费 Docker安装kafkaZookeeper安装Kafka安装 Linux本地安装kafka java环境配置 1、上传jdk-8u261-linux-x64.rpm到服务器并安装&#xff1a; rpm -ivh jdk-8u261-linux-x64.rpm2、配置环境变…

WorkPlus移动应用管理平台,助力企业实现高效移动办公

在移动办公成为当今工作方式的主流趋势下&#xff0c;管理和运营企业移动应用成为了提高工作效率和数据安全的重要环节。而移动应用管理平台作为实现移动办公高效管理的关键工具&#xff0c;WorkPlus以其领先的性能和全面的功能&#xff0c;助力企业实现高效移动办公。 为何选…

DP读书:在常工院的2023年度总结

DarrenPig的年度总结 这是最好的时代&#xff0c;这是最坏的时代。——狄更斯 这是最好的时代&#xff0c;这是最坏的时代。——狄更斯 这是最好的时代&#xff0c;这是最坏的时代。——狄更斯 一、2023我的感受 不就是2023吗&#xff0c;不就是一年的经历吗&#xff0c;大家…

如何使用Docker部署导航页工具Dashy并实现任意浏览器远程访问——“cpolar内网穿透”

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 简介 Dashy 是一个开源的自托管的导航页配置服务&#xff0c;具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你可以将自己常用的一些网站聚合起来放在一起&#xff0c;形成自己的导航…

小红书商品笔记发布流程,如何避免盘营销

随着平台营销内容不断被管制&#xff0c;商品笔记慢慢出现在了人们的视野&#xff0c;这同时也意味着达人和品牌方们&#xff0c;可以名正言顺的在笔记内容中植入产品。商品链接的开通意味着&#xff0c;不管是达人还是品牌转化率都会进一步提升&#xff0c;今天来马文化传媒和…

AIGC:让生成式AI成为自己的外脑(文末送书)

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 什么是AIGC?二. AIGC如何运作&#xff1f;2.1 步骤一&#xff1a;收集数据2.…

仅使用 Python 创建的 Web 应用程序(前端版本)第06章_登录页面

从本章开始,我们将创建每个页面。 本栏的例子 可以访问这里, WTS 首先是登录页面。 完成后的图像如下 创建过程如下 No类型内容1Model创建继承BaseDataModel的数据类User、Session2MockDB创建用户表并添加管理员/成员用户3Service创建AuthAPIClient、UserAPIClient4Page定义…

利用Burp Suite观察https通联

对使用 HTTPS 协议的应用程序进行测试时&#xff0c;常使用 bp 观察流量&#xff0c;为能成功建立HTTPS联接&#xff0c;在将bp设置居代理的同时&#xff0c;还必须导入bp伪证书&#xff0c;这样才能修改请求和响应&#xff0c;加密和解密流量&#xff0c;成功模拟浏览的各种动…

Maven构建工具:Java项目的不可或缺之选

引言 在Java开发领域&#xff0c;构建工具是项目中至关重要的一环。Maven&#xff08;Maven Apache&#xff09;是一个强大的构建工具&#xff0c;用于管理项目的构建、依赖和文档等方面。本篇博文将介绍如何配置和使用Maven来构建和管理Java项目。 第一部分&#xff1a;Mave…

数据脱敏(三)脱敏算法-遮盖算法

脱敏算法篇使用阿里云数据脱敏算法为模板,使用算子平台快速搭建流程来展示数据 遮盖脱敏是一种数据脱敏技术&#xff0c;它的主要目的是通过隐藏或替换敏感信息来保护数据安全&#xff0c;同时保持数据的其他特性不变&#xff0c;以便于数据的进一步使用和分析。这种脱敏技术适…

九州金榜|过年期间如何合理规划孩子学习?

随着春节的临近&#xff0c;家家户户都沉浸在喜庆的氛围中。对于孩子们来说&#xff0c;过年意味着热闹、欢笑和丰盛的美食。然而&#xff0c;即使是过年&#xff0c;学习也不应被忽视。九州金榜家庭教育将和大家一起探讨如何合理安排过年期间孩子的学习。 一、保持学习持续性 …

探索编程世界的利器!选择哪个IDE,成就新手开发之路?

文章目录 一、IDE的概念和作用IDE是什么&#xff1f;为什么说选择一款IDE对开发者来说可以起到事半功倍的作用&#xff1f; 二、当下备受推崇的IDE有哪些&#xff1f;1. Visual Studio Code2. PyCharm3. IntelliJ IDEA 三、如何选择一个适合自己的IDE&#xff1f;四、IDE的使用…

React-Native项目 — 自定义字体的使用

系列文章目录 React-Native环境搭建&#xff08;IOS&#xff09;React-Native项目 — 关于IOS知识储备React-Native项目工程搭建&#xff08;开发模板搭建&#xff09;React-Native项目矢量图标库&#xff08;react-native-vector-icons&#xff09; 目录 系列文章目录前言一、…

Linux多线程——生产消费者模型

Gitee仓库&#xff1a;阻塞队列、环形队列 文章目录 1. 死锁1. 1 死锁概念1.2 死锁的四个必要条件1.3 解决死锁 2. 线程同步3. 条件变量3.1 举个例子3.2 接口 4. 生产消费者模型4.1 什么是生产消费者模型4.2 基于阻塞队列的生产消费模型4.3 环形队列&#xff08;信号量&#xf…

electron-vue更新到最新版本无法使用解决

更新版本&#xff1a; 网上说使用yarn upgrade-interactive --latest但是我这无法运行 所以我是使用以下命令进行更新的&#xff0c;不要急一条一条执行&#xff1a; yarn add electron yarn add electron-builder yarn add electron-debug yarn add electron-devtools-insta…

数据目录驱动测试——深入探讨Pytest插件 pytest-datadir

在软件测试中,有效管理测试数据对于编写全面的测试用例至关重要。Pytest插件 pytest-datadir 提供了一种优雅的解决方案,使得数据目录驱动测试变得更加简单而灵活。本文将深入介绍 pytest-datadir 插件的基本用法和实际案例,助你更好地组织和利用测试数据。 什么是pytest-da…