任何一个成功的应用都是有多个为了实现某一业务目标而相互协作的组件构成的。这些组件必须彼此了解,并且相互协作来完成工作。
上篇文章我们通过一个实例,说明了Spring的重要性。不使用Spring,创建应用之间关联关系的传统方法通常会导致结构严重复杂的代码,这些代码很难被复用也很难进行单元测试。然而在Spring中,对象无需查找或创建与其关联的其他对象。相反的,容器负责把需要相互协作的对象引用赋予各个对象。
创建对象之间协作关系的行为通常称为装配,这也就是依赖注入的本质。
依赖注入与控制反转
学习Spring的人一定都听说过这两个名词,无论是大神还是小白,但是能够大家对他两的认识都是,不理解,不懂。
我告诉大家,其实他们是java中最普通,最常见的两个概念。非常简单。我通过一个简单的例子给大家说明。
“把任务指派给程序员完成”。在java中,一切名词我们都可以看做是对象实例,这句话中,“任务”,“程序员”是名词所以我们考虑设计两个Class:Task和Javaer(java程序员)
package demo;
public class Javaer {
private String name;
public Javaer(String name) {
this.name = name;
}
public void writeCode() {
System.out.println(this.name + " is writing java code");
}
}
package demo;
public class Task() {
private String name;
private Javaer owner;
public Task(String name) {
this.name = name;
this.owner = new Javaer("张三");
}
public void start() {
System.out.println(this.name + " started");
this.owner.writerCode();
}
}
package demo;
public class Demo {
public static void main(String[] args) {
Task task = new Task("任务一");
task.start();
}
}
运行结果:
任务一 started
张三 is writing java code
一些大神一眼就可以发现这种设计的弊端,即代码复用,如果自己用,只是为了完成固定的任务,倒是无所谓,但是如果你要给公司所有的员工使用你的代码,也就是你把程序打成一个类似于类库(jar包)。这么,Task类和程序员"张三"绑定在一起,则大家所有创建的程序员都是“张三”;显然胡扯!
我们自然想到,应该让用户来指派任务负责人。则,
package demo;
public class Task() {
private String name;
private Javaer owner;
public Task(String name) {
this.name = name;
}
public void setOwner(Javaer javaer) {
this.javaer = javaer;
}
public void start() {
System.out.println(this.name + " started");
this.owner.writerCode();
}
}
package demo;
public class Demo {
public static void main(String[] args) {
Task task = new Task("任务一");
Javaer javaer = new Javaer("李四");
task.setOwner(javaer);
task.start();
}
}
这样用户就可以指派特定的java程序员。
我们知道,任务一来程序员,Task类依赖Javaer类,之前,Task类绑定特定的实例,现在这种依赖可以在使用时按需绑定,这就是依赖注入!(set注入和构造函数注入都可以);
当然,程序还有缺陷,难道公司就只要java程序员吗?这种优化也很简单:设计一个Coder接口,声明writerCode()方法,任务的依赖关系改为Coder即可,在具体使用时实例化实现了Coder接口的程序员,在进行注入即可!具体就不展示了!
依赖注入实现了控制反转的思想。
什么是控制反转?简单的说,从主动变为被动就是控制反转。
所有框架的核心就是实现控制反转!
控制反转是一个宽泛的概念,依赖注入只是控制反转的一个例子。
Spring配置的可选方案
spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。但是开发人员需要告诉Spring要创建哪些bean并且知道如何讲这些bean装配在一起。Spring中转配bean的三种机制
- 在XML中进行显示配置
- 在java中进行显示配置
隐式的bean发现机制和自动装配
最好尽可能的使用隐式自动装配机制。显示配置越少越好。当必须要显示配置bean的时候,推荐使用安全类类型比XML更强大的javaConfig。最后,只有当想要的使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。 (自动配置 > 显示java配置 > 显示XML配置)
自动化装配Bean
虽然显示转配技术非常好用,但是在便利性方面最强大的还是Spring的自动化配置。
Spring从两个方面实现了自动化配置:
组件扫描:Spring会自动发现应用中上下文中所创建的bean。
- 自动装配:Spring会自动满足bean之间的依赖。
具体实例应用
作为接口,他定义了CD播放器对盘CD所能进行的操作。
接下来,为了阐述组件扫描和装配,创建CompactDisc类,Spring会发现他,并创建一个bean。然后创建一个CDPlayer类,让Spring发现他,并将CompactDisc bean注入进来。
我们现在创建几个bean,他们代表音响系统中的组件。
首先,建立CD的概念,定义CD的一个接口:
package com.study.application.IOCContainer.SpringConfigBean.soundSystem;
public interface CompactDisc {
void play();
}
那么我们当然得有一个实现接口的类:
创建可被发现的bean
package com.study.application.IOCContainer.SpringConfigBean.soundSystem;
import org.springframework.stereotype.Component;
/*
* 带有@component注解的类,表明该类会作为组件类,并告知Spring要为这个类创建bean.
*/
@Component
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
@Override
public void play() {
System.out.println("Playing " + title + "by " + artist);
}
}
开启扫描组件
package com.study.application.IOCContainer.SpringConfigBean.soundSystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/*
* 组件扫描默认是不启用的,通过使用@ComponentScan注解,启动Spring扫描组件
* 从而命令他去寻找带有@Component注解得类,并为其创建bean
* 如果没有其他配置,@ComponentScan默认会扫描与配置类相同的包
*/
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
当然也可以使用XML来启用组件扫描,通过使用Spring Context命名空间的<context:componetnt>元素。