服务端开发(1) Spring
2023-08-09 14:53:19 # NJU # 服务端开发

概述

1. Web App开发

1.1 前、后端不分离的开发模式

image-20230216170527653

1.2 前、后端分离的开发模式

image-20230216170540465

1.3 微服务架构(Microservices Architecture)模式

微服务架构时将应用拆分成小业务单元开发和部署,使用轻量级协议通信,通过协同工作实现应用逻辑的架构模式

微服务的特性:小,且指责单一、独立的进程、轻量级通信机制、独立部署

image-20230216170820095

2. Spring

2.1 Spring是Java生态圈的主流开发框架

  • 轻量级(Lightweight)
  • 非侵入性(No intrusive)
  • 容器(Container)
  • Inversion of Control 与 Dependency Injection
  • AOP(Aspect Oriented Programming)
  • 持久层(JDBC封装、事务管理、ORM工具整合)
  • Web框架(MVC、其它WEB框架整合)
  • 其他企业服务的封装

2.2 Spring的核心是提供了一个容器(container)

image-20230216171148852

2.3 Spring的衍生

Spring:核心、基础框架

Spring Boot:简化基于Spring的开发,自动配置

Spring Cloud:基于云的、分布式系统开发,相关技术:容器、微服务、DevOps

2.4 Spring的模块组成

image-20230307152941773

2.5 Spring的两个核心技术

DI(Dependency Injection)

  • 保留抽象接口,让组件(Component)依赖于抽象接口,当组件要与其他实际的对象发生依赖关系时,由抽象接口来注入依赖的实际对象

AOP(Aspect Oriented Programming)

  • 通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术
  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率

依赖注入(DI)

1. Spring配置方案

1.1 自动化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;

@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}

public void play() {
cd.play();
}

}
  • 组件扫描(component scanning)
  • 自动装配(autowiring)
组件扫描
1
2
@Configuration
@ComponentScan

等价于: <context:component-scan base-package="soundsystem" />

可以为 @ComponentScan 设置 basePackages 属性来显式指定要扫描的基础包。指定时有三种实现方式:

  • 以字符串的形式来指定: @ComponentScan(basePackages = {"xxx", "xxx"})
    • 此方式类型不安全(not type-safe),重构代码后指定的基础包可能会出现错误。
  • 指定为包中所含的类或接口: @ComponentScan(basePackages="Student.class")
  • 创建一个用来进行扫描的空标记接口(Marker interface)
自动装配
1
@Autowired
  • 可用于构造器
  • 用在属性 Setter 方法
  • @Autowired(required = false)
    • 将 required 属性设置为 false 时,Spring 会尝试执行自动装配,但是如果没有匹配的 bean 的话,Spring 将会让这个 bean 处于未装配的状态

1.2 JavaConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class CDPlayerConfig {

@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}

// @Bean
// public CDPlayer cdPlayer(CompactDisc cd) {
// return new CDPlayer(cd);
// }

@Bean(name="myCdPlayer")
public CDPlayer cdPlayer() {
// 不会实例化多个CompactDisc
return new CDPlayer(compactDisc());
}

}

自动化配置有时会行不通,如第三方库,此时需要我们手动创建 Bean

  • 注入
    • 调用方法()
    • 通过方法参数自动装配(其他配置类,其他方式创建的Bean)
  • @Bean(name="xxx"): 声明一个Bean,并且可以自定义名称

    • Bean的默认名称为方法名
    • 可以通过name字段修改bean的名称
  • 注意与业务逻辑和领域代码分开

1.3 XML配置

1
2
3
4
5
<bean id="compactDisc" class="soundsystem.SgtPeppers" />

<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
  • <bean></bean>
  • 不能类型检查
  • 构造器注入
    • <comstructor-arg>
    • c-命名空间
    • 注入字面量值
    • 注入集合
  • 属性注入
    • p-命名空间
    • util-命名空间
  • 建议:强依赖使用构造器注入

1.4 混合配置

JavaConfig 中的导入

  • @Import(配置类.class, ...)
  • @ImportResource(xml文件)

XML 中的导入

  • <import resource="xml文件" />
  • <bean class="配置类" />

1.5 根配置

1
2
3
4
5
@Configuration
@ComponentScan
@Import(其他配置类.class)
@ImportResource(xml文件)
class BootConfig(){}

2. @Profile

根据不同环境创造不同的 Bean

在 Java 配置中,可以使用 @Profile 注解指定某个 bean 属于哪一个 profile

1
2
3
4
5
6
7
// 类
@Configuration
@Profile("dev")

// 方法
@Bean
@Profile("prov")

激活

  • spring.profiles.default
  • spring.profiles.active
  • @ActiveProfiles("dev")

3. @Conditonal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// MagicConfig.java
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}

// 实现 Condition 接口,重写 match 方法
public class MagicExistsCondition implements Condition {

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

4. 自动装配的歧义性

  • @Component@Bean
  • @Primary: 标识首选的 Bean
  • 定义时
    1. @Componet@Bean
    2. @Qualifier("...") 自定义限定符,通过使用@Qualifier注解,我们可以消除需要注入哪个bean的问题。
  • 使用时
    1. @Autowired
    2. @Qualifier("...") bean名称或自定义限定符,默认Bean名是限定符
  • Spring 注解 @Qualifier 详细解析
  • 可以自定义注解,这些注解本身也加了@Qualifier注解
    1. @Cold
    2. @Creamy
1
2
3
@Qualifier
public @interface Cold{
}

5. Bean的作用域

@Scope 可以与 @Component 和 @Bean 一起使用,指定作用域

  • Singleton,单例,在整个应用中,只创建 bean 的一个实例
  • Prototype,原型,每次注入或者通过Spring应用上下文获取的时候,都会创建一个新 bean 实例
  • Session,会话,在Web应用中,为每个会话创建一个bean实例
  • Request,请求,在Web应用中,为每个请求创建一个bean实例
1
2
3
4
5
// 使用会话和请求作用域
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){....}

6. 通过代理注入给单例对象

image-20230219235848176

面向切面编程(AOP)

1. 概述

软件编程方法的发展

  • 面向过程编程(POP, Procedure Oriented Programming)
  • 面向对象编程(OOP, Object Oriented Programming)
  • 面向切面编程(AOP, Aspect Oriented Programming)
  • 函数式编程(FP, Functional Programming)
  • 反应式编程(RP, Reactive Programming)

2. AOP

切入前 切入后
image-20230228194342301 image-20230228194429951

2.1 横切关注点(cross-cutting concern)

  • 日志
  • 安全
  • 事务
  • 缓存

除了使用 AOP,还可选择

  • 继承(inheritance): 父类添加日志方法

  • 委托(delegation): 通过引用日志对象

2.2 AOP 图解

image-20230228194822706

2.3 AOP 术语

  • 通知(Advice):切面做什么?什么时候做?代表了切面的逻辑。
  • 切点(Pointcut):指定通知要切入到代码的哪些位置,切点表达式
  • 切面(Aspect):通知 + 切点
  • 连接点(Join point):方法(Spring只支持对方法添加通知)、字段修改、构造方法
  • 引入(Introduction):(给对象)引入新的行为和状态
  • 织入(Weaving):切面应用到目标对象的过程

2.4 通知(Advice)类型

  • @Before:在调用目标之前
  • @After:被切的对象方法执行之后,等于@AfterReturnning + @AfterThrowing
  • @AfterReturning:被切对象方法返回后,有异常但是没有return的话不执行AfterReturning
  • @AfterThrowing:被切对象方法出现异常之后
  • @Around:被切对象方法的执行前和执行后,等于@Before + @After

2.5 织入时机

  • 编译器:编译的时候将Aspect织入进去,需要使用特别的编译器。
  • 类加载期:需要类加载器的处理
  • 运行期:Spring所采纳的方式,使用代理对象,只支持方法级别的连接点

image-20230228200339433

3. Spring AOP

  • @AspectJ 注解驱动的切面
  • @EnableAspectJAutoProxy 配置类中,开启AspectJ的自动代理机制

3.1 定义切面(@Aspect)

  • 加注解的普通POJO
  • 定义可重用的切点(@Pointcut)
  • Around通知(proceedigJoinPoint.proceed())
  • 定义参数(args())

3.2 AspectJ 切点指示器(pointcut designator)

例子

1
2
3
4
5
6
@Pointcut(
"execution(* soundsystem.CompactDisc.playTrack( int ))" +
"&& args(trackNumber)" + //获取参数
"&& within(soundsystem.*)" + //限定包路径, 只有该包下面的方法可以使用切面
"&& bean(sgtPeppers)" //限定bean名称,或者: && !bean(sgtPeppers)
)

获取参数例子

1
2
3
4
5
6
7
8
9
10
@Pointcut(
"execution(* soundsystem.CompactDisc.playTrack( int )) " +
"&& args(trackNumber)")
public void trackPlayed(int trackNumber) {}

@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}

注解例子

1
2
3
4
5
@Around("@annotation(innerAuth)") //限定注解
public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) { ... }

@InnerAuth
public R<Boolean> register(@RequestBody SysUser sysUser) { ... }

3.3 引入接口(introduction)

  • @DeclareParents
  • 增加新的行为、属性
1
2
3
4
5
6
7
8
9
10
11
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value = "concert.Performance+",//后面的+表示应用到所有实现了该接口的Bean
defaultImpl = DefaultEncoreable.class)
public static Encoreable encoreable;
}

// DefaultEncoreable.java
public class DefaultEncoreable implements Encoreable {
public void performEncore() { ... }
}

image-20230228212114724