从Spring中学到的【2】--容器类
容器类
我们在实际编码中,常常会遇到各种容器类,他们有时叫做POJO,有时又叫做DTO,VO, DO等,这些类只具有容器的作用,具有完全的get,set方法,作为信息载体,作数据传输用。
其实,很多地方都可以看做将对象看做容器。比如,一些起到标签作用的接口,Serializable, **Aware接口等;注解元信息,比如@Order提供排序信息,@Value提供值注入,@Autowired提供属性注入等。
这种容器类一般很少具有面向对象的方法,又叫做贫血模型。与之对应的是充血模型,适合用于复杂系统,Spring源码显然就是充血模型占绝大多数,基于了面向对象的思想:对象=数据+行为。但是Spring中也经常见到许多容器类,有些类的一部分也可以看做容器。
函数式模型
函数式编程思想下,一个对象通常值为不变的。这样做的好处有很多,仅列举一些:便于支持并发,是线程安全的;对象是无状态的,极大地简化代码的理解,因为每一次值的变化都是显性的;便于编译器优化性能,比如延迟计算,实现对象复用;便于测试,因为没有副作用,每一次执行方法返回的结果都唯一。
这种对象称为值对象,Lombok中使用@Value标注,JDK14开始引入的record关键字实现了相同功能。比如Map容器对Key的要求就是值对象。以后有时间我再写一下函数式的一些基本思想。
举例1:MVC模型
MVC模型中,Model, View, Controller中我们使用的数据传输对象一般就使用POJO。一般情况下,一个请求过来,SpringMvc 可以将Json格式的字符串反序列化为POJO,一般为**Request。进行一系列计算或数据库交互后,返回可序列化的POJO对象,SpringMvc 将其序列化为Json。
举例2:各种配置的属性
我们经常使用的@Value、@ConfigurationProperties注解可以快速配置对象的属性,配合@RefreshScope可以实现动态刷新配置。
@Value注解的中的值还支持EL表达式,供我们灵活取用。
举例3:Spring 事件
观察者设计模式中的事件都是值对象。一个事件可以被多个listener监听,如果有一个listener改变了事件的某些属性,其他listener无法确定事件是否被更改过,系统运行的结果就不稳定。
我们简单地看下refresh模板方法,在容器刷下完毕之后,会调用finishRefresh方法,这个方法会发布事件ContextRefreshedEvent。我们看一下ContextRefreshedEvent的源码就会发现,其值只在构造器创建对象时确定,不包含set方法。
public class ContextRefreshedEvent extends ApplicationContextEvent {
public ContextRefreshedEvent(ApplicationContext source) {
super(source);
}
}
public abstract class ApplicationContextEvent extends ApplicationEvent {
public ApplicationContextEvent(ApplicationContext source) {
super(source);
}
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
public class EventObject implements java.io.Serializable {
private static final long serialVersionUID = 5516075349620653480L;
protected transient Object source;
public EventObject(Object source) {
if (source == null)
throw new IllegalArgumentException("null source");
this.source = source;
}
public Object getSource() {
return source;
}
public String toString() {
return getClass().getName() + "[source=" + source + "]";
}
}
也可以用IDEA提供的Structure功能看,一目了然。

打断点到对应位置

模板方法refresh

举例4:Monad
前置知识:我们知道Java Stream支持以下操作:filter, map, flatMap, collect, reduce。以上这些操作就是我们经常使用的,入参通常为函数。
monad是一个很复杂的概念,也很难解释。我们可以从使用的角度来简化理解,其就是一个容器,这个容器包装了其他的对象,这个容器包装了对象的行为,可以使用map、flatMap等方法。
不严格地说,如果没有Null,Optional就是一个Monad,数据库查询的返回值应该是Optional。
java8 引入Stream流后,集合类型也可以看做Monad,比如members.stream().filter(validPredicate).map(Member::getSalary).mapToInt(x → x).sum()
monad中的 flatMap 可以避免套娃。
在Java中常见的套娃就是Try Catch,使用vavr库中的Try可以解决,Try.of(逻辑1).faltMap(逻辑2),代码运行后,Try包装的对象要么是正常业务执行的返回值,要么是遇到的Exception。
比如我想查询数据库,查询公司职员A的主管。
member = repo.getByName(a);
manager = member.map(Member::getManager).flatMap(repo::getByName);
总结
- 阅读源码的过程中,我们更应该关注的是类的行为,或者说是对于数据的处理。而对于数据类的存放位置,可以放在次要的位置。
- 数据的表示可以是POJO类、值类型、数组、基本数据类型以及他们之间的组合,我们在编码过程中更应该注意其结构的可读性和是否选取 Immutable(不变)来表示。
- 关注数据的传递,可以说数据的传递表达了系统的架构。有时候虽然我们并不知道数据的传递过程,但是我们更在意数据传递的结果。
关于第 3 点举一个例子,使用@Order 注解可以为一组类进行排序,再阅读源码之前,我们虽然不知道是哪个具体的类去处理的,但是其处理的过程必然包括了以下过程:获取注解值,对原有类与排序号进行关联,一个通用的排序方法排序。
SpringBoot自动配置了许多类,我们虽然不知道具体的配置是如何映射到我们在容器中使用的Bean,但是可以确定数据的传递必然经历了 配置 → 解析 → 注册Bean的过程。从实用的角度出发,多数情况下,我们不必知道一个Bean的创建的具体逻辑,我们只是拿来就用,按需配置,我们需要了解的是这个Bean可以做什么。
事实上,Spring项目中,除了少数的方法类(比如各种**Strategy),随便拿出一个类,基本都可以看做容器类,后续我会结合一些代码再进行详细分析,敬请关注。
从Spring中学到的【2】--容器类的更多相关文章
- 从Spring中学到的【1】--读懂继承链
最近看了一些 Spring 源码,发现源码分析的文章很多,而底层思想分析的文章比较少,这个系列文章准备总结一下Spring中给我的启示,包括设计模式思想.SOLID设计原则等,涉及一些编程的基本原则, ...
- spring不依赖注入得到实体bean
如题,我们一般用spring的ioc,通过配置注入接口得到这个实现类,现在通过研究公司平台框架发现还有一种方法得到spring文件配置的bean方法,举个例子(注:这个ApplicationConte ...
- Spring Boot 学习笔记一(SpringBoot启动过程)
SpringBoot启动 Spring Boot通常有一个名为*Application的入口类,在入口类里有一个main方法,这个main方法其实就是一个标准的java应用的入口方法. 在main方法 ...
- spring源码分析系列3:BeanFactory核心容器的研究
目录 @(spring源码分析系列3:核心容器的研究) 在讲容器之前,再明确一下知识点. BeanDefinition是Bean在容器的描述.BeanDefinition与Bean不是一个东西. Be ...
- Spring Boot中Tomcat是怎么启动的
Spring Boot一个非常突出的优点就是不需要我们额外再部署Servlet容器,它内置了多种容器的支持.我们可以通过配置来指定我们需要的容器. 本文以我们平时最常使用的容器Tomcat为列来介绍以 ...
- spring IoC容器类接口关系梳理
整理了下spring中容器类的接口,用UML画了张图(并不十分严格按照UML标准,省略了些方法).
- 普通线程类获取service,controller等spring容器类
package com.zihexin.application.strategy; import org.springframework.beans.BeansException; import or ...
- test 测试spring容器类
- Spring源码分析——资源访问利器Resource之实现类分析
今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...
- Spring中bean的作用域scope详解
参考文献:http://blog.csdn.net/jacklearntech/article/details/40157861 http://www.cnblogs.com/qq78292959/p ...
随机推荐
- Bert不完全手册9. 长文本建模 BigBird & Longformer & Reformer & Performer
这一章我们来唠唠如何优化BERT对文本长度的限制.BERT使用的Transformer结构核心在于注意力机制强大的交互和记忆能力.不过Attention本身O(n^2)的计算和内存复杂度,也限制了Tr ...
- P3998 [SHOI2013]发微博 方法记录
原题链接 [SHOI2013]发微博 题目描述 刚开通的 SH 微博共有 \(n\) 个用户(\(1\sim n\) 标号),在这短短一个月的时间内,用户们活动频繁,共有 \(m\) 条按时间顺序的记 ...
- javaweb 导出文件名乱码的问题解决方案
fileName = new String(fileName.getBytes("ISO8859-1"), "UTF-8"); 或者 String finalF ...
- HTTPS实现原理分析
概述 在上一节中介绍了两种加密方法 对称加密 非对称加密 其中对称加密性能高,但是有泄露密钥的风险,而非对称加密相反,加密性能较差,但是密钥不易泄露,那么能不能把他们进行一下结合呢? HTTPS采用混 ...
- Collection接口中的方法的使用
add(Object e):将元素e添加到集合coll中size():获取添加的元素的个数addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中clear():清 ...
- 使用工厂方法模式设计能够实现包含加法(+)、减法(-)、乘法(*)、除法(/)四种运算的计算机程序,要求输入两个数和运算符,得到运算结果。要求使用相关的工具绘制UML类图并严格按照类图的设计编写程序实
2.使用工厂方法模式设计能够实现包含加法(+).减法(-).乘法(*).除法(/)四种运算的计算机程序,要求输入两个数和运算符,得到运算结果.要求使用相关的工具绘制UML类图并严格按照类图的设计编写程 ...
- Python学习笔记----列表、元组和字典的基础操作
文章目录 一.列表的基本操作 1.1 修改列表中的某个数据 1.2 获取某个元素的返回值(返回的是索引) 1.3 在列表中插入新的元素 1.4 删除列表中的元素 1.5 +和* 二.内置的函数和方法 ...
- Android 13 新特性及适配指南
Android 13(API 33)于 2022年8月15日 正式发布(发布时间较往年早了一些),正式版Release源代码也于当日被推送到AOSP Android开源项目. 截止到笔者撰写这篇文章时 ...
- python信息检索实验之向量空间模型与布尔检索
import numpy as np import pandas as pd import math def bool_retrieval(string): if string.count('and' ...
- 三、Python语法介绍
三.Python语言介绍 3.1.了解Python语言 Python 是1989 年荷兰人 Guido van Rossum (简称 Guido)在圣诞节期间为了打发时间,发明的一门面向对象的解释性编 ...