Spring框架详解:IoC容器、依赖注入和Bean生命周期
1. Spring框架概述
Spring是Java企业级应用开发的主流开源框架。它的核心目标是简化Java EE应用开发,提高开发效率和代码质量。Spring框架提供了广泛的功能,包括依赖注入、面向切面编程、事务管理等。
1.1 Spring的核心特性
-
IoC (Inversion of Control): 控制反转,将对象的创建和管理权交给Spring容器。
-
DI (Dependency Injection): 依赖注入,是IoC的一种实现方式,用于注入对象的依赖关系。
-
AOP (Aspect-Oriented Programming): 面向切面编程,用于将横切关注点与业务逻辑分离。
1.2 Spring版本
本文主要基于Spring 5.3.24版本进行讲解。Spring 5.x系列是一个重要的里程碑版本,引入了许多新特性,如响应式编程支持、函数式风格的Web框架等。
2. IoC容器
IoC (Inversion of Control) 是Spring框架的核心概念之一。它颠覆了传统的程序设计流程,将对象的创建、配置和管理交给了Spring容器。
2.1 IoC的概念
在传统的程序设计中,我们通常会在类A中主动创建类B的实例。而在IoC模式下,类A不再主动创建类B的实例,而是被动地等待IoC容器将B的实例注入到A中。这就是"控制反转"的含义 - 控制权从程序代码转移到了外部容器。
2.2 IoC容器的实现
Spring提供了两种类型的IoC容器:
-
BeanFactory: 这是最简单的容器,提供基本的DI支持。
-
ApplicationContext: 这是更高级的容器,除了BeanFactory的所有功能外,还提供了更多的企业级功能,如事件发布、国际化支持等。
在实际应用中,我们通常使用ApplicationContext,因为它提供了更丰富的功能。
ApplicationContext context = new AnnotationConfigApplicationContext("cn.serein.spring.example");
这行代码创建了一个基于注解配置的ApplicationContext,它会扫描指定包下的所有带有@Component等注解的类,并创建相应的Bean。
2.3 Bean的概念
在Spring中,由IoC容器管理的对象被称为Bean。Bean是构成应用程序主干的对象,由Spring IoC容器实例化、组装和管理。
3. 依赖注入 (DI)
依赖注入是实现IoC的一种方式,它允许Spring容器在运行时将依赖关系注入到对象中。
3.1 注解方式的依赖注入
Spring提供了多种注解来实现依赖注入:
3.1.1 @Component, @Service, @Repository, @Controller
这些注解用于将类标记为Spring管理的组件。
- @Component: 通用的组件注解
- @Service: 用于标记服务层组件
- @Repository: 用于标记数据访问层组件
- @Controller: 用于标记控制器组件
@Service
public class UserService {
// ...
}
3.1.2 @Autowired
用于自动装配bean。Spring会尝试通过类型匹配来注入依赖。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
3.1.3 @Qualifier
当有多个相同类型的bean时,可以使用@Qualifier指定要注入的具体bean。
也就是当一个接口有多个实现类时,需要使用@Qualifer注解指定接口对应的实现类。
@Service
public class UserService {
@Autowired
@Qualifier("mysqlRepository")
private UserRepository userRepository;
}
3.1.4 @Value
用于注入简单类型的值,如字符串、整数等。
@Service
public class UserService {
@Value("${app.name}")
private String appName;
}
3.2 构造器注入 vs Setter注入
Spring支持两种主要的依赖注入方式:构造器注入和Setter注入。
-
构造器注入: 通过构造函数注入依赖。
优点:可以确保依赖不为null,有利于创建不可变对象。
缺点:当依赖较多时,构造函数可能变得臃肿。@Service public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } }
-
Setter注入: 通过setter方法注入依赖。
优点:可以灵活地注入可选依赖。
缺点:无法保证依赖在使用时一定被注入。@Service public class UserService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } }
在实际应用中,构造器注入通常是首选的方式,因为它可以确保必要的依赖在对象创建时就被注入。
4. Bean的生命周期
了解Bean的生命周期对于正确使用Spring框架至关重要。Bean的生命周期可以分为以下几个阶段:
-
实例化: Spring容器创建Bean的实例。
-
属性赋值: Spring将值和引用注入到Bean的属性中。
-
初始化:
- 如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法。
- 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法。
- 如果存在任何BeanPostProcessor,Spring将调用它们的postProcessBeforeInitialization()方法。
- 如果Bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet()方法。
- 如果Bean声明了初始化方法,该方法会被调用。
- 如果存在任何BeanPostProcessor,Spring将调用它们的postProcessAfterInitialization()方法。
-
使用: Bean现在已经准备好被应用程序使用了,也就是开发者使用阶段。
-
销毁:
- 如果Bean实现了DisposableBean接口,Spring将调用它的destroy()方法。
- 如果Bean声明了自定义的销毁方法,该方法会被调用。
4.1 使用注解控制Bean的生命周期
Spring提供了@PostConstruct和@PreDestroy注解来标记初始化和销毁方法:
@Component
public class MyBean {
@PostConstruct
public void init() {
System.out.println("Bean is going through init.");
}
@PreDestroy
public void cleanup() {
System.out.println("Bean will destroy now.");
}
}
4.2 Bean的作用域
Spring支持以下几种bean的作用域:
-
singleton: 默认作用域,每个容器中只有一个bean实例。
-
prototype: 每次请求都会创建一个新的bean实例。
-
request: 每个HTTP请求都会创建一个新的bean实例。
-
session: 每个HTTP会话都会创建一个新的bean实例。
-
application: 每个ServletContext都会创建一个新的bean实例。
可以使用@Scope注解来指定bean的作用域:
@Component
@Scope("prototype")
public class PrototypeBean {
// ...
}
5. 配置Spring应用
5.1 Java配置
从Spring 3.0开始,可以使用Java类来配置Spring应用,无需XML配置文件。
@Configuration
@ComponentScan("com.example")
public class AppConfig {
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
}
在这个例子中,@Configuration注解表明这是一个配置类,@ComponentScan指定要扫描的包,@Bean注解用于声明一个bean。
5.2 使用属性文件
为了使配置更加灵活,我们可以使用属性文件来存储配置信息:
# application.properties
jdbc.url=jdbc:mysql://localhost:3306/mydb
jdbc.username=root
jdbc.password=password
然后在Java配置类中使用@PropertySource来加载属性文件:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
@Autowired
private Environment env;
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
return dataSource;
}
}
6. Spring AOP (面向切面编程)
虽然本文主要聚焦于IoC和DI,但值得一提的是Spring的另一个核心特性:AOP。AOP允许将横切关注点(如日志、事务管理)与业务逻辑分离,从而提高代码的模块化程度。
Spring AOP的基本概念包括:
- Aspect: 横切关注点的模块化,如事务管理。
- Join point: 程序执行过程中明确的点,如方法的调用。
- Advice: 在特定的join point要采取的行动。
- Pointcut: 匹配join points的谓词。
Spring支持使用注解来定义切面:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Method " + joinPoint.getSignature().getName() + " is about to be called");
}
}
这个例子定义了一个切面,它会在com.example.service包中的任何方法执行前打印日志。
总结
Spring框架通过其核心特性IoC和DI,彻底改变了Java应用的开发方式。它提供了一种松耦合、易测试的方式来构建应用程序。通过深入理解Bean的生命周期、依赖注入的方式以及Spring的配置选项,开发者可以更好地利用Spring框架来创建健壮、可维护的应用程序。
随着Spring生态系统的不断发展,如Spring Boot的引入,使得开发Spring应用变得更加简单和快速。