Spring框架 —— AOP面向切面编程

前言

        前面荔枝已经梳理了Spring框架中的IOC部分的知识,接下来荔枝继续梳理Spring框架的另一大重点:AOP面向切面编程。在这篇文章中,荔枝会着重弄清楚AOP的概念并对实现AOP的两种方式进行梳理,同时荔枝也会相应给出代码样例。毕竟荔枝始终觉得只有文字的描述是苍白无力的,有代码其实理解起来会更快哈哈哈。


文章目录

前言

一、代理模式和AOP概念

1.1 代理模式

1.2 AOP概念

二、基于注解方式实现AOP

2.1 动态代理分类和依赖引入

2.2 切入点表达式以及五种通知类型 

2.3 重用切入点表达式

2.4 切面的优先级 

三、基于XML方式实现AOP

总结


一、代理模式和AOP概念

在开发中我们常常考虑将不同功能的程序分离出来降低系统的耦合度,也就是解耦操作。也就是说,通常在针对一些附加功能的时候我们需要一种方法把子类中重复的代码注入到父类中。

1.1 代理模式

        代理模式是设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来一一解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用,同时让附加功能能够集中在一起也有利于统一维护。代理主要分为两种:静态代理和动态代理。

静态代理

静态代理解决了高耦合的问题,通过一个代理类构造方法获取执行类的对象并调用其add方法实现接口功能,并在前后加上附加功能比如日志文件。

package com.crj.aop;public class CalculatorStaticProxy implements Calaulator{//将代理目标对象传递进来private Calaulator calaulator;public CalculatorStaticProxy(Calaulator calaulator) {this.calaulator = calaulator;}@Overridepublic int add(int i, int j) {//输出日志System.out.println("[日志]add方法开始,参数是:i="+i+"、j="+j);calaulator.add(i,j);//输出日志System.out.println("[日志]add方法结束");return 0;}
}

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得声明多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现,这就需要使用动态代理技术了。

动态代理

Java种支持使用Proxy.newProxyInstance(classLoader,interfaces,invocationHandler)来实现接口的动态代理,需要三个参数:需代理类的类加载器、需代理类的实现接口数组和重写的代理方法。其中第三个参数实现目标的代理方法借助InvocationHandler类种的invoke方法来实现。

动态代理类

package com.crj.spring6.aop;import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;//实现动态代理
public class ProxyFactory {//目标对象private Object target;public ProxyFactory(Object target) {this.target = target;}//返回代理对象public Object getProxy(){/*** 参数说明* Proxy.newProxyInstance()方法* 1.类加载器ClassLoader loader 加载动态生成代理类的类加载器* 2.Class[] interfaces 目标对象实现的所有接口的class类型数组* 3.IncocationHandler 设置代理对象实现目标对象方法的过程*///类加载器ClassLoader classLoader = target.getClass().getClassLoader();//目标对象实现的所有接口的class类型数组Class<?>[] interfaces = target.getClass().getInterfaces();//使用匿名内部类来实现接口InvocationHandler invocationHandler = new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** 参数* 第一个参数:代理对象* 第二个参数:需要重写的目标对象的方法* 第三个参数:method方法里面的参数*///方法调用之前输出日志System.out.println("[动态代理日志]"+method.getName()+"参数:"+ Arrays.toString(args));//调用目标方法Object result = method.invoke(target, args);//方法调用之后输出日志System.out.println("[动态代理日志]"+method.getName()+"结果:"+ result);return result;}};return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);}}

测试类

package com.crj.spring6.aop;public class TestCal {public static void main(String[] args) {//创建代理对象ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());//获取代理对象Calaulator proxy = (Calaulator)proxyFactory.getProxy();proxy.add(1,2);}
}

1.2 AOP概念

        AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率,使程序是松耦合的。

相关术语

横切关注点

分散在各个模块中解决同一类问题的,或者说是从每个方法种抽取出来的同类非核心也无就乘坐横切关注点,比如:日志管理、用户验证等。换句话说,有几个附加功能就有几个横切关注点。

通知(增强)

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法,有五种通知类型:

  • 前置通知:在被代理的目标方法前执行
  • 返回通知:在被代理的目标方法成功结束后执行
  • 异常通知:在被代理的目标方法异常结束后执行
  • 后置通知:在被代理的目标方法最终结束后执行
  • 环绕通知:使用try..catch.finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

切面

封装通知方法的类

目标

被代理的目标对象

代理

向目标对象应用通知之后创建的代理对象

连接点

允许使用通知的地方

切入点 

        定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句。Spring的AOP技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。


二、基于注解方式实现AOP

我们知晓,AOP面向切面编程的底层也是通过动态代理来实现的,所以对于动态代理我们除了了解基本的代理分类之外还需要明确AOP种如何使用动态代理的方式来实现AOP。

2.1 动态代理分类和依赖引入

        AOP动态代理有两种:JDK动态代理和CGlib动态代理,分别对应有接口和无接口的动态代理。JDK动态代理会生成接口实现类的代理对象,而CGlib动态代理是通过继承被代理对象并重写的方式实现的。 

pom.xml文件

项目的环境中即pom.xml文件中需要引入aop和aspects依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.crj</groupId><artifactId>spring6</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><modules><module>spring-first</module><module>spring6-ioc-xml</module><module>spring6-ioc-annotation</module><module>spring6-aop</module></modules><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><!--    此处引入spring依赖--><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.2</version></dependency><!--  引入junit依赖--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.6.3</version></dependency><!--    引入log4j2的依赖--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.19.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.19.0</version></dependency><dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>6.0.2</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.2</version></dependency></dependencies>
</project>

除了环境总配置之外,还需要在项目中resources目录下的xml文件中配置开启组件扫描和aspectj自动代理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--开启组件扫描--><context:component-scan base-package="com.crj.spring6.aop.annotationAop"></context:component-scan><!--开启aspectj自动代理,为目标对象生成代理--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

2.2 切入点表达式以及五种通知类型 

在配置完环境依赖后,我们需要在切面类中定义通知的类型并设置切入点,Java中同个定义了几个注解来实现设置通知类型:

  • 前置通知 @Before(value = “切入点表达式”)
  • 返回通知 @AfterReturning
  • 异常通知 @AfterThrowing
  • 后置通知 @After()
  • 环绕通知 @Around()

需要注意切入点表达式的结构:execution(权限修饰符 方法返回值类型 切入点方法的全类名.方法名(方法参数列表)) 

前置通知 

package com.crj.spring6.aop.annotationAop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;//切面类
@Aspect  //切面类
@Component  //IOC容器
public class LogAspect {//设置切入点和通知类型//通知类型://    前置通知 @Before(value = “切入点表达式”)//    返回通知 @AfterReturning//    异常通知 @AfterThrowing//    后置通知 @After()//    环绕通知 @Around()//前置通知@Before(value = "execution(public int com.crj.spring6.aop.annotationAop.CalculatorImpl.add(..))")public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("Logger-.->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));}
}

返回通知

返回通知中我们在注解中可以通过returning属性获取执行目标增强方法后的返回值。

package com.crj.spring6.aop.annotationAop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;//切面类
@Aspect  //切面类
@Component  //IOC容器
public class LogAspect {//返回通知@AfterReturning(value = "execution(* com.crj.spring6.aop.annotationAop.CalculatorImpl.*(..))",returning = "result")public void afterReturnMethods(JoinPoint joinPoint,Object result){//增强方法的名字String methodName = joinPoint.getSignature().getName();//增强方法的参数Object[] args = joinPoint.getArgs();//增强方法的返回值 resultSystem.out.println("Logger-.->返回通知,方法名:"+methodName+",参数:"+ Arrays.toString(args)+",目标方法的返回值:"+result);}}

异常通知

 在异常通知中可以通过throwing属性获得目标增强方法执行异常抛出的信息。

package com.crj.spring6.aop.annotationAop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;//切面类
@Aspect  //切面类
@Component  //IOC容器
public class LogAspect {//异常通知@AfterThrowing(value = "execution(* com.crj.spring6.aop.annotationAop.CalculatorImpl.*(..))",throwing = "ex")public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("Logger-.->异常通知,方法名:"+methodName+",参数:"+ Arrays.toString(args)+",异常方法的信息:"+ex);}
}

环绕通知 

环绕通知需要注意的是@Around注解下的方法参数类型选择的是ProceedingJoinPoint,借助该类型对象的proceed的方法来调用目标函数。

package com.crj.spring6.aop.annotationAop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;//切面类
@Aspect  //切面类
@Component  //IOC容器
public class LogAspect {//环绕通知@Around(value = "execution(* com.crj.spring6.aop.annotationAop.CalculatorImpl.*(..))")public Object aroundMethod(ProceedingJoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();String argString = Arrays.toString(args);Object result = null;try{System.out.println("环绕通知——>目标方法之前执行");//调用目标方法result = joinPoint.proceed();System.out.println("环绕通知——>目标方法返回值之后执行");}catch (Throwable throwable){System.out.println("环绕通知——>目标方法出现异常之后执行");}finally {System.out.println("环绕通知——>后置通知");}return result;}
}

2.3 重用切入点表达式

通过上述的四个通知例子我们发现,切入点表达式除了在一些特殊的场景需求下,其余的情况总是一样的,为了避免代码的冗余,我们可以通过重用切入点表达式来提高程序的可复用性。

//前置通知
@Before(value = "Pointcut()")
public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("Logger-.->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
} @Pointcut(value = "execution(* com.crj.spring6.aop.annotationAop.CalculatorImpl.*(..))")
public void pointCut(){}

注意:上述是在同一个切面中的定义,在不同的切面中的话需要在通知的切入点表达式加上重用函数的全类名 

2.4 切面的优先级 

相同目标方法上同时存在多个切面时,可以通过切面的优先级控制切面的内外嵌套顺序:

  • 优先级高的切面:外面
  • 优先级低的切面:里面

主要是通过使用@Order注解来设定切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低 

三、基于XML方式实现AOP

这里只需要将前面的注解部分去掉,并通过一个xml文件来配置好切面类的设置即可。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--开启组件扫描--><context:component-scan base-package="com.crj.spring6.aop.xmlaop"></context:component-scan><!--配置AOP的五种通知类型--><aop:config><!--配置切面类--><aop:aspect ref="logAspect"><!--配置切入点--><aop:pointcut id="pointcut" expression="execution(* com.crj.spring6.aop.annotationAop.CalculatorImpl.*(..))"/><!--配置五种通知类型--><!--前置--><aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before><!--后置--><aop:after method="aroundMethod" pointcut-ref="pointcut"></aop:after><!--返回--><aop:after-returning method="afterReturnMethods" returning="result" pointcut-ref="pointcut"></aop:after-returning><!--异常--><aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointcut"></aop:after-throwing><!--环绕--><aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around></aop:aspect></aop:config>
</beans>

总结

        哈哈,学到这里荔枝总算把最最重要的部分学完了,马上就可以上手项目实操了,真实有点迫不及待呢哈哈哈哈哈哈~~~希望荔枝梳理的会对有需要的小伙伴有帮助,接下来荔枝可能会继续学习和梳理并总结产出,在暑假结束后荔枝也会对项目进行总结和复盘,希望那时的荔枝会离大佬更进一步哈哈哈哈哈哈。

今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

远程访问本地mysql

文章目录 一、设置本地mysql允许外部访问找到mysql配置文件my.ini &#xff0c;linux环境是my.cnf配置mysql配置文件 二、创建外部访问的mysql用户三、配置mysql用户的权限四、配置防火墙端口五、连接查看本地ip地址 参考 连接命令 mysql -h <host> -P <port> -u &…

Linux【网络基础】IP协议

文章目录 一、IP协议&#xff08;1&#xff09;IP地址协议概念和理解&#xff08;2&#xff09;IP地址协议格式&#xff08;3&#xff09;网络号和主机号&#xff08;4&#xff09;地址管理&#xff08;一&#xff09;分配IP地址方法&#xff08;二&#xff09;CIDR分配IP地址&…

【C++】类和对象(上)

1.面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将意见事情拆分为不同的对象&#xff0c;靠对象之间的交互完成。 …

慕课网Go-4.package、单元测试、并发编程

package 1_1_User.go package usertype User struct {Name string }1_1_UserGet.go package userfunc GetCourse(c User) string {return c.Name }1_1_UserMain.go package mainimport ("fmt"Userch03 "goproj/IMOOC/ch03/user"//别名&#xff0c;防止同名…

uniapp跨域解决

uniapp跨域解决 跨域是什么 跨域指的是浏览器不能执行其他网站的脚本&#xff0c;当一个网页去请求另一个域名的资源时&#xff0c;域名、端口、协议任一不同&#xff0c;就会存在跨域。跨域是由浏览器的同源策略造成的&#xff0c;是浏览器对JavaScript施加的安全限制。 报错…

Wi-Fi 6技术详解

1. 介绍 Wi-Fi 6&#xff0c;也称为802.11ax&#xff0c;是Wi-Fi技术的最新标准。它是对之前标准Wi-Fi 5&#xff08;802.11ac&#xff09;的升级和改进&#xff0c;旨在提供更高的速度、更大的容量、更好的性能和更高的可靠性。Wi-Fi 6技术的引入为无线网络带来了革命性的变化…

机柜PDU的选购也有大学问——与机柜PDU相关的那些事儿

在各行各业数据中心机房供配电建设过程中&#xff0c;机柜专用PDU电源插座看似是一个较为简单的用电设备&#xff0c;事实上又不那么简单。机柜PDU&#xff0c;是为安装在机柜内部的IT设备提供电源分配、管理的末端配电设备&#xff0c;在不同的工作场合对于PDU的规格要求也是不…

寻找峰值——力扣162

文章目录 题目描述法一 寻找最大值法二 二分法 题目描述 法一 寻找最大值 int findPeakElement(vector<int>& nums){return max_element(nums.begin(), nums.end()) - nums.begin();}法二 二分法 int findPeakElement(vector<int>& nums) {int l 0, r n…

目标检测中 anchor base和anchor free

目标检测中两种不同anchor的生成 趋势&#xff1a;anchor free越来越受到实时性检测的青睐&#xff0c;&#xff0c;&#xff0c;

二分图匹配算法

二分图匹配算法是一种用于解决二分图最大匹配问题的算法。 二分图&#xff1a; 在圖論中&#xff0c;二部圖&#xff08;bipartite graph&#xff09;是一類特殊的圖&#xff0c;又稱為、偶图、雙分圖。二分圖的頂點可以分成兩個互斥的独立集 U 和 V 的圖&#xff0c;使得所有…

机器人科普--AGILOX 叉车

机器人科普--AGILOX 叉车 1 概述2 导航3 驱动轮组4 叉举参考 1 概述 AGILOX 叉车&#xff0c;不需要画地图路径&#xff0c;很厉害。 2 导航 中间路径自由导航&#xff0c;末端规划出轨迹路线&#xff0c;并使用优良的控制器做轨迹追踪。 AGILOX &#xff5c; 10 Min setu…

Spring依赖注入

文章目录 前言1.依赖注入简介2. setter注入3. 构造器注入4. 自动装配 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&#xff0c;方便日后回顾。当然&#xff0c;如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚&#xff0…

商城免费搭建之java商城 开源java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c bbc

&#xfeff; 1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、R…

TCP拥塞控制详解 | 1. 概述

网络传输问题本质上是对网络资源的共享和复用问题&#xff0c;因此拥塞控制是网络工程领域的核心问题之一&#xff0c;并且随着互联网和数据中心流量的爆炸式增长&#xff0c;相关算法和机制出现了很多创新&#xff0c;本系列是免费电子书《TCP Congestion Control: A Systems …

vue3搭建Arco design UI框架

技术&#xff1a;Vue3.2.40 UI框架&#xff1a;Arco design 2.44.7 需要安装:yarn 1.22.19 和npm 8.19.4 1.第一步安装本地全局arco脚手架 管理员运行CMD npm i -g arco-cli安装成功后如下&#xff1a; 2.第二步在需要存放项目的文件夹拉取项目 我这里把项目存放在 D:\W…

计算机网络基础(静态路由,动态路由,公网IP,私网IP,NAT技术)

文章目录 一&#xff1a;静态路由和动态路由二&#xff1a;静态路由的配置路由信息的方式演示三&#xff1a;默认路由四&#xff1a;公网IP和私网IP和NAT技术的基本理解 一&#xff1a;静态路由和动态路由 在说静态路由和动态路由前&#xff0c;我们需要来了解一下&#xff0…

【 Redis】的乱码问题

问题描述&#xff1a; 使用RedisTemplate存储的数据&#xff0c;在 redis-cli 客户端查看时&#xff0c;key 和 value 都会携带类似\xac\xad\这样的字符串。 原因&#xff1a; 由于默认使用了 jdk 的序列化方式。以下是支持的序列化方式 项目一般都会有缓存&#xff0c;常常…

go练习 day01

DTO: note_dto.go package dtoimport "king/model"type NoteAddDTO struct {ID uintTitle string json:"title" form:"title" binding:"required" message:"标题不能为空"Content string json:"conten…

Live Market做世界C端用户数据的耕耘,数据和流量的价值呈现

在数字化时代&#xff0c;数据成为了推动经济增长和商业发展的重要资源&#xff0c;而流量则是数据价值的体现和传递媒介。随着全球互联网的普及和移动设备的智能化&#xff0c;C端用户数据的收集和分析变得尤为重要。在这个领域&#xff0c;有一家专注于世界C端用户数据耕耘的…

分享18个用于处理 null、NaN 和undefined 的 JS 代码片段

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏:《 VUE 》 《 javaScript 》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 目录 前言 内容 &#x1f4df; 1. 检查是否为null&#xff1a; &#x1f4df; 2. 检查undefined&#xff1a; &#x1…