spring框架由6个定义良好的模块分类组成

当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:

  1. 在XML中进行显式配置。
  2. 在Java中进行显式配置。
  3. 隐式的bean发现机制和自动装配。

自动化装配bean

Spring从两个角度来实现自动化装配:
组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
自动装配(autowiring):Spring自动满足bean之间的依赖。

@Component注解:
表明该类会作为组件类,并告知Spring要为这个类创建bean。
Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差别,但是大多数场景中,它们是可以互相替换的。但是@Named的名字起得不好,并没有像@Component那样清楚地表明它是做什么的。

给bean设置ID

1
2
3
4
@Component("lonelyHeartsClub")
public class sgtPeppers implements compactDisc{
···
}

@ComponentScan注解:
能够在Spring中启用组件扫描,默认会扫描与配置类相同的包。组件扫描默认是不启用的,需要显示配置一下。
设置组件扫描的基础包

1
2
3
@Configuration
@ComponentScan(basePackageClasses={CDPlayers.class,DVDPlayers.class})
public class CDPlayerConfig {}

为basePackageClasses属性所设置的数组中包含了类,这些类所在的包将会作为组件扫描的基础包。尽管在样例中,为basePackageClasses设置的是组件类,但是可以考虑在包中创建一个用来进行扫描的空标记接口。

@Autowired注解:
实现bean的自动装配,不仅能够用在构造器上,还能用在属性的Setter方法上,也可以用在类的任何方法上。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。
@Inject和@Autowired差不多。

通过Java代码装配bean

@Bean注解:
声明简单的Bean,@Bean注解会告诉Spring这个方法将返回一个对象,该对象要注册为Spring应用上下文的bean。

1
2
3
4
5
6
7
8
9
10
@Bean
public CompactDisc randomBeatlesCD()
{
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer()
{
return new CDPlayer(randomBeatlesCD());
}

看起来,CompactDisc是通过调用randomBeatlesCD()得到的,但情况并非完全如此。因为randomBeatlesCD()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean。默认情况下,Spring中的bean都是单例的。

通过调用方法来引用bean的方式有点令人困惑,其实还有一种理解起来更为简单的方式,它不管compactDisc是如何实例化的。

1
2
3
4
5
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc)
{
return new CDPlayer(compactDisc);
}

通过XML装配bean

注入bean引用如下

1
2
3
<bean id="cpPlayer" class="com.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>

注入字面量

1
2
3
<bean id="compactDisc" class="com.CDPlayer">
<constructor-arg value="aa" />
</bean>

装配集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id="compactDisc" class="com.CDPlayer">
<constructor-arg>
<list>
<value>aa</value>
<value>bb</value>
</list>
</constructor-arg>
</bean>
<bean id="compactDisc2" class="com.CDPlayer2">
<constructor-arg>
<list>
<ref bean="sgtPeppers" />
<ref bean="revolver" />
</list>
</constructor-arg>
</bean>

元素为属性的Setter方法所提供的功能与元素为构造器所提供的功能是一样的。

导入和混合配置

在JavaConfig配置中,我们可以使用@Import和@ImportResource来拆分JavaConfig类。在XML中,我们可以使用元素来拆分XML配置。

元素只能导入其他的XML配置文件,并没有XML元素能够导入JavaConfig类。但是,有一个你已经熟知的元素能够用来将Java配置导入XML配置文件中:元素。

1
2
<bean class="com.CDConfig" />
<import resource="cdplayer-config.xml" />

处理自动装配的歧义性

@Primary注解将bean设置为首选的bean,能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。
如果你使用XML配置bean的话,同样可以实现相同的功能。元素有一个primary属性用来指定首选的bean:

1
<bean id="iceCream" class="com.IceCream" primary="true" />

设置首选bean的局限性在于@Primary无法将可选方案的范围限定到唯一一个无歧义性的选项中,它只能标识一个优先的可选方案。@Qualifier注解是使用限定符的主要方式,它可以与@Autowired和@Inject协同使用。

bean的作用域

Spring定义了多种作用域,可以基于这些作用域创建bean,包括

  1. 单例(Singleton):在整个应用中,只创建bean的一个实例。
  2. 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
  3. 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  4. 请求(Request):在Web应用中,为每个请求创建一个bean实例。

运行时值注入

有的时候我们希望避免硬编码,想让这些值在运行时再确定。Spring提供了两种在运行时求值的方式:

  1. 属性占位符
  2. Spring表达式语言

如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以使用@Value注解,它的使用方式与@Autowired注解非常相似。

1
2
3
4
5
6
public BlankDisc(@Value("${disc.title}") String title,
@Value("${disc.artist}") String artist)
{
this.title = title;
this.artist = artist;
}

属性占位符需要放在”${ … }”之中;SpEL表达式需要放在”#{ … }”之中。
#{T(System).currentTimeMillis()}
它的最终结果是计算表达式的那一刻,当前时间的毫秒数。T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的currentTimeMillis()方法。
#{sgtPeppers.artist}
SpEL表达式也可以引用其他的bean或其他bean的属性。
#{systemProperties['disc.title']}
通过systemProperties对象引用系统属性。

面向切面编程

Spring使用AspectJ注解来声明通知方法

| 注解 | 通知 |
| —— | ———– |
| @After | 通知方法会在目标方法返回或抛出异常后调用 |
| @AfterReturning | 通知方法会在目标方法返回后调用 |
| @AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
| @Around | 通知方法会将目标方法封装起来|
| @Before | 通知方法会在目标方法调用之前执行 |

创建环绕通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//环绕通知方法
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp)
{
try {
System.out.println("silenceCellPhones");
System.out.println("takeSeats");
jp.proceed();
System.out.println("applause");
} catch (Throwable e) {
System.out.println("demandRefund");
}
}

在这里,@Around注解表明watchPerformance()方法会作为performance()切点的环绕通知。在这个通知中,观众在演出前会将手机调至静音并就坐,演出结束后会鼓掌喝彩,如果演出失败,观众会要求退款。

通过注解引入新功能

1
2
3
4
5
6
7
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="concert.Performance+",
defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}

可以看到,EncoreableIntroducer是一个切面。但是,它与我们之前所创建的切面不同,它并没有提供前置、后置或环绕通知,而是通过@DeclareParents注解,将Encoreable接口引入到Performance bean中。
@DeclareParents注解由三部分组成:

  1. value属性指定了哪种类型的bean要引入该接口。在本例中,也就是所有实现Performance的类型。(标记符后面的加号表示是Performance的所有子类型,而不是Performance本身。)
  2. defaultImpl属性指定了为引入功能提供实现的类。在这里,我们指定的是DefaultEncoreable提供实现。
  3. @DeclareParents注解所标注的静态属性指明了要引入了接口。在这里,我们所引入的是Encoreable接口。

切点表达式如下: