1、 spring 框架的核心特点和优势是什么?
答:
- 轻量级和非侵入性:Spring 是一个轻量级的框架,它不会强制性地改变你的应用架构,而是通过依赖注入和面向切面编程等特性,提供了一种非侵入式的开发方式。
- 依赖注入(Dependency Injection):Spring 提供了强大的依赖注入功能,通过容器管理对象之间的依赖关系,降低了耦合度并提高了代码的可测试性和可维护性。
- 面向切面编程(Aspect-Oriented Programming):Spring 支持面向切面编程,通过 AOP 可以将横切关注点(如日志记录、事务管理等)从业务逻辑中抽离出来,提高了代码的模块化和可复用性。
- 容器管理:Spring 的核心是 IoC 容器,它负责创建、配置和管理对象的生命周期,简化了对象的创建和销毁过程,并提供了对对象的实时管理和扩展能力。
- 统一的异常处理和事务管理:Spring 提供了统一的异常处理机制和灵活的事务管理支持,简化了开发者处理异常和管理事务的复杂度。
- 集成各种开源框架和第三方库:Spring 提供了丰富的集成能力,可以无缝地集成其他开源框架和第三方库,如数据库访问、消息队列、缓存等,为开发者提供了更多选择和灵活性。
- 模块化和可扩展性:Spring 框架由多个模块组成,每个模块都具有特定的功能,开发者可以根据项目需求选择需要的模块,并且可以自定义扩展新的功能模块。
- 测试支持和文档丰富:Spring 框架提供了良好的测试支持,可以方便地进行单元测试和集成测试。此外,官方提供了详尽的文档和示例代码,便于开发者学习和使用。
2、 你对依赖注入(Dependency Injection)的理解以及 Spring 是如何实现依赖注入的?
答:
DI理解:
依赖注入是一种设计模式,它用于解耦组件之间的依赖关系。在传统的编程模型中,组件通常通过直接创建和管理其所依赖的对象来满足其功能需求。然而,这样做会导致高度耦合的代码,使得组件难以复用、扩展和测试。依赖注入的目的是通过将组件所依赖的对象的创建和管理过程外部化,从而解除组件与具体实现的直接绑定,实现了低耦合、易于测试和可扩展性的代码。
Spring框架是一个使用广泛的Java应用程序开发框架,它提供了强大的依赖注入功能。Spring通过以下几种方式实现依赖注入:
- 构造函数注入(Constructor Injection):通过构造函数将依赖对象注入到组件中。在类的构造函数中声明依赖的参数,并通过构造函数来传递依赖对象。Spring容器负责解析构造函数参数,并实例化并注入相应的依赖对象。
- Setter方法注入(Setter Injection):通过setter方法将依赖对象注入到组件中。在组件类中定义setter方法,并在需要注入依赖的属性上添加相应的注解(如@Autowired)。Spring容器在实例化组件后,通过调用setter方法来注入依赖对象。
- 接口注入(Interface Injection):通过接口将依赖对象注入到组件中。组件实现一个特定接口,该接口定义了注入依赖的方法。Spring容器通过动态代理机制,在组件实例化后为其生成代理对象,并在代理对象中注入相应的依赖。
-
注解注入(Annotation Injection):使用注解在组件上直接标记依赖关系。Spring框架提供了一系列的注解(如
@Autowired、@Resource
等),通过在组件字段、构造函数或setter方法上添加注解,实现依赖对象的自动注入。
Spring的依赖注入功能基于IoC(Inverse of Control)容器实现。IoC容器负责管理组件的生命周期,并根据组件之间的依赖关系动态地注入依赖对象。在Spring中,常用的IoC容器是ApplicationContext。它负责解析和管理组件的依赖关系,并根据配置文件或注解配置来实现依赖注入。
3、什么是控制反转(Inversion of Control)和面向切面编程(Aspect-Oriented Programming)?Spring 中如何支持这两个概念?
答:
控制反转(Inversion of Control,IoC):
- 控制反转是一种设计原则,也称为依赖反转。它是指将组件之间的依赖关系的创建和管理转移给容器,从而实现了高度解耦的代码。传统的编程模型中,组件通常通过直接创建和管理其所依赖的对象来满足其功能需求。而控制反转则将这一过程外部化,让容器负责解析和管理组件的依赖关系。
- 在Spring中,控制反转通过IoC容器实现。IoC容器负责创建和管理组件的生命周期,并在需要时注入依赖对象,将依赖关系由组件自身转移到容器中。
- 在Spring框架中,常用的IoC容器是
ApplicationContext
。它可以通过XML配置文件、Java注解或Java代码来配置组件的依赖关系。Spring容器根据配置信息,动态地创建和管理组件实例,并将依赖对象注入到组件中,从而实现了控制反转。
面向切面编程(Aspect-Oriented Programming,AOP):
- 面向切面编程是一种用于解决横切关注点问题的编程范式。在传统的面向对象编程中,系统功能往往分散在各个对象中,导致了重复、冗余的代码。
- 而AOP则将这些横切关注点(如日志记录、事务管理等)与主要业务逻辑分离开来,通过切面(Aspect)进行封装和统一处理。
- 在Spring中,AOP功能由Spring AOP模块提供。Spring AOP通过动态代理技术实现切面编程。它通过拦截器(Interceptor)在目标方法执行前、执行后或异常抛出时注入特定的行为。
- 使用AOP时需要定义切点(Pointcut)来指定需要拦截的目标方法,然后编写切面类来封装横切关注点的逻辑。Spring AOP支持多种类型的通知(Advice),如前置通知(Before Advice)、后置通知(After Advice)、异常通知(AfterThrowing Advice)等。
- Spring AOP可以通过XML配置文件、Java注解或Java代码来实现切面的定义和配置。无论使用哪种方式,Spring AOP都会在运行时为目标对象生成代理对象,并将切面逻辑织入到目标方法中,从而实现面向切面的编程。
4、 Spring 框架中的 Bean 是什么,如何定义和管理 Bean?
答:
Bean是什么:
- 在Spring框架中,Bean是指由Spring容器创建、组装和管理的对象。
- Bean代表应用程序中的各个组件或对象,包括服务、数据访问对象、控制器等。通过将对象交给Spring容器管理,我们可以获得依赖注入、AOP等强大的功能特性。
如何定义Bean:
- 使用@Component注解或其派生注解(如@Service、@Repository、@Controller)标记类,表明该类是一个Bean。Spring会自动扫描并将这些注解标记的类注册为Bean。
- 使用@Configuration注解标记一个类,然后使用@Bean注解定义方法,返回类型即为Bean的类型。Spring容器会调用这些方法来创建并注册Bean。
- 在XML配置文件中使用元素显式地定义Bean的配置信息。
如何管理Bean:
- Spring提供了IoC容器(ApplicationContext)来管理Bean的生命周期和依赖关系。
- Bean的实例化:Spring容器负责根据Bean的定义创建Bean的实例。这可以通过构造函数、工厂方法或其他方式完成。
- 依赖注入:Spring容器负责解析Bean之间的依赖关系,并将依赖对象注入到需要的地方。依赖注入可以通过构造函数注入、Setter方法注入或字段注入等方式实现。
- 生命周期管理:Spring容器管理Bean的整个生命周期,包括初始化和销毁阶段。可以通过实现InitializingBean和DisposableBean接口、@PostConstruct和@PreDestroy注解、配置init-method和destroy-method等方式来定义Bean的初始化和销毁操作。
- 作用域:Spring支持多种Bean的作用域,如单例(Singleton)、原型(Prototype)、会话(Session)等。可以通过在@Bean注解中设置相应的作用域属性或在XML配置文件中指定作用域来定义Bean的作用域。
5、你对 Spring MVC 的理解和使用经验,以及如何处理 RESTful API 请求?
答:
Spring MVC是一种基于Java的Web应用程序框架,它提供了一种模型-视图-控制器(MVC)的架构模式来开发灵活和可扩展的Web应用程序。
理解Spring MVC:
- 模型-视图-控制器(MVC):Spring MVC遵循MVC设计模式,将应用程序分为三个主要部分。
- 模型(Model):负责处理应用程序的数据和业务逻辑。
- 视图(View):负责呈现数据给用户,通常是通过JSP、Thymeleaf或其他模板引擎实现。
- 控制器(Controller):接收和处理用户请求,并决定如何更新模型和选择视图。
使用Spring MVC的经验:
- 配置:通过配置文件(如XML或Java Config)设置Spring MVC的基本配置,例如URL映射、视图解析器、拦截器等。
- 注解:利用注解来简化开发。例如,使用@Controller注解标记控制器类,@RequestMapping注解指定处理请求的方法等。
- 数据绑定:Spring MVC提供了数据绑定功能,使得在控制器方法参数中直接绑定请求参数更加方便。
- 视图解析:通过配置合适的视图解析器,将控制器返回的逻辑视图名解析为实际的视图模板,进而呈现给用户。
处理RESTful API请求:
- 使用@RequestMapping注解:在Spring MVC中,可以使用@RequestMapping注解来映射RESTful API的请求。可以通过指定HTTP方法(GET、POST、PUT、DELETE等)和URL路径来定义不同的处理方法。
- 数据绑定和校验:Spring MVC支持将请求参数自动绑定到方法的参数对象中,还可以使用@RequestBody注解将请求体中的JSON或XML数据绑定到对象上。可以使用JSR-303/349校验注解,如@Validated和@Valid,来验证输入参数的有效性。
- 返回结果:通过使用@ResponseBody注解,Spring MVC允许控制器方法直接返回数据对象,并自动序列化为JSON或其他格式的响应。
- 异常处理:可以使用@ControllerAdvice注解定义全局异常处理类,捕获并处理RESTful API的异常,返回适当的错误响应。
6、Spring Boot 和 Spring 的区别是什么?Spring Boot 有哪些特性和优势?
答:
Spring Boot是Spring框架的一个扩展,主要用于简化和加速基于Spring的应用程序的开发。
区别:
- 配置:在传统的Spring中,需要手动进行大量的配置,包括XML配置文件、注解配置等。而Spring Boot采用了约定大于配置的原则,减少了配置的工作量,通过自动配置和默认配置来简化开发。
- 依赖管理:传统的Spring开发中,需要手动管理各种依赖库的版本兼容性。而Spring Boot引入了"Starter"依赖,简化了依赖管理,可以根据需要选择相关功能模块的Starter依赖,在编译时自动解析和导入相关的依赖库。
- 生产就绪特性:Spring Boot提供了一些生产环境所需的特性,如健康检查、性能指标监控、故障处理等。这些特性有助于快速构建可靠且高效的应用程序。
特性和优势:
- 简化配置:Spring Boot通过自动配置和默认配置减少了繁琐的配置工作,开发人员可以更专注于业务逻辑的实现。
- 内嵌服务器:Spring Boot内置了Tomcat、Jetty等常见的Web服务器,使得应用程序可以直接以可执行的JAR包形式运行,无需外部容器。
- 自动化依赖管理:Spring Boot的"Starter"依赖简化了依赖管理,只需添加相关功能模块的Starter依赖,即可自动导入所需的依赖库。
- 微服务支持:Spring Boot提供了对微服务架构的良好支持,包括服务注册与发现、配置中心、负载均衡等。
- 监控和管理:Spring Boot集成了各种监控和管理工具,如Actuator,可以实时查看应用程序的性能指标、健康状况等。
- 外部化配置:Spring Boot支持将应用程序的配置信息外部化,可以通过属性文件、环境变量、命令行参数等进行配置。
7、如何使用 Spring 进行事务管理?讲解 Spring 的事务传播行为和隔离级别。
答:
在Spring中,可以使用声明式事务管理或编程式事务管理来处理事务。
使用Spring进行事务管理的步骤:
- 配置数据源:首先需要配置数据源,例如数据库连接池。
- 配置事务管理器:配置事务管理器,例如使用Spring提供的PlatformTransactionManager接口的具体实现类,如DataSourceTransactionManager。
- 声明事务:在需要进行事务管理的方法上添加@Transactional注解,表明该方法需要进行事务管理。
- 指定事务属性:可以通过@Transactional注解的属性来指定事务的传播行为和隔离级别等。
事务传播行为(Transaction Propagation):
- REQUIRED(默认值):如果当前存在事务,则加入该事务;如果不存在事务,则创建一个新的事务。
- REQUIRES_NEW:每次调用方法时都创建一个新的事务,暂停当前事务(如果存在)。
- SUPPORTS:如果当前存在事务,则加入该事务;如果不存在事务,以非事务方式执行。
- MANDATORY:如果当前存在事务,则加入该事务;如果不存在事务,则抛出异常。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起该事务。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则创建一个嵌套事务,并在一个独立的保存点内执行;如果不存在事务,则行为类似于REQUIRED。
事务隔离级别(Transaction Isolation Level):
- DEFAULT(默认值):使用数据库默认的事务隔离级别。
- READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据。
- READ_COMMITTED:允许读取已提交的数据,防止脏读。
- REPEATABLE_READ:确保同一查询返回相同的结果,防止脏读和不可重复读。
- SERIALIZABLE:最高的隔离级别,确保事务串行执行,防止脏读、不可重复读和幻读。
在Spring中,事务管理是通过AOP(面向切面编程)实现的。Spring通过在方法调用前后应用事务增强器来管理事务的开始、提交、回滚等操作。
需要注意的是,事务管理的配置和使用可能会因具体的Spring版本和使用的持久化技术(如Hibernate、JPA等)而有所差异,请根据具体情况进行调整。
8、你如何进行单元测试和集成测试,使用 Spring 如何简化测试流程?
答:
单元测试:
- 单元测试是针对应用程序中最小的可测试单元(通常是类或方法)进行的测试。
- 在单元测试中,通常使用JUnit等单元测试框架来编写和运行测试用例,验证每个单元的逻辑正确性。
- Spring可以简化单元测试的过程,主要有以下几点:
- 使用Mock对象:Spring提供了Mockito等模拟框架,用于模拟依赖对象的行为,使得单元测试更加独立和可控。
- 注入依赖:Spring的依赖注入机制可以方便地注入被测试单元的依赖对象,使得测试更加简洁和可读。
- 使用测试环境配置:Spring提供了@ContextConfiguration注解,可以在测试环境中加载特定的配置文件或配置类,用于创建ApplicationContext容器并管理依赖对象的生命周期。
集成测试:
- 集成测试是对多个组件或模块之间的交互进行测试,以验证它们能否正确地协同工作。
- 在集成测试中,需要模拟真实的环境和数据,并对系统的各个组件进行综合性的测试。
- Spring可以简化集成测试的过程,主要有以下几点:
- 使用内存数据库:Spring提供了内存数据库(如H2、HSQLDB等),可以在测试时替代真实的数据库,避免对真实数据的依赖和破坏。
- 配置事务管理器:Spring的事务管理器可以方便地配置和管理事务,在测试环境中模拟事务的开始、提交和回滚。
- 测试框架支持:Spring与JUnit等测试框架紧密集成,提供了各种测试注解和工具类,如@SpringBootTest注解、TestRestTemplate等,简化了集成测试的编写和执行。
通过上述方式,Spring简化了测试流程,使得单元测试和集成测试更加容易和高效。它提供了依赖注入、Mock对象、测试环境配置、内存数据库、事务管理器等功能,帮助开发人员编写可靠的测试用例和验证应用程序的正确性和稳定性。这些特性使得测试的编写和执行更加方便,同时提高了测试的可读性和可维护性。
9、在分布式系统中如何使用 Spring Cloud 进行服务注册、发现和调用?
答:
在分布式系统中,Spring Cloud提供了一套完整的解决方案来实现服务注册、发现和调用。
服务注册与发现:
- 使用Eureka:Spring Cloud提供了Netflix Eureka作为默认的服务注册与发现组件。通过在应用程序中引入相应的依赖并配置Eureka服务器地址,应用程序可以将自身注册到Eureka服务器上。(现在主流Nacos)
- 使用Consul或ZooKeeper:除了Eureka,Spring Cloud也支持使用Consul或ZooKeeper等其他注册中心。通过相应的依赖和配置,应用程序可以将自身注册到这些注册中心上。
服务调用:
- 使用Ribbon:Spring Cloud的Ribbon是一个负载均衡客户端,可与服务注册中心集成。通过在应用程序中引入Ribbon依赖,在需要调用其他服务的地方,可以使用@LoadBalanced注解来实现负载均衡的服务调用。
- 使用Feign:Spring Cloud的Feign是一个声明式的Web服务客户端,简化了服务之间的HTTP调用。通过在应用程序中引入Feign依赖,并使用@FeignClient注解声明对目标服务的调用接口,Spring Cloud会在运行时生成具体的代理类来处理服务调用。
服务容错与熔断:
- 使用Hystrix:Spring Cloud的Hystrix是一个容错和熔断框架,用于处理分布式系统中的故障和延迟。通过在应用程序中引入Hystrix依赖,并结合@HystrixCommand注解,可以实现服务调用的容错和熔断。
配置中心:
- 使用Config Server:Spring Cloud的Config Server允许将应用程序的配置集中管理,并提供了对配置的动态刷新能力。通过在应用程序中引入Config Server依赖,并配置Config Server的地址,可以实现配置的集中管理和动态刷新。
通过上述步骤,使用Spring Cloud可以方便地实现服务注册、发现和调用。它提供了多种注册中心的选择,如Eureka、Consul和ZooKeeper,并且集成了负载均衡、声明式的服务调用、容错熔断以及配置中心等功能,使得分布式系统的开发和运维更加简单和可靠。
10、你在实际项目中使用 Spring 的哪些模块和功能,遇到了哪些挑战和解决方案?
答:
常见的使用Spring的模块和功能以及可能遇到的挑战和解决方案的例子:
-
Spring MVC
- 使用Spring MVC进行Web应用程序的开发和部署。
- 可能遇到的挑战:如何处理大量的请求和响应,如何管理控制器和视图的生命周期,如何优化性能等。
- 解决方案:使用Spring的异步请求处理、拦截器、过滤器、缓存等特性来优化性能,使用适当的设计模式和架构来管理控制器和视图的生命周期。
-
Spring Data
- 使用Spring Data来简化对数据访问层的操作,支持各种数据库和ORM框架。
- 可能遇到的挑战:如何处理复杂的查询和关联关系,如何优化性能,如何处理事务等。
- 解决方案:使用Spring Data的查询DSL、动态查询、关联查询、事务管理等特性来解决问题,使用适当的索引和缓存来优化性能。
-
Spring Security
- 使用Spring Security来增强安全性,包括认证、授权、加密等。
- 可能遇到的挑战:如何处理不同的身份认证和授权方式,如何保护敏感数据和资源,如何处理会话管理等。
- 解决方案:使用Spring Security的各种身份认证和授权机制、加密算法、会话管理等特性来解决问题,并遵循最佳实践来保护敏感数据和资源。
-
Spring Boot
- 使用Spring Boot来快速搭建和部署应用程序,提供自动配置和依赖管理等功能。
- 可能遇到的挑战:如何管理应用程序的生命周期,如何处理配置和依赖冲突等。
- 解决方案:使用Spring Boot的启动器、自动配置、命令行工具等特性来简化和优化开发流程,使用适当的设计模式和架构来管理应用程序的生命周期。
11、Spring运用了那些设计模式,具体是在哪里使用这些设计模式?
答:
-
依赖注入(Dependency Injection):
- Spring使用依赖注入模式来管理对象之间的依赖关系。通过将对象的依赖关系从代码中分离出来,Spring可以更加松耦合地管理和配置这些对象。
-
工厂模式(Factory Pattern):
- Spring使用工厂模式来创建和管理bean实例。例如,Spring的ApplicationContext充当了一个BeanFactory,可以根据配置文件或注解来创建bean实例。
-
单例模式(Singleton Pattern):
- Spring中的bean默认为单例模式,即每个bean实例在容器中只会存在一个。这样可以节省资源并确保在整个应用程序中使用同一个实例。
-
代理模式(Proxy Pattern):
- Spring AOP(面向切面编程)使用代理模式来实现横切关注点的功能。通过动态代理,在目标对象的方法执行前后插入额外的逻辑,如日志记录、事务管理等。
-
观察者模式(Observer Pattern):
- Spring的事件机制基于观察者模式。通过定义事件、监听器和发布器,可以实现对象之间的解耦,并在特定事件发生时通知相关的监听器。
-
模板模式(Template Pattern):
- Spring的JdbcTemplate和HibernateTemplate等模板类使用模板模式来提供一致的数据访问接口,并处理常见的数据库操作,如打开连接、执行查询等。
-
适配器模式(Adapter Pattern):
- Spring的MVC框架中使用适配器模式来处理不同类型的请求。通过适配器,可以将特定类型的请求转换为控制器可以处理的形式。
-
策略模式(Strategy Pattern):
- Spring的Validator接口使用策略模式来实现不同的验证规则。通过定义不同的验证策略,可以在运行时选择合适的策略进行验证。
12、 在spring中如何不导入配置里的部分功能 ?
答:
在Spring中,可以通过使用条件注解或者条件化的配置来实现不导入配置里的部分功能。
条件注解:
- 可以使用
@Conditional
注解来根据特定条件来控制Bean的实例化以及配置的导入。 - 例如,我们可以定义一个自定义条件类,用于检查当前是否存在某个特定的Bean或环境变量,如果存在则导入相应的配置;否则不导入。
常用的条件注解:
-
@ConditionalOnProperty
:当指定的属性存在并具有指定的值时,才会加载配置。可以通过设置value
、havingValue
等属性来指定条件。 -
@ConditionalOnClass
:当指定的类在类路径中存在时,才会加载配置。可以通过设置value
属性来指定类名。 -
@ConditionalOnBean
:当指定的Bean在容器中存在时,才会加载配置。可以通过设置value
属性来指定Bean的类型。 -
@ConditionalOnMissingBean
:当指定的Bean在容器中不存在时,才会加载配置。可以通过设置value
属性来指定Bean的类型。 -
@ConditionalOnExpression
:根据SpEL表达式的结果来判断是否加载配置。可以通过设置value
属性来指定SpEL表达式。
条件化的配置 :
- 使用
@Profile
或@Import
注解来根据特定的配置文件或环境变量来控制Bean的实例化以及配置的导入。 - 例如,我们可以定义一个profile为"test"的配置文件,在该配置文件中定义相应的Bean,然后在主配置文件中通过@Profile注解进行引用。这样在"test"环境下,会自动导入该配置文件的相关配置;而在其他环境下,则不会导入。
13、 说一下SpringBootApplication里有什么注解 ?
答:
在一个Spring Boot应用程序的主类中,通常会使用@SpringBootApplication
注解来标识该类是一个Spring Boot应用程序的入口点。
@SpringBootApplication
注解本身是一个组合注解,它包含了一系列常用的注解,包括:
-
@Configuration
:表明该类是一个配置类,用于定义和配置Bean。 -
@EnableAutoConfiguration
:启用Spring Boot的自动配置机制,根据项目的依赖和配置,自动配置Spring应用程序的行为。 -
@ComponentScan
:指定扫描组件的基础包路径,以发现和注册Spring管理的Bean。
综合以上三个注解的功能,@SpringBootApplication
注解能够将配置、自动配置和组件扫描结合起来,使得开发者可以更方便地构建和配置Spring Boot应用程序。
14、 说一下spring中的循环依赖 ?
答:
在Spring框架中,循环依赖是指两个或多个Bean之间相互依赖,形成了一个循环引用的关系。
这种情况下,当Spring容器试图创建这些Bean时,可能会导致无限递归调用,从而引发栈溢出或无法正常创建Bean的问题。
当两个Bean互相依赖时,Spring的默认创建流程如下:
- 首先,创建Bean A的实例。
- 在创建Bean A的过程中,发现需要依赖Bean B。
- 由于Bean B还未创建,Spring会尝试先创建Bean B的实例。
- 在创建Bean B的过程中,发现需要依赖Bean A。
- 由于Bean A还未创建完成,Spring又会尝试创建Bean A的实例。
- 然后又回到步骤2,形成了循环依赖,导致无限递归调用。
为了解决循环依赖的问题,Spring提供了默认策略和三级缓存。
默认策略:
- Spring默认使用单例模式管理Bean,即默认情况下,每个Bean只会被创建一次。当出现循环依赖时,默认策略是将尚未完全创建的Bean放入到“早期引用”缓存中,以解决循环依赖的问题。
- 在创建Bean时,如果发现需要依赖另一个尚未创建完成的Bean,Spring会返回早期引用缓存中的Bean实例,而不是继续递归创建。
三级缓存:
- 为了更好地解决循环依赖的问题,Spring还引入了三级缓存的机制。当Bean被创建时,Spring会将其放入三个级别的缓存中,分别是singletonFactories、earlySingletonObjects和singletonObjects。
- 通过这三个缓存,Spring可以在创建Bean时解决循环依赖问题,并保证每个Bean只被创建一次。
15、 spring中如何解决循环依赖 ?
答:
在Spring框架中,循环依赖是指两个或多个Bean之间相互依赖,形成循环引用的情况。Spring提供了几种方法来解决循环依赖问题:
- 构造函数注入:通过构造函数将依赖项作为参数注入,而不是使用自动装配注解(如@Autowired)进行属性注入。这种方式可以避免循环依赖问题,因为构造函数在对象创建时只执行一次,而属性注入可能会在对象创建后进行。( 使用@Autowired注解配合@Lazy注解来解决问题 )
- Setter方法注入:在Bean中使用Setter方法将依赖项注入,同时设置依赖项的延迟加载。延迟加载可以确保Bean被完全初始化后再进行注入,从而解决循环依赖的问题。( 使用@Autowired注解配合setter方法解决)
- 使用@Lazy注解:在声明Bean时,使用@Lazy注解延迟加载Bean的初始化。这样可以避免循环依赖的问题,因为Bean只有在需要时才会被初始化。
- 使用代理对象:当存在循环依赖时,可以通过使用代理对象来解决。Spring提供了两种代理方式:JDK动态代理和CGLIB代理。通过在相关的Bean上添加@Scope(“prototype”)注解,使其成为原型作用域的Bean,从而使用代理对象解决循环依赖。( 或者创建一个提前暴露的代理对象,然后将其注入到循环依赖的Bean中 )
16、spring中那些地方是使用反射机制来实现的?
答:
- Bean的实例化: Spring通过反射机制实例化Bean。当Spring容器启动时,会扫描配置文件中所有用@Bean、@Controller、@Service、@Repository等注解标注的类,并使用反射机制创建这些Bean的实例。
- 属性注入: Spring通过反射机制实现自动装配,即将Bean之间的依赖关系自动注入到Bean中。在调用Bean的构造方法或setter方法时,Spring通过反射机制注入属性值。
- AOP代理: Spring通过反射机制实现AOP功能。当使用@Aspect注解定义一个切面时,Spring会使用反射机制创建代理对象,将切面织入到目标对象中,并执行增强逻辑。
- 事件监听: Spring通过反射机制实现事件监听功能。当事件发生时,Spring会通过反射机制调用所有注册了对该事件感兴趣的监听器的相关方法。
- 数据库访问: 在Spring中,使用JdbcTemplate实现数据库访问时,Spring会通过反射机制调用JavaBean的setter方法,将查询结果映射到JavaBean的属性中。
17、说一下spring中的三层缓存机制的实现原理?
答:
在Spring框架中,Bean的创建过程是一个比较复杂的过程,涉及到多个阶段和多个缓存。其中,三层缓存机制是Spring框架用于提高Bean创建效率的关键机制之一。
Spring框架的三层缓存机制包括如下三层:
- singletonObjects缓存: 该缓存用于存储完全初始化好的、单例模式的Bean实例对象。如果要获取单例对象,则首先从该缓存中查找是否存在目标对象,如果存在就直接返回。
- earlySingletonObjects缓存: 该缓存用于存储提前暴露出来的、但尚未完全初始化的单例Bean实例对象。在Bean的创建过程中,当创建出一个Bean实例后,Spring会把该Bean实例存放到earlySingletonObjects缓存中,以便在处理循环依赖时使用。
- singletonFactories缓存: 该缓存用于存储用于创建单例Bean的ObjectFactory。在Bean的创建过程中,当创建出一个Bean实例后,Spring会将该Bean实例对应的ObjectFactory存放到singletonFactories缓存中,以便在处理循环依赖时使用。
三层缓存机制的实现原理如下:
- 在获取单例Bean实例时,首先会从singletonObjects缓存中查找是否存在目标对象,如果存在就直接返回。如果不存在,则会进入Bean的创建过程。
- 在创建Bean实例时,首先会从singletonFactories缓存中查找是否存在目标ObjectFactory,如果存在就使用该ObjectFactory创建Bean实例。如果缓存中不存在目标ObjectFactory,则会调用createBean方法创建Bean实例,并将该Bean实例对应的ObjectFactory存放到singletonFactories缓存中。
- 在创建完Bean实例后,Spring会将该Bean实例存放到earlySingletonObjects缓存中,并完成其他的初始化操作。如果在创建Bean实例的过程中发现循环依赖,Spring会从earlySingletonObjects缓存中获取相应的Bean实例并返回。如果earlySingletonObjects缓存中不存在目标Bean实例,则需要再次进入Bean的创建过程。
- 最后,Spring会将完全初始化好的、单例模式的Bean实例存放到singletonObjects缓存中,以供下一次获取该Bean实例时使用。
18、说一下spring中有哪些注解及其作用?
答:
-
@Component
: 用于将类标识为一个可被Spring容器扫描、识别和管理的组件。 -
@Controller
: 用于标识类为控制器,通常用于处理HTTP请求并返回响应。 -
@Service
: 用于标识类为服务层组件,通常用于封装业务逻辑。 -
@Repository
: 用于标识类为数据访问层组件,通常用于与数据库或其他持久化机制进行交互。 -
@Autowired
: 用于自动装配依赖,可以用于构造方法、成员变量、setter方法和其他任意方法上。 -
@Qualifier
: 与@Autowired一起使用,用于指定具体的依赖注入的Bean名称。 -
@Value
: 用于将属性值注入到Bean中,支持从配置文件、环境变量等来源获取。 -
@RequestMapping
: 用于映射HTTP请求路径到控制器的处理方法上。 -
@ResponseBody
: 用于将方法返回值直接作为HTTP响应的内容返回给客户端。 -
@PathVariable
: 用于获取URL路径中的参数值。 -
@RequestParam
: 用于获取请求参数的值。 -
@Configuration
: 用于标识类为配置类,通常与@Bean一起使用,用于定义Bean的创建和配置。 -
@Bean
: 用于将方法返回的对象注册为一个Bean,并交由Spring容器管理。 -
@Aspect
: 用于定义切面,通常与其他注解一起使用,实现AOP功能。 -
@Transactional
: 用于标识事务处理方法,将方法体内的操作纳入到事务管理中。
19、在springboot中,自定义的bean类或一些功能想在启动的时候如何把他跑起来?
答:
在Spring Boot中,可以通过实现CommandLineRunner
接口或使用@PostConstruct
注解来在应用启动时执行自定义的逻辑。
实现CommandLineRunner
接口:
- 创建一个类并实现
CommandLineRunner
接口,然后重写run
方法,在该方法中编写需要在应用启动时执行的逻辑。 - 通过将该类标记为
@Component
,Spring会自动扫描并将其实例化为Bean,并在应用启动时调用run
方法。
使用@PostConstruct
注解:
- 在需要在应用启动时执行的方法上添加
@PostConstruct
注解,该方法会在Bean初始化之后、依赖注入完成之后立即执行。 - 通过将类标记为
@Component
,Spring会将其实例化为Bean,并在初始化完成后调用init
方法。
20、spring中上下文(ApplicationContext)和bean工厂(BeanFactory)的区别?
答:
在Spring框架中,ApplicationContext和BeanFactory是两个核心的容器接口,它们之间有以下区别:
-
定义:
- ApplicationContext:是BeanFactory的子接口,提供了更多的功能,包括国际化支持、事件发布、资源加载等。
- BeanFactory:是Spring的基础容器接口,提供了基本的依赖注入和Bean管理的功能。
-
功能:
- ApplicationContext:除了提供BeanFactory的功能外,还提供了更多的高级功能,如自动装配、AOP、声明式事务、消息处理等。
- BeanFactory:提供基本的依赖注入和Bean管理的功能,是Spring最基础的容器。
-
预加载:
- ApplicationContext:在容器启动时会预先实例化所有的单例Bean。
- BeanFactory:只有在使用时才会实例化Bean。
-
资源管理:
- ApplicationContext:支持国际化资源管理、事件发布和监听、自动装配等。
- BeanFactory:主要关注Bean的创建和管理,不提供其他高级功能。
-
扩展性:
- ApplicationContext:支持各种扩展点,并且可以自定义实现,例如通过自定义BeanPostProcessor、BeanFactoryPostProcessor等扩展Spring容器的功能。
- BeanFactory:提供了基础的扩展点,但相对于ApplicationContext来说较为有限。
21、在spring中@Transactional注解在什么场景下会失效?
答:
在Spring中,@Transactional
注解用于声明事务的边界,并且它可以应用在类级别和方法级别。
在某些场景下,@Transactional注解可能会失效,具体情况如下:
- 异常未被捕获:如果在事务方法中抛出未被捕获的异常,并且该异常不是由Spring的事务管理器所能够回滚的默认异常(比如RuntimeException及其子类),那么事务将不会回滚。如果希望事务回滚,需要确保异常被捕获或者是Spring事务管理器能够回滚的异常。
- 非公共方法:如果在类的内部调用了其他非公共方法(即非被代理的方法),那么使用@Transactional注解也会导致事务失效。这是因为Spring的事务管理是基于AOP的动态代理实现,只有通过代理对象调用的方法才能被事务管理器拦截并应用事务。
- 自调用:如果在同一个类的方法中,一个方法调用了另一个方法,而两个方法都被@Transactional注解修饰,那么事务注解将失效。这是因为Spring的事务注解是通过AOP切面实现的,只有从外部调用的方法才能够被代理拦截,同一个类内部的方法调用不会触发代理。
- 异步方法:如果使用了Spring的异步方法(@Async注解),并且在异步方法中使用了@Transactional注解,那么事务也会失效。这是因为异步方法会在新的线程中执行,无法保证与当前线程相同的事务上下文。
- 不同类之间的方法调用:如果一个被@Transactional注解修饰的方法是在另一个类的方法中被调用(即跨类调用),并且被调用的方法也有@Transactional注解修饰,那么事务注解将失效。这是因为事务注解只在代理对象上起作用,而不会在类之间的调用中传播事务。
22、spring的事务有哪些?
答:
在Spring中,提供了以下几种常见的事务管理方式:
-
编程式事务管理:
- 使用
TransactionTemplate
或PlatformTransactionManager
进行编程式事务管理。通过在代码中手动开启、提交或回滚事务,实现对事务的控制。
- 使用
-
声明式事务管理:
-
基于注解:使用
@Transactional
注解在方法或类上声明事务的行为。 -
基于XML配置:通过在XML配置文件中定义事务属性,如
tx:advice
和tx:attributes
,来实现声明式事务管理。
-
基于注解:使用
-
注解驱动事务管理(Annotation-driven Transaction Management):
- 使用
@EnableTransactionManagement
注解开启注解驱动事务管理。 - 在方法或类上使用
@Transactional
注解声明事务行为。
- 使用
-
基于AspectJ的XML配置:
- 使用
tx:advice
和aop:config
标签结合AspectJ表达式,在XML配置文件中定义事务切面,并指定事务属性。
- 使用
-
基于注解和AspectJ的组合配置:
- 使用
@EnableTransactionManagement
注解开启注解驱动事务管理。 - 在XML配置文件中定义事务切面,并指定事务属性。
- 使用
23、SpringBean容器的生命周期是什么样的?
答:
Spring容器的生命周期可以分为以下几个阶段:
-
实例化(Instantiation):
- 当应用程序启动时,Spring容器会读取配置文件或注解,根据配置信息实例化Bean。这可以通过构造函数、静态工厂方法或工厂Bean来完成。
-
属性赋值(Populate Properties):
- 在实例化Bean之后,Spring容器会将配置文件或注解中指定的属性值赋给Bean的相应属性。这可以通过Setter方法或直接访问成员变量来完成。
-
初始化(Initialization):
- 在属性赋值之后,Spring容器会调用Bean的初始化方法(如果有定义的话),例如通过@PostConstruct注解标注的方法或实现了InitializingBean接口的afterPropertiesSet()方法。开发人员可以在这个阶段执行一些初始化操作。
-
使用(In Use):
- 完成初始化后,Bean就处于可用状态,可以被其他Bean或组件使用。
-
销毁(Destruction):
- 当应用程序关闭或者Spring容器被销毁时,Spring容器会调用Bean的销毁方法(如果有定义的话)。可以通过
@PreDestroy
注解标注的方法或实现了DisposableBean
接口的destroy()
方法来进行资源释放和清理操作。
- 当应用程序关闭或者Spring容器被销毁时,Spring容器会调用Bean的销毁方法(如果有定义的话)。可以通过
需要注意的是,Bean的生命周期可以受到Spring容器的影响。例如,单例作用域的Bean在容器启动时就会被创建,并在容器销毁时被销毁;而原型作用域的Bean在每次获取时都会创建一个新的实例,由应用程序负责销毁。
此外,可以通过实现BeanPostProcessor
接口来拦截Bean的初始化过程,并对其进行自定义处理。BeanPostProcessor接口提供了beforeInitialization()
和afterInitialization()
方法,允许开发人员在Bean的初始化前后进行一些额外的处理。
24、什么是spring装配?
答:
Spring装配(Assembly)是指将各个独立的组件(Bean)通过配置或者注解的方式组合在一起,形成一个完整的应用程序。
在Spring中,有三种常见的装配方式:
-
XML配置:
- 通过XML配置文件中的元素定义Bean,并使用元素设置Bean的属性值和依赖关系。然后通过元素将多个配置文件组合在一起。这种方式已经是比较传统的方式,在早期版本的Spring中被广泛使用。
-
注解配置:
- 使用注解来标记Bean,通过注解的方式实现Bean的装配。例如,使用@Component、@Service、@Repository等注解定义Bean,并使用@Autowired或@Resource注解注入依赖关系。
-
Java配置:
- 使用Java类来代替XML配置文件,通过特定的注解(如@Configuration、@Bean)和Java代码来配置和装配Bean。这种方式更加灵活和类型安全,并且可以充分利用Java编程语言的特性。
Spring装配的目的是将各个组件连接在一起,形成一个松耦合的应用程序。通过装配,可以实现依赖注入(Dependency Injection),将组件之间的依赖关系交给Spring容器来管理,而不是手动创建和维护对象之间的关系。这样可以提高代码的可维护性、可测试性,并支持面向接口的编程。同时,装配还可以帮助实现横向扩展和模块化开发,使应用程序更加灵活和可扩展。
25、spring中自动装配有那些方式?
答:
在Spring中,自动装配是一种依赖注入(Dependency Injection)的方式,可以自动地将Bean之间的依赖关系建立起来。Spring提供了多种自动装配的方式,包括:
-
byName自动装配:
- 根据Bean的名称来自动装配依赖关系。即如果一个Bean的属性名称和其他Bean的名称相同,则Spring会自动将该属性注入对应的Bean实例。这种方式要求Bean的名称必须与属性名称完全一致,并且Bean必须已经定义在容器中。
-
byType自动装配:
- 根据Bean的类型来自动装配依赖关系。即如果一个Bean的属性类型和其他Bean的类型相同,则Spring会自动将该属性注入对应的Bean实例。这种方式要求Bean的类型必须唯一,并且Bean必须已经定义在容器中。
-
constructor自动装配:
- 根据构造函数的参数类型来自动装配依赖关系。即Spring会自动查找容器中与构造函数参数类型匹配的Bean实例,并将它们注入到构造函数中。这种方式要求Bean必须有构造函数,并且构造函数的参数类型必须匹配容器中已经定义的Bean类型。
-
autowire-candidate属性:
- 可以通过设置Bean的autowire-candidate属性来控制是否参与自动装配。如果该属性被设置为false,则该Bean不会被自动装配。
需要注意的是,在使用自动装配时,应尽量避免出现歧义性的依赖关系,否则可能会导致无法确定装配到哪个Bean实例。同时,由于自动装配会隐藏Bean之间的依赖关系,因此在维护和调试时也需要特别注意。
盈若安好,便是晴天