0 前言

Spring的核心就是提供了一个IoC(Inversion of Control)容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

本本主要展示IoC容器对JavaBean装配,以及依赖的注入的几种方式。

看本文之前请务必学习JAVA基础。

1 传统的实现方式

先来看下面这个例子:

class Person {
private String name;
public Person(){};
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Dance {
public void do_dance(Person person){
System.out.println(person.getName()+" is dancing!");
}
}
class Sing {
public void do_sing(Person person){
System.out.println(person.getName()+" is sing!");
}
}
public class Test {
public static void main(String[] args) {
Person person=new Person("Ming");
new Sing().do_sing(person);
new Dance().do_dance(person);
}
}

Dance与Sing组件的实例化都依赖于Person组件的实例化,通过主动构建的方式实现组件的实例化以及依赖关系的构建。在大型项目中,这种写法有以下缺点:一个组件如果要使用另一个组件,必须先知道如何正确地创建它,管理起来麻烦。

随着更多的组件被引入,组件的依赖层级变深,组织起来太复杂。

有些组件需要销毁以便释放资源,但如果该组件被多个组件共享,如何确保它的使用方都已经全部被销毁?

组件相互依赖,测试困难。如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。

为解决这个问题,提出了IoC模式,在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制,根据操作的不同可以分为下文的几种。

2 基于XML配置的依赖注入

在XML中定义Bean并交代组件之间的依赖关系。

Pom配置如下:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.itranswarp.learnjava
spring-ioc-appcontext
1.0-SNAPSHOT
org.apache.maven.plugins
maven-compiler-plugin
1.6
1.6
jar
UTF-8
UTF-8
11
11
11
5.2.3.RELEASE
org.springframework
spring-context
${spring.version}

代码结构如下:

java根据名称注入Bean java注入bean依赖_ci

对应的代码如下:

Personpackage model;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Dancepackage model;
public class Dance {
Person person;
public void setPerson(Person person){
this.person=person;
}
public void do_dance(){
System.out.println(person.getName()+" is dancing!");
}
}
Singpackage model;
public class Sing {
Person person;
public void setPerson(Person person) {
this.person = person;
}
public void do_sing(){
System.out.println(person.getName()+" is sing!");
}
}

Dance、Sing自己并不会创建Person,而是等待外部通过set方法来注入一个Person:

Bean的定义交给XML<?xml version="1.0" encoding="utf-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
Main中通过context来访问XML文件import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("Application.xml");
Dance dance=context.getBean(Dance.class);
dance.do_dance();
Sing sing=context.getBean(Sing.class);
sing.do_sing();
Person person=context.getBean(Person.class);
person.setName("Tom");
Dance dance_=context.getBean(Dance.class);
dance_.do_dance();
Sing sing_=context.getBean(Sing.class);
sing_.do_sing();
}
}
运行结果Ming is dancing!
Ming is sing!
Tom is dancing!
Tom is sing!

使用XML配置的优点是所有的Bean都能一目了然地列出来,并通过配置注入能直观地看到每个Bean的依赖。它的缺点是写起来非常繁琐,每增加一个组件,就必须把新的Bean配置到XML中。

3 基于Java配置的依赖注入

文件结构如下:

java根据名称注入Bean java注入bean依赖_spring_02

基于Java的配置文件(AppConfig),替代了XML

Personpackage model;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Dancepackage model;
public class Dance {
Person person;
public Dance(Person person) {
this.person=person;
}
public void do_dance(){
System.out.println(person.getName()+" is dancing!");
}
}
Singpackage model;
public class Sing {
Person person=new Person();
public void setPerson(Person person){
this.person=person;
}
public void do_sing(){
System.out.println(person.getName()+" is sing!");
}
}
APPConfigimport model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class APPConfig {
@Bean
public Person person(){
Person person=new Person();
person.setName("Ming");
return person;
}
@Bean //通过构造器的注入依赖
public Dance dance(@Autowired Person person){
return new Dance(person);
}
@Bean //通过set注入依赖
public Sing sing(){
Sing sing= new Sing();
sing.setPerson(person());
return sing;
}
}

这里的APPConfig相当于XML,定义了每个bean,以及各个bean之间的依赖关系。@Bean 用于标记JavaBean对象

@Configuration 用于标记这个类是个配置类,主函数中的AnnotationConfigApplicationContext必须传入一个标注了@Configuration的类名注意:代码中同时展示了两种依赖注入的方式,一种是set方法,一种是带参构造器。

Main方法import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(APPConfig.class);
Dance dance=context.getBean(Dance.class);
dance.do_dance();
Sing sing=context.getBean(Sing.class);
sing.do_sing();
Person person=context.getBean(Person.class);
person.setName("Tom");
Dance dance_=context.getBean(Dance.class);
dance_.do_dance();
Sing sing_=context.getBean(Sing.class);
sing_.do_sing();
}
}
运行结果Ming is dancing!
Ming is sing!
Tom is dancing!
Tom is sing!

从运行结果可以看出,大家使用的是同一个Person对象。

4 基于注解搜索的依赖注入

不再需要单独的XML或者APPConfig来告诉容器哪些是Bean以及他们之间的关系,可以通过注解在程序中定义,让程序自动搜索识别。

Personpackage model;
import org.springframework.stereotype.Component;
@Component
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Dancepackage model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Dance {
@Autowired //替代了set
Person person;
public void do_dance(){
System.out.println(person.getName()+" is dancing!");
}
}
Singpackage model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Sing {
@Autowired
Person person;
public void do_sing(){
System.out.println(person.getName()+" is sing!");
}

}@Component注解就相当于定义了一个Bean,它有一个可选的名称,默认是mailService,即小写开头的类名。

@Autowired就相当于把指定类型的Bean注入到指定的字段中。@Autowired大幅简化了注入,因为它不但可以写在set()方法上,还可以直接写在字段上,甚至可以写在构造方法中
Main的代码如下:import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = "model")
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
Dance dance=context.getBean(Dance.class);
dance.do_dance();
Sing sing=context.getBean(Sing.class);
sing.do_sing();
Person person=context.getBean(Person.class);
person.setName("Tom");
Dance dance_=context.getBean(Dance.class);
dance_.do_dance();
Sing sing_=context.getBean(Sing.class);
sing_.do_sing();
}
}
运行结果:null is dancing!
null is sing!
Tom is dancing!

Tom is sing!注意:由于是自动装配,person的name为null。

AppConfig标注了@ComponentScan,它告诉容器,自动搜索当前类所在的包以及子包,把所有标注为@Component的Bean自动创建出来,并根据@Autowired进行装配。直接在Main中通过调用@ComponentScan(value = "model")来实现bean的自动搜索与装配,避免了配置文件的书写。

使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:每个Bean被标注为@Component并正确使用@Autowired注入;

配置类被标注为@Configuration和@ComponentScan;

所有Bean均在指定包以及子包内。

这个方法虽然方便,但不适用于第三方包加入时。

比如当需要调用某个第三方包里的某个方法,该方法在第三方包中没有用@Component`注解,如何告诉IoC容器创建并配置?

或者换个说法,如何创建并配置一个第三方Bean?

待续。。。

5 参考文献