Author 罗先生
本文是IOC系列的第一篇。

1.what is IOC

IOC和DI的联系

IOC是模板,而DI只是一种实现方式

IOC的基本概念

控制反转(Inversion of control, 缩写为IOC),是面向对象编程中的一种设计原则,用来降低代码之间的耦合度。 其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。 还有一种不常见的方式叫做依赖查找(Depencency Lookup)。

2.依赖查找与依赖注入

什么叫依赖

要弄清楚什么叫依赖查找和注入,首先明白什么叫依赖,直接举个Java代码的例子

public class Test{
  private OrderDao orderDao;   //这就是依赖
}

依赖查找

举个例子,Java当中有个叫JNDI的东西,JNDI是这么做的

Connection conn=null; 
try { 
	Context ctx = new InitialContext(); 
	Object datasourceRef = ctx.lookup("java:MySqlDS");  //依赖查找
	//引用数据源 
	DataSource ds = (Datasource) datasourceRef; 
	conn = ds.getConnection(); 
	...... 
	c.close(); 
} catch(Exception e) { 
	e.printStackTrace(); 
} finally { 
	if(conn!=null) { 
		try { 
			conn.close(); 
		} catch(SQLException e) {} 
	} 
}

有个叫**ctx.lookcp()**的东西,这就叫做依赖查找,使用这模式的好处是这样数据源的配置和应用就解耦了,当数据源里面比如说jdbc地址用户名密码改变后,源代码不用改变

依赖注入

而依赖注入更多的就倾向于自动注入。就是你提供需要注入的位置,容器自己给你注入相应的对象

##3.为什么要使用SpringIOC
我们不用SpringIOC是什么样的情况呢?
比如说如下代码:
最普通的做法:

public void test(){
    OrderService service = new OrderServiceImpl();
    orderService.getOrder();
    System.out.println("test");
}

这样做有什么坏处呢:

  1. 当OrderService有很多实现类,而我现在引用的地方要换成OrderServiceImpl1,会出现什么情况呢?需要我把所有引用OrderServiceImpl的地方改成OrderServiceImpl1
  2. 当我需要对OrderService进行代理(事务,切面等)的时候,那么一样的,需要修改所有的实现类,把他出现的地方全改成代理类
    以上都叫做耦合
    改进后的做法:
private OrderService orderSerivce;

public void setOrderSerivce(OrderService orderSerivce) {
   this.orderSerivce = orderSerivce;
}

public void test(){
    orderService.getOrder();
    System.out.println("test");
}

应该面向抽象编程,用父类声明,然后用一个set方法,把真实的实现类set进来,然后细想这个set是不是也要外部new一个出来,这样要改实现类的时候不是还会出现上面的问题吗

在改进后的做法(用容器管理)

@Resource
private OrderService orderSerivce;

public void setOrderSerivce(OrderService orderSerivce) {
   this.orderSerivce = orderSerivce;
}

public void test(){
    orderService.getOrder();
    System.out.println("test");
}

解决了上面的问题,new的动作由容器执行,我们只需要选择注入什么样的类到容器中,可以是是多种实现类或者代理类,最后我们值需要在使用的地方加个声明来表示我们需要注入,当需要改动的时候(其实很少,一般在真实业务开发中一个类就一个实现类)也只需要改变一下声明,声明需要注入的另一个实现类,而代理类也同样如此
为什么使用IOC总结

  • SpringIOC容器帮我们维护了类之间的依赖关系
  • SpringIOC可以为我们提供增强类(AOP代理事务),插件式,可插拔
  • SpringIOC提供了很多Bean的拓展点(BeanPostProcessor,BeanFactoryProcessor,FactoryBean)等

4.Spring实现IOC的思路和方法

Spring实现IOC的思路是提供一些配置信息(XML)用来描述类之间的依赖关系,然后由容器去解析配置信息(XML),继而维护好类之间的依赖关系,这里有个前提是对象之间的关系必须在类中已经定义好,比如A.class中有一个B.class的属性,那么我们可以理解为A依赖了B
看完,上面一段话会不会以下一个疑问呢?
既然对象之间的关系已经在类中定义好,为什么我们还要提供配置信息去描述类之间的依赖关系呢?自动装配就派上用场了
###自动装配
其实就是Spring提供的自动填充依赖的一种方式,可以让容器中的Bean自动注入到需要依赖的位置,值得注意的是我们最常用的形式@Autowired放在属性上,这不叫自动装配,这叫显示声明注入,即按需注入,而不是所有依赖都自动注入
分以下四种[1]

Mode Explanation
no (Default) 默认的,不自动装配。大型项目,更改默认的自动装配模型是不推荐的,因为Spring认为自己明确的指定可以提供更好的控制和看起来更清晰。
byName byName注入,值得注意的是byName的形式他是根据set方法来的,即如果有一个setMaster方法,那么他是去找Spring中有没有一个beanName=master的bean,如果有执行此方法,并把对应的bena赋值给这个方法的参数
byType byType注入,根据class类型进行注入,如果容器中有两个某种类型的bean则报错,但是如果没有此类型的bean,是不会报错的,而且不做任何事(即不进行注入)
constructor constructor注入,与byType是类似的,只是应用在构造方法的参数上,按参数类型进行匹配,如果容器中找不到则报错

配置可以在标签里,属性叫default-autowire
也可以在单个里,属性叫default-autowire
用注解形式的配置暂时还没找到,当然如果能拿Spring上下文对象也可以设置

自动装配的优点:

  • 自动装配可以大大减少指定属性或构造函数参数的数量。
  • 而如果在XML当中,可以大大减少配置量,当增加新的依赖时不需要修改XML文件配置

自动装配的缺点

  • 显示依赖一直都是自动装配的,那么就不能生成简单的属性,比如String,Class(以及这些简单属性的数组等)
  • 自动装配是没有自己注入清晰的,Spring也不能保证自己注入的一定是正确的,虽然Spring在努力避免。。
  • 自动注入在注入单值的时候需要注意,是否存在多个类型相同的bean,这样就会产生注入错误

Spring建议怎么使用自动装配

  • 第一条就是放弃所有自动装配。。(说明Spring自己很不建议使用此功能,实际业务项目也确实不用此功能)
  • 放弃部分自动装配。。。。(设置autowire-candidate为false)
  • 设置Primary,当出现多个时优选Primary的
  • 实现更细粒度的注解配置,如多个type时,用qualify来限制名字

5.spring编程的风格

schemal-based-------xml
annotation-based-----annotation @Resource等
java-based----java Configuration @Configuration

可以联合使用

6.DI的两种方法

Spring5官网文档就提供了两种方法

Constructor-based Dependency Injection

public class SimpleMovieLister { 

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}


<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

Setter-based Dependency Injection

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

上面的例子都是Spring官方文档的例子,还是比较容易看懂的。

两种注入方法的比较

Spring官方文档同样有说,我就以我拙劣的英语大致翻译一下,大致如下意思:
我们可以混合使用两种注入方式,而一种比较好的方式是对于强制性的依赖可以使用构造器的方式注入,而对于可选性的依赖则以set方式注入。set方式也可以是必须注入的,只需要加上@Required注入。Spring建议使用构造器注入时要用有参构造方法

Spring开发团队主张使用构造器方式注入,因为他们认为使用构造器注入能让程序的各个组件成为不可变对象且不为空,因为在使用的时候,都是已经初始化好的对象。

Setter注入应该主要用于可选的依赖,这些依赖可以在类中分配默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类对象能够在以后进行重新配置或重新注入。

对于特定类可能只能有某种注入方式。比如第三方的源代码,他们没有提供setter方法,那么构造方法可能就是唯一可用的DI形式

总结:
Spring更为推荐的方式是对于强制性的依赖可以使用构造器的方式注入,而对于可选性的依赖则以set方式注入。


参考资料:
[1] https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-autowire "Spring 官方"