Cynicism Cynicism
首页
  • 前端学习笔记

    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • TypeScript
    • JS设计模式总结
    • 小程序笔记
  • 后端学习笔记

    • 《JavaWeb》
    • 《SSM》
    • 《瑞吉外卖》
    • 《Git》
    • 《SpringCloud》
    • 《黑马点评》
    • 《Spring原理》
    • 《JVM》
    • 《Java并发编程》
    • 《学成在线》
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 面试
  • 常见问题
  • 实用技巧
  • 友情链接
实习
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Cynicism

Java后端学习中的IKUN
首页
  • 前端学习笔记

    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • TypeScript
    • JS设计模式总结
    • 小程序笔记
  • 后端学习笔记

    • 《JavaWeb》
    • 《SSM》
    • 《瑞吉外卖》
    • 《Git》
    • 《SpringCloud》
    • 《黑马点评》
    • 《Spring原理》
    • 《JVM》
    • 《Java并发编程》
    • 《学成在线》
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 面试
  • 常见问题
  • 实用技巧
  • 友情链接
实习
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Bean生命周期
  • AOP
  • WEB
  • SpringBoot
  • 策略模式
    • 1. 多重if-else
    • 2. 策略模式实现
      • 2.1 定义一个策略接口: Strategy.class
      • 2.2 创建接口的实现类
      • 2.3 创建Context类
      • 2.4 创建实现类
    • 3. 策略模式的优缺点
      • 3.1 优点
      • 3.2 缺点
      • 3.3 策略模式中的三个角色
    • 4. 在Spring源码中应用的策略模式
      • 4.1 Spring源码中的BeanDefinitionReader
      • 4.2 Spring源码中ResourceLoader
    • 5. 总结与分析
  • 初始化IOC容器
  • 《Spring原理》
cynicism
2024-01-05
目录

策略模式

提示

本文主要介绍Spring源码中涉及的策略模式应用

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。

# 1. 多重if-else

public int count(int num1, int num2, String operation) {
    if (operation.equals("+")) {
        return num1 + num2;
    } else if (operation.equals("-")) {
        return num1 - num2;
    } else if (operation.equals("*")) {
        return num1 * num2;
    } else if (operation.equals("/")) {
        return num1 / num2;
    }
}
1
2
3
4
5
6
7
8
9
10
11

有没有觉得这些算法都耦合在一起了,如果需要改某个算法,而要改动整个count()方法,如果其中某个算法出错了,会影响整个count()方法。

# 2. 策略模式实现

# 2.1 定义一个策略接口: Strategy.class

public interface Strategy {
    public int doOperation(int num1, int num2);
}
1
2
3

# 2.2 创建接口的实现类

public class Add implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}

public class Substract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}

public class Multiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

public class Divide implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 / num2;
   }
}
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

# 2.3 创建Context类

public class Context {
   private Strategy strategy;
   
   public SetStrategy(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}
1
2
3
4
5
6
7
8
9
10
11

# 2.4 创建实现类

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context();    
      context.SetStrategy(new Add());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
      context.SetStrategy(new Substract());    
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
      context.SetStrategy(new Multiply());  
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
      context.SetStrategy(new Divide());  
      System.out.println("10 / 5 = " + context.executeStrategy(10, 5));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3. 策略模式的优缺点

# 3.1 优点

  • 策略模式实现类的算法或行为可以自由切换
  • 避免使用多重条件判断
  • 扩展性能好

# 3.2 缺点

  • 策略类增多
  • 所有的策略类需要向外暴露

# 3.3 策略模式中的三个角色

  1. Context封装角色:也叫上下文角色,起到封装作用,屏蔽了高层模块对策略的直接访问
  2. 策略抽象角色:定义策略实现的接口
  3. 策略实现类:实现策略接口,实现具体的策略算法或行为内容并向外界暴露

# 4. 在Spring源码中应用的策略模式

在上图中,BeanDefinitionReader、ResourceLoader、BeanNameGenerator三个接口作为策略接口,而其他的实现类都分别实现了各自的行为用于针对不同的业务场景,那么还有一个Context封装对象,在这里就是ApplicationContext——AbstractXmlApplicationContext里。

# 4.1 Spring源码中的BeanDefinitionReader

在学习BeanDefinitionReader之前,要先了解一下什么是BeanDefinition

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
    ...
    boolean isSingleton();
    boolean isPrototype();
    boolean isAbstract();
    ...
}
1
2
3
4
5
6
7
8
9

可以看到BeanDefinition作为一个接口,主要是用于存储从XML配置文件读取Bean信息到JVM内存的一个载体,具体是存储在了BeanDefinition的实现类——RootBeanDefinition中,下面来看看RootBeanDefinition。

public class RootBeanDefinition extends AbstractBeanDefinition {
    @Nullable
	private BeanDefinitionHolder decoratedDefinition;
	
	@Nullable
	volatile ResolvableType targetType;
	
	// 用于缓存给定bean定义的解析类型确定类
	@Nullable
	volatile Class<?> resolvedTargetType;
	...
}
1
2
3
4
5
6
7
8
9
10
11
12

可以看到RootBeanDefinition不是真正存储Bean信息的载体,继续查看BeanDefinitionHolder

public class BeanDefinitionHolder implements BeanMetadataElement {

    private final BeanDefinition beanDefinition;

	private final String beanName;

	@Nullable
	private final String[] aliases;
}
1
2
3
4
5
6
7
8
9

看到了存储bean用的beanName和别名aliases数组了

回到正题,我们已经知道了BeanDefinition就是一个存储XML配置文件中bean信息的一个载体,那么这个过程是如何实现的呢?答案就在BeanDefinitionReader的实现类————XmlBeanDefinitionReader里面。

XmlBeanDefinitionReader就是一个策略的具体实现,表示的是一种可以从Xml中获取Bean配置信息的策略,除了这种策略外,还有PropertiesBeanDefinitionReader,这种从Properties配置文件获取Bean配置信息的策略。

第三节中总结提到在策略模式中有三种角色,1)Context封装角色;2)策略抽象角色;3)策略实现角色。在这里我们已经找到了策略抽象角色——BeanDefinitionReader和策略实现角色——XmlBeanDefinitionReader和PropertiesBeanDefinitionReader,就差Context封装角色了,那么Spring中哪个类充当了这个角色呢?

答案就是——AbstractXmlApplicationContext类

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// 为给定的BeanFactory创建一个新的XmlBeanDefinitionReader
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// 允许子类提供reader的自定义初始化
		initBeanDefinitionReader(beanDefinitionReader);
		// 真正加载bean定义信息
		loadBeanDefinitions(beanDefinitionReader);
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以看到,策略实现类XmlBeanDefinitionReader在AbstractXmlApplicationContext中执行了具体的策略执行,也就是后面复杂的从Xml配置文件读取bean配置信息的操作。

# 4.2 Spring源码中ResourceLoader

下面先看看ResourceLoader的源码,然后再来简单介绍下其作用

public interface ResourceLoader {
    
    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

    Resource getResource(String location);
    
    ClassLoader getClassLoader();
}
1
2
3
4
5
6
7
8

这里的ResourceLoader就是一个Resource加载器,而Resource是将URL、URI等资源抽象为一个Resource资源对象,方便Spring进一步操作。

下面先来分析下三种角色:

  1. AbstractBeanDefinitionReader 作为Context封装角色
  2. ResourceLoader作为策略的抽象
  3. DefaultResourceLoader和ResourcePatternResolver就是具体的执行策略

由于resourceLoader instanceof ResourcePatternResolver为true,所以走如下逻辑:

  • AbstractBeanDefinitionReader.java
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources);
1
2
  • AbstractApplicationContext.java
    @Override
	public Resource[] getResources(String locationPattern) throws IOException {
		return this.resourcePatternResolver.getResources(locationPattern);
	}
1
2
3
4
  • PathMatchingResourcePatternResolver.java
    @Override
	public Resource[] getResources(String locationPattern) throws IOException {
		
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				
				return findPathMatchingResources(locationPattern);
			}
			else {
				
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			
			int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
					locationPattern.indexOf(':') + 1);
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				
				return findPathMatchingResources(locationPattern);
			}
			else {
				
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}
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

# 5. 总结与分析

  1. 策略模式是一个比较容易理解和使用的设计模式, 策略模式是对算法的封装 , 它把算法的责任和算法本身分割开 , 委派给不同的对象管理 。策略模式通常 把一个系列的算法封装到一系列的策略类里面 ,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
  2. 在策略模式中,应当由客户端自己决定 在什么情况下使用什么具体策略角色。
  1. 策略模式仅仅封装算法,提供新算法插入到已有系统中 ,以及老算法从系统中“退休”的方便 ,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
编辑 (opens new window)
#spring#设计模式
上次更新: 2025/05/12, 04:51:03
SpringBoot
初始化IOC容器

← SpringBoot 初始化IOC容器→

最近更新
01
JVM调优
06-03
02
Linux篇
03-30
03
Kafka篇
03-30
更多文章>
Theme by Vdoing | Copyright © 2023-2025 Cynicism | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式