Spring 面试题【每日20道】【其一】
1、Spring 当中什么是循环依赖(常问)? 中等
在Spring框架中,循环依赖(Circular Dependency)是指两个或多个bean互相之间直接或间接地依赖对方的注入。例如:
- A bean依赖于B bean。
- B bean又依赖于A bean。
这种情况下,如果我们尝试创建这些beans,将会形成一个循环链,导致容器无法完成依赖注入的过程,因为每个bean都在等待另一个bean被完全初始化。
Spring IoC容器通过使用三级缓存来解决部分类型的循环依赖问题,它能够处理构造器注入之外的循环依赖。具体来说,Spring可以处理以下情况的循环依赖:
- A和B都是以setter方法进行属性注入(Field Injection或者Setter Injection),即非构造器注入。
- A是构造器注入,而B是属性注入。
但是,如果两个bean都使用构造器注入,那么Spring将无法解决这种循环依赖,并会抛出BeanCurrentlyInCreationException异常。
为了避免循环依赖带来的问题,开发者可以考虑重构代码,比如:
- 将共同的依赖提取到第三方类中。
- 使用提供者模式(Provider pattern),让bean在其需要的时候再获取依赖,而不是在初始化时就注入。
- 使用延迟加载(@Lazy注解),使得bean不是在启动时就立即初始化。
理解并正确管理循环依赖对于构建健壮、可维护的Spring应用程序非常重要。
2、Spring 如何解决循环依赖? 中等
Spring框架使用了一套复杂的机制来解决循环依赖的问题,特别是对于非构造器注入的bean(即通过字段注入或setter方法注入)。以下是Spring解决循环依赖的基本步骤和原理:
-
三级缓存:
Spring内部维护了三个缓存,用于存放不同状态的单例bean。这三个缓存分别是:singletonObjects
:存放完全初始化完成的bean实例。earlySingletonObjects
:存放提前曝光的尚未完成初始化的bean实例。singletonFactories
:存放bean的ObjectFactory工厂,用于创建提前曝光的bean实例。
-
处理过程:
当Spring容器遇到一个需要被创建的bean时,它会检查这个bean是否已经在singletonObjects
中存在。如果不存在,那么它会开始创建这个bean,并将bean的名字注册到正在创建的集合中以防止重复创建。 -
提前曝光:
在bean的初始化过程中(但还未完全初始化),如果发现有其他bean对当前bean有依赖,Spring会将当前bean的一个早期暴露版本(通常是未完成初始化的对象)放入earlySingletonObjects
缓存中,并从singletonFactories
移除对应的记录。这使得其他依赖于它的bean可以获取到这个bean的一个可用实例,从而打破循环引用。 -
完成初始化:
一旦bean完成其所有属性设置和初始化生命周期回调(如InitializingBean.afterPropertiesSet()
方法或自定义的init-method
),它就会从earlySingletonObjects
移动到singletonObjects
,并且在singletonFactories
中的条目也会被清除。 -
清理:
最后,任何在earlySingletonObjects
中的bean都会被转移到singletonObjects
,而singletonFactories
会被清空,确保所有的bean都是完全初始化的状态。
需要注意的是,上述机制适用于基于setter或字段注入的bean。对于纯构造器注入的bean,由于它们在构造函数中就要求所有依赖项都必须是已经准备好的,所以当出现循环依赖时,Spring无法解决并会抛出异常。
为了更好地管理和避免循环依赖问题,开发者应当遵循良好的设计原则,例如尽量减少组件之间的耦合度,重构代码结构等。
3、为什么 Spring 循环依赖需要三级缓存,二级不够吗? 中等
Spring的三级缓存设计是为了有效地处理循环依赖问题,同时确保bean在不同生命周期阶段的状态管理。让我们来探讨为什么需要三级缓存,而不仅仅是二级。
1. singletonObjects
(一级缓存)
这个缓存存放的是已经完全初始化并可以使用的单例bean实例。当一个bean被创建并且其所有的初始化过程(包括依赖注入、初始化方法调用等)都完成之后,它会被放入到这个缓存中。这是最直观的一级缓存,任何对已创建好的bean的请求都会直接从这里获取。
2. earlySingletonObjects
(二级缓存)
这个缓存存放的是提前曝光的bean实例,即这些bean尚未完成所有初始化工作,但是已经被创建,并且可以满足其他bean的依赖需求。这是因为,在某些情况下,bean可能需要在它的依赖完全解析之前就被引用。这种机制允许bean在未完全初始化的情况下被使用,从而解决了特定类型的循环依赖问题。
3. singletonFactories
(三级缓存)
这个缓存存放的是ObjectFactory对象,它用于创建提前曝光的bean实例。这看起来似乎是一个额外的复杂度,但其实是非常必要的。原因如下:
-
解决并发问题:在多线程环境中,多个线程可能会同时尝试获取同一个尚未初始化的bean。通过
singletonFactories
,Spring可以保证即使有多个线程试图获取同一个bean,也只会有一个线程真正创建该bean,其余线程则会等待直到这个bean被创建出来。 -
避免重复创建:如果仅使用两级缓存,在某些情况下可能会导致bean被重复创建的问题。例如,如果A依赖于B,而B又依赖于A,那么在A和B都在初始化的过程中,如果没有适当的机制来跟踪bean的创建状态,就可能导致A或B被多次创建。
singletonFactories
提供了一种方式来避免这种情况的发生。 -
支持延迟加载:对于那些标记为延迟加载的bean(@Lazy),它们只有在第一次被请求时才会被创建。
singletonFactories
可以帮助Spring实现这一点,因为它可以在实际需要的时候才创建bean,而不是在容器启动时就创建。
因此,三级缓存的设计不仅有助于解决循环依赖的问题,还增强了Spring IoC容器的健壮性和灵活性,使得它可以更好地应对复杂的场景,比如高并发环境下的bean创建和延迟加载的需求。通过这种方式,Spring能够在保证bean正确性的同时,尽可能地提高性能和响应速度。
4、看过源码吗?说下 Spring 由哪些重要的模块组成? 中等
是的,Spring框架是一个模块化设计的企业级应用开发框架,它由多个核心模块和一些辅助模块组成。以下是Spring框架中一些重要的模块:
1. Spring Core(核心容器)
- Beans Module:提供了BeanFactory接口,它是工厂模式的经典实现,用于管理不同类型的bean。
- Core Module:提供了框架的基本组成部分,包括IoC(控制反转)和DI(依赖注入)功能。
- Context Module:构建于Core和Beans模块之上,提供了一种框架式的方式去访问对象。ApplicationContext接口是这个模块的关键部分,它扩展了BeanFactory的功能,添加了对国际化、事件传播、资源加载的支持以及对其他框架如JNDI、EJB、ORM工具等的支持。
2. Spring AOP(面向切面编程)
- 提供了面向切面编程的支持,允许定义方法拦截器和切入点,从而可以将横切关注点(例如事务管理、日志记录)从商业逻辑中分离出来。
3. Spring DAO(数据访问/集成)
- 提供了一个JDBC抽象层,消除了冗长的JDBC编码和对数据库厂商错误代码的解析。此外,还支持与Hibernate、JPA、JDO等持久层框架的集成。
4. Spring ORM(对象关系映射)
- 提供了与流行的ORM框架(如Hibernate、JPA、MyBatis等)集成的支持,通过这些集成,开发者可以在不改变现有代码的情况下使用Spring的数据访问特性。
5. Spring Web
- Web Module:提供了基础的Web开发功能,比如文件上传等功能,并且整合了Servlet API。
- Spring MVC:实现了MVC(模型-视图-控制器)架构模式,为Web应用程序提供了轻量级的开发模型,使得创建可维护的Web应用程序变得更加容易。
- WebSocket Module:提供了对WebSocket协议的支持,使得服务器和客户端之间可以进行双向通信。
6. Spring Test
- 提供了对单元测试和集成测试的支持,包括与JUnit或TestNG框架的集成,以及模拟对象的支持。
7. Spring Security
- 虽然不是核心模块的一部分,但Spring Security是官方推荐的安全模块,提供了认证和授权机制,保护基于Spring的应用程序免受安全威胁。
8. Spring Boot
- 不是传统意义上的模块,而是一个用来简化新Spring应用初始搭建以及开发过程的项目。它包含了自动配置、起步依赖等特性,极大地提高了开发效率。
9. Spring Cloud
- 也非传统模块,而是为了帮助开发者快速构建分布式系统的一系列工具和服务的集合,比如配置管理、服务发现、断路器、路由、微代理、控制总线等。
每个模块都有其特定的角色和责任,它们共同构成了一个强大且灵活的企业级应用开发平台。如果你有兴趣深入了解某个特定模块或者想了解具体的源码细节,请告诉我,我可以为你提供更详细的信息。
5、什么是 Spring IOC? 中等
Spring IOC(Inversion of Control,控制反转)是Spring框架的核心特性之一,它是一种设计原则,用于解耦软件组件之间的依赖关系。在传统的编程中,一个类通常会自己创建和管理其依赖的对象,这种方式导致了高度的耦合性。而通过使用IoC,对象不再直接创建或查找它们所依赖的对象,而是由外部容器负责管理和注入这些依赖。
Spring IoC容器
Spring的IoC容器是实现控制反转机制的地方,它主要负责实例化、配置和组装应用程序中的对象。容器通过读取配置元数据来了解如何将不同的bean装配在一起。这种配置可以采用XML文件、注解或Java代码的形式提供。
两个关键接口:
- BeanFactory:这是Spring IoC容器的基础形式,提供了基本的IoC功能。它是工厂模式的经典实现,用来管理不同类型的bean,并提供了一种方式去获取被管理的bean。
- ApplicationContext:是BeanFactory的子接口,除了提供BeanFactory的所有功能外,还添加了对国际化(i18n)、事件传播、资源加载的支持以及对其他框架如JNDI、EJB、ORM工具等的支持。因此,在大多数情况下,推荐使用
ApplicationContext
而不是BeanFactory
。
控制反转的方式
控制反转可以通过三种方式来实现:
- 构造器注入(Constructor Injection):
- 在对象创建时,通过构造函数参数传递依赖。
- Setter方法注入(Setter Injection):
- 使用setter方法为对象设置依赖属性。
- 接口注入(Interface Injection)(较少使用):
- 定义一个接口来设定依赖关系,然后通过该接口的方法来注入依赖。
主要优点
- 降低耦合度:由于对象不再需要自己管理依赖,所以减少了对象之间的直接联系。
- 提高可测试性:因为依赖是由外部提供的,所以更容易编写单元测试。
- 简化编码:开发者只需要关注业务逻辑,而不必关心如何初始化和配置依赖。
总之,Spring的IoC容器帮助开发者更好地组织和管理应用组件,促进了松耦合的设计,使得应用程序更加模块化、易于维护和扩展。
6、Spring IOC 有什么好处? 中等
Spring的IoC(Inversion of Control,控制反转)容器为应用程序开发带来了许多显著的好处,这些好处主要体现在提高代码质量、增强应用的灵活性和可维护性等方面。以下是Spring IoC的主要优势:
1. 降低耦合度
通过将对象之间的依赖关系交由IoC容器管理,而不是在代码中硬编码,这大大降低了组件间的耦合度。组件不再需要直接实例化或查找它们所依赖的对象,而是被动地接收这些依赖。因此,组件可以更加独立,更易于单独测试和维护。
2. 增强模块化
由于减少了对象之间的直接依赖,应用程序可以被拆分为多个独立的模块。每个模块负责特定的功能,并且可以通过配置文件或注解轻松地与其他模块集成。这种设计使得系统的结构更加清晰,也便于团队协作开发。
3. 简化配置
使用Spring的IoC容器,你可以通过简单的XML配置文件、注解或者Java配置类来定义bean及其依赖关系,而不需要修改源代码。这种方式不仅简化了配置过程,还提高了配置的灵活性,因为可以在不改变代码的情况下调整应用的行为。
4. 支持AOP
Spring框架内置了对面向切面编程(AOP)的支持,它允许你将横切关注点(如日志记录、事务管理等)与业务逻辑分离。通过IoC容器,你可以轻松地将这些通用功能应用于多个bean,而无需更改它们的源代码。
5. 提高可测试性
依赖注入让单元测试变得更加容易。你可以很容易地用模拟对象(mock objects)替换真实的服务,从而专注于测试单个组件的功能。此外,Spring提供了丰富的测试支持工具,如@RunWith(SpringRunner.class)
和@ContextConfiguration
等注解,进一步简化了测试工作。
6. 生命周期管理和资源清理
IoC容器不仅负责创建和组装bean,还管理着它们的生命周期。它可以在适当的时候初始化和销毁bean,并处理相关资源的释放,比如关闭数据库连接等。这有助于确保应用程序的安全性和稳定性。
7. 环境无关性
Spring的IoC容器可以方便地适应不同的部署环境。例如,你可以根据不同的环境(开发、测试、生产)提供不同的配置文件,而不需要改变应用程序的代码。这有助于实现“一次编写,到处运行”的理念。
8. 促进良好的设计实践
IoC鼓励采用依赖注入原则,这是一种优秀的软件工程实践,它促进了松耦合、高内聚的设计模式。遵循这样的设计原则,可以帮助开发者构建出更加健壮和可扩展的应用程序。
综上所述,Spring的IoC容器不仅仅是一个简单工厂模式的实现,它为现代企业级应用开发提供了一套完整的解决方案,旨在提升开发效率、改善代码质量和增强系统的可维护性。
7、Spring 中的 DI 是什么? 中等
在Spring框架中,DI(Dependency Injection,依赖注入)是IoC(Inversion of Control,控制反转)的一种具体实现方式。它是一种设计模式,用于将一个对象的依赖关系从代码内部转移到外部进行配置和管理,从而解耦组件之间的直接依赖。通过DI,开发者不再需要在代码中硬编码依赖关系,而是可以将这些依赖通过构造函数、setter方法或接口由外部容器(如Spring IoC容器)来提供。
DI的主要形式
Spring支持三种主要的依赖注入方式:
-
构造器注入(Constructor Injection):
- 通过构造函数参数传递依赖。这种方式通常用于必须的依赖项,并且一旦设置后不可更改。
-
Setter方法注入(Setter Injection):
- 使用setter方法为对象设置依赖属性。这种方式适用于可选的依赖项,允许在创建对象之后再设置依赖。
-
字段注入(Field Injection):
- 直接在类的字段上使用注解(如@Autowired)来注入依赖。这种方式虽然简洁,但不如前两种方式灵活,因为它使得测试变得更加困难,并且违反了编程到接口的原则。
-
接口注入(Interface Injection)(较少使用):
- 定义一个接口来设定依赖关系,然后通过该接口的方法来注入依赖。这种方式在Spring中并不常见。
DI的优点
- 降低耦合度:对象不再负责创建自己的依赖,这减少了它们之间的直接联系,使得组件更加独立。
- 提高可测试性:因为依赖是由外部提供的,所以更容易编写单元测试,可以通过传入模拟对象(mock objects)来测试组件的行为。
- 简化编码:开发者只需要关注业务逻辑,而不需要关心如何初始化和配置依赖。
- 促进良好的设计实践:鼓励采用面向接口编程和依赖注入原则,有助于构建松耦合、高内聚的设计。
DI与IoC的关系
DI是IoC的一种实现方式,即通过依赖注入实现了控制反转。在没有DI的情况下,对象自己负责管理和查找其依赖,这就导致了紧耦合。而使用DI时,对象被动接收依赖,这种变化就是控制反转的本质,即控制权从应用代码转移到了外部容器。
实现DI的方式
在Spring中,你可以通过XML配置文件、注解(如@Autowired
、@Resource
、@Inject
等)或者Java配置类来定义bean及其依赖关系。Spring IoC容器会根据这些配置自动完成依赖注入过程。
总之,DI是Spring框架中的一个关键特性,它极大地促进了应用程序的模块化、可测试性和灵活性。
8、什么是 Spring Bean? 中等
在Spring框架中,Bean是构成Spring应用程序的基本构建块。它们是由Spring IoC容器管理的对象,通常代表了应用中的组件或服务。Bean是基于Java类创建的,并且可以通过配置元数据(如XML、注解或Java配置类)来定义和组装这些对象。
Spring Bean的主要特性
-
实例化(Instantiation):
- 当Spring容器启动时,它会根据配置信息来实例化bean。这可以通过多种方式完成,比如使用默认构造函数、带参数的构造函数或者工厂方法。
-
配置(Configuration):
- Bean的配置包括设置属性值、指定依赖关系等。这可以是通过XML文件、注解(如
@Component
、@Service
、@Repository
、@Controller
)、或Java配置类(使用@Configuration
和@Bean
注解)来完成的。
- Bean的配置包括设置属性值、指定依赖关系等。这可以是通过XML文件、注解(如
-
依赖注入(Dependency Injection, DI):
- Spring容器负责解析并注入bean所依赖的其他bean。依赖可以是通过构造器、setter方法或字段直接注入。
-
生命周期管理(Lifecycle Management):
- Spring容器不仅管理bean的创建,还控制其整个生命周期。从初始化到销毁,容器都可以执行特定的方法或回调函数。例如,
@PostConstruct
用于初始化后调用,@PreDestroy
用于销毁前调用。
- Spring容器不仅管理bean的创建,还控制其整个生命周期。从初始化到销毁,容器都可以执行特定的方法或回调函数。例如,
-
作用域(Scope):
- 每个bean都有一个明确的作用域,决定了它在应用中的存在范围。常见的作用域有:
singleton
:默认作用域,意味着在整个应用程序上下文中只有一个bean实例。prototype
:每次请求都会创建一个新的bean实例。request
、session
、application
、websocket
:这些作用域与Web应用相关,分别对应于HTTP请求、HTTP会话、Servlet上下文和WebSocket会话。
- 每个bean都有一个明确的作用域,决定了它在应用中的存在范围。常见的作用域有:
-
AOP支持(Aspect-Oriented Programming Support):
- Spring允许对bean应用切面(Aspects),从而将横切关注点(如事务管理、日志记录等)与业务逻辑分离。
-
事件机制(Event Mechanism):
- Spring提供了事件发布/订阅模型,使得bean之间可以进行松耦合的通信。你可以通过监听特定类型的事件来进行响应处理。
-
自动装配(Auto-Wiring):
- 通过
@Autowired
等注解,Spring可以根据类型或名称自动发现并注入合适的bean依赖,减少了显式配置的工作量。
- 通过
定义Bean的方式
-
XML配置:在传统的Spring项目中,开发者会在XML文件中定义bean及其依赖关系。
-
注解配置:现代的Spring项目更倾向于使用注解来简化配置。例如,
@Component
、@Service
、@Repository
和@Controller
注解用于标记普通的组件、服务层组件、持久层组件和表现层组件;而@Autowired
则用来标识需要被注入的依赖。 -
Java配置:使用
@Configuration
和@Bean
注解编写Java类来定义bean,这种方式提供了更加类型安全和面向编程语言特性的配置选项。
总之,Spring Bean是Spring应用程序的核心组成部分,它们由Spring容器管理,并通过依赖注入相互协作,以实现复杂的应用功能。了解如何正确地配置和使用bean对于充分利用Spring框架至关重要。
9、Spring 中的 BeanFactory 是什么? 中等
BeanFactory
是Spring框架中一个核心接口,它定义了一个高级的IoC(Inversion of Control,控制反转)容器,用于管理bean的创建和生命周期。它是Spring IoC容器的基础形式,提供了基本的依赖注入功能。通过BeanFactory
,你可以加载bean定义、配置对象以及管理bean之间的依赖关系。
BeanFactory的主要特点
-
延迟初始化:
BeanFactory
采用的是懒加载模式,即只有在获取某个bean时才会实例化该bean。这意味着资源消耗会更加有效,因为不需要预先加载所有bean。 -
轻量级:相比于
ApplicationContext
,BeanFactory
更为轻量级,因为它不提供某些额外的功能如国际化支持、事件传播等。 -
灵活的配置方式:
BeanFactory
可以通过多种方式配置bean,包括XML文件、Java代码中的注解或编程式地添加bean定义。 -
手动管理bean的生命周期:与
ApplicationContext
不同,BeanFactory
不会自动调用初始化后的方法(例如@PostConstruct
)或者销毁前的方法(例如@PreDestroy
)。开发者需要显式地调用相应的方法来管理bean的生命周期。 -
细粒度控制:对于那些希望对容器有更多的控制权的应用程序来说,
BeanFactory
提供了更精细的操作选项,例如直接访问底层的BeanDefinitionRegistry
接口来进行bean定义的注册。
使用场景
-
资源受限环境:由于其轻量级特性,
BeanFactory
适合于资源有限的环境,比如移动设备或嵌入式系统。 -
性能敏感应用:如果应用程序非常关注启动时间和内存占用,那么使用
BeanFactory
可能会是一个更好的选择,因为它避免了ApplicationContext
带来的额外开销。
示例代码
以下是一个简单的例子,展示了如何使用BeanFactory
:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class MainApp {
public static void main(String[] args) {
// 加载Spring配置文件
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
// 获取名为'myBean'的bean
MyBean myBean = (MyBean) factory.getBean("myBean");
// 调用bean的方法
myBean.printMessage();
}
}
请注意,在现代Spring应用开发中,BeanFactory
通常不是首选,因为大多数情况下推荐使用ApplicationContext
,它不仅包含了BeanFactory
的所有功能,还提供了更多的企业级特性。然而,在某些特定的情况下,了解并能够使用BeanFactory
仍然是有价值的。
10、Spring 中的 FactoryBean 是什么? 中等
FactoryBean
是Spring框架中的一个特殊接口,它允许开发者自定义bean的创建逻辑。通常情况下,Spring容器根据配置(如XML文件、注解或Java配置类)来实例化和管理bean。然而,有时候你可能需要更复杂的初始化逻辑,这时就可以使用FactoryBean
来控制bean的创建过程。
FactoryBean的主要用途
-
复杂对象的创建:当一个对象的创建过程较为复杂,不能简单地通过构造函数或setter方法完成时,可以实现
FactoryBean
接口来自定义创建逻辑。 -
返回代理对象:如果希望返回的是某个对象的代理而不是原始对象本身,比如在AOP(面向切面编程)中创建动态代理对象,
FactoryBean
是一个很好的选择。 -
封装第三方库的对象创建:当你想要将第三方库的对象集成到Spring应用中,并且这些对象的创建过程不遵循标准的Spring bean生命周期时,可以使用
FactoryBean
来适配。 -
延迟加载或按需创建:对于那些不需要立即创建的bean,可以通过
FactoryBean
实现懒加载或按需创建的行为。
FactoryBean接口的方法
要实现FactoryBean
接口,你需要至少实现以下三个方法:
-
Object getObject()
throws Exception;- 返回由工厂创建的实际对象实例。这个方法是核心所在,定义了如何创建bean。
-
Class<?> getObjectType()
;- 返回
getObject()
方法所创建对象的类型。这有助于Spring容器了解即将返回的对象类型,从而优化内部处理。
- 返回
-
boolean isSingleton()
;- 指示由
getObject()
返回的对象是否为单例模式。如果返回true
,则意味着在整个应用程序上下文中只有一个该类型的bean实例;如果是false
,则每次请求都会创建一个新的实例。
- 指示由
示例代码
下面是一个简单的例子,展示了如何实现FactoryBean
接口:
import org.springframework.beans.factory.FactoryBean;
public class MyBeanFactory implements FactoryBean<MyBean> {
private String property;
// 可选:提供setter方法用于注入属性
public void setProperty(String property) {
this.property = property;
}
@Override
public MyBean getObject() throws Exception {
// 实现复杂的对象创建逻辑
return new MyBean(property);
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
@Override
public boolean isSingleton() {
// 根据实际情况返回true或false
return true;
}
}
配置与使用
假设我们已经实现了上述MyBeanFactory
类,接下来可以在Spring配置文件中注册它作为bean,并像使用普通bean一样引用它。需要注意的是,在配置文件中指定FactoryBean
时,bean的ID或名称应当直接指向FactoryBean
本身,而Spring容器会自动调用其getObject()
方法来获取实际的bean实例。
例如,在XML配置中:
<bean id="myBean" class="com.example.MyBeanFactory">
<property name="property" value="someValue"/>
</bean>
或者使用注解配置:
@Configuration
public class AppConfig {
@Bean
public MyBeanFactory myBeanFactory() {
MyBeanFactory factory = new MyBeanFactory();
factory.setProperty("someValue");
return factory;
}
}
然后你可以像这样从Spring容器中获取MyBean
:
@Autowired
private MyBean myBean;
在这种情况下,Spring会识别出myBean
实际上是由MyBeanFactory
创建的对象,而不是MyBeanFactory
本身。
总之,FactoryBean
为Spring提供了极大的灵活性,使得我们可以更加精细地控制bean的创建过程,特别是在面对非标准对象创建需求时非常有用。
11、Spring 中的 ObjectFactory 是什么? 中等
ObjectFactory
是Spring框架中的一个接口,它提供了一种延迟获取对象的方式。与直接使用BeanFactory
或ApplicationContext
不同的是,ObjectFactory
允许你按需创建对象实例,而不是在容器启动时就立即创建所有bean。这有助于提高性能和资源利用率,特别是在处理那些只在特定情况下才需要的对象时。
ObjectFactory的主要特点
-
延迟加载:只有当调用
getObject()
方法时,才会创建并返回对象实例。这种方式可以避免不必要的初始化开销,并且适合于那些可能永远不会被使用的对象。 -
轻量级:相比于完整的
BeanFactory
或ApplicationContext
,ObjectFactory
更加轻量级,因为它只负责创建单一类型的对象。 -
灵活性:你可以将
ObjectFactory
注入到其他bean中,使得这些bean能够在运行时根据需要动态地创建依赖对象,而不需要在构造函数或setter方法中提前定义好这些依赖。 -
非单例模式支持:虽然大多数Spring管理的bean默认是单例的,但通过
ObjectFactory
,你可以很容易地实现每次调用都返回新实例的行为(即非单例模式),而不必改变bean的作用域配置。
使用场景
-
懒加载组件:对于那些不是应用程序启动时必须的对象,使用
ObjectFactory
可以在它们真正需要的时候再进行初始化。 -
条件性依赖:如果某个bean的依赖项仅在某些条件下才需要,那么可以通过
ObjectFactory
来推迟依赖项的创建直到确实需要为止。 -
原型作用域的bean:当你需要每次请求都获得一个新的bean实例时,
ObjectFactory
是一个很好的选择。
示例代码
下面是一个简单的例子,展示了如何使用ObjectFactory
:
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyComponent {
private final ObjectFactory<MyBean> myBeanFactory;
public MyComponent(ObjectFactory<MyBean> myBeanFactory) {
this.myBeanFactory = myBeanFactory;
}
public void useMyBean() {
// 按需创建MyBean实例
MyBean myBean = myBeanFactory.getObject();
myBean.doSomething();
}
}
// 配置类
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
@Bean
public MyComponent myComponent(ConfigurableBeanFactory beanFactory) {
// 传递ObjectFactory给MyComponent
return new MyComponent(beanFactory.getBeanProvider(MyBean.class));
}
}
在这个例子中,MyComponent
类接受一个ObjectFactory<MyBean>
作为构造参数。每当需要MyBean
实例时,它都会调用myBeanFactory.getObject()
方法来创建新的实例。注意,在配置类AppConfig
中,我们使用了ConfigurableBeanFactory.getBeanProvider()
方法来获取ObjectFactory
实例。
在Spring应用中的集成
为了更好地理解ObjectFactory
的工作原理,这里是如何在一个典型的Spring应用中使用它的完整示例:
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
@Bean
public MyComponent myComponent(ObjectFactory<MyBean> myBeanFactory) {
return new MyComponent(myBeanFactory);
}
}
public class MyComponent {
private final ObjectFactory<MyBean> myBeanFactory;
public MyComponent(ObjectFactory<MyBean> myBeanFactory) {
this.myBeanFactory = myBeanFactory;
}
public void useMyBean() {
// 每次调用时都会创建一个新的MyBean实例
MyBean myBean = myBeanFactory.getObject();
myBean.doSomething();
}
}
public class MyBean {
public void doSomething() {
System.out.println("Doing something...");
}
}
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyComponent component = context.getBean(MyComponent.class);
component.useMyBean(); // 第一次调用,创建并使用MyBean
component.useMyBean(); // 第二次调用,再次创建并使用MyBean
}
}
在这个例子中,MainApp
类通过AnnotationConfigApplicationContext
加载配置类AppConfig
,然后从上下文中获取MyComponent
实例。每当调用useMyBean()
方法时,都会通过ObjectFactory
创建一个新的MyBean
实例。
总之,ObjectFactory
为Spring应用提供了创建对象的灵活性和延迟加载的能力,特别适用于那些不需要立即初始化或者仅在特定条件下才需要的对象。
12、Spring 中的 ApplicationContext 是什么? 中等
ApplicationContext
是Spring框架中的核心接口之一,它扩展了BeanFactory
的功能,提供了一种更高级的IoC容器实现。除了基本的依赖注入功能外,ApplicationContext
还增加了许多企业级特性,使得它成为大多数Spring应用程序的首选容器。
ApplicationContext的主要特点
-
支持国际化(i18n):
ApplicationContext
可以管理资源包,从而支持多语言和区域设置的应用程序。
-
事件传播机制:
- 它实现了观察者模式,允许bean监听并响应来自其他bean发布的事件。这对于构建松耦合的应用程序非常有用。
-
自动装配和注解驱动配置:
- 支持通过注解(如
@Autowired
、@Component
等)来简化bean的定义和依赖注入,而不需要大量的XML配置。
- 支持通过注解(如
-
应用层特定的上下文:
- 提供了针对不同应用场景的上下文实现类,例如
WebApplicationContext
用于Web应用,AnnotationConfigApplicationContext
用于基于注解或Java配置的应用。
- 提供了针对不同应用场景的上下文实现类,例如
-
加载多个配置源:
- 可以同时从多个位置加载配置文件,包括文件系统、类路径下的资源以及URL资源。
-
生命周期回调:
ApplicationContext
会自动调用指定的初始化后和销毁前的方法(如@PostConstruct
和@PreDestroy
),简化了bean的生命周期管理。
-
AOP支持:
- 内置了对面向切面编程的支持,使得开发者可以轻松地将横切关注点(如事务管理、日志记录等)与业务逻辑分离。
-
集成第三方库和服务:
- 提供了丰富的工具集来方便地集成各种外部服务和库,比如JNDI查找、EJB集成、ORM框架等。
-
环境感知:
- 通过
Environment
抽象,ApplicationContext
能够感知当前运行时环境,并据此调整配置。
- 通过
使用场景
-
企业级应用开发:由于其丰富的企业级特性,
ApplicationContext
非常适合用于大型复杂的应用程序开发。 -
Web应用:对于基于Spring MVC的Web应用来说,
WebApplicationContext
是不可或缺的一部分,它提供了对Servlet上下文的访问和支持。 -
测试环境:在编写单元测试或集成测试时,
GenericApplicationContext
及其子类可以帮助快速搭建测试用的Spring容器。
示例代码
下面是一个简单的例子,展示了如何使用AnnotationConfigApplicationContext
:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.ApplicationContext;
public class MainApp {
public static void main(String[] args) {
// 创建并启动Spring容器
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取bean实例
MyService myService = context.getBean(MyService.class);
// 使用bean
myService.doSomething();
// 关闭容器(可选)
((AnnotationConfigApplicationContext) context).close();
}
}
// 配置类
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
interface MyService {
void doSomething();
}
class MyServiceImpl implements MyService {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
在这个例子中,MainApp
类创建了一个AnnotationConfigApplicationContext
实例,并通过它来获取和使用MyService
bean。AppConfig
类作为配置类,使用@Configuration
和@Bean
注解来定义bean。
总之,ApplicationContext
为Spring应用程序提供了强大的功能和灵活性,使其成为了现代Java企业级应用开发的重要组成部分。理解如何正确配置和使用ApplicationContext
对于充分利用Spring框架至关重要。
13、Spring Bean 一共有几种作用域? 简单
在Spring框架中,Bean的作用域决定了bean实例的生命周期和可见性。Spring提供了多种作用域来适应不同的应用场景。以下是Spring Bean的几种主要作用域:
1. Singleton(单例)
- 默认作用域:这是Spring中最常用的作用域。
- 行为:在整个应用程序上下文中只有一个bean实例,所有对这个bean的请求都会返回同一个对象引用。
- 适用场景:当需要确保一个类只有一个实例,并且该实例可以在整个应用中共享时使用。
2. Prototype(原型)
- 行为:每次从容器中获取bean时都会创建一个新的实例。这意味着每个依赖注入或通过
getBean()
方法请求该bean的地方都会得到不同的对象。 - 适用场景:适用于那些状态不是线程安全的对象,或者你确实需要为每个请求提供独立的bean实例的情况。
3. Request(请求)
- Web环境特有:仅在基于Web的Spring ApplicationContext中有意义。
- 行为:对于每个HTTP请求都会创建一个新的bean实例,而在请求结束时,bean将被销毁。
- 适用场景:适合用于与特定HTTP请求相关的数据处理。
4. Session(会话)
- Web环境特有:同样只在基于Web的Spring ApplicationContext中有效。
- 行为:在一个HTTP Session期间,只会存在一个bean实例。一旦会话结束,bean也会被销毁。
- 适用场景:适用于存储与用户会话有关的信息。
5. Global Session(全局会话)
- Web环境特有:主要用于Portlet应用,因为Portlets可以有多个窗口,但它们共享同一个全局会话。
- 行为:在整个portlet上下文内,对于每个global HTTP session,bean只会有一个实例。
- 适用场景:在Portlet环境中管理跨窗口共享的数据。
6. Application(应用程序)
- Web环境特有:也是针对Web应用的。
- 行为:在整个Servlet上下文中,bean只有一个实例,类似于singleton,但它是在ServletContext级别而不是ApplicationContext级别。
- 适用场景:当需要在Web应用范围内共享资源时使用。
配置作用域
你可以通过XML配置文件、注解或Java配置类来指定bean的作用域。例如:
-
XML配置:
<bean id="myBean" class="com.example.MyBean" scope="prototype"/>
-
注解配置:
@Component @Scope("request") public class MyBean { // ... }
-
Java配置:
@Configuration public class AppConfig { @Bean @Scope("session") public MyBean myBean() { return new MyBean(); } }
注意事项
- 非Web环境:如果你的应用程序不是Web应用程序,则只有
singleton
和prototype
这两种作用域是可用的。 - 作用域的选择:选择合适的作用域对于确保应用程序的行为符合预期非常重要。错误地选择了作用域可能会导致意想不到的问题,比如多线程环境下不正确的共享状态。
总之,Spring提供的多种bean作用域使得开发者可以根据具体需求灵活地控制bean的生命周期和可见性,从而构建出更加健壮和可维护的应用程序。
14、Spring 一共有几种注入方式? 中等
在Spring框架中,依赖注入(Dependency Injection, DI)是实现控制反转(Inversion of Control, IoC)的关键机制之一。它允许将对象的依赖关系从代码内部转移到外部进行配置和管理,从而解耦组件之间的直接依赖。Spring支持多种注入方式,主要分为三种类型:
1. 构造器注入(Constructor Injection)
-
特点:通过构造函数参数传递依赖。
-
适用场景:通常用于必须的依赖项,并且一旦设置后不可更改。这种方式确保了bean在其生命周期内始终拥有其所需的依赖。
-
优点:
- 更加符合面向对象设计原则,因为依赖被声明为构造函数参数,使得类更加不可变。
- 有助于编写更易测试的代码,因为可以通过构造函数直接传入模拟对象。
-
示例代码:
public class MyService { private final MyRepository myRepository; // 构造器注入 @Autowired public MyService(MyRepository myRepository) { this.myRepository = myRepository; } // ... }
2. Setter方法注入(Setter Injection)
-
特点:使用setter方法为对象设置依赖属性。
-
适用场景:适用于可选的依赖项,允许在创建对象之后再设置依赖。
-
优点:
- 提供了更大的灵活性,可以在对象创建后再修改依赖。
- 对于某些遗留系统或需要兼容性的场景更为友好。
-
示例代码:
public class MyService { private MyRepository myRepository; // Setter方法注入 @Autowired public void setMyRepository(MyRepository myRepository) { this.myRepository = myRepository; } // ... }
3. 字段注入(Field Injection)
-
特点:直接在类的字段上使用注解(如
@Autowired
)来注入依赖。 -
适用场景:这种方式虽然简洁,但不如前两种方式灵活,因为它使得测试变得更加困难,并且违反了编程到接口的原则。
-
优点:
- 简洁明了,减少了样板代码。
-
缺点:
- 不利于单元测试,因为无法轻松地替换依赖。
- 降低了代码的可读性和维护性。
-
示例代码:
public class MyService { // 字段注入 @Autowired private MyRepository myRepository; // ... }
4. 接口注入(Interface Injection)(较少使用)
- 特点:定义一个接口来设定依赖关系,然后通过该接口的方法来注入依赖。这种方式在Spring中并不常见,也不推荐使用。
- 适用场景:主要用于历史原因或特定框架的需求。
注解驱动的自动装配
除了上述三种主要的注入方式外,Spring还提供了基于注解的自动装配功能,例如@Autowired
、@Resource
、@Inject
等,它们可以与构造器、setter方法或字段结合使用,简化了依赖注入的过程。
示例配置
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
@Bean
public MyRepository myRepository() {
return new MyRepositoryImpl();
}
}
在这个例子中,AppConfig
类使用Java配置的方式定义了两个bean——myService
和myRepository
。当这些bean被注入到其他组件时,可以根据选择的注入方式进行配置。
总之,Spring提供的多种注入方式为开发者提供了极大的灵活性,可以根据具体需求选择最适合的方式来管理bean之间的依赖关系。尽管字段注入因其简洁性而受到一些开发者的青睐,但从长远来看,构造器注入和setter方法注入往往能带来更好的代码质量和可维护性。
15、什么是 Spring AOP? 中等
Spring AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架提供的一个模块,它允许开发者将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。所谓横切关注点是指那些在多个业务功能中重复出现的功能,比如日志记录、事务管理、安全检查等。通过AOP,这些功能可以被集中定义在一个地方,并以声明式的方式应用到整个应用程序的不同部分,而不需要直接修改原有的业务代码。
AOP的核心概念
-
Aspect(切面):
- 一个模块化的组件,包含了通知(advice)和切入点(pointcut),用于实现横切关注点。每个方面都封装了特定的非功能性需求,如事务处理或日志记录。
-
Join Point(连接点):
- 程序执行过程中的某个点,例如方法调用、异常抛出或者字段访问。Spring AOP主要关注的是方法级别的连接点。
-
Pointcut(切入点):
- 定义了哪些连接点应该被拦截下来,并应用相应的通知。它可以是一个简单的表达式,也可以是更复杂的匹配规则。
-
Advice(通知/增强处理):
- 在特定的连接点上执行的动作。根据执行时机不同,分为以下几种类型:
- Before advice(前置通知):在目标方法调用之前执行。
- After returning advice(后置返回通知):在目标方法成功完成后执行。
- After throwing advice(异常抛出通知):当目标方法抛出异常时执行。
- After (finally) advice(最终通知):无论目标方法是否正常结束或抛出异常都会执行。
- Around advice(环绕通知):包围目标方法调用,在方法调用前后都可以添加自定义行为。
- 在特定的连接点上执行的动作。根据执行时机不同,分为以下几种类型:
-
Introduction(引入):
- 允许向现有的类添加新的方法或属性,即使这些类没有实现某些接口。这是一种特殊的增强方式。
-
Weaving(织入):
- 将切面与其他对象连接起来并创建新的代理对象的过程。这可以在编译期、加载期或运行期发生。
Spring AOP的特点
-
基于代理的实现:Spring AOP使用JDK动态代理或CGLIB库来创建代理对象。对于实现了接口的bean,默认使用JDK动态代理;而对于没有接口的bean,则使用CGLIB进行子类化。
-
易于集成:由于Spring本身就是一个全面的企业级应用开发框架,因此AOP与Spring的其他特性(如IoC容器、事务管理等)无缝集成。
-
配置简便:可以通过XML配置文件或注解(如
@Aspect
、@Before
、@AfterReturning
等)轻松定义切面和通知。 -
性能开销较小:相比于AspectJ这样的编译时织入工具,Spring AOP是在运行时通过代理机制实现的,所以它的性能影响相对较小。
使用场景
- 事务管理:确保数据库操作要么全部完成,要么全部回滚,保证数据的一致性。
- 日志记录:在方法调用前记录输入参数,在方法返回后记录输出结果,方便调试和问题追踪。
- 权限验证:在访问敏感资源之前检查用户是否有足够的权限。
- 性能监控:测量方法执行时间,收集统计信息,帮助优化系统性能。
示例代码
下面是一个简单的例子,展示了如何使用Spring AOP来实现日志记录:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 定义一个切入点,匹配所有以"service"结尾的服务层方法
@Pointcut("execution(* *..*Service.*(..))")
public void serviceMethods() {}
// 前置通知,在匹配的方法调用之前打印日志
@Before("serviceMethods()")
public void logBefore() {
System.out.println("Logging before method execution...");
}
}
在这个例子中,LoggingAspect
类使用了@Aspect
注解来标识它是一个切面,并且定义了一个名为serviceMethods
的切入点,该切入点匹配所有以“Service”结尾的服务层方法。然后,我们定义了一个名为logBefore
的前置通知,它会在任何匹配的方法调用之前打印一条日志消息。
总之,Spring AOP为开发者提供了一种强大而又灵活的方式来处理横切关注点,使得应用程序结构更加清晰,维护成本更低。理解如何正确地设计和使用AOP对于构建高质量的企业级Java应用至关重要。
16、Spring AOP默认用的是什么动态代理,两者的区别? 中等
在Spring AOP中,默认情况下会根据目标对象是否实现了接口来选择使用不同的动态代理技术。具体来说:
1. JDK动态代理(Java Reflection Proxy)
- 适用条件:当目标对象实现了至少一个接口时,Spring AOP会优先选择JDK动态代理。
- 工作原理:基于Java的反射机制,通过
java.lang.reflect.Proxy
类和InvocationHandler
接口创建代理对象。这个代理对象实现了与目标对象相同的接口,并且可以在方法调用时插入额外的行为(如AOP通知)。 - 优点:
- 纯粹基于标准Java API,无需依赖第三方库。
- 对于已经实现接口的对象来说,性能较好。
- 更加符合面向接口编程的原则。
- 缺点:
- 只能代理接口的方法,无法代理类中的非接口方法或私有方法。
示例代码
// 假设有一个服务接口
public interface MyService {
void doSomething();
}
// 实现该接口的服务类
public class MyServiceImpl implements MyService {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
// 使用JDK动态代理
Proxy.newProxyInstance(
MyServiceImpl.class.getClassLoader(),
new Class<?>[]{MyService.class},
(proxy, method, args) -> {
// 在这里可以添加前置、后置等通知逻辑
System.out.println("Before method execution");
Object result = method.invoke(new MyServiceImpl(), args);
System.out.println("After method execution");
return result;
});
2. CGLIB动态代理(Code Generation Library)
- 适用条件:如果目标对象没有实现任何接口,或者需要代理类本身的方法而非接口方法,那么Spring AOP会选择CGLIB动态代理。
- 工作原理:CGLIB是一个强大的字节码生成库,它能够在运行时为指定的类创建子类,并重写其方法以插入额外的行为。对于每个被代理的方法,CGLIB都会生成一个新的子类并覆盖这些方法,在其中添加通知逻辑。
- 优点:
- 可以代理没有实现接口的类,以及类中定义的所有公共方法。
- 不受接口限制,提供了更广泛的代理能力。
- 缺点:
- 因为涉及到字节码操作,所以相对于JDK动态代理来说,可能会有一定的性能开销。
- CGLIB生成的是目标类的子类,因此不能用于最终类(final classes)或包含最终方法(final methods)的类。
示例代码
// 没有实现接口的服务类
public class MyServiceImpl {
public void doSomething() {
System.out.println("Doing something...");
}
}
// 使用CGLIB动态代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyServiceImpl.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
// 在这里可以添加前置、后置等通知逻辑
System.out.println("Before method execution");
Object result = method.invoke(new MyServiceImpl(), args);
System.out.println("After method execution");
return result;
});
MyServiceImpl proxy = (MyServiceImpl) enhancer.create();
Spring AOP的选择逻辑
Spring AOP会自动检测目标对象是否有实现接口,并据此决定使用哪种代理方式:
- 如果目标对象实现了接口,则默认使用JDK动态代理。
- 如果目标对象没有实现接口,或者开发者明确指定了对类本身进行代理(例如通过配置),则会使用CGLIB动态代理。
此外,还可以通过配置显式地指定要使用的代理类型。例如,在XML配置中可以通过设置<aop:config>
标签的proxy-target-class
属性来强制使用CGLIB代理:
<aop:config proxy-target-class="true">
<!-- 切面配置 -->
</aop:config>
或者在Java配置中使用@EnableAspectJAutoProxy(proxyTargetClass=true)
注解:
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AppConfig {
// ...
}
总之,了解这两种动态代理的区别及其适用场景有助于更好地理解Spring AOP的工作原理,并能够根据实际需求做出合适的选择。
17、能说说 Spring 拦截链的实现吗? 中等
在Spring框架中,拦截链(Interceptor Chain)主要用于Web应用程序的请求处理过程中。它允许开发者在请求到达目标处理器(如控制器方法)之前或之后执行一些额外的操作,比如权限检查、日志记录、性能监控等。Spring MVC提供了内置的支持来实现这种功能,并且可以通过配置多个拦截器形成一个拦截链,每个拦截器都可以对请求进行预处理或后处理。
拦截器与拦截链
1. 拦截器(Interceptor)
-
定义:拦截器是一个实现了
HandlerInterceptor
接口或者继承了HandlerInterceptorAdapter
类的对象。它们可以用来拦截进入控制器的方法调用,在请求处理的不同阶段插入自定义逻辑。 -
主要方法:
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
:在实际处理请求之前调用,返回true
表示继续处理流程;返回false
则中断后续处理,直接返回响应给客户端。postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
:在处理器完成处理但尚未渲染视图时调用,可以修改ModelAndView
对象中的数据。afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
:在整个请求处理完毕后调用,通常用于资源清理或记录异常信息。
2. 拦截链(Interceptor Chain)
- 概念:当有多个拦截器注册到Spring MVC中时,它们会按照顺序组成一个拦截链。每次HTTP请求都会依次经过这个链条上的所有拦截器,先执行每个拦截器的
preHandle
方法,然后是目标处理器方法,接着再逆序执行每个拦截器的postHandle
和afterCompletion
方法。
实现方式
要创建并使用拦截器,你需要做以下几件事:
-
编写拦截器类:实现
HandlerInterceptor
接口或扩展HandlerInterceptorAdapter
类,并重写需要的方法。import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 在这里添加前置处理逻辑 System.out.println("Before handling request..."); return true; // 继续处理 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 在这里添加后置处理逻辑 System.out.println("After handling request but before rendering view..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 在这里添加最终处理逻辑 System.out.println("After completion of request..."); } }
-
注册拦截器:将自定义的拦截器添加到Spring MVC的配置中,可以通过XML配置文件或Java配置类完成。
-
XML配置:
<mvc:interceptors> <bean class="com.example.MyInterceptor"/> </mvc:interceptors>
-
Java配置:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()) .addPathPatterns("/api/**") // 只拦截匹配路径的请求 .excludePathPatterns("/api/public/**"); // 排除某些路径 } }
-
-
配置拦截链顺序:如果你有多个拦截器,可以通过
InterceptorRegistry
指定它们的执行顺序。默认情况下,拦截器会按照注册的顺序执行。 -
路径模式匹配:通过
addPathPatterns()
和excludePathPatterns()
方法可以精确控制哪些URL应该被拦截器处理,哪些不应该。
工作流程
- 请求到来:客户端发送HTTP请求到服务器。
- 拦截链启动:请求首先经过一系列已注册的拦截器,从第一个开始依次调用
preHandle
方法。 - 处理器方法执行:如果所有的
preHandle
都返回true
,则请求会被转发给相应的控制器方法进行处理。 - 视图渲染前:控制器方法执行完毕后,会逆序调用每个拦截器的
postHandle
方法,可以在这一阶段修改模型数据或视图名称。 - 请求完成:无论是否发生了异常,最后都会逆序调用每个拦截器的
afterCompletion
方法来进行清理工作或其他必要的操作。
总之,Spring MVC的拦截链机制为开发者提供了一种灵活的方式来增强请求处理过程中的行为,而无需修改现有的业务逻辑代码。通过合理地配置拦截器及其执行顺序,你可以有效地管理横切关注点,提升应用的安全性和功能性。
18、Spring AOP 和 AspectJ 有什么区别? 中等
Spring AOP 和 AspectJ 都是实现面向切面编程(AOP,Aspect-Oriented Programming)的工具,但它们在设计哲学、实现方式以及使用场景上存在一些显著的区别。理解这些差异有助于选择最适合你项目需求的技术。
1. 设计理念
-
Spring AOP:
- 是Spring框架的一部分,旨在为Spring管理的bean提供AOP功能。
- 主要关注于方法级别的连接点(Join Points),即它主要对方法调用进行拦截。
- 设计目的是为了简化AOP的应用,并与Spring的其他特性(如IoC容器、事务管理等)无缝集成。
-
AspectJ:
- 是一个独立的AOP框架,提供了更全面和强大的AOP支持。
- 支持多种类型的连接点,包括但不限于方法调用、字段访问、构造函数调用等。
- 提供了更为丰富的语言特性,允许开发者以声明式的方式定义切面逻辑。
2. 实现方式
-
Spring AOP:
- 基于代理机制实现,通常使用JDK动态代理或CGLIB库来创建代理对象。
- 对于实现了接口的bean,默认使用JDK动态代理;对于没有接口的bean,则使用CGLIB代理。
- 因此,Spring AOP只能代理public方法,并且无法代理final类或final方法。
-
AspectJ:
- 使用编译时织入(Compile-time weaving)、加载时织入(Load-time weaving, LTW)或运行时织入(Runtime weaving)来直接修改字节码。
- 这意味着它可以影响到任何方法,甚至是private方法、protected方法或package-private方法,而不仅仅是public方法。
- AspectJ可以在编译阶段就将切面代码“编织”进目标类中,从而避免了运行时性能开销。
3. 灵活性与表达力
-
Spring AOP:
- 提供了一套相对简单易用的API,适合大多数常见的AOP需求。
- 支持通过XML配置文件或注解(如
@Aspect
、@Before
、@AfterReturning
等)定义切面。 - 由于其依赖于Spring容器,因此在非Spring环境下使用会受到一定限制。
-
AspectJ:
- 提供了更为灵活和强大的表达式语言(Pointcut Expression Language),可以精确地定义切入点。
- 支持更复杂的切面逻辑,例如引入新的方法或属性到现有类中(Introduction)。
- 可以独立于任何特定框架使用,具有更高的通用性。
4. 性能
-
Spring AOP:
- 因为是基于代理的实现,在每次方法调用时都会产生一定的性能开销,尤其是在需要创建大量代理对象的情况下。
- 不过,这种开销通常是可以接受的,特别是在企业级应用中,相比于其带来的便利性和维护性提升来说。
-
AspectJ:
- 编译时织入或加载时织入可以在很大程度上减少运行时的性能损耗,因为切面逻辑已经被直接嵌入到了目标类中。
- 然而,设置和配置AspectJ织入过程可能比配置Spring AOP稍微复杂一些。
5. 适用场景
-
Spring AOP:
- 如果你的应用主要是基于Spring构建的,并且只需要处理简单的横切关注点(如日志记录、事务管理等),那么Spring AOP是一个很好的选择。
- 它易于集成,不需要额外的编译步骤或特殊配置。
-
AspectJ:
- 当你需要更加精细地控制AOP行为,或者希望能够在更广泛的范围内应用切面逻辑时,AspectJ可能是更好的选择。
- 特别是在需要处理非public方法、final类/方法或需要在编译期就完成织入的情况下。
总结
-
选择Spring AOP:如果你正在开发一个基于Spring的企业级应用,并且只关心方法级别的横切关注点,那么Spring AOP以其简便性和与Spring生态系统的紧密集成成为首选。
-
选择AspectJ:如果你需要更强的AOP功能,比如能够操作私有成员、构造函数或者其他非public元素,或者想要在编译期就将切面逻辑嵌入到代码中,那么AspectJ提供了更广泛的支持和更高的灵活性。
总之,两者各有优势,具体选择取决于项目的实际需求和技术栈的选择。
19、说下 Spring Bean 的生命周期? 中等
Spring Bean的生命周期是指从创建到销毁过程中经历的一系列阶段。了解这些阶段对于正确配置和使用bean至关重要,因为它们影响着bean的行为、初始化以及资源管理。Spring容器负责管理bean的整个生命周期,确保每个bean在适当的时候被正确地初始化、使用和销毁。
Spring Bean 生命周期的主要阶段
-
实例化(Instantiation):
- 容器根据配置信息(如XML文件、注解或Java配置类)创建bean的实例。这可以通过调用默认构造函数、带参数的构造函数或者工厂方法来完成。
-
属性赋值(Populate Properties/Dependency Injection):
- 在bean实例创建后,Spring容器会为该bean设置属性值,并注入它所依赖的其他bean。这一步骤实现了依赖注入(DI),即bean的依赖关系由外部容器来管理和提供。
-
设置Bean名称(Setting Bean Name, if applicable):
- 如果bean实现了
BeanNameAware
接口,那么容器会在这一阶段调用setBeanName(String name)
方法,将bean的名字传递给bean本身。
- 如果bean实现了
-
设置Bean工厂(Setting Bean Factory, if applicable):
- 如果bean实现了
BeanFactoryAware
接口,容器会调用setBeanFactory(BeanFactory beanFactory)
方法,让bean知道它是通过哪个工厂创建的。
- 如果bean实现了
-
前置处理(Post Process Before Initialization):
- 容器会调用所有注册的
BeanPostProcessor
对象的postProcessBeforeInitialization(Object bean, String beanName)
方法,允许对bean进行额外的自定义初始化操作。
- 容器会调用所有注册的
-
初始化(Initialization):
- 这是bean生命周期中的一个重要阶段,在这里可以执行一些必要的初始化工作。
- 如果bean实现了
InitializingBean
接口,容器会调用afterPropertiesSet()
方法。 - 可以通过
<bean>
标签的init-method
属性或@PostConstruct
注解指定一个自定义的初始化方法。
- 如果bean实现了
- 这是bean生命周期中的一个重要阶段,在这里可以执行一些必要的初始化工作。
-
后置处理(Post Process After Initialization):
- 容器再次调用所有注册的
BeanPostProcessor
对象的postProcessAfterInitialization(Object bean, String beanName)
方法,进一步定制bean的行为。
- 容器再次调用所有注册的
-
使用(Usage):
- 经过上述步骤后,bean已经完全初始化并准备好被应用程序使用。此时,它可以响应业务逻辑请求,与其他组件协作完成特定任务。
-
销毁前处理(Pre Destruction Processing):
- 当容器关闭时,它会首先调用所有注册的
BeanPostProcessor
对象的postProcessBeforeDestruction(Object bean, String beanName)
方法,为bean的销毁做准备。
- 当容器关闭时,它会首先调用所有注册的
-
销毁(Destruction):
- 容器调用bean的销毁方法,以便释放资源、清理状态等。
- 如果bean实现了
DisposableBean
接口,容器会调用destroy()
方法。 - 可以通过
<bean>
标签的destroy-method
属性或@PreDestroy
注解指定一个自定义的销毁方法。
- 如果bean实现了
- 容器调用bean的销毁方法,以便释放资源、清理状态等。
示例代码
下面是一个简单的例子,展示了如何实现一些生命周期回调接口:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
private String beanName;
private BeanFactory beanFactory;
private ApplicationContext applicationContext;
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("Bean name set: " + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("Bean factory set.");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("Application context set.");
}
// 自定义初始化方法
@PostConstruct
public void init() {
System.out.println("Initializing bean...");
}
// 自定义销毁方法
@PreDestroy
public void destroy() {
System.out.println("Destroying bean...");
}
}
在这个例子中,MyBean
类实现了多个感知接口(BeanNameAware
、BeanFactoryAware
、ApplicationContextAware
),并且使用了@PostConstruct
和@PreDestroy
注解来定义初始化和销毁的方法。当这个bean被Spring容器管理时,它会在适当的时机触发这些方法,从而完成相应的生命周期回调。
注意事项
-
作用域的影响:不同作用域的bean可能有不同的生命周期行为。例如,单例(singleton)bean在整个应用上下文中只有一个实例,而原型(prototype)bean每次请求都会创建一个新的实例,因此它们的初始化和销毁时机也会有所不同。
-
BeanPostProcessor的作用:
BeanPostProcessor
接口允许开发者在bean的初始化前后对其进行额外的处理,这对于添加通用的预处理或后处理逻辑非常有用。
总之,掌握Spring Bean的生命周期对于构建健壮且易于维护的应用程序非常重要。理解各个阶段的特点和可用的回调机制可以帮助你更好地控制bean的行为,确保它们按照预期的方式运行。
20、说下对 Spring MVC 的理解? 中等
Spring MVC(Model-View-Controller)是Spring框架的一部分,它提供了一个强大的Web应用程序开发模型,实现了MVC设计模式。MVC是一种软件架构模式,将应用程序分为三个主要部分:模型(Model)、视图(View)和控制器(Controller),以促进代码的分离、复用和维护。下面是对Spring MVC的理解,包括其核心组件、工作流程以及优势。
核心组件
1. DispatcherServlet
- 作用:作为前端控制器,负责接收所有HTTP请求,并将其分发给适当的处理器。
- 职责:
- 解析请求信息;
- 查找并调用合适的
HandlerMapping
来确定处理请求的目标控制器方法; - 将请求委托给目标控制器执行业务逻辑;
- 处理异常;
- 返回响应结果给客户端。
2. HandlerMapping
- 作用:映射URL路径到具体的控制器方法。
- 实现方式:
- 基于注解的方式,如使用
@RequestMapping
及其变体(@GetMapping
,@PostMapping
等); - XML配置文件中定义的映射规则;
- 实现自定义的
HandlerMapping
接口。
- 基于注解的方式,如使用
3. Controller
- 作用:处理特定类型的请求,执行相应的业务逻辑。
- 特点:
- 可以是一个简单的POJO类,只需要添加必要的注解即可;
- 使用
@Controller
或@RestController
标注为控制器; - 方法参数可以通过多种来源获取数据,如请求参数、路径变量、HTTP头等;
- 返回值可以是视图名称、
ModelAndView
对象、JSON/XML字符串等。
4. ModelAndView
- 作用:封装了视图名和模型数据,用于传递给视图层进行渲染。
- 构成:
- 模型(Model):包含要展示的数据,通常是一个Map结构;
- 视图(View):表示如何呈现这些数据,例如JSP页面、Thymeleaf模板等。
5. ViewResolver
- 作用:解析逻辑视图名为实际物理视图资源。
- 实现方式:
- 内置支持多种视图技术,如JSP、FreeMarker、Thymeleaf等;
- 可以配置多个
ViewResolver
来区分不同类型的视图。
工作流程
- 请求到达:客户端发送HTTP请求到服务器上的
DispatcherServlet
。 - 查找处理器:
DispatcherServlet
根据请求URL找到对应的HandlerMapping
,进而确定处理该请求的控制器方法。 - 执行处理器:
DispatcherServlet
调用控制器中的方法,传入必要的参数(如表单数据、路径变量等),由控制器执行业务逻辑。 - 返回结果:控制器方法处理完毕后返回一个
ModelAndView
对象或其他形式的结果。 - 解析视图:如果有视图信息,
DispatcherServlet
会通过ViewResolver
解析出具体的视图资源。 - 渲染视图:最后,
DispatcherServlet
将模型数据交给视图进行渲染,并生成最终的HTML内容返回给客户端。
优势
- 松耦合:通过清晰的角色划分,使得各层之间相互独立,降低了模块间的依赖性,提高了代码的可读性和可维护性。
- 灵活性:支持多种视图技术,易于集成不同的前端框架和技术栈;同时提供了丰富的API和扩展点,允许开发者根据需要定制行为。
- 强大的数据绑定与验证机制:内置对表单提交的数据自动绑定到Java对象的支持,并且可以轻松地添加校验规则。
- RESTful API支持:借助
@RestController
和相关注解,可以快速构建符合REST风格的服务端点。 - 国际化(i18n)和本地化(l10n):内置了对多语言和区域设置的支持,方便创建面向全球用户的应用程序。
- 安全性和事务管理:能够与Spring Security无缝集成,确保应用的安全性;并且支持声明式事务管理,简化了事务控制代码。
- 性能优化:提供了缓存、异步处理等功能,有助于提升应用的响应速度和吞吐量。
总之,Spring MVC不仅遵循了经典的MVC设计模式,还结合了Spring框架的优势特性,为Java Web开发提供了高效、灵活且易于扩展的解决方案。理解Spring MVC的工作原理和核心组件有助于开发者构建高质量的企业级Web应用。