面了快一个小时,而且面试官语气很温和。
目录
4.new 两个Integer(1)用==判断是true还是false
9. 调用 CommandLineRunner 和 ApplicationRunner
使用 spring.config.name 和 spring.config.location 属性
1. 使用 mvn dependency:tree 命令分析冲突
1.基本数据类型对应长度
byte(8位),short(16位),int(32位),long(64位),float(32位),double(64位)
char(16位只有正值),boolean
2.String是不是基本类型,可不可以被继承吗?
String不是基本类型是引用类型,String被final修饰了不能被继承。
3.==和equals的区别
==
是 Java 中的一个运算符,它的比较行为取决于操作数的类型:
- 基本数据类型:比较的是两个变量存储的值是否相等。
- 引用数据类型:比较的是两个引用是否指向内存中的同一个对象,即比较的是对象的内存地址
equals()
是 Object
类中定义的一个方法,所有的 Java 类都继承自 Object
类,因此都拥有 equals()
方法。它的比较规则可以根据具体需求在子类中被重写:
Object
类的equals()
方法:默认实现与==
相同,即比较两个对象的内存地址是否相等。- 重写后的
equals()
方法:很多类(如String
、Integer
等)会重写equals()
方法,以实现基于对象内容的比较。
4.new 两个Integer(1)用==判断是true还是false
==
运算符对于引用数据类型,比较的是两个引用是否指向内存中的同一个对象,也就是比较对象的内存地址。当使用 new
关键字创建对象时,每次调用 new
都会在堆内存中开辟一块新的内存空间,即使创建的对象包含相同的值,它们在内存中的地址也是不同的。
如果不使用 new
关键字,而是采用自动装箱的方式创建 Integer
对象,对于 -128
到 127
之间的值,Java 会使用缓存机制,此时相同值的 Integer
对象使用 ==
比较会返回 true
public class IntegerAutoBoxingComparisonExample {
public static void main(String[] args) {
// 自动装箱创建两个 Integer 对象,值都为 1
Integer num3 = 1;
Integer num4 = 1;
// 使用 == 运算符比较这两个对象
boolean result2 = num3 == num4;
System.out.println("使用 == 比较两个自动装箱的 Integer(1) 的结果: " + result2);
}
}
5.程池的几个核心参数
ThreadPoolExecutor
的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:核心线程数。线程池在初始化时会创建一定数量的核心线程,这些线程在空闲时也不会被销毁,会一直存活在线程池中等待任务的到来。
maximumPoolSize:最大线程数。线程池允许创建的最大线程数量。当核心线程都在执行任务,且任务队列已满时,线程池会创建新的线程,直到线程数量达到最大线程数。
keepAliveTime:线程空闲时间。当线程池中的线程数量超过核心线程数时,多余的线程在空闲一段时间后会被销毁,这段空闲时间就是 keepAliveTime。
unit:keepAliveTime 的时间单位,例如 TimeUnit.SECONDS 表示秒。
workQueue:任务队列。用于存储等待执行的任务。当核心线程都在忙碌时,新提交的任务会被放入这个队列中等待执行。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
threadFactory:线程工厂,用于创建线程。可以通过自定义线程工厂来设置线程的名称、优先级等属性。
handler:拒绝策略。当任务队列已满,且线程池中的线程数量达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由提交任务的线程自己执行)等。
6.线程池有几种拒绝策略
AbortPolicy
(默认策略):当触发拒绝策略时,直接抛出 RejectedExecutionException
异常,阻止系统正常工作。
CallerRunsPolicy:
当触发拒绝策略时,由提交任务的线程来执行该任务。这种策略可以降低新任务的提交速度,给线程池一定的缓冲时间来处理已有的任务。
DiscardPolicy:
当触发拒绝策略时,直接丢弃新提交的任务,并且不会抛出任何异常。
DiscardOldestPolicy:
当触发拒绝策略时,丢弃任务队列中最老的任务,也就是队列头部的任务,然后尝试重新提交新任务。
自定义拒绝策略:可以通过实现 RejectedExecutionHandler
接口来自定义拒绝策略
7.final和finally的区别
final:当 final
修饰一个类时,表明这个类是最终类,不能被其他类继承。这样设计可以保证类的安全性和稳定性,防止类的功能被意外修改。当 final
修饰一个方法时,表明这个方法是最终方法,不能被其子类重写。这有助于确保方法的实现逻辑不被改变,保证了方法的一致性。当 final
修饰一个变量时,表明这个变量是常量,一旦被赋值后,其值就不能再被修改。对于基本数据类型,其值不能改变;对于引用数据类型,其引用不能改变,但对象的内容可以改变。
finally:finally
关键字主要用于 try-catch
语句块中,它定义了一个始终会被执行的代码块,无论 try
块中是否发生异常。finally
块通常用于释放资源,如关闭文件、数据库连接、网络连接等。
8.Lambda 表达式
Lambda 表达式的基本语法有以下两种形式:
无参:
() -> 表达式
() -> { 语句块; }
有参:
(参数列表) -> 表达式
(参数列表) -> { 语句块; }
使用场景及示例:
1. 作为函数式接口的实例
Lambda 表达式主要用于实现函数式接口。函数式接口是指只包含一个抽象方法的接口。Java 中有很多内置的函数式接口,如 Runnable
、Comparator
等。
使用 Runnable
接口示例
public class LambdaRunnableExample {
public static void main(String[] args) {
// 使用 Lambda 表达式创建 Runnable 实例
Runnable runnable = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("Running task: " + i);
}
};
// 创建并启动线程
Thread thread = new Thread(runnable);
thread.start();
}
}
在上述代码中,() -> { ... }
就是一个 Lambda 表达式,它实现了 Runnable
接口的 run()
方法。
使用 Comparator
接口示例
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class LambdaComparatorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("banana");
list.add("apple");
list.add("cherry");
// 使用 Lambda 表达式实现 Comparator 接口
Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
// 对列表进行排序
Collections.sort(list, comparator);
// 输出排序后的列表
for (String s : list) {
System.out.println(s);
}
}
}
在这个例子中,(s1, s2) -> s1.compareTo(s2)
是一个 Lambda 表达式,它实现了 Comparator
接口的 compare()
方法。
2. 作为方法参数传递
Lambda 表达式可以作为方法的参数传递,这样可以使代码更加简洁和灵活。
import java.util.Arrays;
import java.util.List;
interface Condition {
boolean test(String s);
}
public class LambdaAsParameterExample {
public static void printStrings(List<String> list, Condition condition) {
for (String s : list) {
if (condition.test(s)) {
System.out.println(s);
}
}
}
public static void main(String[] args) {
List<String> list = Arrays.asList("apple", "banana", "cherry");
// 使用 Lambda 表达式作为方法参数
printStrings(list, s -> s.startsWith("a"));
}
}
在上述代码中,s -> s.startsWith("a")
是一个 Lambda 表达式,它作为 printStrings
方法的第二个参数传递。
注意事项
- Lambda 表达式只能用于函数式接口。
- Lambda 表达式的参数类型可以省略,编译器会根据上下文自动推断。
- 如果 Lambda 表达式的函数体只有一条语句,可以省略花括号
{}
和return
关键字。
9.有哪些设计模式?
可分为创建型、结构型和行为型三大类,
创建型模式
创建型模式主要用于对象的创建过程,帮助我们将对象的创建和使用分离,提高软件的灵活性和可维护性。
- 单例模式(Singleton Pattern)
- 定义:确保一个类只有一个实例,并提供一个全局访问点。
- 应用场景:如任务管理器、数据库连接池等,确保系统中该类只有一个实例,避免资源浪费。
- 工厂模式(Factory Pattern)
- 定义:定义一个创建对象的接口,让子类决定实例化哪个类。
- 应用场景:当创建对象的逻辑较为复杂,或者需要根据不同条件创建不同类型的对象时使用,例如简单工厂可根据传入参数创建不同的图形对象。
- 抽象工厂模式(Abstract Factory Pattern)
- 定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
- 应用场景:在创建一组相关的对象时使用,如不同操作系统下的按钮、文本框等 UI 组件的创建。
- 建造者模式(Builder Pattern)
- 定义:将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
- 应用场景:当创建一个复杂对象,其创建步骤较多且顺序不固定时使用,例如创建汽车对象,可分别设置不同的部件。
- 原型模式(Prototype Pattern)
- 定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 应用场景:当创建对象的成本较高,或者需要创建多个相似对象时使用,如通过克隆已有对象来创建新对象。
结构型模式
结构型模式主要用于处理类或对象的组合,以获得更强大的结构,提高软件的可扩展性和可维护性。
- 代理模式(Proxy Pattern)
- 定义:为其他对象提供一种代理以控制对这个对象的访问。
- 应用场景:当需要对一个对象进行访问控制、增强功能或延迟加载时使用,如网络代理、Spring AOP 中的代理。
- 装饰器模式(Decorator Pattern)
- 定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
- 应用场景:在不改变原有对象结构的基础上,动态地给对象添加新功能,如 Java 的
I/O
流中的装饰器类。
- 适配器模式(Adapter Pattern)
- 定义:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 应用场景:当需要将一个已有的类的接口转换为另一个接口时使用,如将旧系统的接口适配到新系统中。
- 桥接模式(Bridge Pattern)
- 定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
- 应用场景:当一个类存在多个变化维度,且这些维度需要独立变化时使用,如不同颜色和形状的图形。
- 组合模式(Composite Pattern)
- 定义:将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
- 应用场景:当需要处理树形结构的对象时使用,如文件系统、公司组织架构等。
- 外观模式(Facade Pattern)
- 定义:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 应用场景:当需要简化复杂系统的使用,为客户端提供一个统一的接口时使用,如计算机的启动过程。
- 享元模式(Flyweight Pattern)
- 定义:运用共享技术有效地支持大量细粒度的对象。
- 应用场景:当系统中存在大量相似对象,且这些对象的大部分状态可以共享时使用,如文本编辑器中的字符对象。
行为型模式
行为型模式主要用于处理对象之间的交互和职责分配,提高软件的灵活性和可维护性。
- 策略模式(Strategy Pattern)
- 定义:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式使得算法的变化可独立于使用它的客户。
- 应用场景:当需要根据不同情况选择不同算法时使用,如电商系统中的不同促销策略。
- 模板方法模式(Template Method Pattern)
- 定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 应用场景:当多个子类有共同的行为,且这些行为的步骤基本相同,但某些步骤的实现可能不同时使用,如游戏开发中的游戏流程。
- 观察者模式(Observer Pattern)
- 定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
- 应用场景:当一个对象的状态变化需要通知其他多个对象时使用,如 GUI 编程中的事件监听机制。
- 迭代器模式(Iterator Pattern)
- 定义:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
- 应用场景:当需要遍历一个集合对象,且不希望暴露集合的内部实现时使用,如 Java 中的
Iterator
接口。
- 责任链模式(Chain of Responsibility Pattern)
- 定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
- 应用场景:当一个请求需要经过多个对象处理时使用,如 Java Web 中的过滤器链。
- 命令模式(Command Pattern)
- 定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
- 应用场景:当需要将请求的发送者和接收者解耦,并且支持请求的排队、记录和撤销等操作时使用,如 GUI 编程中的菜单命令。
- 备忘录模式(Memento Pattern)
- 定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
- 应用场景:当需要保存对象的历史状态,并且支持撤销操作时使用,如文本编辑器的撤销功能。
- 状态模式(State Pattern)
- 定义:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
- 应用场景:当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时使用,如电梯的不同状态。
- 访问者模式(Visitor Pattern)
- 定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- 应用场景:当需要对一个对象结构中的元素进行多种不同的操作,且不希望修改这些元素的类时使用,如编译器的语法分析器。
- 中介者模式(Mediator Pattern)
- 定义:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 应用场景:当多个对象之间存在复杂的交互关系,且这些交互关系需要集中管理时使用,如多人聊天系统中的聊天服务器。
10.如何使用单例模式
1. 饿汉式单例
- 特点:在类加载时就创建单例实例,线程安全,但可能会造成资源浪费,因为即使该实例在程序运行过程中未被使用,也会被创建。
public class EagerSingleton {
// 静态成员变量,在类加载时就创建单例实例
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造函数,防止外部通过 new 关键字创建实例
private EagerSingleton() {}
// 提供公共的静态方法,用于获取单例实例
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
public class EagerSingletonUsage {
public static void main(String[] args) {
// 获取单例实例
EagerSingleton singleton1 = EagerSingleton.getInstance();
EagerSingleton singleton2 = EagerSingleton.getInstance();
// 验证两个实例是否相同
System.out.println(singleton1 == singleton2); // 输出: true
}
}
2.懒汉式单例(非线程安全)
- 特点:在第一次使用时才创建单例实例,避免了资源浪费,但在多线程环境下可能会创建多个实例,线程不安全。
public class LazySingleton {
// 静态成员变量,初始值为 null
private static LazySingleton INSTANCE;
// 私有构造函数,防止外部通过 new 关键字创建实例
private LazySingleton() {}
// 提供公共的静态方法,用于获取单例实例
public static LazySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
public class LazySingletonUsage {
public static void main(String[] args) {
// 获取单例实例
LazySingleton singleton1 = LazySingleton.getInstance();
LazySingleton singleton2 = LazySingleton.getInstance();
// 验证两个实例是否相同
System.out.println(singleton1 == singleton2); // 输出: true
}
}
11.spring中bean对象的作用域
在 Spring 框架里,Bean 的作用域定义了 Bean 实例在 Spring 容器中的生命周期和可见范围
1. singleton
(单例作用域)
- 描述:这是 Spring 里 Bean 的默认作用域。在单例作用域下,Spring 容器只会创建该 Bean 的一个实例,并且在整个应用的生命周期内,所有对该 Bean 的请求都会返回同一个实例。
- 适用场景:适用于无状态的 Bean,例如服务层、数据访问层的组件,因为它们不会保存特定于某个请求或用户的状态,多个地方共享一个实例不会引发问题。
- 配置示例(XML 方式):
<bean id="userService" class="com.example.UserService" scope="singleton"/>
- 配置示例(注解方式):
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
@Service
@Scope("singleton")
public class UserService {
// 类的具体实现
}
2. prototype
(原型作用域)
- 描述:每次向 Spring 容器请求该 Bean 时,容器都会创建一个新的实例返回。也就是说,不同的请求会得到不同的 Bean 实例。
- 适用场景:适用于有状态的 Bean,比如保存用户会话信息的对象,因为每个用户的会话信息是不同的,需要为每个请求创建独立的实例。
<bean id="shoppingCart" class="com.example.ShoppingCart" scope="prototype"/>
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class ShoppingCart {
// 类的具体实现
}
3. request
(请求作用域)
- 描述:仅在基于 Web 的 Spring 应用中有效。该作用域下的 Bean 实例会在每个 HTTP 请求期间存在,即每个新的 HTTP 请求都会创建一个新的 Bean 实例,当请求处理完成后,该实例会被销毁。
- 适用场景:适用于保存单个请求相关信息的 Bean,例如处理请求参数、请求头的对象。
<bean id="requestInfo" class="com.example.RequestInfo" scope="request">
<aop:scoped-proxy/>
</bean>
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestInfo {
// 类的具体实现
}
4.session
(会话作用域)
- 描述:同样仅在基于 Web 的 Spring 应用中有效。该作用域下的 Bean 实例会在用户的整个会话期间存在,即每个新的用户会话会创建一个新的 Bean 实例,当会话结束时,该实例会被销毁。
- 适用场景:适用于保存用户会话信息的 Bean,例如用户的登录状态、购物车信息等。
<bean id="userSession" class="com.example.UserSession" scope="session">
<aop:scoped-proxy/>
</bean>
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
// 类的具体实现
}
5.application
(应用作用域)
- 描述:在基于 Web 的 Spring 应用中,该作用域下的 Bean 实例会在整个 ServletContext 生命周期内存在,类似于 ServletContext 级别的单例,整个应用中只有一个实例。
- 适用场景:适用于保存整个应用共享信息的 Bean,例如应用的全局配置信息。
<bean id="appConfig" class="com.example.AppConfig" scope="application">
<aop:scoped-proxy/>
</bean>
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class AppConfig {
// 类的具体实现
}
6.websocket
(WebSocket 作用域)
- 描述:这是 Spring 4.2 引入的作用域,仅在 WebSocket 应用中有效。该作用域下的 Bean 实例会在每个 WebSocket 会话期间存在,当 WebSocket 会话关闭时,该实例会被销毁。
- 适用场景:适用于处理 WebSocket 连接相关信息的 Bean,例如管理 WebSocket 连接状态的对象。
12.Spring的注解知道哪些?
@Component
:这是一个通用的注解,可用于标记任意类为 Spring 组件。被标记的类会被 Spring 自动扫描并注册为 Bean。
@Repository
:用于标记数据访问层(DAO)的类,是@Component
的一种特殊形式,能将数据访问层的异常转换为 Spring 的数据访问异常。
@Service
:用于标记服务层的类,同样是@Component
的特殊形式,表明该类是业务逻辑处理的服务类。
@Controller
:用于标记控制器层的类,也是@Component
的特殊形式,在 Spring MVC 中用于处理 HTTP 请求。
@Configuration
:用于标记配置类,该类相当于 Spring 的 XML 配置文件,类中的方法可以使用@Bean
注解来定义 Bean。
@Bean
:通常在@Configuration
注解的类中使用,用于定义一个 Bean,方法的返回值会被注册为 Spring 容器中的 Bean。
@Autowired
:Spring 提供的自动装配注解,默认按照类型进行装配。
@Qualifier
:当有多个相同类型的 Bean 可供注入时,使用@Qualifier
注解指定要注入的 Bean 的名称。
@Resource
:Java JSR-250 规范提供的注解,默认按照名称进行装配,如果没有指定名称,则按照类型进行装配。
@Aspect
:用于标记一个类为切面类,该类中可以定义切入点和通知。
@Before
:前置通知注解,在目标方法执行之前执行。
@After
:后置通知注解,在目标方法执行之后执行,无论目标方法是否抛出异常。
@AfterReturning
:返回通知注解,在目标方法正常返回后执行。
@AfterThrowing
:异常通知注解,在目标方法抛出异常时执行。@Around
:环绕通知注解,环绕目标方法执行,可以在目标方法执行前后进行增强处理
@Transactional
:用于标记一个方法或类需要进行事务管理,Spring 会自动为其创建事务@RequestMapping
:用于映射 HTTP 请求到控制器的处理方法
@GetMapping
:@RequestMapping
的一种快捷方式,专门用于处理 HTTP GET 请求@PostMapping
:@RequestMapping
的快捷方式,用于处理 HTTP POST 请求@RequestBody
:用于将 HTTP 请求的主体内容绑定到方法的参数上,通常用于处理 JSON 或 XML 数据。@ResponseBody
:用于将方法的返回值直接作为 HTTP 响应的主体内容返回,通常用于返回 JSON 或 XML 数据。
13.springmvc的执行流程
(1)当用户通过浏览器发起一个HTTP请求,请求直接到前端控制器DispatcherServlet;
(2)前端控制器接收到请求以后调用处理器映射器HandlerMapping,处理器映射器根据请求的URL找到具体的Handler,并将它返回给前端控制器;
(3)前端控制器调用处理器适配器HandlerAdapter去适配调用Handler;
(4)处理器适配器会根据Handler去调用真正的处理器去处理请求,并且处理对应的业务逻辑;
(5)当处理器处理完业务之后,会返回一个ModelAndView对象给处理器适配器,HandlerAdapter再将该对象返回给前端控制器;这里的Model是返回的数据对象,View是逻辑上的View。
(6)前端控制器DispatcherServlet将返回的ModelAndView对象传给视图解析器ViewResolver进行解析,解析完成之后就会返回一个具体的视图View给前端控制器。(ViewResolver根据逻辑的View查找具体的View)
(7)前端控制器DispatcherServlet将具体的视图进行渲染,渲染完成之后响应给用户(浏览器显示)。参考:SpringMVC的执行流程以及运行原理_springmvc执行流程-CSDN博客
14.spring中Bean对象的生命周期
1. 实例化
这是 Bean 生命周期的起始点,Spring 容器会根据配置信息创建 Bean 的实例。比如,当使用 XML 配置时,Spring 会依据 <bean>
标签中的 class
属性来调用构造函数创建实例;若采用注解方式,Spring 会扫描带有 @Component
、@Service
等注解的类并实例化。
// 使用注解定义一个 Bean
import org.springframework.stereotype.Component;
@Component
public class MyBean {
public MyBean() {
System.out.println("MyBean 实例化");
}
}
2. 属性赋值
在实例化 Bean 之后,Spring 会依据配置为 Bean 的属性进行赋值。这可以通过 XML 中的 <property>
标签、注解(如 @Autowired
、@Resource
)等方式实现。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AnotherBean {
private MyBean myBean;
@Autowired
public AnotherBean(MyBean myBean) {
this.myBean = myBean;
System.out.println("AnotherBean 属性赋值");
}
}
3. 初始化
实现 InitializingBean
接口
若 Bean 实现了 InitializingBean
接口,Spring 会调用其 afterPropertiesSet()
方法。此方法常用于在 Bean 的属性赋值完成后进行一些初始化操作。
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class InitBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitBean 实现 InitializingBean 接口的初始化方法");
}
}
使用 @PostConstruct
注解
在 Bean 的方法上添加 @PostConstruct
注解,该方法会在 Bean 的属性赋值完成后被调用,同样用于初始化操作。
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class PostConstructBean {
@PostConstruct
public void init() {
System.out.println("PostConstructBean 使用 @PostConstruct 注解的初始化方法");
}
}
自定义初始化方法
在 XML 配置或使用 @Bean
注解时,可以指定自定义的初始化方法,Spring 会在合适的时机调用该方法。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(initMethod = "customInit")
public CustomInitBean customInitBean() {
return new CustomInitBean();
}
}
class CustomInitBean {
public void customInit() {
System.out.println("CustomInitBean 自定义初始化方法");
}
}
4. Bean 就绪并使用
经过上述步骤,Bean 已完成初始化,可以被 Spring 容器管理并供其他组件使用。在应用的运行过程中,其他 Bean 可以通过依赖注入的方式获取并调用该 Bean 的方法。
5. 销毁
实现 DisposableBean
接口
若 Bean 实现了 DisposableBean
接口,在 Spring 容器关闭时,会调用其 destroy()
方法,可用于释放资源等操作。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
@Component
public class DisposableBeanExample implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("DisposableBeanExample 实现 DisposableBean 接口的销毁方法");
}
}
使用 @PreDestroy
注解
在 Bean 的方法上添加 @PreDestroy
注解,该方法会在 Spring 容器关闭时被调用,用于执行资源清理等操作。
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class PreDestroyBean {
@PreDestroy
public void cleanup() {
System.out.println("PreDestroyBean 使用 @PreDestroy 注解的销毁方法");
}
}
自定义销毁方法
在 XML 配置或使用 @Bean
注解时,可以指定自定义的销毁方法,Spring 会在容器关闭时调用该方法
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(destroyMethod = "customDestroy")
public CustomDestroyBean customDestroyBean() {
return new CustomDestroyBean();
}
}
class CustomDestroyBean {
public void customDestroy() {
System.out.println("CustomDestroyBean 自定义销毁方法");
}
}
15. @ComponentScan
的作用
在传统的 Spring 开发中,需要在 XML 配置文件里通过 <bean>
标签逐个定义 Bean。但使用 @ComponentScan
注解后,Spring 能够自动扫描指定包及其子包下带有特定注解(如 @Component
、@Repository
、@Service
、@Controller
等)的类,并将这些类注册为 Spring Bean,从而减少了大量的手动配置工作,提高了开发效率。
16.场景题
如果我有hr类和程序员类实现了people接口,然后我autowried people的时候我只想注入hr类如何做?
方法一:使用 @Qualifier
注解
@Qualifier
注解可与 @Autowired
配合使用,通过指定 Bean 的名称来明确注入哪个具体的 Bean 实例。
// 定义 People 接口
interface People {
void work();
}
// 定义 HR 类实现 People 接口
import org.springframework.stereotype.Component;
@Component("hrPeople")
class HR implements People {
@Override
public void work() {
System.out.println("HR is recruiting.");
}
}
// 定义程序员类实现 People 接口
import org.springframework.stereotype.Component;
@Component("programmerPeople")
class Programmer implements People {
@Override
public void work() {
System.out.println("Programmer is coding.");
}
}
// 注入 People 类型的 Bean
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
class CompanyService {
private People people;
@Autowired
@Qualifier("hrPeople")
public CompanyService(People people) {
this.people = people;
}
public void doWork() {
people.work();
}
}
方法二:使用 @Primary
注解
@Primary
注解可标注在 Bean 定义上,当存在多个相同类型的 Bean 时,被 @Primary
标注的 Bean 会作为默认的注入对象。
17.springboot的启动流程
1. 启动入口
Spring Boot 应用的启动通常从包含 @SpringBootApplication
注解的主类开始,主类中包含 main
方法,调用 SpringApplication.run()
方法来启动应用。
2. 创建 SpringApplication
实例
当调用 SpringApplication.run()
方法时,首先会创建一个 SpringApplication
对象。在创建过程中,会进行一系列的初始化操作:
- 推断应用类型:判断应用是 Web 应用(如 Servlet Web 应用、Reactive Web 应用)还是非 Web 应用。
- 查找并加载初始化器(
ApplicationContextInitializer
):初始化器用于在ApplicationContext
创建后但还未刷新之前进行一些额外的配置。 - 查找并加载监听器(
ApplicationListener
):监听器用于监听应用启动过程中的各种事件,如应用启动事件、应用失败事件等。 - 推断主类:确定包含
main
方法的主类。
3. 准备环境
在创建 SpringApplication
对象后,会调用 prepareEnvironment()
方法来准备应用的运行环境:
- 创建并配置
Environment
对象:Environment
对象包含了应用的配置信息,如系统属性、环境变量、配置文件等。 - 触发
ApplicationEnvironmentPreparedEvent
事件:通知所有监听器应用环境已准备好。
4. 创建 ApplicationContext
根据之前推断的应用类型,创建相应的 ApplicationContext
实例。例如,如果是 Servlet Web 应用,会创建 AnnotationConfigServletWebServerApplicationContext
;如果是非 Web 应用,会创建 AnnotationConfigApplicationContext
。
5. 准备 ApplicationContext
在 ApplicationContext
创建后,会进行一些准备工作:
- 设置
Environment
对象:将之前准备好的Environment
对象设置到ApplicationContext
中。 - 应用初始化器(
ApplicationContextInitializer
):依次调用之前加载的初始化器,对ApplicationContext
进行额外的配置。 - 触发
ApplicationContextInitializedEvent
事件:通知所有监听器ApplicationContext
已初始化。
6. 加载 Bean 定义
通过 SpringApplication
的 load()
方法,将主类作为配置类,使用 AnnotatedBeanDefinitionReader
和 ClassPathBeanDefinitionScanner
来扫描和加载 Bean 定义。同时,Spring Boot 的自动配置机制会根据类路径中的依赖和配置信息,自动配置一些常用的 Bean。
7. 刷新 ApplicationContext
调用 ApplicationContext
的 refresh()
方法,这是一个核心步骤,会完成以下工作:
- 创建 BeanFactory:创建
DefaultListableBeanFactory
,用于管理 Bean 的创建和依赖注入。 - 加载 Bean 定义到 BeanFactory:将之前扫描和加载的 Bean 定义注册到
BeanFactory
中。 - 创建并初始化 Bean:根据 Bean 的定义,创建和初始化所有的单例 Bean。
- 启动嵌入式服务器(如果是 Web 应用):如果是 Web 应用,会启动嵌入式服务器(如 Tomcat、Jetty 等),并将
ApplicationContext
注册到服务器中。
8. 触发应用启动完成事件
在 ApplicationContext
刷新完成后,会触发 ApplicationStartedEvent
和 ApplicationReadyEvent
事件,通知所有监听器应用已启动并准备好接受请求。
9. 调用 CommandLineRunner
和 ApplicationRunner
如果应用中定义了实现 CommandLineRunner
或 ApplicationRunner
接口的 Bean,会依次调用它们的 run()
方法,这些方法可以用于在应用启动后执行一些初始化任务。
10. 应用启动完成
经过以上步骤,Spring Boot 应用成功启动,开始处理外部请求。参考SpringBoot启动流程解析(总结的非常好,很清晰!)_springboot的启动流程-CSDN博客
18.SpringBoot核心注解?
@SpringBootApplication
这是一个组合注解,相当于同时使用了 @SpringBootConfiguration
、@EnableAutoConfiguration
和 @ComponentScan
三个注解。
@SpringBootConfiguration:
它是 @Configuration
的派生注解,表明该类是一个配置类,Spring Boot 会将其作为配置源来加载 Bean 定义。通常与 @Bean
注解配合使用,用于定义和管理 Bean。
@EnableAutoConfiguration:
启用 Spring Boot 的自动配置机制。Spring Boot 会根据类路径中的依赖和配置信息,自动为应用配置一些常用的 Bean,减少了手动配置的工作量
@ComponentScan:
用于指定 Spring 要扫描的包路径,Spring 会自动扫描这些包及其子包下带有 @Component
、@Service
、@Repository
、@Controller
等注解的类,并将它们注册为 Bean。
19.一个项目要加载多个yml配置文件有几种方法?
通过设置 JVM 参数
可以在启动 Java 应用时,通过 -D
选项设置 JVM 参数 spring.config.additional-location
来指定额外的配置文件位置,这样就能加载多个 YAML 配置文件。
通过 Nacos 加载多个 YAML 配置文件
Nacos 是一个开源的动态服务发现、配置管理和服务管理平台,可以将多个 YAML 配置文件存储在 Nacos 中,然后让 Spring Boot 项目从 Nacos 加载这些配置
使用 spring.config.name
和 spring.config.location
属性
你可以通过 spring.config.name
指定配置文件的名称,使用 spring.config.location
指定配置文件的位置。可以指定多个配置文件,用逗号分隔。
20.#{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换。
#{}
是 MyBatis 中用于预编译 SQL 语句的占位符。当使用 #{}
时,MyBatis 会将其替换为 ?
占位符,然后使用预编译语句(PreparedStatement
)来执行 SQL。在设置参数时,会自动进行类型转换和防止 SQL 注入
<select id="getUserById" parameterType="int" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id}
</select>
在上述示例中,#{id}
会被替换为 ?
,假设传入的 id
值为 1,实际执行的 SQL 语句为 SELECT * FROM users WHERE id = ?
,然后通过 PreparedStatement
的 setInt
方法将 1 设置到对应的占位符位置。能有效防止 SQL 注入攻击,因为参数是通过预编译语句的参数设置方法传递的,不会直接拼接在 SQL 语句中。
${}
是 MyBatis 中的字符串替换,MyBatis 会直接将 ${}
中的内容替换为传入的参数值,不会进行预编译处理
<select id="getUserByColumn" parameterType="map" resultType="com.example.User">
SELECT * FROM users WHERE ${column} = #{value}
</select>
假设传入的参数是 {"column": "username", "value": "john"}
,那么实际执行的 SQL 语句为 SELECT * FROM users WHERE username = 'john'
。存在 SQL 注入风险,因为参数是直接拼接在 SQL 语句中的,如果传入的参数包含恶意 SQL 代码,可能会导致 SQL 注入攻击。
21.resultType和resultMap
resultType
用于简单的结果映射,当数据库表的字段名与 Java 对象的属性名完全一致时,可以直接使用 resultType
指定查询结果要映射的 Java 类型,MyBatis 会自动将查询结果的列名和对象的属性名进行匹配并赋值。适用于数据库表结构和 Java 对象属性简单且一一对应的情况,能够快速实现结果映射,代码简洁。
resultMap
用于复杂的结果映射,当数据库表的字段名与 Java 对象的属性名不一致,或者需要进行更复杂的映射(如嵌套查询、关联查询等)时,就需要使用 resultMap
来手动定义映射关系。
- 数据库表字段名和 Java 对象属性名不匹配。
- 进行关联查询,需要将多个表的查询结果映射到一个复杂的 Java 对象中。
- 需要处理嵌套对象的映射。
22.redis的数据类型,以及每种数据类型的使用场景
一共五种
(一)String 这个其实没啥好说的,最常规的set/get操作,value可以是String也可以
是数字。一般做一些复杂的计数功能的缓存。
(二)hash 这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。
(三)list 使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队,先进先出的原则。
(四)set 因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。 另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
(五)sorted set sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。
23.git如何合并分支?产生冲突如何解决
1. 快速合并(Fast-forward merge)
当要合并的分支是基于当前分支线性发展时,Git 会采用快速合并的方式。这种合并只是将当前分支的指针直接移动到要合并分支的最新提交上。
1.切换到要合并到的目标分支,通常是主分支(如 master
或 main
)。
git checkout main
2.执行合并操作,将源分支(如 feature-branch
)合并到目标分支。
git merge feature-branch
3.推送到远程仓库(如果需要)。
git push origin main
解决合并冲突的方法
当 Git 无法自动合并两个分支的修改时,就会产生合并冲突。以下是解决合并冲突的一般步骤:
1. 识别冲突
当执行合并操作后,如果出现类似以下的提示,说明发生了合并冲突:
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.
2. 查看冲突文件
使用 git status
命令查看哪些文件发生了冲突。
git status
3. 编辑冲突文件
打开冲突文件,会看到类似以下的冲突标记:
<<<<<<< HEAD
这是当前分支(如 main)的内容。
=======
这是要合并的分支(如 feature-branch)的内容。
>>>>>>> feature-branch
根据实际需求,手动编辑文件,保留需要的内容,删除冲突标记
这是合并后的内容。
4. 标记冲突已解决
编辑完冲突文件后,使用 git add
命令将修改后的文件标记为已解决。
5. 完成合并
使用 git commit
命令完成合并提交。
6. 推送到远程仓库
24.mysql存储引擎
mysql常用引擎包括:MYISAM、Innodb、Memory、MERGE
MYISAM:全表锁,拥有较高的执行速度,不支持事务,不支持外键,并发性能差,占用空间
相对较小,对事务完整性没有要求,以select、insert为主的应用基本上可以使用这引擎
Innodb:行级锁,提供了具有提交、回滚和崩溃回复能力的事务安全,支持自动增长列,支持
外键约束,并发能力强,占用空间是MYISAM的2.5倍,处理效率相对会差一些
Memory:全表锁,存储在内容中,速度快,但会占用和数据量成正比的内存空间且数据在
mysql重启时会丢失,默认使用HASH索引,检索效率非常高,但不适用于精确查找,主要用于
那些内容变化不频繁的代码表
MERGE:是一组MYISAM表的组合
25.MYISAM和Innodb的区别
1. InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提
交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
2. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
3. InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。
但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该
过大,因为主键太大,其他索引也都会很大。而MyISAM是非聚集索引,数据文件是分离的,
索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
4. InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用
一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
5. Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;
26.场景题
我有一张产品表,有字段产品名,然后我要把产品名相同的多条记录筛选出来,一条的不要,如何写sql?
使用子查询和 GROUP BY
结合 HAVING
子句来实现该需求。
SELECT *
FROM products
WHERE product_name IN (
SELECT product_name
FROM products
GROUP BY product_name
HAVING COUNT(*) > 1
);
27.maven命令
mvn compile:
编译项目的源代码。该命令会将 src/main/java
目录下的 Java 源文件编译成字节码文件,存储在 target/classes
目录中
mvn test:
运行项目的测试代码。Maven 会执行 src/test/java
目录下的测试类,并生成测试报告
mvn package:
将项目打包成可分发的格式,如 JAR、WAR 等。对于 Java 项目,默认会生成 JAR 文件;对于 Web 项目,会生成 WAR 文件。打包后的文件会存储在 target
目录中。
mvn install:
将打包好的项目安装到本地 Maven 仓库中。这样,其他项目就可以通过依赖配置引用该项目。
mvn deploy:
将打包好的项目部署到远程 Maven 仓库中,供团队成员或其他项目使用。在执行该命令前,需要在 pom.xml
中配置好远程仓库的信息。
28.maven中依赖的版本产生冲突如何解决?
1. 使用 mvn dependency:tree
命令分析冲突
在解决依赖版本冲突之前,需要先找出冲突的根源。可以使用 mvn dependency:tree
命令以树形结构展示项目的依赖关系,从而清晰地看到哪些依赖引入了冲突的版本。
2. 调整依赖顺序
Maven 采用 “最短路径优先” 和 “最先声明优先” 的原则来解决依赖冲突。“最短路径优先” 指的是离项目根节点最近的依赖版本会被优先使用;“最先声明优先” 指的是在 pom.xml
中先声明的依赖版本会被优先使用。
可以通过调整 pom.xml
中依赖的声明顺序,将需要的版本声明在前面,让 Maven 优先选择该版本。
3. 使用 <exclusions>
标签排除冲突依赖
如果某个依赖引入了不需要的版本,可以使用 <exclusions>
标签将其排除。
4.使用 <dependencyManagement>
统一管理依赖版本
<dependencyManagement>
元素用于统一管理项目中依赖的版本。在该元素中声明的依赖版本,不会直接引入依赖,而是为子模块或其他依赖提供一个版本参考。
5. 升级或降级依赖版本
如果冲突的依赖版本之间存在兼容性问题,可以考虑升级或降级某些依赖的版本,以解决冲突。在升级或降级之前,需要确保新版本与项目的其他部分兼容。