0x00 参考

本笔记直接参考或引自如下链接文章:

https://www.w3cschool.cn/wkspring/

http://c.biancheng.net/spring/

0x01 Spring Bean装配

Bean的装配可以理解为依赖关系注入,Bean的装配方式也就是Bean的依赖注入方式。Spring容器支持多种形式的Bean的装配方式,如基于XML的Bean装配、基于Annotation的Bean装配和自动装配等。

0x02 基于XML装配Bean

Spring基于XML的装配通常采用两种实现方式,即setter方法注入(Setter Injection)和构造函数注入(Constructor Injection),这两种方式在前面也讲过。

在Spring实例化Bean的过程中,首先会调用默认的构造方法实例化Bean对象,然后通过Java的反射机制调用setXxx()方法进行属性的注入。因此,setter方法注入要求一个Bean的对应类必须满足以下两点要求:

  • 必须提供一个默认的无参构造方法;
  • 必须为需要注入的属性提供对应的setter方法;

使用setter方法注入时,在Spring配置文件中,需要使用<bean>元素的子元素<property>元素为每个属性注入值。而使用构造函数注入时,在配置文件中,主要使用<constructor-arg>标签定义构造方法的参数,可以使用其value属性(或子元素)设置该参数的值。

示例可参考前面的Spring DI文章:Spring基础篇之DI(依赖注入)

0x03 基于Annotation装配Bean

在Spring中,尽管使用XML配置文件可以实现Bean的装配工作,但如果应用中Bean的数量较多,会导致XML配置文件过于臃肿,从而给维护和升级带来一定的困难。

Java从JDK 5.0以后,提供了Annotation(注解)功能,Spring也提供了对Annotation技术的全面支持。Spring3中定义了一系列的Annotation(注解),常用的注解如下:

@Required

@Required注释应用于bean属性的setter方法,它表明受影响的bean属性在配置时必须放在XML配置文件中,否则容器就会抛出一个BeanInitializationException异常。

@Component

可以使用此注解描述Spring中的Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。

@Repository

用于将数据访问层(DAO层)的类标识为Spring中的Bean,其功能与@Component 相同。

@Service

通常作用在业务层(Service 层),用于将业务层的类标识为Spring中的Bean,其功能与@Component相同。

@Controller

通常作用在控制层(如Struts2的Action),用于将控制层的类标识为Spring中的Bean,其功能与@Component相同。

@Autowired

用于对Bean的属性变量、属性的Set方法及构造函数进行标注,配合对应的注解处理器完成Bean的自动配置工作。默认按照Bean的类型进行装配。

@Resource

其作用与Autowired一样。其区别在于@Autowired默认按照Bean类型装配,而@Resource默认按照Bean实例名称进行装配。

@Resource中有两个重要属性:name 和 type。如果都不指定,则先按Bean实例名称装配,如果不能匹配,则再按照Bean类型进行装配;如果都无法匹配,则抛出NoSuchBeanDefinitionException异常。

@Qualifier

与@Autowired注解配合使用,会将默认的按Bean类型装配修改为按Bean的实例名称装配,Bean的实例名称由@Qualifier注解的参数指定。

@PostConstruct和@PreDestroy

为了定义一个bean的安装和卸载,我们使用 init-methoddestroy-method 参数简单的声明一下 。init-method属性指定了一个方法,该方法在bean的实例化阶段会立即被调用。同样地,destroy-method指定了一个方法,该方法只在一个bean从容器中删除之前被调用。

作用和前面说到的Bean后置处理器一样,只不过换了注释的形式来实现。

@Configuration和@Bean

带有@Configuration的注解类表示这个类可以使用Spring IoC容器作为bean定义的来源。@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册为在Spring应用程序上下文中的bean。

最简单可行的@Configuration类如下所示:

1
2
3
4
5
6
7
8
9
package com.tutorialspoint;
import org.springframework.context.annotation.*;
@Configuration
public class HelloWorldConfig {
@Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}

上面的代码将等同于下面的 XML 配置:

1
2
3
<beans>
<bean id="helloWorld" class="com.mi1k7ea.HelloWorld" />
</beans>

在这里,带有@Bean注解的方法名称作为bean的ID,它创建并返回实际的bean。你的配置类可以声明多个@Bean。一旦定义了配置类,你就可以使用AnnotationConfigApplicationContext来加载并把它们提供给Spring容器,如下所示:

1
2
3
4
5
6
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfig.class);
HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
helloWorld.setMessage("Hello World!");
helloWorld.getMessage();
}

你可以加载各种配置类,如下所示:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}

Demo

PersonDao.java,DAO层即接口类:

1
2
3
4
5
package com.mi1k7ea;

public interface PersonDao {
void add();
}

PersonDaoImpl.java,DAO层接口类的实现类,首先使用@Repository注解将PersonDaoImpl类标识为Spring中的Bean,其写法相当于配置文件中<bean id="personDao" class="com.mi1k7ea.PersonDaoImpl"/>的书写。然后在add()方法中输出一句话,用于验证是否成功调用了该方法:

1
2
3
4
5
6
7
8
9
10
11
package com.mi1k7ea;

import org.springframework.stereotype.Repository;

@Repository("personDao")
public class PersonDaoImpl implements PersonDao {
@Override
public void add() {
System.out.println("Dao层的add()方法执行了...");
}
}

PersonService.java,Service层接口类:

1
2
3
4
5
package com.mi1k7ea;

public interface PersonService {
void add();
}

PersonServiceImpl.java,Service层接口类的实现类, 首先使用@Service注解将PersonServiceImpl类标识为Spring中的Bean,其写法相当于配置文件中<bean id="personService" class="com.mi1k7ea.PersonServiceImpl"/>的书写。然后使用@Resource注解标注在属性personDao上(也可标注在personDao的setPersonDao()方法上),这相当于配置文件中<property name="personDao" ref="personDao"/>的写法。最后在该类的add()方法中调用personDao中的add()方法,并输出一句话 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.mi1k7ea;

import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service("personService")
public class PersonServiceImpl implements PersonService {
@Resource(name = "personDao")
private PersonDao personDao;
public PersonDao getPersonDao() {
return personDao;
}
@Override
public void add() {
personDao.add();// 调用personDao中的add()方法
System.out.println("Service层的add()方法执行了...");
}
}

PersonAction.java,首先使用@Controller注解标注PersonAction类,其写法相当于在配置文件中编写<bean id="personAction" class="com.mi1k7ea.PersonAction"/>。然后使用了@Resource注解标注在personService上,这相当于在配置文件内编写<property name="personService" ref="personService"/>。最后在其add()方法中调用了personService中的add()方法,并输出一句话 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.mi1k7ea;

import org.springframework.stereotype.Controller;

import javax.annotation.Resource;

@Controller("personAction")
public class PersonAction {
@Resource(name = "personService")
private PersonService personService;
public PersonService getPersonService() {
return personService;
}
public void add() {
personService.add(); // 调用personService中的add()方法
System.out.println("Action层的add()方法执行了...");
}
}

MainApp.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mi1k7ea;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
// 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Beans.xml");
// 获得personAction实例
PersonAction personAction = (PersonAction) applicationContext.getBean("personAction");
// 调用personAction中的add()方法
personAction.add();
}
}

Beans.xml,使用context命名空间的component-scan元素进行注解的扫描,其base-package属性用于通知Spring所需要扫描的目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!--使用context命名空间,通知spring扫描指定目录,进行注解的解析-->
<context:component-scan base-package="com.mi1k7ea"/>

</beans>

运行输出:

0x04 自动装配Bean

自动装配就是指Spring容器可以自动装配(autowire)相互协作的Bean之间的关联关系,将一个Bean注入其他Bean的Property中。

要使用自动装配,就需要配置<bean>元素的autowire属性。autowire 属性有以下五个值:

名称 说明
byName 根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。
byType 根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。
constructor 根据构造方法的参数的数据类型,进行 byType 模式的自动装配。
autodetect 如果发现默认的构造方法,则用 constructor 模式,否则用 byType 模式。
no 默认情况下,不使用自动装配,Bean 依赖必须通过 ref 元素定义。

byName

这种模式由属性名称指定自动装配。Spring容器看作beans,在XML配置文件中beans的auto-wire属性设置为byName。然后,它尝试将它的属性与配置文件中定义为相同名称的beans进行匹配和连接。如果找到匹配项,它将注入这些beans,否则,它将抛出异常。

例如,在配置文件中,如果一个bean定义设置为自动装配byName,并且它包含spellChecker属性(即它有一个 setSpellChecker(…) 方法),那么Spring就会查找定义名为spellChecker的bean,并且用它来设置这个属性。你仍然可以使用<property>标签连接其余的属性。

Demo

SpellChecker.java不变。

TextEditor.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.mi1k7ea;

public class TextEditor {
private SpellChecker spellChecker;
private String name;
public void setSpellChecker( SpellChecker spellChecker ){
this.spellChecker = spellChecker;
}
public SpellChecker getSpellChecker() {
return spellChecker;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}

MainApp.java:

1
2
3
4
5
6
7
8
9
10
11
12
package com.mi1k7ea;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
TextEditor te = (TextEditor) context.getBean("textEditor");
te.spellCheck();
}
}

Beans.xml,给textEditor这个Bean添加值为byName的autowire属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.mi1k7ea.TextEditor" autowire="byName">
<property name="name" value="mi1k7ea" />
</bean>

<!-- Definition for spellChecker bean -->
<bean id="spellChecker" class="com.mi1k7ea.SpellChecker" />

</beans>

这里我们对比下,不应用自动装配的Beans.xml,需要通过bean元素的子元素property的ref属性来设置装配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.mi1k7ea.TextEditor">
<property name="spellChecker" ref="spellChecker" />
<property name="name" value="Generic Text Editor" />
</bean>

<!-- Definition for spellChecker bean -->
<bean id="spellChecker" class="com.mi1k7ea.SpellChecker" />

</beans>

运行输出:

byType

这种模式由属性类型指定自动装配。Spring容器看作beans,在XML配置文件中beans的autowire属性设置为byType。然后,如果它的type恰好与配置文件中beans名称中的一个相匹配,它将尝试匹配和连接它的属性。如果找到匹配项,它将注入这些beans,否则,它将抛出异常。

例如,在配置文件中,如果一个bean定义设置为自动装配byType,并且它包含SpellChecker类型的spellChecker属性,那么Spring就会查找定义名为SpellChecker的bean,并且用它来设置这个属性。你仍然可以使用<property>标签连接其余属性。

Demo

MainApp.java,SpellChecker.java,TextEditor.java均不变。

Beans.xml,将autowire属性值改为byType:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.mi1k7ea.TextEditor" autowire="byType">
<property name="name" value="mi1k7ea" />
</bean>

<!-- Definition for spellChecker bean -->
<bean id="spellChecker" class="com.mi1k7ea.SpellChecker" />

</beans>

运行输出同上。

constructor

这种模式与byType非常相似,但它应用于构造器参数。Spring容器看作beans,在XML配置文件中beans的autowire属性设置为constructor。然后,它尝试把它的构造函数的参数与配置文件中beans名称中的一个进行匹配和连线。如果找到匹配项,它会注入这些bean,否则,它会抛出异常。

例如,在配置文件中,如果一个bean定义设置为通过构造函数自动装配,而且它有一个带有SpellChecker类型的参数之一的构造函数,那么Spring就会查找定义名为SpellChecker的bean,并用它来设置构造函数的参数。你仍然可以使用<constructor-arg>标签连接其余属性。

Demo

MainApp.java,SpellChecker.java均不变。

TextEditor.java,添加一个有参构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.mi1k7ea;

public class TextEditor {
private SpellChecker spellChecker;
private String name;
public TextEditor( SpellChecker spellChecker, String name ) {
this.spellChecker = spellChecker;
this.name = name;
}
public SpellChecker getSpellChecker() {
return spellChecker;
}
public String getName() {
return name;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}

Beans.xml,将autowire属性值改为constructor,其中另一个参数name通过constructor-arg子元素即构造函数的形式进行装配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.mi1k7ea.TextEditor" autowire="constructor">
<constructor-arg value="Generic Text Editor"/>
</bean>

<!-- Definition for spellChecker bean -->
<bean id="SpellChecker" class="com.mi1k7ea.SpellChecker" />

</beans>

运行输出同上。

TextEditor.java均不变。