java 高级面试题整理(薄弱技术-2023)

session 和cookie的区别和联系

session
1.什么是session
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了session是一种特殊的cookie。cookie是保存在客户端的,而session是保存在服务端。

2.为什么要用session
由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了

3.session原理
当客户端第一次请求服务器的时候,服务器生成一份session保存在服务端,将该数据(session)的id以cookie的形式传递给客户端;以后的每次请求,浏览器都会自动的携带cookie来访问服务器(session数据id)。
 

Cookie:
它是客户端浏览器用来保存服务端数据的一种机制。当通过浏览器进行网页访问的时候,服务器可以把某一些状态数据以 key-value 的方式写入到 Cookie 里面存储到客户端浏览器。然后客户端下一次再访问服务器的时候,就可以携带这些状态数据发送到服务器端,服务端可以根据 Cookie 里面携带的内容来识别使用者。
Session:
表示一个会话,它是属于服务器端的容器对象,默认情况下,针对每一个浏览器的请求。Servlet 容器都会分配一个 Session。Session 本质上是一个 ConcurrentHashMap,可以存储当前会话产生的一些状态数据。
Http 协议本身是一个无状态协议,也就是服务器并不知道客户端发送过来的多次请求是属于同一个用户。所以 Session 是用来弥补 Http 无状态的不足,简单来说,服务器端可以利用 session来存储客户端在同一个会话里面的多次请求记录。基于服务端的 session 存储机制,再结合客户端的 Cookie 机制,就可以实现有状态的Http 协议。

(如图)具体的工作原理是:

客户端第一次访问服务端的时候,服务端会针对这次请求创建一个会话,并生成一个唯一的 sessionId 来标注这个会话。然后服务端把这个 sessionid 写入到客户端浏览器的 cookie 里面,用来实现客户端状态的保存。在后续的请求里面,每次都会携带 sessionid,服务器端就可以根据这个 sessionid 来识别当前的会话状态。

反射

什么是反射(Reflection )

主要是指程序可以访问、检测和修改它本身状态或行为的一种能力

Java 反射的优点:
        增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作提高代码的复用率,比如动态代理,就是用到了反射来实现可以在运行时轻松获取任意一个类的方法、属性,并且还能通过反射进行动态调用
Java 反射的缺点:
        反射会涉及到动态类型的解析,所以 JVM 无法对这些代码进行优化,导致性能要比非反射调用更低。使用反射以后,代码的可读性会下降反射可以绕过一些限制访问的属性或者方法,可能会导致破坏了代码本身的抽象性

innoDB 如何解决幻读

1、 Mysql 的事务隔离级别
Mysql 有四种事务隔离
每个数据行上的非唯一索引列上都会存在一把 next-key lock,当某个事务持有该数据行的 next-key lock 时,会锁住一段左开右闭区间的数据。因此,当通过 id>4 这样一种范围查询加锁时,会加 next-key Lock,锁定的区间范围是:(4, 7] , (7,10],(10,+∞]间隙锁和 next-key Lock 的区别在于加锁的范围,间隙锁只锁定两个索引之间的引用间隙,而 next-key Lock 会锁定多个索引区间,它包含记录锁和间隙锁。当我们使用了范围查询,不仅仅命中了 Record 记录,还包含了 Gap 间隙,在这种情况下我们使用的就是临键锁,它是 MySQL 里面默认的行锁算法。
2 、总结
虽然 InnoDB 中通过间隙锁的方式解决了幻读问题,但是加锁之后一定会影响到并发性能,因此,如果对性能要求较高的业务场景中,可以把隔离级别设置成 RC,这个级别中不存在间隙锁。

介绍下 Spring IoC 的工作流程

IOC 是什么

    IOC 的全称是 Inversion Of Control, 也就是控制反转,它的核心思想是把对象的管理权限交给容器。 
    应用程序如果需要使用到某个对象实例,直接从 IOC 容器中去获取就行,这样设计的好处是降低了程序里面对象与对象之间的耦合性。
使得程序的整个体系结构变得更加灵活。


Bean 的声明方式

    Spring 里面很多方式去定义 Bean,比如 XML 里面的<bean>标签、@Service、 @Component、@Repository、@Configuration 配置类中的@Bean 注解等等。
    Spring 在启动的时候,会去解析这些 Bean 然后保存到 IOC 容器里面。

Spring IOC 的工作流程大致可以分为三个阶段。

第一个阶段,
    就是 IOC 容器的初始化这个阶段主要是根据程序中定义的 XML 或者注解等 Bean 的声明方式,通过解析和加载后生成 BeanDefinition,然后把 BeanDefinition 注册到 IOC容器。通过注解或者 xml 声明的 bean 都会解析得到一个 BeanDefinition 实体,实体中包含这个 bean 中定义的基本属性。最后把这个 BeanDefinition 保存到一个 Map 集合里面,从而完成了 IOC 的初始化。IoC 容器的作用就是对这些注册的 Bean 的定义信息进行处理和维护,它是IoC 容器控制反转的核心。
第二个阶段,完成 Bean 初始化及依赖注入
    1. 通过反射针对没有设置 lazy-init 属性的单例 bean 进行初始化。
    2. 完成 Bean 的依赖注入。
第三个阶段,Bean 的使用
    通常我们会通过@Autowired 或者 BeanFactory.getBean()从 IOC 容器中获取指定的 bean 实例。另外,针对设置 layy-init 属性以及非单例 bean 的实例化,是在每次获取 bean 对象的时候,调用 bean 的初始化方法来完成实例化的,并且 Spring IOC 容器不会去管理这些 Bean。

Spring Boot 自动装配是什么

SpringBoot 自动装配主要是基于注解编程 和 约定优于配置的思想来设计的。
自动装配就是由 Spring 自动把其他组件中的 Bean 装载到 IoC 容器中,不需要开发人员再去配置文件中添加大量的配置。我们只需要在 Spring Boot 的启动类上添加@SpringBootApplication 注解,开启自动装配。
 

自动装配原理

@SpringBootApplication 这个注解是暴露给用户使用的入口,它的底层是由@EnableAutoConfiguration 这个注解来实现的。

第一步:启动依赖组件的时候,组件中必须要包含 @Configuration 的配置类,在这个配置类里面声明为 @Bean 注解,就将方法的返回值或者属性值 注入到 IoC 容器中。
第二步:如果是使用第三方 jar 包,Spring Boot 采用 SPI 机制,只需要在/META-INF/目录下增加 spring.factories 配置文件。然后,Spring Boot 会根据约定规则,自动使用 SpringFactoriesLoader 来加载配置文件中的内容。
第三步:Spring 获取到第三方 jar 中的配置以后,会使用调用 ImportSelector 接口来完成动态加载。

Spring Boot 这样的设计的好处

1、大幅减少了配置文件,
2、而且各模块之间的依赖实现了深度解耦。
3、减少maven依赖的版本冲突
4、只需要引入一个 starter 依赖就可以

Spring Boot 的约定优于配置,你的理解是什么?

1. 首先, 约定优于配置是一种软件设计的范式,它的核心思想是减少软件开发人员对于配置项的维护,从而让开发人员更加聚焦在业务逻辑上。
2. Spring Boot 就是约定优于配置这一理念下的产物,它类似于 Spring 框架下的一个脚手架,通过 Spring Boot,我们可以快速开发基于 Spring 生态下的应用程序。
3. 基于传统的 Spring 框架开发 web 应用,我们需要做很多和业务开发无关并且只需要做一次的配置,比如
    a. 管理 jar 包依赖
    b. web.xml 维护
    c. Dispatch-Servlet.xml 配置项维护
    d. 应用部署到 Web 容器
    e. 第三方组件集成到 Spring IOC 容器中的配置项维护而在 Spring Boot 中,我们不需要再去做这些繁琐的配置,Spring Boot 已经自动帮我们完成了,这就是约定由于配置思想的体现。
4. Spring Boot 约定由于配置的体现有很多,比如
    a. Spring Boot Starter 启动依赖,它能帮我们管理所有 jar 包版本
    b. 如果当前应用依赖了 spring mvc 相关的 jar,那么 Spring Boot 会自动内置Tomcat 容器来运行 web 应用,我们不需要再去单独做应用部署。
    c. Spring Boot 的自动装配机制的实现中,通过扫描约定路径下的spring.factories 文件来识别配置类,实现 Bean 的自动装配。
    d. 默认加载的配置文件 application.properties 等等
 

单例模式的定义

1、确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点

2、单例被破坏的五个场景

lock 和 synchronized 区别

1. 从功能角度来看,
    Lock 和 Synchronized 都是 Java 中用来解决线程安全问题的工具。
2. 从特性来看,
    a. Synchronized 是 Java 中的同步关键字,Lock 是 J.U.C 包中提供的接口,这个接口有很多实现类,其中就包括 ReentrantLock 重入锁
    b. Synchronized 可以通过两种方式来控制锁的粒度,
    c. Lock 比 Synchronized 的灵活性更高,Lock 可以自主决定什么时候加锁,什么时候释放锁,只需要调用 lock()和 unlock()这两个方法就行,同时 Lock 还提供了非阻塞的竞争锁方法 tryLock()方法,这个方法通过返回 true/false 来告诉当前线程是否已经有其他线程正在使用锁。Synchronized 由于是关键字,所以它无法实现非阻塞竞争锁的方法,另外,Synchronized 锁的释放是被动的,就是当 Synchronized 同步代码块执行完以后或者代码出现异常时才会释放。
    d. Lock 提供了公平锁和非公平锁的机制,公平锁是指线程竞争锁资源时,如果已经有其他线程正在排队等待锁释放,那么当前竞争锁资源的线程无法插队。而非公平锁,就是不管是否有线程在排队等待锁,它都会尝试去竞争一次锁。Synchronized 只提供了一种非公平锁的实现。

3. 从性能方面来看
    Synchronized 和 Lock 在性能方面相差不大,在实现上会有一些区别,Synchronized 引入了偏向锁、轻量级锁、重量级锁以及锁升级的方式来优化加锁的性能,而 Lock 中则用到了自旋锁的方式来实现性能优化。

线程池如何知道一个线程的任务已经执行完成

1. 在线程池内部,当我们把一个任务丢给线程池去执行,线程池会调度工作线程来执行这个任务的 run 方法,run 方法正常结束,也就意味着任务完成了。所以线程池中的工作线程是通过同步调用任务的 run()方法并且等待 run 方法返回后,再去统计任务的完成数量。
2. 如果想在线程池外部去获得线程池内部任务的执行状态,有几种方法可以实现。
    a. 线程池提供了一个 isTerminated()方法,可以判断线程池的运行状态,我们可以循环判断 isTerminated()方法的返回结果来了解线程池的运行状态,一旦线程池的运行状态是 Terminated,意味着线程池中的所有任务都已经执行完了。想要通过这个方法获取状态的前提是,程序中主动调用了线程池的 shutdown()方法。在实际业务中,一般不会主动去关闭线程池,因此这个方法在实用性和灵活性方面都不是很好。
    b. 在线程池中,有一个 submit()方法,它提供了一个 Future 的返回值,我们通过 Future.get()方法来获得任务的执行结果,当线程池中的任务没执行完之前,future.get()方法会一直阻塞,直到任务执行结束。因此,只要 future.get()方法正常返回,也就意味着传入到线程池中的任务已经执行完成了!
    c. 可以引入一个 CountDownLatch 计数器,它可以通过初始化指定一个计数器进行倒计时,其中有两个方法分别是 await()阻塞线程,以及 countDown()进行倒计时,一旦倒计时归零,所以被阻塞在 await()方法的线程都会被释放。基于这样的原理,我们可以定义一个 CountDownLatch 对象并且计数器为 1,接着在线程池代码块后面调用 await()方法阻塞主线程,然后,当传入到线程池中的任务执行完成后,调用 countDown()方法表示任务执行结束。最后,计数器归零 0,唤醒阻塞在 await()方法的线程
3. 基于这个问题,
    不管是线程池内部还是外部,要想知道线程是否执行结束,我们必须要获取线程执行结束后的状态,而线程本身没有返回值,所以只能通过阻塞-唤醒的方式来实现,future.get 和 CountDownLatch 都是这样一个原理。

HashMap 是怎么解决哈希冲突的?

要了解 Hash 冲突,那首先我们要先了解 Hash 算法和 Hash 表。(如图)


1. Hash 算法,就是把任意长度的输入,通过散列算法,变成固定长度的输出,这个输出结果是散列值。
2. Hash 表又叫做“散列表”,它是通过 key 直接访问在内存存储位置的数据结构,在具体实现上,我们通过 hash 函数把 key 映射到表中的某个位置,来获取这个位置的数据,从而加快查找速度。
3. 所谓 hash 冲突,是由于哈希算法被计算的数据是无限的,而计算后的结果范围有限,所以总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突。
4. 通常解决 hash 冲突的方法有 4 种。
    a. 开放定址法,也称为线性探测法,就是从发生冲突的那个位置开始,按照一定的次序从 hash 表中找到一个空闲的位置,然后把发生冲突的元素存入到这个空闲位置中。ThreadLocal 就用到了线性探测法来解决 hash 冲突的。向这样一种情况(如图),在 hash 表索引 1 的位置存了一个 key=name,当再次添加key=hobby 时,hash 计算得到的索引也是 1,这个就是 hash 冲突。而开放定址法,就是按顺序向前找到一个空闲的位置来存储冲突的 key。
    b. 链式寻址法,这是一种非常常见的方法,简单理解就是把存在 hash 冲突的 key,以单向链表的方式来存储,比如 HashMap 就是采用链式寻址法来实现的。向这样一种情况(如图),存在冲突的 key 直接以单向链表的方式进行存储。
    c. 再 hash 法,就是当通过某个 hash 函数计算的 key 存在冲突时,再用另外一个 hash 函数对这个 key 做 hash,一直运算直到不再产生冲突。这种方式会增加计算时间,性能影响较大。
    d. 建立公共溢出区, 就是把 hash 表分为基本表和溢出表两个部分,凡事存在冲突的元素,一律放入到溢出表中。
5. HashMap 在 JDK1.8 版本中,通过链式寻址法+红黑树的方式来解决 hash 冲突问题,其中红黑树是为了优化 Hash 表链表过长导致时间复杂度增加的问题。当链表长度大于 8 并且 hash 表的容量大于 64 的时候,再向链表中添加元素就会触发转化。

Spring 生命周期全过程大致分为五个阶段:创建前准备阶段、创建实例阶段、依赖注入阶段、容器缓存阶段和销毁实例阶段。

1. 创建前准备阶段【干什么、作用】

    这个阶段主要的作用是,Bean 在开始加载之前,需要从上下文和相关配置中解析并查找 Bean 有关的扩展实现,比如像`init-method`-容器在初始化 bean 时调用的方法、`destory-method`,容器在销毁 bean 时调用的方法。以及,BeanFactoryPostProcessor 这类的 bean 加载过程中的前置和后置处理。这些类或者配置其实是 Spring 提供给开发者,用来实现 Bean 加载过程中的扩展机制,在很多和 Spring 集成的中间件中比较常见,比如 Dubbo。

2. 创建实例阶段

    这个阶段主要是通过反射来创建 Bean 的实例对象,并且扫描和解析 Bean 声明的一些属性。

3. 依赖注入阶段

    如果被实例化的 Bean 存在依赖其他 Bean 对象的情况,则需要对这些依赖 bean 进行对象注入。比如常见的`@Autowired`、setter 注入等依赖注入的配置形式。同时,在这个阶段会触发一些扩展的调用,比如常见的扩展类:BeanPostProcessors(用来实现 bean 初始化前后的扩展回调)、InitializingBean(这个类有一个 afterPropertiesSet(),这个在工作中也比较常见)、BeanFactoryAware 等等。

4. 容器缓存阶段

    容器缓存阶段主要是把 bean 保存到容器以及 Spring 的缓存中,到了这个阶段,Bean就可以被开发者使用了。这个阶段涉及到的操作,常见的有,`init-method`这个属性配置的方法, 会在这个阶段调用。以及像 BeanPostProcessors 方法中的后置处理器方法如:postProcessAfterInitialization,也会在这个阶段触发。

5. 销毁实例阶段

    当 Spring 应用上下文关闭时,该上下文中的所有 bean 都会被销毁。如果存在 Bean 实现了 DisposableBean 接口,或者配置了`destory-method`属性,会在这个阶段被调用。
 

Spring 是如何解决循环依赖问题的?

        我们都知道,如果在代码中,将两个或多个 Bean 互相之间持有对方的引用就会发生循环依赖。循环的依赖将会导致注入死循环。这是 Spring 发生循环依赖的原因。

 

        而 Spring 中设计了三级缓存来解决循环依赖问题,当我们去调用 getBean()方法的时候,Spring 会先从一级缓存中去找到目标 Bean,如果发现一级缓存中没有便会去二级缓存中去找,而如果一、二级缓存中都没有找到,意味着该目标 Bean 还没有实例化。于是,Spring 容器会实例化目标 Bean(PS:刚初始化的 Bean 称为早期 Bean) 。然后,将目标 Bean 放入到二级缓存中,同时,加上标记是否存在循环依赖。如果不存在循环依赖便会将目标 Bean 存入到二级缓存,否则,便会标记该 Bean 存在循环依赖,然后将等待下一次轮询赋值,也就是解析@Autowired 注解。等@Autowired 注解赋值完成后(PS:完成赋值的 Bean 称为成熟 Bean) ,会将目标 Bean 存入到一级缓存。

Spring 一级缓存中存放所有的成熟 Bean,
二级缓存中存放所有的早期 Bean,先取一级缓存,再去二级缓存。

         三级缓存是用来存储代理 Bean,当调用 getBean()方法时,发现目标 Bean 需要通过
代理工厂来创建,此时会将创建好的实例保存到三级缓存,最终也会将赋值好的 Bean
同步到一级缓存中。 

Spring 中哪些情况下,不能解决循环依赖问题?

1.多例 Bean 通过 setter 注入的情况,不能解决循环依赖问题
2.构造器注入的 Bean 的情况,不能解决循环依赖问题
3.单例的代理 Bean 通过 Setter 注入的情况,不能解决循环依赖问题
4.设置了@DependsOn 的 Bean 的情况,不能解决循环依赖问题

说一下你对 Redis 的理解

1. Redis 是一个高性能的基于 Key-Value 结构存储的 Nosql 开源数据库。
2. 目前市面上绝大部分公司都采用 Redis 来实现分布式缓存,从而提高数据的检索效率。
3. Redis 之所以这么流行,主要有几个特点:
        a. 它是基于内存存储,在进行数据 IO 操作时,能够 10WQPS
        b. 提供了非常丰富的数据存储结构,如 String、List、Hash、Set、ZSet 等。
        c. Redis 底层采用单线程实现数据的 IO,所以在数据算法层面并不需要要考虑并发安全性,所以底层算法上的时间复杂度基本上都是常量。
4. Redis 虽然是内存存储,但是它也可以支持持久化,避免因为服务器故障导致数据丢失的问题

扫码登录到底是怎么实现的?

1、首先,在网页端打开登录页面,展示一个二维码,这个二维码有一个唯一编号是服务端生成的。然后浏览器定时轮询这个二维码的状态.(或者通过websocket推送)
2、接着,APP 扫描这个二维码(微信扫描),把 APP 的 token 信息、二维码 ID 发送给 Server 端,Server 收到请求后修改二维码的扫码状态,并生成一个临时 token
3、 此时,网页端展示的二维码状态会提示已扫码,待确认。 而 APP 端扫码之后,会提示确认授权的操作。
4、于是,用户确认登录后,携带临时 token 给到 server,server 端修改二维码状态并为网页端生成授权 token
5、最后,网页端轮询到状态变化并获取到 token(websocket推送),从而完成扫码授权。
 

Redis 为什么这么快?

决定 Redis 请求效率的因素主要是三个方面,分别是网络、cpu、内存。

1、在网络层面,Redis 采用多路复用的设计,提升了并发处理的连接数,不过这个阶段,{如图}Server 端的所有 IO 操作,都是由同一个主线程处理的这个时候 IO 的瓶颈就会影响到 Redis 端的整体处理性能。

 2、如果采用多线程,对于 Redis 中的数据操作,都需要通过同步的方式来保证线程安全性,这反而会影响到 redis 的性能

3、从内存层面来说,Redis 本身就是一个内存数据库,内存的 IO 速度本身就很快,所以内存的瓶颈只是受限于内存大小

 怎么理解接口幂等,项目中如何保证的接口幂等

什么是幂等?
简单来说,就是一个接口,使用相同的参数重复执行的情况下,对数据造成的改变只发生一次。(或者说客户端请求微服务之间接口重试等操作)

通常的解决方案有几种。
        1、使用数据库唯一索引的方式实现, 我们可以专门创建一个消息表,里面有一个消息内容的字段并且设置为唯一索引,每次收到消息以后生成 md5 值插入到这个消息表里面。一旦出现重复消息,就会抛异常,我们可以捕获这个异常来避免重复对数据做变更

        2、使用 Redis 里面的 setNx 命令,我们可以把当前请求中带有唯一标识的信息存储到Redis 里面,根据 setNx 命令返回的结果来判断是否是重复执行,如果是则丢弃该请求。

        3、使用状态机的方式来实现幂等,在很多的业务场景中,都会存在业务状态的流转,
并且这些状态流转只会前进,所以我们在对数据进行修改的时候,只需要在条件里
面带上状态,就能避免数据被重复修改的问题。

说一下你对双亲委派的理解

而类的加载过程,需要涉及到类加载器。
JVM 在运行的时候,会产生 3 个类加载器,这三个类加载器组成了一个层级关系每个类加载器分别去加载不同作用范围的 jar 包,比如
    Bootstrap ClassLoader,主要是负责 Java 核心类库的加载,也就是 %{JDK_HOME}\lib 下的 rt.jar、resources.jar 等
    Extension ClassLoader,主要负责%{JDK_HOME}\lib\ext 目录下的 jar 包和 class文件
    Application ClassLoader,主要负责当前应用里面的 classpath 下的所有 jar 包和类文件,是自定义类加载器的父级


除了系统自己提供的类加载器以外,还可以通过 ClassLoader 类实现自定义加载器,去
满足一些特殊场景的需求。

双亲委派:首先会把这个 class 的查询和加载委派给父加载器去执行,如果父加载器都无法加载,再尝试自己来加载这个 class。


 好处:可以避免重复加载导致程序混乱的问题,因为如果父加载器已经加载过了,那么子类就没必要去加载了。


你是怎么理解线程安全问题的?

线程安全问题的具体表现在三个方面,原子性、有序性、可见性。

原子性:是指当一个线程执行一系列程序指令操作的时候,它应该是不可中断的,因为一旦出现中断,站在多线程的视角来看,这一系列的程序指令会出现前后执行结果不一致的问题。
可见性:就是说在多线程环境下,由于读和写是发生在不同的线程里面,有可能出现某个线程对共享变量的修改,对其他线程不是实时可见的。导致可见性问题的原因有很多,比如 CPU 的高速缓存、CPU 的指令重排序、编译器的指令重排序。
有序性:指的是程序编写的指令顺序和最终 CPU 运行的指令顺序可能出现不一致的现象,这种现象也可以称为指令重排序,所以有序性也会导致可见性问题。
 

说一说 Mybatis 里面的缓存机制

一级缓存的具体实现原理是:
        在 SqlSession 里面持有一个 Executor,每个 Executor 中有一个 LocalCache 对象。当用户发起查询的时候,Mybatis 会根据执行语句在 Local Cache 里面查询,如果没命中,再去查询数据库并写入到 LocalCache,否则直接返回。所以,以及缓存的生命周期是 SqlSessiion,而且在多个 Sqlsession 或者分布式环境下,可能会导致数据库写操作出现脏数据。
一级缓存的具体实现原理是
    在 SqlSession 里面持有一个 Executor,每个 Executor 中有一个 LocalCache 对象。当用户发起查询的时候,Mybatis 会根据执行语句在 Local Cache 里面查询,如果没命中,再去查询数据库并写入到 LocalCache,否则直接返回。所以,以及缓存的生命周期是 SqlSessiion,而且在多个 Sqlsession 或者分布式环境下,可能会导致数据库写操作出现脏数据。
二级缓存的具体实现原理是
        开启二级缓存以后,会被多个 SqlSession 共享,所以它是一个全局缓存。因此它的查询流程是先查二级缓存,再查一级缓存,最后再查数据库。另外,MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时缓存粒度也能够到 namespace 级别,并且还可以通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强。

Spring 中 Bean 的作用域有哪些?

    1、singleton, 也就是单例,意味着在整个 Spring 容器中只会存在一个 Bean 实例。
    2、prototype,翻译成原型,意味着每次从 IOC 容器去获取指定 Bean 的时候,都会返回一个新的实例对象。但是在基于 Spring 框架下的 Web 应用里面,增加了一个会话纬度来控制 Bean 的生命周期,主要有三个选择
    3、request, 针对每一次 http 请求,都会创建一个新的 Bean
    4、session,以 sesssion 会话为纬度,同一个 session 共享同一个 Bean 实例,不同的 session 产生不同的 Bean 实例
    5、globalSession,针对全局 session 纬度,共享同一个 Bean 实例
         
Thread 和 Runnable 的区别

1、Thread 是一个类,Runnable 是接口,因为在 Java 语言里面的继承特性,接口可以支持多继承,而类只能单一继承。所以如果在已经存在继承关系的类里面要实现线程的话,只能实现 Runnable 接口。
2、Runnable 表示一个线程的顶级接口,Thread 类其实也是实现了 Runnable 接口。
3、站在面向对象的思想来说,Runnable 相当于一个任务,而 Thread 才是真正处理的线程,所以我们只需要用 Runnable 去定义一个具体的任务,然后交给 Thread去处理就可以了,这样达到了松耦合的设计目的。
4、Runnable 接口定义了线程执行任务的标准方法 run,所以它
     

Kafka 怎么避免重复消费

        1. 提高消费端的处理性能避免触发 Balance,比如可以用异步的方式来处理消息,缩
短单个消息消费的市场。或者还可以调整消息处理的超时时间。还可以减少一次性
从 Broker 上拉取数据的条数。
        2. 可以针对消息生成 md5 然后保存到 mysql 或者 redis 里面,在处理消息之前先去
mysql 或者 redis 里面判断是否已经消费过。这个方案其实就是利用幂等性的思想。

Mybatis 是如何进行分页的

一般可以把分页分成两类
    逻辑分页,先查询出所有的数据缓存到内存,再根据业务相关需求,从内存数据中筛选出合适的数据进行分页。
    物理分页 ,直接利用数据库支持的分页语法来实现,比如 Mysql 里面提供了分页关键词 Limit
Mybatis 提供了四种分页方式
    1、在 Mybatis Mapper 配置文件里面直接写分页 SQL,这种方式比较灵活,实现也简单。
    2、RowBounds 实现逻辑分页,也就是一次性加载所有符合查询条件的目标数据,根据分页参数值在内存中实现分页。
    3、Interceptor 拦截器实现,通过拦截需要分页的 select 语句,然后在这个 sql 语句里面动态拼接分页关键字,从而实现分页查询。(如图)Interceptor 是 Mybatis 提供的一种针对不同生命周期的拦截器,比如:
        a、拦截执行器方法
        b、拦截参数的处理
        c、拦截结果集的处理
        d、拦截 SQL 语法构建的处理
    4、插件(PageHelper)及(MyBaits-Plus、tkmybatis)框架实现;这些插件本质上也是使用 Mybatis 的拦截器来实现的。

SpringBoot 如何解决跨域问题

    跨域指的是浏览器在执行网页中的 JavaScript 代码时,由于浏览器同源策略的限制
   只能访问同源(协议、域名、端口号均相同)的资源,而不能访问其他源(协议、域名、端口号任意一个不同)的资源。

        常见的解决跨域问题的方法有两种,一种是 jsonp,另一种是 CORS。
        其中,CORS 是一种在服务器后端解决跨域的方案,它的工作原理很简单。如果一个网站需要访问另一个网站的资源,浏览器会先发送一个 OPTIONS 请求,根据服务器返回的 Access-Control-Allow-Origin 头信息,决定是否允许跨域访问。所以,我们只需要在服务器端配置 Access-Control-Allow-Origin 属性,并配置允许哪些域名支持跨域请求即可

在 Spring Boot 中,提供了两种配置 Access-Control-Allow-Origin 属性的方式来解决跨域问题
    通过@CrossOrigin(origins = "http://localhost:8080")注解,指定允许哪些origins 允许跨域
    使用 WebMvcConfigurer 接口,重写 addCorsMappings 方法来配置允许跨域的请求源

JDK动态代理与CGLIB实现的区别

JDK动态代理和CGLIB是Java中常用的两种代理技术,它们在实现原理和使用方式上有一些区别。

  • JDK动态代理是基于接口的代理技术,要求目标类必须实现一个或多个接口。它使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来生成代理类和处理代理方法的调用。在运行时,JDK动态代理会动态生成一个代理类,该代理类实现了目标接口,并在方法调用前后插入额外的代码(即代理逻辑)。然而,JDK动态代理只能代理接口,无法代理普通的类。
  • CGLIB是基于继承的代理技术,可以代理普通的类,不需要目标类实现接口它使用字节码生成库,在运行时通过生成目标类的子类来实现代理。CGLIB通过继承目标类创建一个子类,并重写目标方法,以在方法调用前后插入额外的代码(即代理逻辑)。但是,由于继承关系,CGLIB无法代理被标记为final的方法。

总的来说,JDK动态代理适用于基于接口的代理需求,而CGLIB适用于代理普通类的需求。选择使用哪种代理方式取决于具体的需求。如果目标类已经实现了接口且需要基于接口进行代理,可以选择JDK动态代理。而如果目标类没有实现接口,或者需要代理普通类的方法,可以选择CGLIB

谈谈自定义注解的场景及实现

自定义注解是Java语言的一个强大特性,可以为代码添加元数据信息,提供额外配置或标记。它适用于多种场景。

  1. 配置和扩展框架:通过自定义注解,可以为框架提供配置参数或进行扩展。例如,Spring框架中的@Autowired注解用于自动装配依赖项,@RequestMapping注解用于映射请求到控制器方法。
  2. 运行时检查:自定义注解可在运行时对代码进行检查,并进行相应处理。例如,JUnit框架的@Test注解标记测试方法,在运行测试时会自动识别并执行这些方法。
  3. 规范约束:自定义注解用于规范代码风格和约束。例如,Java代码规范检查工具Checkstyle可使用自定义注解标记违规行为。

实现自定义注解的步骤如下:

  1. 使用@interface关键字定义注解。
  2. 可在注解中定义属性,并指定默认值。
  3. 根据需求,可添加元注解来控制注解的使用方式。
  4. 在代码中使用自定义注解。
  5. 使用反射机制解析注解信息。

通过合理运用自定义注解,可提高代码的可读性、可维护性和可扩展性。

说说你对设计模式的理解

      设计模式是一套经过验证的被广泛应用于软件开发中的解决特定问题重复利用的方案集合。它们是在软件开发领域诸多经验的基础上总结出来的,是具有普适性、可重用性和可扩展性的解决方案。

     设计模式通过抽象、封装、继承、多态等特性帮助我们设计出高质量、易扩展、易重构的代码,遵循面向对象的设计原则,如单一职责、开闭原则、依赖倒置、里氏替换等,从而提高代码的可维护性、可测试性和可读性。

    设计模式的优点在于它们已经被广泛验证,可以避免一些常见的软件开发问题,同时也提供了一种标准化的方案来解决这些问题。使用设计模式可以提高代码的复用性,减少代码的重复编写,增加代码的灵活性和可扩展性。设计模式还能降低项目的风险,提高系统的稳定性。

       不过,设计模式不是万能的,对于简单的问题,可能会使代码变得过于复杂,甚至导致反效果。

      在使用设计模式时,需要根据具体的问题需求和实际情况来选择合适的模式,避免滥用模式,并保持代码的简洁、清晰和可读性。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
乐信是一家技术驱动型的公司,因此他们在Java高级面试中会注重应聘者对于Java的深入理解和实际应用能力。 在面试中,乐信可能会问到以下几个方面的问题: 1. Java基础知识:乐信会考察应聘者对于Java基础知识的掌握程度,例如面向对象编程的概念和原则、多线程编程、异常处理机制等。 2. Java集合框架:乐信非常重视应聘者对于Java集合框架的熟悉程度,包括ArrayList、LinkedList、HashMap等常用集合的特性、适用场景以及性能考量等。 3. JVM和垃圾回收机制:乐信关注应聘者对于JVM和垃圾回收机制的了解程度,例如内存模型、类加载机制、垃圾回收算法等。 4. 多线程编程:乐信会提问多线程相关的问题,包括线程的生命周期、线程同步与互斥、线程池的使用等。 5. 设计模式:乐信希望应聘者能够熟练掌握常见的设计模式,并能够根据实际场景选择合适的设计模式进行代码设计。 除了以上几个方面,乐信还可能会针对应聘者的工作经验和项目经历提问,以了解应聘者在实际项目中的工作能力和解决问题的能力。此外,乐信还可能会进行编码和算法方面的考察,以评估应聘者的编码能力和解决复杂问题的能力。 总结来说,乐信的Java高级面试注重应聘者对于Java核心概念的理解、实际项目经验的应用和对于算法的掌握程度。准备这些方面的知识和经验,可在乐信的Java高级面试中有更好的表现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值