4. 依赖查找&依赖注入

191 阅读7分钟

本小节源码:Spring-DI

1. 依赖查找

前面两个小节我们学习了如何将 Bean 对象分别以配置文件与注解的方式存入 Spring 中,然而通过 Bean 从 Spring 容器中取出的过程其实就是依赖查找,这里我简单归纳一下各种依赖查找的方式。

1.1 通过beanId查找

ByName类配置进xml文件以存入Spring容器:

<bean id="test1" class="com.chenshu.beans.ByName"></bean>

测试代码:

BeanFactory factory = new ClassPathXmlApplicationContext("spring-conf.xml");
ByName test1 = (ByName) factory.getBean("test1");
System.out.println(test1);

成功打印出全限定类名:

com.chenshu.beans.ByName@548e7350

1.2 通过beanClass查找

这次我们直接通过class存入Spring容器,不再声明Bean的id

<bean class="com.chenshu.beans.ByType"></bean>

由于我们通过传类型来查找 Bean 因此不需要强转:

ByType byType = factory.getBean(ByType.class);
System.out.println(byType);

成功打印出全限定类名:

com.chenshu.beans.ByType@754ba872

1.3 通过interface查找

创建下面的DemoDao接口和DemoDaoImpl实现类:

image.png

DemoDaoImpl实现类配置入Spring的xml文件

<bean class="com.chenshu.dao.impl.DemoDaoImpl"></bean>

测试代码:

DemoDao demoDao = factory.getBean(DemoDao.class);
System.out.println(demoDao);

通过传接口的class对象,发现可以成功取出它的实例(前提是该接口只有一个对应的bean实例):

com.chenshu.dao.impl.DemoDaoImpl@6eebc39e

2. 依赖注入

前面创建的 Bean 都是没有预设属性的,如果需要预设的属性就得运用到 IOC 的实现方式——“依赖注入”了。这里我将使用xml文件配置以及注解配置两种方式分别演示一遍如何依赖注入,xml文件配置的方式作为简单的了解即可,注解的方式需要掌握。

2.1 xml文件配置

在xml文件配置的依赖注入方式有以下两种:

  • Setter注入
  • Constructor注入

2.1.1 Setter注入

Setter注入,简单来说就是Spring通过Setter方法将属性值注入,因此Bean对象必须实现Setter()方法:

public class User {
    private int age;
    private String name;

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + ''' +
                '}';
    }
}

简单类型

在bean的配置中新增property标签,name的值对应的就是需要引入依赖的字段名,value的值对应的就是字段值:

<bean id="user" class="com.chenshu.beans.User">
    <property name="age" value="19"></property>
    <property name="name" value="zhangsan"></property>
</bean>

测试代码:

User user = (User) factory.getBean("user");
System.out.println(user);

成功打印出刚刚我们所配置的字段值:

User{age=19, name='zhangsan'}

引用类型

在Spring框架中,如果一个Bean需要依赖另一个对象(即引用另一个对象),那么被引用的对象通常也应该是一个Spring容器管理的Bean。这是因为Spring的依赖注入机制是基于容器的,它负责创建、配置和管理Bean的生命周期。

新建一个service包,并在包中创建一个UserService的类:

public class UserService {
    private DemoDao demoDao;

    public void setDemoDao(DemoDao demoDao) {
        this.demoDao = demoDao;
    }

    public DemoDao getDemoDao() {
        return demoDao;
    }
}

并且该类想要依赖下面这个前面已经配置过的Bean:

<bean class="com.chenshu.dao.impl.DemoDaoImpl"></bean>

由于前面给大家演示的时候并没有给这个 Bean 取一个name,因此我们这里修改一下配置:

<bean id="demoDaoImpl" class="com.chenshu.dao.impl.DemoDaoImpl"></bean>

将UserService添加至xml文件,并添加property标签,其中name的值就是UserService需要依赖注入的字段名,ref的值对应的是需要引入的依赖的beanId,这就是为什么要给需要依赖的Bean取name的原因

<bean class="com.chenshu.service.UserService">
    <property name="demoDao" ref="demoDaoImpl"></property>
</bean>

测试代码:

UserService userService = factory.getBean(UserService.class);
System.out.println(userService.getDemoDao());

成功打印出我们所配置的值的全限定类名:

com.chenshu.dao.impl.DemoDaoImpl@67784306

2.1.2 Constructor注入

既然是构造器注入,那自然免不了编写它的构造器,你需要注入几个字段就编写其对应的构造器就好,我这里选择把两个字段都注入:

public User(int age, String name) {
    this.age = age;
    this.name = name;
}

简单类型

除了标签名,其他都和Setter大体一致,这里直接给出代码:

<bean id="user" class="com.chenshu.beans.User">
    <constructor-arg name="age" value="20"></constructor-arg>
    <constructor-arg name="name" value="lisi"></constructor-arg>
</bean>

运行结果:

User{age=20, name='lisi'}

引用类型

编写UserService的构造器:

public UserService(DemoDao demoDao) {
    this.demoDao = demoDao;
}

配置文件:

<bean class="com.chenshu.service.UserService">
    <constructor-arg name="demoDao" ref="demoDaoImpl"></constructor-arg>
</bean>

运行结果:

com.chenshu.dao.impl.DemoDaoImpl@65e2dbf3

2.2 注解配置

注解配置更加简单、方便。人总是趋利的,在企业开发的时候绝大多数还是使用注解的方式进行配置,注解配置实现依赖注入的方式有以下三种:

  • Field注入
  • Setter注入
  • Construcor注入

依赖注入需要使用@Autowired@Resource注解,这里我先演示前者,下一节会讲它们的区别。

这里我将使用纯注解的方式注册和动态注入Bean。

前置操作:

  1. 在xml文件中添加扫描路径
<content:component-scan base-package="com.chenshu"></content:component-scan>
  1. 通过@Controller、@Service、@Repository这三个注解分别将UserController、UserService、UserDaoImpl存入 IOC 容器。

image.png


2.2.1 Field(属性)注入

所谓field注入就是属性注入,这种方式很简单,直接在需要依赖注入的属性上加上@Autowired注解就好了,它也是日常开发中最常使用的一种注入方式

UserController需要依赖UserSerice

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    public void doController() {
        userService.doService();
        System.out.println("Do User Controller.");
    }
}

UserService需要依赖UserDaoImpl,由于UserDaoUserDaoImpl的接口,我们直接使用UserDao来接收:

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public void doService() {
        userDao.selectAll();
        System.out.println("Do User Service");
    }
}

UserDaoImpl没有需要依赖的对象:

@Repository
public class UserDaoImpl implements DemoDao {
    @Override
    public void selectAll() {
        System.out.println("select * from user");
    }
}

通过依赖查找找到UserController,并调用doController()

public static void main(String[] args) {
        BeanFactory factory = new ClassPathXmlApplicationContext("spring-conf.xml");
        UserController userController = factory.getBean("userController", UserController.class);
        userController.doController();
    }

依赖成功注入,并分别成功调用了三个 Bean 的方法:

select * from user
Do User Service
Do User Controller.

看到这里是否觉得很眼熟,我们可以回顾一下前面所学章节:1.Spring的核心思想 —— IOC和DI,再次理解一下何为IOC和DI

Field注入优缺点总结

优点:写法简单

缺点:

  1. 不能注入final修饰的属性:final修饰的变量只能在使用时直接赋值或者在构造方法赋值,而属性注入的方式,是在构造出对象之后Spring帮你赋值,不符合jdk的规范;
  2. 通用性问题:只能适用于IOC容器;
  3. 更容易违背单一设计原则:单一设计原则指的是一个类应只负责一项职责,由于这种写法过于简单,违背单一设计原则的概率就提升了;

2.2.2 Setter注入

所谓Setter注入,就是Spring通过setter方法来注入依赖的Bean,这种方式需要构造一个Setter方法,再在Setter上面加上一个@Autowired注解:

@Controller
public class UserController {

    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void doController() {
        userService.doService();
        System.out.println("Do User Controller.");
    }
}

依赖成功注入,并分别成功调用了三个 Bean 的方法:

select * from user
Do User Service
Do User Controller.

Setter注入优缺点总结

优点: 符合单一设计原则

缺点:

  1. 不能注入final修饰的属性,原因同属性注入
  2. 【重要】注入对象可能被改变:由于setter方法可能会被多次调用,所以就有被修改的风险;

2.2.3 Constructor注入

Constructor注入(Spring官方推荐的依赖注入方式),也就是Spring通过Bean提供的构造器,将依赖注入Bean,跟Setter大同小异,只要在构造方法中将需要添加的依赖作为构造器的参数,再在构造器上添加一个@Autowired注解就可以了:

@Controller
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void doController() {
        userService.doService();
        System.out.println("Do User Controller.");
    }
}

依赖成功注入,并分别成功调用了三个 Bean 的方法:

select * from user
Do User Service
Do User Controller.

与Setter的小区别: 当这个Bean的构造方法只有一个时,可以省略@Autowired参数


Constructor注入优点总结

  1. 可以注入final修饰的对象;
  2. 注入对象不会被修改:不像setter注入那样,构造方法在对象创建时只会执行一次,因此不存在依赖Bean被修改的情况;
  3. 完全初始化:因为依赖在类的构造方法中执行的,而构造方法是在对象创建之初执行的方法;
  4. 通用性更好,因为构造方法是 JDK 支持,所以更换任何框架,它都是适用的。