《Spring 实战》读书笔记一
作者: dkvirus 发表于: 2018-10-21 10:46:50 最近更新: 2018-11-04 22:17:41

阅读《Spring 实战》第一部分笔记:DI、AOP、Bean。

1. 概念

EJB(重量级企业级JAVA技术,到底有多重?)、Java2企业版(Java2 Enterprise Edition,J2EE)、POJO(Plain Old Java object 老式JAVA对象) 这些词貌似是上古时代开发 JAVA 用到的,Spring 的出现替代了这些东东,因为更加简单。没经历过那个时代,也不知道到底简单在哪。

在 Spring 官网上可以看到目前 Spring 及其衍生框架提供几乎所有关于 Java 应用的领域,但 Spring 声称仅仅简化 Java 开发,没有做更多复杂的事,书里总结了四点,dk 表示每个字都能看懂,但连在一起目前还看不太懂。

  • 基于 POJO 轻量级和最小侵入性编程;
  • 通过依赖注入和面向接口实现松耦合;
  • 基于切面和惯例进行声明式编程;
  • 通过切面和模板减少样板式代码。

讲到Spring,面试时候最喜欢问两个概念:依赖注入(DI,Dependecncy Injection)和面向切面编程(AOP,Aspect-Oriented Programming)。

2. 依赖注入(DI)

书里描述依赖注入的例子让人抓狂,示例代码用 js 描述如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 不使用依赖注入的写法如下:只能执行【执行拯救公主的探险】,代码写死了
function embarkOnQuest () {
var quest = new RescueDamselQuest(); // 拯救公主的探险
quest.embark;
}

// 使用依赖注入写法如下:可以执行任何探险,因为探险在方法外部定义
var quest = new RescueDamselQuest(); // 拯救公主的探险
// var quest = new SlayDragonQuest(); // 杀死恶龙的探险
function embarkOnQuest (quest) {
quest.embark;
}

真是蠢到不行的一个例子。将冒险作为参数传入,在 spring 里叫做构造注入,难道不应该本来就这么写的吗??

Angular 从一开始也号称有依赖注入的概念,Java 的依赖注入搞不懂可以换个思路研究研究 Angular 也是一个不错的方向。

3. 实例 Bean

Bean 在 Spring 里也是经常看到的一个词儿,说实话我也不知道我有没有理解这个概念,压根没有个清晰的划分,这是什么?总感觉跟路边算命先生一样说一些适用于每个人但又不那么具体的话。关于创建 Bean 我的理解就是不需要手动 new 对象,Spring 做这个过程,Bean 就是指对象。

1
2
3
4
5
var quest = new RescueDamselQuest(); // 拯救公主的探险,这里使用 new 关键字,quest 是个实例对象
// var quest; // 这样写 quest 仅仅是个变量,并不是实例对象
function embarkOnQuest (quest) {
quest.embark;
}

使用 Spring 里的 Bean 干了下面两件事:

1
2
3
function embarkOnQuest (quest) { // 你不需要手动用 new 创建实例对象
quest.embark;
}

spring 帮你做了 new 这个动作,这个过程就是所谓的【装配 Bean】。dk 是感觉不到哪里有简便,还是前一种写法看着更符合常理一些。

4. 面向切面编程(AOP)

AOP 还挺容易理解的,将公共部分抽离出来单独管理。吟游诗人与冒险家的例子也比较合适,实际工作中使用 aop 记录日志也是比较常见的场景。

5. 模板

Java 自带连接数据库使用的是 JDBC,代码写起来一大坨,还很多都是重复的。Spring 封装了 JDBC,提供 JDBCTemplate 方便使用。

6. 容器和应用上下文

1
People jack = new People();

其中,People 是类,jack 是一个对象引用。要得到一个对象引用必须要用 new 关键字进行创建,使用 Spring 的依赖注入并没有显示的使用 new 创建就获得了对象引用,这并不是说可以跳过 new 关键词创建直接就得到对象引用,而是 new 这个过程在 Spring 内部隐式的完成。Spring 内部创建了对象,得给外部使用才有价值啊!因此对外提供了一个接口 —— 应用上下文。通过上下文可以直接获取 Spring 内部创建好的对象引用。

应用上下文创建有多种方式,记录一下混个眼熟。

  • AnnotationConfigApplicationContext 通过 Java 配置类获取 Spring 上下文;
  • AnnotationConfigWebApplicationContext 通过 Java 配置类获取 Spring Web 上下文;
  • ClassPathXmlApplicationContext 通过 xml 文件获取 Spring 上下文;
  • FileSystemXmlApplicationContext 通过多个 xml 文件获取 Spring 上下文;
  • XmlWebApplicationContext 通过xml文件获取 Spring Web 上下文。

7. 装配 Bean

Spring 容器负责创建应用程序中的 Bean。

关于 Bean,dk 目前理解就是创建的对象。Spring 内部负责创建对象和处理对象之间的依赖关系,怎么处理,由开发人员告诉它规则。开发人员有三种方式告诉Spring处理规则。

  • XML 文件中写规则;
  • JAVA 配置文件中写规则;
  • 隐式 bean,也就是注解,这个不用说最方便;
  • 补充一下:以上三种方式可以混合使用,并不是只能使用其中的一种哦。

8. 方式一:注解管理 Bean

1)@Component

1
2
@Component
public class Xxx {}

有 @Component 注解修饰的类,会告知 Spring 要为这个类创建 Bean。单纯这样整,是不会创建 Bean,因为 @Component 注解没有被「激活」。

2)@ComponentScan

1
2
3
@Configuration
@ComponentScan
public class XyyConfig {}

  • Configuration 表明这个类是一个配置类,包含 Spring 上下文中如何创建 bean 的细节;
  • @ComponentScan 会「激活」与配置类相同包里的 @Component 注解功能,自动创建 Bean。dk 不太理解,为啥还要多此一举?

3)测试时用到的注解

1
2
3
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=XyyConfig.class)
public class Xxx {}

  • @RunWith 注解接收 Spring 的 SpringJunit4ClssRunner 类作为参数,目的是自动创建 Spring 应用上下文。
  • @ContextConfiguration 告诉 Spring 加载 XyyConfig 类中的配置,因为配置类中有 @ComponentScan 修饰,因此会自动激活 @Component 注解功能,自动创建 Bean。

4)@Autowired

1
2
3
4
public class Xxx {
@Autowired
private Xyy xyy;
}

上面代码中,加上 @Autowired 就相当于是使用 new 创建了一个对象,写法和下面的代码意思一样。这个依赖注入就有那么一丢丢感觉了。

1
2
3
public class Xxx {
private Xyy xyy = new Xyy();
}

@Autowired 注解是 Spring 提供的,Java 原生也提供了一个依赖注入的注解 @Inject,二者功能相同。

5)Bean 命名

Spring 要管理很多 Bean,怎么区分不同的 Bean 呢?答案是给每个 Bean 起个名字,默认名字是用 @Component 注解的类首字母小写,比如上面代码 Bean 的名字就是 xxx。保不准那么多包有命名相同的类,那默认情况下 Spring 容器内 Bean 命名也就冲突了,就会报错,可以自定义 Bean 的名字,如下。

1
2
@Component("xxx2")
public class Xxx {}

@Component 是 Spring 提供的注解,Java 原生也提供了功能相同的注解 @Named,下面代码功能与上面的一样。

1
2
@Named("xxx2")
public class Xxx {}

6)指定扫描包范围

默认情况下,@ComponentScan 注解只对配置类所在的包起作用,可以定义在其它包中也起作用。

  • @ComponentScan(“com.dkvirus.blog”)
  • @ComponentScan(basePackages=”com.dkvirus.blog”)

上面两种注解功能是一样的,指定具体包起作用,还可以同时指定多个包,写法如下。

1
@ComponentScan(basePackages={ "com.dkvirus.blog", "com.dkvirus.profile" })

上面那种指定多个包有个弊端,就是重构代码的时候,包名可能会变更,不能每次重构代码还回过头改一下包名,下面这种方法可以使用类所在的包作为组件扫描的基础包,而这个类可以定义一个空类(没有内容,仅仅标识用),这样即便重构代码,也不会动没有任何内容的标识类吧。

1
@ComponentScan(backPackageClasses={ Xxx.class, Xyy.class })

9. 方式二:Java 配置类管理 Bean

适用场景:例如要将第三方库中的组件装配到 Spring 容器中,这就没法使用注解方式了,因为那是别人的代码,没法在那上面加注解,但是知道第三方库组件的路径就可以通过 Java 配置类管理 Bean。

1
2
3
4
5
6
7
@Configuration
public class XxxConfig {
@Bean
public People setPeople () {
return new People()
}
}

在方法上添加 @Bean 注解,方法最终返回一个对象,这里注意 @Bean 注解一定要用在Java配置类中,怎么确认Java配置类,有 @Configuration 注解就是的类即是 Java 配置类。Spring 看到这个,会自动在 Spring 容器中创建 Bean,bean 的名字就死方法名,这里是 setPeople。之前通过 @Component 注解创建 Bean,bean 的名字是类名首字母小写,这里可以比较记忆。

1
@Bean(name="otherName")

自定义 bean 名称代码如下所示。

10. 方式三:Xml 管理 Bean

2015 年那会培训班里教 Spring,主要还是 xml 配置,这才几年时间,注解配置几乎取代 xml 配置,技术的发展日新月异啊。

1)声明 bean

1
<bean id="beanname1" class="com.dkvirus.blog.Xxx" />
  • id 定义 bean 的名字;
  • class 要创建对象的那个类。

2)构造器注入 bean

一直不太理解,为啥还有构造器注入这个东东,虽然不理解,还是记一下吧,看到能认识就好。

1
2
3
4
5
<bean id="xyy" class="com.dkvirus.blog.Xyy" />

<bean id="xxx" class="com.dkvirus.blog.Xxx">
<constructor-arg ref="xyy">
</bean>

3)字符串属性注入

1
2
3
4
5
6
7
public class Xxx {
private String title;

public Xxx (String title) {
this.title = title;
}
}
1
2
3
<bean id="xxx" class="com.dkvirus.blog.Xxx">
<constructor-arg value="xxxxxx" />
</bean>

4)集合属性注入

1
2
3
4
5
6
7
public class Xxx {
private List<String> list;

public Xxx (List<String> list) {
this.list = list;
}
}

下面的代码中,也可以用 <ref> 元素替代 <value> 元素。

1
2
3
4
5
6
7
8
9
<bean id="xxx" class="com.dkvirus.blog.Xxx">
<constructor-arg>
<list>
<value>xxx</value>
<value>yyy</value>
<value>zzz</value>
</list>
</constructor-arg>
</bean>

5)启用扫描 bean

1
<context:component-scan base-package="com.dkvirus.blog" />

启动组件扫描,激活 com.dkvirus.blog 包下所有 @Component 注解功能。

11. 装配 Bean 之 @Profile

实际开发中,开发环境需要创建 Config1.java 对应的 Bean,测试环境需要创建 Config2.java 对应的 Bean,生产环境需要创建 Config3.java 对应的 Bean。传统做法是在不同开发阶段,手动修改代码,指定创建哪一个 Bean。无疑,这是很麻烦的事儿,Spring 提供 profile 机制可以优雅的解决该问题。

@Profile 注解可以用在类上,也可以用在方法上,下面示例代码为用在方法。根据当前环境的不同,Spring 会自动创建有相应注解的 Bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class XxxConfig {

@Bean
@Profile("dev")
public Xxx getDevConfig () { }

@Bean
@Profile("test")
public Xxx getTestConfig () { }

@Bean
@Profile("prod")
public Xxx getProdConfig () { }

}

Spring 怎么知道当前激活哪一种环境?通过 @ActiveProfiles 注解确定,下面注解表明默认创建注解了 dev 的 Bean。

1
2
@ActiveProfiles("dev")
public class Xxx { }

12. 装配 Bean 之 @Conditional

前面的 profile 创建 Bean 已经有点条件化创建 Bean 的影子。@Conditional 注解提供更加灵活的条件化创建 Bean,该注解与 @Bean 注解配合使用。

1
2
3
4
5
6
7
8
9
@Configuration
public class Xxx {

@Bean
@Conditional(XyyCondition.class)
public Xyy xyyBean () {
return new Xyy();
}
}

可以看到,上面的例子含义为:当 XyyCondition(@Conditional注解里面的参数)这个类满足条件时才会创建 XyyBean。XyyCondition.java 名称随意起,内部实现 Condition 接口并实现 matches 方法,当该方法返回 true 时创建 Bean,返回 false 时不创建。因为是用 Java 代码做判断,因此非常灵活。

1
2
3
4
5
6
7
public XyyCondition implements Condition {

publi boolean matches () {
// ......
return true;
}
}

13. 装配 Bean 之 @Primary、@Qualifier

Spring 容器管理 Bean,每个 bean 都有个名字,实际编码过程会出现 bean 命名相同,Spring 无法确定要创建哪一个 Bean 而导致的报错问题。前面有介绍到通过 @Component(“xxx”) 命名 bean 解决 bean 歧义问题,这里再介绍两种种方法处理 bean 冲突问题,权当了解,看到能认识。

1)@Primary

首选注解,以下面代码为例,当有其它 Bean 也叫做 xxx 时,由于下面代码类拥有 @Primary (首选,优先级高)注解,虽然 bean 名称相同,但 Spring 会毫不犹豫的挑选出下面这个类创建 Bean,不会报错。

1
2
3
@Component 
@Primary
public class Xxx { }

2)@Qualifier

@Primary 是首选的意思,但保不准多个命名相同的 Bean 都添加了该注解,那 Spring 也分不清首选的首选到底是哪一个了。

@Qualifier 也是解决 bean 歧义的一个注解,dk 暂时还不理解具体啥意思,记录下看到知道这玩意也是解决 bean 冲突即可。

14. 装配 Bean 之 @Scope

默认情况下创建的 Bean 是单例模式,实际开发业务场景千变万化,保不准还需要什么其它模式,通过 @Scope 可以设置 bean 的作用域。首先了解下 bean 有哪些作用域:

  • 单例(Singleton):在整个应用中,只会创建 bean 的一个实例;
  • 原型(Prototype):每次注入都会创建一个新的 bean 实例;
  • 会话(Session):web 应用中为每个会话创建一个 bean 实例;
  • 请求(Request):web 应用中为每个请求创建一个 bean 实例。

实际使用:

1
2
3
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Xxx { }

1
2
3
@Component
@Scope("prototype")
public class Xxx { }

15. Spring AOP

第四章读了第一节就不想读了,除非用到或看到相关代码,光看文字要弄懂难度不小。

首页
友链
归档
dkvirus
动态
RSS