Spring基础分析04-IoC/DI

大家好,今天一起学习一下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都展现了其灵活性和强大功能。

相关推荐
Kookoos19 分钟前
ABP VNext + Orleans:Actor 模型下的分布式状态管理最佳实践
分布式·后端·c#·.net·.netcore·abp vnext
PWRJOY23 分钟前
Flask 会话管理:从原理到实战,深度解析 session 机制
后端·python·flask
Uranus^27 分钟前
使用Spring Boot和Spring Security结合JWT实现安全的RESTful API
java·spring boot·spring security·jwt·restful api
FAQEW31 分钟前
介绍一下什么是反射(面试题详细讲解)
java·开发语言·反射
知识浅谈43 分钟前
Spring AI 使用教程
人工智能·spring
是三好1 小时前
并发容器(Collections)
java·多线程·juc
jian110581 小时前
java项目实战、pom.xml配置解释、pojo 普通java对象
java·开发语言·python
述雾学java2 小时前
Spring Boot是什么?MybatisPlus常用注解,LambdaQueryWrapper常用方法
java·spring boot·后端
jinhuazhe20132 小时前
maven 3.0多线程编译提高编译速度
java·maven
xosg2 小时前
HTMLUnknownElement的使用
java·前端·javascript