Spring是一种Java开发框架,旨在简化企业级应用程序的开发和部署。它具有以下优点:
控制反转(IoC)是Spring的一个重要特性,它使得对象的创建和依赖关系的管理由Spring容器来完成。IoC有三种实现方式:注解形式、构造器形式和set方法注入。通过IoC,我们不再需要使用new关键字手动创建对象,而是将对象的创建和管理交给Spring容器处理。
面向切面编程(AOP)是Spring的另一个重要特性,它通过动态代理实现。AOP常用于日志收集、事务管理等方面。通过AOP,我们可以在被代理对象的方法执行前后,加入一些统一的业务逻辑处理,例如日志记录或权限校验。
启动流程几乎跟源码息息相关,如果没有看过源码可能对启动流程只能靠自己的理解去背,如果对源码右深入理解,那么这道题可以这么说:
1:初始化reader和scanner
2:使用scanner组件扫描basePackage下的所有对象,将配置类的BeanDefinition注册到容器中。
3:refresh(); 刷新容器。
可以分为以下几个步骤:
然后在细说自己知道的部分源码,比如我还了解到一些关于源码的细节。例如,在获取Bean定义后,Spring会在实例化之前通过合并Bean定义来进行初始化,并且AOP的逻辑是在初始化之后通过后置处理器进行动态代理。
此外,如果我们需要监听Spring的启动过程以及在启动后实现自己的业务逻辑,除了可以使用初始化对象的方法afterPropertiesSet外,还可以通过注册一个监听器来监听Spring发布的各种事件。这样,我们就可以在特定的事件触发时执行我们自己的逻辑。
在Spring框架中,Bean的创建过程涉及到多个环节和细节。下面我将更详细地介绍每个步骤的具体内容。可以大致分为五个步骤:获取Bean定义、实例化、赋值、初始化和销毁。
Spring框架中的Bean默认是单例模式,因此不是线程安全的。如果要处理线程安全问题,可以采取以下几种方式:
大家都知道spring采用的是三级缓存,那么如何理解三级缓存处理了循环依赖问题呢?
一级缓存:缓存最终的单例池对象: private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
二级缓存:缓存初始化的对象:private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
三级缓存:缓存对象的ObjectFactory: private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
其实当一个对象实例化后就会存储在 singletonFactories三级缓存,当被引用时,会执行一个后置处理器方法,这里也是给aop创建代理对象的时机:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
如果是正常的普通对象,会直接进行如二级缓存,并返回一个实例化后的对象,所以之所以使用到了三级缓存,而不是光是用二级缓存就是考虑到了循环依赖可能是一个代理对象,我们无法直接提供实例化的对象而是一个代理对象。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
Spring框架提供了两种方式来处理事务:编程式事务和声明式事务。
编程式事务是通过使用TransactionTemplate来进行事务管理的方式。它需要手动在代码中显式地指定事务的开启、提交和回滚操作。这种方式对代码的侵入性较高,因为需要在每个需要进行事务管理的方法中编写事务处理的代码。一般情况下,不推荐使用编程式事务,除非在特定的场景下需要对事务进行更精细的控制。
声明式事务是通过使用注解或XML配置的方式来声明事务的行为。在Spring中,最常用的是使用注解来声明事务。通过在方法或类上添加@Transactional注解,可以指定事务的传播行为、隔离级别、超时时间等属性。事务的传播行为指的是当一个方法调用另一个带有事务注解的方法时,事务应该如何进行传播和管理。
Spring框架提供了以下几种事务的传播级别:
Spring框架提供了以下几种事务的隔离级别:
在Spring MVC中,默认情况下,控制器是以单例模式创建的。这意味着在应用程序的整个生命周期中,只会创建一个控制器实例来处理所有的请求。为了保证控制器的线程安全性,可以采取以下措施:
1:保持控制器的无状态属性:控制器应该尽量避免使用实例变量来保存状态信息,尽量使用方法参数或局部变量来处理请求。这样可以确保每个请求都有独立的数据副本,避免多个线程之间的竞争和冲突。
2:设置控制器的作用域为非单例模式:可以将控制器的作用域设置为非单例模式,如prototype或request。这样每次请求都会创建一个新的控制器实例,确保每个请求都有独立的控制器对象,避免线程安全问题。
本次面试涉及了Spring框架的多个方面,包括IOC和AOP的理解、Spring容器的启动流程、Bean的创建过程、Bean的线程安全性、循环依赖的处理、事务的处理以及Spring MVC中控制器的线程安全性。通过这些问题的回答,展示了对Spring框架的深入理解和应用经验。同时,也凸显了对面试题目的认真思考和清晰表达的能力。
Spring是一个基于Java的企业级应用程序开发框架,它使用了多种设计模式来实现其各种特性和功能。本文将介绍一些在Spring中使用的常见设计模式以及相应的代码示例和说明。