Spring in Acton 4 读书笔记之 AOP 原理及 Spring 对 AOP 的支持

字数1,504 大约花费5分钟

Spring in Action(Spring 实战)的第四章第一节(4.1 What is aspect-oriented programming)讲述了 AOP 原理及 Spring 对 AOP 的支持。有关 AOP 的主要思想和优点,可以参看这篇笔记《Spring in Action》第四版第一章《将 Spring 付诸实践》读书笔记(一)。本文将讲解 AOP 的各个组成部分、使用方式,以及 Spring 对 AOP 的支持。

最近在看美剧《西部世界》,我就结合书中的例子用西部世界的机器人来举例吧。

AOP 各个部分的概念和作用(Defining AOP terminology)

aop 各个部分的概念

如图所示,在程序执行过程中,在其中执行某些特定任务的过程前后,需要执行一些操作。join point 代表要执行额外动作的场景,advice 则定义哪个时间点 (任务执行前还是执行后) 要做哪些额外操作,但 advice 还不知道要关注哪些场景。pointcut 告诉每一个 advice 要执行操作的场景。这样就完整地定义了在哪些场景下、场景执行的哪个时间点下执行哪些动作了。这样听起来有些抽象。举个例子吧。

美剧《西部世界》里的游乐场,给很多机器人编写了脚本,让它们按照具体进行行动。假设其中一个观众机器人去剧场看一场表演。好了,观众到了剧场,在表演开始前,她要关闭手机。并且在表演开始前,她要坐下。在表演结束后,她要鼓掌。如果在表演过程中,表演由于设备故障无法继续,她要喊退票(^_^)。如果我们只把 advice 告诉这位机器人,那么她的知识是,她知道自己有四种任务,在某些事情开始前,她要做关闭手机和坐下两件事,在某些事情开始前,她要在坐下。某些事情结束后,她要鼓掌,如果在某些事情进行过程中,出了异常,她要喊退票。她不知道是某些事情到底是哪些事情。这时候,再告诉她,这些事情都是指一场表演。那她就知道该在什么时候做什么了。advice 和 pointcut 合起来就是 aspect,构成了所有需要知道的信息。

Spring in Acton 4 读书笔记之在运行时注入值

字数1,492 大约花费6分钟

Spring in Action(Spring 实战)第三章第五节(3.5 Runtime value injection)讲述运行时注入值,第三章讲述装配 bean 的一些高级专题,之前的章节一直在讨论用 Spring 注入 bean, 实际上,有时候也需要给一些变量注入值。

比如下面的例子其实就是通过构造函数给 BlankDisc 的 title 属性注入值,这里用的是硬编码的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 代码清单一,BlankDisc 类
public class BlankDisc {

private final String title;
private final String artist;

public BlankDisc(String title, String artist) {
this.title = title;
this.artist = artist;
}

public String getTitle() {
return title;
}

public String getArtist() {
return artist;
}

}
1
2
3
4
5
6
7
// 代码清单二,使用 hardcode 注入 title 和 artist
@Bean
public CompactDisc sgtPeppers() {
return new BlankDisc(
"Sgt. Pepper's Lonely Hearts Club Band",
"The Beatles");
}

硬编码常常会引起问题,Spring 提供了以下两种方式,在运行时给变量注入值:

Spring in Action 4 读书笔记之如何定义 bean 的使用范围

字数959 大约花费4分钟

Spring in Action(Spring 实战)的第三章第四节(3.4 Scoping beans)讲述了 bean 的使用范围。在不同场景下,可以使用不同的使用范围,比如 web 程序一般使用和普通应用程序不同的范围。

bean 的使用范围

默认情况下,Spring 应用上下文中的 bean 都是单例。这意味着无论一个 bean 被注入多少次,总是注入同一个实例。

有时候,要处理互斥的任务,要求 bean 的属性值只被其中一个任务访问和修改,这时候,单例会引起问题。别担心,除了单例外,Spring 还定义了 bean 的其它使用范围。Spring 定义的 bean 使用范围如下:

  • Singleton:单例,整个应用程序使用唯一实例。
  • Prototype:每次注入时都会从 Spring 应用上下文中创建一个新的实例。
  • Session:在 web 应用中,为每个 session 创建一个实例。
  • Request:在 web 应用中,为每个 request 创建一个实例。

可以使用 @Scope 标签标注 @Component 标签(定义准备被自动注入的 bean),也可以使用 @Scope 标签标注 @Bean 标签(显式定义 bean)。

Spring in Action 4 读书笔记之解决自动装配 bean 的二义性问题

字数1,201 大约花费5分钟

Spring in Action(Spring 实战)的第三章第三节(3.3 Addressing ambiguity in autowiring)讲述了如何解决自动装配 bean 的二义性问题。

Spring 的自动注入机制极大地减少装配 bean 的工作量,但在默认情况下,这个机制仅在只有一个 bean 满足条件时才工作。当有多个 bean 满足条件时,这种二义性使 Spring 无法确定使用哪一个 bean,也就无法完成自动装配,并且会抛出 NoUniqueBeanDefinitionException 异常。

比如下面的代码就存在二义性:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

@Component
public class Cake implements Dessert { ... }

@Component
public class Cookies implements Dessert { ... }

@Component
public class IceCream implements Dessert { ... }

因为 Cake,Cookies,IceCream 都实现了 Dessert 接口,Dessert 声明的 bean 存在二义性,所以 setDessert 方法在自动注入 Dessert 类型的组件时,不知道应该注入哪种具体类型的 component, 将会抛出 NoUniqueBeanDefinitionException 异常:

Spring in Action 读书笔记之根据条件创建 bean

字数783 大约花费4分钟

有时候希望只在某些情况下才创建 bean,Spring4 引入的 @Conditional 标签可以做到这一点。

Spring in Action(Spring 实战)的第三章第二节(3.2 Conditional beans)讲述了如何根据条件创建 bean,以下是我阅读这一节的读书笔记。

以下使用显示创建 bean 的方法,举例说明如何根据条件创建 bean(创建 bean 的方法)。

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@PropertySource("classpath:site.properties")
public class MagicConfig {

@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}

}

magicBean()加了 @Conditional 标签,并且指定 MagicExistsCondition 定义判断逻辑。

1
2
3
4
5
6
7
8
9
public class MagicExistsCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}

}

Spring in Acton 4 读书笔记之根据开发环境装配 bean

字数1,481 大约花费7分钟

Spring in Action(Spring 实战)的第三章第一节(3.1 Environments and profiles)讲述了根据开发环境装配 bean,本文是阅读这一节的心得笔记。

开发中遇到的最大挑战之一,是环境的变化。数据库配置、加密算法以及与外部系统的集成等都和开发环境相关。

编译时定义 bean

以数据库为例,在开发的时候,会倾向于使用测试数据,比如在 Spring 的配置类里,可能会像下面这样配置 EmbeddedDatabaseBuilder(),以创建一个 DataSource 的 bean:

1
2
3
4
5
6
7
@Bean(destroyMethod="shutdown")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}

这样,在开发环境中,可以根据需要,在 schema.sql 和 test-data.sql 中增加模拟数据。生产环境不能这么做,而更倾向于使用 JNDI,创建一个 DataSource 的 bean:

《Spring in Acton》第四版第二章《装配 bean》读书笔记

字数1,930 大约花费9分钟

Spring in Action(Spring 实战)的第二章讲述了如何装配 bean。

Spring 中装配的主要方式

  • XML 显式配置
  • Java 显式配置
  • 使用 Java 隐式扫描 bean 并自动装配

三种方式各有好处,作者认为选哪种只是口味问题,但强烈建议使用第三种(使用 Java 隐式扫描 bean 并自动装配),并且建议即使要用显式配置,也尽量使用 Java 配置,因为 Java 更好用(powerful),具有类型安全检查,并且更好重构。只有在 XML 有命名空间可以很方便使用,而 JavaConfig 中没有的时候才使用 XML 显式配置。

使用 Java 隐式扫描 bean 并自动装配

Spring 的这种方案实在是太好用了,应该尽量利用这种机制。这个方案中,我们要做的事情包括两个方面:让 bean 可以被扫描以及在使用 bean 的地方声明注入 bean。这样,Spring 就可以扫描并生成 bean,并且在要使用的地方注入 bean 了。

《Spring in Action》第四版第一章《将 Spring 付诸实践》读书笔记(二)

字数2,080 大约花费8分钟

Spring in Action(Spring 实战)的这一章是对 Spring 进行概述,讲述了 Spring 的项目宗旨,基本原理和关键策略。并且对 Spring 框架进行了概览,比较 Spring4 与之前版本的区别。

这一章的读书笔记分为两篇,上一篇 是对 Spring 的项目宗旨,基本原理和关键策略的读书笔记,这一篇对 Spring 框架进行概览,并比较 Spring4 与之前版本的区别。

Spring 不仅通过依赖注入、面向方面编程和减少样板化重复代码(boilerplate)简化了 Java 开发,而且在核心框架之外,Spring 生态系统将功能扩展到 web services, REST, mobile 和 NoSQL 等领域。

Spring 各模块

Spring4 由 20 个完全不同的模块组成

20 个模块可以分成六组

《Spring in Action》第四版第一章《将 Spring 付诸实践》读书笔记(一)

字数4,614 大约花费18分钟

Spring 的宗旨和关键策略

Spring in Action(Spring 实战)的这一章是对 Spring 进行概述,讲述了 Spring 的项目宗旨,基本原理和关键策略。并且对 Spring 框架进行了概览,比较 Spring4 与之前版本的区别。这一章的读书笔记分为两篇,这一篇是对 Spring 的项目宗旨,基本原理和关键策略的读书笔记, 下一篇 对 Spring 框架进行概览,并比较 Spring4 与之前版本的区别。

Spring 项目的宗旨是简化 Java 开发,主要使用以下四个关键策略:

  • 使用 POJO 进行轻量化和最小侵入的开发
  • 利用依赖注入和面向接口编程,实现松耦合
  • 通过面向方面编程以及约定来实现声明式编程
  • 通过面向方面编程和模板(template)来消除样板化的重复代码(boilerplate code)

笔者按照 POJO、依赖注入、面向方面编程和使用模板四个方面整理了以下笔记。

释放 POJO 的能量

java 三段式中包装类报空指针异常的 bug

字数802 大约花费3分钟

最近在开发中遇到一个莫名其妙的 bug, 一个给 Double 类型赋值的三段式报了空指针异常,从程序字面上完全看不出有什么问题。项目代码,不方便直接贴,我写了下面这段例子进行说明:

直接使用 null 赋值给三段式

1
2
3
4
5
6
7
public static Double test() {
Double b = null;
Double result = b == null
? null
: b.doubleValue();
return result;
}

这段代码能够成功执行,看一下编译后的字节码吧(通过 ItelliJ 查看反编译后的代码)

1
2
3
4
5
public static Double test() {
Object b = null;
Double result = b == null?null:Double.valueOf(((Double)b).doubleValue());
return result;
}

对于三段式中为 double 类型的可选项 b.doubleValue(), 会将 b.doubleValue()进行包装再赋值给 result. 由于另一个可选项是常量 null,所以不会进行拆包。