SSM框架,spring-aop的学习

代理模式

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

可以将重复的非核心的代码写在代理类中,将核心的代码写在目标方法中,每次通过调用代理类的方法来间接调用目标方法,以方便统一管理重复的非核心的代码。

静态代理

每个目标方法都有其对应的代理类中的代理方法,重复的非核心的代码都写在代理类中,例:

public class StaticProxyCalculator implements Calculator {private Calculator calculator;public StaticProxyCalculator(Calculator calculator) {this.calculator = calculator;}@Overridepublic int add(int i, int j) {System.out.println("i = " + i + ", j = " + j);int result = calculator.add(i,j);System.out.println("result = " + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("i = " + i + ", j = " + j);int result = calculator.sub(i,j);System.out.println("result = " + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("i = " + i + ", j = " + j);int result = calculator.mul(i,j);return result;}@Overridepublic int div(int i, int j) {System.out.println("i = " + i + ", j = " + j);int result = calculator.div(i,j);System.out.println("result = " + result);return 0;}
}

但是这种方法还是有大量重复,也没有很好地解决统一管理的问题,也不具备灵活性,所以要使用动态代理的方法

动态代理

动态代理技术分类

  • JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口,他会根据目标类的接口动态生成一个代理对象,代理对象和目标对象有相同的接口。
  • cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口。

以jdk代理为例(了解即可)

代理类:

public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxy(){/*** newProxyInstance():创建一个代理实例* 其中有三个参数:* 1、classLoader:加载动态生成的代理类的类加载器* 2、interfaces:目标对象实现的所有接口的class对象所组成的数组* 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法*/ClassLoader classLoader = target.getClass().getClassLoader();Class<?>[] interfaces = target.getClass().getInterfaces();InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** proxy:代理对象* method:代理对象需要实现的方法,即其中需要重写的方法* args:method所对应方法的参数*/Object result = null;try {System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));result = method.invoke(target, args);System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);} catch (Exception e) {e.printStackTrace();System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());} finally {System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");}return result;}};return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);}
}
public class Test {/*** 静态代理*/@org.junit.jupiter.api.Testpublic void test1(){Calculator target = new CalculatorPureImpl();Calculator calculator = new StaticProxyCalculator(target);calculator.add(3,4);}/*** 动态代理*/@org.junit.jupiter.api.Testpublic void test2(){//创建目标对象Calculator target = new CalculatorPureImpl();//获取代理对象ProxyFactory factory = new ProxyFactory(target);Calculator proxy = (Calculator) factory.getProxy();proxy.add(3,4);}
}

不需要编写代理代码,我们可以使用Spring AOP实现代理

面向切面编程(AOP)

面向切面编程(Aspect Oriented Programming)是对面向对象编程的补充和完善。面向对象编程是通过纵向的继承和实现来实现功能的管理。而面向切面编程是将代码中的重复的非核心的业务提取到一个公共的地方,通过动态代理技术横向插入到各个方法中去。解决非核心代码的冗余问题。

关于AOP的几个名词

横切关注点

从每一个方法中抽取出来的同一类非核心业务。业务处理的主要流程是核心关注点,其他非主要流程就是横切关注点。

通知(增强)

每一个横切关注点上要做的事情都需要一个方法来实现,这样的方法称之为通知方法

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

连接点

指那些被拦截的点,在Spring中,指可以被动态代理拦截目标类的方法

切入点

定位连接点的方式,被选中的连接点

切面(aspect)

切入点和通知的结合,指的是通知类

目标(target)

被代理的目标对象

代理(proxy)

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

织入(weave)

指把通知应用到目标上,生成代理对象的过程。可以再编译期间织入,也可以再运行期间织入,spring使用运行期间织入

Spring AOP框架

Spring AOP框架,是基于AOP编程思维,封装动态代理技术,简化动态代理技术实现的框架。SpringAOP内部帮助实现动态代理,只需写少量的配置,指定生效范围即可,即可完成面向切面思维编程的实现

aop只能对ioc容器内的对象创建代理对象,代理对象会存储到ioc容器

基于注解方式实现

底层技术组成

  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口。
  • cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口。
  • AspectJ:早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。

aop的实现步骤:

  1. 正常写核心业务和配置ioc
  2. 定义增强类,定义增强方法
  3. 配置增强类
  4. 开启aop的配置

aop大致实现例子:

增强类:

/***内部存储增强代码
*/
@Component
@Aspect
public class LogAdvice {@Before("execution(public int com.ergou.service.impl.CalculatorPureImpl.add(int,int))")public void start(){System.out.println("方法开始了");}@After("execution(public int com.ergou.service.impl.CalculatorPureImpl.add(int,int))")public void after(){System.out.println("方法结束了");}@AfterThrowing("execution(public int com.ergou.service.impl.CalculatorPureImpl.add(int,int))")public void error(){System.out.println("方法报错了");}
}

配置类配置aop

@Configuration
@ComponentScan("com.ergou")
//让程序允许使用AspectJ注解
@EnableAspectJAutoProxy
public class JavaConfig {
}

xml文件配置aop(和配置类二选一)

<context:component-scan base-package="com.ergou"/>
<aop:aspectj-autoproxy/>

原来的业务代码不需要做改动

测试:

@SpringJUnitConfig(value = JavaConfig.class)
public class Test {
//
@Autowiredprivate Calculator calculator;@org.junit.jupiter.api.Testpublic void test1(){int add = calculator.add(3,4);System.out.println(add);}
}

注:使用jdk的动态代理,代理类和目标对象会有同样的接口,要使用代理功能,要用目标对象的接口获取ioc容器中的对象(获取到的将会是代理对象而不是目标对象,也证明ioc中真正存储的对象是代理对象而不是目标对象)

cglib的生效场景是目标方法的类没有实现接口

五种常见的通知注解

  • @Before: 前置通知
  • @AfterReturning:后置通知
  • @Around: 环绕通知
  • @AfterThrowing:异常通知
  • @After :最终通知

获取通知细节信息

需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。

  • JoinPoint 接口通过 getSignature() 方法获取目标方法的签名(方法声明时的完整信息)
  • 通过目标方法签名对象获取方法名
  • 通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组
@Before("execution(* com.ergou.service.impl.*.*(..))")
public void start(JoinPoint joinPoint){
//获取方法所属的目标类的信息
String simpleName = joinPoint.getTarget().getClass().getSimpleName();System.out.println(simpleName);
//获取方法名称
String name = joinPoint.getSignature().getName();System.out.println(name);
//获取访问修饰符
int modifiers = joinPoint.getSignature().getModifiers();String s = Modifier.toString(modifiers);
//获取参数列表
Object[] args = joinPoint.getArgs();for (Object o :args) {System.out.println(o);}
}

获取返回结果,要在@AfterReturning注解标注的方法内进行,先在方法的形参列表中加一个Object类型的形参变量(假设变量名取为result)用来接收返回值信息,在@AfterReturning注解中,加上属性returning,其值为接收返回值信息的形参变量名result,例:

@AfterReturning(value = "execution(* com.ergou.service.impl.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){System.out.println(result);
}

获取异常信息,与获取返回结果类似。要在@AfterThrowing注解标注的方法内进行,先在方法的形参列表中加一个Throwable类型的形参变量(假设变量名取为throwable)用来接收异常信息,在@AfterThrowing注解中加上属性throwing,其值为接收异常信息的形参变量名throwable,例:

@AfterThrowing(value = "execution(* com.ergou.service.impl.*.*(..))",throwing = "throwable")
public void afterThrowing(JoinPoint joinPoint,Throwable throwable){throwable.getStackTrace();
}

切点表达式

切点表达式作用

AOP切点表达式(Pointcut Expression)是一种用于指定切点的语言,它可以通过定义匹配规则,来选择需要被切入的目标对象

切点表达式写在通知注解的value属性中,如果没有其他属性,可以省略value=

切点表达式语法

 

语法细节

  • ..不能作为开头,可以写做*..
  • 参数列表部分,如果写做(String..),意思是参数列表只要开头的第一个参数是String类型的即可,后面随意。(..String)即为结尾为String类型的参数,前面随意。(String..int)同理。

切点表达式的提取和复用

当前类中提取:

步骤:

  1. 定义一个空方法,加上@Pointcut,在注解的括号中写入指定的切点表达式
  2. 在通知注解中的value属性中直接写@Pointcut注解标记的方法的方法名加括号即可

例:

@Aspect
@Component
public class MyAdvice {@Pointcut("execution(* com.ergou.service.impl.*.*(..))")public void pc(){}@Before("pc()")public void start(JoinPoint joinPoint){
//获取方法所属的目标类的信息
String simpleName = joinPoint.getTarget().getClass().getSimpleName();System.out.println(simpleName);
//获取方法名称
String name = joinPoint.getSignature().getName();System.out.println(name);
//获取访问修饰符
int modifiers = joinPoint.getSignature().getModifiers();String s = Modifier.toString(modifiers);
//获取参数列表
Object[] args = joinPoint.getArgs();for (Object o :args) {System.out.println(o);}}@AfterReturning(value = "pc()",returning = "result")public void afterReturning(JoinPoint joinPoint,Object result){System.out.println(result);}@After("pc()")public void after(){}@AfterThrowing(value = "pc()",throwing = "throwable")public void afterThrowing(JoinPoint joinPoint,Throwable throwable){throwable.getStackTrace();}
}

创建一个存储切点的类单独维护切点表达式:

  1. 创建一个类,用来存储切点表达式,里面同样用@Pointcut注解来存储切点表达式
  2. 通知注解中的value属性中写上要引用的切点表达式所在类的全限定符加上.方法名加括号

例:

@Component
public class MyPointcut {@Pointcut("execution(* com.ergou.service.impl.*.*(..))")public void pc(){}
}

这个存储切点表达式的类同样也要放进ioc容器

@AfterReturning(value = "com.ergou.pointcut.MyPointcut.pc()",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){System.out.println(result);
}

环绕通知

环绕通知对应整个 try...catch...finally 结构,包括前面四种通知的所有功能。

环绕通知使用步骤:

  1. 在通知类中写一个环绕通知方法,参数列表中写一个ProceedingJoinPoint类型的参数,此参数会自动接收目标方法的信息。并在此环绕通知方法上方写上@Around注解,其value属性指定切点表达式
  2. 环绕通知需要在通知中,定义目标方法的执行。目标方法的执行代码为Object result = joinPoint.proceed(args)。其中,result是用来接收目标方法的返回值的对象,因为result最后还要被环绕通知方法返回;args是传入目标方法的参数,通过调用joinPoint对象的相关方法来获取。
  3. 用 try...catch...finally 结构将执行代码围绕起来,就可以在相应的位置放通知功能的代码
  4. 最后return返回接收了目标方法的返回值的result对象

例:

/***环绕通知需要在通知中,定义目标方法的执行
* @paramjoinPoint接收了目标方法的信息 比起之前的JoinPoint类型的对象,多了一个执行的功能
* @return*/
@Around("com.ergou.pointcut.MyPointcut.pc()")
public Object transaction(ProceedingJoinPoint joinPoint){Object[] args = joinPoint.getArgs();Object result = null;try {
//前置通知代码
System.out.println("开启事务");result = joinPoint.proceed(args);
//后置通知代码
System.out.println("提交事务");} catch (Throwable e) {
//异常通知代码
System.out.println("事务回滚");throw new RuntimeException(e);} finally {
//最终通知代码
System.out.println("必须执行的代码");}return result;
}

切面优先级设置

切面优先级是指要调用目标方法时,如果有多个切面,优先级高的切面的通知方法是在外层。

使用@Order注解标记在通知类上,在其中指定一个数字,值越小,优先级越高。

@Component
@Aspect
//值越小,优先级越高
@Order(2)
public class TxAdvice {@Before("com.ergou.pointcut.MyPointcut.pc()")public void begin(){}@AfterReturning("com.ergou.pointcut.MyPointcut.pc()")public void commit(){}@AfterThrowing("com.ergou.pointcut.MyPointcut.pc()")public void rollback(){}
}

优先级高的因为在外层,所以前置先执行,后置后执行

xml方式配置aop

了解即可,主要使用注解方式

<!-- 配置目标类的bean -->
<bean id="calculatorPure" class="com.atguigu.aop.imp.CalculatorPureImpl"/><!-- 配置切面类的bean -->
<bean id="logAspect" class="com.atguigu.aop.aspect.LogAspect"/><!-- 配置AOP -->
<aop:config><!-- 配置切入点表达式 --><aop:pointcut id="logPointCut" expression="execution(* *..*.*(..))"/><!-- aop:aspect标签:配置切面 --><!-- ref属性:关联切面类的bean --><aop:aspect ref="logAspect"><!-- aop:before标签:配置前置通知 --><!-- method属性:指定前置通知的方法名 --><!-- pointcut-ref属性:引用切入点表达式 --><aop:before method="printLogBeforeCore" pointcut-ref="logPointCut"/><!-- aop:after-returning标签:配置返回通知 --><!-- returning属性:指定通知方法中用来接收目标方法返回值的参数名 --><aop:after-returningmethod="printLogAfterCoreSuccess"pointcut-ref="logPointCut"returning="targetMethodReturnValue"/><!-- aop:after-throwing标签:配置异常通知 --><!-- throwing属性:指定通知方法中用来接收目标方法抛出异常的异常对象的参数名 --><aop:after-throwingmethod="printLogAfterCoreException"pointcut-ref="logPointCut"throwing="targetMethodException"/><!-- aop:after标签:配置后置通知 --><aop:after method="printLogCoreFinallyEnd" pointcut-ref="logPointCut"/><!-- aop:around标签:配置环绕通知 --><!--<aop:around method="……" pointcut-ref="logPointCut"/>--></aop:aspect>
</aop:config>

根据类型装配 bean

  1. 情景一

    • bean 对应的类没有实现任何接口
    • 根据 bean 本身的类型获取 bean
      • 测试:IOC容器中同类型的 bean 只有一个

        正常获取到 IOC 容器中的那个 bean 对象

      • 测试:IOC 容器中同类型的 bean 有多个

        会抛出 NoUniqueBeanDefinitionException 异常,表示 IOC 容器中这个类型的 bean 有多个

  2. 情景二

    • bean 对应的类实现了接口,这个接口也只有这一个实现类
      • 测试:根据接口类型获取 bean
      • 测试:根据类获取 bean
      • 结论:上面两种情况其实都能够正常获取到 bean,而且是同一个对象
  3. 情景三

    • 声明一个接口
    • 接口有多个实现类
    • 接口所有实现类都放入 IOC 容器
      • 测试:根据接口类型获取 bean

        会抛出 NoUniqueBeanDefinitionException 异常,表示 IOC 容器中这个类型的 bean 有多个

      • 测试:根据类获取bean

        正常

  4. 情景四(jdk代理情景)

    • 声明一个接口

    • 接口有一个实现类

    • 创建一个切面类,对上面接口的实现类应用通知

      • 测试:根据接口类型获取bean

        正常

      • 测试:根据类获取bean

        无法获取 原因分析:

    • 应用了切面后,真正放在IOC容器中的是代理类的对象

    • 目标类并没有被放到IOC容器中,所以根据目标类的类型从IOC容器中是找不到的

  5. 情景五(cglib代理场景)

    1. 声明一个类
    2. 创建一个切面类,对上面的类应用通知
      1. 测试:根据类获取 bean,能获取到

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

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

相关文章

PPT导出PDF时保持图像高清的方法

问题: 我们经常会发现&#xff0c;在PPT中插入的图片非常高清&#xff0c;但是通过PPT转换为PDF之后&#xff0c;图片就会出现不同程度的失真。 问题产生的原因: 这是因为Acrobat的PDF Maker在将PPT转换为PDF的时候&#xff0c;对PPT中的图片进行了压缩 Solution: 在PPT的…

博途PLC数值积分器(矩形梯形积分自由切换)

数值积分器的相关介绍,大家可以也可以参看下面几篇文章,链接如下: PLC算法系列数值积分器 https://rxxw-control.blog.csdn.net/article/details/128562853https://rxxw-control.blog.csdn.net/article/details/128562853SMART PLC 梯形和矩形积分 https://rxxw-control.…

【数据结构】18 二叉搜索树(查找,插入,删除)

定义 二叉搜索树也叫二叉排序树或者二叉查找树。它是一种对排序和查找都很有用的特殊二叉树。 一个二叉搜索树可以为空&#xff0c;如果它不为空&#xff0c;它将满足以下性质&#xff1a; 非空左子树的所有键值小于其根节点的键值非空右子树的所有键值都大于其根结点的键值左…

2.16学习总结

1.邮递员送信&#xff08;dijkstra 不只是从起到到目标点&#xff0c;还要走回去&#xff09; 2.炸铁路(并查集) 3.统计方形&#xff08;数据加强版&#xff09;&#xff08;排列组合&#xff09; 4.滑雪&#xff08;记忆化&#xff09; 5.小车问题&#xff08;数学问题&#x…

无人机系统组装与调试,多旋翼无人机组装与调试技术详解,无人机飞控系统原理

多旋翼无人机飞控系统的组装 在开始组装前&#xff0c;确保您已准备好所有必要的工具和材料。这包括螺丝刀、电烙铁、焊台、杜邦线、飞控板、GPS模块、电机、桨叶等。 飞控安装 安全开关安装&#xff0c;将安全开关固定在机架上。将安全开关的线插到飞控SWITCH插口上。 电调…

【半监督图像分割 2023 】BHPC

【半监督图像分割 2023 】BHPC 论文题目&#xff1a;Semi-supervised medical image segmentation via hard positives oriented contrastive learning 中文题目&#xff1a;通过面向硬阳性的对比学习进行半监督医学图像分割 论文链接&#xff1a; 论文代码&#xff1a;https:/…

亚马逊、国际站、速卖通新店怎么销量破冰?自养号测评爆款打造思路

亚马逊作为全球最大的电子商务平台之一&#xff0c;吸引了众多卖家进驻其平台。对于新店铺来说&#xff0c;如何在竞争激烈的市场中突破销量瓶颈&#xff0c;成为卖家们关注的焦点。 一、亚马逊新店怎么销量破冰&#xff1f; 优化商品信息&#xff1a;在亚马逊平台上&#xff…

牛客网SQL进阶128:未完成试卷数大于1的有效用户

官网链接&#xff1a; 未完成试卷数大于1的有效用户_牛客题霸_牛客网现有试卷作答记录表exam_record&#xff08;uid用户ID, exam_id试卷ID, st。题目来自【牛客题霸】https://www.nowcoder.com/practice/46cb7a33f7204f3ba7f6536d2fc04286?tpId240&tqId2183007&ru%2…

【深度学习】Pytorch 系列教程(三):PyTorch数据结构:2、张量的数学运算(1):向量运算(加减乘除、数乘、内积、外积、范数、广播机制)

文章目录 一、前言二、实验环境三、PyTorch数据结构0、分类1、Tensor&#xff08;张量&#xff09;1. 维度&#xff08;Dimensions&#xff09;2. 数据类型&#xff08;Data Types&#xff09;3. GPU加速&#xff08;GPU Acceleration&#xff09; 2、张量的数学运算1. 向量运算…

蓝桥杯真题:扑克牌移动

import java.util.*; public class Main {public static List moveCard(List src){if(srcnull) return null;List dst new Vector();for(;;){if(src.size()0) break; // 填空src.add(src.remove(0));//remove(0) 是一个 List 接口中的方法调用&#xff0c;它表示移除列表中指…

Sentinel注解@SentinelResource详解

Sentinel注解SentinelResource详解 熔断 针对访问超过限制【sentinel中配置的限制】的资源&#xff0c;通过java代码配置&#xff0c;返回一个自定义的结果&#xff0c;需要用到 SentinelResource 注解的 blockHandlerClass 和 blockHandler 属性。 blockHandlerClass&#…

数据集合

目录 并集 union union all 区别 交集 intersect 差集 minus 错误操作 Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 常用的数学集合有&#xff1a;交集、并集、差集、补集 每一次查询实际上都会返回数据集合&#xff0c;…

【机器学习】机器学习常见算法详解第4篇:KNN算法计算过程(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论机器学习算法相关知识。机器学习算法文章笔记以算法、案例为驱动的学习&#xff0c;伴随浅显易懂的数学知识&#xff0c;让大家掌握机器学习常见算法原理&#xff0c;应用Scikit-learn实现机器学习算法的应用&#xff0…

房屋租赁系统的Java实战开发之旅

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

第11章 GUI

11.1 Swing概述 Swing是Java语言开发图形化界面的一个工具包。它以抽象窗口工具包&#xff08;AWT&#xff09;为基础&#xff0c;使跨平台应用程序可以使用可插拔的外观风格。Swing拥有丰富的库和组件&#xff0c;使用非常灵活&#xff0c;开发人员只用很少的代码就可以创建出…

免费申请一个美国EDU学生邮箱

EDU邮箱的作用 例如大名鼎鼎的GitHub学生包。包含各种服务器的优惠卷&#xff0c;可以让你免费使用1-2年的服务器。免费的域名。免费的网站证书。太多了。 微软&#xff1a;免费的5T的OneDrive账户。 Google&#xff1a;G Sutie Drive无限容量 微软、苹果、AWS、都有针对学…

二叉树前序中序后序遍历(非递归)

大家好&#xff0c;又和大家见面啦&#xff01;今天我们一起去看一下二叉树的前序中序后序的遍历&#xff0c;相信这个对大家来说是信手拈来&#xff0c;但是&#xff0c;今天我们并不是使用常见的递归方式来解题&#xff0c;我们采用迭代方式解答。我们先看第一道前序遍历 1…

CCF编程能力等级认证GESP—C++6级—20231209

CCF编程能力等级认证GESP—C6级—20231209 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)闯关游戏工作沟通 答案及解析单选题判断题编程题1编程题2 单选题…

js中数字精度丢失问题详解(如何解决)

文章目录 一、场景复现二、浮点数三、问题分析小结 四、解决方案参考文献 一、场景复现 一个经典的面试题 0.1 0.2 0.3 // false为什么是false呢? 先看下面这个比喻 比如一个数 130.33333333...... 3会一直无限循环&#xff0c;数学可以表示&#xff0c;但是计算机要存…

mysql 2-17

UNION关键字和UNION ALL 自然连接 USING使用 函数 单行函数 基本函数 三角函数 指数和对数 进制间的转换 字符串函数 时间和日期函数 计算日期和时间的函数 日期的格式化和解析 流程控制函数