探索Spring系列(三)揭开SpringIOC的面纱

前言

  作为一名java开发人员,我们接触最早最多的框架肯定就是spring了,一次次不停的使用spring框架提供的功能帮助我们快速开发,然而其中的核心功能IOC(控制反转)和AOP能详细阐明的却不多,我们知其然,却不知其所以然,那么下面我们就一起探索一下spring中的IOC吧

正篇

  • IOC概念

例:在现实生活中我的头发长了,影响了我的帅气,那么我就需要理发,我不可能自己给自己理发,首先我没有理发的工具,其次我可能把自己的头发剪得像狗啃的一样,那么影响我帅气的外表,就没有妹子愿意跟我一起约会了,这肯定是我不愿意发生的。其次,我也不是王思聪,我也不肯能拥有一名只为自己服务的人员,那么正确的做法就是去村口的理发店找Tony老师来给我打造帅气的发型。我们来分解一下剪发的过程,我是需求提出者,头发变帅是要的结果,首先如果我自己为自己理发,不仅可能达不到预期的效果,而且还费时费力,那么我就需要一名专业的理发师来为我服务,达到最终的目的,这个过程我们可能称之为解耦合的过程。
在Java开发中,一个功能的实现往往是由多个类和方法来共同协作完成的,在没有IOC之前我们创建一个类的依赖类的方式是new object(),控制权在本类手中,这些依赖关系将会使系统的复杂度提高,不利于维护和开发,这是我们不愿意看到的,在有了IOC之后,一个类所需要的依赖类由IOC来管理,那么我们只要关心本身类的功能和方法即可,将控制权交给了IOC容器,这就是我理解的控制反转。

其中BeanFactory是IOC容器的基本实现,ApplicationContext是BeanFactory的子接口,提供更高级的特性。 而下面几个类则是具体的实现类,可以加载配置文件中定义的bean,管理所有加载的bean,有请求的时候分配bean。

  • DI(依赖注入)

创建应用对象之间的关联关系的传统方法(通过构造器或者查找)通常会导致结构复杂的代码,这些代码难以被复用也很难进行单元测试,如果情况不严重的话,这些对象所做的事情只是超出了应该做的范围,而最坏的情况是,这些对象的彼此之间高度耦合,难以复用和测试。
在spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要互相协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。(摘录spring实战第四版第二章)

  • 自动化配置Bean

1:在Java中进行显式配置 (个人喜欢)
我们定义接口以及其实现类

package com.lly.springtest1.ioc;
/**
 * @Author lly
 * @Description 水果接口类
 * @Date 2019/1/29 10:23 AM
 * @Param  
 * @return 
 **/
public interface IFruitService {
    /**
     * @Author lly
     * @Description  显示水果信息
     * @Date 2019/1/29 10:25 AM
     * @Param  []
     * @return
     **/
    void showFruitInfo();
}

复制代码
package com.lly.springtest1.ioc;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @ClassName OrangeServiceImpl
 * @Description 水果实现类-橘子
 * @Author lly
 * @Date 2019/1/29 10:17 AM
 * @Version 1.0
 **/
@Component
@Data
@Slf4j
public class OrangeServiceImpl implements IFruitService {


    @Override
    public void showFruitInfo() {
        log.info("橘子的重量是:{}kg",10);
    }
}


复制代码

扫描组件配置类,@ComponentScan注解会默认扫描与其配置类相同的包以及这个包下面的所有的子包,查找带有@Component注解的类,当然我们可以直接定@ComponentScan扫描的包

#单个包
@ComponentScan("包名")
@ComponentScan(basePackage="包名")
#多个包
@ComponentScan(basePackage={"包名","包名"})
复制代码
package com.lly.springtest1.ioc;

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

/**
 * @ClassName TestIoc
 * @Description 扫描组件配置类
 * @Author lly
 * @Date 2019/1/29 10:30 AM
 * @Version 1.0
 **/
@ComponentScan
@Configuration
public class TestIoc {

}

复制代码

单元测试

package com.lly.springtest1.ioc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestIoc.class)
public class TestIocTest {
    @Autowired
    private IFruitService iFruitService;
    @Test
    public void  showInfo(){
        iFruitService.showFruitInfo();
    }


}
复制代码

结果

可以看到我们的组件orangeEntity已经成功被spring容器管理了,成功注入到测试类中
关于 @Autowired 自动装配的方式除了上述还有通过构造器和setter方法注入效果都是一样的

2:隐式的bean发现机制和自动装配 (个人喜欢)
其实这种方式我们在 探索Spring系列(一)Spring容器和Bean的生命周期 这里章节已经见到过了,下面贴出核心代码

package com.lly.springtest1.entity;

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


@Configuration
public class BeanLifeCycle {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanLifeCycle.class);
        context.close();
    }

    @Bean
    public MyBeanPostProcessor getBean() {
        return new MyBeanPostProcessor();
    }

    @Bean(initMethod = "myInit",destroyMethod = "myDestroy")
    public GirlFriendEntity getGirl() {
        GirlFriendEntity girl = new GirlFriendEntity();
        girl.setName("颖宝");
        return girl;
    }
}

复制代码

这种配置的方式也可以将bean纳入spring容器的管理中,下面我们来测试一下

package com.lly.springtest1.entity;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = BeanLifeCycle.class)
public class BeanLifeCycleTest {


    @Autowired
    private  MyBeanPostProcessor myBeanPostProcessor;

    @Test
    public void  show(){
        Assert.assertNotNull(myBeanPostProcessor);

    }
}
复制代码

可以看到bean已经被容器管理了,成功注入到测试类中去了

3:xml中显示配置(个人不喜欢这种方式,配置很繁琐,有兴趣的同学自行了解学习)

4:总结,上述几种装配bean的方式,都可以实现同样的功能,也可以混合使用,使用哪种方式完全可以按照开发者个人的习惯和喜好来决定,但是作者目前使用的都是前两种,消除配置式编程,更快乐,在目前比较流行的springboot开发中也是推荐前两种

  • 高级装配Bean

1:消除歧义性 在上文中我们定义个一个IFruitService,orangeEntity 这个类实现了这个接口,我们在测试类中是直接注入的,那么我可以想想一下,在实际的开发中,可能存在一个接口对应多个实现类的情况,spring在帮我自动注入的时候是怎么帮我们选择的呢,下面我们来实验一下。
我们再创建一个水果的实现类 香蕉

package com.lly.springtest1.ioc;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @ClassName BananaServiceImpl
 * @Description 水果实现类-橘子
 * @Author lly
 * @Date 2019/1/29 10:17 AM
 * @Version 1.0
 **/
@Component
@Data
@Slf4j
public class BananaServiceImpl implements IFruitService {
    @Override
    public void showFruitInfo() {
        log.info("香蕉的重量是:{}kg",100);
    }
}

复制代码

然后我们再次启动测试类发现

错误信息如下:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lly.springtest1.ioc.IFruitService' available: expected single matching bean but found 2: bananaServiceImpl,orangeServiceImpl
复制代码

通过查看我们发现IFruitService这个接口有2个实现类,我们没有指定要使用哪个,那么spring是不会知道我们将要使用哪个的。

解决方法:
spring提供2中方式来解决这个问题
第一种:使用@Qualifier注解来指定我们要使用的具体实现类

我们可以看到在我们指定了具体实现类后测试用例顺利通过。
第二种:使用@Primary注解来指定哪个实现类作为首选实现类,我们在香蕉类上来加上这个注解

package com.lly.springtest1.ioc;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

/**
 * @ClassName BananaServiceImpl
 * @Description 水果实现类-橘子
 * @Author lly
 * @Date 2019/1/29 10:17 AM
 * @Version 1.0
 **/
@Component
@Data
@Slf4j
@Primary
public class BananaServiceImpl implements IFruitService {


    @Override
    public void showFruitInfo() {
        log.info("香蕉的重量是:{}kg",100);
    }
}

复制代码

测试类改造如下

package com.lly.springtest1.ioc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestIoc.class)
public class TestIocTest {
    @Autowired
    private IFruitService iFruitService;
    @Test
    public void  showInfo(){
        iFruitService.showFruitInfo();
    }

}
复制代码

启动查看结果

测试用例通过,当然这个注解在接口实现类上市互斥的,只有一个实现类上可以加,如果超过1个实现类上使用此注解,同样会出现我们刚开始出现的异常信息

  • Bean的作用域

1)单例(singleton)在整个应用中,只创建一个实例;(默认情况下,spring应用上下文中所有的bean都是单例模式)。 2)原型(prototype)每次注入或者通过spring应该上下文获取的都是一个新的实例。 3)会话(session)在web应用中,为每个会话创建一个bean实例。 4)请求(request)在web应用中。为每个请求创建一个bean实例。

注解式指定bean作用域

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
@Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.INTERFACES)
复制代码

另外还可以使用xml配置式,这里不做详解

总结

工欲善其事必先利其器,spring作为我们使用最频繁的框架,熟悉其主要功能和原理是很有必有的,不然我们一直摸着石头过河,下面一章我们将要介绍spring另外一个重要功能AOP(面向切面编程)

转载于:https://juejin.im/post/5c4ec59c51882523d320436a

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值