1. Spring的两大特性:控制反转和依赖注入请解释一下?
控制反转,它是一种思想,不是具体的技术,它说的是将对象的控制权交给spring容器去控制对象的生成和生命周期。
依赖注入:对象和对象之间的依赖关系通过注解的方式实现。如Spring中常用的注解,@Autowired。这块是相对其它的注入方式,如构造器注入或者是setter方法注入。
aop 也是一个非常好用的特性,可以实现一些复用性强的功能,如日志打印,权限校验,事务控制,异常统一处理
2. 解释一下Spring的Aop特性?
切面技术,用于解决项目中散乱而又冗余的代码,可以使用在日志打印,权限校验,流量监控等。
参考博文:Spring AOP示例与实现原理总结——传统spring aop、基于切面注入、基于@Aspect注解的实现 - 浪荡绅士 - 博客园
1. 使用Aspect切面包实现
2.使用spring的aop框架实现,创建一个类继承methodInterceptor实现invoke方法。创建ProxyFactoryBean注入目标对象和拦截器。
实现原理:
Cglib动态代理:被代理的类可实现也可不实现接口。实现方式:Enhance(需要设置目标类和切面环绕逻辑)+环绕逻辑在MethodInterceptor接口中的interceptor中实现
java动态代理:被代理的类一定要实现一个接口,使用过java.lang.Proxy类实现,调用newProxyInstance方法,传入类加载器,接口类,实现调用方法invoke。
Java动态代理确实是通过组合的方式来实现的,代理对象持有一个被代理对象的引用,并通过实现被代理对象的接口,然后在接口方法调用时,实际上是调用的InvocationHandler的invoke方法,这个方法里面可以添加自己的逻辑,然后再调用被代理对象的方法。
而CGLIB则是通过创建一个被代理对象的子类来实现代理,这个子类会覆盖父类的所有非final的方法。在覆盖的方法中,可以在调用父类方法的前后加入自己的逻辑。这种方式更像是通过继承和覆盖的方式来实现代理。
使用场景:在Spring框架中,如果目标对象实现了接口,默认情况下会采用Java动态代理进行代理;如果目标对象没有实现接口,则会选择CGLIB进行代理。
使用时需要注意的问题:
1. 切面是否生效,需要同步代理调用,对象内部方法调用会失效
2. 切面的范围是否准确
3. 切面的逻辑执行的顺序:before逻辑执行越靠前,after逻辑执行就越靠后
4. 不要影响主流程:影响的代码很广,最好是异步执行。
3. BeanFactory 和 ApplicationContext 有什么区别?
都是Bean对象获取可以使用的方式,注解的底层也是使用AppliactionContext接口的实现类来生成或获取bean对象的。@Compoent底层就是使用Application的实现类
AnnotationConfigApplicationContext来完成注入容器的过程的。
BeanFactory,底层接口,提供实例化对象和取对象的功能。
ApplicationContext, 更高级的容器,提供更多的功能。如访问文件资源,aop,国际化,消息发送和响应机制。
3. 请解释 Spring Bean 的生命周期?
实例化:调用构造方法的过程
属性赋值:调用setter方法的过程。bean依赖注入的方法,setter方法上需要加@autowired注解。
初始化:扩展的方法,InitializingBean接口实现
销毁:扩展方法,继承dispoalBean接口实现
备注:
Bean的注入通常使用@Autowired注解,该注解用于bean的field、setter方法以及构造方法上,显式地声明依赖。
4. Spring 框架中都用到了哪些设计模式?
参考博文:Spring中涉及的设计模式总结_iCoding91的博客-优快云博客_spring中的设计模式
1. 抽象工厂:例如beanFactory和它的各种实现类。beanFactory只是定义了一些取对象的接口,具体对象如何生成如何获取由它的实现类取实现。其实factoryBean采用的方案也是抽象工厂模式。
2. 单例模式:spring中的bean的生成方式默认是单例的。当然也可以通过设置属性,改为多例。
3.适配器模式:衔接两个不兼容的接口,外界调用的方式不变,添加了适配类的处理逻辑。

具体的实现逻辑
例如
// 目标接口
interface Target {
void request();
}
// 适配者类
class Adaptee {
void specificRequest() {
System.out.println("Called specificRequest()");
}
}
// 适配器类
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest(); // 适配逻辑
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request(); // 调用适配器的方法
}
}
4. 代理模式:Aop的底层使用动态代理的方式来实现。
5. 观察者模式:
5. 过滤器的实现方案和原理?
6. 请详细描述springmvc处理请求全流程?为什么SpringMVC需要使用适配器模式?
了解springMVC的请求全流程需要了解这几个类?
1. dispatchServlet,核心组件,请求的入口,负责协调HandleMapping,AdaptHandler, veiwResolver 等组件的工作。
2. HandlerMapping,为请求找到合适的适配器。
3. AdapterHandler,为请求找到支持的处理器,
4. Handler,这个处理器是对应controller方法的代理类,负责处理具体的业务逻辑。
5. ModelAndView, 处理器返回的处理结果
6. ViewResolver, 视图解析器,作用解析ModelAndView,得到View对象
7. View,渲染视图
步骤:
1. 请求通过网络到达应用端
2. 应用端将请求传给DispatchServlet处理,它通过HandlerMappinng找到合适的AdapterHandler
3. AdapterHandler,它帮助找到对应的Handler, handler的作用是代理对应的controller,执行业务逻辑
4. handler处理完之后,会得到一个ModelAndView对象,这个对将被返给dispatchServlet
5. dispatcherServlet然后会将ModelAndView, 交给ViewResolver进行处理(在Spring MVC中,View是用来渲染模型数据的,这个渲染过程可以是生成HTML页面,也可以是生成JSON、XML等格式的数据)
6. ViewResolver处理完之后会将得到的View对象,View对象是用来渲染视图的,最后会将渲染结果返给前端(虽然你在Controller类中定义的方法返回的是自定义的类,但实际上,Spring MVC会将这个类转换为一个ModelAndView对象,然后使用这个对象渲染视图。这是Spring MVC的一个特性,它使得我们在开发过程中可以更专注于业务逻辑,而不需要关心视图和模型的具体处理过程)
7. spring 一个bean装配的过程?

1. 调用构造方法实例化
2. 调用set方法设置对象属性值
3. 执行初始化的过程
4. 使用IOC容器中的对象
5. 对象销毁(应用程序关闭的时候,会执行)
初始化的方法流程:
1. 如果类实现了下面的Aware接口,就会调用对应的set方法。 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name;
如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory;如果实现ApplicationContextAware,调用setApplicationContext设置ApplicationContext;
2. 实现BeanPostProcessor接口的postProcessBeforeInitialization方法;提供的功能和切面类似,他可以去统一的对一定范围的bean对象,做一些初始化的处理。
3. 执行初始化方法: @Compoent 修饰的类可以实现InitialingBean接口,重写afterPropertiesSet方法;@Bean注解的方式可以使用定义initMethod属性,指定bean的初始方法
4. 调用BeanPostProcessor的postProcessAfterInitialization
5. 调用BeanPostProcessor的后初始化方法;
8. 介绍下Spring IOC容器的初始化过程?
a . 资源定位,spring需要知道要加载的BeanDefinition有哪些,这些资源可以理解为定义了bean的xml文件资源,或者是打上@Component,@Configure,@Bean标签的类。其实在这个过程就会调用createBeabFactory方法来创建容器。
b. 资源加载,将a中定位到的资源,加载成ioc容器中的内部结构,这些内部结构其实就是用BeanDefinition对象来表示
c. 资源注册,向IOC容器容器中注册这些BeanDefinition的过程,调用BeanDefinitionRegistry接口完成,这些BeanDifinition对象会通过HashMap对象来完成
上面的过程是IOC容器的初始化过程,这个过程只是容器的初始化和资源的准备过程。依赖注入在这个阶段是没有发生的。
9. bean依赖注入的过程?
a. 用户调用getBean的时候或者容器完成bean的实例化的时候,会根据beanDefinition分析bean的依赖注入链
b. 如果实例化的对象有依赖注入的对象,那必须的完成依赖对象的实例化,才能实例化本对象。对象实例化的过程也伴随着bean初始化的过程。
10. SpringBoot中常见注解的含义?
(1) @EnableAutoConfiguration 激活SpringBoot内建的和自定义组件的自动装配特性。
(2)
11. SpringBoot中需要研究的特性?
reactive模式,使用webFlux框架,这个异常无阻塞框架可以解决高并发场景的什么问题。
11. SpringBoot的启动流程介绍?
SpringBoot的启动流程,非常适合和Spring的IOC容器和依赖注入的过程一起来说。
SpringBoot启动过程需要依赖的3大注解:
@EnableAutoConfiguration最为重要, 通过@Import注解,将bean资源注册到IOC容器中。其中bean资源注册的过程是由@AutoConfigurationPackage完成,autoConfigrationPackage又是通过@import(AutoConfigurationPackage.Registrar.class)实现。@Import(AutoConfigurationSelectorImportSelector.class)的类中有一个selectImports方法, 可以读取到spring.factories中的自动化配置的类信息。
@SpringBootConfiguration, 标记注解类,定位需要加载的资源
@ComponentScan,配置扫描路径,完成bean资源的加载过程
上面的过程和Spring容器初始化的过程是相符的,
那,ioc容器建立了之后,后面的流程就是bean初始化和依赖注入的过程。
SpringBoot启动流程及其原理 - rhyme - 博客园
12. spring中的bean是如何保证单例的?
spring中bean的获取是从ioc容器中获取的。
由AbstractBeanFactory可以,getBean方法源码,spring是从3级缓存中去获取bean对象的,这3级缓存分别是singletonObjects(concurrentHashMap结构),earlysingletonObjects, singletonFactory。
如果缓存中获取不到,并且bean又是单例模式(bean默认都是单例的)。
单例bean的创建过程,通过反射的方式创建bean对象,然后再完成bean初始化的过程。最后会将初始化完成的bean放在一级缓存singletonObjects。
怎么保证线程安全呢?
这段代码极其隐晦的藏在了getSingleton里面如下。其采用的就是synchronized + double check的方式。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
总结:bean的创建很像懒汉式的创建模式,也是bean使用的时候再去创建。这样的话,就需要保证并发情况下,依然是单例的。spring使用的是double check+ synchronized的方式,即保证并起下的单例,有不影响bean获取的效率。
13. Spring是如何防止循环引用的?
获取bean的代码逻辑:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
会使用的DefaultSingletonBeanRegistry中的三级缓存:singletonObjects, earlySingletonObjects, singletonFactory
举个例子:A依赖B, B又依赖A。假设A的bean先创建,实例化后调用set方法设置属性时发现,有个属性值是B的bean。其实在这个时候,A的bean已经创建好了,只是没有完成全部的初始化过程,这个半成品可以先暂时放在singletonFactory中。然后进行B的实例化,调用set方法时发现,它依赖A。这时,它可以从singletonFactory中取A的半成品进行依赖,然后完成后续的初始化动作。
参考文章:
一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】-腾讯云开发者社区-腾讯云
核心流程图:

实现的关键:
1. 2,3级缓存用来存放bean对象的半成品。
2. 如果缓存中存在bean实例,就不会继续创建新的实例。
代理类提前暴露:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
populateBean 属性赋值
initializeBean bean初始化过程
14. 什么时候bean会被移动到一级缓存中?
在bean完成初始化后,会被移动到一级缓存中,并删除2,3级缓存中的数据。
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

面试必杀技,讲一讲Spring中的循环依赖-阿里云开发者社区
15. 为什么要使用3级缓存来解决循环依赖,使用二级缓存不行吗?
可以的。只是如果使用二级缓存,在实例化的时候就要决定是创建代理对象还是原对象。而spring的3级缓存使用的是工厂对象,可以实现在注入的时候再决定是生成代理对象还是原始对象。本质还是想对象实例化过程和代理类的依赖注入过程分开(实例化时不用考虑是实例化代理类还是原始类,在依赖注入时再考虑生成代理类)。
参考:Spring 为什么要三级缓存,二级缓存不行吗?_spring为什么是三级缓存不是二级缓存-优快云博客

我们思考一种简单的情况,就以单独创建A为例,假设AB之间现在没有依赖关系,但是A被代理了,这个时候当A完成实例化后还是会进入下面这段代码:
// A是单例的,mbd.isSingleton()条件满足
// allowCircularReferences:这个变量代表是否允许循环依赖,默认是开启的,条件也满足
// isSingletonCurrentlyInCreation:正在在创建A,也满足
// 所以earlySingletonExposure=true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 还是会进入到这段代码中
if (earlySingletonExposure) {
// 还是会通过三级缓存提前暴露一个工厂对象
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
18.为什么spring应用启动好之后,第一次请求会很慢?
在 Spring 应用中,第一次请求往往会比较慢,主要原因有以下几种:
1. JIT 编译
-
Just-In-Time Compilation:Java 虚拟机(JVM)在运行时会对字节码进行即时编译,以提高性能。第一次请求时,JVM 可能需要编译大量的字节码,这会导致延迟。
-
热点代码识别:JVM 在运行应用时,会识别和优化频繁执行的代码,这个过程在第一次请求时会消耗额外时间。
2. 类加载
-
类加载延迟:在应用启动时,Spring 会加载必要的类和配置。第一次请求时,可能会加载尚未加载的类,导致延迟。
-
依赖注入:Spring 的依赖注入机制需要解析和实例化 bean,第一次请求时需要初始化所有相关的 bean。
3. 资源初始化
-
数据库连接:第一次请求可能会导致数据库连接的建立和相关资源的初始化,这通常会增加延迟。
-
缓存填充:如果应用使用了缓存,第一次请求时可能需要填充缓存,这会导致响应时间延长。
4. 中间件和服务启动
-
外部服务连接:如果应用依赖于外部服务(例如消息队列、第三方 API),第一次请求时可能需要建立连接或进行认证。
-
Spring Boot 自动配置:在 Spring Boot 应用中,自动配置可能会在第一次请求时进行大量的初始化。
5. HTTP 连接
-
TCP 握手:对于第一次请求,TCP 连接需要进行三次握手,这会增加延迟。
-
DNS 解析:如果请求涉及不同的服务,DNS 解析也可能导致延迟。
19. 一个bean的代理 和 bean 可以同时存在于容器中吗?
在 Spring 中,一个 Bean 的代理和原始 Bean 可以同时存在于容器中,但这通常取决于具体的配置和使用方式。以下是一些关键点和场景,说明它们如何共存和相互作用:
1. 代理的概念
在 Spring 中,代理通常用于 AOP(面向切面编程),以便在方法调用前后添加额外的行为(如日志记录、事务管理等)。代理可以是 JDK 动态代理(基于接口)或 CGLIB 代理(基于类)。
2. 同时存在的情况
-
AOP 代理:当你使用 AOP 时,Spring 通常会创建一个代理 Bean,替代原始 Bean。这意味着,客户端通常会获取到代理,而不是原始 Bean。然而,原始 Bean 仍然存在于 Spring 容器中。
-
原始 Bean 访问:如果你需要直接访问原始 Bean,可以通过
ApplicationContext.getBean()方法获取它,通常在不使用代理的情况下直接返回原始 Bean。
3. 具体场景
-
没有接口的 Bean:如果一个 Bean 没有实现接口,Spring 会使用 CGLIB 代理。在这种情况下,原始 Bean 将会存在,而代理 Bean 是一个 CGLIB 生成的子类,代理和原始 Bean 可以同时存在。
-
使用
@Primary注解:如果你有多个 Bean(包括代理和原始 Bean)具有相同的类型,你可以使用@Primary注解来指示 Spring 在自动注入时优先选择哪个 Bean。
20. 那bean和bean的代理的beanName一样吗
1. 代理的生成
- 当 Spring 创建一个 Bean 的代理时,该代理通常会使用与原始 Bean 相同的名称。这意味着,无论你请求的是原始 Bean 还是其代理,获取的对象都可以通过相同的名称来访问。
- 例如,如果你有一个名为
myService的 Bean,创建其代理后,你仍然可以通过myService来访问该 Bean。Spring 会返回代理对象。
2. 访问 Bean 和代理
- 获取原始 Bean 和代理:在大多数情况下,用户会通过相同的 Bean 名称来访问 Bean。Spring 会决定返回原始 Bean 还是代理,具体取决于请求的上下文(例如,是否有 AOP 切面)
21. bean怎么解决循环依赖问题?
首先3级缓存,解决了field和setter的场景的循环依赖,但是没有解决构造器场景的循环依赖
而lazy可以解决构造器场景,setter场景和field场景的循环依赖问题,当然还是设计上不要有循环依赖是最好了。
113






