从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 ...
随机推荐
- 几个Caller-特性的妙用
System.Runtime.CompilerServices命名空间下有4个以"Caller"为前缀命名的Attribute,我们可以将它标注到方法参数上自动获取当前调用上下文的 ...
- python 运行错误收集
目录 global全局声明错误 global全局声明错误 SyntaxError: name 'is_login' is used prior to global declaration 解决办法:g ...
- uoj348【WC2018】州区划分
题目链接 直接讲吨吨吨给的标准做法吧.记\(f(i,j)\)表示各个州(可以重叠)的城市数量之和为i,这些州的并集为j的方案数,反正若有两个州之间有交集最后的\(|j|\)会不等于\(i\).有 \( ...
- 齐博x1给表单某个字段设置初始值
自定义表单虽然后台可以设置默认初始值,但是有时候想在前台动态设置初始值的话,可以在URL中添加该字段名,给他动态赋值即可.比如下面的price字段就是动态赋值的.
- python渗透测试入门——基础的网络编程工具
<Python黑帽子--黑客与渗透测试编程之道学习>这本书是我在学习安全的过程中发现的在我看来十分优秀的一本书,业内也拥有很高的评价,所以在这里将自己的学习内容分享出来. 1.基础的网络编 ...
- JVM学习笔记——垃圾回收篇
JVM学习笔记--垃圾回收篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的垃圾回收部分 我们会分为以下几部分进行介绍: 判断垃圾回收对象 垃圾回收算法 分代垃圾回收 垃圾回收器 ...
- Spring Boot 中使用 Swagger
前后端分离开发,后端需要编写接⼝说明⽂档,会耗费⽐较多的时间. swagger 是⼀个⽤于⽣成服务器接⼝的规范性⽂档,并且能够对接⼝进⾏测试的⼯具. 作用 ⽣成接⼝说明⽂档 对接⼝进⾏测试 使用步骤 ...
- 使用idea创建第一个Mybatis程序及可能遇到的问题
第一个Mybatis程序 思路:搭建环境->导入Mybatis->编写代码->执行 搭建环境 创建数据库 CREATE DATABASE `mybatis` USE `mybatis ...
- .NET应用开发之SQLServer常见问题分析
日常我们开发.NET应用时会使用SQLServer数据库,对于SQLServer数据库的日常开发有一些技能和工具,准备给大家分享一下. 一.场景1:SQLServer死锁分析 执行以下SQL,启用S ...
- 还在为数据库事务一致性检测而苦恼?让Elle帮帮你,以TDSQL为例我们测测 | DB·洞见#7
数据库用户通常依赖隔离级别来确保数据一致性,但很多数据库却并未达到其所表明的级别.主要原因是:一方面,数据库开发者对各个级别的理解有细微差异:另一方面,实现层面没有达到理论上的要求. 用户在使用或开发 ...