面试总结之Spring篇

news2023/12/2 7:55:10

一、AOP

1、什么是AOP

1.1、概述

  • AOP(Aspect-Oriented Programming):面向切面编程,即把一些业务逻辑中的相同代码抽取出来,让业务逻辑更加简练清爽
    在这里插入图片描述
  • 如果要CRUD写一堆业务,可如何实现业务代码前后进行打印日志和参数的校验?
    可以把日志记录和数据校验可重用的功能模块分离出来,然后在程序合适的位置动态地植入这些代码并执行,如此,让业务逻辑只包含核心的业务代码,而没有通用逻辑的代码,使业务模块更简洁,实现了业务逻辑和通用逻辑的代码分离,便于维护和升级,降低了业务逻辑和通用逻辑的耦合性
    在这里插入图片描述
  • AOP可以将遍布应用的功能分离出来形成可重用的组件,在编译期间、装载期间或运行期间实现给原程序动态添加功能(在不修改源代码的情况下),从而实现对业务逻辑的隔离,提高代码的模块化能力
    在这里插入图片描述
  • AOP的核心是动态代理,如果实现了接口,就使用JDK的动态代理,不然就使用CGLIB代理,主要应用于处理具有横切性质的系统级功能,如日志收集、事务管理、安全检查、缓存、对象池管理等

1.2、AOP的核心概念

  • 目标对象(Target):代理的目标对象
    在这里插入图片描述
  • 切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象,在Spring中,通过@Aspect注解声明当前类为切面,一般要在切面定义切入点和通知
  • 连接点(JoinPoint):被拦截的点,由于Spring只支持方法类型的连接点,所以在Spring中,连接点指的是被拦截到的方法,实际上连接点还可以是字段或者构造器
  • 切点(PointCut):带有通知的连接点,在程序中主要体现为书写切入点表达式
    在这里插入图片描述
// 以自定义注解 @CustomLog为切点
@Pointcut("@annotation(com.example.interviewStudy.annotation.CustomLog)")
public void logPcut() {
}
  • 通知(Advice):拦截到连接点之后要执行的代码,也称作增强
  • 织入(Weave):将切面/ 切面类和目标类动态接入
    编译器织入:切面在目标类编译时织入
    类加载期织入:切面在目标类加载到JVM时织入,需要特殊的类加载器,可以在目标类被引入应用之前增强该目标类的字节码,AspectJ采用编译期织入和类加载器织入
    运行期织入:切面在应用运行的某时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象,这也是SpringAOP织入切面的方式
  • 增强器(advisor):筛选类中的哪些方法是连接点(哪些方法需要被拦截)
  • 引介(introduction):⼀种特殊的增强,可以动态地为类添加⼀些属性和方法

1.3、AOP的环绕方式

AOP有五种通知的方式:

  • 前置通知 (@Before):在切入点方法执行之前执行
  • 环绕通知 (@Around):手动调用切入点方法并对其进行增强的通知方式
  • 后置通知 (@After):在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行
  • 异常通知 (@AfterThrowing):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行
  • 返回通知 (@AfterReturning):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行

当有多个切面的情况下,可以通过 @Order指定先后顺序,数字越小,优先级越高

2、AOP在项目中的运用

2.1、日志输出

  • 在SpringBoot项目中,使用AOP 打印接口的入参和出参日志,以及执行时间
    1)引入依赖
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>
  • 2)自定义注解 作为切入点
import java.lang.annotation.*;

@Target({ElementType.METHOD})   // 指定注解使用在方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomLog {

    String info();
}
  • 3)配置AOP切面
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect   // 标识当前类为切面
@Component
public class CustomLogAspect {

    // getLogger(Class<?> clazz)
    public static final Logger logger = LoggerFactory.getLogger(CustomLogAspect.class);

    // 以自定义注解 @CustomLog为切点
    @Pointcut("@annotation(com.example.interviewStudy.annotation.CustomLog)")
    public void logPcut() {
    }

    // 前置通知: 在切点之前织入
    @Before("logPcut()")
    public void doBefore(JoinPoint joinPoint) throws JsonProcessingException {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("========== 开始打印请求参数 ===========");
        logger.info("URL: {}", request.getRequestURL().toString());
        logger.info("HTTP Method: {}", request.getMethod());
        logger.info("Controller的全路径 和 执行方法: {} , {}方法", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        logger.info("请求入参:{}", new ObjectMapper().writeValueAsString(joinPoint.getArgs()));
    }

    // 后置通知,在切入点之后织入
    @After("logPcut()")
    public void doAfter() {
        logger.info("======== 请求日志输出完毕 ========");
    }

    /**
     * 环绕通知: ProceedingJoinPoint对象调用proceed方法,实现 原本目标方法的调用
     *   ProceedingJoinPoint 只支持环绕通知,如果其他通知也采用ProceedingJoinPoint作为连接点,就会出现异常
     *   ==> Caused by: java.lang.IllegalArgumentException: ProceedingJoinPoint is only supported for around advice
     * */
    @Around("logPcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        long start = System.currentTimeMillis();

        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();

        logger.info("请求结果: {}", new ObjectMapper().writeValueAsString(result));
        logger.info("请求处理耗时: {} ms", (end - start));
        return result;
    }
}
  • 4)在接口上添加自定义注解
@RestController
@RequestMapping("/aop")
public class CustomAspectController {

    @GetMapping("/hello")
    @CustomLog(info = "hello,使用AOP实现请求日志输出")
    public String hello(String uname) {
        return "Hello,welcome to studing AOP, your name is " + uname;
    }
}

执行结果:
在这里插入图片描述

3、JDK和CGLIB的动态代理

  • 动态代理主要有JDK动态代理和CGLIB的动态代理

1)JDK动态代理

  • Interface:对于JDK动态代理,目标类需要实现一个Interface
  • InvocationHandler:通过实现InvocationHandler接口,定义横切逻辑,再通过反射机制(invoke)调用目标类的方法,在此过程,可能包装逻辑,对目标方法进行前置/ 后置处理
  • Proxy:利用InvocationHandler动态创建一个符合目标类实现接口的实例,生成目标类的代理对象

我们来看⼀个常见的⼩场景,客服中转,解决⽤户问题:
在这里插入图片描述
代码实现:
在这里插入图片描述

  • 接口
public interface ISolver {
    public String solve();
}
  • 目标类:需要实现对应接口
public class ProblemSolver implements ISolver{
    @Override
    public String solve() {
        System.out.println("ProblemSolver,solve方法 ==> 问题正在解决中...");
        return "OKK";
    }
}
  • 动态代理工厂:ProxyFactory,直接用反射生成一个目标对象的代理对象,如下是用匿名内部类的方式重写了InvocationHandler的方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactory {

    // 维护一个目标对象
    private Object target;

    public ProxyFactory(Object target){
        this.target = target;
    }

    // 为目标对象生成代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("请描述您的问题:");
                        // 通过反射机制 调用目标对象方法
                        Object result = method.invoke(target, args);
                        System.out.println("问题已经得到解决!!");
                        // 返回 目标对象方法的返回值
                        return result;
                    }
                });
    }
}
  • 客户端:Client,生成一个代理对象实例,通过代理对象 调用目标对象的方法
public class Client {
    public static void main(String[] args) {
        ISolver developer = new ProblemSolver();
        // 创建代理对象实例
        ISolver instance = (ISolver) new ProxyFactory(developer).getProxyInstance();
        // 代理对象调用目标对象方法,得到目标方法的返回值并输出
        String res = instance.solve();
        System.out.println(res);
    }
}

执行结果:
在这里插入图片描述

2)CGLIB动态代理

  • 目标类(不需要像JDK动态代理一样实现接口):
public class CglibSolver {

    public String solve(){
        System.out.println("Testing implement proxy by cglib");
        return "CglibSolver ==> solve方法";
    }
}
  • 动态代理工厂:
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyFactory implements MethodInterceptor, Callback {

    private Object target;

    public ProxyFactory(Object target){
        this.target = target;
    }

    public Object getProxyInstance(){
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数,用于监听当前事件
        enhancer.setCallback(this);
        // 创建子类对象代理
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("请问有什么可以帮到您?");
        // 调用目标对象的方法
        Object result = method.invoke(target, args);
        System.out.println("问题得到解决啦哈!");

        return result;
    }
}
  • 客户端:Client
public class CgClient {
    public static void main(String[] args) {
        CglibSolver solver = new CglibSolver();
        // 创建代理对象
        CglibSolver proxy = (CglibSolver) new ProxyFactory(solver).getProxyInstance();
        // 通过代理对象实例调用目标对象方法
        String result = proxy.solve();
        System.out.println("result : " + result);

    }
}

执行结果(代理对象替目标对象执行调用方法):
在这里插入图片描述

4、Spring AOP和AspectJ AOP的区别

1) Spring AOP

Spring AOP属于运行时增强,主要具有如下特点:

  • 基于动态代理来实现,默认如果使用接口的方式来实现,则使用JDK提供的动态代理;如果是方法,则使用CGLIB来实现
  • Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现
  • 在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如Aspect好

2)AspectJ

  • AspectJ是功能强大的AOP框架,属于编译时增强,可以单独使用,也可以整合到其他框架中,是AOP编程的完全解决方案

  • AspectJ属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,生成的类没有额外运行时开销,可织入时机如下:
    A、编译期织入(Compile-time weaving):如 A类使用AspectJ添加了某属性,B类引用了A类,该场景就需要编译期进行织入,否则没法编译B类
    B、编译后织入(Post-compile weaving):在已生成了字节码/ class文件,或已经打包成jar包后,该情况需要增强,就需要使用到编译后织入
    C、类加载后织入(Load-time weaving):在加载类时进行织入

  • 两者整体对比如下:
    在这里插入图片描述

二、事务

1、Spring事务的种类

Spring支持编程式事务声明式事务管理两种方式:
1)编程式事务管理:使用TransactionTemplate,需要显示地执行事务
2)声明式事务管理:建立在AOP之上,其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,即在目标方法开始之前启动事务,在执行完目标方法之后根据执行情况 进行提交或回滚事务

  • 优点:不需要在业务逻辑代码中掺杂事务管理的代码,只需要在配置文件中进行相关的事务规则声明或通过 @Transactional注解声明事务(以及在启动类上添加@EnableTransactionManagement注解开启事务管理),将事务规则应用到业务逻辑中,减少业务代码的侵入
  • 缺点:最细粒度只能作用到方法级别,无法做到像编程式事务那样作用到代码块级别

2、声明式事务的失效情况

  • 1)@Transactional 应用在非public修饰的方法上
    在Spring AOP代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的intercept方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource
    的 computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息,
    computeTransactionAttribute会检查目标方法的修饰符是否为public,不是public则不会获取@Transactional的属性配置信息
    在这里插入图片描述
  • 2)@Transactional 注解属性 propagation设置错误
    TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
    TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则将当前事务挂起
    TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
    在这里插入图片描述
  • 3)@Transactional 注解属性 rollbackFor 设置错误
    rollbackFor可以指定能够触发事务回滚的异常类型,Spring默认抛出了未检查Unchecked异常(继承自RuntimeException的异常)或者Error才回滚事务,其他异常不会触发回滚事务
    在这里插入图片描述
// 希望自定义的异常可以进行回滚
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)

如果在目标方法中抛出的异常为rollbackFor指定的异常子类,事务同样回滚

  • 4)事务方法被同类中的其他方法调用
    开发中避免不了会对同一个类中的方法调用,比如
    当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理(使用Spring AOP动态代理造成)
    ==》 举例来说:
    Test类中的方法A调用本类的方法B(无论方法B是用public还是private修饰),B方法有声明注解事务,但A方法没有声明注解事务,当外部调用方法A后,方法B的事务不会起作用
    如果B方法内部抛了异常,而A方法此时通过try-catch捕获了B方法的异常,则该事务就不能正常回滚,会抛出异常:
org.springframework.transaction.UnexpectedRollbackException:Transaction rolled back because i t has been marked a s rollback - only

3、声明式事务的实现原理

4、Spring事务的隔离级别

  • Spring的接口TransactionDefinition定义了表示隔离级别的常量,主要是对应后端数据库的事务隔离级别:
  • ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL默认可重复读,Oracle默认读已提交
  • ISOLATION_READ_UNCOMMITTED:读未提交
  • ISOLATION_READ_COMMITTED:读已提交
  • ISOLATION_REPEATABLE_READ:可重复读
  • ISOLATION_SERIALIZABLE:串行化

5、Spring事务的传播机制

未完待续…

三、MVC

1、SpringMVC的工作流程

在这里插入图片描述

  • 1)客户端向服务端发送一次请求,该请求会先到前端控制器/ 中央控制器 DispatcherServlet
  • 2)DispatcherServlet 接收到请求后会调用HandlerMapping处理器映射器,由此得知,该请求由哪个Controller来处理(此时并不调用Controller)
  • 3)DispatcherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器应该要去执行哪个Controller
  • 4)HandlerAdapter处理器适配器去执行Controller并得到ModelAndView(数据和视图),并层层返回给DispatcherServlet
  • 5)DispatcherServlet 将 ModelAndView 交给 ViewResolver视图解析器解析,然后返回真正的视图
  • 6)DispatcherServlet将模型数据填充到视图中
  • 7)DispatcherServlet将结果响应给客户端

SpringMVC虽然整体流程复杂,但大部分的组件不需要开发人员创建和管理,只需要通过配置文件的方式完成配置即可,真正需要开发人员处理的只有Handler(Controller)、View、Model

2、SpringMVC的核心组件

  • 1)DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥
  • 2)Handler:处理器,完成具体的业务逻辑,相当于Servlet或Action
  • 3)HandlerMapping:DispatcherServlet接收到请求后,通过HandlerMapping将不同的请求映射到不同的Handler
  • 4)HandlerInterceptor:处理拦截器的接口,如果需要完成一些拦截处理,可以实现该接口
  • 5)HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor (系统有默认的HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)
  • 6)HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到JavaBean等,这些操作都是由HandlerAdapter来完成,开发者只需要关注于业务逻辑的处理即可,DispatcherServlet通过HandlerAdapter执行不同的Handler
  • 7)ModelAndView:装载了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet
  • 8)ViewResolver:视图解析器,DispatcherServlet通过视图解析器将逻辑视图 解析为物理视图,最终将渲染结果响应给客户端

3、SpringMVC Restful风格的接口流程

  • Restful接口,响应格式是json,需要使用@ResponseBody注解:
@GetMapping("/user")
@ResponseBody
public User user() {
    return new User(1,"张三");
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系七分地网进行投诉反馈,一经查实,立即删除!

相关文章

2023年中国智能电视柜产量、需求量、市场规模及行业价格走势[图]

电视柜是随着电视机的发展和普及而演变出的家具种类&#xff0c;其主要作用是承载电视机&#xff0c;又称视听柜&#xff0c;随着生活水平的提高&#xff0c;与电视机相配套的电器设备也成为电视柜的收纳对象。 随着智能家具的发展&#xff0c;智能电视机柜的造型和风格都是有了…

前端最新支持四级及以下结构仿企查查、天眼查关联投资机构 股权结构 tree树形结构 控股结构

随着技术的发展&#xff0c;开发的复杂度也越来越高&#xff0c;传统开发方式将一个系统做成了整块应用&#xff0c;经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改&#xff0c;造成牵一发而动全身。通过组件化开发&#xff0c;可以有效实现单…

【大规模 MIMO 检测】基于ADMM的大型MU-MIMO无穷大范数检测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Cannot download sources:IDEA源码无法下载

问题 Swagger的相关包&#xff0c;无法看到注释&#xff1b; 在class文件的页面&#xff0c;点击下载源码&#xff0c;源码下载不了&#xff0c;IDEA报下面的错误。 报错 Cannot download sources Sources not found for: io.swagger.core.v3:swagger-annotations:2.2.9 解决…

二值贝叶斯滤波计算4d毫米波聚类目标动静属性

机器人学中有些问题是二值问题&#xff0c;对于这种二值问题的概率评估问题可以用二值贝叶斯滤波器binary Bayes filter来解决的。比如机器人前方有一个门&#xff0c;机器人想判断这个门是开是关。这个二值状态是固定的&#xff0c;并不会随着测量数据变量的改变而改变。就像门…

Android Jetpack组件架构:ViewModel的原理

Android Jetpack组件架构&#xff1a;ViewModel的原理 导言 本篇文章是关于介绍ViewModel的&#xff0c;由于ViewModel的使用还是挺简单的&#xff0c;这里就不再介绍其的基本应用&#xff0c;我们主要来分析ViewModel的原理。 ViewModel的生命周期 众所周知&#xff0c;一般…

FFmpeg 命令:从入门到精通 | ffplay 简单过滤器

FFmpeg 命令&#xff1a;从入门到精通 | ffplay 简单过滤器 FFmpeg 命令&#xff1a;从入门到精通 | ffplay 简单过滤器视频旋转视频反转视频旋转和反转音频变速播放视频变速播放音视频同时变速更多参考 FFmpeg 命令&#xff1a;从入门到精通 | ffplay 简单过滤器 本节介绍了简…

亲测可用国产GPT人工智能

分享一些靠谱、可用、可以白嫖的GPT大模型。配合大模型&#xff0c;工作效率都会极大提升。 清华大学ChatGLM 官网&#xff1a; 智谱清言中国版对话语言模型&#xff0c;与GLM大模型进行对话。https://chatglm.cn/开源的、支持中英双语的1300亿参数的对话语言模型&#xff0…

毅速课堂:3D打印随形水路设计应注意什么?

随形水路是一种基于3D打印技术的新型模具冷却水路&#xff0c;能有效提高冷却效率、缩短冷却周期、提升产品良率、提高生产效率、 与传统的水路设计相比&#xff0c;随形水路更加贴合模具型腔表面&#xff0c;能够更加均匀地分配冷却水&#xff0c;使模具各部分的冷却效果得到有…

Android 使用Kotlin封装RecyclerView

文章目录 1.概述2.运行效果图3.代码实现3.1 扩展RecyclerView 3.2 扩展Adapter3.3 RecyclerView装饰绘制3.3.1 以图片实现分割线3.3.2 画网格线3.3.3空白的分割线3.3.4 不同方向上的分割线 3.4 使用方法 1.概述 在一个开源项目上看到了一个Android Kotlin版的RecyclerView封装…

Egg 封装接口返回信息

中间件封装 代码 const msgArr {"200":成功,"401":token失效 } module.exports (option, app) > {return async function(ctx, next) {try{//成功是返回的信息ctx.emit(code,data,msg)>{console.log(1111,code,data,msg)ctx.body {code,data:dat…

What is an HTTP Flood DDoS attack?

HTTP 洪水攻击是一种针对 Web 和应用程序服务器的第 7 层分布式拒绝服务 &#xff08;DDoS&#xff09; 攻击。HTTP 洪水攻击通过使用 HTTP GET 或 HTTP POST 请求执行 DDoS 攻击。这些请求是有效的&#xff0c;并且针对可用资源&#xff0c;因此很难防范 HTTP 洪水攻击。 匿名…

Vue控制textarea可输入行数限制-案例

控制只能输入六行内容 UI部分代码 //我使用了antd ui库 <a-form-model-item ref"address_group" label"规则描述" prop"address_group" > <a-textarea:rows"6"style"width: 60%"placeholder"一次最多输入6行…

【51单片机】10-蜂鸣器

1.蜂鸣器的原理 这里的“源”不是指电源。而是指震荡源。 也就是说&#xff0c;有源蜂鸣器内部带震荡源&#xff0c;所以只要一通电就会叫。 而无源内部不带震荡源&#xff0c;所以如果用直流信号无法令其鸣叫。必须用2K~5K的方波去驱动它。 有源蜂鸣器往往比无源的贵&#xff…

聊聊Go与依赖注入

如果你读过Robert C. Martin[1]的《敏捷软件开发&#xff1a;原则、模式与实践》[2](书的封皮见下图)&#xff0c;那么你一定知道经典的SOLID设计原则[3]中的“D”&#xff1a;依赖倒置原则&#xff08;Dependency Inversion Principle, DIP&#xff09;。 依赖倒置原则是面向对…

Linux shell编程学习笔记4:修改命令行提示符格式(内容和颜色)

一、命令行提示符格式内容因shell类型而异 Linux终端命令行提示符内容格式则因shell的类型而异&#xff0c;例如CoreLinux默认的shell是sh&#xff0c;其命令行提示符为黑底白字&#xff0c;内容为&#xff1a; tcbox:/$ 其中&#xff0c;tc为当前用户名&#xff0c;box为主机…

【论文极速读】Prompt Tuning——一种高效的LLM模型下游任务适配方式

【论文极速读】Prompt Tuning——一种高效的LLM模型下游任务适配方式 FesianXu 20230928 at Baidu Search Team 前言 Prompt Tuning是一种PEFT方法&#xff08;Parameter-Efficient FineTune&#xff09;&#xff0c;旨在以高效的方式对LLM模型进行下游任务适配&#xff0c;本…

使用GDIView排查GDI对象泄漏导致的程序UI界面绘制异常问题

目录 1、问题说明 2、初步分析 3、查看任务管理器&#xff0c;并使用GDIView工具分析 4、GDIView可能对Win10兼容性不好&#xff0c;显示的GDI对象个数不太准确 5、采用历史版本比对法&#xff0c;确定初次出现问题的时间点&#xff0c;并查看前一天的代码修改记录 6、将…

28271-2012 米制超细牙螺纹 公差

声明 本文是学习GB-T 28271-2012 米制超细牙螺纹 公差. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了米制超细牙螺纹的公差和标记。 本标准适用于精密仪器和电子设备等领域的螺纹连接。 2 规范性引用文件 下列文件对于本文件…

electron之快速上手

前一篇文章已经介绍了如何创建一个electron项目&#xff0c;没有看过的小伙伴可以去实操一下。 接下来给大家介绍一下electron项目的架构是什么样的。 electron之快速上手 electron项目一般有两个进程&#xff1a;主进程和渲染进程。 主进程&#xff1a;整个项目的唯一入口&…