xiaozhigang

长风破浪会有时,直挂云帆济沧海。

简述

如果在线上环境,需要新建一个索引,会发生什么,会不会导致不可用,具体的创建步骤又是啥。

首先肯定会有一段时间导致表不可用的,主要就是看不可用的时间长短。

在不同的版本上主要有三种创建方式:Copy Table方式、Inplace方式、Online方式

Copy Table方式

早期版本,直接通过复制表实现

步骤

1、先为原表table创建临时表table_copy。

2、向临时表table_copy添加索引。

3、将原表table数据查询后插入到临时表table_copy,在将原表table改名为table_1。

4、在将临时表table_copy改名为table

不可用

在创建的过程中原表是可读的,但是在查询插入阶段是不可写的。

Inplace方式

MySQL5.5版本,没有复制临时表,减少了空间消耗,相对copy方式是一种进步。

步骤

1、读取主表索引列数据,并对数据排序

2、直接新建索引

不可用

创建过程中原表可读不可写。

Online方式

MySQL5.6.7版本,在原本inplace的基础上增添了可写的功能,相对inplace又是一种进步。

步骤

1、使用inplace方式创建索引,不用临时表。

2、在遍历聚簇索引,收集记录插入到新索引的过程中,记录修改保存到row log

3、聚簇索引遍历结束,并将所有记录插入之后,重放row log,是的记录一致。

不可用

只有在步骤3,重放row log的过程中锁表不可写。

什么是JVM类加载

指将编译后的class文件,读到内存中,首先将其放在运行时数据区的方法区内,然后再堆内创建class对象。class对象封装了类在方法区内的数据结构,并提供了访问方法区内的数据结构的接口。

image-20240409215152713

类加载器

自带加载器

自带加载器有三个:

1、启动类加载器:负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。

2、扩展类加载器:该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

3、应用程序类加载器:该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

类加载的三种方式

  • 命令行启动应用时候由JVM初始化加载
  • 通过Class.forName()方法动态加载
  • 通过ClassLoader.loadClass()方法动态加载

双亲委派机制

1、一个类加载器收到类加载请求,不会首先自己去加载这个类,而是把请求委托到父类加载器去完成。

2、依次向上。

3、所有类加载请求都会被传递到顶层的启动类加载器中。

4、只有父加载器无法加载该类,才会由子类尝试加载。

image-20240409221650719

类的生命周期

生命周期主要包括7个部分:加载,验证,准备、解析、初始化、使用、卸载

类加载过程:加载,验证,准备、解析、初始化

其中 验证,准备、解析 这三个部分统称为连接

image-20240409223238427

加载,验证,准备、初始化 这四个部分是按顺序发生的,而解析则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。其他阶段是按顺序发生,不一定按顺序结束,通常是相互交叉的混合进行,在一个阶段执行的过程中调用另一个阶段。

加载:查找并加载类的二进制数据

验证:确保被加载类的正确性

准备:为类的静态变量分配内存,并将其初始化默认值

解析:把类中的符号引用转换为直接引用

初始化:到此才开始真正执行Java程序代码,执行类构造器方法的过程

简述

提到分布式,那么CAP原则是绕不过去的,那么什么是CAP原则呢。

CAP原则:在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)不可能都同时满足,最多只能同时满足两个。

img

详解

选项 描述
一致性(Consistency 指在分布式系统中,多个副本之间能够保持严格的数据一致性。
可用性(Availability) 指服务一直处于可用状态,每次请求都能获取非错响应
分区容错性(Partition tolerance) 指在分布式系统中,某个分区或副本出故障了,其他分区或副本能继续对外提供服务

为什么不能CAP兼容

1、在分布式系统中,分区容错性肯定是必须的,所以必须要有P。

2、添加C,就是添加一致性,那么就是要保证所有分区中的数据是一致的。假设有A、B、C三个分区,现在分区上的数据一致。

3、添加A,添加可用性,保证服务一直可用,我们看能不能达到这种效果。

​ A、B、C三个分区,分区上的数据一致。现在操作A分区中的数据发生更改,在保证一直性的情况下,得先同步更改B、C分区上得数据。更改数据需要时间,在同步更改的这段时间中,是不是就不能对外提供服务。 如果要先保证可用性,那是不是就不能先同步更改B、C分区上的数据。由此就可以看出CAP不能同时存在。

解决方案

虽然CAP不能同时存在,但是在分布式系统中一般AP、CP也可以根据场景的不同解决我们的问题了。

在可用性要求高的场景中,可以放弃一致性。这要求在数据不一致的情况下不影响我们的服务,比如NoSQL。

在一致性要求高的场景中,可以放弃可用性。这种场景是对数据有很高的要求,比如转账服务,可以牺牲一段可用时间,以保证各分区数据同步一致。

折中方案

在一致性要求比较高的场景中,同步各分区的时候,是不是需要同步到所有分区,如果同步一个或者大部分分区就可以的,那么这时候返回,是不是会提高可用性。这是一个在保证一致性的情况下提高可用性的办法,前提是同步到一个或大部分分区就可以保证我们的业务。

归根究底,一致性和可用性不能同时兼得,一个增加另一个必然减少。

​ 在java中,我们用到的代理主要有两种:静态代理、动态代理。静态代理相对比较简单易懂,逻辑清晰;而动态代理逻辑就比较绕,比较复杂。相对的,一得一失,静态代理代码冗余不够灵活,而动态代理的代码就比较灵活方便。

特性 静态代理 动态代理
代理类生成时间 编译期写死 运行时动态生成
开发成本 高(每个接口都要写代理类) 低(统一写 InvocationHandler / MethodInterceptor)
灵活性
性能 较高(无反射) 稍低(有反射/字节码增强)
使用场景 代理对象少,结构简单 需要通用代理、AOP 框架(Spring)

静态代理

写法:需要在代码里手动编写一个代理类,代理类和目标类实现同一个接口。

特点

  • 编译期确定代理关系。
  • 代理类是提前写好的,结构固定。
  • 每增加一个接口或方法,都需要手动维护代理类,扩展性差。

优点:逻辑清晰,性能开销小(没有反射)。

缺点:代码冗余严重,不灵活。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface UserService {
void addUser(String name);
}

public class UserServiceImpl implements UserService {
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
}

// 静态代理类
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void addUser(String name) {
System.out.println("前置处理");
target.addUser(name);
System.out.println("后置处理");
}
}

public class Main {
public static void main(String[] args) {
UserService service = new UserServiceProxy(new UserServiceImpl());
service.addUser("张三");
}
}

动态代理

写法:在运行时动态生成代理类,不需要提前写死。

  • JDK 动态代理:基于接口 + InvocationHandler + 反射。
  • CGLIB 动态代理:基于字节码生成(子类继承目标类)。

特点

  • 运行期确定代理关系。
  • 不需要手动写代理类,灵活。
  • 依赖反射或字节码增强,性能比静态代理稍弱(但现代 JVM 优化后差距不大)。

优点:通用性强,减少代码冗余。

缺点:调试困难,可能有性能损耗。

​ 动态代理分为两类:JDK动态代理、CGLIB动态代理

特性 JDK 动态代理 CGLIB 代理
代理对象 接口的实现类 目标类的子类
是否需要接口 必须有接口 不需要接口
底层实现 反射(Proxy + InvocationHandler) 字节码增强(ASM + MethodInterceptor)
性能 JDK 1.8 之前略慢,之后优化较好 一般比反射快,但生成类开销大
限制 只能代理接口方法 不能代理 final 类/方法

JDK动态代理

接口 & 实现类
1
2
3
4
5
6
7
8
9
10
public interface UserService {
void addUser(String name);
}

public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
}
JDK 动态代理实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();

UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{UserService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK代理] 调用方法前: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("[JDK代理] 调用方法后: " + method.getName());
return result;
}
}
);

proxy.addUser("张三");
}
}

CGLIB动态代理

目标类(没有接口)
1
2
3
4
5
public class OrderService {
public void createOrder(String orderId) {
System.out.println("创建订单: " + orderId);
}
}
CGLIB 代理实现

需要引入依赖(如果是 Spring Boot 项目已经有了 spring-core,包含 CGLIB)。

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyDemo {
public static void main(String[] args) {
OrderService target = new OrderService();

OrderService proxy = (OrderService) Enhancer.create(
OrderService.class,
new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB代理] 调用方法前: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("[CGLIB代理] 调用方法后: " + method.getName());
return result;
}
}
);

proxy.createOrder("ORD123456");
}
}

架构图

四层结构

cola-arch.jpg

  1. 适配层(Adapter Layer):负责对前端展示(web,wireless,wap)的路由和适配,对于传统B/S系统而言,adapter就相当于MVC中的controller;

  2. 应用层(Application Layer):主要负责获取输入,组装上下文,参数校验,调用领域层做业务处理,如果需要的话,发送消息通知等。层次是开放的,应用层也可以绕过领域层,直接访问基础实施层;

  3. 领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对App层提供业务实体和业务逻辑计算。领域是应用的核心,不依赖任何其他层次;

  4. 基础实施层(Infrastructure Layer):主要负责技术细节问题的处理,比如数据库的CRUD、搜索引擎、文件系统、分布式服务的RPC等。此外,领域防腐的重任也落在这里,外部依赖需要通过gateway的转义处理,才能被上面的App层和Domain层使用。

    阅读全文 »

简述

ThreadLocal是什么,是java中用于实现线程内变量的工具类。允许每个线程都拥有自己的副本,从而实现线程隔离,解决多线程的共享对象的线程安全问题。

image-20240330210435879

ThreadLocal的使用

1
2
3
public static ThreadLocal<String> localVariable = new ThreadLocal<>(); // 新建
localVariable.set("他是沙雕"); // 设置值
String value = localVariable.get(); // 获取值

详述

实现原理

ThreadLocal的底层实现是一个特殊的map,可以理解往ThreadLocal设置一个xxx值,就是往这个特殊的map中设置一个key为当前线程,value为xxx的值。由于他的key只能为当前线程,所以在某个线程中set和get都不会影响其他线程,从而到达线程安全。下图的ThreadLocalMap就是拿个特殊的map。

image-20240330211735870

总体来说,java在运行的过程中会维护一个ThreadLocalMap,这个ThreadLocalMap的key为各个线程,value为各个线程想存放的东西,可以是String,可以是map,List等等。当时由于key为各个线程,所有每个线程只能由一个Entry ,也就是一个键值对(key-value),当然线程也可以不设置不存放。

image-20240330213034272

内存泄露

都知道ThreadLocal会内存泄漏,那么这个内存泄漏又是怎么回事。

都知道在java虚拟机中栈是私有的,而堆是共享的,所以ThreadLocalMap是存放在堆中的,而栈中存的只是一个引用,如下图。

image-20240330214507001

由于Entry extends WeakReference<ThreadLocal<?>>,所以可以看到,Entry中的key指向ThreadLocal是弱引用,也就是线程执行完之后,这个ThreadLocal就会被释放清理掉,但是由于ThreadLocalMap指向Entry是强引用,所以Entry不会被释放清理,如果这Entry一直不被清理释放,那么ThreadLocalMap的内存占用就会越来越大,以至于内存也漏。

所以为了防止这种问题的发生,在用完时,记得释放清理。

1
2
3
4
5
6
try {
threadLocal.set(value);
// 执行业务操作
} finally {
threadLocal.remove(); // 确保能够执行清理
}

简述

IoC是指控制反转,是指有容器来控制对象的生命周期和对象之间的关系。简单来说就是之前使用对象首先需要new一个对象,而现在直接从容器中取就可以了。这就像有小农经济转变到商品经济。

小农经济:自给自足,什么东西都需要自己动手也就类似于对象需要自己new。

商品经济:需要啥去商店买就行,这里的商店就相当于容器,商品就相当于对象,生成商品的工厂就相当于对象工厂等。

Spring IOC 的简单实现

分析

1、首先我们得有一个容器,这个容器负责保存和生成bean,这也就是对象工厂BeanFactory 。

2、其次,得有一个bean注册器,这负责bean的注册和真正生成。

3、然后,还得有一个资源加载器,用来加载bean的定义

4、最后,得有一个bean的定义模型和配置文件。

实现

分析为倒叙分析,实现就得看正序实现了,不然依赖就颠倒了。

1、配置文件和bean模型

先准备一个示例的bean

1
2
3
4
5
public class BeanExample {
public void print(String string){
System.out.println("test out: " + string);
}
}

配置文件,偷懒使用一个<key,value>键值对代替

1
beanExample:com.shopmall.springIoc.BeanExample

bean模型定义

1
2
3
4
5
6
7
8
@Getter
@Setter
public class BeanDefinition {
private String beanName;

private Class beanClass;
}

2、资源加载器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ResourceLoader {
public static Map<String, BeanDefinition> getResource() {
Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
Properties properties = new Properties();
try {
InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties");
properties.load(inputStream);
for (String key : properties.stringPropertyNames()) {
String className = properties.getProperty(key);
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanName(key);
Class clazz = Class.forName(className);
beanDefinition.setBeanClass(clazz);
beanDefinitionMap.put(key, beanDefinition);
}
inputStream.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return beanDefinitionMap;
}
}
3、对象注册器

简化过程,都用单例了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class BeanRegister {

//单例Bean缓存
private final Map<String, Object> singletonBeanMap = new HashMap<>();

/**
* 获取单例Bean
*
* @param beanName bean名称
* @return Object 单例Bean
*/
public Object getSingletonBean(String beanName) {
return singletonBeanMap.get(beanName);
}

/**
* 注册单例bean
*
* @param beanName bean名称
* @param bean 单例bean
*/
public void registerSingletonBean(String beanName, Object bean) {
if (singletonBeanMap.containsKey(beanName)) {
return;
}
singletonBeanMap.put(beanName, bean);
}

}

4、容器bean工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class BeanFactory {
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

private BeanRegister beanRegister;

public BeanFactory() {
//创建bean注册器
beanRegister = new BeanRegister();
//加载资源
this.beanDefinitionMap = new ResourceLoader().getResource();
}

/**
* 获取bean
*
* @param beanName bean名称
* @return Object Bean
*/
public Object getBean(String beanName) {
//从bean缓存中取
Object bean = beanRegister.getSingletonBean(beanName);
if (bean != null) {
return bean;
}
//根据bean定义,创建bean
return createBean(beanDefinitionMap.get(beanName));
}

/**
* 创建Bean
*
* @param beanDefinition bean定义
* @return Object Bean
*/
private Object createBean(BeanDefinition beanDefinition) {
try {
Object bean = beanDefinition.getBeanClass().newInstance();
//缓存bean
beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean);
return bean;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
5、测试运行

简述

都知道spring的自动加载是通过注解实现的,但是这注解又是怎么实现的呢?

通过@SpringBootApplication注解开启,读取配置文件中的配置类,然后过滤掉不需要的配置类,最后将剩余的配置类加载配置。

具体过程

上面说了一下大概的概念,接下来我们分析一下具体的执行过程。

1、开启自动加载

都知道自动开启加载注解@SpringBootApplication,但是这个注解是个复合注解,他是由多个注解合并而成,但是主要的是三个注解:

1
2
3
@SpringBootConfiguration // 配置文件
@EnableAutoConfiguration // 开启自动配置
@ComponentScan // 扫描

image-20240401205815186

@EnableAutoConfiguration注解继续下钻,主要有@AutoConfigurationPackage和@Import({AutoConfigurationImportSelector.class})两个注解,@AutoConfigurationPackage继续下钻主要有@Import({AutoConfigurationPackages.Registrar.class})这个注解。

image-20250705155304630

所以基本可以理解

@EnableAutoConfiguration = @Import({AutoConfigurationImportSelector.class}) + @Import({AutoConfigurationPackages.Registrar.class})

即自动加载主要是依赖AutoConfigurationImportSelector.class,AutoConfigurationPackages.Registrar.class两个类。

2、@EnableAutoConfiguration下的两个类

AutoConfigurationPackages.Registrar.class:作用就是获取要扫描的包路径

1
2
3
4
5
6
7
8
9
10
11
12
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}

public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}

AutoConfigurationImportSelector.class:可以看到AutoConfigurationImportSelector实现了3种接口

1、DeferredImportSelector接口,继承了ImportSelector接口,用于bean的注入

2、以Aware结尾的接口,这类接口时为了完成某类资源的设置。

3、Ordered接口,用于指定bean的加载顺序。

1
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered

3、AutoConfigurationImportSelector

AutoConfigurationImportSelector实现了DeferredImportSelector接口,我们先看一下DeferredImportSelector接口。

image-20240401215546520

接口里就包含了要加载的bean信息,再回到实现类,主要方法process。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));

// 获取自动配置的类
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 遍历自动配置的bean
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
// 如果存在这个类就进行加入
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}

其中,主要的就是获取自动配置类:getAutoConfigurationEntry方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 关键
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

根据关键代码继续下钻

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations,
            "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

继续下钻

image-20240401232921570

到这就基本清楚了,这里加载了META-INF/spring.factories文件下的配置,然后返回到getAutoConfigurationEntry方法里进行过滤,当然过滤的方式也是按条件注解@ConditionalOnxxx过滤生效。

image-20240401233308499

总结

@SpringBootApplication下分三个注解,@EnableAutoConfiguration注解负责自动加载配置,

@EnableAutoConfiguration注解又引入两个类,AutoConfigurationImportSelector.class 和 AutoConfigurationPackages.Registrar.class

主要逻辑都在AutoConfigurationImportSelector.class中,此类中有个getAutoConfigurationEntry方法,

这个方法调用了两个方法getCandidateConfigurations 加载 和 getConfigurationClassFilter().filter 过滤。

如此加载进了配置文件中的配置,过滤了条件不满足的配置,以达到开箱即用。

image-20240401234620520

代理模式

Spring AOP 就是基于代理模式,被代理对象有实现某个接口,则用JDK Proxy创建对象,没有实现接口则用Cglib创建代理对象。

当然也可以使用AspectJ,AspectJ是Java生态系统中最完整的AOP框架。

image-20240414185312122

Spring AOP属于运行时增强,AspectJ是编译时增强。

Spring AOP AspectJ
1 与spring ioc紧密集成,新项目使用 维护老项目使用
2 运行时织入 编译时生成
3 不支持static和final修饰方法和类 支持
4 简单,有注解 复杂,需要.aj文件来创建切面,并且需要使用ajc来编译代码

两者异同:https://developer.aliyun.com/article/720402

模板方法

父类定义算法骨架或者关键步骤,而具体实现延迟到子类中,使子类再不改变父类结构的情况下可重定义某些特定步骤的实现。

Spring 中 JdbcTemplateHibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。定义了资源获取、执行SQL、释放资源这些基本流程,执行sql的具体方式又以回调函数的形式开放。

image-20240414192751147

参考文献:https://blog.csdn.net/zhangweiocp/article/details/115486257

观察者模式

观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,依赖这个对象的所有对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。

事件驱动模型中的三种角色:事件角色、事件监听者角色、事件发布者角色。

image-20240414190146289

spring的事件流程总结

1、定义一个事件: 实现一个继承自ApplicationEvent,并且写相应的构造函数

2、定义一个事件监听者:实现 ApplicationListener 接口,重写 onApplicationEvent() 方法

3、使用事件发布者发布消息: 可以通过 ApplicationEventPublisherpublishEvent() 方法发布消息

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;

private String message;

public DemoEvent(Object source,String message){
super(source);
this.message = message;
}

public String getMessage() {
return message;
}


// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{

//使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}

}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {

@Autowired
ApplicationContext applicationContext;

public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}

当调用 DemoPublisherpublish() 方法的时候,比如 demoPublisher.publish("你好") ,控制台就会打印出:接收到的信息是:你好

装饰者模式

装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个 Decorator 套在原有代码外面。最典型的就是JDK中的InputStream,OutputStream,两个类下的所有子类都是再不修改父类代码的情况下扩展了他的功能。

装饰者模式示意图

总结

Spring 框架中用到了哪些设计模式?

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controllerl

分支定义

  1. master

    1
    2
    3
    长期分支,存在与整个项目开发过程。

    由项目主要技术负责人管理该分支。
  2. release/xxx

    1
    2
    3
    4
    release/test 和 release/prod
    既可以为长期分支也可以为短期分支,可能存在于一个或者多个版本之间.

    由测试负责人负责人管理该分支。
  3. feature/fixbug/hotfix

    1
    2
    3
    4
    5
    临时分支
    用于开发的具体功能特性和修复bug的分支,功能完成后删除.
    格式为:feature_$date_$name_$description
    fixbug_$date_$name_$description
    hotfix_$date_$name_$description
阅读全文 »
0%