1、什么是Spring?
Spring是一个功能强大、灵活易用的Java开发框架,它由Rod Johnson创建,最初是为了解决企业应用开发的复杂性,如对象管理、依赖注入等。它通过控制反转和面向切面编程等核心技术,为开发者提供了全面的基础设施支持,降低了应用开发的复杂性,提高了代码的可维护性和可扩展性。
- 核心技术:
- 控制反转(IoC):这是Spring的核心思想之一。IoC使得对象的创建和依赖关系的管理不再由应用程序代码直接控制,而是交给Spring容器来管理。这样,应用程序代码更加简洁,且对象之间的耦合度降低,提高了代码的可维护性和可扩展性。
- 面向切面编程(AOP):AOP是Spring的另一个核心技术,它允许将横切关注点(如日志记录、事务管理、安全控制等)与业务逻辑分离,通过配置或注解的方式将这些关注点“织入”到业务逻辑中,从而实现了系统级服务的最大复用。
- 框架特性:
- Spring框架具有轻量级、非侵入性、模块化等特点。它提供了丰富的功能模块,如Spring Core、Spring AOP、Spring ORM、Spring DAO等,可以满足不同场景下的开发需求。
- Spring还强调与现有框架的整合,可以与Struts、Hibernate、MyBatis等众多框架无缝集成,为开发者提供了极大的灵活性。
- 应用范围:
- Spring框架不仅适用于企业级应用开发,还可以应用于桌面应用程序、小应用程序以及任何需要管理对象生命周期和依赖关系的Java应用。
2、Spring框架有哪些优缺点?
优点:
- 轻量级与松耦合:Spring框架核心容器轻量级,不依赖其他框架或中间件,且通过控制反转(IoC)和依赖注入(DI)机制降低了组件间的耦合度,提高了应用的灵活性和可扩展性。
- 面向切面编程(AOP)支持:Spring提供了AOP功能,使得开发者可以更容易地实现横切关注点的编程,如日志记录、事务管理等,提高了代码的可维护性和可重用性。
- 事务管理:Spring框架提供了强大的事务管理机制,支持多种事务管理方式,保证了数据的完整性和一致性。
- 便于测试:Spring框架支持JUnit等测试框架,使得单元测试和集成测试变得更加方便。
- 集成性:Spring框架与其他框架和技术的集成非常方便,如Hibernate、MyBatis等,降低了使用难度。
- 降低Java EE API使用难度:Spring对难用的Java EE API提供了简易封装,降低了使用难度。
缺点:
- 运行效率:由于Spring框架提供了大量的功能和灵活性,可能会增加一定的运行时开销,导致应用运行效率相对较低。
- 依赖反射:Spring框架依赖反射机制,反射会影响性能。
- 配置复杂:Spring框架在使用时需要进行大量的XML或注解配置,增加了开发者的工作量和代码的维护成本。
3、Spring框架有哪些常用模块?
Spring框架的常用模块主要包括以下几个:
- Spring Core(核心容器):这是Spring框架的基础部分,提供了依赖注入(Dependency Injection, DI)和控制反转(Inversion of Control, IoC)的功能。IoC容器负责创建、配置和管理Bean,降低了代码的耦合度,使得应用更易于维护和测试。
- Spring Context(应用上下文):构建于Core模块之上,提供了上下文信息,包括企业服务如JNDI、EJB、电子邮件、国际化、校验和调度功能等。它使得Spring框架能够更方便地与其他框架和系统进行集成。
- Spring AOP(面向切面编程):AOP模块直接将面向方面的编程功能集成到了Spring框架中,为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,可以更容易地实现横切关注点的编程,如日志记录、事务管理等。
- 数据访问模块:包括Spring JDBC、Spring ORM等,用于访问和操作数据库中的数据。JDBC模块提供了一个JDBC的抽象层,消除了冗长的JDBC编码;ORM模块则为主流的对象关系映射API提供了集成层,如Hibernate、MyBatis等。
- Spring MVC(Web模块):这是Spring框架中用于构建Web应用程序的模块,提供了全功能的MVC实现。通过策略接口,MVC框架变得高度可配置,并且容纳了大量视图技术,如JSP、POI等。
- Spring Test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。
4、Spring 框架中都用到了哪些设计模式?
- 工厂模式:
- 简单工厂模式:Spring中的BeanFactory就是简单工厂模式的体现,它根据传入的一个唯一标识来获得Bean对象。
- 工厂方法模式:在Spring中,通过定义工厂方法,并通过配置文件或注解将其纳入Spring容器来管理,以实现对象的创建和初始化。
- 单例模式:
- Spring框架广泛使用了单例模式来确保某些组件或工具类在应用程序生命周期中只有一个实例,从而节省内存和提高性能。例如,数据库连接池、缓存、日志记录器等常采用单例模式。
- 代理模式:
- Spring的AOP(面向切面编程)就是代理模式的一个典型应用,它通过代理对象来实现对目标对象的增强或拦截。
- 适配器模式:
- Spring中的HandlerAdapter就是适配器模式的应用,它使得不同的处理器能够适配到统一的接口上,从而实现了灵活的调用。
- 观察者模式:
- Spring中的事件驱动模型就是观察者模式的一种实现,它允许事件发布者和事件监听者之间实现松耦合的通信。
5、什么是Spring IOC 容器?
Spring IOC 容器是Spring框架中的核心组件,用于管理Java对象的实例化和初始化,以及控制对象与对象之间的依赖关系。以下是关于Spring IOC 容器的详细解释:
- IOC(Inversion of Control)控制反转:
- IOC不是一门技术,而是一种设计思想,旨在降低程序的耦合度,提高程序的扩展能力。
- 控制反转的核心是将对象的创建权利和对象与对象之间关系的维护权交给第三方容器,即Spring IOC容器负责。
- 依赖注入(DI, Dependency Injection):
- 依赖注入是实现控制反转的一种方式,它允许容器在运行时将依赖关系注入到对象中,而不是由对象自己创建或查找依赖。
- 通过依赖注入,Spring IOC容器能够灵活地管理对象之间的依赖关系,提高了代码的可维护性和可重用性。
- IOC容器的作用:
- 创建对象:Spring IOC容器负责创建由它管理的Java对象,这些对象被称为Spring Bean。
- 维护生命周期:容器不仅创建对象,还负责维护这些对象的生命周期,包括初始化、使用和销毁等过程。
- 置依赖关系:容器通过依赖注入(DI)的方式,动态地向某个对象提供它所需要的其他对象,从而实现了对象之间的松耦合。
- IOC容器的实现:
- 在Spring框架中,BeanFactory和ApplicationContext是实现IOC容器的两种方式。
- BeanFactory提供了基本的依赖注入支持,而ApplicationContext在BeanFactory的基础上构建了更多的企业级功能,如事件发布、国际化信息支持等。
6、BeanFactory 和 ApplicationContext有什么区别?
BeanFactory和ApplicationContext都作为Spring的IoC容器,但ApplicationContext作为增强型容器,提供了更全面的企业级服务和更友好的编程模型,更适合现代Spring应用的开发需求。
-
继承关系:
- BeanFactory:作为Spring IoC容器的基础接口,提供了最基础的Bean管理功能,如Bean的定义、实例化、依赖注入等。它是Spring框架中最底层的容器接口,主要关注Bean的基本生命周期管理。
- ApplicationContext:ApplicationContext是BeanFactory的子接口,继承并扩展了BeanFactory的功能。它不仅包含了BeanFactory的所有特性,还提供了许多面向企业级应用的附加服务,如国际化支持、事件发布与监听、AOP支持等。因此,通常被认为是Spring应用的首选容器。
-
加载模式:
- BeanFactory:默认采用懒加载(lazy initialization)模式,即只有在首次请求某个Bean时,BeanFactory才会创建对应的实例。
- ApplicationContext:默认采用预加载(eager loading)模式,在启动时一次性创建所有非懒加载的单例Bean。这有助于提前发现配置问题,并使得那些需要在应用启动时就准备就绪的服务能够立即使用。
-
功能扩展:
- BeanFactory:功能相对基础,专注于Bean的生命周期管理、依赖解析与注入,适用于轻量级或嵌入式场景。
- ApplicationContext:除了基本的Bean管理外,还提供了国际化支持、事件发布与监听机制、资源访问等功能,以及支持载入多个配置文件和Bean的后处理器等。这些扩展功能使得ApplicationContext更适合于企业级应用的开发。
-
使用便捷性:
- BeanFactory:由于功能较为基础,使用时可能需要编写更多代码来实现某些高级功能。
- ApplicationContext:提供了丰富的API和便捷的方法来访问容器中的Bean、获取配置信息、处理国际化消息等,简化了开发工作。
-
实现方式:
- BeanFactory:是Spring IoC容器的基础接口,提供了配置和管理bean的功能。它有很多实现类,如XmlBeanFactory、ListableBeanFactory和DefaultListableBeanFactory等。
- ApplicationContext:是BeanFactory的子接口,提供了更多的功能。常见的实现类有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext等。
-
适用场景:
- BeanFactory:适用于对运行时内存占用要求严格、启动速度要求较高或者只需要基础IoC/DI功能的轻量级场景。
- ApplicationContext:适用于绝大部分企业级应用开发,尤其是需要利用Spring框架的全套特性(如AOP、事件驱动、国际化等)以及需要复杂Bean生命周期管理、高级服务支持的场景。
7、Spring Beans的配置方式有哪些?
在Spring框架中,配置依赖注入主要有以下几种方式:
- XML配置
这是Spring框架早期常用的配置方式,通过在XML配置文件中定义Bean和它们之间的依赖关系。- 定义Bean:使用标签定义Bean,指定其类名和ID。
- 依赖注入:
- 构造器注入:在标签中使用标签定义构造函数的参数。
- Setter方法注入:在标签中使用标签定义Bean的属性值或依赖对象。
<bean id="exampleService" class="com.example.ExampleService"> <constructor-arg ref="dependency"/> </bean> <bean id="dependency" class="com.example.Dependency"/>
- 注解配置
随着Spring框架的发展,注解方式逐渐成为主流,它使得代码更加简洁和易于维护。- 定义Bean:使用@Component、@Service、@Repository、@Controller等注解来标记需要Spring管理的Bean。
- 依赖注入:
- 构造器注入:通过类的构造函数来注入依赖项。这种方式的好处是依赖项在对象创建时即被注入,因此依赖项是不可变的,更适合不可变的依赖关系。构造函数注入通常被认为是最佳实践,因为它可以确保依赖项在对象创建时就被初始化,并且对象在整个生命周期中始终保持不变。
- Setter方法注入:通过类的setter方法来注入依赖项。这种方式允许依赖项在对象实例化之后进行设置或更改,适合可选的或延迟初始化的依赖。然而,Setter方法注入可能导致对象状态的不确定性,因为依赖项可以在任何时候被更改,且不支持不可变对象。
- 字段注入:直接在类的字段上使用@Autowired注解,Spring会自动将依赖注入到这些字段中。这种方式代码简洁,但通常不是最佳实践,因为它不支持不可变对象,可能导致代码难以阅读和维护,且依赖项的生命周期可能不明确。
@Service public class ExampleService { private final Dependency dependency; @Autowired public ExampleService(Dependency dependency) { this.dependency = dependency; } }
- Java配置类
Spring 3.0引入了Java配置类,通过Java代码来配置Bean和依赖注入。- 定义Bean:使用@Configuration注解标记配置类,并使用@Bean注解在方法上,方法名默认为Bean的ID,方法返回值为Bean的实例。
- 依赖注入:在方法内部,可以直接通过调用其他@Bean注解的方法来注入依赖。
@Configuration public class AppConfig { @Bean public ExampleService exampleService(Dependency dependency) { return new ExampleService(dependency); } @Bean public Dependency dependency() { return new Dependency(); } }
8、哪种依赖注入方式最好?
在Spring框架中,构造器注入通常被认为是最佳的依赖注入方式。以下是详细的解释和比较:
- 构造器注入:
- 优点:
- 确保所有必需的依赖项在对象创建时就已经存在,保证了对象状态的一致性和不可变性。
- 有助于解决循环依赖的问题,因为Spring会在检测到循环依赖时抛出异常,迫使开发者解决。
- 优点:
- Setter方法注入:
- 优点:
- 提供了按需注入的选择,适用于部分依赖或可选依赖。
- 使得对象在创建后还可以修改其依赖项,增加了灵活性。
- 缺点:
- 可能导致对象在创建后状态不一致,因为依赖项可以在对象生命周期的任何时候被更改。
- 不如构造器注入安全,因为外部代码可以通过Setter方法更改对象的依赖项。
- 优点:
- 字段注入(基于字段的方式注入):
- 优点:
- 实现简单,通过一个注解(如@Autowired)就可以完成注入。
- 缺点:
- 可能导致外部可见性问题,因为字段是公开的或可以通过反射访问。
- 可能导致循环依赖问题被隐藏,而不是解决。
- 无法注入final类型的属性,因为final字段必须在类实例化时初始化。
- 与Spring框架强绑定,脱离框架后代码可能无法正确运行。
- 优点:
综上所述,构造器注入因其能够确保对象状态的一致性和不可变性,以及有助于解决循环依赖问题,通常被认为是最佳的依赖注入方式。Setter方法注入和字段注入虽然在某些场景下也有其用途,但相比之下存在更多的缺点和风险。因此,在Spring框架中进行依赖注入时,建议优先考虑使用构造器注入。
9、什么是循环依赖?
循环依赖是指两个或多个Bean(或组件)在Spring框架中相互依赖,形成一个闭环,导致在Bean的创建和依赖注入过程中出现问题。具体来说,就是Bean A依赖于Bean B,同时Bean B也依赖于Bean A,或者更复杂的依赖链,如A依赖B,B依赖C,C又依赖A。
10、循环依赖常见的几种场景
(1)单例的setter注入
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1(){}
}
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2(){}
}
这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制`三级缓存`,
让我们根本无法感知它有问题。
(2)多例的setter注入
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1(){}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2(){}
}
如果只有上面两个多例循环依赖的话,程序是能够正常启动的,因为多例对象不会被提前初始化bean,
所以程序能够正常启动。如果在定义一个单例的类,在它里面注入TestService1,这时候就会出现循环依赖
的报错,并且这种报错问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。
@Service
publicclass TestService3 {
@Autowired
private TestService1 testService1;
}
(3)构造器注入
@Service
publicclass TestService1 {
public TestService1(TestService2 testService2) {}
}
@Service
publicclass TestService2 {
public TestService2(TestService1 testService1) {}
}
出现循环依赖报错,构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。
(4)单例的代理对象setter注入
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
@Async
public void test1(){}
}
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2(){}
}
这种通过@Async注解的场景,会通过 AOP 自动生成代理对象,在bean初始化完成之后,后面还有一步去检查:
第二级缓存中的对象 和 原始对象 是否相等,因为生成的是代理对象与原始对象不相等,所以报错。
(5)DependsOn循环依赖
@DependsOn(value = "testService2")
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1(){}
}
@DependsOn(value = "testService1")
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2(){}
}
在 AbstractBeanFactory 类的 doGetBean 方法中,会检查dependsOn的实例有没有循环依赖,
如果有循环依赖则抛异常。
11、Spring框架如何解决循环依赖?
Spring框架解决循环依赖的方法主要依赖于以下几种策略:
- 使用Setter注入代替构造器注入:
- 构造器注入在对象实例化时完成,如果此时存在循环依赖,Spring无法解决。而Setter注入允许Spring先实例化Bean,然后逐步注入依赖,即使依赖尚未完全初始化。这样,Spring可以通过提前暴露Bean的部分引用来解决循环依赖问题。
- 使用@Lazy注解:
- 在循环依赖的Bean定义上使用@Lazy注解,可以让Spring延迟初始化该Bean,直到第一次实际使用时才进行。这样,在初始化一个Bean时,如果遇到循环依赖,Spring不会立即实例化依赖的Bean,从而避免死锁。
- 三级缓存机制:
- Spring框架内部使用三级缓存机制来解决循环依赖问题。具体来说,Spring在Bean实例化后但还未注入所有依赖项时,会将这个Bean提前暴露出去(通过二级缓存),以便其他Bean能够注入它的依赖项。如果此时其他Bean也需要依赖这个正在创建的Bean,Spring会从二级缓存中获取这个Bean的引用,而不是重新创建一个实例。这样,即使存在循环依赖,也可以避免死锁。
12、Spring的三级缓存是什么?
Spring三级缓存是Spring框架在处理Bean对象时采用的一种缓存机制,用于提高系统性能和减少资源消耗,同时解决循环依赖问题。具体来说,Spring的三级缓存由三个缓存级别组成:
- 一级缓存(singletonObjects):这是Spring容器中最终存放完全初始化并且准备好供使用的Bean对象的缓存。当通过getBean()方法获取Bean对象时,Spring会首先检查singletonObjects缓存中是否已经存在该Bean对象,如果存在就直接返回,如果不存在则继续后续操作。
- 二级缓存(earlySingletonObjects):这是Spring容器通过BeanDefinition创建Bean对象的一个缓存,用于存放尚未完全初始化的Bean对象。当一级缓存中不存在所需Bean对象时,Spring会检查二级缓存是否存在该Bean对象。如果存在,则返回已经创建但尚未初始化的Bean对象。
- 三级缓存(singletonFactories):这是存储生成单例对象的工厂对象的缓存。当需要实例化一个单例Bean时,如果一级和二级缓存中都不存在该Bean对象,Spring会调用工厂方法创建一个工厂对象,并将该工厂对象存入三级缓存中。在后续的实例化过程中,Spring会通过该工厂对象来创建Bean实例,并存入一级缓存中。
13、Spring的三级缓存解决循环依赖的流程是什么样?
当Spring容器在创建和初始化Bean时遇到循环依赖,会按照以下步骤处理:
- 查找一级缓存:
- 首先从singletonObjects中查找所需的Bean实例,如果找到则直接返回。
- 查找二级缓存:
- 如果一级缓存中没有找到,Spring会尝试从earlySingletonObjects(二级缓存)中查找。如果找到,说明该Bean已经实例化但尚未完全初始化,Spring会返回该Bean的早期引用,从而解决循环依赖问题。
- 利用三级缓存创建早期引用:
- 如果一级和二级缓存中都没有找到所需的Bean,Spring会从singletonFactories(三级缓存)中获取一个ObjectFactory对象,并调用其getObject方法创建一个早期引用(即部分初始化的Bean实例),然后将这个早期引用放入二级缓存中。
- 完成Bean的初始化:
- 在Bean的所有依赖都被注入后,Spring会完成Bean的初始化过程,包括调用初始化方法等。完成初始化后,Spring会将完全初始化的Bean实例放入一级缓存中,并从二级和三级缓存中移除相应的条目。
需要注意的是,Spring的三级缓存机制主要适用于单例Bean的Setter或字段注入类型的循环依赖问题。对于构造器注入的循环依赖,Spring无法通过三级缓存解决,因为构造器必须一次性注入所有依赖1。
14、可以使用二级缓存解决循环依赖的问题吗?
三级缓存的引入是为了更全面地解决Spring中的循环依赖问题,特别是涉及到动态代理和AOP时的复杂情况。二级缓存虽然能在一定程度上解决循环依赖问题,但无法满足Spring在处理动态代理和保持设计原则一致性方面的需求。
- 支持动态代理和AOP:
- Spring的AOP(面向切面编程)允许在Bean的创建过程中或之后为其添加额外的行为。这通常涉及到代理对象的创建。如果只在二级缓存中存储已经实例化的Bean,那么在AOP处理时,可能会遇到尚未初始化的Bean被代理的情况,导致代理对象与原始对象之间的不一致。三级缓存的引入,特别是singletonFactories缓存,允许Spring在Bean完全初始化之前存储一个工厂对象,该对象可以延迟创建Bean的实例,直到需要时再进行代理和初始化。这样,即使存在循环依赖,也能保证每个Bean都是唯一且正确代理的。
- 保持设计原则的一致性:
- Spring的设计原则是在IOC(控制反转)结束之后再进行AOP(即Bean实例化、属性设置、初始化之后再生成代理对象)。为了在不打破这个设计原则的情况下解决循环依赖问题,Spring使用了第三级缓存。这样,即使在循环依赖的情况下,也能保证Bean的生命周期按照既定的顺序进行,即先实例化、再属性注入、最后初始化和代理。
15、Spring框架中的单例bean是线程安全的吗?
Spring框架中的单例bean不是线程安全的。Spring中的bean默认是单例模式的,即在整个Spring容器中只创建一个bean实例,并在整个应用的生命周期中共享该实例。然而,Spring框架本身并不对单例bean进行线程安全封装,如果bean内部存在可变状态(即有成员变量,并且并发线程会对这些成员变量进行修改操作),那么就需要开发者自行处理线程安全问题。
具体来说,如果单例bean是无状态的(即没有成员变量,或者多线程只会对bean成员变量进行查询操作,不会进行修改操作),那么它是线程安全的。但如果单例bean是有状态的,那么它就是非线程安全的,因为并发线程可能会对bean的内部状态进行修改,导致数据不一致或其他线程安全问题。
因此,在使用Spring框架时,如果需要使用有状态的bean,并且希望它是线程安全的,那么可以考虑以下几种解决方案:
- 将bean的作用域由“singleton”变更为“prototype”,这样每次请求都会创建一个新的bean实例,从而避免线程安全问题。
- 对bean的内部状态进行同步处理,比如使用synchronized关键字或者java.util.concurrent包中的同步工具类来确保线程安全。
- 使用Spring提供的线程安全集合类或者将bean的状态存储在线程安全的数据结构中,比如ConcurrentHashMap等。
16、ThreadLocal是什么?
ThreadLocal,即线程局部变量,是Java中一种用于实现线程间数据隔离的机制。它并不是用来解决共享变量问题的,而是为每个使用该变量的线程提供一个独立的变量副本,这样每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
- ThreadLocal的作用:
- 实现线程间数据隔离:ThreadLocal为每个线程提供独立的变量副本,确保线程间的数据不会相互干扰。
- 简化多线程编程:通过使用ThreadLocal,可以避免在多线程编程中传递共享变量,从而简化代码编写。
- ThreadLocal在Spring中的应用:
- 管理Request作用域的Bean:在Spring中,可以使用ThreadLocal来存储与当前请求相关的Bean,这样在每个线程中都能独立地访问这些Bean,而不会与其他线程的数据混淆。
- 事务管理:Spring事务管理中也使用了ThreadLocal。例如,在事务开始时,Spring会将数据库连接绑定到当前线程,并通过ThreadLocal来存储这个连接。这样,在事务执行过程中,每个线程都能使用自己的数据库连接,确保事务的隔离性和一致性。
- 任务调度与AOP:在Spring的任务调度和AOP模块中,ThreadLocal也被广泛应用,用于存储与当前线程相关的上下文信息。
- ThreadLocal的使用方法:
- set(T value):将当前线程与指定的值关联起来。
- get():返回当前线程与ThreadLocal关联的值。如果当前线程没有与ThreadLocal关联的值,则返回初始值(通常为null,但可以通过重写initialValue方法来指定其他初始值)。
- remove():移除当前线程与ThreadLocal的关联,这样当前线程就不再持有该ThreadLocal变量的副本。
- 注意事项:
- 内存泄漏:如果在使用ThreadLocal后没有及时调用remove()方法,可能会导致内存泄漏,因为ThreadLocal会持有线程的引用,而线程又持有ThreadLocal的引用,从而形成循环引用。
- 适用场景:ThreadLocal适用于那些需要在线程间隔离数据,但又不想通过传递参数来实现的场景。然而,如果数据本身是多线程共享的,或者可以通过其他方式(如局部变量)来避免共享问题,那么就不需要使用ThreadLocal。
17、Spring框架中bean的生命周期有哪几个阶段?
在Spring框架中,Bean的生命周期可以大致分为以下几个阶段:
- 定义与配置阶段:
- 通过XML文件、注解或Java代码等方式定义Bean的元信息,如类名、作用域、是否延迟加载等。
- 解析与注册阶段:
- Spring容器启动后,会解析这些元信息,生成BeanDefinition对象,该对象包含了Bean的所有配置信息。然后,将Bean注册到容器中。
- 实例化前阶段:
- 在Bean实例化之前,Spring容器可能会进行一些前置处理,如合并BeanDefinition、加载类、执行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法等。
- 实例化阶段:
- Spring容器会根据BeanDefinition中的信息,通过反射机制创建Bean的实例。如果Bean有指定的工厂方法或工厂Bean,则会通过相应的方式创建实例。
- 属性赋值阶段:
- 实例化完成后,Spring容器会根据BeanDefinition中的配置信息,为Bean的属性赋值。这包括基本类型的属性、其他Bean的引用等。如果属性之间存在依赖关系,Spring容器会处理这些依赖关系。
- 初始化前阶段:
- 在Bean初始化之前,Spring容器可能会再次执行一些前置处理,如触发BeanPostProcessor的postProcessBeforeInitialization方法等。
- 初始化阶段:
- Spring容器会执行Bean的初始化方法。这些初始化方法可能包括实现了特定接口的回调方法(如InitializingBean接口的afterPropertiesSet方法)、自定义的初始化方法(在XML配置或注解中指定)等。
- 使用阶段:
- 初始化完成后,Bean就可以被应用程序中的其他部分使用了。在这个阶段,Bean会根据其定义的作用域和生命周期策略,在需要时被创建、使用、销毁。
- 销毁前阶段:
- 在Bean销毁之前,Spring容器可能会执行一些前置处理,如触发BeanPostProcessor的postProcessBeforeDestruction方法等。
- 销毁阶段:
- 当Bean不再需要时,Spring容器会对其进行销毁操作。对于单例Bean,这通常发生在容器关闭时;对于多例Bean,则可能发生在请求结束时或Bean被显式销毁时。如果Bean实现了DisposableBean接口或在其配置中指定了销毁方法(destroy-method属性),则这些方法会在Bean销毁时被调用。
18、Spring自动装配策略有哪些?
自动装配策略是Spring框架中用于自动解析并注入Bean依赖项的一种机制。它根据一定的规则,在Spring容器启动时自动为Bean寻找并注入所需的依赖,从而简化了配置过程,提高了开发效率。Spring提供了多种自动装配策略,主要包括以下几种:
- byName自动装配:
- 这种策略会根据Bean的属性名在容器中查找匹配的Bean进行注入。例如,如果一个Bean有一个名为“course”的属性,Spring会尝试在容器中找到一个id或名称为“course”的Bean,并将其注入到该属性中。
- byType自动装配:
- 这种策略会根据Bean的属性类型在容器中查找匹配的Bean进行注入。如果容器中存在多个类型匹配的Bean,Spring会优先选择设置了primary="true"的Bean,或者根据其他条件进行筛选。
- constructor自动装配:
- 这种策略会通过构造函数参数的类型来查找并注入依赖。Spring会尝试在容器中找到与构造函数参数类型匹配的Bean,并通过构造函数将其注入到目标Bean中。
- 默认自动装配(或称为autodetect):
- 在Spring的早期版本中,还存在一种默认自动装配策略,它会首先尝试通过构造函数自动装配,如果失败,则尝试通过byType方式进行自动装配。然而,需要注意的是,从Spring 3.0版本开始,这种策略已经不再被支持。
19、自动装配有哪些局限性?
- 重写问题:尽管自动装配能够简化依赖注入的过程,但在某些情况下,你仍然需要使用和等配置来显式定义依赖关系,这意味着自动装配并不是完全无需配置的。
- 基本数据类型限制:自动装配无法处理基本数据类型、字符串以及类本身。这些类型的依赖需要手动进行配置和注入。
- 模糊特性:自动装配的精确性不如显式装配。在复杂的依赖关系中,自动装配可能会导致不明确或错误的依赖注入,因此,在可能的情况下,建议使用显式装配来确保依赖关系的准确性。
- 歧义性问题:当容器中存在多个类型匹配的Bean时,自动装配可能会导致歧义性,无法确定应该注入哪个Bean。这种情况下,Spring会抛出异常,需要使用@Qualifier注解来指定特定的Bean进行装配。
- 单一容器限制:自动装配仅适用于单一的Spring容器。如果应用程序使用了多个Spring容器,自动装配可能无法跨容器进行,这限制了其在某些复杂应用场景中的使用。
20、可以在Spring中注入一个null 和一个空字符串吗?
可以,Spring框架支持通过配置或注解的方式注入null和空字符串。以下是一些实现方法:
(1)通过XML配置注入null:
<bean id="userService" class="com.skjava.UserService">
<property name="orderService">
<null/>
</property>
</bean>
(2)通过XML配置注入空字符串:
<bean id="userService" class="com.skjava.UserService">
<property name="userName" value=""/>
</bean>
(3)通过注解注入null:
@Autowired(required = false)
private OrderService orderService;
(4)通过注解注入空字符串:
@Value("")
private String userName;
21、FactoryBean 和 BeanFactory有什么区别?
BeanFactory是Spring框架中用于管理Bean的工厂容器,而FactoryBean则是一个特殊的Bean,用于创建和返回特定的Bean实例。它们在作用、返回值、配置方式以及生命周期上存在着明显的区别。
- 作用不同:
- BeanFactory是Spring框架的基础设施,它是一个用于管理Bean的工厂容器。它负责从配置文件或注解中读取Bean的定义信息,并根据需要创建相应的Bean实例。BeanFactory在加载配置文件时不会初始化Bean,而是在获取Bean的时候才会进行实例化。
- FactoryBean则是一个特殊的Bean,它自身也是一个Bean,但同时充当了工厂的角色。FactoryBean接口定义了getObject()方法,用于创建和返回一个对象实例,这个对象实际上是FactoryBean所管理的对象。当在Spring配置中声明一个FactoryBean时,Spring容器并不会直接使用FactoryBean实例,而是调用它的getObject()方法来获取真正的目标对象。
- 返回值不同:
- 通过BeanFactory获取的Bean是直接由BeanFactory管理的Bean实例。
- 通过FactoryBean获取的则是FactoryBean的getObject()方法返回的对象实例,而不是FactoryBean本身。如果想要访问FactoryBean本身,可以在获取Bean时在Bean名称前面加上&符号。
- 配置方式不同:
- BeanFactory的配置通常是在XML文件中定义的,可以通过ClassPathXmlApplicationContext、FileSystemXmlApplicationContext等来加载运行时资源。
- FactoryBean的配置则是将自定义工厂定义为Spring容器中的一个普通Bean,并在该对象上指定FactoryBean的实现类路径,由Spring容器创建并管理。
- 生命周期不同:
- BeanFactory在容器启动时不会预先实例化所有单例Bean,而是在使用者第一次调用getBean()方法时才会实例化并放入BeanFactory中。
- FactoryBean则可以实现Bean的生命周期接口,从而改变Bean的初始化和销毁过程,提供了更灵活的Bean管理方式。
22、Spring常用注解有哪些?
- @Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
- @Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的Bean会自动导入到IoC容器。
- @Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。
- @Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。
- @Required:这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出异常。
- @Autowired:默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
- @Resource:默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
- @Qualifier:当创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,可以使用@Qualifier 注解 和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。
- @RequestMapping:用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类/方法。
23、JdbcTemplate是什么?
JdbcTemplate是Spring框架中用于简化JDBC(Java数据库连接)操作的一个工具类。JdbcTemplate的灵活性较低,因为它是基于模板的,不支持动态SQL生成,可能导致SQL注入的风险。对于需要灵活配置和复杂查询的场景,MyBatis可能提供了更多的灵活性和性能优化选项。
- 作用:
- JdbcTemplate提供了一系列便捷的方法来执行SQL查询、更新、批处理等操作,同时处理了数据库连接的获取和关闭、异常处理等问题。使用JdbcTemplate可以显著减少样板代码,提高开发效率。
- 特点:
- 简化数据库操作:JdbcTemplate帮助我们处理很多繁琐的任务,例如打开和关闭数据库连接、处理SQL语句、处理异常等。
- 异常处理:将SQL异常转换为Spring的DataAccessException,便于统一处理。
- 资源管理:自动管理数据库连接和事务。
- 支持批量操作:提供了批量更新和批处理操作的方法。
- 性能对比:
- JdbcTemplate的查询速度通常比MyBatis快2-10倍,因为它直接使用SQL语句,避免了额外的对象关系映射(ORM)开销。
示例:
@Autowired
private JdbcTemplate jdbcTemplate;
public User getUserById(Long id) {
return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", new Object[]{id}, new UserRowMapper());
}
public void createUser(User user) {
jdbcTemplate.update("INSERT INTO users (name, email) VALUES (?, ?)", user.getName(), user.getEmail());
}
24、什么是AOP?
AOP(Aspect-Oriented Programming),即面向切面编程,是一种通过分离应用中的横切关注点(Cross-Cutting Concerns)来实现模块化的软件开发范式。它利用预编译方式和运行期动态代理,在不修改源代码的情况下,为程序动态统一添加某种特定功能,实现代码的解耦和复用。
25、AOP有哪些实现方式?
AOP(面向切面编程)的实现方式有多种,以下是几种常见的实现方式:
- Spring AOP实现方式:
- 使用Spring自带的AOP API接口:
- 创建实现了MethodBeforeAdvice、AfterReturningAdvice等接口的类,这些方法定义了增强逻辑。
- 在Spring配置文件中配置AOP切入点,并通过aop:advisor元素将增强逻辑与切入点关联起来。
- 自定义类结合XML配置:
- 创建一个包含增强逻辑的类。
- 在Spring的XML配置文件中,使用aop:aspect定义切面,并通过aop:before、aop:after等标签指定增强逻辑与切入点的关系。
- 使用注解方式:
- 在Spring配置文件中开启注解支持(aop:aspectj-autoproxy/)。
- 在切面类上使用@Aspect注解声明这是一个切面类。
- 在切面类的方法上使用@Before、@After、@Around等注解定义增强逻辑,并通过pointcut表达式指定切入点。
- 使用Spring自带的AOP API接口:
- AspectJ AOP:
- 在编译期或运行期通过修改字节码来实现切面的编织,可以更细粒度地控制切面的织入。
26、代理是什么?
Spring的代理是指在Spring框架中,通过创建一个代理对象来实现对目标对象的访问和控制。代理对象在访问目标对象之前或之后添加一些额外的逻辑,以实现对目标对象的增强或改变其行为。
在Spring框架中,代理主要用于实现AOP和事务处理等功能。代理对象在运行时动态生成,并注入到Spring容器中。当客户端调用目标对象的方法时,实际上是通过代理对象进行调用的。代理对象可以在调用目标对象的方法前后执行额外的逻辑,如日志记录、事务管理、安全检查等。
在Spring框架中,当第一次调用被代理对象的方法时,会根据AOP配置和选择的代理方式来生成代理对象。如果使用AspectJ的注解方式来配置AOP,Spring会在运行时使用AspectJ编译器生成代理对象。
27、代理的类型有哪些?
Spring提供了两种类型的代理,Spring框架默认会根据目标对象是否实现了接口来自动选择代理方式。如果目标对象实现了接口,则使用JDK动态代理;如果没有实现接口,则使用CGLIB代理。也可以在配置文件中手动指定使用哪种代理方式。
- 基于接口的代理(JDK动态代理):
- 这种代理方式要求目标对象必须实现一个接口。
- Spring使用JDK的动态代理技术来实现基于接口的代理。动态代理是在运行时生成代理类的字节码,并动态地加载到JVM中,实现对目标对象方法的代理。
- 基于类的代理(CGLIB代理):
- 代理对象继承了目标对象的类,并通过继承和重写来实现对目标对象的代理。
- Spring使用CGLIB(Code Generation Library)来实现基于类的代理。CGLIB是一个强大的、高性能的字节码生成库,它通过生成目标对象的子类来实现对目标对象的代理。
28、Spring AOP有哪些核心概念?
- 切面(Aspect):
- 切面是一个横切关注点的模块化,它封装了横切逻辑。切面里面包含了通知和切入点,可以看作是横切多个对象的功能,它将关注点与业务逻辑分离开来。
- 连接点(JoinPoint):
- 连接点表示程序执行过程中明确的点,比如方法的调用或执行。在Spring AOP中,连接点通常指的是方法的执行点。
- 切入点(Pointcut):
- 切入点用于定义哪些连接点将应用通知。它是一个表达式,用于匹配方法或类,从而确定在哪里应用切面。例如,execution(* com.example…*(…))表示匹配com.example包及其子包中的所有方法。
- 通知(Advice):
- 通知是切面在特定的连接点上执行的动作。它定义了切面“什么”以及“何时”执行。通知有多种类型,包括前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)等。
- 目标对象(Target Object):
- 目标对象是被一个或多个切面切入的对象,也就是切面最终作用的对象。它通常包含主业务逻辑类的对象。
- 代理对象(Proxy Object):
- Spring会通过代理的方式对目标对象生成代理对象。代理对象中包含了目标对象以及增强的功能(即切面中的通知)。通过代理对象间接地对目标对象起到增强的效果。
- 织入(Weaving):
- 织入是将切面应用到目标对象并创建代理对象的过程。在Spring AOP中,织入是在运行时通过代理机制完成的。
- 引入(Inter-type Declaration):
- 也称为内部类型声明,它为已有的类添加额外新的字段或方法。在Spring AOP中,可以通过引入为被代理对象添加新的接口或实现。