我们都知道,在使用Spring 框架时,常常遇到 Bean 概念,它其实是描述 Java 的软件组件模型,简单说,就是一个个组成一个可运行机器的小齿轮。而在 Spring 中存在一个专门管理这些齿轮的工厂,它是 IOC( Inversion of Control (IoC:控制反转)理论的实现,称为 DI(Dependency Injection:依赖注入),其中,IOC 是设计思想,DI 是实现方式,是同一个概念的不同角度描述。

在 Spring 框架中,DI 的具体实现类是称为容器,我们通过容器生成一个个小齿轮(Bean),从创建、实例化、销毁等等都交由容器去实现,进一步解耦合调用者与被调用者的代码,并且,去除了大量重复和无意义的代码,方便测试,利于功能复用。Spring 框架为了让用户更为简单的配置 Bean,使用了注解的形式进行配置,而无需去使用麻烦的 XML 进行一一对象配置。

概念性的说明就到这,更重要的是,我们去理解其运行原理,并实际使用它。在不使用 IOC 设计思想时,我们根据下图即可看出,由于某些情况下,以前的 A.class 被弃用了,经过修改变为 B.class ,此时调用 A.class 的对象都得修改代码,以适应变化,以防程序出错。当然,有人会说,那使用抽象接口不就行了,用什么 IOC 呢!其实,我们不能预料未来那些类会发生改变,只能尽可能做好抽象接口处理,总会有遗漏,况且,不是什么类都适合抽象接口,或者,如果每个类都适合并且也做了抽象接口的话,那将出现很多无意义的代码,变得臃肿。最重要的是,即使在存在抽象接口的情况下,你使用新的类,也必须对新的类进行实例化,这部分代码必须在调用者层面去修改,也就意味着调用双方还是得进行修改代码,重新编译源代码,不仅麻烦,还可能编译出错。

无 IOC 情况

当能够使用 IOC 思想之后,就能完美解决,让容器工厂去创建、实例化对象,注入给调用者即可。即使同一个抽象接口下,需要指定哪一个优先注入给调用者也是无需修改调用者代码,除非,还需要使用旧对象的一些方法,其实也可以直接复制代码,也是无需修改调用者代码的,但如果,实在需要旧对象,也可在调用者处修改代码,在获取 Bean 时指定对象。

存在 IOC 情况

在 Spring 中,对象被大致分为四大类,与之相对应的也有四个注解,它们仅仅体现在语义上的不同,方便编写者阅读代码而已。当然,对于对象的属性也有相对应的注入注解,方便自定义对象初始属性。对应的注解说明如下:

对象注入

属性注入

IOC 思想在 Spring 框架中不仅仅体现在对象的创建、实例化上,还能够在对象销毁前自动化地为我们执行某些操作。在测试之前,我们先学会使用容器工厂。

当我们需要使用容器工厂时,就必须使其方法所属类继承 ApplicationContextAware 并实现 setApplicationContext 方法,其中,在 setApplicationContext 方法中必须设置一个属性对象来接受容器工厂类 ApplicationContext 。并且,为了保证与主配置类相同启动环境,test 类也得使用 ContextConfiguration(classes = xxxApplication.class 来配置环境。代码如下:

package top.yumuing.community;

import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;

@SpringBootTest
@ContextConfigurationclasses=CommunityApplication.classclasses = CommunityApplication.classclasses=CommunityApplication.class
class CommunityApplicationTests implements ApplicationContextAware {

    private  ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

在方法前一行添加 @PostConstruct 即可 Bean 实例化后第一时间执行该方法,添加 @PreDestroy 即可在 Bean 销毁前最后执行。以上均为 Spring 框架的容器工厂自动执行,无需额外代码。测试类为 TestBeanManagement,代码如下:

package top.yumuing.community.test;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class TestBeanManagement {
    public TestBeanManagement(){
        System.out.println("实例化");
    }

    @PostConstruct
    public void init(){
        System.out.println("初始化");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("销毁了");
    }
}

测试方法如下:

package top.yumuing.community;

import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;

@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
class CommunityApplicationTests implements ApplicationContextAware {

    private  ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    @Test
    public void testBean(){
        TestBeanManagement testBeanManagement = applicationContext.getBean(TestBeanManagement.class);
        System.out.println(testBeanManagement);
    }
}

有时,我们使用了某些 jar 包里的对象,需要被注入时,又没办法直接在 jar 里增加注解怎么办?毕竟,jar 包可能加密,或者是需要调用 SDK 中的对象,源代码不可获取。此时,可以采用配置类的方式,通过自行实例化对象,再注册成 Bean,进而通过容器工厂进行调用,无需增加注解在 jar 包里。这种方法是我们主动去获取 Bean ,还是显得较为麻烦,后续有说明,在 Spring 框架下,正确的依赖注入方式,主动获取的方法代码如下:

TestConfig 为配置 jar 包内的 SimpleDateFormat 注册成 Bean 的类,CommunityApplicationTests 为调用该注册为 Bean 的 SimpleDateFormat 类。当然,实际项目目录会按业务、数据库、控制三级平级创建service、dao、controller三个目录,为了方便演示,简化为 test 目录,目录结构如下:

目录结构

TestConfig.java 代码如下:

package top.yumuing.community.test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.text.SimpleDateFormat;

@Configuration
public class TestConfig {

    @Bean
    public SimpleDateFormat simpleDateFormat(){
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }

}

CommunityApplicationTests.java 代码如下:

package top.yumuing.community;

import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;

import java.text.SimpleDateFormat;
import java.util.Date;

@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
class CommunityApplicationTests implements ApplicationContextAware {

    private  ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Test
    public void testBeanConfig(){
        SimpleDateFormat simpleDateFormat = applicationContext.getBean("simpleDateFormat",SimpleDateFormat.class);
        System.out.println(simpleDateFormat.format(new Date()));
    }

}

当然,这是我们利用容器工厂的特性进行主动获取 Bean 的方式,明显违背了 IOC 思想,我们将再次在相同的环境下,使用 IOC 思想实现的依赖注入方式解决问题。代码如下:

package top.yumuing.community;

import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;

import java.text.SimpleDateFormat;
import java.util.Date;

@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
class CommunityApplicationTests implements ApplicationContextAware {

    private  ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    @Autowired
    public SimpleDateFormat simpleDateFormat;
    @Test
    public void testDI(){
        System.out.println(simpleDateFormat.format(new Date()));
    }
}

如果想要指定继承了某个接口的对象,不仅仅需要使用 @Autowired 进行属性注入,还要配合 @Qualifier("Bean 名") 来指定。这次我们模拟实际项目代码,由 controller 调用 service,再由 service 调用 dao 的方式实现。目录结构如下:其中 TestDaoOne 与 TestDaoTwo 继承 TestDao 接口。

屏幕截图_20230201_001318

TestDao 代码如下:

package top.yumuing.community.test;

public interface TestDao {
    String select();
}

TestDaoOne 代码如下:

package top.yumuing.community.test;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

@Repository
@Primary
public class TestDaoOne implements TestDao{

    @Override
    public String select() {
        return "one";
    }
}

TestDaoTwo 代码如下:

package top.yumuing.community.test;

import org.springframework.stereotype.Repository;

@Repository("testDaoTwo")
public class TestDaoTwo implements TestDao{

    @Override
    public String select() {
        return "two";
    }
}

TestService 代码如下:

package top.yumuing.community.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class TestService {
    @Autowired
    public TestDao testDaoOne;

    public String getDateOne(){
        return testDaoOne.select();
    }

    @Autowired
    @Qualifier("testDaoTwo")
    public TestDao testDaoTwo;
    public String getDateTwo(){
        return testDaoTwo.select();
    }
}

HelloController 代码如下:

package top.yumuing.community.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/yumuing")
public class HelloController {
    @RequestMapping("/hello")
    @ResponseBody
    public String hello(){
        return "hello";
    }


    @Autowired
    public TestService testService;

    @RequestMapping("/dateOne")
    @ResponseBody
    public String oneDate(){
        return testService.getDateOne();
    }

    @RequestMapping("/dateTwo")
    @ResponseBody
    public String twoDate(){
        return testService.getDateTwo();
    }
}

通过以上的了解,我们就可以简单的知道,何为 IOC ,它又帮助我们减轻了什么,并且该怎么去运用它。除此之外,也简单明白了在Spring 框架中进行测试的方法,以及依赖注入的相关注解。

本文源代码

{hide}
源代码
{/hide}

求点赞转发

最后修改:2023 年 03 月 07 日
声明 😋 -博客名称: Yumuing 博客:做技术的朝拜者
🤓 -本文链接: https://yumuing.top/archives/8.html
🤔 -内容来源: 部分内容可能来源于公共网络,如有侵权,请联系博主进行核实删除。
😶 -转载说明: 请勿用于商业用途,转载请注明出处!



如果文章对你有用,评论一下、点个赞吧!或者请博主喝一杯咖啡吧!