《Spring 实战》笔记
1、Spring 起步
Spring 的核心是提供了一个容器(container),通常称为 Spring 应用上下文( Spring application context ),它们会创建和管理应用组件。这些组件也可以称为 bean ,会在 Spring 应用上下文中装配在一起,从而形成一个完整的应用程序。这就像砖块、砂浆、木材、管道和电线组合在一起,形成一栋房子似的。
装配 bean 的方式:依赖注入( dependency injection ,DI )。
组件并不会创建它所依赖的组件并管理它们的生命周期,而是使用容器来创建组件,再把组件注入到需要它的 bean 中。
如图,商品服务需要依赖于库存服务,就把库存服务注入到商品服务中,图1.1阐述了这些 bean 和 Spring 应用上下文之间的关系。
深入理解依赖注入
程序清单 1.2 DamselRescuingKnight 只能执行 RescueDamselQuest 探险任务
package com.springinaction.knights;
public class DamselRescuingKnight implements Knight{
private RescueDamselQuest quest;
public DamselRescuingKnight(){
this.quest = new RescueDamselQuest(); //与 RescueDamselQuest 紧耦合
}
@Override
public void embarkOnQuest() {
quest.embark();
}
}
DamselRescuingKnight 在构造器中创建了 RescueDamselQuest,使得它们紧耦合到了一起。
紧耦合的缺点:单元测试困难、代码难以复用、难以理解、“打地鼠”式 bug 。
但是另一方面,耦合又是必须的,因为没有耦合,什么也做不了。
程序清单 1.3 BraveKnight 足够灵活可以接受任何赋予他的探险任务
package com.springinaction.knights;
public class BraveKnight implements Knight{
private Quest quest;
public BraveKnight(Quest quest){ //Quest 被注入进来
this.quest = quest;
}
@Override
public void embarkOnQuest() {
quest.embark();
}
}
不同于之前的 DamselRescuingKnight ,BraveKnight 没有自行创建探险任务,而是在构造的时候把探险任务作为构造器参数传入。这是依赖注入的方式之一,即构造器注入(construction injection)。
松耦合:如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,name这种依赖就能在对象本身毫不知情的情况下,用不同的具体实现进行替换;
替换方式:mock 实现。
程序清单1.4 为了测试 BraveKnight ,需要注入一个 mock Quest
package com.springinaction.knights;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
class BraveKnightTest {
@Test
public void knightShouldEmbarkOnQuest(){
Quest mockQuest = mock(Quest.class); //创建mock Quest
BraveKnight knight = new BraveKnight(mockQuest); //注入mock Quest
knight.embarkOnQuest();
verify(mockQuest,times(1)).embark();
}
}
使用 mock 框架 Mockito 去创建一个 Quest 接口的 mock 实现。通过这个对象,就可以创建一个新的 BraveKnight 实例,并通过构造器注入这个 mock Quest,当调用 embarkOnQuest 方法时,要求 embark() 方法仅仅被调用了一次。
如何将特定的 Quest 注入到 BraveKnight 中:
程序清单1.5 SlayDragonQuest 是要注入到 BraveKnight 中的 Quest 实现
package com.springinaction.knights;
import java.io.PrintStream;
public class SlayDragonQuest implements Quest{
private PrintStream stream;
public SlayDragonQuest(PrintStream stream){
this.stream = stream;
}
@Override
public void embark() {
stream.println("Embarking on quest to slay the dragon");
}
}
装配(writing):创建应用组件之间协作的行为。
Spring有多种装配 bean 的方式,如 XML、基于 Java 的配置(更常用)。
程序清单1.6 使用 Spring 将 SlayDragonQuest 注入到 BraveKnight 中
<?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.xsd">
<!-- 注入 Quest bean-->
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<!-- 创建 SlayDragonQuest-->
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
</beans>
BraveKnight 和 SlayDragonQuest 被声明为 Spring 中的 bean。BraveKnight bean 在构造时传入了对 SlayDragonQuest bean 的引用,将其作为构造器参数。
程序清单1.7 Spring 提供了基于 Java 的配置,可作为 XML 的替代方案
package com.springinaction.knights.config;
import com.springinaction.knights.BraveKnight;
import com.springinaction.knights.Knight;
import com.springinaction.knights.Quest;
import com.springinaction.knights.SlayDragonQuest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class KnightConfig {
@Bean
public Knight knight(){
return new BraveKnight(quest());
}
@Bean
public Quest quest(){
return new SlayDragonQuest(System.out);
}
}
@Configuration 注解会告知 Spring 这是一个配置类,会为 Spring 应用上下文提供 bean。@Bean 注解标注的方表明这些方法所返回的对象会以 bean 的形式添加到 Spring 的应用上下文中(bean ID 和方法名相同)。
优先级:自动装配(@Autowire) > 基于 Java 配置 > 基于 XML 配置
随着 Spring Boot 的引入,自动配置的能力已经远远超出了组件扫描和自动装配。Spring Boot 是 Spring 框架的扩展,提供了很多增强生产效率的方法。最为大家所熟知的增强方法是自动配置(autoconfigration),Spring Boot 能够基于类路径中的条目、环境变量和其他因素合理猜测需要配置的组件并将它们装配在一起。
程序清单1.8 KnightMain加载包含 Knight 的 Spring 应用上下文
package com.springinaction.knights;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("knights.xml");
Knight knight = context.getBean(Knight.class); //获取 knight bean
knight.embarkOnQuest(); //使用 knight
context.close();
}
}
以上代码基于 knights.xml 创建了 Spring 应用上下文,调用上下文获取了一个 ID 为 knight 的 bean,简单调用接口中的方法,这个类完全不知道具体实现了哪个方法。
应用切面
DI 能够让相互协作的软件组件保持松散耦合,而面向切片编程(aspect-oriented programming,AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。
而 AOP 能够使这些服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。
如图,我们可以把切面想象为覆盖在很多组件之上的一个外壳,借助 AOP ,可以使用各种功能层去包裹核心业务层。将安全、事务和日志关注点与核心业务逻辑相分离。
程序清单1.9 吟游诗人
package com.springinaction.knights;
import java.io.PrintStream;
public class Minstrel {
private PrintStream stream;
public Minstrel(PrintStream stream){
this.stream = stream;
}
public void singBeforeQuest(){
stream.println("Fa la la, the knight is so brave!");
}
public void singAfterQuest(){
stream.println("Tee hee hee, the brave knight" +
"did embark on a quest"
);
}
}
BraveKnight 必须要调用 Minstrel 的方法
package com.springinaction.knights;
public class BraveKnight implements Knight{
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest,Minstrel minstrel){
this.quest = quest;
this.minstrel = minstrel;
}
@Override
public void embarkOnQuest() {
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
此时如果 Minstrel 为 null 会发生什么?简单的 BraveKnight 类变得复杂且混乱。
但利用AOP,可以声明吟游诗人必须歌颂骑士的探险事迹,而骑士本身并不用直接访问 Minstrel 的方法。
要将 Minstrel 抽象为一个切面,就要在一个 Spring 配置文件中声明它。
程序清单1.11 将 Minstrel 声明为一个切点
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注入 Quest bean-->
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<!-- 创建 SlayDragonQuest-->
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
<!-- 声明 Minstrel bean-->
<bean id="minstrel" class="com.springinaction.knights.Minstrel">
<constructor-arg value="#{T(System).out}" />
</bean>
<aop:config>
<aop:aspect ref="minstrel">
<!-- 定义切点-->
<aop:pointcut id="embark"
expression="execution(* *.embarkOnQuest(..))"/>
<!-- 声明前置通知-->
<aop:before pointcut-ref="embark"
method="singBeforeQuest"/>
<!-- 声明后置通知-->
<aop:after pointcut-ref="embark"
method="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>
Spring AOP 可以做很多有实际意义的事情,比如基于Spring AOP 实现声明式事务和安全。
容纳你的 Bean
在基于 Spring 的应用中,你的应用对象生存于 Spring 容器(container)中。Spring 容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期,从生存到死亡(在这里,可能就是 new 到 finalize() )。
Spring 自带了多个容器的实现,可以归为两种不同的类型。
bean 工厂是最简单的容器,提供基本的 DI 支持。
应用上下文基于 BeanFactory 构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。一般我们使用应用上下文。
- AnnotationConfigApplicationContext:从一个或多个基于 Java 的配置类中加载 Spring 应用上下文。
- AnnotationConfigWebApplicationContext:从一个或多个基于 Java 的配置类中加载 Spring Web应用上下文。
- ClassPathXmlApplicationContext:从类路径下的一个或多个 XML 配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
- FileSystemXmlApplicationContext:从文件系统下的一个或多个 XML 配置文件中加载上下文定义。
- XmlWebApplicationContext:从 Web 应用下的一个或多个 XML 配置文件中加载上下文定义。
bean 的生命周期
Spring项目结构
- mvnw 和 mvnw.cmd:Maven 包装器(wrapper)脚本,即使没有安装 Maven 也可以构建项目。
- pom.xml:Maven 构建规范。
- SpringinactionApplication.java:Spring Boot 主类,会启动项目。
- static:存放为浏览器提供服务的静态内容。
- templates:存放用来渲染内容到浏览器的模板文件,如 Thymeleaf 模板引擎。
- SpringinactionApplicationTests.java:测试类。
pom.xml
starter 依赖的特别之处在于它们本身不包含库代码,而是传递性地拉取其他的库。好处:文件体积小、易于管理、集成性、版本兼容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I38RYRpm-1605951120429)(C:\Users\john\Desktop\书本手册\java\《Spring实战》笔记\1-9.jpg)]
这个插件提供了一个 Maven goal,允许我们使用 Maven 来运行应用。它确保依赖库都会包含在 JAR 文件中和其可用性,会在 JAR 中生成一个 manifest 文件,将引导类声明为主类。
主类
@SpringBootApplication
public class SpringinactionApplication {
public static void main(String[] args) {
SpringApplication.run(SpringinactionApplication.class, args);
}
}
@SpringBootApplication 中包含了三个注解
- @SpringBootConfiguration:配置类。
- @EnableAutoConfiguration:自动配置。
- @ComponentScan:组件扫描,这样就能通过 @Component 、@Controller、@Service 等注解声明其他类,Spring 会自动发现它们并注册为 Spring 应用上下文中的组件。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
run() 方法创建 Spring 应用上下文。