大家好,今天一起学习一下Spring的IOC/DI~
Spring最为人所知的核心特性之一就是IoC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)。通过这些模式,Spring有效地解决了传统面向对象编程中的紧耦合问题,促进了组件间的解耦,提高了代码的可测试性和灵活性。
IoC/DI 的基本概念
控制反转(IoC)
控制反转是一种设计原则,它描述了对象获取其依赖关系的方式发生了改变。在传统的面向对象编程中,对象通常会自己负责创建或查找其依赖的对象。而在IoC模式下,这种依赖关系被"反转"------不再由对象自身管理,而是由外部容器来负责创建和注入依赖项。这样做的好处是减少了对象之间的直接依赖,增强了系统的松耦合性。
依赖注入(DI)
依赖注入是实现IoC的一种具体方式,它允许开发者以声明式的方式指定对象间的依赖关系,而不是通过硬编码。DI可以通过构造器、Setter方法或字段来完成。这种方式不仅简化了对象的创建过程,还使得代码更容易理解和维护。
Spring IoC容器
BeanFactory vs ApplicationContext
在前面的文章中已经介绍过,Spring提供了两种主要类型的IoC容器:BeanFactory和ApplicationContext。虽然两者都可以用来管理和操作Bean,但它们之间存在一些差异:
-
BeanFactory:这是最基础的IoC容器接口,提供了标准的IoC功能。它的优点在于资源消耗较少,适合于资源受限的环境。
-
ApplicationContext:它是BeanFactory的子接口,除了继承所有功能外,还增加了对国际化(i18n)、事件传播、资源加载等功能的支持。因此,在大多数情况下,推荐使用ApplicationContext。
// 使用BeanFactory
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
MyBean myBean = (MyBean) beanFactory.getBean("myBean");// 使用ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyBean myBean = context.getBean(MyBean.class);
Bean的生命周期管理
Spring IoC容器负责管理Bean的整个生命周期,从实例化到初始化再到销毁。在这个过程中,容器可以调用特定的方法来执行自定义逻辑,比如设置属性值、调用初始化方法或销毁方法等。此外,Spring还支持感知容器的回调接口,如InitializingBean、DisposableBean等,允许Bean在特定阶段执行额外的操作。
public class MyBean implements InitializingBean, DisposableBean {
private String message;
public void setMessage(String message) {
this.message = message;
}
@Override
public void afterPropertiesSet() throws Exception {
// 初始化后执行
System.out.println("Initializing: " + message);
}
@Override
public void destroy() throws Exception {
// 销毁前执行
System.out.println("Destroying: " + message);
}
}
依赖注入的方式
构造器注入
构造器注入是最推荐的方式,因为它确保了所有必需的依赖都被正确初始化。这有助于提高不可变性和显式的依赖声明,同时也便于单元测试。
public class ServiceConsumer {
private final Service service;
// 构造器注入
public ServiceConsumer(Service service) {
this.service = service;
}
public void performAction() {
service.execute();
}
}
@Configuration
public class AppConfig {
@Bean
public Service service() {
return new Service();
}
@Bean
public ServiceConsumer serviceConsumer() {
return new ServiceConsumer(service());
}
}
Setter注入
对于可选依赖或者当存在循环依赖的情况下,Setter方法注入可能是更好的选择。它允许在对象创建之后再设置依赖项,但这可能会导致对象处于不完全初始化的状态。
public class AnotherService {
private OptionalDependency optionalDependency;
@Autowired(required = false)
public void setOptionalDependency(OptionalDependency optionalDependency) {
this.optionalDependency = optionalDependency;
}
}
字段注入
尽管Spring支持字段注入,但这种方式并不被提倡,因为它破坏了不可变性和显式的依赖声明。然而,在某些场景下,例如为了减少样板代码,字段注入也可以作为一种折衷方案。
@Component
public class FieldInjectionExample {
@Autowired
private SomeDependency dependency; // 不推荐的做法
}
基于注解的配置
随着Java语言的发展,Spring也引入了基于注解的配置方式,让我们可以用更加简洁直观的方式来定义Bean和注入依赖。
使用@Component、@Service等注解
通过@Component、@Service、@Repository、@Controller等注解,我们可以标记类为Spring管理的Bean。Spring会自动扫描这些带有注解的类,并将它们注册到容器中。
@Service
public class UserService {
public void registerUser(String username) {
// 用户注册逻辑
}
}
@Autowired自动装配
@Autowired注解用于指示Spring自动装配某个属性、构造器参数或方法参数。它可以省去显式地定义Bean之间的依赖关系,进一步简化了配置工作。
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void registerUser(String username) {
userRepository.save(new User(username));
}
}
基于XML的配置
尽管基于注解的配置已经成为主流,但在某些情况下,使用XML文件来配置Bean仍然是必要的。例如,当需要引用第三方库中的类时,或者当希望保持配置与代码分离以便于管理和部署时。
XML文件配置Bean
<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.UserRepository"/>
</beans>
外部属性文件配置
有时我们需要从外部属性文件中读取配置信息,比如数据库连接字符串或其他环境变量。Spring支持通过PropertyPlaceholderConfigurer或@Value注解来实现这一点。
<context:property-placeholder location="classpath:application.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</bean>
# application.properties
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/mydb
db.username=root
db.password=secret
简单的Web应用程序示例
创建服务层
首先,定义几个服务类来处理业务逻辑。将使用构造器注入来确保依赖关系明确且不可变。
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User registerUser(String username, String password) {
User user = new User(username, password);
return userRepository.save(user);
}
public User authenticate(String username, String password) {
User user = userRepository.findByUsername(username);
if (user != null && user.getPassword().equals(password)) {
return user;
}
throw new AuthenticationException("Invalid credentials");
}
}
创建控制器
接下来,定义RESTful API端点来暴露服务层的功能。这里将利用Spring MVC提供的强大路由映射机制。
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/register")
public ResponseEntity<User> register(@RequestBody UserDTO userDTO) {
User registeredUser = userService.registerUser(userDTO.getUsername(), userDTO.getPassword());
return ResponseEntity.status(HttpStatus.CREATED).body(registeredUser);
}
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody UserDTO userDTO) {
userService.authenticate(userDTO.getUsername(), userDTO.getPassword());
return ResponseEntity.ok("Login successful");
}
}
数据传输对象(DTO)
为了保护内部模型不受外部影响,应该使用数据传输对象(DTO)来进行请求和响应的数据交换。
public class UserDTO {
private String username;
private String password;
// Getters and setters omitted for brevity
}
Spring的IoC/DI特性极大地简化了Java企业级应用的开发过程。它不仅帮助我们构建松耦合、易于维护的应用程序,还提供了丰富的工具和API来应对各种复杂场景。无论是通过注解还是XML配置,Spring都展现了其灵活性和强大功能。