SpringIOC思想和DI依赖注入

简述

IOC(Inversion of Control,控制反转)和 DI(Depandence Injection依赖注入)

我们知道,java实现的复杂系统中通过多个类之间的协作完成特定的业务,在传统或者说古老的做法中,每个对象负责管理与自己协作的对象的引用,即管理着所依赖的对象的引用。这会导致高度耦合和难以测试。
而IOC控制反转思想就是将对象的依赖关系交给第三方组件来设定,对象无需自行创建或管理自己的依赖关系,这样就可以让相互协作的软件组件松散耦合。

那么问题来了,怎么实现控制反转思想呢:DI依赖注入。
Spring提供的IOC容器就是上文中的第三方组件。在Spring中,用bean表示应用组件,将创建应用组件之间协作的行为称为装配,Spring中提供的IOC容器(ApplicationContext),通过DI(依赖注入)装配bean以此来实现IOC控制反转。

也就是Spring应用上下文通过不同的装配方式,不同的依赖注入方式来组装bean,管理他们的创建和依赖关系。总结来说:IOC思想的实现:IOC容器加载装配的配置方式,配置方式中定义了DI(依赖注入)采用的注入方式,最后依赖注入实现控制反转。

Spring提供了三种依赖注入的方法:构造方法注入、属性setter注入和接口注入(基本不用了,就不阐述了)。
Spring提供了三种bean的装配方式:1.通过xml装配bean,2.通过java代码装配bean,3.自动化装配bean。
下面我们来看下构造方法注入、属性setter注入,默认都使用xml装配bean。

set方法注入

假设我们定义了一个接口为动物的一些动作,活动。例如,跑,跳。movementService(interface),并提供跳jump和run的方法。并提供了它的实现类movementServiceImpl,如图:

/**
 * @Des 动物的一些能力接口,例如跑,跳
 * @Author chenzhe
 * @Date 2020-08-08 15:23
 */
public interface movementService {  
    public String jump();
    public String run();
}
/**
 * @Des movementService的实现类,实现了jump run能力
 * @Author chenzhe
 * @Date 2020-08-08 15:51
 */
public class movementServiceImpl implements movementService{
    @Override
    public String jump(){
        return "跳跳跳";
    }
    @Override
    public String run(){
        return "跑跑跑";
    }
}

再定义老虎捕食的接口及其实现类,老虎的捕食能力需要引用动物的能力:

/**
 * @Des 老虎捕食接口的实现
 * @Author chenzhe
 * @Date 2020-08-10 10:23
 */
public class tigerPredationServiceImpl implements tigerPredationService {
    private movementService movementService;
    public void setMovementService(movementService movementService) {
        this.movementService = movementService;
    }
    @Override
    public String eat() {
        return "吃吃吃";
    }
    @Override
    public String preyOn() {
        return "老虎正在+" + movementService.jump();
    }

}

下图是在xml中配置bean以及setter方法注入。值得注意的是xml配置中name=“movementService”,Spring中的IOC容器在读取xml后会去tigerPredationServiceImpl找到set+movementService进行注入。所以name要保证和set方法对应,属性名movementService开头可以大写或者小写,但是后面的要对应

 <!--基于xml装配方式的setter方法注入-->
     <!--注册movementServiceImpl -->
    <bean id="movementService" class="com.cz.service.impl.movementServiceImpl"></bean>
     <!--注册tigerPredationServiceImpl -->
    <bean id="tigerPredationServiceImpl" class="com.cz.service.impl.tigerPredationServiceImpl">
         <!--set方法注入-->
        <property name="movementService" ref="movementService"/>
    </bean>

在test文件夹下新建一个测试类myFirstTest,验证注入成功:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class myFirstTest {
    @Test
    public void test(){
        System.out.println("+++++++++++++++++++++++");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        tigerPredationService tigerPredationService = applicationContext.getBean("tigerPredationServiceImpl",tigerPredationServiceImpl.class);
        String result = tigerPredationService.preyOn();
        System.out.println(result);
        System.out.println("+++++++++++++++++++++++");
    }
}

结果如下:
+++++++++++++++++++++++
老虎正在+跳跳跳
+++++++++++++++++++++++

构造器方法注入

构造器注入方式是怎么配置的呢,首先在tigerPredationServiceImpl的构造方法中将引用的bean对象做为参数传入,如图

/**
 * @Des 老虎捕食接口的实现
 * @Author chenzhe
 * @Date 2020-08-10 10:23
 */
public class tigerPredationServiceImpl implements tigerPredationService {
    private movementService movementService;
    public tigerPredationServiceImpl(movementService movementService) {
        this.movementService = movementService;
    }
    @Override
    public String eat() {
        return "吃吃吃";
    }
    @Override
    public String preyOn() {
        return "老虎正在+" + movementService.jump();
    }
}

然后在xml中配置构造器注入bean,如下

<!--基于xml装配方式的构造器方法注入-->
     <!--注册movementServiceImpl -->
    <bean id="movementService" class="com.cz.service.impl.movementServiceImpl"></bean>
    <bean id="tigerPredationServiceImpl" class="com.cz.service.impl.tigerPredationServiceImpl">
    <!--构造器注入-->
        <constructor-arg>
            <ref bean="movementService"/>
        </constructor-arg>
    </bean>

运行test结果和set方法一样:
+++++++++++++++++++++++
老虎正在+跳跳跳
+++++++++++++++++++++++

上述都是基于xml这种装配方法,显而易见,xml装配方式一个巨大的缺点就是,每新增一个应用组件开发者就要配置一个bean并配置bean的依赖注入,这会导致xml文件越来越庞大,并且不易维护。所以,Spring提供了另外两种装配方式,更加方便,易维护。下面是Spring提供的其他两种不同的装配方式。

基于java代码装配bean,在java代码中设置通过构造器方法注入

新建tigerPredationConfig类作为java配置类,如下

/**
 * @Des 基于java代码配置组件扫描
 * @Author chenzhe
 * @Date 2020-08-12 14:16
 */
@Configuration
public class tigerPredationConfig {
}

在类上面加上注解@Configuration,表明这是一个java配置类,Spring在扫描时会根据带有@Configuration的配置类装配bean。
在movementService()方法上加上@Bean,Spring会将这个方法中返回的对象注册成Spring中的bean,bean的名称会约定为和方法名相同,即movementService,当然也支持自定义bean名称,如下

/**
 * @Des 基于java代码配置组件扫描
 * @Author chenzhe
 * @Date 2020-08-12 14:16
 */
@Configuration
public class tigerPredationConfig {
    @Bean(name = "movementService")
    public movementService movementService(){
        return new movementServiceImpl();
    }
    @Bean
    public tigerPredationService tigerPredationService(){
        return new tigerPredationServiceImpl(movementService());
    }
}

再看注册tigerPredationServiceImpl bean,tigerPredationServiceImpl()方法也返回了一个对象,但是在new tigerPredationServiceImpl时,movementService作为构造器中的传参传入,实现实例创建中对象依赖的注入。当然,也可以这样传参:

/**
 * @Des 基于java代码配置组件扫描
 * @Author chenzhe
 * @Date 2020-08-12 14:16
 */
@Configuration
public class tigerPredationConfig {
    @Bean(name = "movementService")
    public movementService movementService() {
        return new movementServiceImpl();
    }
    @Bean
    public tigerPredationService tigerPredationService(movementService movementService) {
        return new tigerPredationServiceImpl(movementService);
    }
}

此时tigerPredationServiceImpl类如下

/**
 * @Des 老虎捕食接口的实现
 * @Author chenzhe
 * @Date 2020-08-10 10:23
 */
public class tigerPredationServiceImpl implements tigerPredationService {
    private movementService movementService;
    public tigerPredationServiceImpl(movementService movementService) {
        this.movementService = movementService;
    }
    @Override
    public String eat() {
        return "吃吃吃";
    }
    @Override
    public String preyOn() {
        return "老虎正在+" + movementService.jump();
    }
}

可见其构造方法中movementService作为传参传入。当然在Spring配置文件中不要忘了加扫描包的配置:
<context:component-scan base-package=“com.cz” />,这样Spring才能去识别@Configuration。

基于java代码装配bean,在java代码中设置通过set方法注入

java代码装配set方法注入的bean如下

/**
 * @Des 基于java代码配置组件扫描
 * @Author chenzhe
 * @Date 2020-08-12 14:16
 */
@Configuration
public class tigerPredationConfig {
    @Bean(name = "movementService")
    public movementService movementService() {
        return new movementServiceImpl();
    }
    @Bean
    public tigerPredationService tigerPredationService(movementService movementService) {
        tigerPredationServiceImpl tigerPredationServiceImpl = new  tigerPredationServiceImpl();
        tigerPredationServiceImpl.setMovementService(movementService);
        return tigerPredationServiceImpl;
    }
}

此时tigerPredationServiceImpl类中应有set方法对应,如下

/**
 * @Des 老虎捕食接口的实现
 * @Author chenzhe
 * @Date 2020-08-10 10:23
 */
public class tigerPredationServiceImpl implements tigerPredationService {
    private movementService movementService;
    public void setMovementService(movementService movementService) {
        this.movementService = movementService;
    }
    @Override
    public String eat() {
        return "吃吃吃";
    }
    @Override
    public String preyOn() {
        return "老虎正在+" + movementService.jump();
    }
}

当然,这时候测试类要改下,因为不是xml配置的,所以Spring的容器读取xml来getBean就得不到tigerPredationServiceImpl和movementServiceImpl的注册了。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class myFirstTest {
    @Test
    public void test(){
        System.out.println("+++++++++++++++++++++++");
        tigerPredationService tigerPredationService = new tigerPredationServiceImpl();
        ((tigerPredationServiceImpl) tigerPredationService).setMovementService(new movementServiceImpl());
        String result = tigerPredationService.preyOn();
        System.out.println(result);
        System.out.println("+++++++++++++++++++++++");
    }
}

运行test结果验证如下:
+++++++++++++++++++++++
老虎正在+跳跳跳
+++++++++++++++++++++++

自动化装配bean

Spring实现自动化装配是通过两点:组件扫描和自动装配而组件扫描可以由xml配置,也可以由java代码配置。自动装配则是由注解实现。
上面说了,在Spring中对组件扫描进行配置才能让Spring 发现类或方法上的注解来注册bean以及依赖注入bean。下面就是xml配置组件扫描 <context:component-scan base-package=“com.cz” />其中com.cz就是要扫描的包名,组件扫描的范围就是这个包及所有子包下面的所有组件。
而基于java代码的组件扫描配置如图:

/**
 * @Des 基于java代码配置组件扫描
 * @Author chenzhe
 * @Date 2020-08-12 14:16
 */
@Configuration
@ComponentScan
public class tigerPredationConfig {
    @Bean(name = "movementService")
    public movementService movementService() {
        return new movementServiceImpl();
    }
    @Bean
    public tigerPredationService tigerPredationService(movementService movementService) {
        tigerPredationServiceImpl tigerPredationServiceImpl = new  tigerPredationServiceImpl();
        tigerPredationServiceImpl.setMovementService(movementService);
        return tigerPredationServiceImpl;
    }
}

@Configuration表示此类是一个配置类,可替换xml配置,而加上@ComponentScan注解,Spring会扫描和tigerPredationConfig同一包下的注解,当扫描到诸如@Component,就会将该类注册为Spring中的bean,如图将movementServiceImpl注册为一个bean

/**
 * @Des movementService的实现类,实现了jump run能力
 * @Author chenzhe
 * @Date 2020-08-08 15:51
 */
@Component("movementServiceImpl")
public class movementServiceImpl implements movementService{
    @Override
    public String jump(){
        return "跳跳跳";
    }

    @Override
    public String run(){
        return "跑跑跑";
    }
}

当然,在实际开放中,为了维护方便和代码规范,通常配置类都放在同一包下,此时为了让Spring可以扫描其他包,或者指定包下的组件,就可以这样配置:

/**
 * @Des 基于java代码配置组件扫描
 * @Author chenzhe
 * @Date 2020-08-12 14:16
 */
@Configuration
@ComponentScan(basePackages = "com")
public class tigerPredationConfig {
    @Bean(name = "movementService")
    public movementService movementService() {
        return new movementServiceImpl();
    }
    @Bean
    public tigerPredationService tigerPredationService(movementService movementService) {
        tigerPredationServiceImpl tigerPredationServiceImpl = new  tigerPredationServiceImpl();
        tigerPredationServiceImpl.setMovementService(movementService);
        return tigerPredationServiceImpl;
    }
}

其中@ComponentScan(basePackages = “com”)指定了Spring组件扫描的基础包为com,当然Spring还提供了多个包的组件扫描,或者指定类的组件扫描:

/**
 * @Des 基于java代码配置组件扫描
 * @Author chenzhe
 * @Date 2020-08-12 14:16
 */
@Configuration
@ComponentScan(basePackages = {"com.cz.service","com.cz.controller","com.cz.util"})
public class tigerPredationConfig {
    @Bean(name = "movementService")
    public movementService movementService() {
        return new movementServiceImpl();
    }
    @Bean
    public tigerPredationService tigerPredationService(movementService movementService) {
        tigerPredationServiceImpl tigerPredationServiceImpl = new  tigerPredationServiceImpl();
        tigerPredationServiceImpl.setMovementService(movementService);
        return tigerPredationServiceImpl;
    }
}
/**
 * @Des 基于java代码配置组件扫描
 * @Author chenzhe
 * @Date 2020-08-12 14:16
 */
@Configuration
@ComponentScan(basePackageClasses = {tigerPredationServiceImpl.class,movementServiceImpl.class})
public class tigerPredationConfig {
    @Bean(name = "movementService")
    public movementService movementService() {
        return new movementServiceImpl();
    }
    @Bean
    public tigerPredationService tigerPredationService(movementService movementService) {
        tigerPredationServiceImpl tigerPredationServiceImpl = new  tigerPredationServiceImpl();
        tigerPredationServiceImpl.setMovementService(movementService);
        return tigerPredationServiceImpl;
    }
}

讲完了组件扫描,怎么去实现Spring的自动装配呢,这就要用到自动装配注解了:@Autowired、@Resource、@Inject如下

/**
 * @Des 老虎捕食接口的实现
 * @Author chenzhe
 * @Date 2020-08-10 10:23
 */
@Component
public class tigerPredationServiceImpl implements tigerPredationService {
    private movementService movementService;
    @Autowired
    public tigerPredationServiceImpl(movementService movementService) {
        this.movementService = movementService;
    }
    @Override
    public String eat() {
        return "吃吃吃";
    }
    @Override
    public String preyOn() {
        return "老虎正在+" + movementService.jump();
    }
}

当然除了构造器自动装配,还可以set方法自动装配,如下

/**
 * @Des 老虎捕食接口的实现
 * @Author chenzhe
 * @Date 2020-08-10 10:23
 */
@Component
public class tigerPredationServiceImpl implements tigerPredationService {
    private movementService movementService;
    @Autowired
    public void setMovementService(movementService movementService) {
        this.movementService = movementService;
    }
    @Override
    public String eat() {
        return "吃吃吃";
    }
    @Override
    public String preyOn() {
        return "老虎正在+" + movementService.jump();
    }
}

其实set方法并没有什么特殊,@Autowired注解可以作用在类的任何方法上,比如下面的testInject方法注入效果是一样的

/**
 * @Des 老虎捕食接口的实现
 * @Author chenzhe
 * @Date 2020-08-10 10:23
 */
@Component
public class tigerPredationServiceImpl implements tigerPredationService {
    private movementService movementService;
    @Autowired
    public void testInject(movementService movementService) {
        this.movementService = movementService;
    }
    @Override
    public String eat() {
        return "吃吃吃";
    }
    @Override
    public String preyOn() {
        return "老虎正在+" + movementService.jump();
    }
}

至此, Spring提供的两种依赖注入的方法:构造方法注入、属性setter注入和三种bean的装配方式:1.通过xml装配bean,2.通过java代码装配bean,3.自动化装配bean都讲完了。本文其实主要讲思想,其中一些细节,比如xml配置中的一些标签运用,再比如@Resource、@Inject都能实现和@Autowired的自动装配等细节都没有阐述,思想明白了,就看工具怎么使用了,大家多码多练熟能生巧。思想才是核心。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值