Spring
为什么要使用Spring?
Spring是一个开源的Java应用程序开发框架,它提供了一个综合的编程和配置模型,用于构建现代化的企业级应用程序。
- 轻量级和非侵入性:Spring框架被设计成轻量级和非侵入性的,这意味着开发者可以选择性地使用Spring的各个模块,而不必强制改变现有的应用程序架构。这种设计使得Spring能够轻松地集成到现有的应用程序中,而不会对应用程序的代码结构产生重大影响。
- 提高编码效率和开发速度:Spring框架提供了许多开发中常用的功能和抽象,例如数据库访问、事务管理、Web开发支持等。这些功能的集成和封装使得开发人员可以更简单地处理复杂的开发任务,减少了开发复杂性,从而提高了编码效率和开发速度。
- 松耦合和可测试性:Spring通过控制反转(IoC)和依赖注入(DI)模式实现了松耦合的开发环境。它使对象之间的依赖关系从代码中解耦,而是由Spring容器负责管理和注入依赖关系。这种松耦合的设计使得应用程序的组件更容易替换、重用和测试。
- 丰富的功能和模块:Spring框架提供了丰富的功能和模块,包括IoC容器、AOP支持、声明式事务管理、数据访问支持、Web开发支持、安全性支持以及集成支持等。这些功能和模块使得Java开发人员能够更高效地开发企业级应用程序,满足各种复杂的需求。
- 良好的扩展性和灵活性:Spring框架的设计原则和模式使得代码更加模块化、可维护和可测试,并促进了应用程序的灵活性和可扩展性。开发人员可以根据自己的需求选择性地使用Spring的模块和组件,这种模块化的设计使得Spring可以适应各种应用场景。
- 强大的社区支持和文档资源:Spring作为一个流行的Java开发框架,拥有庞大的社区支持和丰富的文档资源。这意味着开发者在遇到问题时可以更容易地找到解决方案,同时也可以从社区中获取最新的技术动态和最佳实践。
Spring 的特性?
Spring本身的特性主要体现在以下几个方面:
轻量级框架
- 大小轻量:从JAR包的大小来说,Spring是一个轻量级框架,其核心JAR包的大小相对较小,便于部署和传输。
- 资源占用轻量:从系统的资源使用来看,Spring也是一个轻量级框架,在其运行期间只需少量的操作系统资源(CPU和内存)便能稳定运行。
模块化设计
- 核心容器:由spring-beans、spring-core、spring-context和spring-expression等模块组成,提供了IoC容器、依赖注入、上下文管理等功能。
- AOP(面向切面编程):提供了面向切面的编程支持,允许开发者在程序执行的关键点插入额外的代码,以实现对程序的动态修改和增强。
- 数据访问及集成:包括spring-jdbc、spring-tx、spring-orm等模块,提供了对JDBC、事务管理、ORM框架(如Hibernate、JPA)的支持,简化了数据访问层的开发。
控制反转(IoC)与依赖注入(DI)
- 控制反转:Spring的控制反转机制允许将对象的创建和依赖关系的管理交给Spring容器来负责,而不是在对象内部进行创建和依赖关系的维护。这种机制降低了对象之间的耦合度,提高了代码的可重用性和可测试性。
- 依赖注入:依赖注入是控制反转的一种实现方式,它允许开发者在配置文件中声明对象之间的依赖关系,由Spring容器在运行时将这些依赖关系注入到对象中。这种方式使得对象之间的依赖关系更加清晰和易于管理。
面向切面编程(AOP)
- Spring提供了面向切面的编程支持,允许开发者在程序执行的关键点插入额外的代码(如日志记录、事务管理、安全控制等),以实现对程序的动态修改和增强。这种编程方式提高了代码的内聚性和可维护性,使得业务逻辑更加清晰和易于理解。
框架灵活性
- Spring作为一个轻量级的J2EE框架,具有事务管理、持久化框架集成和Java Web服务等功能。应用程序可以根据需求引入相应的模块,以实现不同的功能。这种灵活性使得Spring能够适应各种应用场景和需求。
易于集成和扩展
- Spring框架易于与其他Java框架和库进行集成,如Spring MVC、MyBatis、Hibernate等。同时,Spring也提供了丰富的扩展点,允许开发者根据自己的需求对框架进行定制和扩展。
Spring Framework 主要模块有哪些?
Spring Framework中包含了多个模块,每个模块都提供了不同的功能,以满足开发人员在企业级应用开发中的需求。以下是Spring Framework的主要模块及其功能介绍:
核心容器模块:
- spring-core:提供Spring框架的基本组成部分和功能,如控制反转(IoC)和依赖注入(DI)的最基本实现。
- spring-beans:提供BeanFactory接口以及与XML文件中的元素解析相关的类和接口,是Spring框架中管理Bean的基础。
- spring-context:提供应用程序范围的上下文信息,如环境配置、国际化、事件传播等,还扩展了BeanFactory,添加了Bean生命周期控制等功能。
- spring-context-support:提供Spring应用程序上下文所需的其他功能,如缓存管理、邮件发送等。
- spring-expression:提供Spring SpEL(Spring Expression Language)表达式语言的实现,用于在运行时查询和操作对象图。
AOP(面向切面编程)模块:
- spring-aop:提供Spring AOP框架的实现,支持拦截器、切点、通知等AOP概念,允许开发人员在不修改源代码的情况下添加横切关注点(如日志、事务管理等)。
- spring-aspects:提供基于注解的AOP切面实现,进一步简化了AOP的使用。
- spring-instrument:提供类工具支持和类加载器实现,通常用于在JVM启动时生成代理类,以支持AOP等功能。
数据访问/集成模块:
- spring-jdbc:提供JDBC的抽象层和DAO支持,简化了JDBC编程。
- spring-tx:提供事务管理的支持,包括声明式事务处理和编程式事务处理。
- spring-orm:提供整合Hibernate、JPA等ORM框架的支持,方便进行对象与关系数据库之间的映射。
- spring-jms:提供支持JMS API的抽象层和Spring的JMS支持,用于消息的生产和消费。
- spring-oxm:提供一个抽象层以支撑OXM(Object-to-XML-Mapping),实现Java对象与XML数据之间的映射。
- spring-data:提供对各种数据存储技术(如关系型数据库、NoSQL数据库等)的支持,包括许多子模块(如spring-data-jpa、spring-data-mongodb等)。
Web模块:
- spring-web:提供Spring MVC框架的实现和基本的Web支持,如文件分块上传功能等。
- spring-webmvc:是spring-web的一部分,提供了一个完整的MVC实现,包括DispatcherServlet等组件,以及@Controller和@RequestMapping等注解。
- spring-websocket:提供WebSocket的支持,允许在Web应用程序中实现双向通信。
- spring-webflux:响应式Web框架模块,支持非阻塞I/O和反应式编程模型,用于构建异步、非阻塞、事件驱动的服务。
消息模块:
- spring-messaging:为消息传递提供了支持,包含了许多类和接口(如Message、MessageChannel、MessageHandler等),并提供了对STOMP协议的支持,可以在WebSocket上使用STOMP协议进行消息传递。
测试模块:
- spring-test:提供对Spring应用程序进行单元测试和集成测试的支持。它包含了许多实用的类和注解(如@RunWith、@SpringBootTest等),并提供了对JUnit、TestNG等测试框架的集成支持。
其他模块:
- spring-jcl:通用日志抽象,提供对不同日志框架的统一封装,方便开发人员在不同日志框架之间进行切换。
- spring-javadoc:提供Spring API的JavaDoc文档,方便开发人员查阅API文档。
- spring-boot:虽然不是Spring Framework的一部分,但是它是一个相关的项目,可以帮助简化Spring应用程序的构建和配置。
Spring 框架中都用到了哪些设计模式?
Spring框架中运用了多种设计模式,提高了代码的可复用性和可维护性,还增强了系统的灵活性和扩展性。
单例模式(Singleton Pattern):
- 在Spring默认的作用域中,每个Bean都是单例的。这通过Bean的scope属性进行控制,当scope为singleton时,即表示使用单例模式。
工厂模式(Factory Pattern):
- Spring通过BeanFactory和ApplicationContext等接口创建并管理对象实例,这是工厂模式的实现。
代理模式(Proxy Pattern):
- Spring提供了两种代理方式:JDK动态代理和CGLIB代理。
模板方法模式(Template Method Pattern):
- 在Spring的JdbcTemplate、HibernateTemplate、RestTemplate等模板类中,使用了模板方法模式。这些类定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现。
观察者模式(Observer Pattern):
- Spring事件机制(ApplicationEvent 和 ApplicationListener)实现了观察者模式,允许组件之间通过事件进行通信。
策略模式(Strategy Pattern):
- 在Spring中,策略模式主要用于实现不同的算法或策略。例如,Spring的TaskScheduler接口就定义了不同的任务调度策略,如同步执行、异步执行等。
适配器模式(Adapter Pattern):
- Spring的HttpMessageConverter接口也实现了适配器模式,将不同的数据类型转换为HTTP消息。
装饰器模式(Decorator Pattern):
- 在Spring中,装饰器模式通常用于在不修改原始代码的情况下,为对象添加额外的功能。Spring AOP中的切面可以看作是装饰器模式的应用,通过切面动态地增强已有对象的功能。
原型模式 (Prototype Pattern)
- 用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。Spring允许将Bean配置为原型模式,每次请求都会创建一个新的实例。例如,通过在Bean定义中设置 scope="prototype"。
桥接模式 (Bridge Pattern)
- 将抽象部分与实现部分分离,使它们可以独立变化。Spring的事务管理中,事务的抽象和具体的事务管理策略(如JDBC、JPA等)是分离的,通过桥接模式实现灵活的事务管理。
组合模式 (Composite Pattern)
- 将对象组合成树形结构以表示“部分-整体”的层次结构。Spring的
PropertyPlaceholderConfigurer
可以看作是组合模式的应用,它允许配置多个属性文件。
策略模式 (Strategy Pattern)
- 定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。Spring的事务管理策略(如
PlatformTransactionManager
)和数据源策略(如DataSource
)都是策略模式的实现。
建造者模式 (Builder Pattern)
- 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。Spring的
BeanDefinitionBuilder
用于构建复杂的Bean定义。
门面模式 (Facade Pattern)
- 提供一个统一的接口,用来访问子系统中的一群接口。ApplicationContext 提供了访问Spring容器中各种功能的统一接口,简化了客户端的使用。
Spring 配置文件作用?
Spring配置文件在Spring框架中扮演着至关重要的角色,它是用于描述应用程序组件、依赖关系和其他配置信息的重要文件。以下是Spring配置文件的主要作用:
定义Bean:
- Spring配置文件可以声明和配置各种Bean对象,包括服务层组件、数据访问层组件、控制层组件等。这些Bean对象在应用程序中扮演着重要的角色,并且可以通过Spring容器进行管理。
管理依赖关系:
- 通过配置文件,开发者可以定义各个Bean之间的依赖关系。Spring容器能够根据这些定义自动地进行依赖注入,将各个组件之间的关系解耦,从而提高系统的灵活性和可维护性。
配置AOP切面:
- Spring配置文件可以定义切面和切面中的通知。切面可以对应用程序中的某一类对象进行横切关注点的植入,如事务管理、日志记录等。通过配置切面,可以在不修改原有代码的情况下实现对特定功能的增强。
配置中间件和框架:
- 在Spring配置文件中,开发者可以配置各种中间件和框架,如数据库连接池、消息中间件、缓存框架等。这些配置使得开发者能够方便地使用这些中间件和框架的功能,从而提高系统的效率和性能。
管理和调整系统参数:
- Spring配置文件还可以用来管理和调整系统的各种参数,如数据库连接地址、日志级别等。通过配置文件,开发者可以灵活地定义这些参数值,而无需修改代码。
启用和配置框架特性:
- Spring提供了许多可选的框架特性,如事务管理、缓存管理等。开发者可以通过配置文件来启用和配置这些功能,以满足应用程序的需求。
环境配置:
- Spring配置文件可以定义应用程序运行时所需的环境配置,如开发环境、测试环境和生产环境等。通过将这些配置信息集中在一个配置文件中,可以方便地进行统一的管理和维护。
引入其他配置文件:
- 可以通过
<import>
标签引入其他配置文件,以实现模块化配置。
Spring 配置方式有哪些?
Spring 框架提供了多种配置方式,以适应不同的应用场景和开发需求。这些配置方式可以分为基于 XML 的配置、基于 Java 注解的配置以及基于 Java 类的配置。每种方式都有其特点和适用场景。
基于 XML 的配置
- 这是Spring框架早期最常用的配置方式,通过XML文件定义Spring容器的Bean和相关配置。
- XML配置文件通常以
.xml
为扩展名,例如applicationContext.xml
。 - XML配置文件遵循Spring框架定义的XML Schema,可以通过DTD或XSD进行验证。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.example.UserService">
<property name="userRepository" ref="userRepository"/>
</bean>
<bean id="userRepository" class="com.example.UserRepositoryImpl"/>
</beans>
加载 XML 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
基于 Java 注解的配置
常用注解
@Component
:标识一个组件,如服务、控制器等。@Service
:标识一个业务逻辑组件。@Repository
:标识一个数据访问组件。@Controller
:标识一个 MVC 控制器组件。@Autowired
:用于自动注入依赖。@Qualifier
:当存在多个相同类型的 Bean 时,用于指定具体的 Bean。@Value
:用于注入属性值。@Configuration
:标识一个配置类。@Bean
:用于在配置类中定义 Bean。
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(userRepository());
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
}
@Component
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
加载注解配置
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
基于 Java 类的配置
通过编写 Java 类并使用 @Configuration
和 @Bean
注解来定义 Bean 和它们的依赖关系。相比于 XML 配置,Java 类配置更加类型安全且易于重构。
特点
- 类型安全:编译时检查,减少运行时错误。
- 易于重构:IDE 支持重构工具,使得代码更易维护。
- 灵活性:可以利用 Java 语言特性,如条件判断、循环等。
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(userRepository());
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
}
加载 Java 类配置
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
混合配置
有时在一个项目中可能需要混合使用 XML 和 Java 配置。例如,在 XML 中定义部分 Bean,而在 Java 类中定义其他 Bean。Spring 允许这种混合配置的方式,提供了更大的灵活性。
在 Java 配置类中导入 XML 配置
@Configuration
@ImportResource("classpath:applicationContext.xml")
public class AppConfig {
// Java 配置
}
Spring Boot 自动配置
Spring Boot 提供了一套自动配置机制,它可以根据项目的依赖自动配置许多常见的设置。开发者只需要在 application.properties
或 application.yml
文件中进行少量配置即可。
特点
- 简化配置:通过约定优于配置的原则,减少了大量重复性的配置工作。
- 默认配置:提供了一系列合理的默认配置,适用于大多数应用。
- 自定义配置:允许开发者根据需要覆盖默认配置。
server.port=8081
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
启动 Spring Boot 应用
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Spring 提供了多种配置方式,包括基于 XML 的配置、基于 Java 注解的配置、基于 Java 类的配置、混合配置以及 Spring Boot 的自动配置。选择合适的配置方式取决于具体的应用需求和个人偏好。随着 Spring Boot 的流行,自动配置和基于 Java 的配置方式越来越受到青睐,因为它们提供了更好的类型安全性和代码可读性。
Spring 读取配置文件的方式?
读取配置文件的方法
使用 @Value
注解:
适用于读取少量配置属性的情况。可以直接在需要注入配置属性的字段上使用 @Value
注解,并使用 ${}
语法引用配置文件中的属性。例如:
@Component
public class MyService {
@Value("${server.port}")
private int serverPort;
// 其他代码
}
使用 @ConfigurationProperties
注解:
适用于读取大量配置属性的情况。可以创建一个配置类,并使用 @ConfigurationProperties
注解来指定配置文件的前缀。Spring会自动将配置文件中的属性绑定到该类的字段上。例如:
@ConfigurationProperties(prefix = "spring.datasource")
@Component
public class DataSourceProperties {
private String url;
private String username;
private String password;
// Getter 和 Setter 方法
}
在使用时,可以通过 @Autowired
注解将配置类注入到其他组件中。
使用 Environment
接口:
Environment
接口提供了访问配置文件属性的方法。可以通过 @Autowired
注解将 Environment
注入到组件中,并使用 getProperty
方法来获取配置文件的属性。例如:
@Component
public class MyComponent {
@Autowired
private Environment env;
public void someMethod() {
String serverPort = env.getProperty("server.port");
// 其他代码
}
}
读取自定义配置文件
使用 @PropertySource
注解:
可以在配置类上使用 @PropertySource
注解来指定自定义配置文件的路径。然后,可以使用 @Value
注解或 Environment
接口来读取配置文件的属性。例如:
@Configuration
@PropertySource("classpath:custom.properties")
public class CustomConfig {
// 可以使用 @Value 注解或 Environment 接口来读取 custom.properties 文件中的属性
}
配置 PropertySourcesPlaceholderConfigurer
Bean:
在Spring配置文件中定义一个 PropertySourcesPlaceholderConfigurer
Bean,并指定自定义配置文件的路径。然后,可以使用 @Value
注解来读取配置文件的属性。例如:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfig() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("custom.properties"));
return configurer;
}
}
使用Java原生方式读取配置文件
虽然Spring提供了多种读取配置文件的方法,但在某些情况下,可能需要使用Java原生方式来读取配置文件。例如,可以使用 Properties
类来读取 .properties
文件,或使用第三方库(如 SnakeYAML)来读取 .yml
文件。
Spring 配置文件的优先级和加载顺序?
Spring配置文件的优先级和加载顺序如下:
命令行参数:
- 具有最高优先级。通过命令行参数指定的配置会覆盖其他所有配置。
操作系统环境变量:
- 环境变量中以
SPRING_
开头的变量会被转换为配置属性。
- 环境变量中以
bootstrap
系列配置文件:bootstrap.properties
或bootstrap.yml
文件的加载顺序最高,通常用于系统级别的参数。
外部配置文件:
- 位于JAR包外部的配置文件(如
application.properties
或application.yml
)的优先级高于JAR包内部的配置文件。
- 位于JAR包外部的配置文件(如
特定环境配置文件:
- 特定环境的配置文件(如
application-dev.yml
)会覆盖默认的application.properties
或application.yml
中的相应属性。
- 特定环境的配置文件(如
类路径下的配置文件:
- 类路径下的
application.properties
或application.yml
文件(classpath:/
)。
- 类路径下的
类路径内
**config**
子目录的配置文件:- 类路径下的
config
子目录中的application.properties
或application.yml
文件(classpath:/config/
)。
- 类路径下的
项目根目录下的配置文件:
- 项目根目录下的
application.properties
或application.yml
文件。
- 项目根目录下的
项目根目录下
config
子目录的配置文件:- 项目根目录下的
config
子目录中的application.properties
或application.yml
文件。
- 项目根目录下的
文件类型优先级:
- 在同一路径下,
.properties
文件的优先级高于.yml
或.yaml
文件。
Spring 会按照上述顺序加载配置文件,后面加载的配置会覆盖前面加载的配置。这意味着,如果你在多个位置定义了相同的属性,那么高优先级位置的配置将覆盖低优先级位置的配置,所有的配置会形成互补配置。
Spring 如何通过注解配置?
在Spring框架中,通过注解配置文件是一种灵活且强大的配置方式。
一、基本步骤
- 添加必要的依赖: 确保项目中包含了Spring框架的依赖,特别是与注解配置相关的依赖,如
spring-context
。 - 创建配置类: 使用
@Configuration
注解来标识一个配置类,该类将包含Bean的定义和依赖关系的配置。 - 定义Bean: 在配置类中使用
@Bean
注解来定义Bean。@Bean
注解的方法将返回一个对象,该对象将被注册为Spring容器中的Bean。 - 启用组件扫描: 使用
@ComponentScan
注解来指定Spring在创建容器时要扫描的包。这样,Spring会自动注册这些包下用@Component
、@Repository
、@Service
、@Controller
等注解标注的类作为Bean。 - 引入外部配置文件: 如果需要引入外部的XML配置文件或.properties文件,可以使用
@ImportResource
注解和@PropertySource
注解。
二、常用注解
@Configuration:
用于定义配置类,替代传统的XML配置文件。
配置类中可以定义Bean的创建和依赖关系。
@Bean:
用于把当前方法的返回值作为Bean对象存入Spring的容器中。
属性
name
用于指定Bean的id,当不写时,默认值是当前方法的名称。
@ComponentScan:
用于指定Spring在哪些包下搜索
@Component
注解(包括@Repository
、@Service
、@Controller
等)。这样Spring容器就能自动扫描并注册这些Bean。
@Component:
- 一个通用的Spring组件注解,用于标识一个类作为Spring组件(Bean)自动注册到Spring上下文中。
@Repository:
用于标注数据访问层(DAO)组件,通常与持久化相关的操作。
将DAO类标注为Spring Bean。
@Service:
- 用于标注服务层(Service)组件,通常作为业务逻辑层,处理复杂的业务逻辑,例如事务管理、数据处理等。
@Controller:
- 用于标注控制层组件(Controller),处理HTTP请求和响应,通常用于Spring MVC框架中。
@Autowired:
- 用于自动装配Bean,Spring会自动将符合类型要求的Bean注入到标注了
@Autowired
的字段、构造方法或者方法中。
- 用于自动装配Bean,Spring会自动将符合类型要求的Bean注入到标注了
@Qualifier:
- 与
@Autowired
结合使用,用于指定具体要注入的Bean,当一个接口有多个实现类时特别有用。
- 与
@Value:
- 用于注入配置文件中的值到类的字段中,支持SpEL表达式。
@Scope:
- 用于指定Bean的作用域,包括
singleton
、prototype
、request
、session
、globalSession
、application
、websocket
等。
- 用于指定Bean的作用域,包括
@Import:
用于导入其他的配置类。
属性
value
用于指定其他配置类的字节码。
@PropertySource:
用于指定.properties文件的位置。
配置类中的Bean可以使用
@Value
注解来注入这些文件中的属性值。
@ImportResource:
- 用于引入Spring的XML配置文件。
@Configuration
@ComponentScan(basePackages = "com.example")
@PropertySource(value = {"classpath:application.properties"})
public class AppConfig {
@Value("${app.name}")
private String appName;
@Bean
public MyService myService() {
return new MyServiceImpl();
}
// 其他Bean的定义...
}
@Service
public class MyServiceImpl implements MyService {
// 业务逻辑...
}
@Controller
@RequestMapping("/example")
public class ExampleController {
@Autowired
private MyService myService;
// 处理HTTP请求的方法...
}
在这个示例中,AppConfig
是一个配置类,它使用@Configuration
注解进行标识,并使用@ComponentScan
注解指定了要扫描的包。此外,它还使用@PropertySource
注解引入了一个.properties文件,并使用@Value
注解注入了文件中的属性值。MyServiceImpl
是一个服务层的Bean,它使用@Service
注解进行标注。ExampleController
是一个控制层的Bean,它使用@Controller
注解进行标注,并自动装配了MyService
。
@Configuration 配置类注解有什么特点?
@Configuration
注解是 Spring 框架中用于定义配置类的关键注解。它标识一个类可以作为 Spring 应用上下文的 Bean 定义的来源。配置类通常包含一系列带有 @Bean
注解的方法,这些方法返回的对象将被注册为 Spring 容器中的 Bean。当Spring容器启动时,会扫描所有@Configuration注解标记的配置类,并将其加载到容器中。然后,通过ConfigurationClassPostProcessor
类处理这些配置类,将其转换为Bean定义并注册到Spring IoC容器中。
以下是 @Configuration
注解的主要特点:
替代 XML 配置
@Configuration
类提供了一种纯 Java 的方式来配置 Spring 容器,减少了对 XML 文件的依赖。- 通过 Java 代码进行配置,可以利用编译时检查、重构工具支持等特性,提高开发效率和代码质量。
Bean 方法注入
- @Configuration 注解的类可以包含一个或多个由@Bean注解的方法,这些方法定义了如何创建和配置Spring Bean。
- 这种方式将Bean的定义集中在一个或多个配置类中,方便管理和维护。
- 这种方式使得配置更加直观和易于理解,因为配置是通过Java代码而不是XML来完成的。
- 这种方式提供了更大的灵活性和控制力,适用于需要精细调整 Bean 创建过程的情况。
- 方法名默认作为Bean的id,但也可以通过@Bean注解的value或name属性来指定自定义的id。
代理机制
- 默认情况下,@Configuration 注解标记的配置类会被Spring容器代理,以确保@Bean方法之间的调用能够返回单例Bean实例。
- 代理行为可以通过@Configuration注解的proxyBeanMethods属性来控制,如果将其设置为false,则配置类不会被代理,每次调用@Bean方法都会返回一个新的Bean实例。
- 基于CGLIB的代理机制:Spring通过CGLIB代理技术来确保@Configuration类中的@Bean方法的单例特性。当第一次调用@Bean方法时,会创建Bean实例并将其缓存。之后再次调用同一 @Bean方法时,实际上是从缓存中获取已创建的实例,而不是重新创建。
- 直接调用
@Bean
方法(例如this.beanMethod()
)不会触发代理行为,而是按普通方法调用处理,可能导致多个实例被创建。因此应避免这种方式。
条件化 Bean
- 可以结合
@Conditional
注解及其子注解(如@Profile
,@ConditionalOnProperty
等)来根据特定条件决定是否注册某个 Bean。 - 使得应用程序能够根据不同环境或配置动态调整其行为,增强了灵活性。
@Configuration
@Profile("dev")
public class DevelopmentConfig {
// 开发环境下的配置
}
@Configuration
@Profile("prod")
public class ProductionConfig {
// 生产环境下的配置
}
@Configuration
public class AppConfig {
@Bean
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public MyFeature myFeature() {
return new MyFeature();
}
}
集成第三方库
- 配置类非常适合用来配置和初始化第三方库或框架,因为它们允许你以编程方式创建和配置复杂的对象图。
- 简化了与外部依赖的集成,同时保持了应用内部的一致性和可维护性。
自动装配
- 由于@Configuration注解本身带有@Component注解,因此它可以被Spring的组件扫描机制自动检测。
支持模块化配置
- 可以使用
@Import
注解导入其他配置类,构建层次化的配置体系。 - 有助于组织和模块化配置逻辑,特别是对于大型项目而言。
与其他注解结合使用:
- @Configuration类通常与其他Spring注解(如@ComponentScan、@PropertySource等)结合使用,以提供全面的配置机制。
- 例如,@ComponentScan注解可以用于指定Spring容器应该扫描哪些包以查找组件;@PropertySource注解可以用于将属性文件的值导入Spring环境。
外部化配置
- 配置类可以方便地读取外部配置文件(如properties或yml文件)中的属性,并将这些属性注入到Bean中。
@Configuration
public class AppConfig {
// 使用 @Value 注解:
@Value("${app.name}")
private String appName;
@Bean
public MyService myService() {
return new MyServiceImpl(appName);
}
}
提供额外的语义信息:
- 与@Component注解相比,@Configuration注解为配置类提供了额外的语义信息。这有助于开发人员更好地理解代码的结构和目的,并提高代码的可读性和可维护性。
使用 @Configuration 的约束?
- 配置类必须以类的方式提供(比如不能是由工厂方法返回的实例)。
- 配置类必须是非 final 的。
- 配置类必须是非本地的(即可能不在方法中声明),native 标注的方法。
- 任何嵌套的配置类必须声明为 static。
- @Bean 方法可能不会反过来创建更多的配置类。
- 如果配置类中包含了BeanFactory后处理器,可能会导致配置类提前创建,从而造成依赖注入失败。这种情况下,可以考虑使用静态工厂方法或直接为@Bean的方法参数进行依赖注入。
什么是控制反转和依赖注入?
控制反转(IoC)
定义:控制反转是面向对象编程中的一种设计原则,它通过将对象之间的依赖关系从对象内部转移到外部容器(如Spring框架的IoC容器)中管理,来实现对象之间的解耦。
特点:
高等级的代码不能依赖低等级的代码。
抽象接口不能依赖具体实现。
对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它,即依赖被注入到对象中。
依赖注入(DI)
- 定义:依赖注入是控制反转的一种实现方式。它是指,在程序运行过程中,当一个对象需要调用另一个对象来协助其完成任务时,不是由该对象自己创建或查找所需的对象,而是由外部容器(如IoC容器)在运行时动态地将这些依赖对象注入到该对象中。
优点:
降低耦合度:依赖注入将类的依赖关系和类本身分离,使得类的实现更加灵活。
提高代码复用性:由于依赖关系由外部容器管理,因此依赖对象可以被多个类共享,提高了代码的复用性。
增强代码可测试性:在测试时,可以很容易地替换依赖对象,从而提高了代码的可测试性。
应用场景:依赖注入广泛应用于大型项目和框架中,如Java的Spring框架、前端的Angular框架等。这些框架通过依赖注入来管理对象的生命周期和依赖关系,降低代码之间的耦合度,从而提高了代码的灵活性、可测试性和可维护性,显著提升代码的质量。
什么是 Spring IOC 容器?
Spring IoC(Inversion of Control,控制反转)容器是Spring框架的核心组件之一。
IoC容器负责实例化、配置和组装Bean。通常所说的Spring容器指的是IoC容器,它利用IoC(控制反转)技术促进了松耦合。
在IoC模式下,对象的创建、初始化、销毁等生命周期的管理不再由对象本身负责,而是由外部容器(即IoC容器)来负责。这样,对象就无需关心其依赖对象的创建和管理,只需关注自身的业务逻辑即可。当对象需要某个依赖时,它会通过容器来获取,而不是自己创建或查找。
Spring IoC容器通过读取配置文件或注解等方式,了解需要实例化的对象及其依赖关系,并在运行时动态地将这些对象创建出来,然后按照依赖关系将它们组装在一起。这样,开发者就可以通过配置文件或注解来灵活地管理对象及其依赖关系,而无需在代码中硬编码这些关系。
Spring IoC容器支持多种类型的Bean定义,包括基于Java配置的Bean、基于XML配置的Bean以及基于注解的Bean等。开发者可以根据自己的需求选择适合的Bean定义方式,并通过IoC容器来管理这些Bean。
总之,Spring IoC容器是Spring框架中实现控制反转的核心组件,它负责对象的创建、配置和组装,使得开发者可以更加灵活地管理对象及其依赖关系,从而提高了代码的可维护性和可扩展性。
Spring IOC 的优点?
Spring IOC(Inversion of Control,控制反转)容器是Spring框架的核心功能之一。它极大地简化了Java应用程序的开发和维护工作。
- 解耦和模块化:Spring IOC容器通过依赖注入将组件之间的依赖关系外部化,降低了耦合度,使得应用程序更容易维护和扩展。
- 灵活的配置:Spring IOC容器允许将配置信息外部化,通常使用XML配置文件、Java注解或Java配置类。这使得配置更加灵活,可以在不修改代码的情况下进行更改。
- 自动管理依赖关系:Spring IOC容器通过依赖注入自动管理组件之间的依赖关系,无需手动编写繁琐的依赖管理代码。这简化了开发,提高了可维护性。
- 测试友好:由于组件之间的松散耦合,Spring应用程序更容易进行单元测试和集成测试,从而提高了代码质量和可靠性。
- 重用和扩展性:Spring IOC容器提供了丰富的库和模块,可以轻松地集成各种技术组件,如数据库访问、事务管理、消息队列等,从而增强了应用程序的重用和扩展性。
Spring IOC 容器的功能?
Bean的实例化:IoC容器负责根据配置(如XML文件、注解或Java配置类)来创建Bean实例。这些Bean可以是普通的Java对象,也可以是具有特定功能的组件。
依赖注入:依赖注入是IoC容器的核心功能之一。它允许在创建Bean时,将Bean所需的依赖关系通过配置注入到Bean中,而不是在Bean内部直接创建或查找依赖对象。这有助于降低代码的耦合度,提高代码的可维护性和可测试性。
Bean的生命周期管理:IoC容器还负责管理Bean的生命周期,包括Bean的创建、初始化、使用和销毁等阶段。在Bean创建后,容器可以调用初始化方法(如
afterPropertiesSet
或@PostConstruct
)来进行一些初始化操作。在容器关闭时,容器会调用销毁方法(如destroy
或@PreDestroy
)来进行清理操作。作用域管理:除了单例和原型,Spring还支持请求(Request)、会话(Session)、应用程序(Application)和WebSocket作用域。
配置管理:IoC容器提供了一种灵活的方式来配置Bean之间的依赖关系。这些配置信息通常是通过XML文件、注解或Java配置类来提供的。这提供了灵活性,允许开发者根据项目的需求选择最适合的配置方式,开发者可以轻松地修改Bean的配置,而无需修改代码。
支持AOP(面向切面编程):IoC容器与Spring AOP紧密集成,允许开发者在Bean中定义切面(Aspect),以实现横切关注点(如日志记录、事务管理等)的分离。
事件发布与监听:IoC容器支持事件发布与监听机制,允许Bean之间通过事件进行通信。这有助于在Bean之间实现松耦合的交互。
国际化支持:IoC容器提供了国际化支持,允许开发者根据用户的语言环境来加载不同的资源文件,以实现应用程序的本地化。
环境抽象(Environment Abstraction):提供了对不同环境(如开发、测试、生产)的配置抽象,可以轻松切换和读取环境特定的配置。
类型安全和表达式语言:支持使用Spring Expression Language(SpEL)进行复杂的查询和操作。
资源加载与访问:IoC容器提供了对**资源抽象 (Resource Abstraction),**统一访问各种资源提供加载和访问支持,如文件系统、类路径、URL 等,允许开发者轻松地读取配置文件、属性文件等资源。
**异常处理:**提供了一套丰富的异常层次结构,用于处理各种异常情况。并将底层异常转换为更高级别的异常,便于统一处理。
扩展点:提供了多个扩展点,如
BeanFactoryPostProcessor
、BeanPostProcessor
,允许在容器启动的不同阶段插入自定义逻辑。集成其他框架:IoC容器提供了与其他Java框架(如MyBatis、Hibernate等)的集成支持,使得开发者可以在Spring应用程序中使用这些框架的功能。
Spring 中 IOC容器的实现?
Spring IoC 容器的实现主要基于两个核心接口:BeanFactory
和 ApplicationContext
。这两个接口提供了不同的功能和用途,但都用于管理和配置应用程序中的 Bean。
BeanFactory
: 是 Spring IoC 容器的基础接口,它提供了基本的依赖注入功能。BeanFactory
接口定义了读取 Bean 配置文件、管理 Bean 的生命周期以及获取 Bean 等基本操作。
主要方法
getBean(String name)
:根据名称获取 Bean。getBean(Class<T> requiredType)
:根据类型获取 Bean。containsBean(String name)
:检查容器中是否存在指定名称的 Bean。isSingleton(String name)
:检查指定名称的 Bean 是否是单例。isPrototype(String name)
:检查指定名称的 Bean 是否是原型。getType(String name)
:获取指定名称的 Bean 的类型。getAliases(String name)
:获取指定名称的 Bean 的别名。
实现类
DefaultListableBeanFactory
:这是BeanFactory
的一个标准实现,提供了完整的 Bean 工厂功能,并且可以被继承以扩展其功能。XmlBeanFactory
(已弃用):早期版本中使用 XML 文件来加载 Bean 的工厂。从 Spring 3.0 开始,推荐使用ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
。
ApplicationContext
: 是 BeanFactory
的子接口,它不仅提供了 BeanFactory
的所有功能,还增加了企业级应用所需的功能,如 AOP 支持、国际化、事件发布等。ApplicationContext
是 Spring 应用程序中最常用的 IoC 容器。
主要方法
- 继承自
BeanFactory
的所有方法。 getBeanNamesForType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
:获取指定类型的 Bean 名称。getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
:获取指定类型的 Bean 实例。getMessage(String code, Object[] args, String defaultMessage, Locale locale)
:获取国际化消息。publishEvent(ApplicationEvent event)
:发布应用事件。
常见实现
ClassPathXmlApplicationContext
:从类路径加载 XML 配置文件。FileSystemXmlApplicationContext
:从文件系统加载 XML 配置文件。AnnotationConfigApplicationContext
:从基于 Java 的配置类加载配置。WebApplicationContext
:用于 Web 应用,通常由ContextLoaderListener
或DispatcherServlet
自动创建。ApplicationContext
通常是分层的:根应用上下文 (Root WebApplicationContext),由ContextLoaderListener
创建,通常包含整个应用共享的 Bean;Web 应用上下文 (WebApplicationContext),由DispatcherServlet
创建,通常包含特定于 Web 层的 Bean。
BeanFactory 和 ApplicationContext 的区别?
BeanFactory:Spring IoC 容器顶级接口,是最基础的容器接口,是一个 Bean 工厂,使用简单工厂模式,可以理解为含有 Bean 集合的工厂类,作用是管理 Bean,包括实例化、定位、配置对象及建立这些对象间的依赖。**BeanFactory 实例化后并不会自动实例化 Bean,只有当 Bean 被使用时才实例化与装配依赖关系,属于延迟加载,适合多例模式。**BeanFactory 虽然功能强大但使用起来相对繁琐,因此在新版本的Spring中,推荐使用更高级的容器ApplicationContext。
ApplicationContext:Spring 框架中的一个核心接口,它是BeanFactory 子接口,负责管理和配置应用中的对象(称为Beans),并提供了更全面的功能。除了包含BeanFactory的所有功能外,ApplicationContext 还添加了国际化支持、资源访问、事件传播以及更高级的容器特性,如自动装配和生命周期管理等。容器会在初始化时对配置的 Bean 进行预实例化,Bean 的依赖注入在容器初始化时就已经完成,属于立即加载。同时,ApplicationContext还提供了对环境的抽象和Web支持等功能。在新版本的Spring中,推荐使用ApplicationContext或其子类来代替BeanFactory作为IOC容器。ApplicationContext提供了更丰富的功能和更便捷的使用方式,因此在实际应用中得到了广泛的应用。
Spring 依赖注入的实现方法有哪些?
Spring 框架提供了多种依赖注入(Dependency Injection, DI)的实现方法,每种方法都有其特定的使用场景和优势。以下是 Spring 中常见的依赖注入实现方法:
构造函数注入 (Constructor Injection)
优点:
不可变性:一旦构造完成,依赖关系就固定了,不会被修改。
就绪状态:对象构造完成后就处于就绪状态,可以马上使用。
强制依赖:Spring推荐的依赖注入方式,必须提供所有必需的依赖项,否则无法创建 Bean。
易于测试:可以通过构造函数传入模拟对象进行单元测试。
缺点:
当依赖对象较多时,构造方法的参数列表会比较长。
构造方法无法被继承,无法设置默认值。
对于非必需的依赖处理可能需要引入多个构造方法,参数数量的变动可能会造成维护的困难。
public class ExampleService {
private final Dependency dependency;
public ExampleService(Dependency dependency) {
this.dependency = dependency;
}
// 业务逻辑
}
Setter 方法注入 (Setter Injection)
优点:
- 可选依赖:可以有选择地注入依赖,不强制要求所有依赖都必须提供。
- 灵活性:可以在运行时动态更改依赖。
public class ExampleService {
private Dependency dependency;
@Autowired
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
// 业务逻辑
}
字段/属性注入 (Field Injection)
优点:简洁,代码更简洁,不需要额外的方法或构造函数。
缺点:
测试困难:字段注入使得类难以进行单元测试,因为依赖不能轻易被替换。
松散耦合:不如构造函数注入那样强制依赖关系,可能导致潜在的空指针异常。
隐藏必需字段:Spring不推荐使用字段注入,因为它可能会隐藏必需的字段,这些字段本应在构造器中被赋值。
public class ExampleService {
@Autowired
private Dependency dependency;
// 业务逻辑
}
方法(非 setter 方法)注入 (Method Injection)
优点:灵活可以在方法调用时注入依赖,适用于某些特定场景。
public class ExampleService {
private Dependency dependency;
@Autowired
public void injectDependency(Dependency dependency) {
this.dependency = dependency;
}
// 业务逻辑
}
接口注入 (Interface Injection)
缺点:这种方式在 Spring 中不常用,但在某些框架中可能会见到。通常不推荐使用,因为它增加了不必要的复杂性。在这种方式中,依赖关系通过接口方法来注入,将需要注入的依赖类定义为接口类型,在类中使用@Autowired注解注入实现该接口的类。在接口注入中,首先定义一个接口,该接口包含一个或多个用于设置依赖关系的方法。然后,具体的实现类实现这个接口,并在实现中处理依赖关系的注入。
public interface DependencyAware {
void setDependency(Dependency dependency);
}
@Component
public class MyService implements DependencyAware {
private Dependency dependency;
@Override
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}
自动装配 (Autowiring)
Spring 可以自动检测并注入依赖,而无需显式指定。
类型:
byType:根据类型自动装配。
byName:根据名称自动装配。
constructor:通过构造函数自动装配。
no:不使用自动装配。
<bean id="exampleService" class="com.example.ExampleService" autowire="byType"/>
或者在 Java 配置中:
@Configuration
public class AppConfig {
@Autowired
public ExampleService exampleService(Dependency dependency) {
return new ExampleService(dependency);
}
}
总结
- 构造函数注入:推荐用于强制性的依赖,确保不可变性和易于测试。
- Setter 方法注入:适用于可选的依赖,允许在运行时动态更改。
- 字段注入:代码简洁,但不利于测试和维护。
- 方法注入:适用于特定场景,如需要在方法调用时注入依赖。
- 自动装配:简化配置,但可能降低代码的可读性和可维护性。
- 接口注入 :不推荐。
选择合适的依赖注入方式取决于具体的应用场景和需求。理解这些方法的特点和适用场景有助于更好地设计和管理应用程序中的依赖关系。
Spring 构造方法注入和设值注入有什么区别?
构造方法注入 vs. 设值注入
特性 | 构造方法注入 | 设值注入 |
---|---|---|
依赖强制性 | 必须提供所有依赖 | 可以选择性地提供依赖 |
不可变性 | 依赖关系一旦构造完成便不可更改 | 依赖关系可以在运行时更改 |
测试友好性 | 更易于测试,因为依赖关系在构造函数中明确 | 测试时需要手动调用 setter 方法 |
编译时检查 | 支持编译时检查依赖关系 | 编译时不能检查依赖关系 |
适用场景 | 适合强制依赖、复杂初始化逻辑 | 适合非必需依赖、默认值、简单的依赖关系 |
灵活性 | 较低,因为依赖关系固定 | 较高,允许在运行时更改依赖 |
总结
- 构造方法注入 推荐用于强制性的依赖,确保不可变性和易于测试,特别适用于需要保证 Bean 完整性和复杂初始化逻辑的场景。
- 设值注入 更适合于可选的依赖,提供更大的灵活性,尤其是在需要在运行时动态更改依赖的情况下。
Spring 如何通过注解创建 Bean?
Spring 提供了多种注解来定义和管理 Bean,其中一些最常用的注解包括 @Component
、@Repository
、@Service
和 @Controller
。此外,@Bean
注解通常用于在配置类中手动定义 Bean。
@Component
- 把当前类对象存入 Spring 容器中,相当于在 xml 中配置一个 bean 标签。value 属性指定 bean 的 id,默认使用当前类的首字母··小写的类名。
@Controller,@Service,@Repository
- 都是 @Component 的衍生注解,作用及属性都是一模一样的。
- 提供了更加明确语义,@Controller 用于表现层,@Service用于业务层,@Repository用于持久层。如果注解中有且只有一个 value 属性要赋值时可以省略 value。
@Bean
- 如果想将第三方的类变成组件又没有源代码,也就没办法使用 @Component 进行自动配置,这种时候就要使用 @Bean 注解。
- 被 @Bean 注解的方法返回值是一个对象,将会实例化,配置和初始化一个新对象并返回,这个对象由 Spring 的 IoC 容器管理。name 属性 用于给当前 @Bean 注解方法创建的对象指定一个名称,即 bean 的 id。
- 当使用注解配置方法时, 如果方法有参数,Spring 会去容器查找是否有可用 bean对象,查找方式和 @Autowired 一样
- @Bean 注解通常用于在配置类(带有
@Configuration
注解的类)中手动定义 Bean。
@ComponentScan
- 启用组件扫描,为了让 Spring 识别这些注解并创建相应的 Bean,你需要在配置类或启动类上启用组件扫描。这通常通过
@ComponentScan
注解或@SpringBootApplication
(它包含了@ComponentScan
)来实现。
Spring 依赖注入的相关注解?
@Autowired
- 用于自动装配Bean,它可以自动从Spring上下文中找到合适的Bean来注入。
- 可以用在构造函数、Setter方法和字段上。
- 默认情况下,按类型匹配Bean进行注入。
- 如果存在多个相同类型的Bean,则进一步按名称匹配,如果仍有多个看Bean上是否包含**@Primary** 注解,如果包含就返回。不能把多个Bean都设置为@Primary ,不然会抛出
NoUniqueBeanDefinitionException
这个异常。 - 也可以在Bean上配置**@Priority注解**,它有个int类型的属性value,可以配置优先级大小。数字越小的,就被优先匹配。不能把多个Bean的优先级配置成相同大小的数值,否则
NoUniqueBeanDefinitionException
异常。Priority的包在javax.annotation.Priority,如果想使用它还要引入maven 依赖。 - 如果找不到匹配的Bean,将抛出异常,除非将required属性设置为false。
@Qualifier
- 与@Autowired配合使用,用于指定Bean的名称,从而解决多个相同类型Bean的注入问题。
- 可以用在字段、Setter方法的参数和构造函数参数上。
@Resource
- 用于指定名称注入Bean,它是JSR-250提供的注解,Spring对其进行了支持。
- 可以用在字段、Setter方法和构造函数上。
- 默认情况下,按名称匹配Bean进行注入,如果找不到按名称匹配的Bean,则按类型匹配。
- 如果指定了 name 或 type,则按指定的进行装配。
- 如果仍然找不到匹配的Bean,则不会抛出异常,而是返回null(在Spring 4.3及更高版本中,如果找不到匹配的Bean,会抛出异常,但可以通过设置@Resource的
shareable
属性为false
和lookup
方法为true
来改变这一行为,不过这并不是标准的依赖注入用法)。
@Inject
- 与@Autowired类似,用于自动装配Bean,但它是JSR-330提供的注解,更加标准化。
- 可以用在构造函数、Setter方法和字段上。
- 按类型匹配Bean进行注入。
- 如果存在多个相同类型的Bean,则可能需要配合@Qualifier使用来指定Bean名称。
- 找不到匹配的Bean时,将抛出异常。
@Component
用于标记类为Spring组件,使其被Spring IoC容器管理。这些注解包括@Service、@Controller、@Repository等,它们分别标记Service层类、Controller层类和数据存储层的类。
适用类定义上。
其特化注解
@Component:泛指一个组件,Spring扫描注解配置时,会标记这些类要生成Bean。
@Service:标记在服务层(业务逻辑层)的类。
@Controller:标记在控制层(MVC控制器)的类。
@Repository:标记在数据访问层(DAO层)的类,主要用于操作数据库。
@Value :用于注入基本数据类型和 String 类型。
使用@Component 定义Bean的名称默认是什么?
使用 @Component
注解(或其派生注解如 @Service
, @Repository
, @Controller
)来定义一个 Bean 时,如果没有显式指定 Bean 的名称,Spring 会AnnotationBeanNameGenerator.buildDefaultBeanName
根据类名生成一个默认的 Bean 名称。
- 如果类名以一个大写字母开头,后面跟随小写字母和其他字符,那么默认的 Bean 名称将是类名的首字母小写版本。例如,
MyComponent
类的默认 Bean 名称将是myComponent
。 - 如果类名的前两个字母都是大写(这种情况通常出现在遵循某些命名约定或缩写的类中),那么默认的 Bean 名称将保持不变。例如,
URLComponent
类的默认 Bean 名称将是URLComponent
。
自定义 Bean 的名称,可以通过 @Component
注解的 value
或 name
属性来实现。
@Component(value = "customBeanName")
public class MyComponent {
// Your code here
}
使用多个名称
如果你需要为一个 Bean 指定多个名称,可以使用 name
属性,并以逗号分隔各个名称。例如:
@Component(name = "name1, name2, name3")
public class MyService {
// 类的内容
}
在这种情况下,MyService
类的 Bean 将会有三个名称:name1
, name2
, 和 name3
。
使用@Bean 定义Bean的默认名称是什么?
默认名称:Spring 会将带有
@Bean
注解的方法名作为 Bean 的名称。这意味着如果方法名为myService
,那么默认的 Bean 名称也将是myService
。自定义 Bean 名称:如果你想为 Bean 指定一个不同于方法名的名称,可以在
@Bean
注解中通过name
属性来指定。你可以提供一个或多个名称(别名),以逗号分隔。
@Configuration
public class AppConfig {
// myService() 方法返回的 Bean 默认名称为 myService。
@Bean
public MyService myService() {
return new MyServiceImpl();
}
// 尽管方法名为 myService,但 Bean 的名称将是 customBeanName。
@Bean(name = "customBeanName")
public MyService myService() {
return new MyServiceImpl();
}
// Bean 可以通过 primaryService 或 mainService 来引用。
@Bean({"primaryService", "mainService"})
public MyService myService() {
return new MyServiceImpl();
}
}
注意事项
- 唯一性:确保 Bean 名称在 Spring 容器中是唯一的,以避免冲突。如果有两个或更多的 Bean 使用相同的名称,可能会导致不可预测的行为。
- 大小写敏感:Bean 名称是大小写敏感的,因此
myService
和MyService
在 Spring 容器中被视为不同的 Bean 名称。 - 命名规范:遵循一致的命名规范有助于提高代码的可读性和维护性。通常推荐使用小写字母开头的驼峰命名法(CamelCase)。
@Component 和 @Bean 的区别是什么?
@Component
和 @Bean
都是 Spring 框架中用于定义和管理 Bean 的注解,但它们有不同的使用场景和方式。
- 自动检测 vs 手动定义:
@Component
用于自动检测并注册 Bean,而@Bean
用于在配置类(带有@Configuration
注解的类)中手动定义和配置 Bean。 - 类级别 vs 方法级别:
@Component
注解直接作用于类上,而@Bean
注解作用于方法上。 - 使用场景:
@Component
更适合用于标记普通的 Spring 组件,而@Bean
更适合用于需要手动定义和配置 Bean 的场景,如第三方库的类或者需要动态创建的 Bean。 - Bean 默认名称:
@Component
默认情况下,Spring 会使用类名首字母小写作为 Bean 的名称。@Bean
默认情况下, 会使用方法的名称作为 Bean 的名称。
Spring 中的自动装配是什么?
定义与功能
Spring 的自动装配(autowiring)是一种依赖注入(Dependency Injection, DI)机制,它允许 Spring 容器自动解析并注入 Bean 的依赖关系,而无需显式地在代码中指定这些依赖。通过自动装配,开发者可以减少配置工作量,简化应用程序的维护,并提高代码的可读性和模块化程度。
优点:
简化配置:自动装配减少了XML配置或注解配置的工作量,提高了开发效率。
灵活性:自动装配可以根据类型、名称等条件自动匹配依赖关系,提高了配置的灵活性。
增强模块化:促进了组件之间的松耦合,有助于构建更清晰、更易测试的应用程序结构。
缺点:
- 隐式依赖:自动装配可能导致隐式的依赖关系,使得代码的可读性和可维护性降低。
- 冲突问题:自动装配可能引发Bean类型或名称冲突问题,导致注入失败或注入错误的Bean。
- 歧义性问题:当存在多个相同类型的 Bean 时,可能会导致自动装配失败。此时,可以使用
@Qualifier
或其他方式明确指定要注入的 Bean。 - 性能影响:虽然自动装配简化了开发流程,但过度依赖可能导致难以追踪的依赖关系,影响应用的性能和调试难度。
- 调试困难:自动装配使得依赖关系不明确,增加了调试和排查问题的难度。
应用场景:
- 大型项目或模块化应用程序:在这些项目中,组件之间存在复杂的依赖关系,自动装配可以简化配置,减少出错的可能性。
- 简单的依赖关系:对于简单的依赖关系,自动装配可以显著简化配置,提高开发效率。
Spring 自动装配匹配模式有哪些?
- 默认不自动装配:即
no
模式,表示不进行自动装配,所有依赖必须手动配置。 - 按名称自动装配(
byName
):Spring容器会查找与依赖属性名称匹配的Bean,并将其注入到目标Bean中。要求目标 Bean 的名称与属性名完全一致,这种方式通常用于需要明确指定依赖关系的场景。 - 按类型自动装配(
byType
):Spring容器会查找与依赖属性类型匹配的Bean,并将其注入到目标Bean中。如果存在多个相同类型的Bean,则需要进行额外的配置(如使用@Primary注解或@Qualifier注解)来指定首选的Bean,否则则抛出异常。 - 构造函数自动装配(
constructor
):Spring容器会查找与构造器参数类型匹配的Bean,并将其作为参数传递给目标Bean的构造函数。这种方式通常用于确保依赖对象是必须的,不能为空。 - 自动选择(
autodetect
):首先尝试使用构造器注入(constructor
),如果失败则退回到按类型注入(byType
)。已其标注为@Deprecated
。
Spring 中的自动装配配置方式?
- 使用@Autowired注解:这是最常用的自动装配方式。@Autowired注解可以应用到Bean的属性变量、属性的setter方法、非setter方法及构造函数等。对于构造器注入,
@Autowired
是可选的,因为 Spring 默认会对唯一构造器进行自动装配。Spring会在容器中查找与注解标注的变量或方法参数类型匹配的Bean,并将其注入。如果容器中有多个相同类型的Bean,Spring会抛出异常,除非使用@Qualifier注解来指定具体的Bean名称。 - 使用@Resource注解:@Resource注解的功能与@Autowired类似,但区别在于@Resource可以通过Bean实例名称进行装配。它有两个重要属性:name和type,其中name属性指定Bean的名称,type属性指定Bean的类型。
- 使用@Inject注解:来自 JSR-330 规范的注解,功能类似于
@Autowired
,但在某些情况下可能更通用。 - 使用XML配置:在早期的Spring版本中,可以通过XML配置文件来指定自动装配的方式。例如,可以使用autowire="byType"或autowire="byName"等属性来开启自动装配。然而,随着Spring注解的普及,这种方式已经逐渐被淘汰。
- 构造函数自动装配:Spring还支持通过构造函数参数进行自动装配。在这种情况下,Spring会查找与构造器参数类型匹配的Bean,并将其作为参数传递给目标Bean的构造函数。
@Autowired 装配查找顺序是什么?
@Autowired
注解在 Spring 中用于自动装配依赖,其查找和注入依赖的顺序遵循一定的规则。
优先级:构造器注入
Spring 首选使用构造器注入来满足依赖关系。如果你有一个类只有一个构造器,并且该构造器有参数需要注入,Spring 会默认选择构造器注入而不需要显式地标注
@Autowired
。java@Component public class MyService { private final MyRepository myRepository; // 构造器注入,@Autowired 可选 public MyService(MyRepository myRepository) { this.myRepository = myRepository; } }
按类型匹配(byType)
如果构造器注入不适用或不存在唯一构造器,Spring 将尝试根据依赖的类型来查找合适的 Bean 进行注入。这意味着它会在应用上下文中查找与所需依赖类型匹配的 Bean。
单个匹配:如果有且仅有一个匹配类型的 Bean,则直接注入。
多个匹配:如果存在多个相同类型的 Bean,则会抛出
NoUniqueBeanDefinitionException
异常,除非进一步指定。
java@Component public class MyService { @Autowired private MyRepository myRepository; // 假设只有一个 MyRepository 类型的 Bean }
按名称匹配(byName)
当按照类型匹配失败时(即存在多个相同类型的 Bean),Spring 会尝试通过属性名或方法名来匹配 Bean 名称。例如,如果属性名为
myRepository
,那么 Spring 会寻找名为myRepository
的 Bean 来进行注入。java@Component("specificMyRepository") public class SpecificMyRepository implements MyRepository { // ... } @Component public class MyService { // 如果没有其他 MyRepository 类型的 Bean,将注入名为 "specificMyRepository" 的 Bean @Autowired private MyRepository myRepository; }
使用
@Qualifier
明确指定为了避免歧义性问题,可以使用
@Qualifier
注解明确指定要注入的 Bean 名称或别名。这在存在多个相同类型的 Bean 时特别有用。java@Component public class MyService { @Autowired @Qualifier("specificMyRepository") // 指定注入名为 "specificMyRepository" 的 Bean private MyRepository myRepository; }
使用
@Primary
标记首选 Bean当存在多个相同类型的 Bean 时,你可以使用
@Primary
注解标记一个 Bean 作为首选项。这样,在没有其他指示的情况下,Spring 会优先选择被@Primary
标记的 Bean 进行注入。java@Component @Primary public class PrimaryMyRepository implements MyRepository { } @Component public class SecondaryMyRepository implements MyRepository { } @Component public class MyService { @Autowired private MyRepository myRepository; // 默认注入 PrimaryMyRepository }
处理可选依赖
@Autowired注解默认要求依赖对象必须存在,如果按照上述顺序都无法找到匹配的bean,则会抛出BeanCreationException异常。如果允许为null,可以使用
@Autowired(required = false)
或者Optional
来实现。java@Component public class MyService { @Autowired(required = false) private MyRepository myRepository; // 如果没有找到 MyRepository 类型的 Bean,myRepository 将为 null }
@Autowired
的装配查找顺序如下:
- 构造器注入:首先尝试使用构造器注入。
- 按类型匹配:其次,根据依赖的类型查找合适的 Bean。
- 按名称匹配:再次,尝试通过属性名或方法名来匹配 Bean 名称。
- 使用
@Qualifier
明确指定:最后,可以通过@Qualifier
注解明确指定要注入的 Bean。 - 使用
@Primary
标记首选 Bean:在存在多个相同类型的 Bean 时,@Primary
可以帮助指定首选项。 - 处理可选依赖:对于可选依赖,可以使用
@Autowired(required = false)
或Optional
。
@Resource 装配查找顺序是什么?
同时指定name和type:从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
指定了name:从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
指定了type:从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常。
未指定name和type:
默认按照byName方式进行装配,即使用字段名或属性名作为bean名称寻找依赖对象。
如果没有找到匹配的bean,则回退为按类型(byType)装配。
如果仍然没有找到匹配的bean,但在容器中存在一个原始类型相匹配的bean,则会自动装配这个bean。
Autowire 和 @Resource 的区别是什么?
@Autowired
和 @Resource
都是 Spring 框架中用于依赖注入的注解,但它们有一些关键的区别。
来源和定义
@Autowired
是 Spring 框架提供的注解,用于自动注入依赖对象。@Resource
是由 JSR-250 规范提供的注解,也被 Spring 框架支持用于依赖注入。
查找顺序
@Autowired
默认按照类型(byType)进行查找依赖对象。如果存在多个同类型的 Bean,它会尝试按照名称(byName)进行匹配,或者使用@Qualifier
注解来指定具体的 Bean 名称,使用效果和@Resource一样。@Resource
默认按照名称(byName)进行查找依赖对象。如果找不到对应的 Bean,它会按照类型(byType)进行查找。
参数支持
@Autowired
注解只支持一个required
参数,用于指定依赖对象是否必须存在。如果设置为false
,则允许依赖对象为null
。@Resource
注解支持多个参数,如name
、type
和lookup
等。其中name
参数用于指定要注入的 Bean 的名称。如果 name 属性一旦指定,就只会按照名称进行装配
使用场景
@Autowired
更适合用于 Spring 框架内部组件之间的依赖注入,因为它与 Spring 的容器集成更加紧密。@Resource
可以用于任何符合 JSR-250 规范的容器,不仅限于 Spring,因此在需要跨容器或跨平台时使用可能更加方便。
异常处理
- 当
@Autowired
注解的required
属性为true
(默认值)且找不到匹配的 Bean 时,会抛出异常。 @Resource
在找不到匹配的 Bean 时,通常也会抛出异常,但具体行为可能因实现而异。
Spring Bean 的作用范围(Scope)?
在Spring框架中,Bean的作用范围(Scope)定义了Bean的生命周期和可见性。
singleton(单例):
- 这是默认的作用范围。
- 在整个Spring IoC容器中,无论请求多少次,Spring容器都只会创建一个该Bean的实例。
- 单例Bean对于每个Spring IoC容器是唯一的。
prototype(原型):
- 每次请求都会创建一个新的Bean实例。
- 也就是说,每次通过Spring容器获取该Bean时,都会得到一个全新的实例。
request(请求):
- 该作用范围仅在基于Web的Spring ApplicationContext中有效。
- 在每次HTTP请求时,Spring容器都会创建一个新的Bean实例,并且该实例仅在请求的生命周期内有效。
- 当请求结束时,Bean实例会被销毁。
session(会话):
- 该作用范围也仅在基于Web的Spring ApplicationContext中有效。
- 对于每个HTTP会话,Spring容器都会创建一个新的Bean实例,并且该实例在会话的生命周期内有效。
- 当会话结束时,Bean实例会被销毁。
globalSession(全局会话):
- 该作用范围用于Portlet应用程序中。
- 在Portlet应用中,全局会话是跨多个portlet请求和响应共享的会话。
- 对于每个全局会话,Spring容器都会创建一个新的Bean实例,并且该实例在全局会话的生命周期内有效。
application(应用):
- 在ServletContext范围内有效,也就是说,在Web应用的整个生命周期内,Spring容器只会创建一个该Bean的实例。
- 这与singleton作用范围相似,但singleton是在每个Spring IoC容器范围内,而application是在整个Web应用范围内。
websocket(WebSocket):
- 该作用范围用于WebSocket应用程序中。
- 对于每个WebSocket会话,Spring容器都会创建一个新的Bean实例,并且该实例在WebSocket会话的生命周期内有效。
custom scope(自定义作用域)
- 可以自定义作用域来满足特定的需求。
- 通过实现
org.springframework.beans.factory.config.Scope
接口并注册到Spring容器中。 - 当上述标准作用域无法满足需求时,可以自定义作用域。
总结
- Singleton 和 Application 作用域的Bean在整个应用或Web应用上下文中只有一个实例,需要特别注意线程安全问题。
- Prototype 作用域的Bean每次请求都会创建一个新的实例,适用于有状态的Bean。
- Request 和 Session 作用域的Bean分别对应于HTTP请求和会话,每个请求或会话有自己的实例。
- WebSocket 作用域的Bean对应于WebSocket会话。
- 自定义作用域 可以根据具体需求实现特定的作用域行为。
Spring Bean 如何自定义作用范围?
在 Spring 框架中,作用域(Scope)定义了 Bean 的生命周期和可见性。Spring 默认提供了几种常见的作用域,如 singleton
、prototype
、request
、session
和 application
。然而,在某些情况下,这些默认的作用域可能无法满足特定的应用需求。这时,自定义作用域就显得尤为重要。通过自定义作用域,开发者可以根据业务逻辑创建具有特殊生命周期管理的 Bean。
自定义作用域的主要用途
实现特定的生命周期管理:对于某些应用程序组件,可能需要更细粒度的生命周期控制,例如按照业务流程、用户交互或其他逻辑单元来管理 Bean 的创建和销毁。
提高资源利用率:通过自定义作用域,可以确保 Bean 在适当的时间点被创建和销毁,从而优化资源使用,减少不必要的内存占用。
增强模块化设计:自定义作用域有助于将应用程序划分为更加独立和可重用的模块,每个模块可以根据其自身的上下文环境定义合适的 Bean 生命周期。
适应复杂应用场景:在分布式系统、微服务架构或涉及长时间运行任务的应用中,自定义作用域可以帮助更好地处理并发、状态管理和数据一致性等问题。
如何自定义作用域
实现Scope接口:要实现自定义作用域,你需要创建一个类并实现
org.springframework.beans.factory.config.Scope
接口。这个接口要求你实现以下几个方法:Object get(String name, ObjectFactory<?> objectFactory)
:从作用域中获取指定名称的 Bean;如果不存在,则使用提供的objectFactory
创建一个新的实例。Object remove(String name)
:从作用域中移除指定名称的 Bean。void registerDestructionCallback(String name, Runnable callback)
:注册当 Bean 被销毁时要执行的回调函数。String getConversationId()
:返回当前作用域的会话标识符(可选实现)。
注册作用域:在Spring配置文件或配置类中,通过实现
BeanDefinitionRegistryPostProcessor
接口或使用@Bean
注解和CustomScopeConfigurer
来注册你的自定义作用域。
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ScopeAnnotationTypeFilter;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.context.support.CustomScopeConfigurer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// 实现自定义作用域
public class CustomScope implements Scope {
private final Map<String, Object> scope = new ConcurrentHashMap<>();
private final Map<String, Runnable> destructionCallbacks = new ConcurrentHashMap<>();
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object scopedObject = scope.get(name);
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
scope.put(name, scopedObject);
}
return scopedObject;
}
@Override
public Object remove(String name) {
return scope.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
@Override
public Object resolveContextualObject(String key) {
return null; // 可选实现
}
@Override
public String getConversationId() {
return "custom-scope-id"; // 可选实现
}
// 清理所有 Bean 和回调
public void clear() {
for (Map.Entry<String, Runnable> entry : destructionCallbacks.entrySet()) {
entry.getValue().run();
}
scope.clear();
destructionCallbacks.clear();
}
}
@Configuration
public class AppConfig implements BeanFactoryPostProcessor {
// 注册自定义作用域
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
CustomScope customScope = new CustomScope();
beanFactory.registerScope("custom", customScope);
}
@Bean
@Scope("custom")
public MyScopedBean myScopedBean() {
return new MyScopedBean();
}
}
// 使用自定义作用域
@Component
@Scope("custom")
public class MyScopedBean {
// Bean logic here
}
注意事项
- 线程安全:确保你的自定义作用域实现是线程安全的,特别是在多线程环境中。
- 销毁回调:在自定义作用域中,你可能需要实现销毁回调逻辑,以确保Bean在不再需要时能够被正确销毁。
- 代理模式:在使用自定义作用域时,通常需要使用AOP代理(通过
@Scope
注解的proxyMode
属性)来确保在注入点获得的是正确的Bean实例。
Spring Bean 是否线程安全?
Spring框架提供的是Bean的创建和管理机制,而Bean是否线程安全取决于Bean的实现。如果Bean是有状态的,并且需要在多线程环境中使用,那么开发者需要确保Bean的实现是线程安全的,例如通过使用同步机制、不可变对象、线程局部存储等方式来保证线程安全。
Singleton Bean:单例Bean在Spring容器中只有一个实例,所有请求都会共享这个实例。如果Bean是无状态的(即不保存任何数据),那么它是线程安全的。但如果Bean是有状态的(即保存了数据),那么它可能不是线程安全的,因为多个线程可能会同时访问和修改Bean的状态,导致数据不一致。
Prototype Bean:原型Bean每次请求都会创建一个新的实例,因此每个线程或请求都会获得独立的Bean实例,不存在线程安全问题。但如果Bean的实现不是线程安全的,那么在使用过程中仍然可能出现线程安全问题。
其他作用范围(Request, Session, Application, WebSocket):这些作用范围的Bean与特定的请求、会话或应用上下文相关联,每个请求或会话都会获得独立的Bean实例,因此不存在线程安全问题。但如果Bean的实现不是线程安全的,那么在使用过程中仍然可能出现线程安全问题。
Spring 启动流程?
一、初始化Spring容器
实例化BeanFactory:
- Spring首先会实例化一个
BeanFactory
,这是Spring容器的核心接口,用于创建和管理所有的Bean。在大多数情况下,这个BeanFactory
的实现是DefaultListableBeanFactory
。
- Spring首先会实例化一个
注册内置的BeanPostProcessor:
- 接着,Spring会注册一些内置的
BeanPostProcessor
到容器中。BeanPostProcessor
是一个非常重要的接口,它允许在Bean的初始化前后执行一些自定义的逻辑。
- 接着,Spring会注册一些内置的
二、扫描并注册BeanDefinition
实例化BeanDefinitionReader:
- Spring会实例化一个
BeanDefinitionReader
,用于读取和解析配置信息(如注解、XML文件等),并将这些信息转换成BeanDefinition
对象。
- Spring会实例化一个
扫描指定包:
BeanDefinitionReader
会扫描指定的包,查找带有@Component
、@Service
、@Repository
和@Controller
等注解的类。
创建BeanDefinition对象:
- 对于找到的每个类,
BeanDefinitionReader
会创建一个BeanDefinition
对象,该对象封装了类的元数据(如类名、作用域、依赖关系等)。
- 对于找到的每个类,
注册BeanDefinition:
- 这些
BeanDefinition
对象会被注册到BeanDefinitionRegistry
中,通常是通过DefaultListableBeanFactory
来实现的。
- 这些
处理@Configuration注解的配置类:
- Spring还会处理带有
@Configuration
注解的配置类,识别带有@Bean
注解的方法,并将这些方法的返回值视为Bean定义,进行相同的处理。
- Spring还会处理带有
三、调用refresh()方法刷新容器
prepareRefresh():
- 刷新前的预处理,准备一些必要的资源。
obtainFreshBeanFactory():
- 获取在容器初始化时创建的
BeanFactory
。
- 获取在容器初始化时创建的
prepareBeanFactory(beanFactory):
- 对
BeanFactory
进行预处理,向容器中添加一些必要的组件。
- 对
postProcessBeanFactory(beanFactory):
- 允许子类重写该方法,以在
BeanFactory
创建并预处理完成后进行进一步的设置。
- 允许子类重写该方法,以在
invokeBeanFactoryPostProcessors(beanFactory):
- 执行
BeanFactoryPostProcessor
的方法,这些后置处理器可以对BeanDefinition
进行一定程度的修改与替换。
- 执行
registerBeanPostProcessors(beanFactory):
- 向容器中注册
BeanPostProcessor
,这些后置处理器可以干预Spring初始化Bean的流程,完成代理、自动注入、循环依赖等功能。
- 向容器中注册
initMessageSource():
- 初始化
MessageSource
组件,用于国际化功能。
- 初始化
initApplicationEventMulticaster():
- 初始化事件派发器,用于注册和派发事件。
onRefresh():
- 留给子容器或子类重写的方法,以便在容器刷新时自定义逻辑。
registerListeners():
- 将容器中的
ApplicationListener
注册到事件派发器中,并派发之前步骤产生的事件。
- 将容器中的
finishBeanFactoryInitialization(beanFactory):
预实例化单例 Bean:对于配置为单例的 Bean,容器会在启动时预先实例化它们。非单例 Bean 会在每次请求时按需实例化。
这是通过调用
preInstantiateSingletons()
方法来实现的,该方法会遍历BeanDefinition
对象,并调用getBean()
方法创建对象实例。实例化过程:
实例化:调用构造函数或工厂方法创建 Bean 实例。
填充属性:根据
BeanDefinition
中的属性信息,设置 Bean 的属性值。依赖注入:解决 Bean 之间的依赖关系,注入所需的依赖。
初始化前处理:调用所有已注册的
BeanPostProcessor
的postProcessBeforeInitialization
方法。初始化:调用 Bean 的初始化方法(如
@PostConstruct
注解的方法或init-method
属性指定的方法)。初始化后处理:调用所有已注册的
BeanPostProcessor
的postProcessAfterInitialization
方法。
finishRefresh():
- 发布
BeanFactory
容器刷新完成事件,标志着Spring容器的启动过程结束。
- 发布
四、处理其他注解和Bean
- 在Spring启动过程中,还会处理其他注解,如
@Import
等,这些注解可能会引入其他配置或Bean到应用程序上下文中。 - 对于带有
@Lazy
标记的Bean,它们不会在初始启动过程中被创建,而是在首次请求时创建,这有助于优化启动时间。
Spring IOC 容器初始化过程?
Resource资源定位。
这个Resource定位指的是BeanDefinition的资源定位,就是对开发者的配置信息进行资源的定位,并将其封装成Resource对象。这一过程由ResourceLoader通过Resource接口来完成。ResourceLoader负责定位不同形式的资源,并为它们提供统一的访问接口。这些资源可能位于文件系统中、类路径中、网络上或者作为字节数组存在。
在Spring中,Resource接口有多个实现类,用于处理不同类型的资源,如:
FileSystemResource:用于访问文件系统中的资源。
ClassPathResource:用于访问类路径中的资源。
ServletContextResource:用于访问Web应用根目录中的资源。
UrlResource:用于访问网络资源。
ByteArrayResource:用于访问字节数组资源。
BeanDefinition的载入。
BeanDefinition的载入过程是将通过 Resource 定位到的用户定义好的Bean表示为容器内部的数据结构。这个过程通常涉及读取和解析配置文件或注解配置,并将配置信息转换为BeanDefinition对象。BeanDefinition是POJO对象在容器中的抽象,它包含了Bean的各种属性信息,如类名、是否单例、依赖关系、是否懒加载等。在Spring中,BeanDefinition的载入是通过BeanDefinitionReader来完成的。BeanDefinitionReader会读取配置文件或注解配置,并解析其中的Bean定义信息,然后将这些信息转换为BeanDefinition对象,并存储在容器中。
BeanDefinition的注册
BeanDefinition的注册过程是将载入过程中解析得到的BeanDefinition向IOC容器进行注册。这个注册过程是通过调用BeanDefinitionRegistry接口来完成的。在Spring中,BeanDefinitionRegistry是一个接口,它定义了向容器注册BeanDefinition的方法。常用的实现类有DefaultListableBeanFactory,它是一个功能丰富的BeanFactory实现,支持BeanDefinition的注册、查找和管理。
在注册过程中,BeanDefinition会被注入BeanFactory 的HashMap缓存中:
beanDefinitionNames
缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 。beanDefinitionMap
缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和 BeanDefinition 映射。aliasMap
缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和别名映射。
IOC容器通过HashMap来持有和管理BeanDefinition数据。当容器需要创建Bean实例时,就根据BeanDefinition中的信息来进行实例化和依赖注入。
- 向IOC容器注册所有单例非懒加载的 bean 对象。包括:
- 实例化 Instantiation(构造器推断)
- 属性赋值,依赖注入 Populate(循环依赖)
- 初始化 Initialization
- 完成容器刷新,推送 ContextRefreshedEvent事件到监听器
注意:Bean的定义和初始化在 Spring IoC 容器是两大步骤,它是先定义,然后再是初始化和依赖注入。所以当Spring做完了前 3 步后,Bean 就在 Spring IoC 容器中被定义了,而没有被初始化,更没有完成依赖注入,所以此时仍然没有对应的 Bean 的实例,也就是没有注入其配置的资源给 Bean,也就是它还不能完全使用。对于初始化和依赖注入,Spring Bean 还有一个配置选项 lazy-init,其含义就是:是否默认初始化 Spring Bean。在没有任何配置的情况下,它的默认值为default,实际值为 false(默认非懒加载),也就是 Spring IoC 容器默认会自动初始化 Bean。如果将其设置为 true(懒加载),那么只有当我们使用 Spring IoC 容器的 getBean 方法获取它时,它才会进行 Bean 的初始化,完成依赖注入。
Spring 实例化对象的方式主要包括?
Spring框架提供了多种方式来实例化对象,这些方法可以根据不同的需求和场景选择使用。
通过构造器实例化
- 如果一个类有无参构造器(包括默认的无参构造器),Spring会直接使用这个构造器来创建Bean。这是最常见也是默认的方式。
markdown<bean id="exampleBean" class="com.example.ExampleBean"/> @Bean public ExampleBean exampleBean() { return new ExampleBean(); }
- 使用带参数的构造器
markdown<bean id="exampleBean" class="com.example.ExampleBean"> <constructor-arg value="someValue"/> </bean> @Bean public ExampleBean exampleBean(Dependency dependency) { return new ExampleBean(dependency); }
注意:
- 如果一个类只有一个构造器,那么即使没有
@Autowired
注解,Spring也会自动使用该构造器来创建Bean,并且将匹配的依赖注入到构造器中。 - 如果有多个构造函数,且无默认构造函数将会报错。
javapublic class ExampleBean { private final Dependency dependency; @Autowired public ExampleBean(Dependency dependency) { this.dependency = dependency; } }
通过FactoryBean接口实例化
FactoryBean是Spring提供的一个接口,用于通过工厂模式创建对象。实现该接口的类需要实现
getObject()
和getObjectType()
方法,分别用于返回创建的对象和对象的类型。markdownpublic class AnimalFactoryBean implements FactoryBean<Animal> { @Override public Animal getObject() throws Exception { return new Animal("小狗", 2); } @Override public Class<?> getObjectType() { return Animal.class; } } <bean id="animal" class="com.example.AnimalFactoryBean"/>
使用静态工厂方法
可以通过配置一个静态工厂方法来创建Bean。这种方法适合于那些已经存在的、不能修改的类。通过
factory-method
属性指定静态工厂方法。markdown<bean id="exampleBean" class="com.example.ExampleFactory" factory-method="createExampleBean"/> @Bean public static ExampleBean exampleBean() { return ExampleFactory.createExampleBean(); }
使用实例工厂方法
与静态工厂方法类似,但这里的方法是实例方法,因此首先需要创建工厂类的实例。并通过
factory-bean
和factory-method
属性分别引用工厂对象和其创建方法。markdown<bean id="exampleFactory" class="com.example.ExampleFactory"/> <bean id="exampleBean" factory-bean="exampleFactory" factory-method="createExampleBean"/> @Bean public ExampleFactory exampleFactory() { return new ExampleFactory(); } @Bean public ExampleBean exampleBean(ExampleFactory factory) { return factory.createExampleBean(); }
Spring 实例化对象的过程?
Spring中,一个Bean需要通过实例化来获取一个对象,而实例化的过程涉及到构造方法的调用。Spring容器会根据BeanDefinition(Bean定义信息)和配置选项,经过一系列步骤和逻辑判断,最终创建一个全新的Bean实例。
加载BeanDefinition:
Spring容器首先会加载配置文件信息,装配BeanDefinition。BeanDefinition包含了Bean的各种属性,如类名、构造方法、依赖等。
获取Class对象:
根据BeanDefinition加载类并获取对应的Class对象。
Supplier创建对象:
如果BeanDefinition绑定了一个Supplier,那么Spring会调用Supplier的get方法以获取一个对象,并将其直接返回。
工厂方法创建对象:
如果BeanDefinition中存在factoryMethodName属性,那么Spring会调用该工厂方法以获取一个Bean对象,并将其返回。
已自动构造过的Bean:
如果BeanDefinition已经自动构造过了(例如,通过缓存的构造方法或构造方法参数值),那么Spring会调用autowireConstructor()方法来自动构造一个对象。
推断构造方法:
如果BeanDefinition没有定义自动构造方法,那么Spring会调用SmartInstantiationAwareBeanPostProcessor接口的determineCandidateConstructors()方法来确定哪些构造方法是可用的。
在推断构造方法时,Spring会考虑以下因素:
查找带有
@Autowired
注解的构造器Spring首先会在目标类中查找带有
@Autowired
注解的构造器。如果只有一个构造器带有
@Autowired
注解,那么Spring将使用该构造器。
查找无参构造器
如果没有找到带有
@Autowired
注解的构造器,Spring会检查是否存在无参构造器(包括默认的无参构造器)。如果存在无参构造器,Spring将使用会标记无参的构造方法为默认构造方法。
查找单个构造器
如果既没有带有
@Autowired
注解的构造器,也没有无参构造器,但只有一个构造器,那么Spring将使用这个唯一的构造器进行实例化。构造函数筛选结束,最终返回的内容可能有以下几种:
- 返回一个@Autowired required = true的构造方法
- 返回一个或多个@Autowired required = false和一个没被@Autowired的无参的构造方法
- 返回唯一的一个有参的构造方法
- 如果只有一个无参的构造方法,这里会返回 null,外层逻辑会默认使用无参构造方法进行实例化
自动构造对象:
如果存在可用的构造方法,或者满足上述推断构造方法的条件之一,那么Spring会调用autowireConstructor()方法来自动构造一个对象。
在调用autowireConstructor()方法时,Spring会根据构造方法参数值来确定所需要的最少的构造方法参数值的个数,并对所有的构造方法进行排序(参数个数多的在前面)。然后,Spring会遍历每个构造方法,并根据构造方法参数类型找值或利用调用getBean方法时所指定的构造方法参数值进行实例化。
无参构造方法实例化:
- 如果以上条件都不满足,那么Spring会根据无参的构造方法实例化一个对象。
注意事项
- 在Spring中,如果一个类有多个构造方法,并且没有通过@Autowired注解或其他方式指定使用哪个构造方法,或者存在多个 Autowired required = true 的构造函数,那么Spring可能会因为无法推断出使用哪个构造方法而报错。
- 可以通过在需要的构造方法上添加@Autowired注解或使用getBean方法时指定构造方法参数值来明确告诉Spring使用哪个构造方法。
- 可存在一个或者多个 Autowired.required = false 的构造函数
Spring 属性赋值的过程?
- 若
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()
返回值为false则会终止赋值过程。 - 注解驱动的Bean执行属性填充在
AutowiredAnnotationBeanPostProcessor#postProcessProperties()
中。 - 在做属性填充时,如果当前的Bean实例依赖的成员(另一个Bean)未被加载,会进入选举候选名单的逻辑中,进行各种判断后,选出最适合的Bean实例进行getBean操作.
- @Autowired在进行自动装配的过程中,默认按照"byType"的方式进行Bean加载,如果出现无法挑选出合适的Bean的情况,再将属性名与候选Bean名单中的beanName进行对比。
- 正确地声明@Primary和Order等注解让Bean在多态的选举中优选胜出.
- required=false可以让程序在找不到Bean的时候不抛出异常,但是调用期间还是会报错,不建议这种使用.
- XML的自动装配模式与注解驱动的模式在代码上是不同的分岔.
- BeanWrapperImpl 类负责对完成初始化的 Bean 对象进行依赖注入,对于非集合类型属性,使用 JDK 反射,通过属性的 setter 方法为属性设置注入后的值。对于集合类型的属性,将属性值解析为目标类型的集合后直接赋值给属性。
Spring 三级缓存的作用?
Spring 采用三级缓存机制(一级缓存 singletonObjects
、二级缓存 earlySingletonObjects
和三级缓存 singletonFactories
)主要是为了有效地解决循环依赖问题,同时确保 Bean 的线程安全性和可预测的行为。以下是详细解释为什么需要这种复杂的缓存机制:
处理循环依赖
三级缓存的实现
一级缓存 (
**singletonObjects**
):存储已经完全初始化的 singleton Bean 实例。当 Spring 容器需要获取一个 singleton Bean 时,首先会检查这个缓存。如果找到了对应的 Bean,则直接返回它。二级缓存 (
**earlySingletonObjects**
):用于存储尚未完全初始化但已经完成了属性填充的 singleton Bean 实例。当检测到循环依赖时,Spring 会将未完成初始化的 Bean 提前暴露给其他 Bean 使用。这使得即使 Bean 还没有完成所有的初始化步骤,也可以被其他 Bean 引用,从而打破循环依赖链。三级缓存 (
**singletonFactories**
):用于存储 ObjectFactory 工厂方法,这些工厂方法可以在必要时创建并返回 Bean 的实例。这个缓存主要用于解决AOP生成代理对象的问题。当从二级缓存中取出 Bean 后,Spring 会将其移至一级缓存,并将之前的 ObjectFactory 移入三级缓存。如果后续有其他 Bean 请求相同的 Bean,Spring 可以通过 ObjectFactory 的 getObject() 方法来获取最新的、已完成初始化的 Bean 实例。
三级缓存的具体工作流程如下:
创建Bean时,Spring首先会将正在创建的Bean的工厂对象放入三级缓存中。
当其他Bean需要引用这个Bean时,Spring会检查一级缓存是否存在该Bean的实例。如果不存在,再检查二级缓存是否存在尚未完成属性赋值的早期Bean对象。如果仍然不存在,Spring会检查三级缓存中是否存在Bean的工厂对象。
如果三级缓存中存在工厂对象,Spring会通过工厂对象创建Bean的早期引用(可能是代理对象或原始对象),并将其放入二级缓存中,同时从三级缓存中移除该工厂对象。
当Bean完全初始化后,Spring会将其从二级缓存移动到一级缓存中,完成Bean的创建和初始化过程。
确保线程安全
多线程环境下的并发访问:在多线程环境中,多个线程可能同时请求同一个 singleton Bean。为了避免竞争条件和数据不一致的问题,Spring 必须保证 Bean 的创建和初始化过程是线程安全的。
三级缓存的作用:通过三级缓存机制,Spring 可以精确地控制 Bean 的创建时机和顺序,确保每个 Bean 只被初始化一次,并且在所有依赖关系都满足后才被公开使用。这有助于防止并发问题的发生,提高系统的稳定性和性能。
支持延迟加载和懒加载
延迟加载的需求:某些 Bean 可能不需要在应用程序启动时立即初始化,而是可以等到实际需要时再进行。这对于减少启动时间和内存占用非常有用。
三级缓存的作用:利用三级缓存,Spring 可以实现更精细的控制,例如只在第一次真正需要某个 Bean 时才从 ObjectFactory 创建其实例,而在此之前只是保存了如何创建该 Bean 的信息。这种方式既支持了延迟加载,又不影响整体的应用逻辑。
优化性能
减少不必要的重复创建:通过缓存机制,Spring 避免了对相同 Bean 的重复创建和初始化,提高了资源利用率。
加快查找速度:一级缓存中的 Bean 是可以直接使用的,因此对于那些已经被完全初始化的 Bean,查找速度非常快,几乎不需要额外的时间开销。
保持一致性
Bean 生命周期管理:Spring 需要确保所有 Bean 的生命周期是一致的,即它们都在适当的时间点被创建、初始化、使用以及销毁。
三级缓存的作用:借助三级缓存,Spring 能够更好地跟踪每个 Bean 的状态变化,确保在整个应用生命周期内正确地管理 Bean 的创建和销毁,避免出现孤儿 Bean 或者内存泄漏等问题。
Spring 的三级缓存机制是一个精心设计的解决方案,旨在应对循环依赖、线程安全、延迟加载等多种复杂场景下的挑战。它不仅提升了框架自身的灵活性和可靠性,也为开发者提供了更加高效、稳定的开发体验。
Spring 中如果A,B 对象相互依赖,它们创建流程是?
假设存在两个 Bean:BeanA
和BeanB
,BeanA
依赖BeanB
,BeanB
也依赖BeanA
。
- 创建Bean A:Spring开始创建Bean A,并将其放入三级缓存
singletonFactories
中。 - 创建Bean B:在创建Bean A的过程中,发现Bean A依赖于Bean B,于是开始创建Bean B。
- 创建Bean B:Spring开始创建Bean B,并将其放入三级缓存
singletonFactories
中。 - 检测到Bean A依赖:在创建Bean B的过程中,发现Bean B依赖于Bean A。
- 从三级缓存获取Bean A:Spring从三级缓存
singletonFactories
中获取Bean A的早期暴露版本(如果存在)。 - 继续创建Bean B:Spring继续创建并初始化Bean B。
- 完成Bean B的创建:Bean B创建完成后,将其从三级缓存移动到二级缓存
earlySingletonObjects
,再移动到一级缓存singletonObjects
。 - 完成Bean A的创建:回到Bean A的创建过程,继续完成Bean A的创建和初始化。
- 完成Bean A的创建:Bean A创建完成后,将其从三级缓存移动到二级缓存
earlySingletonObjects
,再移动到一级缓存singletonObjects
。
Spring 为什么不使用二级缓存解决循环依赖问题?
Spring在处理Bean的循环依赖问题时,实际上使用了三级缓存而不是两级缓存,原因如下:
代理对象生成:
- 在Spring中,如果一个Bean需要被代理(比如在声明式事务中经常遇到),则需要在Bean的创建过程中生成代理对象。这个代理对象通常是在Bean的初始化之后创建的,因此不能简单地使用二级缓存(singletonObjects)来存储最终的代理对象,因为二级缓存中存放的是提前暴露的Bean,而这个Bean在创建时还未完成代理化。
早期暴露Bean:
- 为了解决循环依赖,Spring需要在Bean的属性赋值阶段就提前暴露一个尚未完成初始化的Bean。这就需要一个额外的缓存(singletonsCurrentlyInCreation),它用来存放正在创建中的Bean。这样,当发生循环依赖时,可以通过这个缓存来解决。
三级缓存的作用:
- singletonsCurrentlyInCreation:用来存放正在创建中的Bean,用于检测循环依赖。
- singletonObjects:用来存放已经创建完成的单例Bean,包括原始对象和代理对象。
- earlySingletonObjects:用来存放早期曝光的Bean,即在属性赋值之前就暴露的Bean。
完整的Bean生命周期:
- 在Bean的生命周期中,Spring需要处理Bean的实例化、属性赋值、初始化等多个步骤。在属性赋值阶段,如果发生循环依赖,需要能够提供一个尚未完成初始化的Bean实例。在初始化阶段之后,可能还需要为Bean创建一个代理对象。因此,需要三级缓存来分别处理这些不同的阶段。
避免重复代理:
- 如果只使用两级缓存,可能会在代理对象创建时遇到问题,因为无法区分一个Bean是否已经生成过代理对象。三级缓存可以确保代理对象只被创建一次,避免重复代理。
总结来说,三级缓存的设计使得Spring能够更细致地控制Bean的创建过程,特别是在处理循环依赖和代理创建时,提供了必要的灵活性和控制力。这是Spring框架为了保证Bean生命周期和代理机制正确运作所采取的一种设计决策。
Spring 不能解决循环依赖的场景?
Spring框架能够处理一部分循环依赖的情况,但仍有一些场景是无法处理的:
构造器循环依赖:
- Spring无法解决构造器中的循环依赖问题。如果两个Bean在构造函数中相互依赖,即A的构造函数需要B的实例,同时B的构造函数也需要A的实例,这种情况下Spring无法创建这样的Bean,会抛出
UnsatisfiedDependencyException
异常。
原型(Prototype)Bean的循环依赖:
- Spring不支持原型Bean的属性注入循环依赖。与单例Bean不同,原型Bean在每次请求时都会创建一个新的实例,Spring容器不缓存原型Bean,因此无法提前暴露一个创建中的Bean来解决循环依赖。对于原型Bean的循环依赖,Spring会在检测到循环时抛出
BeanCurrentlyInCreationException
异常。
特殊的AOP代理Bean的循环依赖:
- 代理对象的setter循环依赖,Spring也无法处理setter方法的循环依赖。这是因为代理对象的创建通常发生在Bean的初始化之后,而setter注入的循环依赖需要在Bean的属性赋值阶段解决,这与代理对象的创建时机冲突。
- 使用
@Async
注解增强的Bean,Spring可能无法处理循环依赖。这是因为这些特殊的AOP代理可能在Bean的创建和初始化过程中引入了额外的复杂性。
Bean 初始化过程中调用其他 Bean 的方法
- 如果一个 Bean 在其初始化逻辑(如
@PostConstruct
或InitializingBean
接口的方法)中直接调用了另一个尚未完成初始化的 Bean 的方法,可能会导致问题。 - 在这种情况下,即使 Spring 提前暴露了部分初始化的 Bean,但因为目标 Bean 还没有完成所有的初始化步骤,所以调用它的方法可能会抛出异常或者表现出不可预测的行为。
静态字段注入
- 尝试将依赖注入到静态字段中会导致 Spring 无法正确管理这些依赖。
- 静态字段属于类级别而非实例级别,这意味着即使 Spring 成功地为某个 Bean 注入了依赖,这些依赖也不会反映在静态字段上,因为静态字段不属于任何特定的 Bean 实例。
跨不同上下文的循环依赖
- 如果循环依赖跨越了不同的 Spring 应用上下文(ApplicationContext),则 Spring 无法有效地解决这些依赖。
- 每个应用上下文都有自己独立的 Bean 容器和生命周期管理机制,跨上下文的依赖超出了单个容器的能力范围。
怎么避免循环依赖问题?
重新设计组件:
- 重新审视和调整组件的设计,将它们分解成更小的、更松耦合的部分,以消除循环依赖。
使用Setter注入而不是构造器注入:
- Spring可以解决Setter注入的循环依赖问题,因为Bean可以在其依赖未完全注入的情况下部分构造。但对于构造器注入,Spring需要在构造Bean时解决所有依赖,因此无法处理构造器注入的循环依赖。
使用@Lazy
注解:
- 对于Setter注入,可以使用
@Lazy
注解来延迟依赖Bean的注入,直到Bean被实际使用时才创建。
使用ApplicationContextProvider
:
- 在非Spring管理的类中需要访问Spring Bean时,可以使用
ApplicationContextProvider
来避免循环依赖。
减少直接依赖:
- 通过引入中介者(例如,使用观察者模式、事件机制、中间服务等)来减少组件间的直接依赖。
使用@PostConstruct
注解:
- 将依赖注入的逻辑放在
@PostConstruct
注解的方法中,确保在依赖注入完成后再执行初始化逻辑。
避免不必要的依赖:
- 检查代码中的依赖关系,移除不必要的依赖,减少循环依赖的可能性。
使用prototype
作用域:
- 如果Bean的作用域被设置为
prototype
,Spring容器每次请求都会创建一个新的Bean实例,从而避免了单例Bean的循环依赖问题。
使用ObjectProvider
:
- 在Spring 5中引入的
ObjectProvider
提供了一种更灵活的方式来解决循环依赖问题,它允许延迟查找Bean。
外部化依赖:
- 将依赖关系移到配置文件或环境变量中,减少代码中的直接依赖。
ObjectFactory,FactoryBean 的区别?
ObjectFactory:
是一个功能接口,它是一个函数式接口,只有一个无参的
getObject()
方法来获取对象的实例,它允许延迟对象创建和配置,以及在运行时动态地决定要返回的对象实例。ObjectFactory通常作为内部机制的一部分,用于工厂模式和依赖注入中,支持更复杂的Bean生命周期和依赖管理功能。它更多地被用在BeanFactory子类的内部类或者使用lambda表达式封装Bean的创建过程中。在实际应用中,直接使用ObjectFactory的情况并不多见。它主要用于延迟查找的场景,当得到ObjectFactory对象时,相当于Bean没有被创建,只有当调用getObject()方法时,才会触发Bean实例化等生命周期。FactoryBean
是Spring框架中的一个特殊接口,用于创建复杂的或可配置的Bean实例,使用了**工厂方法模式,**隐藏实际创建Bean的复杂逻辑,对外提供一个简单的接口,通过实现FactoryBean接口,可以在Bean创建过程中添加额外的逻辑,如依赖注入、配置读取等用于创建复杂的或可配置的 Bean实例,如数据库连接池、消息服务等。FactoryBean本身也是一个Bean,但它创建的Bean是通过getObject()方法返回的。当从Spring容器中获取FactoryBean创建的Bean时,实际上获取的是FactoryBean的getObject()方法返回的对象,而不是FactoryBean本身。如果要获取FactoryBean对象本身,需要在Bean名称前加上“&”前缀。通过FactoryBean,可以更加灵活地控制Bean的创建和配置过程。
Spring 后置处理器的作用?
Spring 后置处理器(Post Processors)是 Spring 框架中非常重要的组件,它们允许开发者在 Bean 的生命周期的特定阶段插入自定义逻辑。通过实现 BeanFactoryPostProcessor
或 BeanPostProcessor
接口,后置处理器可以在 Bean 实例化前后对 Bean 定义或 Bean 实例进行修改和增强。以下是这两种主要类型的后置处理器及其作用:
BeanFactoryPostProcessor
分为两类:
- BeanFactoryPostProcessor:不带注册功能。
- BeanDefinitionRegistryPostProcessor:继承了BeanFactoryPostProcessor接口,带注册功能。
主要作用
- 修改 Bean 定义:主要用于在容器加载Bean定义后,所有Bean实例化之前对Bean的元数据进行修改,从而实现对容器功能的扩展。如改变Bean的作用域、添加或修改Bean的属性等,这些修改会影响到后续Bean的实例化过程。
- 解析占位符:常见的
PropertyPlaceholderConfigurer
和PropertySourcesPlaceholderConfigurer
是BeanFactoryPostProcessor
的实现类,用于解析配置文件中的占位符。 - 动态注册 Bean:可以在运行时动态地向容器中添加新的 Bean 定义,或者删除现有的 Bean 定义。
使用场景
- 需要在应用程序启动时根据环境变量或其他外部配置来调整 Bean 的属性。
- 动态加载配置文件或资源。
- 修改 Bean 的定义以适应不同的部署环境。
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@Configuration
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 在这里可以修改 Bean 定义或执行其他操作
System.out.println("MyBeanFactoryPostProcessor is running...");
}
}
BeanPostProcessor
主要作用
- 前置处理:在 Bean 实例化之后但在初始化方法调用之前,可以对 Bean 进行额外的处理。这通常用于包装 Bean、设置代理或其他增强功能。
- 后置处理:在 Bean 初始化方法调用之后,可以再次对 Bean 进行处理。这通常用于验证 Bean 的状态或执行一些清理工作。
- AOP 支持:Spring AOP 使用
BeanPostProcessor
来创建代理对象,从而为 Bean 添加横切关注点(如事务管理、日志记录等)。
使用场景
- 实现 AOP 切面编程,例如通过代理模式为 Bean 添加额外的行为。
- 对 Bean 进行验证或初始化检查。
- 在 Bean 初始化前后执行某些定制化的操作。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之前执行的操作
System.out.println("Pre-initialization of " + beanName);
return bean; // 返回处理后的 Bean 或者返回 null 表示不修改 Bean
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之后执行的操作
System.out.println("Post-initialization of " + beanName);
return bean; // 返回处理后的 Bean 或者返回 null 表示不修改 Bean
}
}
注意事项
- 顺序问题:如多个后置处理器存在,它们的执行顺序可能会对结果产生影响。可以通过实现
Ordered
接口或使用@Order
注解来指定处理器的优先级。 - 性能考虑:由于后置处理器会在每次 Bean 创建时触发,因此应尽量避免在其中执行耗时操作,以免影响应用程序的整体性能。
- 理解生命周期:熟悉 Bean 的生命周期对于正确使用后置处理器至关重要,这样可以确保你的自定义逻辑能够在适当的时间点生效。
- 避免修改依赖关系:不应该用于修改Bean的依赖关系,因为这可能会导致循环依赖问题。
- 避免依赖其他 Bean:不应依赖于其他Bean,因为它可能在这些Bean初始化之前就被调用。
Spring 常见内置处理器有哪些?
**AutowiredAnnotationBeanPostProcessor:**负责处理自动装配(@Autowired)注解,将标注了 @Autowired 的字段、方法或构造函数的依赖项自动注入到 Bean 中。
**CommonAnnotationBeanPostProcessor:**处理常见的注解,如不仅可以处理 @Resource 注解的依赖注入,还可以调用 @PostConstruct 注解的方法在 Bean 初始化之后执行,以及在 Bean 销毁之前执行 @PreDestroy 注解的方法。
**InitDestroyAnnotationBeanPostProcessor:**用于处理带有
@PostConstruct
和@PreDestroy
注解的方法。**RequiredAnnotationBeanPostProcessor:**检查 Bean 中的字段是否标注了 @Required 注解,并确保这些字段在 Bean 初始化之前已经被注入值。如果未注入值,则抛出异常。
**ConfigurationClassPostProcessor:**处理使用 @Configuration 注解的类,并生成相应的 BeanDefinition。它负责解析配置类中的 @Bean 注解,将标注了 @Bean 的方法注册为 Spring 容器中的 Bean。
**EventListenerMethodProcessor:**处理使用 @EventListener 注解的方法,将这些方法注册为事件监听器,并在相应的事件发生时调用。
**PersistenceAnnotationBeanPostProcessor:**处理持久层注解如
@PersistenceContext
和@PersistenceUnit
。**ConfigurationClassPostProcessor:**处理
@Configuration
注解的类,处理@Bean
和@Component
注解的方法,以及@PropertySource
和@Import
注解。**ApplicationContextAwareProcessor:**确保实现了
Aware
接口(如ApplicationContextAware
、BeanFactoryAware
等)的 Bean 能够获取到相应的上下文或工厂对象。**PropertySourcesPlaceholderConfigurer:**从属性源(如
.properties
文件、环境变量等)中解析并替换配置文件中的占位符。虽然它不是BeanPostProcessor
的直接实现,但它在Bean创建过程中起到了类似的作用。**EnvironmentBeanPostProcessor:**处理
@Profile
注解,根据当前激活的 profile 来决定是否注册某些 Bean。MBeanExportingBeanPostProcessor:用于将Spring管理的Bean导出为JMX(Java Management Extensions)MBean,以便可以通过JMX服务器进行监控和管理。
CustomEditorConfigurer:用于注册一组属性编辑器,这些编辑器可以自定义如何将字符串配置值转换为特定的目标类型。
EventPublicationInterceptor:用于在Bean的初始化后注册事件发布器,以便Bean可以发布应用事件。
AspectJAfterReturningAdviceInterceptor:用于处理AspectJ的
@AfterReturning
增强,这是AOP的一部分。InstantiationAwareBeanPostProcessor:扩展了BeanPostProcessor 接口,允许在实例化之前和之后执行操作。
SmartInstantiationAwareBeanPostProcessor:进一步扩展了InstantiationAwareBeanPostProcessor接口,提供determineCandidateConstructors方法,用于确定构造器;提供getEarlyBeanReference方法,用于获取早期的Bean引用。
ApplicationListenerDetector:检测Bean是否实现了ApplicationListener接口,并将其注册为应用程序事件监听器。
AbstractAutoProxyCreator:根据配置创建AOP代理对象。
MergedBeanDefinitionPostProcessor: 用于处理父子Bean定义的合并。
这些内置的后置处理器在 Spring 容器的内部实现中扮演着重要角色,它们使得 Spring 框架能够自动处理各种注解和配置,从而简化了开发者的工作。开发者通常不需要手动注册这些内置的后置处理器,因为它们会在 Spring 容器启动时自动被识别和注册。
Spring 常见 Aware 接口实现?
在 Spring 框架中,Aware
接口及其后置处理器 ApplicationContextAwareProcessor
提供了一种机制,使得 Bean 可以获取到应用上下文(ApplicationContext
)以及其他重要的框架组件。通过实现这些接口,Bean 能够在初始化过程中自动获得对特定资源的引用,从而简化了配置并增强了灵活性。以下是几种常见的 Aware
接口以及它们的作用:
ApplicationContextAware
主要作用:获取应用上下文:允许 Bean 在其生命周期内访问 ApplicationContext
,从而可以执行诸如查找其他 Bean、发布事件等操作。
使用场景
- 需要在运行时动态地查找或创建 Bean。
- 发布应用程序事件。
- 获取配置文件中的属性值。
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
BeanFactoryAware
主要作用:获取 Bean 工厂:允许 Bean 访问底层的 BeanFactory
,这对于需要更细粒度控制 Bean 创建过程的情况非常有用。
使用场景
- 动态注册新的 Bean 定义。
- 获取某些不在应用上下文中的 Bean。
public void setBeanFactory(BeanFactory beanFactory) throws BeansException;
BeanNameAware
主要作用:获取 Bean 名称:允许 Bean 知道它自己在 Spring 容器中的名称。
使用场景
- 当 Bean 的行为依赖于其名称时,例如用于日志记录或调试目的。
public void setBeanName(String name);
ResourceLoaderAware
主要作用:获取资源加载器:允许 Bean 访问 ResourceLoader
,从而可以加载外部资源文件(如配置文件、静态资源等)。
使用场景
- 加载配置文件或其他外部资源。
- 读取类路径下的资源。
public void setResourceLoader(ResourceLoader resourceLoader);
MessageSourceAware
主要作用:获取消息源:允许 Bean 访问 MessageSource
,从而可以支持国际化和本地化功能。
使用场景
- 实现多语言支持的应用程序。
- 根据用户区域设置动态加载消息。
public void setMessageSource(MessageSource messageSource);
ApplicationEventPublisherAware
主要作用:获取事件发布器:允许 Bean 发布自定义的应用程序事件。
使用场景
- 构建事件驱动架构的应用程序。
- 在业务逻辑的关键点发布事件以便其他组件监听和响应。
void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);
EnvironmentAware
主要作用:获取环境对象:允许 Bean 访问 Environment
对象,从而可以读取环境变量、系统属性等信息。
使用场景
- 根据不同的部署环境调整应用程序的行为。
- 读取命令行参数或环境变量。
public void setEnvironment(Environment environment) ;
ServletContextAware
主要作用:获取 Servlet 上下文:允许 Bean 访问 Web 应用程序的 ServletContext
,这在构建基于 Servlet 的 Web 应用程序时特别有用。
使用场景
- 访问 Web 应用程序的全局配置。
- 获取 Web 应用程序的资源路径。
public void setServletContext(ServletContext servletContext);
注意事项
- 谨慎使用:虽然
Aware
接口提供了极大的灵活性,但过度使用可能会导致代码紧耦合,降低可测试性。因此,在设计时应权衡是否真的需要某个Aware
接口的功能。 - 替代方案:对于一些简单的场景,考虑使用构造器注入或 setter 方法注入来传递所需的依赖,而不是依赖
Aware
接口。这样可以使代码更加清晰和易于维护。 - 理解生命周期:熟悉 Bean 的生命周期对于正确使用
Aware
接口至关重要,确保你的自定义逻辑能够在适当的时间点生效。
Spring Bean 的生命周期?
对于 Spring Bean 的生命周期来说:实例化 Instantiation;属性赋值 Populate;初始化 Initialization;使用(Usage);销毁 Destruction
实例化(Instantiation)
- 创建BeanDefinition:当Spring容器启动时,它会根据配置信息(如XML配置文件或注解)生成BeanDefinition对象,这些对象包含了Bean的定义信息,如类名、作用域、依赖等。
- 合并BeanDefinition:如果Bean存在父定义,则合并BeanDefinition,继承父定义中的属性并覆盖相同的属性。
- 实例化Bean:根据BeanDefinition中的类名,Spring容器通过调用无造方法或使用工厂方法创建Bean的实例。
这一步仅仅是简单的实例化,并未进行依赖注入和初始化。
属性赋值(Populate Properties)
依赖注入:在Bean实例化之后,Spring容器会根据BeanDefinition中的依赖信息,通过构造器注入、Setter方法注入或字段注入等方式,将依赖的Bean注入到当前Bean中。通过三级缓存解决循环依赖的问题。
如果Bean上使用了@PostConstruct注解,Spring容器会在Bean属性赋值完成后调用该方法。
初始化(Initialization)
BeanNameAware、BeanFactoryAware、BeanClassLoaderAware
- 如果 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的名称传递给 setBeanName 方法。
- 如果 Bean 实现了 BeanFactoryAware接口,Spring 将调用 setBeanFactory 方法,传入 BeanFactory 容器。
- 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
执行其他Aware接口回调:如果Bean实现了
xxxAware
接口(如ApplicationContextAware等),Spring容器会在Bean初始化之前调用这些接口的回调方法,将相关的容器信息注入给Bean。执行
BeanPostProcessor.postProcessBeforeInitialization
:在Bean正式初始化之前,Spring容器会调用所有注册的BeanPostProcessor的postProcessBeforeInitialization方法,允许开发者在Bean初始化之前进行自定义处理。常用于定义初始化Bean的前置工作,比如系统缓存的初始化。执行初始化方法:
如果Bean实现了InitializingBean接口,Spring容器会调用其afterPropertiesSet方法。
如果在配置中指定了init-method属性,Spring容器会调用该属性指定的方法。
执行BeanPostProcessor.postProcessAfterInitialization:在Bean初始化之后,Spring容器会调用所有注册的BeanPostProcessor的postProcessAfterInitialization方法,允许开发者在Bean初始化之后进行自定义处理。
使用(Usage)
- Bean的使用:在Spring容器管理下,Bean可以在应用中的任何地方被按需使用。容器负责维护Bean的实例和生命周期。
销毁(Destruction)
- 执行销毁逻辑:当Spring容器关闭时,它会释放所有管理的Bean,并执行销毁逻辑。销毁逻辑可以通过以下几种方式实现:
- 当要销毁 Bean 的时候,如果 Bean 实现了
@PreDestroy
注解 ,执行 destroy() 方法。 - 当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean
接口,执行 destroy() 方法。 - 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
- 当要销毁 Bean 的时候,如果 Bean 实现了
- 执行销毁逻辑:当Spring容器关闭时,它会释放所有管理的Bean,并执行销毁逻辑。销毁逻辑可以通过以下几种方式实现:
注意:
初始:类构造器 > @PostConstruct > InitializingBean > init-method
销毁:@PreDestroy > DisposableBean > destroyMethod
Spring 内置哪些类型的事件?
ContextRefreshedEvent(上下文更新事件)
触发时机 :当Spring容器完成初始化或者被刷新时触发。这包括在容器首次启动时,以及在执行某些特定操作(如动态修改了Bean的定义信息,然后调用ApplicationContext的refresh方法)之后。
应用场景和作用
初始化后操作:可以在监听器中执行一些需要在容器完全初始化后才能进行的操作。例如,在一个Web应用中,当Spring容器初始化完成后,可以在监听器中启动一些后台任务,如定时任务调度器,或者初始化缓存系统,将一些基础数据加载到缓存中。
检查Bean状态:可以用于检查容器中所有Bean是否正确加载和初始化。例如,检查关键的业务Bean是否已经成功创建,并且它们的依赖关系是否正确配置。如果发现问题,可以记录错误或者采取相应的补救措施。
ContextStartedEvent(容器启动事件)
触发时机 :当Spring容器的start方法被调用时触发。这个事件通常用于那些支持生命周期管理的容器,如ConfigurableApplicationContext。与ContextRefreshedEvent不同,start操作更侧重于启动一个已经初始化的容器相关的资源。
应用场景和作用
- 启动外部资源:在监听器中,可以启动与容器相关的外部资源,如开启消息队列的消费者,开始接收消息进行处理。或者启动一个远程服务调用客户端,使其能够开始与远程服务器进行通信。
- 恢复系统状态:如果应用程序支持暂停和恢复功能,当容器重新启动时,可以在监听器中恢复之前保存的系统状态。例如,重新加载之前保存的用户会话信息或者应用程序的配置状态。
ContextStoppedEvent(容器停止事件)
触发时机 :当Spring容器的stop方法被调用时触发。这表示容器的运行时状态被停止,但容器本身仍然存在,并且可以通过start方法重新启动。
应用场景和作用
清理资源:在监听器中,可以清理在容器运行过程中占用的资源。例如,关闭数据库连接、释放文件句柄、停止正在运行的定时任务等。这有助于避免资源泄漏,并且使得容器能够安全地停止。
保存系统状态:如果应用程序需要保存当前状态以便后续恢复,可以在监听器中实现状态保存的逻辑。例如,将当前用户的会话信息或者应用程序的配置修改保存到持久化存储中。
ContextClosedEvent(容器关闭事件)
触发时机 :当Spring容器被完全关闭时触发,通常是通过调用ApplicationContext的close方法。这意味着容器的生命周期结束,所有资源都应该被释放。
应用场景和作用
- 资源释放和清理:与ContextStoppedEvent类似,但更强调彻底的资源释放。可以在监听器中确保所有的资源,包括缓存数据、线程池中的线程等都被正确地清理。并且可以进行一些善后工作,如记录容器关闭的日志信息,通知其他相关系统组件容器已经关闭等。
RequestHandledEvent(请求处理完成事件)
触发时机 :在一个基于Spring MVC的Web应用中,当一个HTTP请求被成功处理完成后触发。这个事件包含了关于请求的信息,如请求的URL、处理请求的时间等。
应用场景和作用
性能统计和监控:可以在监听器中收集请求处理的相关数据,用于性能统计和监控。例如,记录每个请求的处理时间,统计不同URL的请求频率,以便分析系统的性能瓶颈和热门功能。
日志记录和审计:用于记录请求处理的详细信息,进行审计和故障排查。例如,记录请求的参数、返回的结果等信息,当出现问题时,可以通过这些记录快速定位问题所在。
ServletRequestHandledEvent(Servlet请求处理完成事件)
触发时机:这是RequestHandledEvent的一个子类,专门用于处理Servlet请求。它在Servlet请求处理完成后触发,并且包含了更多与Servlet相关的详细信息,如Servlet名称、请求和响应的相关属性等。
应用场景和作用
更详细的Web请求监控:在基于Servlet的Spring Web应用中,可以使用这个事件来进行更详细的请求监控和分析。例如,分析不同Servlet之间的请求负载分布,或者查看特定Servlet的请求处理性能是否存在问题。
与Servlet容器集成:可以与Servlet容器的功能更好地集成。例如,根据Servlet容器的特性来调整请求处理策略,或者利用Servlet容器提供的资源管理功能来优化请求处理过程。
Spring 怎么创建、发布和监听自定义事件?
在Spring框架中,自定义事件的创建涉及几个关键步骤,包括定义事件类、创建事件监听器以及发布事件。
一、定义事件类
创建自定义事件类:
- 通常,自定义事件类需要继承
ApplicationEvent
类。 - 在自定义事件类中,可以定义额外的属性来存储事件相关的数据。
- 提供一个构造方法,该构造方法需要调用父类
ApplicationEvent
的构造方法,并传入事件源(通常是触发事件的对象)。
import org.springframework.context.ApplicationEvent;
public class MyCustomEvent extends ApplicationEvent {
private String eventData; // 事件相关数据
public MyCustomEvent(Object source, String eventData) {
super(source);
this.eventData = eventData;
}
public String getEventData() {
return eventData;
}
}
二、创建事件监听器
方式一:使用@EventListener注解:
- 在监听器类中,可以使用
@EventListener
注解来标注处理事件的方法。 - 该方法的参数类型应该是自定义事件类。
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MyCustomEventListener {
@EventListener
public void handleCustomEvent(MyCustomEvent event) {
System.out.println("接收到自定义事件: " + event.getEventData());
// 处理事件的逻辑
}
}
方式二:实现ApplicationListener接口:
- 另一种方式是让监听器类实现
ApplicationListener
接口,并覆盖onApplicationEvent
方法。 - 该方法的参数类型也应该是自定义事件类。
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class MyApplicationListener implements ApplicationListener<MyCustomEvent> {
@Override
public void onApplicationEvent(MyCustomEvent event) {
System.out.println("通过ApplicationListener接收到自定义事件: " + event.getEventData());
// 处理事件的逻辑
}
}
三、发布事件
使用ApplicationEventPublisher:
- 要发布事件,可以使用
ApplicationEventPublisher
接口。 - 通常,可以通过自动装配(@Autowired)将
ApplicationEventPublisher
注入到需要发布事件的类中。 - 然后,调用
publishEvent
方法来发布事件。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class MyEventPublisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publishCustomEvent() {
MyCustomEvent event = new MyCustomEvent(this, "这是自定义事件的数据");
eventPublisher.publishEvent(event);
}
}
四、注意事项
确保监听器类被Spring管理:监听器类需要使用
@Component
或其他Spring管理组件的注解进行标注,以确保它们被Spring IoC容器管理。事件发布者的调用:在调用发布事件的方法时,需要确保发布者(如上面的
MyEventPublisher
类)已经被Spring容器管理,并且可以通过依赖注入的方式获取到ApplicationEventPublisher
。事件传递的数据:
在自定义事件类中,可以定义任意数量的属性来传递事件相关的数据。
在监听器处理事件的方法中,可以通过事件对象的getter方法来获取这些数据。
Spring 中的常见扩展点有哪些?
Spring框架中的扩展点是指一组接口或机制,允许开发者在不修改核心框架源代码的情况下,定制和扩展Spring框架的功能、行为或配置。
BeanFactoryPostProcessor
- 用途:允许在 Bean 定义加载后、实例化之前修改 Bean 定义的属性。
- 典型使用场景:配置文件占位符解析(如
PropertyPlaceholderConfigurer
)、国际化资源消息源配置等。 - 实现方式:实现
org.springframework.beans.factory.config.BeanFactoryPostProcessor
接口。
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 修改 Bean 定义
}
}
BeanPostProcessor
- 用途:允许在 Bean 实例化之后、初始化前后进行处理。
- 典型使用场景:AOP 代理创建、依赖注入后的额外处理、验证 Bean 状态等。
- 实现方式:实现
org.springframework.beans.factory.config.BeanPostProcessor
接口。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 执行 bean 的初始化方法前被触发;
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 执行 bean 的初始化方法后被触发。
return bean;
}
}
ApplicationListener
- 用途:监听并响应特定的应用事件(如上下文刷新、启动失败等)。Spring框架提供了很多事件,如ContextRefreshedEvent、ContextStartedEvent和ContextClosedEvent等。
- 典型使用场景:日志记录、发送通知、清理资源等。
- 实现方式:实现
org.springframework.context.ApplicationListener<E extends ApplicationEvent>
接口,其中E
是具体事件类型。
@Component
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 处理上下文刷新事件
}
}
SmartLifecycle
- 用途:控制 Bean 的生命周期行为,允许更细粒度地管理启动和停止过程。
- 典型使用场景:后台任务管理、定时任务调度等。
- 实现方式:实现
org.springframework.context.SmartLifecycle
接口。
@Component
public class MySmartLifecycle implements SmartLifecycle {
private boolean running = false;
@Override
public void start() {
// 启动逻辑
running = true;
}
@Override
public void stop() {
// 停止逻辑
running = false;
}
@Override
public boolean isRunning() {
return running;
}
}
InitializingBean
- 用途:用于定义 Bean 初始化完成后的操作。
- 典型使用场景:加载资源、初始化属性等。
- 实现方式:实现
org.springframework.beans.factory.InitializingBean
。在bean的属性填充之后,初始化方法(init-method)之前被触发,该方法的作用基本等同于 init-method 。
public class MyBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}
}
DisposableBean
- 用途:定义 Bean 销毁前后的操作。
- 典型使用场景:资源释放、连接池管理等。
- 实现方式:实现
org.springframework.beans.factory.DisposableBean
。用于在Bean销毁前进行操作,该方法的作用基本等同于 destroy-method,主用用于执行销毁相关操作。
public class MyBean implements DisposableBean {
@Override
public void destroy() throws Exception {
// 销毁逻辑
}
}
FactoryBean
- 用途:允许自定义对象的创建逻辑,通常用于返回复杂对象或代理对象。
- 典型使用场景:创建代理对象、管理第三方库的复杂初始化等。
- 实现方式:实现
org.springframework.beans.factory.FactoryBean<T>
接口。
public class MyFactoryBean implements FactoryBean<MyObject> {
@Override
public MyObject getObject() throws Exception {
// 创建并返回对象
return new MyObject();
}
@Override
public Class<?> getObjectType() {
return MyObject.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
Custom EditorRegistrar
- 用途:注册自定义的 PropertyEditor,用于将字符串转换为复杂对象。
- 典型使用场景:表单数据绑定、命令行参数解析等。
- 实现方式:实现
org.springframework.beans.PropertyEditorRegistrar
接口,并使用@InitBinder
注解(在控制器中)。
@Controller
public class MyController {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(MyComplexType.class, new MyComplexTypeEditor());
}
}
MethodInterceptor
- 用途:拦截方法调用,允许在方法执行前后添加额外逻辑。
- 典型使用场景:事务管理、性能监控、日志记录等。
- 实现方式:实现
org.aopalliance.intercept.MethodInterceptor
接口。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 方法调用前逻辑
Object result = invocation.proceed();
// 方法调用后逻辑
return result;
}
}
WebMvcConfigurer
- 用途:自定义 Spring MVC 配置,如视图解析器、静态资源映射等。
- 典型使用场景:定制化 Web 应用的 MVC 行为。
- 实现方式:实现
org.springframework.web.servlet.config.annotation.WebMvcConfigurer
接口。
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/static/");
}
}
EnvironmentPostProcessor
- 用途:在应用程序环境初始化完成后对其进行修改。
- 典型使用场景:动态修改配置文件路径、添加额外的配置源等。
- 实现方式:实现
org.springframework.boot.env.EnvironmentPostProcessor
接口。
public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 修改环境配置
}
}
此外,Spring还提供了其他扩展点,如 ApplicationContextInitializer、BeanDefinitionRegistryPostProcessor、SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor、DestructionAwareBeanPostProcessor、InstantiationAwareBeanPostProcessor、ApplicationContextAware、EmbeddedValueResolverAware、EnvironmentAware、ImportBeanDefinitionRegistrar、ImportSelector、ResourceLoaderAware、ServletContextAware、BeanNameAware,@PostConstruct和 @PreDestroy 等,这些扩展点允许开发者在Spring框架的不同阶段进行自定义和扩展。
AOP的核心概念有哪些?
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,是一种通过预编译方式和运行期动态代理实现程序功能统一维护的技术。 它允许开发者将横切关注点(cross-cutting concerns)与业务逻辑分离,从而降低各部分之间的耦合度,提高代码的模块化和可维护性、灵活性和可扩展性。横切关注点是指那些在多个地方出现、与业务逻辑不直接相关的功能,例如日志记录、事务管理、权限检查、异常处理等。AOP是面向对象编程(OOP)的延续和补充。OOP专注于核心业务逻辑的实现,而AOP则负责处理非业务逻辑的关注点。两者相辅相成,共同构成了现代软件开发中的重要组成部分。
AOP 通过以下几个核心概念来实现这一目标:
连接点(Join Point):连接点是程序执行过程中的一个特定点,即业务层中的所有方法。比如方法的调用或者异常的抛出。通知就是在这些连接点上执行的。
切点(Pointcut):切点是一组匹配连接点的规则,它定义了哪些连接点会被切面的通知所影响。切入点一定是连接点,但连接点不一定是切入点。
切面(Aspect):切面是横切关注点的模块化,它包含了一组连接点(Join Point)和在这些连接点上应用的通知(Advice)。它定义了在何处、何时以及如何应用横切关注点。
通知(Advice):通知是在切点(Pointcut)指定的连接点上执行的一段代码。它定义了切面在何时何地如何被应用。
织入(Weaving):织入是将切面应用到目标对象并创建代理对象的过程。这可以在编译时、类加载时或运行时进行。
目标对象(Target Object):目标对象是被一个或多个切面所通知的对象,也就是真正的业务逻辑所在的地方。
代理(Proxy):AOP框架创建的代理对象,它包装了目标对象,并在调用目标对象的方法时插入额外的行为。
引入(Introduction):引入允许向目标对象添加新的方法或属性。
特点:
- 关注点分离:AOP实现了关注点(如日志、事务等)与业务逻辑的分离,使代码更加清晰和模块化。
- 动态代理:AOP通过动态代理的方式在不修改源代码的情况下给程序动态统一添加某种特定功能。
- 高内聚低耦合:AOP使得业务逻辑各部分之间的耦合度降低,提高了代码的可重用性和灵活性。
核心原理:AOP的核心原理是使用动态代理的方式在执行方法前后或者出现异常的时候加入相关的逻辑。这些逻辑被称为“增强”或“通知”。
AOP 有哪些常见实现方式?
AOP(面向切面编程)的实现方式多种多样,以下是几种常见的实现方式:
基于代理的实现:
- JDK动态代理:这种方式基于Java的反射机制,通过实现
java.lang.reflect.InvocationHandler
接口来创建代理对象。JDK动态代理只能代理实现了接口的类。 - CGLIB代理:CGLIB是一个开源项目,它可以在运行时动态生成和转换字节码。CGLIB代理可以代理没有实现接口的类,它通过继承目标类来创建代理对象。
基于AspectJ的实现:
- AspectJ是一个功能强大的AOP框架,它提供了完整的AOP编程支持,包括切面定义、切入点表达式、通知类型等。AspectJ可以通过编译时织入或加载时织入的方式实现AOP。
- 编译时织入:在编译阶段,AspectJ通过修改字节码将切面逻辑织入到目标类中。
- 加载时织入:在类加载阶段,AspectJ通过Java的类加载机制将切面逻辑织入到目标类中。
基于Spring AOP的实现:
- Spring AOP是Spring框架中的AOP实现,它基于动态代理和AspectJ的注解支持。Spring AOP主要用于解决Spring容器中Bean的横切关注点问题。
- Spring AOP支持方法级别的切面,可以通过注解或XML配置来定义切面、切入点和通知类型。
- Spring AOP还可以与AspectJ结合使用,以提供更强大的AOP功能。
其他实现方式:
- 除了上述常见的实现方式外,还有一些其他的AOP实现方式,如使用自定义类来实现AOP(主要是切面定义)、使用Spring的API接口实现AOP等。这些实现方式通常需要根据具体的需求和技术选型来选择。
AOP 通知类型有哪些?
AOP(面向切面编程)的通知类型主要有以下几种:
前置通知(Before Advice):
在目标方法执行之前执行。
可以使用
@Before
注解来定义前置通知。可以没有参数,也可以额外接收一个JoinPoint,Spring会自动将该对象传入,代表当前的连接点,通过该对象可以获取目标对象和目标方法相关的信息。
如果接收JoinPoint,必须保证其为方法的第一个参数,否则报错!
前置通知通常用于执行一些前置条件检查或准备工作。
后置通知(After Advice):
在目标方法执行之后执行,无论目标方法是否成功完成。
可以使用
@After
注解来定义后置通知。后置通知通常用于释放资源或执行一些清理工作。
在后置通知中也可以选择性的接收一个JoinPoint来获取连接点的额外信息,但是这个参数必须处在参数列表的第一位,否则抛异常。在后置通知中,还可以通过配置获取返回值。
返回通知(After Returning Advice):
在目标方法成功执行并返回结果之后执行。
可以使用
@AfterReturning
注解来定义返回通知。返回通知通常用于处理正常的返回结果,如记录日志或更新缓存。
异常通知(After Throwing Advice):
在目标方法抛出异常之后执行。
可以使用
@AfterThrowing
注解来定义异常通知。异常通知通常用于处理异常,如记录异常信息或进行异常恢复。
环绕通知(Around Advice):
包围目标方法的执行,可以在方法执行前后和方法抛出异常时执行自定义逻辑。
可以使用
@Around
注解来定义环绕通知。环绕通知是最强大的通知类型,它可以实现其他四种通知类型的功能。环绕通知的方法需要接收一个
ProceedingJoinPoint
类型的参数,通过调用该参数的proceed
方法来继续执行目标方法。这个参数必须处在环绕通知的第一个形参位置。环绕通知需要返回返回值,否则真正调用之将拿不到返回值,只能得到一个null。
Spring AOP 的相关注解有哪些?
- @Aspect:该注解用于标识一个类作为切面类,允许在其中定义切点和通知。切面类是可以定义切入点表达式和前后通知的类。
- @Pointcut:该注解用于定义一个切点,可以与@Before、@AfterReturning、@AfterThrowing等注解结合使用。通过value属性指定切点表达式,用于匹配连接点。
- @Before:该注解用于定义前置通知,它会在目标方法执行之前执行被此注解标注的方法。通过value或pointcut属性指定切点表达式。
- @After:该注解用于定义后置通知,它会在目标方法执行之后执行被此注解标注的方法。无论目标方法是否成功完成,后置通知都会被执行。通过value或pointcut属性指定切点表达式。
- @AfterReturning:该注解用于定义返回通知,它会在目标方法成功执行并返回结果之后执行被此注解标注的方法。除了可以写切入点表达式外,还可以指定一个返回值形参名returning,代表目标方法的返回值。
- @AfterThrowing:该注解用于定义异常抛出通知,它会在目标方法抛出异常之后执行被此注解标注的方法。除了可以写切入点表达式外,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象。
- @Around:该注解用于定义环绕通知,它包围目标方法的执行,允许在方法执行前后和方法抛出异常时执行自定义逻辑。环绕通知是最重要的通知类型,像事务、日志等都是环绕通知。环绕通知的方法需要接收一个ProceedingJoinPoint类型的参数,通过调用该参数的proceed方法来继续执行目标方法。
- @EnableAspectJAutoProxy:该注解用于开启对AspectJ代理的支持,通常在配置类上使用。它使得Spring容器能够识别并创建切面代理对象。
在Spring 中配置AOP的步骤?
在Spring中实现AOP(面向切面编程)的步骤可以详细划分为以下几个环节:
一、准备工作
引入依赖:
在项目的构建文件(如Maven的
pom.xml
或Gradle的build.gradle
)中,添加Spring AOP和AspectJ的依赖。Spring Boot项目通常可以通过引入
spring-boot-starter-aop
依赖来自动配置AOP所需的环境。
定义业务接口和实现类:
- 创建需要被增强的业务接口及其实现类。这些类将作为AOP的目标对象。
二、创建切面类
使用@Aspect注解:
- 在切面类上使用
@Aspect
注解,表明该类是一个切面类。
- 在切面类上使用
定义切点:
使用
@Pointcut
注解定义切点表达式,用于指定哪些方法需要被拦截。切点表达式通常使用AspectJ表达式语法,可以匹配方法签名、参数类型、注解等。
定义通知:
在切面类中定义各种通知(如前置通知、后置通知、环绕通知等),并使用相应的注解(如
@Before
、@After
、@Around
等)将其与切点表达式关联起来。通知方法包含了横切逻辑,即需要在目标方法执行前后或异常时执行的代码。
三、配置Spring容器
启用AspectJ自动代理:
- 在Spring配置类上使用
@EnableAspectJAutoProxy
注解,启用AspectJ自动代理功能。这样,Spring容器在创建Bean时会自动为匹配的Bean创建代理对象。
- 在Spring配置类上使用
扫描切面类:
- 确保Spring配置类能够扫描到切面类所在的包。这可以通过
@ComponentScan
注解来指定扫描路径。
- 确保Spring配置类能够扫描到切面类所在的包。这可以通过
// 业务接口
public interface UserService {
void addUser();
}
// 业务实现类
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加用户");
}
}
// 切面类
@Aspect
@Component
public class UserAspect {
// 定义切点表达式
@Pointcut("execution(* com.example.demo.service.UserServiceImpl.addUser(..))")
public void addUserPointcut() {}
// 前置通知
@Before("addUserPointcut()")
public void beforeAddUser() {
System.out.println("执行addUser方法之前");
}
// 后置通知
@After("addUserPointcut()")
public void afterAddUser() {
System.out.println("执行addUser方法之后");
}
}
// Spring配置类
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy
public class AppConfig {
}
// 应用程序入口
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.addUser();
}
}
综上所述,Spring中AOP的实现步骤包括引入依赖、创建切面类、配置Spring容器、运行和测试等环节。通过这些步骤,可以将横切关注点与业务逻辑分离,提高代码的可维护性和可扩展性。
Spring AOP 中通知类型的执行顺序?
在Spring AOP中,通知的执行顺序取决于切面的定义和匹配的切入点表达式。以下是常见的执行顺序:
环绕通知(@Around)前置部分:如果定义了环绕通知,则首先执行环绕通知的前置部分(在目标方法执行之前)。
前置通知(@Before):接着执行所有前置通知。
目标方法:然后执行目标方法本身。
环绕通知(@Around)后置部分:在目标方法执行之后,如果定义了环绕通知,则执行环绕通知的后置部分。
正常返回通知或异常通知:
如果目标方法正常执行并返回结果,则执行所有正常返回通知**(@AfterReturning)**。
如果目标方法抛出异常,则执行所有异常通知**(@AfterThrowing)**。
后置通知(@After):无论目标方法是否抛出异常,最后都会执行所有后置通知。
注意:具体的执行顺序可能因配置和使用的Spring版本而略有不同。
- 在Spring 4中,正常的执行顺序是前置通知、后置通知、正常返回通知(如果目标方法正常执行)或异常通知(如果目标方法抛出异常)。
- 在Spring 5中,正常的执行顺序是前置通知、正常返回通知(如果目标方法正常执行)、后置通知,异常执行顺序是前置通知、异常通知、后置通知。如果使用了环绕通知,则环绕通知的前置部分会在前置通知之前执行,环绕通知的后置部分会在后置通知和正常返回通知或异常通知之后执行。
在实际应用中,可以根据需求选择合适的通知类型,并按照需要组合它们来构建复杂的切面逻辑。
Spring 多个AOP的执行顺序怎么定?
在Spring中,当有多个AOP切面(Aspect)时,它们的执行顺序可以通过多种方式来确定。以下是几种常见的方法:
一、通过@Order注解指定顺序
Spring提供了@Order注解,用于指定切面之间的执行顺序。@Order注解的value属性是一个整数,值越小优先级越高,因此切面会按照value值从小到大的顺序执行。
// FirstAspect会先于SecondAspect执行。
@Aspect
@Component
@Order(1)
public class FirstAspect {
// 切面逻辑
}
@Aspect
@Component
@Order(2)
public class SecondAspect {
// 切面逻辑
}
二、通过实现Ordered接口指定顺序
除了@Order注解外,还可以通过实现Ordered接口来指定切面的执行顺序。Ordered接口包含一个getOrder()方法,返回一个整数,用于表示切面的优先级。返回值越小,优先级越高。
@Aspect
@Component
public class OrderedAspect implements Ordered {
@Override
public int getOrder() {
return 1; // 优先级较高
}
// 切面逻辑
}
三、通过配置文件指定顺序
在Spring的配置文件中,也可以通过指定切面的order属性来确定它们的执行顺序。例如,在XML配置文件中,可以使用<aop:aspect>
标签的order
属性来指定切面的顺序。
<aop:config expose-proxy="true">
<aop:aspect ref="firstAspect" order="1">
<!-- 切面配置 -->
</aop:aspect>
<aop:aspect ref="secondAspect" order="2">
<!-- 切面配置 -->
</aop:aspect>
</aop:config>
四、默认执行顺序
如果没有通过上述方式指定切面的执行顺序,Spring将按照切面bean的名称字母顺序来执行。例如,名为"AspectA"的切面会先于名为"AspectB"的切面执行。
Spring 实现动态代理的原理是怎样的?
Spring AOP 代理对象生成策略?
Spring 框架默认使用两种主要的代理生成策略来创建 AOP 代理对象:JDK 动态代理 和 CGLIB 代理。选择哪种策略取决于目标 Bean 的特性和配置。
JDK 动态代理(默认优先)
条件:接口存在:如果目标 Bean 实现了一个或多个接口,Spring 会优先使用 JDK 动态代理。
工作原理:JDK 动态代理通过
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口实现。它为每个接口创建一个代理类,并在方法调用时将请求转发给InvocationHandler
,从而允许在实际方法执行前后插入额外逻辑。优点
性能较好:由于它是 Java 标准库的一部分,通常具有较好的性能。
代码清晰:基于接口的代理更加直观和易于理解。
缺点:仅限接口,只能代理实现了接口的方法,不能代理类中的非接口方法。
CGLIB 代理
条件:无接口,如果目标 Bean 没有实现任何接口,或者配置明确指定了使用 CGLIB。proxy-target-class 属性:当配置中设置了
proxy-target-class="true"
,无论目标 Bean 是否实现了接口,都会使用 CGLIB 代理。工作原理:子类化:CGLIB 通过生成目标类的子类来实现代理。这种方式可以拦截到所有方法调用,包括那些没有被接口定义的方法。
优点
全面代理:可以代理所有方法,不仅仅是接口方法。
灵活性高:适用于没有接口的目标类。
缺点
性能稍差:相比于 JDK 动态代理,CGLIB 代理可能稍微慢一些,因为它涉及到字节码操作。
复杂性增加:可能会引入更多的复杂性和潜在问题,例如与 final 方法或类的冲突。
配置选项
默认为false即jdk代理,为true即为cglib代理。
xml 配置
xml<aop:config proxy-target-class="true"/>
Java 配置
java@EnableAspectJAutoProxy(proxyTargetClass = true)
自动选择:如果没有显式配置,默认情况下,Spring 会尝试使用 JDK 动态代理。只有当发现目标 Bean 没有实现任何接口时,才会自动切换到 CGLIB 代理。
注意事项
- final 方法和类:CGLIB 无法代理
final
方法或类,因为它们不能被继承和覆盖。 - 性能影响:虽然大多数情况下代理机制不会显著影响性能,但在性能敏感的应用中,应该谨慎选择代理策略。
- 依赖注入:对于 CGLIB 代理,构造器注入可能会带来挑战,因为 CGLIB 创建的是目标类的子类,这意味着构造器注入的参数需要特别处理。
- SpringBoot:1x 默认为 JDK,2x 默认为 CGLIB
JDK 动态代理和 CGLIB 代理的区别?
JDK 动态代理和 Cglib 代理是 Java 中实现动态代理的两种不同机制,它们在多个方面存在显著的区别。
一、基于接口 vs 基于类
- JDK 动态代理:JDK 动态代理只能代理实现了接口的类,生成的代理对象会实现与目标对象相同的接口。
- Cglib 代理:CGLIB 代理可以代理任何类,即使该类没有实现接口,通过生成目标类的子类来实现代理,并重写父类的方法。
二、实现机制
- JDK 动态代理:使用反射机制,通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口来实现代理。代理对象仅代理接口中的方法。当调用代理对象的方法时,代理类会拦截方法调用,并通过 InvocationHandler.invoke() 方法执行额外的逻辑。
- Cglib 代理:基于字节码操作,它使用 CGLIB(Code Generation Library)生成目标类的子类并重写目标类的方法来实现代理。通过
Enhancer
类来创建代理对象,需要提供一个MethodInterceptor
或Callback
实例,该实例负责处理代理对象的方法调用。
三、性能差异
- 对于实现了接口的类来说,JDK 动态代理在创建代理对象时开销较小,因为它仅依赖反射机制来处理接口方法的调用。然而,对于频繁调用代理方法的场景,JDK 动态代理可能比 Cglib 略慢,因为每次调用都涉及反射。 由于 Cglib 是通过字节码生成来创建代理类,生成代理类的开销比 JDK 动态代理高一些,尤其是在代理类较多的情况下。但 Cglib 代理的实际方法调用性能更高,因为它通过字节码操作,减少了反射调用的开销。
- JDK1.7 之前,由于使用了 FastClass 机制,Cglib 在执行效率上比 JDK 快,但是随着 JDK 动态代理的不断优化,从 JDK 1.7 开始,JDK 动态代理已经明显比 Cglib 更快了。
四、使用场景
- JDK 动态代理:适用于接口驱动的编程。如果目标类实现了接口,那么使用 JDK 动态代理是首选方式。这在应用中很常见,因为业务逻辑往往是通过接口定义的。
- Cglib 代理:适用于没有实现接口的类。例如,一些现有类或者第三方库的类没有提供接口的情况下,可以使用 Cglib 动态代理。此外,当需要对类进行代理且类不能是 final 时,也可以使用 Cglib。
Spring AOP 中编织的时机是?
织入是将增强应用到目标对象并创建代理对象的过程。Spring AOP采用运行时织入(Runtime Weaving),即在运行时通过动态代理技术创建代理对象,并在调用目标方法时执行增强逻辑。
Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP和AspectJ AOP都是实现面向切面编程(AOP)的技术,但它们在实现机制、功能和适用场景上有一些重要的区别,同时也存在一定的联系。
实现机制:
- Spring AOP:基于动态代理技术实现。对于实现了接口的类,使用JDK动态代理;对于没有实现接口的类,使用CGLIB代理。切面在运行时被织入到目标对象中。
- AspectJ AOP:可以在编译时将切面逻辑织入到目标类中,生成新的字节码文件(编译时织入)。它也可以在运行时通过加载时织入(Load-Time Weaving,LTW)来织入切面。
连接点:
- Spring AOP:主要对Spring管理的Bean生效,只支持方法级别的连接点,即只能在方法调用前后插入切面逻辑。
- AspectJ AOP:可以应用于所有Java对象,支持更广泛的连接点,包括方法调用、字段访问、构造函数调用、异常处理等。
表达式:
- Spring AOP:支持基本的切入点表达式,如
execution
、within
等,但相对 AspectJ 较为有限。 - AspectJ AOP:提供更加复杂和灵活的切入点表达式,可以精确地匹配各种连接点。
切面类型:
- Spring AOP:主要支持前置通知、后置通知、环绕通知、异常通知等有限的切面类型。
- AspectJ AOP:提供了更强大的切面编程能力,支持更多的切面类型,包括引入新方法和字段等。
依赖性和独立性:
- Spring AOP:需要Spring容器的支持,通常与Spring应用程序集成。
- AspectJ AOP:不依赖于任何特定的框架,可以独立使用。
性能和内存占用:
- Spring AOP:动态代理的方式相对轻量,性能较好。但每次方法调用都会涉及额外的代理对象,对于大规模或频繁的切面逻辑可能会影响性能并增加内存占用。
- AspectJ AOP:编译时织入可以减少运行时开销,提高性能。同时,由于直接修改字节码,减少了运行时的开销和内存占用,但配置和使用更为复杂。
适用场景
- Spring AOP:简单的 AOP 场景,如果只需要对方法执行进行拦截,并且应用程序已经使用了 Spring 框架,那么 Spring AOP 是一个非常好的选择。
- AspectJ AOP:复杂的 AOP 场景,当需要更细粒度的控制或更广泛的连接点支持时,AspectJ 提供了更好的解决方案。性能敏感的应用,对于性能要求较高的应用,特别是那些需要频繁调用 AOP 逻辑的场景,AspectJ 的静态编织方式可以减少运行时开销。
总结
- Spring AOP 更适合于大多数企业级应用,尤其是那些已经使用 Spring 框架并且只需要对方法执行进行拦截的情况。它的实现基于代理,易于集成和使用,但功能相对有限。
- AspectJ AOP 提供了更强大的功能和更高的灵活性,适合需要高级 AOP 特性的复杂应用场景。虽然其学习曲线较高,但对于需要全面连接点支持和高性能的应用来说是一个很好的选择。
什么是SpringMVC?
SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架。是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦。
MVC框架模式是一种复合模式,MVC的三个核心部件分别是:
- Model(模型):所有的用户数据、状态以及程序逻辑,独立于视图和控制器
- View(视图):呈现模型,类似于Web程序中的界面,视图会从模型中拿到需要展现的状态以及数据,对于相同的数据可以有多种不同的显示形式(视图)
- Controller(控制器):负责获取用户的输入信息,进行解析并反馈给模型,通常情况下一个视图具有一个控制器
SpringMVC有以下一些SpringMVC特点:框架提供了一整套完善的组件。SpringMVC是以强大的Spring容器为基础的框架。框架的配置简单又不失灵活性。代码的可重用性很高。可扩展性好。
Spring MVC 有哪些组件?
Spring MVC的核心组件主要包括以下几个:
- DispatcherServlet:前端控制器,是整个请求处理流程的入口。它负责接收请求,并根据请求的URL将其分派给适当的处理器(Controller)。
- HandlerMapping:处理器映射器,用于将客户端请求映射到处理程序(Controller 方法)。它根据请求的URL、请求的类型或其他标准确定应该由哪个处理程序来处理请求。
- Controller:控制器,是Spring MVC中的核心组件之一,负责处理客户端请求并生成相应的响应(
ModelAndView
或直接返回数据@ResponseBody
)。它通常包含了一些处理方法,用于处理特定类型的请求。 - HandlerAdapter:处理器适配器,负责执行实际的处理程序(Controller 方法)并处理其输出。Spring MVC支持多种处理程序适配器,用于适配不同类型的处理程序。
- Model:模型数据,通常是一个Map对象,用于存储和传递控制器处理结果的数据。
- View:视图组件,用于展示结果。Spring MVC支持多种视图技术,如JSP、Thymeleaf、FreeMarker等。
- ModelAndView:模型和视图的容器。它将处理方法的执行结果(模型数据)和视图名称封装在一起,以便将其传递给DispatcherServlet。
- ViewResolver:视图解析器,负责将逻辑视图名称解析为实际的视图对象。它根据视图名称查找对应的视图实现,并将其返回给DispatcherServlet以便呈现给客户端。
此外,还有一些可选的或辅助的组件,
- HandlerInterceptor:处理程序拦截器,用于在请求处理过程中执行预处理和后处理操作,如日志记录、权限检查等。
- HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor。
- WebDataBinder:数据绑定器,负责将请求参数绑定到处理方法的参数上,并支持数据验证和数据转换等功能。
- MultipartResolver:文件上传解析器,用于处理文件上传的解析,将客户端上传的文件数据解析为MultipartFile对象。
- LocaleResolver:区域解析器,用于解析客户端的区域设置信息,以确定应该使用哪种语言和区域的资源。
- ThemeResolver:主题解析器,用于解析客户端的主题信息,以确定应该使用哪种主题样式来呈现视图。 这些组件共同协作,使得Spring MVC能够高效地处理Web请求,并生成相应的响应。
这些组件共同协作,提供了一个灵活、可扩展的Web应用开发模型。开发者可以根据需要自定义这些组件,以适应不同的业务需求。
Spring MVC 的处理流程?
Spring MVC 的处理流程可以分为几个主要步骤,从客户端请求到达服务器开始,直到响应被发送回客户端。以下是详细的处理流程:
请求发送阶段
- 用户向Spring MVC应用发送一个HTTP请求。这个请求包含了请求的URL、请求方法(如GET、POST等)、请求头信息(如Content - Type)以及可能的请求体(如表单数据、JSON数据等)。
前端控制器(DispatcherServlet)接收阶段
所有的请求首先会被前端控制器
DispatcherServlet
拦截。DispatcherServlet
是通过在web.xml
中配置或使用 Java 配置类中的@WebServlet
注解来启动的。
处理器映射(HandlerMapping)查找阶段
DispatcherServlet
会将请求委托给HandlerMapping
来查找合适的处理器(Handler)。HandlerMapping
会根据请求的各种特征(如URL路径、请求参数、HTTP方法等)来查找对应的处理器(控制器方法上)。常见的
HandlerMapping
实现有RequestMappingHandlerMapping
和BeanNameUrlHandlerMapping
。
创建 HandlerExecutionChain
HandlerMapping找到对应的处理器后,还会生成一个处理器执行链(HandlerExecutionChain),这个执行链中包含了处理器对象以及可能存在的处理器拦截器(HandlerInterceptor)。
拦截器可以在处理器执行前后进行额外的处理,如日志记录、权限检查等。
处理器适配器(HandlerAdapter)调用阶段
Spring MVC中的处理器可以有多种形式(如基于注解的Controller方法、实现了特定接口的处理器等),
HandlerAdapter
的作用是对不同类型的处理器进行适配,使得DispatcherServlet
能够以统一的方式调用处理器。一旦
HandlerMapping
找到了合适的处理器,DispatcherServlet
会通过HandlerAdapter
来调用这个处理器。HandlerAdapter
会处理参数绑定、类型转换等,并调用控制器方法。
处理器(Handler,通常是Controller)处理阶段
处理器(Controller)在HandlerAdapter的调用下执行具体的业务逻辑,并生成一个
ModelAndView
对象或直接返回数据(使用@ResponseBody
)。ModelAndView
对象包含了模型数据和视图名称。
模型与视图(Model - And - View)返回阶段(如果适用)
- 处理器执行完成后,HandlerAdapter 会将生成的ModelAndView对象返回给DispatcherServlet。
视图解析(View Resolving)阶段(如果适用)
如果控制器返回的是
ModelAndView
,DispatcherServlet
会通过ViewResolver
将逻辑视图名称解析为实际的视图实现。View Resolver
会根据配置的视图解析规则(如视图文件的前缀和后缀)将视图名称转换为实际的视图对象。常见的
ViewResolver
实现有InternalResourceViewResolver
、ThymeleafViewResolver
等。
视图渲染(View Rendering)阶段(如果适用)
- 实际的视图对象会接收模型数据,并将数据渲染到视图中,生成最终的HTML(或其他格式)页面返回给客户端。
响应返回阶段
- 最后,DispatcherServlet 将渲染好的视图或者数据作为响应发送回客户端。
处理异常
如果在处理过程中发生异常,
DispatcherServlet
会查找并调用相应的异常处理器(ExceptionHandler)。异常处理器可以是全局的,也可以是局部的(定义在控制器类中)。
@ControllerAdvice 有什么作用?
@ControllerAdvice
注解通过提供全局异常处理、全局数据绑定、全局数据预处理和全局数据处理等功能,有助于减少重复代码的编写,提高代码的重用性和可维护性。
全局异常处理
集中处理应用程序中的异常,避免在每个控制器中重复编写异常处理代码。
使用@ExceptionHandler注解来捕获特定类型的异常,并返回统一的错误信息或视图。
可以处理多种不同类型的异常,并为每种异常提供不同的处理逻辑。
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
全局数据绑定
将一些通用的数据绑定到模型中,使得这些数据在每个请求中都可用。
定义一个带有@ModelAttribute注解的方法,该方法会在每个请求之前被调用,将数据添加到模型中。
这些数据对所有的@RequestMapping方法都是可见的。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
@ControllerAdvice
public class GlobalModelAttributes {
@ModelAttribute
public void addGlobalAttributes(Model model) {
model.addAttribute("appVersion", "1.0.0");
model.addAttribute("user", "John Doe");
}
}
全局数据预处理
对请求参数进行预处理,如字符串转日期、去除空格等操作。
定义一个带有@InitBinder注解的方法,该方法会在每个请求之前被调用,配置数据绑定规则。
可以注册自定义的转换器(Converter)和格式化器(Formatter),以支持特定类型的数据转换。
import org.springframework.format.datetime.DateFormatter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
@ControllerAdvice
public class GlobalDataPreprocessor {
@InitBinder
public void initBinder(WebDataBinder binder) {
// 注册日期格式化器
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
// 去除字符串中的空格
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
}
全局响应处理
对响应数据进行全局处理,如格式转换、加密解密、数据校验等操作。
定义一个带有@ResponseBody注解的方法,该方法会在响应时被调用。
可以实现自定义的消息转换器(HttpMessageConverter)或拦截器(HandlerInterceptor)。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import javax.annotation.PostConstruct;
import java.util.List;
@ControllerAdvice
public class GlobalResponseHandler {
private final RequestMappingHandlerAdapter handlerAdapter;
private final ObjectMapper objectMapper;
public GlobalResponseHandler(RequestMappingHandlerAdapter handlerAdapter, ObjectMapper objectMapper) {
this.handlerAdapter = handlerAdapter;
this.objectMapper = objectMapper;
}
@PostConstruct
public void init() {
List<HttpMessageConverter<?>> converters = handlerAdapter.getMessageConverters();
// 添加自定义的消息转换器
converters.add(new CustomHttpMessageConverter(objectMapper));
}
}
@RestController vs @Controller的区别?
@RestController
和@Controller
都是Spring MVC中用于创建Web控制器的注解,@RestController
是一个为创建RESTful控制器而设计的注解,它简化了数据响应的处理,而@Controller
提供了更广泛的灵活性,适用于多种类型的Web应用开发。但它们之间有一些关键的区别:
注解组合:
@RestController
是@Controller
和@ResponseBody
的组合注解。这意味着在@RestController
标注的类中,所有的方法都会自动地像使用@ResponseBody
注解一样,将返回值添加到响应体中。@Controller
仅表示该类是一个控制器,其方法的返回值不会自动作为响应体,而是需要通过@ResponseBody
或返回视图模板名称来指定。
返回类型:
@RestController
常用于创建RESTful Web服务,因此其方法的返回值通常是JSON、XML等数据结构,直接写入HTTP响应体。@Controller
则更灵活,可以返回视图名称、ModelAndView
对象、@ResponseBody
注解的数据,或者使用ResponseEntity
来提供更丰富的响应控制。
用途:
@RestController
适用于构建RESTful接口,强调无状态、数据驱动的Web接口。@Controller
适用于构建传统的Web应用,可能涉及到页面跳转和视图渲染。
默认的响应状态码:
@RestController
返回的数据默认响应状态码是200(OK),除非方法内部显式地设置了不同的状态码。@Controller
可以返回ModelAndView
对象,允许设置不同的视图和状态码。
异常处理:
- 在
@RestController
中,异常通常需要通过@ExceptionHandler
注解或异常处理器来处理,因为响应体中的数据不能表示异常信息。 - 在
@Controller
中,异常可以直接通过视图渲染来处理,例如重定向到错误页面。
内容协商:
@RestController
通常与@RequestMapping
、@GetMapping
、@PostMapping
等注解一起使用,支持内容协商,根据请求的Accept头来决定返回数据的格式(如JSON或XML)。@Controller
也可以支持内容协商,但更常用于渲染特定的视图。
SpringMVC 常用注解?
SpringMVC中常用注解非常多,这些注解极大地简化了Web应用程序的开发和配置。以下是一些主要的常用注解:
控制器相关注解
- @Controller:标识一个类为SpringMVC的控制器。被@Controller标识的类中的方法处理HTTP请求,并返回响应结果。返回值通常是视图名称或ModelAndView对象,用于决定要渲染的视图页面。
- @RestController:是@Controller和@ResponseBody的组合注解,标识一个类为SpringMVC的Rest风格的控制器。@RestController注解的类中的所有方法都会以JSON或XML等形式返回响应结果,而不会去解析视图。
请求映射注解
- @RequestMapping:用于映射请求到控制器方法。它可以用于类级别和方法级别。在类级别时,@RequestMapping注解指定控制器处理的请求路径前缀;在方法级别时,它映射具体的请求路径到控制器方法。@RequestMapping还支持指定请求方法(如GET、POST等)、请求参数、请求头等。
- @GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping:这些注解是@RequestMapping的特化,分别用于映射HTTP GET、POST、PUT、DELETE、PATCH请求到控制器方法。
请求参数处理注解
- @RequestParam:将请求参数绑定到控制器方法的参数上。它可以指定请求参数的名称和是否必须。
- @PathVariable:将URL中的一部分作为控制器方法的参数。通常用于RESTful风格的URL映射。
- @RequestBody:接收JSON或XML等类型的请求体数据,并将其反序列化为Java对象。
- @ResponseBody:表示该方法的返回结果直接写入HTTP response body中。通常用于异步获取数据时使用,如AJAX请求。
其他注解
@RequestHeader:获取请求头中的值,并将其绑定到控制器方法的参数上。
@CookieValue:从Cookie中获取值,并将其绑定到控制器方法的参数上。
@SessionAttribute:获取Session中的值,并将其绑定到控制器方法的参数上。同时,也可以使用HttpSession对象来获取。
@ModelAttribute:在方法级别上,@ModelAttribute可以绑定请求参数到Model对象;在类级别上,@ModelAttribute则表明该类中的某个方法会用于预处理请求参数,并将结果放入Model中。
@ExceptionHandler:注解在方法上,表示该方法用于处理特定的异常,处理范围是当前类。
**@ControllerAdvice:**定义全局异常处理器或全局数据绑定器。
@InitBinder:自定义数据绑定和类型转换。
@Valid, @Validated:用于验证请求参数或模型对象。
@Value:注入配置文件中的属性值。
SpringMVC 拦截器配置步骤?
通过实现 HandlerInterceptor
接口并注册到 SpringMVC 配置中来配置拦截器。
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Pre Handle method is called");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Post Handle method is called");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("After Completion method is called");
}
}
然后在 Spring MVC 配置类中注册拦截器:
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("/**");
}
}
SpringMVC 拦截器调用时机是怎样的?
Spring MVC拦截器(Interceptor)是在请求处理过程中介入的组件,它的调用时机介于前端控制器(DispatcherServlet)接收请求之后和处理器(Handler,通常是Controller)处理请求之前,以及处理器处理完请求之后和视图渲染(View Rendering)之前。Spring MVC中的拦截器(Interceptor)用于在请求的多个特定点插入横切逻辑,比如身份验证、日志记录、请求预处理等。拦截器的调用时机遵循以下顺序:
preHandle方法调用前:
在Controller方法调用之前被调用。
所有拦截器的
preHandle
方法按顺序执行,如果其中一个返回false
,则后续拦截器的preHandle
方法和当前请求的Controller方法都不会被执行。请求处理流程被中断,并且可以直接在拦截器中通过response
返回一个响应给客户端。如果所有
preHandle
方法都返回true
,请求会传递到Controller方法。这个阶段拦截器可以对请求进行检查、修改请求参数、验证用户权限等操作。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在这里进行预处理
return true; // 返回 true 继续执行,返回 false 中断执行
}
Controller方法调用:
- 如果所有
preHandle
方法都成功执行(即都返回true
),那么将执行请求映射到的Controller中的方法。
- 如果所有
postHandle方法调用:
在Controller方法执行之后、视图渲染之前被调用。
所有拦截器的
postHandle
方法按逆序执行,即后添加的拦截器先执行。入参除了
HttpServletRequest
和HttpServletResponse
,还有一个ModelAndView
参数,允许对模型数据和视图进行修改。这个阶段拦截器可以对处理器返回的模型(Model)和视图(View)进行修改。例如,在一个记录操作日志的拦截器中,
postHandle
方法可以获取处理器返回的模型数据,记录其中一些关键信息(如修改的数据内容)到日志中,用于审计目的。
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在这里进行后处理
}
视图渲染:
- 如果Controller方法返回的是视图名称,那么Spring MVC将使用相应的ViewResolver解析器来解析视图并进行渲染。
afterCompletion方法调用:
在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。
除了
HttpServletRequest
和HttpServletResponse
,还有Object handler
和Exception ex
参数,其中ex
是处理过程中可能抛出的异常,如果没有异常则为null
。所有拦截器的
afterCompletion
方法按逆序执行。个阶段拦截器主要用于进行一些资源清理工作,如关闭数据库连接、释放文件句柄等操作,无论请求处理过程是否正常完成或者出现异常都会执行。
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在这里进行最终处理
}
SpringMVC 多个拦截器时它们的执行顺序?
在 Spring 框架中,假设有以下两个拦截器:
FirstInterceptor,SecondInterceptor
在配置类中注册这两个拦截器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FirstInterceptor())
.addPathPatterns("/**");
registry.addInterceptor(new SecondInterceptor())
.addPathPatterns("/**");
}
}
当请求到达时,输出顺序将是:
SecondInterceptor - preHandle
FirstInterceptor - preHandle
// 处理器执行
FirstInterceptor - postHandle
SecondInterceptor - postHandle
FirstInterceptor - afterCompletion
SecondInterceptor - afterCompletion
注意事项
preHandle
方法按配置顺序依次执行,当方法返回false
会中断请求处理流程,不再继续调用后续的拦截器和处理器。postHandle
和afterCompletion
方法在请求处理流程中按逆序执行,即先注册的拦截器后执行,后注册的拦截器先执行。
通过这种方式,Spring 允许开发者灵活地控制请求的处理流程,并在不同的阶段插入自定义逻辑。
SpringMVC 过滤器配置步骤?
在 Spring MVC 中配置过滤器(Filter)是增强 Web 应用程序功能的一种常见方式。过滤器可以在请求到达控制器之前或响应返回客户端之前对 HTTP 请求和响应进行预处理或后处理。以下是详细的配置步骤,涵盖了基于 XML 的配置、基于 Java 的配置以及使用注解的方式。
第一步:创建自定义过滤器
首先,创建一个实现了 javax.servlet.Filter
接口的类,并重写其方法以定义过滤逻辑:
public class MyCustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化时执行的代码,例如读取初始化参数
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
// 在请求到达目标资源之前执行的代码
System.out.println("Before request processing");
// 继续调用下一个过滤器或目标资源
chain.doFilter(request, response);
// 在响应返回客户端之前执行的代码
System.out.println("After request processing");
} catch (Exception e) {
// 异常处理
throw new ServletException("MyCustomFilter failed", e);
}
}
@Override
public void destroy() {
// 销毁时执行的清理代码
}
}
第二步:注册过滤器
基于 XML 的配置,如果你的应用程序仍然使用 XML 配置,可以在
web.xml
文件中注册过滤器:xml<filter> <filter-name>myCustomFilter</filter-name> <filter-class>com.example.MyCustomFilter</filter-class> <!-- 可选:初始化参数 --> <init-param> <param-name>paramName</param-name> <param-value>paramValue</param-value> </init-param> </filter> <filter-mapping> <filter-name>myCustomFilter</filter-name> <url-pattern>/*</url-pattern> <!-- 指定拦截的 URL 模式 --> </filter-mapping>
基于 Java 的配置
对于现代的 Spring Boot 或 Spring 应用程序,推荐使用 Java 配置来注册过滤器。这里有几种不同的方法:
使用
FilterRegistrationBean
java@Configuration public class FilterConfig { // 使用 FilterRegistrationBean @Bean public FilterRegistrationBean<MyCustomFilter> loggingFilter() { FilterRegistrationBean<MyCustomFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new MyCustomFilter()); registration.addUrlPatterns("/api/*"); // 指定拦截的 URL 模式 registration.setName("myCustomFilter"); registration.setOrder(1); // 设置过滤器的顺序 return registration; } }
实现
WebApplicationInitializer
java//如果你想更细粒度地控制 Servlet 容器的初始化过程,可以实现 WebApplicationInitializer 接口并在其中注册过滤器: public class AppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) throws ServletException { // 创建并刷新 Spring 上下文 AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(AppConfig.class); ctx.setServletContext(container); // 注册 DispatcherServlet ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx)); servlet.setLoadOnStartup(1); servlet.addMapping("/"); // 注册过滤器 FilterRegistration.Dynamic filter = container.addFilter("myCustomFilter", MyCustomFilter.class); filter.addMappingForUrlPatterns(null, false, "/*"); } }
使用注解驱动的方式
java// 如果应用程序运行在一个支持 Java EE 的环境中,可以直接在过滤器类上使用 @WebFilter 注解来声明过滤器: @WebFilter(urlPatterns = "/*") public class MyCustomFilter implements Filter { // 实现过滤逻辑 }
第三步:设置过滤器顺序
- 当有多个过滤器时,它们会按照一定的顺序执行。
- 可以通过设置
order
属性来控制这个顺序,数值越小优先级越高。 - 例如,在使用
FilterRegistrationBean
时,你可以通过setOrder()
方法指定顺序:registration.setOrder(1);
注意事项
- 性能影响:虽然过滤器提供了强大的功能,但它们也会增加每次请求的处理开销。因此,在设计时应评估是否真的需要每个过滤器,并尽量优化其内部逻辑以减少性能影响。
- 安全性:确保过滤器的安全性,特别是涉及到敏感数据处理的过滤器,如身份验证和授权逻辑,防止潜在的安全漏洞。
- 短路机制:如果某个过滤器决定不继续传递请求到下一个过滤器或目标资源,它可以不调用
chain.doFilter()
方法,从而实现短路效果。
SpringMVC 中过滤器(Filter)的调用时机?
在 Spring MVC 中,过滤器(Filter)的调用时机是在请求到达控制器之前和响应返回客户端之前。它们是 Servlet 规范的一部分,因此适用于所有基于 Servlet 的 Web 应用程序。理解过滤器的调用时机对于正确实现和调试应用程序至关重要。
过滤器调用流程
请求进入阶段
前置处理:当一个 HTTP 请求到达服务器时,它首先会通过一系列的过滤器链(Filter Chain)。每个过滤器都有机会在请求被传递给下一个过滤器或目标资源(如 Spring MVC 控制器)之前执行一些逻辑。
典型操作:
认证与授权:检查用户是否已登录、是否有权限访问特定资源。
日志记录:记录请求的详细信息,例如 URL、参数、时间戳等。
编码设置:确保字符编码的一致性,避免乱码问题。
性能监控:开始计时,以便后续统计请求处理耗时。
跨域资源共享(CORS):处理预检请求,添加必要的响应头。
请求处理阶段
- 传递给下一个过滤器或目标资源:在完成所有必要的前置处理后,过滤器必须调用
filterChain.doFilter(request, response)
方法将请求传递给下一个过滤器或最终的目标资源(如 Spring MVC 的 DispatcherServlet 和相应的控制器)。
- 传递给下一个过滤器或目标资源:在完成所有必要的前置处理后,过滤器必须调用
响应返回阶段
后置处理:一旦目标资源完成了请求的处理,并生成了响应,控制权会再次回到过滤器链。此时,过滤器可以执行一些后置处理逻辑,比如修改响应内容、关闭资源、结束性能监控等。
典型操作:
响应修饰:修改响应内容或添加额外的响应头。
清理工作:释放不再需要的资源,如数据库连接、文件句柄等。
- 日志记录:记录响应的状态码、处理时间和任何异常信息。
- 性能监控:停止计时并记录请求处理的时间。
- 响应发送给客户端
- 完成:最后,经过所有过滤器的后置处理后,响应被发送回客户端。
过滤器链中的顺序
- 顺序问题:多个过滤器之间存在执行顺序,可以通过配置中的
order
属性来指定。数值越小,优先级越高,意味着该过滤器会更早地被调用。 - 短路机制:如果某个过滤器决定不继续传递请求到下一个过滤器或目标资源,它可以不调用
chain.doFilter()
方法,从而实现短路效果。
注意事项
- 性能影响:虽然过滤器提供了强大的功能,但它们也会增加每次请求的处理开销。因此,在设计时应评估是否真的需要每个过滤器,并尽量优化其内部逻辑以减少性能影响。
- 安全性:确保过滤器的安全性,特别是涉及到敏感数据处理的过滤器,如身份验证和授权逻辑,防止潜在的安全漏洞。
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class MyCustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化时执行的代码,例如读取初始化参数
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
// 在请求到达目标资源之前执行的代码
System.out.println("Before request processing");
// 继续调用下一个过滤器或目标资源
chain.doFilter(request, response);
// 在响应返回客户端之前执行的代码
System.out.println("After request processing");
} catch (Exception e) {
// 异常处理
throw new ServletException("MyCustomFilter failed", e);
}
}
@Override
public void destroy() {
// 销毁时执行的清理代码
}
}
SpringMVC 多个过滤器时它们的执行顺序?
在 Spring MVC 中,当配置了多个过滤器(Filter)时,它们的执行顺序是至关重要的,因为这将影响到请求和响应的处理逻辑。默认情况下,过滤器按照它们被注册的顺序执行,并且分为两个阶段:前置处理 和 后置处理。理解这一点对于确保应用程序的行为符合预期非常重要。
过滤器的执行顺序
前置处理顺序
当一个 HTTP 请求到达服务器时,它会依次通过所有已注册的过滤器,从第一个到最后一个。
每个过滤器都有机会在请求传递给下一个过滤器或目标资源之前执行一些逻辑。
为了继续处理请求,过滤器必须调用
filterChain.doFilter(request, response)
方法。如果不调用此方法,则会中断过滤器链,阻止请求传递给后续的过滤器或目标资源。
后置处理顺序
在请求被目标资源(如控制器)处理完毕并生成响应之后,控制权会按相反的顺序返回给过滤器链。
即最先执行的过滤器会在最后执行其后置处理逻辑,而最后执行前置处理的过滤器则最先执行其后置处理逻辑。
后置处理通常用于修改响应、释放资源或进行日志记录等操作。
控制过滤器顺序的方法
使用
FilterRegistrationBean
设置顺序如果你使用的是基于 Java 的配置,可以通过
FilterRegistrationBean
来设置过滤器的顺序。setOrder()
方法允许你指定一个整数值来定义过滤器的优先级,数值越小,优先级越高。javaimport org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<FirstFilter> firstFilter() { FilterRegistrationBean<FirstFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new FirstFilter()); registration.addUrlPatterns("/api/*"); registration.setName("firstFilter"); registration.setOrder(1); // 数值越小,优先级越高 return registration; } @Bean public FilterRegistrationBean<SecondFilter> secondFilter() { FilterRegistrationBean<SecondFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new SecondFilter()); registration.addUrlPatterns("/api/*"); registration.setName("secondFilter"); registration.setOrder(2); return registration; } }
使用
@Order
注解另一种方式是在实现
javax.servlet.Filter
接口的类上使用@Order
注解来指定过滤器的顺序:javaimport javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component @Order(1) // 数值越小,优先级越高 public class FirstFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { System.out.println("FirstFilter pre-handle"); chain.doFilter(request, response); System.out.println("FirstFilter post-handle"); } catch (Exception e) { throw new ServletException("FirstFilter failed", e); } } @Override public void destroy() {} } @Component @Order(2) public class SecondFilter implements Filter { // 类似于 FirstFilter 的实现 }
基于 XML 的配置
如果使用 XML 配置文件,你可以通过
<filter-mapping>
元素的顺序来隐式地定义过滤器的执行顺序。但是,推荐使用上述基于 Java 的配置方式,因为它们提供了更明确的控制和灵活性。xml<filter> <filter-name>firstFilter</filter-name> <filter-class>com.example.FirstFilter</filter-class> </filter> <filter-mapping> <filter-name>firstFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>secondFilter</filter-name> <filter-class>com.example.SecondFilter</filter-class> </filter> <filter-mapping> <filter-name>secondFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
在 Spring MVC 中配置多个过滤器时,了解它们的执行顺序是非常重要的。通过合理的配置,可以确保各个过滤器按照预期的顺序执行前置和后置处理逻辑,从而增强应用程序的功能性和安全性。无论是使用 FilterRegistrationBean
设置顺序,还是直接在过滤器类上使用 @Order
注解,都可以有效地管理过滤器的执行顺序。
SpringMVC 中过滤器典型应用场景?
在Spring MVC中,过滤器(Filter)通常用于处理请求和响应的预处理和后处理任务。以下是一些过滤器的典型应用场景:
- 字符编码过滤器:设置请求和响应的字符编码,通常设置为UTF-8,以确保正确处理国际化字符。
- 安全过滤器:实现用户认证和授权,如Spring Security提供的过滤器,用于保护URL和验证用户身份。
- 资源访问权限控制:除了登录权限验证外,过滤器还可以用于控制对特定资源的访问权限。例如,可以根据用户的角色或权限级别来限制对某些页面的访问。
- 敏感词过滤:通过过滤器,可以对请求中的敏感词进行过滤,防止用户输入非法或敏感信息。这在维护网络安全和防止恶意攻击方面非常重要。
- CORS过滤器:处理跨域请求,允许或限制来自不同源的HTTP请求。
- CSRF保护过滤器:防止跨站请求伪造攻击,通过验证请求中的CSRF令牌。
- 压缩过滤器:对响应内容进行压缩,减少传输数据量,提高响应速度。
- 日志记录过滤器:记录请求和响应的详细信息,用于调试和监控。
- 性能监视:通过过滤器,可以对请求的处理时间进行监视和统计,从而评估系统的性能。这有助于发现性能瓶颈并进行优化。
- 请求限制过滤器:限制请求的频率或数量,防止服务过载。
- 静态资源过滤器:用于处理对静态资源(如CSS、JavaScript文件和图片)的请求,可能涉及资源缓存控制。
- 缓存控制:过滤器可以用于控制响应的缓存策略,例如设置缓存时间、缓存头部信息等。这有助于优化系统性能并减少服务器负载。
- Session管理过滤器:管理HTTP会话,如创建、验证和销毁会话。
- Header装饰过滤器:添加、修改或删除HTTP请求和响应的头信息。
- 重定向过滤器:根据需要将请求重定向到不同的URL。
- 请求内容修改过滤器:修改或预处理请求内容,例如解析特定的请求参数格式。
- 响应内容修改过滤器:修改或后处理响应内容,例如添加额外的响应头或修改响应体。
- API版本控制过滤器:根据请求路径或头信息来选择不同的API版本。
- 异常处理过滤器:捕获和处理请求处理过程中的异常,返回统一的错误响应。
SpringMVC 拦截器和过滤器有什么区别?
Spring MVC 中的拦截器(Interceptor)和过滤器(Filter)都是用于在请求处理过程中执行一些预处理或后处理操作的组件,但它们在实现方式、功能范围以及执行顺序上有一些重要的区别。
所属框架与规范:
- 过滤器(Filter):过滤器是Servlet规范的一部分,因此它适用于所有基于Servlet的容器,包括Spring MVC。它可以在请求到达Servlet之前或响应发送给客户端之后执行,用于执行一些公共的操作,比如日志记录、请求参数的预处理、安全检查等。
- 拦截器(Interceptor):拦截器是Spring MVC特有的概念,它只能在Spring MVC上下文中使用。拦截器主要用于处理HTTP请求和响应的生命周期,但仅限于那些通过Spring MVC DispatcherServlet处理的请求。
作用范围:
- 过滤器:可以应用于整个Web应用程序,也可以针对特定URL模式。它可以拦截请求,也可以修改响应。它主要在Servlet容器级别生效,通常在Web应用的早期阶段被调用。
- 拦截器:只能拦截Spring MVC的请求,它工作在DispatcherServlet的生命周期中,可以在控制器方法执行之前和之后进行操作。
配置方式:
- 过滤器:可以通过web.xml或Java配置类来配置。
- 拦截器:通过实现HandlerInterceptor接口来实现,并在Spring的配置中注册。
执行机制:
- 过滤器:基于函数回调方式实现,通常按照定义的顺序链式执行。
- 拦截器:基于Java反射机制实现,通常按配置的顺序执行(PreHandle -> PostHandle -> AfterCompletion)。
操作对象:
- 过滤器:操作Request、Response对象。
- 拦截器:操作Request、Response、handler、modelAndView、exception对象。
SpringMVC 过滤器和拦截器执行顺序?
在 Spring MVC 应用程序中,过滤器(Filter)和拦截器(Interceptor)的执行顺序是明确且有规律的。理解它们的执行顺序对于构建高效、安全的应用程序至关重要。
过滤器(Filter)执行顺序
- 前置处理:当一个 HTTP 请求到达时,过滤器在请求到达
DispatcherServlet
之前首先会经过一系列的过滤器。这些过滤器按照它们被注册的顺序依次调用doFilter
方法。 - 后置处理:一旦所有过滤器都完成了前置处理,并且请求被传递给下一个组件(如拦截器或控制器),则开始后置处理。后置处理也是按照过滤器的逆序进行的,即最先执行的过滤器最后完成其后置处理。
拦截器(Interceptor)执行顺序
拦截器在请求被 DispatcherServlet
分发给控制器之前执行。拦截器可以在三个不同的阶段工作:preHandle
、postHandle
和 afterCompletion
。
preHandle
:在进入控制器之前调用,按照它们被注册的顺序依次执行。postHandle
:在控制器方法执行之后但在视图渲染之前调用,仍然按照它们被注册的顺序依次执行。afterCompletion
:在视图渲染完成后调用,按注册顺序的逆序执行。
过滤器与拦截器之间的顺序
总体来说,执行顺序如下:
过滤器链的前置处理:
- 按照注册顺序,依次调用每个过滤器的
doFilter
方法。
- 按照注册顺序,依次调用每个过滤器的
DispatcherServlet:
DispatcherServlet
接收到请求并开始处理。
拦截器链的前置处理:
- 按照注册顺序,依次调用每个拦截器的
preHandle
方法。
- 按照注册顺序,依次调用每个拦截器的
控制器方法执行:
- 如果所有
preHandle
方法返回true
,则继续执行目标控制器的方法。
- 如果所有
拦截器链的后置处理:
- 控制器方法执行完毕后,按注册顺序依次调用每个拦截器的
postHandle
方法(如果有)。
- 控制器方法执行完毕后,按注册顺序依次调用每个拦截器的
视图渲染:
- 视图解析并渲染响应内容。
拦截器链的完成处理:
- 视图渲染完成后,按注册顺序的逆序依次调用每个拦截器的
afterCompletion
方法。
- 视图渲染完成后,按注册顺序的逆序依次调用每个拦截器的
过滤器链的后置处理:
- 最后,按照过滤器注册顺序的逆序,依次调用每个过滤器的
doFilter
方法中的后置逻辑部分。
- 最后,按照过滤器注册顺序的逆序,依次调用每个过滤器的
示例
假设你有一个应用程序配置了两个过滤器(FilterA
和 FilterB
)以及两个拦截器(InterceptorA
和 InterceptorB
)。它们的执行顺序将是这样的:
FilterA -> FilterB -> InterceptorA.preHandle() -> InterceptorB.preHandle() -> Controller
-> InterceptorB.postHandle() -> InterceptorA.postHandle() -> View Render
-> InterceptorB.afterCompletion() -> InterceptorA.afterCompletion()
-> FilterB (后置处理) -> FilterA (后置处理)
Spring MVC如何设置请求转发和重定向?
在Spring MVC中,请求转发和重定向是两种常见的处理请求的方式。它们分别用于不同的场景:
- 请求转发(Forward):将请求转发到另一个资源(如另一个控制器方法或前端页面),客户端的浏览器URL不会改变。
- 重定向(Redirect):将客户端重定向到一个新的URL,客户端的浏览器URL会改变。
请求转发
使用RequestDispatcher
进行转发:
@RequestMapping("/forwardExample")
public String forwardExample(HttpServletRequest request, HttpServletResponse response) {
RequestDispatcher dispatcher = request.getRequestDispatcher("/targetView");
dispatcher.forward(request, response);
return null; // 返回null表示不进行视图解析
}
返回视图名称进行转发
在Controller中返回具体的视图名称,SpringMVC 会自动处理转发。
@RequestMapping("/forwardExample")
public String forwardExample() {
return "targetView"; // 返回视图名称,Spring MVC将请求转发到对应的视图
}
使用 forward:
前缀或 ModelAndView
对象。
@Controller
public class MyController {
@RequestMapping("/forwardExample")
public ModelAndView handleRequest() {
// 创建一个 ModelAndView 对象
ModelAndView modelAndView = new ModelAndView();
// 设置要转发的视图名称
modelAndView.setViewName("forward:/anotherControllerMethod");
// 添加模型数据(可选)
modelAndView.addObject("message", "This is a forwarded request");
return modelAndView;
}
@RequestMapping("/forwardExample")
public String handleRequest() {
// 直接返回带有 forward: 前缀的视图名称
return "forward:/anotherControllerMethod";
}
}
重定向
重定向通常用于将客户端重定向到一个新的URL,这可以是同一个应用中的另一个控制器方法,也可以是外部URL。可以通过以下几种方式实现重定向:
使用redirect:
前缀进行重定向:在Controller的方法返回值前加上redirect:
前缀,Spring MVC将执行重定向。
@Controller
public class MyController {
@RequestMapping("/redirectExample")
public String handleRequest() {
// 返回带有 redirect: 前缀的 URL
return "redirect:/anotherControllerMethod";
}
}
使用 RedirectView
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
@Controller
public class MyController {
@RequestMapping("/redirectExample")
public ModelAndView handleRequest() {
// 创建一个 RedirectView 对象
RedirectView redirectView = new RedirectView();
// 设置重定向的目标 URL
redirectView.setUrl("/anotherControllerMethod");
// 创建一个 ModelAndView 对象并设置视图为 RedirectView
ModelAndView modelAndView = new ModelAndView(redirectView);
// 添加模型数据(可选)
modelAndView.addObject("message", "This is a redirected request");
return modelAndView;
}
}
使用RedirectAttributes
进行重定向:当需要在重定向过程中传递参数时,可以使用RedirectAttributes
。
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
public class MyController {
@RequestMapping(value = "/redirectWithFlash", method = RequestMethod.GET)
public String handleRequest(RedirectAttributes redirectAttributes) {
// 添加闪存属性
redirectAttributes.addFlashAttribute("message", "This is a redirected request with flash attribute");
// 重定向到另一个控制器方法
return "redirect:/anotherControllerMethod";
}
@RequestMapping(value = "/anotherControllerMethod", method = RequestMethod.GET)
public String anotherControllerMethod(ModelMap model, WebRequest request) {
// 获取闪存属性
String message = (String) request.getAttribute("message", WebRequest.SCOPE_FLASH);
model.addAttribute("message", message);
return "resultPage"; // 返回视图名称
}
}
使用HttpServletResponse
进行重定向:直接使用HttpServletResponse
对象的sendRedirect
方法进行重定向。
@RequestMapping("/redirectExample")
public void redirectExample(HttpServletResponse response) throws IOException {
response.sendRedirect("/targetUrl");
}
注意事项
- 请求转发通常用于服务器内部资源的访问,而重定向则用于客户端访问不同的资源或URL。
- 使用
redirect:
前缀的方式更为简洁,并且Spring MVC会自动处理URL的解析。 - 在使用
RedirectAttributes
时,传递的参数将在重定向后的请求中可用,这在表单提交后重定向以防止重复提交时非常有用。
Spring中的事务是如何实现的?
Spring中的事务实现主要依赖于Spring的事务抽象和事务管理器,以及底层的事务API(如JTA、JDBC、JPA、Hibernate等)。以下是Spring实现事务的几个关键步骤和组件:
事务抽象
- Spring提供了一个高层次的事务抽象,允许开发者以统一的方式处理事务,而不需要关心底层的事务管理机制。这是通过
PlatformTransactionManager
接口实现的,它是Spring事务管理的核心。
平台事务管理器(PlatformTransactionManager)
- 这个接口定义了一组标准方法来控制事务的生命周期,包括开启事务、提交事务、回滚事务等。
- Spring提供了多种
PlatformTransactionManager
的实现,对应不同的持久层框架,如DataSourceTransactionManager
(用于JDBC)、HibernateTransactionManager
(用于Hibernate)、JpaTransactionManager
(用于JPA)等。
事务定义(TransactionDefinition)
TransactionDefinition
接口定义了事务的属性,包括传播行为(propagation behavior)、隔离级别(isolation level)、超时时间(timeout)等。- 这些属性可以通过编程方式或声明式事务管理来配置。
事务状态(TransactionStatus)
TransactionStatus
对象表示当前事务的状态,包括事务是否新开启、是否有保存点、是否标记为回滚等。
声明式事务管理
- 声明式事务管理是Spring推荐的方式,通过在方法上添加
@Transactional
注解来声明事务。 - Spring使用AOP(面向切面编程)技术,在方法执行前后添加事务管理逻辑。
TransactionInterceptor
是实现这一功能的拦截器。
编程式事务管理
- 编程式事务管理允许开发者通过编程方式显式控制事务的生命周期。
- 可以通过
TransactionTemplate
或直接使用PlatformTransactionManager
来编程式地管理事务。
事务的传播行为
- 事务的传播行为定义了当一个事务方法被另一个事务方法调用时,事务如何被传播。
- Spring定义了多种传播行为,如REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER和NESTED。
事务的隔离级别
- 隔离级别定义了事务之间的隔离程度,防止脏读、不可重复读和幻读。
- Spring允许配置事务的隔离级别,以确保事务的正确性和数据的一致性。
事务的回滚
- Spring支持基于异常的回滚策略,可以指定哪些异常会导致事务回滚。
- 可以通过
@Transactional
注解的rollbackFor
和noRollbackFor
属性来配置。
Spring的事务实现依赖于PlatformTransactionManager
接口和声明式事务管理,通过AOP技术在方法执行前后添加事务管理逻辑。这种方式简化了事务管理,使得开发者可以专注于业务逻辑,而不需要关心底层的事务实现细节。
Spring 事务开启方式有哪些?
在 Spring 框架中,事务管理是一个非常重要的功能,它确保了数据的一致性和完整性。Spring 提供了多种方式来开启和管理事务,以下是几种常见的事务开启方式:
声明式事务(Declarative Transaction Management)
声明式事务是通过配置文件或注解来管理事务的,这种方式使得事务管理代码与业务逻辑分离,更加清晰和易于维护。
- 使用
@Transactional
注解,@Transactional
注解是最常用的声明式事务管理方式。你可以将这个注解应用在类级别或方法级别。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
//类级别
@Service
@Transactional
public class UserService {
public void addUser(User user) {
// 业务逻辑
}
public void updateUser(User user) {
// 业务逻辑
}
}
// 方法级别:
@Service
public class UserService {
@Transactional
public void addUser(User user) {
// 业务逻辑
}
@Transactional
public void updateUser(User user) {
// 业务逻辑
}
}
- 配置文件方式,可以通过 XML 配置文件来定义事务管理器和事务属性。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="userService" class="com.example.service.UserService">
<tx:attributes>
<tx:method name="addUser" propagation="REQUIRED" isolation="DEFAULT" timeout="-1" read-only="false"/>
<tx:method name="updateUser" propagation="REQUIRED" isolation="DEFAULT" timeout="-1" read-only="false"/>
</tx:attributes>
</bean>
编程式事务(Programmatic Transaction Management)
编程式事务是通过编程的方式来管理事务,这种方式提供了更细粒度的控制,但代码会更加复杂。
- 使用
TransactionTemplate
,TransactionTemplate
是一个模板类,它简化了编程式事务管理的过程。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void addUser(User user) {
transactionTemplate.execute(status -> {
// 业务逻辑
return null;
});
}
public void updateUser(User user) {
transactionTemplate.execute(status -> {
// 业务逻辑
return null;
});
}
}
- 使用
PlatformTransactionManager
它 Spring 事务管理的核心接口,你可以手动创建和提交事务。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
public void addUser(User user) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务逻辑
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
public void updateUser(User user) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务逻辑
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
Spring 事务的隔离级别有哪些?
Spring事务的隔离级别主要有以下几种:
- DEFAULT(默认): 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
- READ_UNCOMMITTED(读未提交): 最低的隔离级别,允许一个事务读取另一个尚未提交的事务的数据。 可能会导致脏读、不可重复读和幻读问题。
- READ_COMMITTED(读已提交): 保证一个事务只能读取到已提交的数据,避免脏读问题。 但是在并发情况下,可能会导致不可重复读和幻读问题。
- REPEATABLE_READ(可重复读): 保证一个事务在多次读取同一数据时,能够得到一致的结果。 避免了脏读和不可重复读问题,但仍可能出现幻读问题。
- SERIALIZABLE(串行化): 最高的隔离级别,事务串行执行,避免脏读、不可重复读和幻读问题。 性能较低,一般情况下不建议使用。
这些隔离级别可以帮助你在不同的并发访问场景下,选择合适的事务隔离级别,以确保数据的一致性和并发操作的正确性。
Spring 事务中哪几种事务传播行为?
在 Spring 框架中,事务传播行为定义了当一个事务方法被另一个事务方法调用时,事务应该如何传播。Spring 提供了多种事务传播行为选项,这些选项通过 Propagation 枚举来指定。
REQUIRED (默认)
- 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- 这是默认的传播行为。
SUPPORTS
- 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式继续运行。
- 适用于那些不需要事务支持的操作。
MANDATORY
- 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常(IllegalTransactionStateException)。
- 适用于那些必须在事务上下文中执行的方法。
REQUIRES_NEW
- 创建一个新的事务,如果当前存在事务,则将当前事务挂起。
- 新事务独立于当前事务,即使外部事务回滚,新事务也不会回滚。
NOT_SUPPORTED
- 以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
- 适用于那些不需要事务支持且希望脱离当前事务上下文的操作。
NEVER
- 以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException)。
- 适用于那些绝对不能在事务上下文中执行的方法。
NESTED
- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个新的事务。
- 嵌套事务可以独立于外部事务进行回滚,但外部事务的提交会同时提交所有嵌套事务。
Spring @Transaction 失效场景?
Spring 的事务底层是由 AOP 方式实现,并依靠数据库底层事务进行事务处理。所以在以下情况下会出现事务失效的情况:
非public方法上的@Transactional
注解:
@Transactional
注解仅在public方法上有效,如果在非public方法上使用,事务将不会生效。
事务传播行为(propagation)设置错误:
- 如果配置了
TransactionDefinition.PROPAGATION_SUPPORTS
、TransactionDefinition.PROPAGATION_NOT_SUPPORTED
或TransactionDefinition.PROPAGATION_NEVER
,事务可能不会回滚。
异常被catch并处理,没有重新抛出:
- 如果事务方法内部捕获了异常但没有重新抛出,事务将不会回滚。
方法内部的自调用:
- 在同一个类中,事务方法通过
this.xxx
调用自身或其他事务方法,由于不是通过代理对象调用,因为@Transactional是基于动态代理实现的,事务将失效。
目标类没有配置为Bean:
- 如果使用
@Transactional
的类没有被Spring容器管理(即没有被声明为一个Bean),事务将不会生效。
数据库不支持事务:
- 某些数据库存储引擎(如MySQL的MyISAM)不支持事务,这将导致事务失效,尽管这种情况较为少见。
注解属性rollbackFor设置错误:
- 注解默认只会在运行时异常(RuntimeException)及其子类抛出时才会触发事务回滚。如果在带有
@Transactional
注解的方法中抛出的是检查异常(Checked Exception,如IOException
、SQLException
等),事务不会自动回滚,除非在@Transactional
注解中明确配置rollbackFor
属性来指定需要回滚的异常类型。
@Transactional注解应用到了final 方法或者 class 上
- 这种情况下,事务也是会失效的。
多线程环境下操作不在同一个Session 中
@Transactional
注解是基于线程本地变量(Thread - Local)来管理事务的。如果在一个线程中开启了事务,在另一个线程中调用了带有@Transactional
注解的方法,事务不会共享,新的线程不会在原有的事务环境下运行。每个线程都有自己独立的事务上下文。
了解这些失效场景和原因,可以帮助我们更好地使用Spring事务管理,确保事务的正确性和数据的一致性。
Spring JDBC API 中一些常用的类和接口?
Spring 的 JDBC API 提供了一套简化数据库访问的工具,它在标准 JDBC API 的基础上进行了封装,减少了样板代码,并提供了更好的异常处理机制。以下是 Spring JDBC API 中一些常用的类和接口:
JdbcTemplate
Spring JDBC 模块的核心类之一,它简化了与数据库的交互过程。通过 JdbcTemplate
,开发者可以执行查询、更新、批量更新等操作,并且不需要手动管理资源(如连接、语句和结果集)。
常用方法
query
:用于执行查询操作,并返回一个列表或单个对象。update
:用于执行插入、更新或删除操作。batchUpdate
:用于执行批量更新操作。execute
:用于执行任意 SQL 语句(包括 DDL 语句)。
@Autowired
private JdbcTemplate jdbcTemplate;
public List<User> findAllUsers() {
return jdbcTemplate.query("SELECT * FROM users", (rs, rowNum) ->
new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"))
);
}
NamedParameterJdbcTemplate
JdbcTemplate 的扩展,它允许使用命名参数而不是问号占位符 (?
) 来设置 SQL 查询中的参数。这使得 SQL 语句更易读,并且更容易维护。
常用方法
query
:支持命名参数的查询操作。update
:支持命名参数的更新操作。
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public User findUserById(Long id) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("id", id);
return namedParameterJdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = :id",
parameters,
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"))
);
}
SimpleJdbcInsert 和 SimpleJdbcCall
这些类提供了一种更加简洁的方式来执行插入和存储过程调用操作。
SimpleJdbcInsert
:简化了插入操作,特别是对于自动生成主键的情况。SimpleJdbcCall
:简化了存储过程和函数的调用。
@Autowired
private JdbcTemplate jdbcTemplate;
public Long insertUser(User user) {
SimpleJdbcInsert insertActor = new SimpleJdbcInsert(jdbcTemplate)
.withTableName("users")
.usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", user.getName());
parameters.put("email", user.getEmail());
Number key = insertActor.executeAndReturnKey(parameters);
return key.longValue();
}
RowMapper
RowMapper
接口用于将 ResultSet
中的一行数据映射到一个 Java 对象中。JdbcTemplate
和 NamedParameterJdbcTemplate
都使用 RowMapper
来处理查询结果。
实现方式
- 可以实现
RowMapper<T>
接口来定义如何将每一行数据转换为指定类型的对象。 - 也可以使用 lambda 表达式来简化映射逻辑。
public class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"));
}
}
或者使用 Lambda 表达式:
return jdbcTemplate.query("SELECT * FROM users", (rs, rowNum) ->
new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"))
);
SQLExceptionTranslator
SQLExceptionTranslator
接口用于将底层 JDBC 异常转换为 Spring 的 DataAccessException
。JdbcTemplate
默认会自动进行这种转换,从而提供了一个统一的异常层次结构。
常用实现
SQLStateSQLExceptionTranslator
:根据 SQL 状态码翻译异常。SQLErrorCodeSQLExceptionTranslator
:根据数据库错误代码翻译异常。
DataSource
DataSource
是 JDBC 的一部分,但在 Spring 中经常被用来配置数据库连接池。它是 JdbcTemplate
和其他相关类所需的数据源。
常见实现
DriverManagerDataSource
:简单的数据源实现,适合开发环境或测试。BasicDataSource
(Apache Commons DBCP):一个功能齐全的数据源实现。HikariDataSource
:高性能的数据源实现,推荐用于生产环境。
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
Spring JDBC API 通过提供一系列强大的工具类和接口,极大地简化了数据库访问层的开发工作。JdbcTemplate
和 NamedParameterJdbcTemplate
是最常用的核心类,它们不仅减少了样板代码的数量,还提供了更好的异常处理和事务管理支持。此外,RowMapper
接口使得查询结果的映射变得非常简单,而 SQLExceptionTranslator
则确保了异常处理的一致性。结合使用这些组件,你可以快速构建高效、可靠的数据库访问逻辑。
列举 Spring DAO 抛出的异常?
Spring DAO(Data Access Object)层在数据访问过程中可能会抛出多种异常,这些异常都是org.springframework.dao.DataAccessException
的子类。以下是一些常见的Spring DAO异常及其简要描述:
DataAccessException:
- 这是Spring框架中所有数据访问异常的通用父异常。它捕获了底层数据访问技术(如JDBC、Hibernate、JPA等)可能抛出的异常,并提供了一个一般性的异常类型,以便在DAO层捕获和处理这些异常。
DataIntegrityViolationException:
- 这个异常通常表示数据库约束完整性的违反,如外键约束、非空约束、唯一性约束等。当数据库操作违反了这些约束时,将抛出此异常。
UncategorizedDataAccessException:
- 这是一个通用的SQL异常,用于表示在执行SQL操作时发生的未分类的问题。通常,这种异常包含有关底层数据库错误的详细信息。
InvalidDataAccessApiUsageException:
- 这个异常通常表示在使用Spring的数据访问API时发生的非法操作。例如,使用不支持的特性或方法可能会引发此异常。
TransientDataAccessResourceException:
- 当底层数据资源(例如数据库服务器)出现临时问题时,可能会抛出此异常。通常,这是一个短暂的错误,可以尝试重新执行操作。
ObjectOptimisticLockingFailureException:
- 用于表示在乐观锁定机制下的并发冲突。当两个或多个客户端尝试同时修改相同的数据时,可能会发生此异常。
IncorrectResultSizeDataAccessException:
- 当查询的结果集大小与预期不符时,将抛出此异常。例如,期望只有一个结果但查询返回多个结果时,或者期望多个结果但查询只返回一个结果时。
EmptyResultDataAccessException:
- 当查询未返回任何结果但期望至少有一个结果时,将抛出此异常。
DeadlockLoserDataAccessException:
- 表示当前的操作因为死锁而失败。
DataAccessResourceFailureException:
- 数据访问资源彻底失败,例如不能连接数据库。
CleanupFailureDataAccessException:
- 一项操作成功地执行,但在释放数据库资源时发生异常(例如,关闭一个Connection)。
DataRetrievalFailureException:
- 某些数据不能被检测到,例如不能通过关键字找到一条记录。
InvalidDataAccessResourceUsageException:
- 错误使用数据访问资源,例如用错误的SQL语法访问关系型数据库。
IncorrectUpdateSemanticsDataAccessException:
- Update时发生某些没有预料到的情况,例如更改超过预期的记录数。
TypemismatchDataAccessException:
- Java类型和数据类型不匹配,例如试图把String类型插入到数据库的数值型字段中。