????本篇博客重在讲解SpringBoot、Spring、SpringMVC等相关面试题,将会实时更新,欢迎大家添加作者文末联系方式交流
????JAVA面试题专栏:JAVA崭新面试题——2024版_dream_ready的博客-****博客
????作者首页: dream_ready-****博客
????有任何问题都可以评论留言,作者将会实时回复
????未来将会更新数据结构面试题以及Spring和MySQL等等的面试题,将会涵盖JAVA后端所有技术框架,敬请期待!!!
注:本篇博客精简高效,问题对应的答案是直接可以口述给面试官
有的问题答案较长,可以仅记住前半部分。我写的时候也为这样的答案量身定做了一番,即便直接回答前半部分也不突兀,也能达到不错的水平
目录
1、什么是框架?
2、什么是Spring?
3、Spring有哪些模块
4、什么是SpringBoot?SpringBoot和Spring的关系
5、什么是SpringMVC?SpringMVC和三层架构的关系
6、Spring,SpringBoot和SpringMVC的关系以及区别
7、什么是maven?Spring和maven的区别?
8、什么是IOC?为什么要使用IOC?
9、什么是DI,DI和IOC有什么关系?
10、DI依赖注入的方式和区别?Bean的注入方式和区别?
11、@Autowired和@Resource有什么区别?
12、什么是AOP?它有哪些使用场景?
13、AOP是如何组成的?它有几种增强方法?
14、AOP底层是如何实现的?
15、JDK动态代理和CGLib有什么区别?
16、什么是Bean
17、Bean的生命周期?
18、BeanFactory和FactoryBean有什么区别?
19、Bean是线程安全的吗?实际工作中怎么保证其线程安全?
20、Spring 容器启动阶段会干什么?Spring启动阶段会干什么?
21、Spring Boot 中的常用注解有哪些?
22、如何实现自定义注解?实际工作中哪里会用到自定义注解?
23、什么是拦截器和过滤器以及二者的区别
什么是拦截器?
什么是过滤器?
拦截器和过滤器有什么区别?
24、当客户端发送请求到后端时的执行顺序
25、如何实现拦截器?
26、如何实现过滤器?
27、SpringBoot如何实现跨域?跨域问题的本质是什么?
28、Spring中使用了哪些设计模式?
29、SpringBoot如何实现缓存预热?
30、Spring如何解决循环依赖问题?什么是三级缓存?为什么一定要用三级缓存?
31、SpringBoot的启动流程是怎么样的?
32、Spring的自动装配(自动配置)
1、什么是框架?
什么是框架基本是不会被面试官提问到的,但很多人对什么是框架其实都处在一个模糊的概念上,所以我第一个书写的面试题就是 什么是框架
框架是在软件开发中用来提供一种基础架构和支持的软件库或工具集合
框架是一种预先设计好的软件架构,它定义了应用程序的基本结构,并提供了一套约定和接口,使得开发者可以在此基础上添加自己的业务逻辑。框架通常包含了一系列工具和库,帮助开发者更高效地开发应用程序。
以上都较为官方,其实像Spring就是别人写好的框架,咱使用Spring开发项目严格意义上就来说就是在使用别人开发好的软件架构进行二开(这句话勿死扣,知道我想表达的意思即可)
包括 若依、JeecgBoot等都算框架,还有前端的element广义上也算框架。(不要咬文嚼字,如果想要咬文嚼字的话就死背官方定义)
2、什么是Spring?
一句话概括:Spring 是一个轻量级、非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架。
非入侵式是软件开发中的一种设计方式,这种设计方式不会强迫开发者改变他们原有的编码习惯或添加额外的框架特定代码,以适应该系统或组件
Spring是一个轻量级Java开发框架,最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
Spring 为 Java 程序提供了全面的基础架构支持,包含了很多非常实用的功能,如 Spring JDBC、Spring AOP、Spring ORM、Spring Test 等,这些模块的出现,大大的缩短了应用程序的开发时间,同时提高了应用开发的效率。
Spring的优点毋庸置疑:
- 方便解耦,简化开发
- Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。
- AOP编程的支持
- Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
- 声明式事务的支持
- 只需要通过配置就可以完成对事务的管理,而无需手动编程。
- 方便程序的测试
- Spring对Junit4支持,可以通过注解方便的测试Spring程序。
- 方便集成各种优秀框架
- Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
- 降低JavaEE API的使用难度
- Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
以上这些优点基于自己往常写的代码说几个就行,重要的是下面这句话
Spring的两大核心概念: IOC 和 AOP
IOC 和 AOP 在本篇博客下文中有更详细的介绍,此处先简单解释一下
IoC 是“控制反转”的意思,它不是一个具体的技术,而是一个实现对象解耦的思想。
控制反转的意思是将要使用的对象生命周期的控制权进行反转,传统开发是当前类控制依赖对象的生命周期的,现在交给其他人(Spring),这就是 控制(权)反转。
原本对象的生命周期,创建销毁等是由我们程序员手动控制的,但是在使用Spring后,对应的生命周期,创建销毁等都由Spring控制了,这就是 控制反转,其实说 控制权反转 更好理解
AOP 就是 面向切面编程,简单来说就是统一功能处理
AOP 可以说是 OOP(面向对象编程)的补充和完善,OOP 引入封装、继承和多态性等概念来建立一种公共对象处理的能力,当我们需要处理公共行为的时候,OOP 就会显得无能为力,而 AOP 的出现正好解决了这个问题。比如统一的日志处理模块、授权验证模块等都可以使用 AOP 很轻松的处理。
注:也有人会说,Spring的核心概念还有DI(依赖注入),其实DI是IOC的实现手段,IOC是思想,DI是实现方法,下文中有详细说明
3、Spring有哪些模块
其实这个时候问的就是广义的Spring,即Spring家族有哪些模块
Spring框架是一个全面的企业级Java应用开发平台,它由多个模块组成,每个模块负责不同的功能领域。以下是Spring框架的主要模块及其简要描述:
- Spring Boot - 提供了快速构建独立Spring应用的方法,简化了配置文件,自动配置了Spring,同时也提供了一些生产就绪的功能。
- Spring Web - 支持Web应用的开发,并提供了对Servlet API的集成。
- Spring Security - 提供了强大的安全性和认证/授权服务。
- Spring Data - 简化了数据访问层的开发,支持各种数据存储技术。
- Spring Test - 提供了支持测试的模块,尤其是针对Spring组件的单元测试和集成测试的支持。
- Spring AOP - 实现了面向切面编程(AOP)。它允许你定义方法拦截器(Method Interceptors)和切入点(Pointcuts),使得你可以在不修改代码的情况下增加新的行为。
- Spring Cloud - 提供了构建分布式系统的工具,如配置管理、服务发现、断路器等。
- Spring AI - 简化机器学习模型的集成、训练和部署,使其更容易与现有的Spring应用程序集成。
其实真要说的话还有很多,但以上供我们回复面试官是足够了,下面的有点印象就行
-
Spring Core - 提供了基本的核心容器功能,如依赖注入(DI)和IoC(控制反转)容器。它是Spring框架的基础,其他模块建立在其之上。
-
Spring Context - 建立在Spring Core之上,提供了丰富的配置方式,如属性文件、国际化支持、事件传播机制以及应用层的事件驱动模型等。
-
Spring DAO - 数据访问对象(DAO)模块简化了JDBC的使用,并且提供了一种异常层次结构,使得你可以从DAO层抛出一致的异常。
-
Spring ORM - 集成了主流的ORM框架,如Hibernate、MyBatis和JPA等,使你可以用这些框架来处理数据持久化。
-
Spring MVC - 实现了一个模型-视图-控制器(Model-View-Controller, MVC)框架,它是Spring的Web应用框架,也是Spring框架的一部分。
-
Spring Web Services - 支持创建和使用Web服务。
-
Spring Integration - 提供了企业集成模式的支持,帮助构建消息驱动的应用程序。
每个模块都可以单独使用,也可以组合起来使用,以满足不同应用场景的需求。(但这些模块之间并不是完全的隔离关系,是互相有关联的)
4、什么是SpringBoot?SpringBoot和Spring的关系
Spring Boot 本质上是 Spring 框架的延伸和扩展,它的诞生是为了简化 Spring 框架初始搭建以及开发的过程,使用它可以不再依赖 Spring 应用程序中的 XML 配置,为更快、更高效的开发 Spring 提供更加有力的支持。
总结:Spring 简化了 Java 程序的开发,而Spring Boot 简化了 Spring 的开发
当然,SpringBoot不只是集成并简化了Spring,他还集成了tomcat等容器。
Spring 和 Spring Boot 的区别就像,自己做菜需要买各种原料(类似 Spring),而预制菜(半成品)只需要加热炒熟(类似于 Spring Boot)一样。
其实挺好理解的,java程序的特点之一就是易封装,这使得java的框架层出不穷,不停地封装,甚至是封装再封装。Spring就是对Java的许多基础功能进行了封装,后来发现即便如此,开发起来还是麻烦,于是又对Spring进行了封装,得到了SpringBoot(这段话是为了更好的理解,死扣的话不是绝对性地正确,但讲给面试官是问题不大的)
细说区别:
- 简化配置:Spring Boot引入自动配置功能,开发者无需手动编写繁复的XML或注解配置,许多常见功能(如数据库连接、Web服务器、模板引擎等)都已预设默认配置。
- 一键启动:Spring Boot整合了内嵌的Servlet容器(如Tomcat),使得应用可直接作为Java应用启动,无需部署到单独的服务器。
- starter项目:Spring Boot通过起步依赖(Starters)简化了Maven或Gradle构建配置,只需添加少量依赖就能引入一组相关的库。
- 微服务友好:Spring Boot天生支持微服务架构,配合Spring Cloud,可以快速搭建分布式系统的各个模块。
5、什么是SpringMVC?SpringMVC和三层架构的关系
SpringMVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分。简化开发,减少出错,方便组内开发人员之间的配合。
以上是它的较为官方的概念,下面我将用大白话来诠释什么是SpringMVC
- MVC是思想,就是把一个项目分成了三部分
- SpringMVC进行了实现,称为SpringMVC
- Model模型层用来处理业务逻辑,处理数据,内部放置的是项目的逻辑以及方法的实现相关代码
- Controller控制器层选择处理模型,选择视图,实现前后端交互,是View层和Model层交流的桥梁
- View层面向用户,用于界面显示,人机交互
用户的请求在View层接收后,发送到Controller层,Controller层交给对应的,能处理用户请求的Model层
- View层相当于用户,Controller相当于前台,Model相当于各个部门
- 用户带着要求来到前台(View),前台(Controller)听完用户要求后,将其交给对应的销售部、广告部等部门(Model)
下面这张图只是用户的请求直接发给了Controller控制器层,没有经过View层
比如浏览器url路径直接发送请求或者PostMan等工具发送请求时就更适用于下面这张图
下面这篇博客更加的详细: (非常推荐,阅读完可以理清关系)
什么是SpringMVC?简单好理解!什么是应用分层?SpringMVC与应用分层的关系? 什么是三层架构?SpringMVC与三层架构的关系?_分层解耦就是springmvc吗-****博客
6、Spring,SpringBoot和SpringMVC的关系以及区别
- Spring是一个轻量级Java开发框架,简化了Java开发
- SpringBoot 是 Spring 框架的延伸和扩展,简化了 Spring 的开发
- SpringMVC是一个实现了MVC设计模式的的轻量级web框架
下面这篇博客更加的详细:
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习_面试题 spring springboit springmvc的区别-****博客
7、什么是maven?Spring和maven的区别?
- Maven 是一个项目管理和构建工具,它提供了一种标准化的项目布局、依赖管理和构建生命周期,它通过 pom.xml 文件来管理项目的依赖项、构建过程和其他元数据。
- Spring 是一个应用框架,它提供了许多模块化的组件。这些组件可以通过 Maven 作为依赖项添加到项目中。
Spring 框架本身并没有直接“集成”Maven,但它们在实际开发中经常一起使用
通过 Maven 管理项目的依赖项和构建过程,而 Spring 则提供应用程序的业务逻辑和架构支持。Spring Boot 更进一步简化了这一过程,通过 spring-boot-maven-plugin 插件和 spring-boot-starter-parent POM 文件,使得开发者可以更加专注于业务逻辑的实现,而不是繁琐的配置和构建过程。
通过这种方式,Spring 和 Maven 的结合使用,使得 Java 开发更加高效和便捷。
8、什么是IOC?为什么要使用IOC?
IOC 和 AOP 是 Spring 中最核心的两个概念,IOC是AOP的基础
IOC 是 Inversion of Control 的缩写,翻译成中文是"控制反转”的意思,它不是一个具体的技术,而是一个实现对象解耦的思想。
IOC,控制反转,也叫依赖注入,其实我个人认为它叫 控制权反转 更好理解
控制反转的意思是将要使用的对象生命周期的控制权进行反转,传统开发是当前类控制依赖对象的生命周期的,现在交给其他人(Spring),这就是控制(权)反转。
原本对象的生命周期,创建销毁等是由我们程序员手动控制的,但是在使用Spring后,对应的生命周期,创建销毁等都由Spring控制了,这就是 控制反转。
其实很多人是对把对象交给Spring管理这句话是比较迷茫的,我这里用代码来解释一下:
正常情况下我们使用对象是需要实例化类来获得该类的实例对象的
不使用IOC容器:需要手动创建对象并显示地注入依赖项
User user = new User();
使用IOC容器:依赖注入由容器自动完成,代码更简洁,耦合度更低,易于测试和维护
@Autowired private User user;
可以看到,我们没有显示地创建对象,没有赋予 new User() 这个实例,但我们在代码中就可以使用user了,就是因为我们已经将对象交给Spring管理了,后续我们使用user使用的都是Spring交给我们的user对象
嘻嘻,到此处可能感觉把对象交给Spring管理好像没有方便许多,但其实交给Spring管理是为了更多地可能性,比如AOP统一功能处理
- 现在假设我想在所有方法执行前打印666,那我需要在所有方法体内第一行写上打印666的代码。但我将该方法或该类交给Spring管理后,我就可以写一个统一功能处理的代码,拿到所有方法的代理对象,然后在代理对象执行前打印666就行了。
- 但如果我没有将该方法或类交给Spring管理,那Spring拿不到对应的代理对象的话,就无法将这个统一功能处理应用到该方法上。
这也就是为什么说IOC是AOP(面向切面编程)的基础
IOC的优点:
- 解耦和松散耦合:IOC通过将组件之间的依赖关系从代码中分离出来,实现了松散耦合。这意味着组件不需要直接了解它们之间的详细实现,从而提高了代码的可维护性和可重用性。
- 代码简洁性:IOC使你的代码更加专注于业务逻辑,而不需要过多关注依赖的创建和管理。这使得代码更加清晰、简洁和易于理解。
- 生命周期管理:IOC容器可以管理组件的生命周期,确保它们在合适的时间进行创建、初始化和销毁。
- 可重用性:由于依赖关系由容器管理,可以更容易地将组件在不同的应用程序中重用。
- AOP 实现基础:IOC是实现 AOP(面向切面编程)的基础,允许你将横切关注点(如日志、安全性)与核心业务逻辑分离
9、什么是DI,DI和IOC有什么关系?
DI是“依赖注入”的意思。
IOC是思想,DI是具体的实现技术
依赖注入不是一种设计实现,而是一种具体的技术,它是在 IoC 容器运行期间,动态地将某个依赖对象注入到当前对象的技术就叫做 DI(依赖注入)
比如 A对象需要依赖 B对象,那么在 A运行时,动态的将依赖对象 B注入到当前类中,而非通过直接 new 的方式获取 B 对象的方式,就是依赖注入。
IOC 和 DI虽然定义不同,但它们所做的事情都是一样的,都是用来实现对象解耦的,而二者又有所不同:IOC 是一种设计思想,而 DI 是一种具体的实现技术。
举个例子吧,我想做一个木雕,以寄托对她的思念。想做一个木雕这是想法,也就是IOC,而我拿着一把刻刀,一段上好槐木做出来了这个木雕,这是具体实现,实现技术就是用刻刀和槐木声泪泣下地刻下她在我脑海中最后的身影,这就是DI
想实现年薪百万,这是IOC,学习JAVA炒粉通过炒粉年入百万,这是DI
IOC除了DI外,还有其他实现方式么?
- 有!
IOC除了DI依赖注入外,还可以通过依赖查找(Dependency search)来实现
在 Spring 框架中,依赖査找通过 ApplicationContext 接口的 getBean()方法来实现,如下代码所示:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 1. 先得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2. 得到 Bean (依赖查找 -> IoC 的一种实现)
UserService userService = context.getBean("userService", UserService.class);
// 3. 使用 Bean 对象
userService.sayHi();
}
}
10、DI依赖注入的方式和区别?Bean的注入方式和区别?
依赖注入是一种编程模式,而Bean的注入是这种模式在一个具体框架中的应用。
所以这里他俩的注入方式和区别都是一样的,只不过有的面试官喜欢提问DI依赖注入的方式和区别,有的喜欢提问Bean的注入方式和区别
Bean对象有以下3种注入方式:
- 属性注入
- Setter注入
- 构造方法注入
①、属性注入
属性注入是我们最熟悉,也是日常开发中使用最多的一种注入方式,说白了就是通过注解的方式注入,常用的注解有 @Autowired 和 @Resource
它的实现代码如下:
@RestController
public class UserController { // 类名应该遵循大驼峰命名规则
@Autowired
private UserService userService; // 变量名也应该遵循小驼峰命名规则
@RequestMapping("/add")
public UserInfo add(String username, String password) {
return userService.add(username, password);
}
}
优点分析
- 属性注入最大的优点就是实现简单、使用简单,只需要给变量上添加一个注解(@Autowired),就可以在不 new对象的情况下,直接获得注入的对象了(这就是 DI的功能和魅力所在),所以它的优点就是使用简单。
缺点分析
- 功能性问题:无法注入一个不可变的对象(final 修饰的对象)
原因也很简单:在 Java 中 final 对象(不可变)要么直接赋值,要么在构造方法中赋值,所以当使用属性注入final 对象时,它不符合 Java 中 final 的使用规范,所以就不能注入成功了。
- 通用性问题:只能适应于 IOC 容器,Idea 也会提示你,不建议使用
Spring的IOC容器负责创建和管理应用程序中的bean,并在适当的时候将它们注入到其他bean中,这就导致脱离了Spring后这个注解就会出现错误
②、Setter注入
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@GetMapping("/add")
public UserInfo add(String username, String password) {
return userService.add(username, password);
}
}
从上面代码可以看出,Setter 注入比属性注入要麻烦很多。
优点分析
- Setter注入符合单一职责的设计原则,因为每一个 Setter 只针对一个对象
缺点分析:
- 不能注入不可变对象(final 修饰的对象)
- 注入的对象可调用多次,也就是注入对象会被修改
拿上面的代码解释这一点,如果多次调用 setUserService 方法,userService 字段就会被多次更改。这样的话,userService字段就会被新的OtherUserService实例覆盖,这可能导致对象状态的不确定性和多线程环境下的问题
③ 构造方法注入
构造方法注入是 Spring 官方从 4.x 之后推荐的注入方式,它的实现代码如下:
它推荐归它推荐,咱该用啥还是用啥
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/add")
public UserInfo add(String username, String password) {
return userService.add(username, password);
}
}
注:如果当前的类中只有一个构造方法,那么 @Autowired 也可以省略
优点分析:
构造方法注入相比于前两种注入方法,它可以注入不可变对象,并且它只会执行一次,也不存在像 Setter 注入那样,被注入的对象随时被修改的情况,它的优点有以下4个:
- 可注入不可变对象
- 注入对象不会被修改
- 注入对象会被完全初始化
- 通用性更好
缺点分析:
- 唯一缺点,没有属性注入写法简单(看起来没啥,但很致命哈哈bukebian
优点原因分析
- 注入对象不会被修改:构造方法在对象创建时只会执行一次,因此它不存在注入对象被随时(调用)修改的情况。
- 完全初始化:因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化,这也是构造方法注入的优点之一。
- 通用性更好:构造方法和属性注入不同,构造方法注入可适用于任何环境,无论是 IOC 框架还是非 IOC 框架,构造方法注入的代码都是通用的,所以它的通用性更好
11、@Autowired和@Resource有什么区别?
@Autowired 和 @Resource 都是用来实现依赖注入的注解(在 Spring/Spring Boot 项目中)
相同点
对于下面的代码来说,如果是Spring容器的话,两个注解的功能基本是等价的,他们都可以将bean注入到对应的field(字段)中
@Autowired
private Bean beanA;
@Resource
private Bean beanB;
不同点
- 来源不同:@Autowired 来自 Spring 框架,而 @Resource 来自于 Java JSR-250(Java规范提案)
- 依赖查找的顺序不同:@Autowired 先根据类型再根据名称查询,而 @Resource 先根据名称再根据类型查询
- 支持的参数不同:@Autowired 只支持设置1个required 参数,而 @Resource 支持设置多个参数(name、type 等)
- 依赖注入的用法支持不同:@Autowired 既支持构造方法注入,又支持属性注入和 Setter 注入,而@Resource 只支持属性注入和 Setter 注入
这两个哪个好?
- 其实没有根本性的好坏之分,取决于你们公司的代码规范吧
12、什么是AOP?它有哪些使用场景?
AOP (也叫面向切面编程) ,我更喜欢叫它 统一功能处理
统一功能处理嘛,这个从字面意思理解就行,进行一些统一功能,全局功能的操作,比如我要在所有方法执行前打印666,这个就属于 统一功能处理 的范畴,可以利用SpringAOP来完成这个操作
AOP 可以说是 OOP(面向对象编程) 的补充和完善,OOP 引入封装、继承和多态性等概念来建立一种公共对象处理的能力,当我们需要处理公共行为的时候,OOP 就会显得无能为力,而 AOP 的出现正好解决了这个问题。比如统一的日志处理模块、授权验证模块等都可以使用 AOP 很轻松的处理。
AOP 优点主要有以下几个:
- 集中处理某一类问题,方便维护
- 逻辑更加清晰
- 降低模块间的耦合度
AOP 常见使用场景有以下几个:
- 用户登录和鉴权,
- 统一日志记录
- 统一方法执行时间统计
- 统一的返回格式设置
- 统一的异常处理
- 声明式事务的实现
13、AOP是如何组成的?它有几种增强方法?
AOP 是由切面(Aspect)、切点(Pointcut)、通知(Advice)和连接点(Join Point)组成的。
①切面(Aspect)
切面(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。
简单来说,切面就是当前 AOP 功能的类型,比如当前 AOP 是用户登录和鉴权的功能,那么它就是一个切面。
② 切点(Pointcut)
切点 Pointcut:它的作用就是提供一组规则,用来匹配连接点的。
简单来说,切点就是设置拦截规则的,满足规则的方法将会被拦截。
③ 通知(Advice)
切面也是有目标的,这个目标即它必须完成的工作。在 AOP 术语中,切面的工作被称之为通知。
简单来说,当控制器(方法)被拦截之后,如果它符合我们定义的切点条件,那么就会触发我们事先定义好的一些额外行为,这些额外行为就是所谓的“通知”。
④ 连接点(Join Point)
连接点是指在应用程序执行过程中可以被拦截的特定点。换句话说,连接点是在程序执行过程中的某个特定位置,比如方法调用、方法执行、异常抛出等。
连接点是 AOP 中的基本单位,它代表了一个可以被切面拦截的位置,允许在这些位置插入额外的逻辑。切面可以通过连接点来捕获并应用它们的横切关注点(如日志、事务、安全性等)。
在 Spring AOP 中,一些连接点包括:
- 方法调用:当一个方法被调用时,连接点发生。
- 方法执行:当一个方法的实际代码开始执行时,连接点发生。
- 异常抛出:当方法抛出异常时,连接点发生。
- 字段访问:当访问一个类的字段时,连接点发生。
连接点通过一个表示方法或其他事件的点来标识。例如,表达式 execution(*com.example.Service.doSomething(..)) 表示在 com.example.Service 类的 doSomething 方法被调用或执行时的连接点。
连接点是实现 AOP 的基础,允许切面在特定位置插入增强逻辑,从而实现横切关注点的功能。切面可以选择在哪些连接点上应用(通过定义切点实现),以便将适当的逻辑织入到应用程序的执行流程中。
AOP有几种增强方法?
AOP 的增强方法也就是 Advice 通知,Spring AOP 总共有以下 5 种通知类型:
- 前置通知:使用 @Before 实现,通知方法会在目标方法调用之前执行
- 后置通知:使用 @After 实现,通知方法会在目标方法返回或者抛出异常后调用
- 返回通知:使用 @AfterReturning 实现,通知方法会在目标方法返回后调用
- 抛出异常通知:使用 @AfterThrowing 实现,通知方法会在目标方法抛出异常后调用
- 环绕通知:使用 @Arund 实现,通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
它的使用示例如下:
@Component
@Aspect
public class LoggingAspect {
// 前置通知
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("前置通知执行...");
System.out.println("方法名称: " + joinPoint.getSignature().getName());
}
// 后置通知
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("后置通知执行...");
}
// 返回后通知
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
System.out.println("返回后通知执行...");
System.out.println("返回结果: " + result);
}
// 抛出异常通知
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
System.out.println("抛出异常通知执行...");
System.out.println("异常信息: " + ex.getMessage());
}
// 环绕通知
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知开始执行...");
Object result = pjp.proceed(); // 继续执行目标方法
System.out.println("环绕通知结束执行...");
return result;
}
}
14、AOP底层是如何实现的?
Spring 中的 AOP 底层是通过代理来实现的,而 Spring 中使用了以下两种代理:
- JDK 动态代理:对于实现了接口的目标类,Spring 使用 Java 提供的 java.lang.reflect.Proxy 类来创建代理对象。它要求目标类实现至少一个接口,并通过接口生成代理对象,代理对象实现了相同的接口,并且调用代理方法时会触发代理处理器的逻辑。JDK动态代理底层又是通过反射实现的。
- CGLib 代理:对于没有实现接口的目标类,Spring 使用 CGLIB 来创建代理对象。CGLIB 会生成目标类的子类作为代理,重写方法并在方法前后插入切面逻辑。CGLib 底层是通过生成目标类的字节码来实现的。
Spring AOP 默认使用的是 JDK 动态代理(这点在官方文档种有具体的说明)
然而,Spring Boot 2.0 之后却默认并只使用了 CGLib(源码中可看到,无论被代理类是否实现接口,都会使用CGLib来实现)
想要解决这个问题,可以在配置文件中设置以下配置:
# 启用AOP自动代理
spring.aop.auto=true
# 代理方式: 设置为 true 表示强制使用 CGLIB 代理
spring.aop.proxy-target-class=true
15、JDK动态代理和CGLib有什么区别?
JDK 动态代理(JDK Proxy)和 CGLib 都是 Spring 中用于实现 AOP 的代理技术,但它们存在以下区别:
- 来源不同:JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现。Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;而 CGLib 是第三方提供的工具,基于 ASM (一个字节码操作框架) 实现的。
- 使用场景不同:JDK Proxy 只能代理实现了接口的类,而 CGLib 无需实现接口,它是通过实现目标类的子类来完成调用的,所以要求被代理类不能被 final 修饰。
- 性能不同:JDKProxy 在JDK7之前性能远不如 CGLib,但JDK7之后 (JDK7和JDK8都有优化) JDKProxy 性能就略高于 CGLib 了。
小结:JDK Proxy 是 Java 自带的,在 JDK 高版本性能比较高的动态代理工具,但它要求被代理类必须要实现接口,它的性能在 JDK7 之后也略高于 CGLib;而 CGLib 是基于字节码技术实现的第三方动态代理,它是通过生成代理对象的子类来实现代理的,所以要求被代理类不能被 final 修饰。
16、什么是Bean
初级开发的话,你就需要记住Bean就是对象,就是你理解的Java中的对象,是所有对象的统称
然后我们讨论Bean,实际就是在讨论对象
17、Bean的生命周期?
Bean 的大致生命周期为:实例化 -> 属性赋值 -> 初始化 -> 使用 -> 销毁
Bean 生命周期是指其在 Spring 容器中从创建到销毁的过程
在 Spring 源码中,AbstractAutowireCapableBeanFactory 是用于实现 Bean 的自动装配和创建的关键类。它里面的 doCreateBean 方法包含了 Bean 生命周期的实现逻辑(AbstractAutowireCapableBeanFactory 是 BeanFactory的子类,这里主要记住BeanFactory)
如下源码所示(以下源码基于 Spring 6):
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper) this.factoryBeanInstanceCache.remove(beanName);
}
// a. 实例化 Bean
if (instanceWrapper == null) {
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
// 忽略其他方法...
Object exposedObject = instanceWrapper.getWrappedInstance();
try {
// b. 设置属性
this.populateBean(beanName, mbd, instanceWrapper);
// c. 执行初始化方法
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException bce) {
if (beanName.equals(bce.getBeanName())) {
throw bce;
}
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, var18.getMessage(), var18);
}
// 忽略其他方法...
// 定义 Bean 的销毁回调方法
try {
this.registerDisposableBeanIfNecessary(beanName, exposedObject, mbd);
} catch (BeanDefinitionValidationException var16) {
// 处理 BeanDefinitionValidationException
throw new BeanCreationException(mbd.getResourceDescription(), beanName, var16.getMessage(), var16);
}
return exposedObject;
}
所以从以上源码也可以看出 Bean 的大致生命周期为:
实例化 -> 属性赋值 -> 初始化 -> 使用 -> 销毁
Bean 生命周期主要会经历以下几个阶段:
- 实例化(Instantiation):从无到有,为 Bean 分配内存空间,调用 Bean 的构造方法
- 属性赋值(Populate Properties):在创建实例后,Spring 将会通过 依赖注入(DI) 的方式将 Bean 的属性赋值。
-
初始化(Initialization):初始化阶段主要有以下 3个步骤:
- 初始化前置处理:在 Bean 的初始化之前,会调用所有实现了 BeanPostProcessor 接口的类的postProcessBeforeInitialization 方法。
- 初始化方法调用:如果在 Bean 上定义了初始化方法,通过 @PostConstruct 注解或实现了 InitializingBean 接口。
- 初始化后置处理:在 Bean 的初始化之后,会调用所有实现了 BeanPostProcessor 接口的类的postProcessAfterInitialization 方法。
- 使用(Using):在初始化之后,Bean 可以被使用。在这个阶段,Bean 执行它的业务逻辑。
- 销毁(Destruction):销毁 Bean 实例。
18、BeanFactory和FactoryBean有什么区别?
BeanFactory 和 FactoryBean 完全不同的两个接口,BeanFactory 是用来管理 Bean 对象的,而 FactoryBean本质上一个 Bean,也归 BeanFactory 管理,但使用 FactoryBean 可以来创建普通的 Bean 对象和 AOP 代理对象,它们具体区别如下:
① BeanFactory
Beanfactory 是 Spring 框架的核心接口之一,它是一个工厂模式的实现,负责管理 bean 的生命周期、创建和销毁等操作。BeanFactory 提供了各种方法来获取 bean,包括按名称获取、按类型获取等。
其中,ApplicationContext 就是 BeanFactory 的子类,咱们通常会使用 ApplicationContext 来获取某个 Bean:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService service = context.getBean(MyService.class);
service.doWork();
}
刚才Bean的生命周期种,我们提到了在 Spring 源码中,AbstractAutowireCapableBeanFactory 是用于实现 Bean 的自动装配和创建的关键类,这里又提到了ApplicationContext,这里我用继承关系图来演示他们的关系:
AbstractAutowireCapableBeanFactory 和 ApplicationContext 都是 BeanFactory 的子类,BeanFactory是祖宗哈哈
BeanFactory
|
+-- ListableBeanFactory
| |
| +-- ApplicationContext
|
+-- AutowireCapableBeanFactory
|
+-- AbstractAutowireCapableBeanFactory
因为Bean的创建和销毁等操作一般都是交给Spring的,所以其实我们使用BeanFactory 的主要场景是从 IoC 容器中获取 Bean 对象
② FactoryBean
它是一个 Bean,但又不仅仅是一个普通的 Bean,它是一个能生成 Bean 对象的工厂。
它的使用示例如下:
@Component
public class MyBean implements FactoryBean<MyBean> {
private String message;
public MyBean() {
this.message = "通过构造方法初始化实例";
}
@Override
public MyBean getObject() throws Exception {
// 方法增强
return new MyBean("通过 FactoryBean.getObject() 创建实例");
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
@Override
public boolean isSingleton() {
return true; // 或者 false,取决于是否需要单例
}
public String getMessage() {
return message;
}
}
FactoryBean 在 Spring 中最为典型的一个应用就是用来创建 AOP 的代理对象
FactoryBean 源码如下:
import org.springframework.beans.factory.FactoryBean;
public interface FactoryBean<T> {
/**
* 从工厂中获取 Bean。
*
* @return 返回由 FactoryBean 创建的对象实例。
* @throws Exception 如果创建对象时出现异常。
*/
@Nullable
T getObject() throws Exception;
/**
* 获取 Bean 工厂创建的对象的类型。
*
* @return 返回由 FactoryBean 创建的对象类型。
*/
@Nullable
Class<?> getObjectType();
/**
* 判断 Bean 工厂创建的对象是否是单例模式。
*
* @return 如果是单例模式返回 true,否则返回 false。
*/
default boolean isSingleton() {
return true;
}
}
既然已经有了 Beanfactory,功能也足够强大,那为什么要使用 FactoryBean呢?
- *度更高:FactoryBean 本身也是一个 Bean,这意味着它可以像其他 Bean 一样被配置和管理
- 复杂对象的创建:对于那些需要复杂初始化逻辑的对象,FactoryBean 提供了一种方便的方式来创建和初始化这些对象。(看代码)
- 延迟初始化:FactoryBean 可以在需要时才创建对象,这有助于节省资源和提高性能。
- 增强对象创建过程:通过 Factoryeean 可以在创建对象的过程中添加额外的逻辑,如装饰器模式的应用(不懂装饰器模式的话建议这一条不要说,防止被面试官追问)
19、Bean是线程安全的吗?实际工作中怎么保证其线程安全?
默认情况下,Bean 是非线程安全的。因为默认情况下 Bean 的作用域是单例模式,那么此时,所有的请求都会共享同一个 Bean 实例,这意味着如果这个 Bean 实例在多线程下,被同时修改(成员变量)时,就可能出现线程安全问题。
Bean 的作用域指的是确定在应用程序中创建和管理 Bean 实例的范围。也就是在 Spring 中,可以通过指定不同的作用域来控制 Bean 实例的生命周期和可见性。例如,单例模式就是所有线程可见并共享的,而原型模式则是每次请求都创建一个新的原型对象。
注:这里简单补充一下什么是单例模式?后续我出一篇专门博客介绍这些设计模式
- 单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点
单例Bean一定是非线程安全的吗?
并不是,单例 Bean 分为以下两种类型:
- 无状态 Bean (线程安全):Bean 没有成员变量,或多线程只会对 Bean 成员变量进行査询操作,不会修改操作。
- 有状态 Bean (非线程安全):Bean 有成员变量,并且并发线程会对成员变量进行修改操作。
所以说:有状态的单例 Bean 是非线程安全的,而无状态的 Bean 是线程安全的。
但在程序中,只要有一种情况会出现线程安全问题,那么它的整体就是非线程安全的,所以总的来说,单例Bean 还是非线程安全的。
① 无状态的Bean
无状态的 Bean 指的是不存在成员变量,或只有查询操作,没有修改操作,它的实现示例代码如下:
注:其实大部分情况下,咱的service实现类的代码都是无状态的Bean,没有成员变量
有的同学说,多线程下插入操作也不是线程安全的呀,确实,但这个线程安全和Bean的线程安全无关。
@Service
public class YwAssessPersonServiceImpl implements IYwAssessPersonService {
@Override
public List<YwAssessPerson> selectYwAssessPersonList(YwAssessPerson ywAssessPerson) {
// 执行查询操作,不保存任何状态
}
@Override
public YwAssessPerson selectYwAssessPersonBySid(Long sid) {
// 根据ID查询,不保存任何状态
}
@Override
public AjaxResult insertYwAssessPerson(YwAssessPerson ywAssessPerson) {
// 插入操作,不保存任何状态
}
@Override
public AjaxResult updateYwAssessPerson(YwAssessPerson ywAssessPerson) {
// 更新操作,不保存任何状态
}
@Override
public int deleteYwAssessPersonBySids(Long[] sids) {
// 删除操作,不保存任何状态
}
// 其他方法...
}
② 有状态的Bean
有成员变量,并且存在对成员变量的修改操作,如下代码所示:
@Service
public class YwAssessPersonServiceImpl implements