IoC和Spring的三种注入方式

名词解释

依赖倒置原则(DIP):一种软件软件架构设计原则
  • 上层模块不应该依赖于下层模块,它们共同依赖于一个抽象。

  • 抽象不能依赖于具象,具象依赖于抽象。

  依赖倒置解决的问题是:如果上层模块依赖下层模块(依赖是指上层模块在类内创建下层模块实例,根据下层模块提供的方法编写上层模块的功能),每当下层模块需要修改,上层模块也要一并修改

控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)

  将设计好的类交给系统控制,而不是在类的内部控制,称为控制反转

依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)

  如果不直接在类内实例底层模块,就要提供其他方法传入要执行的底层模块,即依赖注入

IoC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)

具体实现

在Java平台的Spring框架下,有三种依赖注入方式

  • 构造函数注入

  • setter注入

  • 基于注解的注入

    ​ 假设使用场景为用户使用媒体播放器播放媒体文件,下面演示三种IoC实现方式

构造函数注入

Media接口和实现类
package example1;

public interface MediaInterface {
}
package example1;

public class Media implements MediaInterface{
    public Media() {
        System.out.println("创建媒体文件");
    }
}
MediaPlayer接口和实现类
package example1;

public interface MediaPlayerInterface {
    void play();
}
package example1;

public class MediaPlayer implements MediaPlayerInterface {
    private MediaInterface media;

    public MediaPlayer(MediaInterface media) {
        this.media = media;
        System.out.println("创建播放器");
    }

    @Override
    public void play() {
        System.out.println("播放器播放");
    }
}
User接口和实现类
package example1;

public interface UserInterface {
    void play();
}

package example1;

public class User implements UserInterface {
    MediaPlayerInterface mediaPlayer;

    public User(MediaPlayerInterface mediaPlayer) {
        this.mediaPlayer = mediaPlayer;
        System.out.println("产生用户");
    }

    @Override
    public void play() {
        mediaPlayer.play();
    }
}

Beans配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="user" class="example1.User">
        <constructor-arg ref="mediaPlayer"/>
    </bean>

    <bean id="mediaPlayer" class="example1.MediaPlayer">
        <constructor-arg ref="media"/>
    </bean>

    <bean id="media" class="example1.Media"/>

</beans>
主函数入口
package example1;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        User user = (User) context.getBean("user");
        user.play();
    }
}

运行结果

创建媒体文件
创建播放器
产生用户
播放器播放

setter注入

  所有接口文件不变,修改实现类和Beans配置文件

Media类
package example1;

public class Media implements MediaInterface{
    public Media() {
        System.out.println("创建媒体文件");
    }
}
MediaPlayer类
package example1;

public class MediaPlayer implements MediaPlayerInterface {
    private MediaInterface media;

    public void setMedia(MediaInterface media) {
        this.media = media;
        System.out.println("创建播放器");
    }

    @Override
    public void play() {
        System.out.println("播放器播放");
    }
}

User类
package example1;

public class User implements UserInterface {
    MediaPlayerInterface mediaPlayer;

    public void setMediaPlayer(MediaPlayerInterface mediaPlayer) {
        this.mediaPlayer = mediaPlayer;
        System.out.println("产生用户");
    }

    @Override
    public void play() {
        mediaPlayer.play();
    }
}

Beans配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="user" class="example1.User">
        <property name="mediaPlayer" ref="mediaPlayer"/>
    </bean>

    <bean id="mediaPlayer" class="example1.MediaPlayer">
        <property name="media" ref="media"/>
    </bean>

    <bean id="media" class="example1.Media"/>

</beans>

基于注解的注入

  Spring提供了多种注解应对各种情况,这里只用到最简单的 @Component@Autowired

@Component

作用:将当前类对象存入Spring容器中,默认value是类名首字母小写

使用范围:类前

@Autowired

作用:按照类型从Spring容器中选择匹配数据类型注入

使用范围:属性前,方法前

Media类
package example1;

import org.springframework.stereotype.Component;

@Component
public class Media implements MediaInterface {
    public Media() {
        System.out.println("创建媒体文件");
    }
}
MediaPlayer类
package example1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MediaPlayer implements MediaPlayerInterface {

    @Autowired
    private MediaInterface media;

    @Override
    public void play() {
        System.out.println("播放器播放");
    }
}
User类
package example1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class User implements UserInterface {
    @Autowired
    MediaPlayerInterface mediaPlayer;

    @Override
    public void play() {
        mediaPlayer.play();
    }
}
Beans配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="example1"/>

</beans>

  显然,在这些代码中并没有实例化 MediaMediaPlayerUser 类,即这些类并不是由我们直接在类内控制,而是全部交给了系统控制,这就是控制反转。这些类之间的依赖关系,也全部交给了xml文件来管理,而不是在代码内部产生依赖。

  假如要写一个新的下层类,那么只要下层类实现上层类需要的操作,然后放入Spring容器中,Spring就会自动完成组装,而不需要我们在类内更改依赖关系

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章