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

前言

  做为一名java开发人员,咱们接触最先最多的框架确定就是spring了,一次次不停的使用spring框架提供的功能帮助咱们快速开发,然而其中的核心功能IOC(控制反转)和AOP能详细阐明的却很少,咱们知其然,殊不知其因此然,那么下面咱们就一块儿探索一下spring中的IOC吧java

正篇

  • IOC概念

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

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

  • DI(依赖注入)

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

  • 自动化配置Bean

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

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扫描的包bash

#单个包
@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 {

}

复制代码

单元测试session

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的生命周期 这里章节已经见到过了,下面贴出核心代码ide

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(面向切面编程)

相关文章
相关标签/搜索