1.AOP
AOP概念:AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,以提高代码的模块化性、可维护性和复用性。
- 横切关注点 比如日志、事务、安全性等,这些关注点会横跨多个模块,导致代码重复、耦合性增加、难以维护等问题。AOP 通过将这些横切关注点抽象成一个个“切面”(Aspect),并将其独立于业务逻辑之外,以达到解耦的目的。
AOP 的核心概念包括以下几个要素:
- 切面(Aspect): 切面是横切关注点的抽象,它包含了一组横切关注点以及在何时何处应用这些关注点的逻辑。通常,切面由一组通知(Advice)和一个切点(Pointcut)组成。
- 通知(Advice): 通知是切面中具体的逻辑实现,它定义了在何时何地执行横切关注点的具体行为,包括“前置通知”(Before Advice)、“后置通知”(After Advice)、“环绕通知”(Around Advice)等。
- 切点(Pointcut): 切点是在程序中指定的某个位置,通知将在这些位置执行。切点可以使用表达式或其他方式进行定义,以便匹配到程序中的特定方法或代码块。
- 连接点(Join Point): 连接点是在程序执行过程中可以应用通知的具体位置,通常是方法调用、方法执行或异常抛出等。
- 织入(Weaving): 织入是将切面逻辑应用到目标对象中的过程,可以在编译时、加载时或运行时进行。织入可以通过源代码修改、字节码操作、动态代理等方式实现。
你的项目中有没有使用到AOP:
记录操作日志,缓存,spring实现的事务;
核心是:使用aop中的环绕通知 + 切点表达式(找到要记录日志的方法),通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),获取到这些参数以后,保存到数据库。
Spring 中的事务是如何实现的?
其本质是通过AOP功能,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或回滚事务。
2.后端如何与商家建立链接,实现实时通信?
使用 Websocket 来实现用户端和商家端通信:
WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它允许客户端和服务器之间建立持久的、双向的通信通道,使得服务器可以主动向客户端推送消息,而无需客户端发送请求。
客户端和服务器之间可以实时地发送消息和接收消息,不需要频繁地发起请求。这样可以减少网络流量和延迟,并提供更好的用户体验。
应用场景:
用户下单并支付成功后,需要第一时间通知外卖商家。
来单提醒:
- 当客户支付后,调用WebSocket 的相关API实现服务端向客户端推送消息;
- 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
- 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type\orderId\content
- type 为消息类型, 1为来单提醒,2为客户催单
- orderId 为订单id
- content 为消息内容
用户在小程序中点击催单按钮后,需要第一时间通知外卖商家。
客户催单:
- 通过WebSocket 实现管理端页面和服务端保持长连接状态
- 当用户点击催单按钮后,调用WebSocket 的先关API实现服务端向客户端推送消息
- 客户端浏览器解析服务器推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报。
- 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type\orderId\content
3.webSocket的作用是什么 怎么实现的 为什么要用ThreadLocal?底层原理是什么
- WebSocket 允许客户端和服务器之间建立持久的连接,可以在连接建立后双向实时地传输数据,实现实时通信功能。
- 相比传统的HTTP 请求-响应模式,WebSocket 的持久连接减少了每次通信的开销。不需要再没次通信时都重新建立连接,减少了通信的延迟和资源消耗。
- 在我们项目中就是支持用户端以及商家管理端与服务器进行双向通信。
3.1 你为什么要用ThreadLocal?底层原理是什么?
- ThreadLocal 是Java 中的一个类,用于多线程环境下保存线程局部变量。
- 它提供了一种线程安全的方式,让每个线程都拥有自己独立的变量副本,从而避免了线程间的数据共享和竞争。
- 我们使用ThreadLocal 是来存储在多个方法中需要共享的数据,具体来说就是项目中的用户Id,其他方法需要调用这个参数,我们就不用显示传递给它了。
- ThreaLocal 的底层原理是通过一个ThreadLocalMap 来实现的。在每个线程中都有一个ThreadLocalMa,用于存储线程局部变量。ThreadLocalMap 中的键是ThreadLocal 对象,值是对应线程的变量副本。当一个线程需要获取变量值时,它首先会获取自己线程的ThreadLocalMap,并根据ThreadLocal 对象获取对应的变量副本。这样就实现了每个线程都有自己的变量副本,互不影响。
3.2 Websocket 与 HTTP 有什么区别? 既然 WebSocket 支持双向通信,功能看似比 HTTP 强大,那么是不是可以基于 WebSocket 开发所有的业务功能?
HTTP 协议和 WebSocket 协议对比:
- HTTP 是短连接
- WebSocket 是长连接
- HTTP 通信是单向的,基于请求响应模式
- WebSocket 支持双向通信
- HTTP 和 WebSocket 底层都是 TCP 连接
不能使用 WebSocket 并不能完全取代 HTTP,它只适合在特定的场景下使用,原因如下:
- 资源开销:WebSocket 需要保持持久连接,对服务器资源有更高要求,不适合所有场景。
- 功能与约定:HTTP 提供丰富的功能和约定(如状态码、缓存控制),适合更广泛的业务需求。
- 安全性和兼容性:虽然 WebSocket 支持加密,但管理安全性可能更复杂;且某些环境下 WebSocket 不被支持或有限制。
- 设计和实践:RESTful API 和相关的 HTTP 设计原则不易直接应用于 WebSocket。
结论:WebSocket 并不能完全取代HTTP,它只适合在特定的场景下使用。
4.怎么保证在同时操作多张数据库表出现程序错误时保证数据的一致性?
我在涉及多表操作时使用了事务(Transaction): 将涉及到的数据库操作封装在一个事务中。在事务中,要么所有的数据库操作都成功提交,要么全部失败回滚,保证了数据的一致性。如果发生异常,可以通过捕获异常并执行回滚操作来保证数据的一致性。
具体操作:
- 在启动类上方添加@EnableTransactionManagement
- 开启事务注解之后,我们只需要在需要捆绑成为一个事务的方法上添加@Transactional
- 这样就把对两张表的操作捆绑成为了一个事务。
5你用什么技术实现数据导出的功能的?
Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。
一般情况下,POI 都是用于操作 Excel 文件。
Apache POI 的应用场景:
- 银行网银系统导出交易明细
- 各种业务系统导出Excel报表
- 批量导入业务数据
项目中的应用:(产品原型)
在数据统计页面,有一个数据导出的按钮,点击该按钮时,其实就会下载一个文件。这个文件实际上是一个Excel形式的文件,文件中主要包含最近30日运营相关的数据。表格的形式已经固定,主要由概览数据和明细数据两部分组成。真正导出这个报表之后,相对应的数字就会填充在表格中,就可以进行存档。
项目中的具体做法:
- 设计Excel模板文件
- 查询近30天的运营数据
- 将查询到的运营数据写入模板文件
- 通过输出流将Excel文件下载到客户端浏览器
6.Spring boot 全局异常处理器
什么是全局异常处理器:
软件开发springboot 项目过程中,不可避免的需要处理各种异常,sping mvc 架构中会出现大量的 try{...} finally{...} 代码块,不仅有大量的冗余代码,而且还影响代码的可读性。这样就需要定义个 全局统一异常处理器,以便业务层再也不必处理异常。
为什么需要全局异常
- 不用强制写try-catch,由全局异常处理器统一捕获异常。
- 自定义异常,只能用全局异常来捕获。不能直接返回给客户端,客户端是看不懂的,需要接入全局异常处理器
- JSR303规范的Validator参数校验器,参数校验不通过会抛异常,是无法使用try-catch 语句直接捕获,只能使用全局异常处理器。
原理和目标:
简单的说,@ControllerAdvice 注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独的一个类,定义一套对各种异常的处理机制,然后在类的签名上注解@ControllerAdvice ,统一对不同阶段的,不同异常进行处理。这就是统一异常处理的原理。
对异常阶段进行分类,大致可以分为:进入Controller前的异常和Service 层异常。
在我们的项目中,异常处理都是通过spring的全局异常处理器来实现的,核心是两个注解:
- 一个是@RestControllerAdvice,标注在类上,可以定义全局异常处理类对异常进行拦截
- 一个是@ExceptionHandler,标注在异常处理类中的方法上,可以声明每个方法能够处理的异常类型
在我们的项目中,将异常分为了三大类:
在苍穹外卖项目的全局异常处理器中一般定义三种异常:
- 第一类是指定异常,指定异常指的是用户操作产生的与程序设计相关的异常,比如说字段重复异常、Validation校验异常等等,这类异常捕获之后,我们会根据异常的消息提示,给前端一个确定的返回结果
- 第二类是业务异常处理,业务异常是由于用户不正当操作产生的与业务相关的的异常,这种异常往往需要我们自定义,然后在程序的相关位置手动抛出,在抛出的时候还会指定异常提示信息。然后异常处理器捕获之后,直接将异常提示消息返回给前端
- 第三种异常是兜底异常,此处主要捕获的是不属于上面两种异常的异常,一般是一些程序员代码不够严谨引发的运行时异常,对于这些异常,我们处理方案是首先要把错误记录到日志系统中,然后给前端一个类似于服务器开小差了之类的统一提示
7 Redis 作为缓存,MySQL 的数据如何保证与Redis 进行同步?
采用 redisson 实现的读写锁,保证了对共享资源的互斥访问。读写锁允许多个线程同时获取读锁,因此在读取共享资源时,可以实现并发读取,提高了系统的读取性能。但是当有线程获取写锁时,其他线程无法同时获取读锁,保证了操作的原子性,避免了读取到部分更新的数据。
8 Redis 的缓存淘汰机制是怎么样的?
Redis 的缓存淘汰(Eviction)机制是指当Redis 的内存空间不足以容纳新的数据时,Redis 将根据预先设置的策略,自动淘汰部分现有数据,为新数据腾出空间。
这个在Redis中提供了很多种,默认是 noeviction ,不淘汰任何key,但是内存满时不允许写入新数据。
- LRU(Least Recently Used,最近最少使用)LRU 会淘汰最近最久未使用的数据。当Redis 内存空间不足时,Redis 将淘汰最近最久未使用的数据,为新数据腾出空间。
- LFU(Least Frequently Used, 最不经常使用)LFU 会淘汰使用频率最低的数据。当Redis 内存空间不足时,Redis 将淘汰使用频率最低的数据,为新数据腾出空间。
- Random(随机淘汰)随机淘汰策略会随机选择一些数据进行淘汰,没有明确的淘汰规则。当Redis 内存空间不足时,Redis 会随机选择一些数据进行淘汰,并为新数据腾出空间。
- TTL(Time To Live)当数据设置了过期时间,在数据过期后,Redis 会自动将该数据从缓存中淘汰。
- Maxmemory Policy 通过配置max-memory-policy 参数来指定缓存淘汰策略,可以选择以上任意一种策略或它们的组合。
9 redis 在项目中的作用?
Redis 是高性能的,基于键值对的,写入缓存的 内存存储系统。它支持多种数据结构如字符串、哈希表、列表、集合、有序集合等,并提供了丰富的操作命令。
项目中引入 Redis 的地方是:
查询店铺营业状态 像这种店铺营业状态,本项目无非就两个状态:营业中/打样。而且它属于高频查询。只要用户浏览到这个店铺,前端就要自动发送请求到后端查询店铺状态。Redis 是基于键值对这种形式存储的,而且 Redis 也把将数据放到缓存中,而不是磁盘,有效缓解了这种高频查询给磁盘带来的压力。
缓存菜品 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。结果:系统响应慢、用户体验差。通过Redis来缓存菜品数据,减少数据库查询操作。
10redis的缓存原理,为什么速度快?
- Redis 将数据存储在内存中,相比于磁盘的数据库系统,内存存储具有更快的读写速度
- Redis 使用单线程模型来处理客户端请求,避免了多线程并发导致的线程切换开销与竞争
- Redis 内置了多种优化后的数据类型 / 结构实现,性能非常高
- Redis 使用的是非阻塞 IO 多路复用,使用了单线程来轮询描述,将数据库的开、关、读、写都转成了事件,减少了线程切换时上下文的切换和竞争。
11.Nginx 反向代理和负载均衡
nginx 反向代理,就是将前端发送的动态请求由 nginx 转发到后端服务器。
那为什么不直接通过浏览器直接请求后台服务器,需要通过nginx 反向代理呢?
12 nginx 反向代理的好处:
- 提高访问速度 因为nginx本身可以进行缓存,如果访问的同一接口,并且做了数据缓存,nginx就直接可把数据返回,不需要真正访问服务端,从而提高访问速度
- 进行负载均衡 所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器。
- 保证后端服务安全 因为一般后台服务地址不会暴露,所以浏览器不能直接访问,可以把nginx 作为请求访问的入口;请求到达nginx 后转发到具体的服务器中,从而保证后端服务的安全。
12.2 正向代理:
正向代理是客户端发送请求后代理服务器访问目标服务器,代理服务器代表客户端发送请求并将相应返回给客户端。正向代理隐藏了客户端的真实身份和位置信息,为客户端提供代理访问互联网的功能。
12.3 反向代理:
反向代理是指服务器接受客户端的请求,然后将请求转发给后端服务器,并将后端服务器的响应返回给客户端。反向代理隐藏了服务器的真实身份和位置信息,客户端只知道与反向代理进行通信,而不知道真正的服务器。
13. 什么是反射?
反射是一种在程序运行时检查和操作类的机制,通过获取类的信息并动态调用方法、创建对象等。这种机制让程序能够在运行时根据需要动态地获取和操作类的结构和成员。
- 获取 Class 对象 程序通过类的全限定名、对象的getClass()方法或 .Class 语法来获取对应的Class对象。
- 查询类信息 通过Class对象获取类的信息,包括类名、包名、父类、实现的接口、构造函数、方法、字段等。
- 动态创建对象 通过 Class 对象的 newInstance ()方法调用类的默认构造函数来创建对象,或者通过 Constructor 对象调用类的其他构造函数来创建对象。
- 动态调用方法 通过 Method 对象调用类的方法,传递参数并获取返回值。
- 动态访问字段 通过 Field 对象获取和设置类的字段值。
整个流程就是通过获取 Class 对象,然后根据需要动态地调用类的方法、创建对象、访问字段等操作,实现了对类的动态操作和调用。
14商品超卖问题如何解决?
-
- 悲观锁: 在商品购买过程中,使用悲观锁(Pessimistic Locking)对库存进行加锁,确保同一时间只有一个用户可以执行减库存操作,避免并发冲突导致的超卖问题。例如,在数据库层面使用数据库行级锁或者事务进行控制。
- 乐观锁: 使用乐观锁(Optimistic Locking)进行并发控制。在购买商品时,先查询当前库存数量,然后在更新库存时进行版本号比对,如果版本号一致才执行更新操作,否则返回库存不足错误。这种方式适用于读多写少的场景。