面经1——长沙某小厂

车程来回两个多小时,开始自己的第一次面试。抱着积攒面试经验的心态,没怎么准备就去了,面试官没怎么问八股,主要针对我的简历问了我技术相关的知识,下面我将面试官问的问题总结一下,供自己后面含泪复习,有兴趣的小伙伴也可以一起学习。

1、lombok依赖背后是怎么实现的?

用过springboot框架的都知道lombok是一个Java库,我们在实体类上加上@Data注解之后,可以不用自己写类的get()、set()方法了,非常方便

Lombok 的实现方式并不是在运行时动态代理或者使用字节码操作(尽管它内部有类似机制),而是在编译阶段对源代码进行处理。下面是 Lombok 实现的一些关键点:
注解处理器(Annotation Processors):
Lombok 使用了 Java 编译器 API 和注解处理程序(Annotation Processor)。当使用 Lombok 注解时,这些注解会在编译过程中被识别。
在编译期间,Lombok 的注解处理器会读取这些注解,并根据需要生成相应的代码。这个过程是透明的,因为最终生成的 .class 文件中包含了所有必要的方法,但是这些方法并不会出现在源代码文件中。
插件支持:
Lombok 需要 IDE 插件的支持,这样可以在开发环境中实时地显示由 Lombok 生成的方法。如果没有插件支持,在 IDE 中可能看不到由 Lombok 自动生成的方法,但这并不影响编译或运行。
源代码转换:
当使用如 @Data, @Getter, @Setter 等 Lombok 注解时,Lombok 的注解处理器会在编译时扫描使用这些注解的类,并基于类结构自动生成相应的方法。例如,如果一个类中有多个字段并且使用了 @Getter 注解,那么每个字段的 getter 方法都会被自动生成。

2、项目中出现哪些问题,是怎么解决的?

这个大家根据自己的情况回答即可

3、事务用到哪个注解,用在类上还是方法上,使用时有什么注意事项?

常见的事务相关注解
@Transactional:这是 Spring 用于管理事务的主要注解。
使用位置
方法级别:通常情况下,@Transactional 注解应该应用于具体的方法上,以表示该方法需要在一个事务中执行。这样可以灵活地控制哪些方法需要事务支持。
类级别:也可以将 @Transactional 应用于类级别,这表示类中的所有方法都应该是事务性的。然而,这种方法不如方法级别的应用灵活,因为它无法针对不同的方法提供不同的事务配置。
使用注意事项
AOP 代理:为了使 @Transactional 注解生效,目标对象必须通过 AOP 代理。也就是说,对象应该是由 Spring 容器管理的 bean,并且应该是通过代理创建的。如果你直接实例化一个类,即使它带有 @Transactional 注解,事务管理也不会起作用。
传播行为:@Transactional 注解支持配置事务的传播行为(Propagation),比如是否需要新事务、加入现有事务等。开发者可以根据业务需求选择合适的传播行为。
隔离级别:可以通过 isolation 属性来指定事务的隔离级别,例如 ISOLATION_DEFAULT、ISOLATION_READ_UNCOMMITTED 等,以适应不同的并发控制需求。
只读事务:对于那些只读取数据而不修改数据的操作,可以使用 readOnly 属性标记为只读事务。这有助于数据库优化,因为某些数据库系统在只读事务上可能会采用更宽松的锁定策略。
异常回滚:默认情况下,如果方法抛出了未检查异常(即继承自 RuntimeException 的异常),则事务会被回滚。你可以通过 rollbackFor 和 noRollbackFor 属性来指定哪些异常触发回滚或不触发回滚。
性能考虑:频繁的事务操作可能会对性能产生影响。对于一些不需要事务的操作,应该避免使用 @Transactional 注解。
嵌套事务:如果一个事务方法调用了另一个事务方法,则需要考虑事务的嵌套问题。默认情况下,如果方法内部调用了同一对象的另一个事务方法,后者的事务将作为当前事务的一部分执行,而不是作为一个新的独立事务。这可以通过调整 propagation 属性来改变。

4、用到过哪些注解?某某注解的使用规则以及为什么这么设计

会用还不行,还要知道原理

5、IOC和AOP的原理及区别

IOC(Inversion of Control,控制反转)
原理
定义:控制反转是一种设计原则,用于降低代码之间的耦合度。在传统的编程模式中,组件之间通过直接实例化其他组件来获取依赖项,而在使用 IOC 的情况下,依赖关系是由外部容器(通常是一个框架提供的服务)注入的,而不是由组件自身管理的。
实现:在 Java 中,IOC 最常见的实现形式是依赖注入(Dependency Injection,DI)。依赖注入意味着将一个对象所依赖的对象交给外部容器去创建,并通过构造函数、setter 方法或者字段注入等方式传递给对象。
好处:通过依赖注入,对象不再需要知道其依赖项是如何创建、组合或配置的,这使得对象更加松散耦合,易于测试和维护。

// 无 IOC
public class Service {
    private Repository repository;

    public Service() {
        this.repository = new Repository(); // 紧耦合
    }
}
// 使用 IOC
public class Service {
    private Repository repository;

    public Service(Repository repository) { // 依赖通过构造函数注入
        this.repository = repository;
    }

AOP(Aspect Oriented Programming,面向切面编程)
原理
定义:面向切面编程是一种编程范式,它旨在将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。横切关注点是指那些分散在整个应用程序中的、与核心业务逻辑无关的功能,比如日志记录、事务管理、安全控制等。
实现:AOP 通过代理(Proxy)和织入(Weaving)技术来实现。在 Java 中,最常用的是通过 Spring AOP 或 AspectJ 框架来实现 AOP。代理模式下,AOP 框架会创建一个代理对象,该对象包装了原始对象,并在其前后添加了增强的代码。织入指的是将切面(Aspect)与普通业务逻辑相结合的过程,可以在编译时或运行时完成。
好处:通过将横切关注点与业务逻辑分离,AOP 可以帮助开发者更专注于核心业务逻辑的开发,同时使得这些关注点更易于管理和重用。

区别
关注点不同:IOC 主要关注于组件间的依赖关系管理,而 AOP 则侧重于处理横切关注点。
解决的问题不同:IOC 解决的是对象创建和依赖管理的问题,使得对象之间更加解耦;而 AOP 解决的是如何优雅地处理那些散布在各个模块中的公共逻辑,使得这些逻辑可以从核心业务逻辑中分离出来。
实现方式不同:虽然两者都可以通过 DI 框架来实现(比如 Spring),但是它们的应用场景和实现细节有所不同。IOC 更多地涉及到对象的创建和生命周期管理,而 AOP 更多地涉及到在方法执行前后的增强。
总结来说,IOC 和 AOP 都是为了提高代码的可维护性和解耦性,但它们解决的问题领域和实现方式有所不同。

6、Java中如何实现两个集合中重复元素的查询

方法一:使用 HashSet

使用 HashSet 来存储第一个集合中的元素,然后遍历第二个集合,检查元素是否已经在 HashSet 中存在。

import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;

public class DuplicateFinder {
    public static <T> List<T> findDuplicates(List<T> list1, List<T> list2) {
        Set<T> set = new HashSet<>(list1);
        List<T> duplicates = new ArrayList<>();
        for (T item : list2) {
            if (set.contains(item)) {
                duplicates.add(item);
            }
        }
        return duplicates;
    }

    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>(List.of(1, 2, 3, 4, 5));
        List<Integer> list2 = new ArrayList<>(List.of(4, 5, 6, 7, 8));
        List<Integer> duplicates = findDuplicates(list1, list2);
        System.out.println(duplicates); // 输出 [4, 5]
    }
}
方法二:使用 Set 交集

利用 retainAll() 方法来找出两个集合的交集。

import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;

public class DuplicateFinder {
    public static <T> List<T> findDuplicates(List<T> list1, List<T> list2) {
        Set<T> set1 = new HashSet<>(list1);
        Set<T> set2 = new HashSet<>(list2);
        set1.retainAll(set2);
        return new ArrayList<>(set1);
    }

    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>(List.of(1, 2, 3, 4, 5));
        List<Integer> list2 = new ArrayList<>(List.of(4, 5, 6, 7, 8));
        List<Integer> duplicates = findDuplicates(list1, list2);
        System.out.println(duplicates); // 输出 [4, 5]
    }
}
方法三:使用 Java Streams

如果你使用的是 Java 8 或更高版本,可以利用 Stream API 来简化代码。

import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

public class DuplicateFinder {
    public static <T> List<T> findDuplicates(List<T> list1, List<T> list2) {
        Set<T> set = new HashSet<>(list1);
        return list2.stream()
                    .filter(set::contains)
                    .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        List<Integer> list1 = List.of(1, 2, 3, 4, 5);
        List<Integer> list2 = List.of(4, 5, 6, 7, 8);
        List<Integer> duplicates = findDuplicates(list1, list2);
        System.out.println(duplicates); // 输出 [4, 5]
    }
}
方法四:使用 Guava 库

如果你的项目中引入了 Google Guava 库,可以使用 Sets.intersection() 方法来查找两个集合的交集。

import com.google.common.collect.Sets;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;

public class DuplicateFinder {
    public static <T> List<T> findDuplicates(List<T> list1, List<T> list2) {
        Set<T> set1 = new HashSet<>(list1);
        Set<T> set2 = new HashSet<>(list2);
        Set<T> intersection = Sets.intersection(set1, set2).immutableCopy();
        return new ArrayList<>(intersection);
    }

    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>(List.of(1, 2, 3, 4, 5));
        List<Integer> list2 = new ArrayList<>(List.of(4, 5, 6, 7, 8));
        List<Integer> duplicates = findDuplicates(list1, list2);
        System.out.println(duplicates); // 输出 [4, 5]
    }
}

7、事务一般用于什么场景?

事务(Transaction)在软件开发中主要用于处理那些需要保证数据一致性和完整性的操作。事务提供了一种机制,确保一系列操作要么全部成功,要么全部失败。事务通常用于以下几种场景:

数据库操作
批量更新:当需要执行一系列的数据库操作,如插入、更新或删除多条记录时,这些操作应该作为一个整体来执行。如果其中一个操作失败,之前成功的操作也需要回滚,以保持数据的一致性。
资金转移:例如银行转账,从一个账户扣款并存入另一个账户,这两个操作必须一起成功或一起失败,否则会导致账目不平衡。
复杂的业务逻辑:任何涉及多个表的数据修改操作,都需要保证原子性、一致性、隔离性和持久性(ACID 特性)。
分布式系统
分布式事务:在微服务架构中,多个服务可能需要协调执行一系列操作。例如,订单服务创建订单后,库存服务需要减库存,支付服务需要冻结金额等。如果这些操作不能保证一致性,就可能需要使用分布式事务来协调。
消息队列:在使用消息队列时,发送方发送消息和接收方确认消息接收需要保证一致性。如果消息发送成功但接收方未能正确处理,则需要有机制保证消息能够重新处理或回滚。
文件系统操作
文件上传/下载:当需要将一个大文件分割成多个部分上传或下载时,这些部分的上传/下载操作需要作为一个事务处理,确保文件的完整性。
文件备份:在备份文件系统时,备份操作需要确保所有文件的状态一致,否则备份数据可能无效。
并发控制
并发更新冲突:在高并发环境下,多个用户或进程可能同时访问和修改同一资源。事务可以确保在并发情况下,数据的一致性和完整性不会被破坏。
其他场景
状态机转换:当业务流程涉及多个状态的转换时,这些转换操作通常需要在一个事务中完成,以确保状态转换的一致性和正确性。
日志记录:在一些场景下,事务不仅涉及数据的变更,还可能包括日志记录,确保日志信息与数据变更同步完成。

事务的使用是为了确保数据操作的一致性和可靠性。在设计系统时,需要根据具体的业务需求来决定何时使用事务。不恰当的事务使用可能导致性能问题(如锁争用),因此在设计时还需要考虑事务的粒度和并发控制策略。

8、怎么实现统一错误码和全局异常处理器?

9、怎么实现单元测试?单元测试的目的是什么?

记得提断言

总结:知其然还得知其所以然,活到老学到老

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光阿盖尔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值