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都展现了其灵活性和强大功能。

相关推荐
ssxueyi41 分钟前
Java 常见Exception异常解决方法
java·开发语言·数据库·异常处理·exception
xzq_java44 分钟前
Javafx.麦当劳点餐系统(Java简洁版)
java·sql
silver6871 小时前
jvm内存优化
java
lzz的编码时刻1 小时前
观察者模式:事件处理机制与松耦合设计
java·开发语言·设计模式
yun_shui_1 小时前
【力扣-KMP】28.找出字符串第一个匹配项的下标
java·数据结构·算法·leetcode
lvyuanj2 小时前
IDEA skywalking 启动报错 ClassNotFoundException InstanceConstructorInterceptor
java·intellij-idea·skywalking
上海拔俗网络2 小时前
“AI全网络深度学习系统:开启智能时代的新篇章
java·团队开发
轩辕Ruins2 小时前
idea无法编译src/main/java下的xml或者properties文件
java·intellij-idea
z千鑫2 小时前
【Flask+OpenAI】利用Flask+OpenAI Key实现GPT4-智能AI对话接口demo - 从0到1手把手全教程(附源码)
人工智能·后端·python·chatgpt·flask·ai编程
Bang邦2 小时前
解决 MyBatis 中空字符串与数字比较引发的条件判断错误
java·mybatis·ognl·sql动态查询