注意:文章若有错误的地方,欢迎评论区里面指正 🍭
系列文章目录
- 【面试题】MySQL常见面试题总结
- 【面试题】面试题分享之JVM篇
- 【面试题】面试题分享之Java并发篇
1、Spring是什么?
-
Spring是一种轻量级框架,旨在提高开发人员的开发效率以及系统的可维护性。
我们一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。
Spring官网列出的Spring的6个特征:
- 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。
- 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
- 数据访问:事务,DAO支持,JDBC,ORM,编组XML。
- Web支持:Spring MVC和Spring WebFlux Web框架。
- 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
- 语言:Kotlin,Groovy,动态语言。
2、列举一些重要的Spring模块?
-
Spring Core:Spring框架的核心,提供了控制反转(IoC)容器和依赖注入(DI)功能。
-
Spring Beans:定义了Spring的beans的配置方式,包括基于注解和XML的配置。
-
Spring Context:提供了一种更加丰富的框架应用上下文,包括国际化、事件传播、资源访问等。
-
Spring AOP:面向切面编程模块,允许开发者定义横切关注点,如日志记录、事务管理等。
-
Spring DAO:数据访问对象模块,提供了对JDBC的抽象,简化了数据访问层的编码。
-
Spring JDBC:提供了对Java数据库连接(JDBC)的抽象和简化,使得数据库操作更加方便。
-
Spring ORM:对象关系映射模块,支持与Hibernate、JPA、MyBatis等ORM工具的集成。
-
Spring TX:事务管理模块,提供了声明式和编程式事务管理的支持。
3、Spring的设计核心是什么?
IOC和AOP
👩💻面试官追问:谈谈你对IOC和AOP的理解?
答案:
4、Spring中的bean的作用域有哪些?
-
singleton(单例模式):唯一bean实例,Spring中的bean默认都是单例的。
-
prototype(原型模式):每次请求都会创建一个新的bean实例。
-
request(请求作用域):每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
-
session(会话作用域):每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
-
global-session(全局作用域):全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。
5、Spring中Bean的注入方式?
- 构造函数注入(Constructor Injection)
- 通过构造函数将依赖项传递给Bean,确保了当Bean实例化时,它的依赖项就已经被设置好。
- 这种方式可以保证依赖项为非空,因为构造函数必须在对象创建时被调用。
- setter方法注入(Setter Injection)
- 通过调用Bean的setter方法来注入依赖项。
- 这种方式提供了更大的灵活性,可以在Bean创建之后更改依赖关系。
- 字段注入(Field Injection)
- Spring可以直接注入Bean的字段,这种方式不需要编写构造函数或setter方法。
- 字段注入较少使用,因为它违反了“封装”原则,使得字段可以被直接访问和修改。
- 注解注入
- Spring支持使用注解来简化配置,常见的注解包括
@Autowired
(用于构造函数、setter方法或字段)和@Inject
(JSR-330标准)。 - 注解注入可以与构造函数注入或setter方法注入结合使用。
- Spring支持使用注解来简化配置,常见的注解包括
除了以上方法,还有:使用@ComponentScan、@Bean、@Value、@Qualifier注解等等。用的最多的就是构造器注入、@autowired、@Bean、@Value。
6、
BeanFactory 和 ApplicationContext有什么区别?
是spring的核心接口,都可以作为容器,ApplicationContext是BeanFactory的子接口。
BeanFactory: 是spring最底层的接口,包含各种Bean的定义和Bean的管理。
ApplicationContext: 作为BeanFactory的派生,除了有BeanFactory的功能以外,还提供了更多的功能。
区别:
- BeanFactroy采用的是延迟加载形式来注入Bean的,使用到bean才会加载。ApplicationContext一次性加载所有bean。
- BeanFactory需要手动注册,而ApplicationContext则是自动注册。
- BeanFactory不支持国际化,ApplicationContext支持国际化(实现MessageSource接口)。
- BeanFactory不支持AOP,ApplicationContext支持AOP,可以与Spring的AOP框架集成,提供声明式事务管理。
7、Spring中的单例bean的线程安全问题了解吗?
单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
有两种常见的解决方案:
- 在bean对象中尽量避免定义可变的成员变量(不太现实)。
- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。
示例:
public class UserService {private String username;public String setUserName(String username){this.username = "User :" + username;//模拟业务代码try {Thread.sleep(500);}catch (InterruptedException e){e.printStackTrace();}return this.username;}
}
@Configuration
public class MainConfig {@Beanpublic UserService userService(){return new UserService();}
}
public class Client {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();applicationContext.register(MainConfig.class);applicationContext.refresh();UserService userService = applicationContext.getBean(UserService.class);//线程一new Thread(()->System.out.println(userService.setUserName("张三"))).start();//线程二new Thread(()-> System.out.println(userService.setUserName("李四"))).start();}
}User :李四
User :李四
我们可以看到,打印出了一样的结果,说明不同线程对同一个Bean修改时会出现线程安全,我们修改userService代码将他变为线程安全
public class UserService {
// private String username;//将username放入ThreadLocal里面ThreadLocal<String> username =ThreadLocal.withInitial(() -> "default");public String setUserName(String username){this.username.set("User :" + username);//模拟业务代码try {Thread.sleep(500);}catch (InterruptedException e){e.printStackTrace();}return this.username.get();}
}User :李四
User :张三
8、Spring如何处理线程的并发问题?
除了上一题中给出的两种解决方法外,还有其他的解决方法,比如:
- 把成员变量写在方法内。
- 修改bean的作用域,singleton改为prototype。(@Scope(“prototype”))
- 使用synchronized修饰。
9、Spring中的bean生命周期?
-
实例化Bean:通过构造器或静态工厂方法创建Bean实例。
-
设置Bean属性:为Bean的属性设置值,这些属性可能是通过XML配置、注解或Java配置类指定的。
-
处理BeanNameAware和BeanFactoryAware:
- 如果Bean实现了
BeanNameAware
接口,Spring将调用其setBeanName()
方法,传入Bean的ID。 - 如果Bean实现了
BeanFactoryAware
接口,Spring将调用其setBeanFactory()
方法,传入BeanFactory
容器的实例。
- 如果Bean实现了
-
BeanPostProcessor前置处理:在Bean的初始化方法调用之前,Spring允许通过
BeanPostProcessor
接口的postProcessBeforeInitialization()
方法对Bean进行额外处理。 -
初始化Bean:
- 如果Bean实现了
InitializingBean
接口,Spring将调用其afterPropertiesSet()
方法。 - 如果Bean在XML中配置了
init-method
属性,Spring将调用指定的初始化方法。
- 如果Bean实现了
-
BeanPostProcessor后置处理:在Bean初始化之后,Spring调用
BeanPostProcessor
接口的postProcessAfterInitialization()
方法,允许对Bean进行进一步的处理。 -
使用Bean:此时Bean已经准备好被应用程序使用了,Spring容器将根据配置将Bean注入到其他需要的地方。
-
处理DisposableBean和destroy-method:
- 当容器关闭时,如果Bean实现了
DisposableBean
接口,Spring将调用其destroy()
方法。 - 如果Bean在XML中配置了
destroy-method
属性,Spring将调用指定的销毁方法。
- 当容器关闭时,如果Bean实现了
-
销毁Bean:容器关闭时,Bean的实例将被销毁,释放资源。
10、Spring Bean的扩展点?
Spring提供很多的扩展点,这里就列举几个比较常用的。
-
BeanFactoryPostProcessor:在BeanFactory初始化之后,容器中的所有Bean定义被加载,但实例化之前,可以修改BeanFactory中的Bean定义。
-
BeanPostProcessor:在Bean的初始化过程中,可以在Bean属性设置好之后和初始化方法调用之前或之后,对Bean进行额外处理。
-
BeanNameAware 和 BeanFactoryAware:这些是Aware接口,允许Bean获取自己的名称和BeanFactory的引用。
-
InitializingBean 和 DisposableBean:这些是特殊的接口,可以在Bean初始化和销毁时提供自定义的逻辑。
-
@PostConstruct 和 @PreDestroy:这两个注解分别用于标记在Bean初始化之后和销毁之前执行的方法。
-
自定义初始化和销毁方法:可以在Bean定义中指定初始化方法(
init-method
)和销毁方法(destroy-method
)。 -
ApplicationContextAware 和 EnvironmentAware:允许Bean获取Spring的
ApplicationContext
和外部配置的Environment
,从而访问配置信息或上下文功能。
11、介绍下 Spring IOC 容器加载流程?
我们大致可以分为两个阶段:
- IOC容器初始化阶段:根据程序里面定义XML、注解等Bean的声明方式,通过解析和加载后生成的BeanDefinition,然后把BeanDefinition存入到一个Map集合里面,完成IOC的初始化阶段。
- 完成Bean的初始化和DI:通过反射对一个没有设置Lazy-init属性的单例Bean进行初始化,然后进行依赖注入,将属性注入到Bean对象中。
12、说说自己对于Spring MVC的了解?
简单流程如下所示:
具体工作原理:
流程说明:
- 客户端(浏览器)发送请求,直接请求到DispatcherServlet。
- DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。
- 解析到对应的Handler(也就是我们平常说的Controller控制器)。
- HandlerAdapter会根据Handler来调用真正的处理器来处理请求和执行相对应的业务逻辑。
- 处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是逻辑上的View。
- ViewResolver会根据逻辑View去查找实际的View。
- DispatcherServlet把返回的Model传给View(视图渲染)。
- 把View返回给请求者(浏览器)。
13、Spring框架中用到了哪些设计模式?
- 工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。
- 代理设计模式:Spring AOP功能的实现。
- 单例设计模式:Spring中的bean默认都是单例的。
- 模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
14、@Component和@Bean的区别是什么?
- 作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。
- @Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。
- @Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。
15、Spring事务管理的方式有几种?
1.编程式事务:在代码中硬编码(不推荐使用)如:beginTransaction()、commit()、rollback()等。
2.声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。
16、Spring事务中的隔离级别有哪几种?
在TransactionDefinition接口中定义了五个表示隔离级别的常量:
- ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。
- ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
17、Spring事务中有哪几种事务传播行为?
在TransactionDefinition接口中定义了7个表示事务传播行为的常量。
支持当前事务的情况:
- PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。
- 不支持当前事务的情况:
- PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
- 其他情况:
- PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED