从 WebGL 到 WebGPU:给小朋友写一个 three.js 太阳系

本文是给家里小朋友做「太阳系漫游」网页(直达)过程中的技术调研与实践记录:先简单梳理浏览器 3D 技术的发展脉络,然后看两份有代表性的资料——three.js 官方的 llms.txt 和一个用 WebGPU 手写引擎的钓鱼游戏 Tidewater,最后拆解我们自己的太阳系架构与踩坑实录。全文无框架、零构建依赖,代码都在 GitHub。

一、浏览器 3D 的十五年

把时间线拉长看,网页 3D 大致经历了四个阶段:

  • 插件时代(2006–2011):3D 内容靠 Flash(Away3D)、Unity Web Player、Java Applet 这类插件承载,浏览器本身对 3D 一无所知。
  • WebGL 时代(2011–2022):2011 年 3 月 Khronos 发布 WebGL 1.0(基于 OpenGL ES 2.0),浏览器第一次原生拥有 GPU 渲染能力;同年 mrdoob(Ricardo Cabello)开源了 three.js。2017 年 WebGL 2.0 与 glTF 2.0 格式先后落地,前者带来实例化渲染等能力,后者统一了 3D 模型的「JPEG」标准。
  • WebGPU 时代(2023– ):2023 年 4 月 Chrome 113 默认启用 WebGPU——对标 Vulkan/Metal/DirectX 12 的现代图形 API,支持计算着色器(Compute Shader)和间接绘制,WGSL 取代 GLSL。three.js 随之推出 WebGPURenderer 和 TSL 着色语言。
  • AI 与 3D 汇合(进行中):一方面官方文档开始为 LLM 优化(下文的 llms.txt),另一方面 Text-to-3D / 图生 3D 模型工具开始进入生产流程。

工具链层面同样在演进:从「全局脚本 + 多个 script 标签」到 ES Module + Import Map,从手动管理矩阵到场景图/材质系统,从 GLSL 手写着色器到节点式着色语言。这些变化决定了今天写一个网页 3D 项目的「正确姿势」。

二、three.js 的 llms.txt:给 AI 看的官方文档

2.1 llms.txt 是什么

llms.txt 是 2024 年由 Answer.AI 提出的社区约定:网站在根路径放一个 Markdown 索引,供大语言模型快速定位权威内容——因为直接把整站 HTML 塞给模型,噪音太大。

three.js 官方文档现在提供了 threejs.org/docs/llms.txt,开头第一句就是「生成 three.js 代码时请遵循以下指南」。它同时承担两个角色:

  1. 防坑指南:用 WRONG/CORRECT 正反代码对照,纠正 LLM 最常犯的过时写法;
  2. 文档导航:按板块列出官方手册与 API 的链接,让模型(和人)按需深入。

2.2 结构概要

整份文件的组织如下:

板块 内容
Instructions for LLMs 四条强制规范:Import Map 写入方式、WebGLRenderer vs WebGPURenderer 的选择标准、TSL 着色语言、NodeMaterial 材质族
Getting Started Installation / Creating a Scene / Fundamentals / Responsive Design
Renderer Guides WebGPURenderer 指南
Core Concepts TSL 指南与参考、动画系统、模型加载、场景图、材质、纹理、灯光、相机、阴影
Essential API 按 Core / Scenes / Cameras / Renderers / Objects / Materials / Geometries / Lights / Loaders / Controls / Math 分类的 API 链接

其中最有时代感的两条规范:

① 引入方式——明确禁止 LLM 最爱生成的旧式写法:

1
2
3
4
5
6
7
8
<!-- WRONG:LLM 十有八九会生成这个 -->
<script src="https://unpkg.com/three@0.128.0/build/three.min.js"></script>

<!-- CORRECT:ESM + Import Map -->
<script type="importmap">
{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.186.0/build/three.module.min.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.0/examples/jsm/" } }
</script>

② 渲染器选择——默认用 WebGLRenderer 保证兼容性;只有需要 TSL 自定义着色器、Compute Shader 或节点材质时才上 WebGPURenderer(且必须 await renderer.init())。TSL 的目标之一是让同一份着色逻辑在两个渲染器间迁移。

给 AI 看的文档,本质上是把「这个生态认为的正确姿势」固化成机器可读的约束——对我们人类的价值是:它是这个生态当前最佳实践的一份浓缩清单。

三、Tidewater:看看 WebGPU 能把网页做到什么程度

如果说 llms.txt 是「怎么正确写」,那 Tidewater(作者 Daniel Greenheck,MIT 协议)就是「能写到什么程度」的样本:一个直接跑在 WebGPU + WGSL 上、自研渲染引擎、零框架的岛屿钓鱼游戏——驾船出海、抛竿、搏鱼、天黑前把渔获卖给码头边的 Joe。

几个让我印象深刻的技术点:

  • 水体:Tessendorf 谱方法的四级联 FFT 海洋(含泡沫、白帽、风条纹、涌浪),加上深度感知的破碎浪、浅水上冲流、船尾迹和海床焦散——基本是主机游戏的配置;
  • 大气与光照:基于物理的 Hillaire 2020 大气模型(太阳/月亮/星空)、体积云及其投影、级联阴影 + 屏幕空间接触阴影、GTAO 环境光遮蔽;
  • 后处理链:TAAU 时序超分 + 锐化、bloom、自动曝光、运动模糊、水下合成;
  • 工程化:Vite 构建、src/engine|ocean|sky|world|post|game 分层清晰、无头引擎冒烟测试、大量 ?noClouds/?noSim 类降级开关、动态分辨率。

页面有一条很诚实的提示:首次启动要编译数百个着色器,「可能需要一两分钟」,之后靠浏览器缓存加速。这其实是 WebGPU 应用当前普遍的代价——着色器编译与管线缓存策略,会直接影响用户的第一印象。

它和 llms.txt 一起构成了一个有意思的对照:three.js 在把「正确姿势」标准化以便普及,而 Tidewater 们在探索标准化之外的极限。两者都值得写业务代码时对照着看。

四、本项目:给小朋友的太阳系

有了上面的坐标系,再回头看这个项目要做什么:给 5~10 岁的小朋友做一个「太阳系漫游」——可以选星球抵近观看、切换视角看整个太阳系旋转、抵近看地月旋转、每个星球有中英双语说明。成品在这里(入口在博客导航)。

太阳系全景:太阳、轨道线、小行星带与八大行星

4.1 约束决定架构

三条约束几乎决定了所有技术选择:

  1. 观众在国内、以平板和手机为主 → Three.js 必须本地 vendored(不走 CDN),页面不能有国外依赖;
  2. 面向 5 岁小朋友 → 没有文字化 UI(零件/信息全是画出来的或图标化)、没有失败状态、暗面不能黑到看不见;
  3. 和游戏合集同一个部署体系(Hexo 产物仓库 + rsync)→ 页面放 source/games/solar-system/,用 skip_render: games/** 原样拷贝。

最终结构非常克制:

1
2
3
4
5
6
source/games/solar-system/
├── index.html 页面外壳(深空主题 + UI 面板 + 51LA 统计)
├── main.js 场景 / 交互 / 双语数据(约 500 行)
├── lib/three.module.min.js vendored three@0.170(约 660 KB)
├── tex/earth.jpg moon.jpg NASA 公有领域贴图(地球/月球)
└── tools/build-solar.mjs 打包自包含单文件 dist/solar-system.html

4.2 几个核心设计

数据驱动的天体表。太阳、行星、月球都是同一份 BODIES 数据表的实例:半径、轨道、自转、轴倾角、贴图类型,以及中英双语的说明文案。信息卡、底部胶囊按钮、射线拾取全部由这张表驱动——加一个天体就是加一行数据。公转周期按开普勒第三定律压缩(T ∝ a^1.5),水星飞快、海王星缓慢,比例关系是真的:

1
2
3
// 开普勒第三定律的玩具版:轨道半径决定角速度
const a = (simT * KEPLER) / Math.pow(body.orbit, 1.5);
sys.position.set(Math.cos(a) * body.orbit, 0, Math.sin(a) * body.orbit);

程序化贴图 + 真实贴图的混合。木星条纹、火星锈斑与极冠、水星陨石坑全部用 canvas 逐行生成(512×256 一次性生成,零外部资源);只有地球和月球用 NASA 公有领域贴图。小朋友看到的土星不是「贴图球」,而是条纹 + 大红斑 + 冰环的组合。

木星特写:程序化条纹与大红斑(NASA 贴图的地球/月球在文中另有展示)

自写轨道控制。拖拽旋转、滚轮缩放、双指捏合,加起来六十行——不引入 OrbitControls 附件,vendored 目录里只有一个 three 模块文件。聚焦某个天体时,相机会被摆到「太阳 → 天体」的延长线上(略抬高),保证看到的是被照亮的一面,地月同框时月球轨道环也一目了然。

抵近地球:地月同框,月球轨道环清晰可见

面向 5 岁的视觉兜底。环境光调到 1.0,让行星暗面呈深蓝而不是纯黑;行星名用 sprite 标签(中文 + 英文)随场景旋转,可一键关闭;所有交互目标都是大按钮。

4.3 单文件打包:Import Map 的另一种用法

和游戏合集一样,这个页面也要有「双击就能玩、微信转发就能开」的版本。做法是把 three.js 整个模块文件变成 import map 里的 data URL:

1
2
3
4
5
<script type="importmap">
{ "imports": { "three": "data:text/javascript;base64,..." } }
</script>
<script>window.TEX_URLS = { earth: 'data:image/jpeg;base64,...', moon: '...' };</script>
<script type="module">/* main.js 原样内联 */</script>

main.js 里的 import * as THREE from 'three' 一个字不用改,贴图路径在打包时替换为 data URL。最终 dist/solar-system.html 约 1.9MB,无任何网络依赖。

4.4 踩坑实录(保存时间最长的三条)

坑一:白球事件——「赋值写在了错误的重构里」。上线后实测地球是纯白球体。排查链路:屏幕中心射线拾取确认「白球确实是 Earth 网格」→ 浏览器内采样贴图文件确认「文件和解码都正常(2048×1024,均值 RGB 84,92,105)」→ 材质探针确认 map 已绑定——最后发现是一次重构把 earthMesh.material = realEarth 这行赋值弄丢了:带贴图的材质创建出来了,但从未挂上网格,场景里一直渲染的是占位用的白球。教训:「材质已创建」和「材质已挂上网格」是两件事,肉眼断言不可靠,要用射线拾取+像素采样这种可复现的证据链。

坑二:three r155+ 的物理光照单位。PointLight 的 intensity 语义在 r155 之后变成物理单位,旧教程里的数值直接照抄会过曝——地球被冲成白球有一半功劳在它。最终方案:点光 1.15 + 环境光 1.0 + ACES 色调映射(exposure 0.9),过曝和死黑两头都压住。

坑三:无头测试里 rAF 是冻结的。自动化截图时 requestAnimationFrame 驱动的动画可能一帧都不跑。解法和官方测试套件思路一致:暴露一个手动推帧的钩子,测试里循环调它——这比在测试环境里折腾虚拟时间的 rAF 可靠得多。

五、结语

回到开头的时间线:从插件时代的 Flash 到今天一个 5 岁小朋友可以在 iPad 上拖动旋转的太阳系,浏览器 3D 用十五年走完了「能力补齐 → 生态标准化 → 极限探索」的三段路。three.js 的 llms.txt 代表前两段的沉淀——把正确姿势文档化;Tidewater 代表第三段——在标准之上把实时渲染推到主机游戏的配置。

对我们这种「给小朋友做一个页面」的项目来说,结论很简单:用 three.js + 本地 vendored + 数据驱动的架构,就在「正确姿势」的安全区里;而 Tidewater 们展示了这条路的天花板有多高。

参考

虚无主义的四个阶段——陀思妥耶夫斯基与 AI 时代

写在前面:这篇文章是写给程序员朋友的。

你说你学文学和心理学经典,是为了思考「前人面对巨大的社会变革、生产力发展时困惑的东西」。这个动机本身就带着 AI agent 时代的印记 —— 我们大概正站在一次结构上 comparable 的变革边上。

那么先去找一个已经站过同样位置的人。陀思妥耶夫斯基 1860 年代面对的处境,和今天的技术冲击在结构上高度相似:理性主义许诺的乌托邦破产了,工业化把城市变成了道德真空,而人被迫直面自己究竟是不是自由的。 这篇文章是我读完《地下室手记》《赌徒》之后整理的一份接受史笔记,加一份书单。


一、陀思妥耶夫斯基为什么被追崇

1.1 一个反直觉的判断

陀氏的思想性不在于他写出了什么答案,而在于他拒绝提供答案。

他一生只反复问一个问题,写在给友人的信里:「上帝是否存在」 —— 他自觉又不自觉地为这个问题苦恼了一辈子。但这个问题在他笔下从来没有被回答过。他做的是另一件事:把这个问题逼到每一个普通人的胸口上。

1.2 他发明了一种「不给出路的写法」

1929 年,苏联文艺理论家巴赫金在《陀思妥耶夫斯基创作问题》中提出「复调」(полифония,原为音乐术语):

「有着众多的各自独立而不相融合的声音和意识,由具有充分价值的不同声音组成真正的复调……众多的平等地位的意识连同他们各自的世界,结合在某个统一的事件中,而互相间不发生融合。」

在托尔斯泰那里,作家是一个上帝般的声音 —— 托尔斯泰本人太想用文学回答世界,所以《战争与和平》里全是他的议论,直接说出来,笨拙而专断。以赛亚·伯林用「狐狸与刺猬」的比喻暗示了这一点:刺猬(想用一个中心思想解释一切的类型)在陀氏笔下全都失败了。

陀氏的做法是把所有声音平摊:

  • 《卡拉马佐夫兄弟》里,没有一个立场是作者的立场。伊万、阿廖沙、德米特里三兄弟不是「三种性格」,是三种世界观,且都不能被驳倒。
  • 伊万说「孩子上楼时人人都该为他负责」;阿廖沙说「为父者的荣耀应传给孩子」;德米特里什么理论都不信,只知道他还爱着、还痛苦着 —— 三人加起来才是完整的人。
  • 而译者曾思艺在《地下室手记》中加了一篇 12000 字的译后记,主题就叫「身份焦虑与身份认同」—— 这是外部读者给他定的题。陀氏自己没给,这道题是读出来的。

巴赫金进一步指出,陀氏的独白从来不是真正的独白 —— 他的每句话都在预演一个他者的回应,所以地下室人的每句话都预埋着对看不见的反驳者的攻击。

这一层后来被梅洛-庞蒂、萨特、波伏娃全部接走。

1.3 他的三个核心问题,今天一个都没过时

(1)自由意志:人是自由的,还是被环境与欲望决定的?

《地下室手记》是对车尔尼雪夫斯基《怎么办?》的直接回应 —— 那个时代的「合理利己主义」相信人可以算清利害、选最优解。陀氏的反驳是:人有一种东西就是宁可受苦也不选最优解。

地下室人最后一句:

「我宁要这份折磨,不要这份满足。」

这不是反理性,是把理性的边界划出来。现代心理学称之为「动机性推理」(motivated reasoning)—— 你想要的结论影响了你如何组装证据。Kunda 1990 年那篇经典论文就在讲这个机制。陀氏在 1864 年已经把这个机制写成了文学。

(2)上帝死了之后,人会不会崩溃?

《罪与罚》拉斯柯尔尼科夫杀人的理由不是穷、不是恨,是**「我要证明自己能越线」**。他杀人之后没成为拿破仑,只成了生病的动物。书中反复出现一句话:

「痛苦和折磨对于心灵总是有益的。」

尼采读到这里的二十余年后(1888 年《偶像的黄昏》)说,陀氏是「唯一让我有所得的心理学家」。

《卡拉马佐夫兄弟》里的「宗教大法官」是同一问题的极端推演:一个大人物向被囚的基督提议 —— 放弃自由,换取幸福与面包。基督没有回答。

「人们不需要面包,他们需要自由。」

这一段是二十世纪所有关于「自由 vs 安宁」的辩论的源头。

(3)他人是否真的理解我?

这是最被低估的一层。陀氏笔下没有一个完整的人,人只能被他人从外面「猜」出来。他的世界观里,人与人之间隔着一道永远无法穿透的墙 —— 你永远只能得到他人的外部视角,而这个视角必然是不完整的、可能是歪曲的。

所以陀氏提供的是问题意识,不是答案。而一个时代需要问题,不需要答案。 这才是他被反复重读的真正原因。

1.4 一个必须承认的悖论

陀氏的世界观是反现代的。

他支持沙皇、拥护东正教、排斥革命、厌女、把希望寄托在顺从与信仰上。1930 年代苏联批判他并非全无根据。

他 1849 年还是个同情空想社会主义的左翼青年(因参加彼得拉舍夫斯基小组被捕,经历「假死刑」后流放西伯利亚四年),1849 年后成了自己年轻时候的对立面 —— 他对车尔尼雪夫斯基的批判狠到被称为「祖国自觉的敌人」。他 1861 年为废奴农奴制「欢欣雀跃」,1877–78 年俄土战争期间跑到教堂为俄国人民祈祷,晚年写「我们的国家本质不是征服,而是爱」。

所以「被追崇」这件事本身就是理解他的线索:几乎所有重要读者 —— 尼采、卡夫卡、弗洛伊德、萨特、纪德 —— 读的都是他没打算让人接受的部分。他们从他身上挖出了虚无主义、弑父、自由意志的困境,然后把他的基督教结论绕过去。


二、陀氏的生平

费奥多尔·米哈伊洛维奇·陀思妥耶夫斯基(Фёдор Михайлович Достоевский,1821.11.11 – 1881.2.9),俄国作家。

2.1 家世与童年

出生于莫斯科一个医生家庭。父亲是退伍军医,母亲是慈祥开明的家庭主妇。母亲在他 10 岁前就开始给他读普希金等俄罗斯文学 —— 陀氏的文学启蒙几乎全部来自母亲。

10 岁时父亲获得贵族爵位,在外省购置土地庄园,拥有几十名农奴。童年寒暑假常在庄园度过,家道几经起落,也从小听惯了穷人病人的哭声。

16 岁那年,母亲死于肺结核;两年后父亲去世 —— 一说自然死亡,一说被农奴杀害。

2.2 癫痫

陀氏从小患癫痫,9 岁首次发病,终生伴随。病源至今有争论:一说是遗传,一说与父亲嗜酒、性情暴烈的家庭环境有关。

从 39 岁开始,他把自己每一次发作记录在一个笔记本上,直到 59 岁去世,共记录 102 次癫痫发作,平均约每十周一次。

关于发作前一刹那的濒死体验,他在《白痴》中借梅什金之口讲过 —— 那段著名的「瞬间被异光照亮」的描写。他自己加了一句黑色幽默:「患癫痫症的穆罕默德大概就是在这一秒钟内,还不等被碰倒的水罐流出水来,就察看了真主的所有住所。」

癫痫深刻影响了他的文学:他笔下几乎每部重要作品都有癫痫发作案例 —— 梅什金公爵、基里洛夫、斯麦尔佳科夫、拉斯柯尔尼科夫的热病。纪德据此推测,癫痫对陀氏伦理道德观的形成起了不容忽视的作用。

2.3 四个创作时期

时期 代表作 特征
1846–1848 起步 《穷人》《双重人格》《白夜》 受空想社会主义影响的左翼青年;《穷人》获别林斯基赞赏,被誉为「第二个果戈里」
1849–1859 转折 假死刑、西伯利亚四年苦役、《死屋手记》《被侮辱与被损害的》 思想转变的起点。他原本反感农民囚犯,直到有一天想起 9 岁时农奴马列伊护送他回家的故事 —— 他才意识到这些人外表粗暴但内心善良、有根深蒂固的东正教情怀
1860–1870 高产 《地下室手记》《赌徒》《罪与罚》《白痴》《群魔》《少年》 1864 年游历欧洲见识西方文明衰落后写成《地下室手记》,被视为此后所有创作的宣言。1866 年为躲债速记口述《赌徒》,速记员安娜·斯尼特金娜后来成为他第二任妻子
1871–1881 成熟 《卡拉马佐夫兄弟》 集大成之作,完稿付印;他曾计划写续集让阿廖沙的故事成为正传(未动笔)。1881 年 2 月 9 日因搬动书柜导致肺血管破裂去世,终年 59 岁

2.4 生平中的几件大事

  • 1849 年 4 月 23 日 — 因参加彼得拉舍夫斯基小组被捕,在谢苗诺夫校场执行「假死刑」,遇赦后流放西伯利亚鄂木斯克服四年苦役。苦役营里的囚犯农夫马列伊成了他思想转变的关键
  • 1866 年 — 与出版商签下苛刻合同:一年内交不出新的长篇,此后九年全部作品的版权归出版商。为脱困请速记员口述《赌徒》
  • 1867–1871 年 — 出国躲债四年(主要在德国),欧洲的见闻促成《群魔》
  • 与托尔斯泰失之交臂 — 托尔斯泰当时已隐居亚斯纳雅波良纳,两人一生未见。托尔斯泰在陀氏去世后对斯特拉霍夫说:「突然间他死了,我才了解他是我最最亲近、亲爱又需要的朋友……我失去了支柱。我仓皇失措,后来才明白,他对我来讲多可贵,我哭过了,而现在还想哭。」陀氏去世前二十天两人本有一次机会见面 —— 斯特拉霍夫见到托尔斯泰,听完讲座后先行离开,陀氏责备他:「至少我可以看他一眼!」

三、接受史:从 1900 年到现在

一百二十多年间,同一个作家被反复重新解释 —— 每一次读法都带着当时代的病。

3.1 1900–1913:德语世界的发现

1887 年,尼采在法译本《地下精神》中描述初读《地下室手记》的感受:「读得我几乎陶醉在喜悦中。」《偶像的黄昏》中他写道:

「陀思妥耶夫斯基是唯一让我有所得的心理学家,他是我生命中最美妙的好运之一。」

1913 年,卡夫卡在给未婚妻菲莉斯的信中,把陀氏列为与自己「有血亲关系的四人」之一 —— 格里尔帕策、陀思妥耶夫斯基、克莱斯特、福楼拜。卡夫卡的藏书中有《陀氏书信》《卡拉马佐夫兄弟》《罪与罚》《赌徒》,1914 年德文全集面世后他得以通读。

「在陀思妥耶夫斯基的棺材后面,大学生们想负担起他的枷锁。」—— 卡夫卡日记,1914.3.15

两人相似的地方惊人:病(卡夫卡肺结核 vs 陀氏癫痫)、与父亲的关系、罪疚感、以及「双重身」母题(《变形记》开头与《双重人格》高度呼应)。区别是:陀氏笔下的人还是社会中的例外,卡夫卡笔下的人已经成了现代的常态。

3.2 1920s–1940s:中国翻译潮

1920 年 5 月,上海《国民日报》副刊连载《贼》—— 已知最早的陀氏中译文。

1926 年 6 月,韦丛芜译《穷人》由北京未名社出版,这是首个中译本,鲁迅评价「很弥补了些遗憾」。此书到 1947 年累计再版 12 次。

1931 年是译介的一个高峰:

  • 韦丛芜译《罪与罚》(未名丛刊,影响甚广)
  • 李霁野译《被侮辱与被损害的》(商务印书馆,1984 年译文社《陀思妥耶夫斯基选集》仍选用此译本)
  • 洪灵菲译《地下室手记》(上海湖风书局)—— 第一部中文译本
  • 周起应译《大宗教裁判官》(《青年界》第 1 卷第 5 期)—— 单独抽出「宗教大法官」一节翻译,这个选择本身说明它已被当作独立的哲学文本

1947 年 10 月,耿济之从俄文原文直译的《卡拉马佐夫兄弟》全译本由上海晨光出版 —— 中国第一部直接从俄文翻译的陀氏作品。

为什么鲁迅这一代人急着译他? 陀氏属于「专制与革命对抗」的俄国,属于「抵抗压迫、求自由解放」的东欧 —— 当时中国学者把他看作被压迫民族的灵魂代言。李霁野的说法最清楚:介绍俄国和东欧文学,是为了「振作国民精神,唤起沉睡中的国人」。

3.3 1929 / 1963:巴赫金的「复调」

巴赫金 1929 年的《陀思妥耶夫斯基创作问题》(1963 年扩充再版为《陀思妥耶夫斯基诗学诸问题》)把复调从音乐术语变成文学理论核心,再变成一个跨学科工具 ——

电影(《公民凯恩》)、宗教哲学(布朗肖「两种声音」理论)、女性主义批评、《红楼梦》和鲁迅小说研究,都借用了这个框架。

这个理论的锋利处在于:它不是说陀氏写得「好」,而是说他的小说的结构本身就是哲学主张 —— 众多意识平等并存、互不融合,作者退到「近似旁观者的地位」。这在文学史上是没有先例的。

争议也一直存在:如果每个声音都是独立主体,作者在哪里?复调理论是否滑向相对主义?—— 这个问题到今天还没定论。

3.4 1930s:苏联的批判

社会主义现实主义时期,陀氏因「宣扬宗教与顺从」被长期贬斥。这是他接受史上最冷的一段。

连卢卡奇都不能给他好评。《小说理论》(1916)虽然承认陀氏「属于新世界」,但他在 1943 年的专文《陀思妥耶夫斯基》里批评他:与其说是一位小说家,不如说更像一位典型的政论家;与其说写的是小说,不如说写的是长篇政论文章。

3.5 1928–1930:弗洛伊德的解剖

弗洛伊德在《陀思妥耶夫斯基与弑父》中把作家拆成四重人格:诗人、神经症者、道德家、罪人。

他的判定:创作家的陀氏无可置疑,《卡拉马佐夫兄弟》是「有史以来最卓越的小说」,与《俄狄浦斯王》《哈姆雷特》并列 —— 因为三者都处理同一个主题:弑父。而弑父是人类与个体最原初、最本质的罪,也是罪疚感的主要来源。

然后他做了一件冒犯整个文学界的事:把陀氏本人当成病人来治。 他判定陀氏的癫痫是弑父愿望的躯体化,癫痫发作是他用来赎清弑父罪的方式,赌博是自我惩罚,因此才能写成小说。

精神分析界的反应很微妙 —— 逻辑上说得通,但把人变成病例,文学批评界反弹极大。随后理查德·科恩堡等人用这套理论反过来读陀氏小说,于是「双重身」成为解读《罪与罚》的钥匙。

这场争论留下的真问题,是陀氏作品比精神分析提供了更丰富的动机理论。 科恩堡自己也承认:知道陀氏有弑父冲动或癫痫,对理解小说毫无帮助 —— 知道鲁迅的童年或沈从文的经历,不等于你读懂了《阿Q正传》和《边城》。

3.6 1930s–1960s:存在主义的祖宗

萨特、波伏娃、卡缪把陀氏当作存在主义的先驱。法国中学生把《地下室手记》当作哲学入门 —— 不是文学,是哲学教材。

黑塞说过一句耐人寻味的话:

「欧洲的年轻世代,特别是德国的年轻人,是把陀思妥耶夫斯基视为他们的典范,而不是歌德或尼采。」

加缪在《反抗者》里把陀氏与虚无主义反抗放在同一张图里讨论。陀氏提供了存在主义的核心焦虑 —— 自由是否真实、他人是否可理解、上帝缺席后如何自我支撑 —— 却给出了存在主义不接受的答案(顺服与信仰)。

3.7 1960s–今:祛魅与再祛魅

一百年后,批评开始清算他:

  • 政治立场:晚年的沙皇歌颂、对革命与集体狂热的排斥
  • 厌女倾向:《地下室人》《卡拉马佐夫》里的女性形象
  • 回避现实责任:他悼念上帝消逝、人类走向荒原,却对沙俄对殖民地和社会底层的掠夺选择性失明
  • 李泽厚(2021 年逝世)说他最爱的两位作家是鲁迅和陀思妥耶夫斯基 —— 理由是「同样能带给人灵魂极其强烈的冲击」

2021 年诞辰 200 周年,广西师范大学出版社推出「下一个 200 年,再读陀思妥耶夫斯基」系列,业内提出的批评是:我们纪念他的伟大,但被神化的他是一个符号化的他。

同一时期有一件耐人寻味的事:巴赫金在《卡拉马佐夫兄弟》的解读中指出 —— 陀氏笔下越是次要的人物,议论往往越不容忽视;甚至德米特里这个「常人眼中的恶棍和杀人犯」却有着真正高尚的心灵,而阿廖沙这个「浊世中的圣徒」却并非完美。

这正是 2021 年那批批评的核心主张的镜像:一个真正的祛魅读法,不是把陀氏从神坛拉下来打成反动作家(那还是另一种崇拜),而是把他笔下那些有瑕疵的人,还原成完整的人 —— 包括加缪、包括陀氏本人。


四、虚无主义的四个阶段

你现在同时在读四本书:陀氏《地下室手记》、陀氏《赌徒》、哈耶克《到奴役之路》、尼采《查拉图斯特拉如是说》。这不是巧合 —— 这条路线有名字:虚无主义的四个阶段。

第一阶段 —— 陀氏:虚无主义的诞生

尼采 1887 年读到《地下室手记》时说「读得我几乎陶醉在喜悦中」,因为他在里面看到了自己。

陀氏写的是虚无主义的起点 —— 人意识到自己没有根基之后的第一个反应:不是建设,而是自毁。

《地下室手记》里的核心机制是「自我折磨的逻辑」:地下室人反复论证,我明知一切庸俗,还是宁愿做两件蠢事也不愿幸福。他自嘲这套逻辑:至少能做到的是惩罚自己,因为「受苦的权利」是他仅剩的东西。

他在第一章末尾说的那句话是整个环节的钥匙:

「你们各位,我虽是疯子,但我的清醒还未丧失。」

他在第一句就拒绝当权威,同时拒绝当疯子。 他不给你答案,他给你一副你没法推卸的镜子。

而他最终没能追上莉莎 —— 那是一个人在虚无面前第一次举起手又放下的时刻。

第二阶段 —— 尼采:虚无主义的自觉

《查拉图斯特拉如是说》开篇就是「查拉图斯特拉下山」—— 死了的上帝从坟墓里走出来,把这消息带下山。上帝已死,人类必须自己决定自己是谁。

而你现在读的这本书,最关键的段落发生在第二部分的下山篇讲完上帝之死后:查拉图斯特拉下山,发现的不是解脱,是空虚。市场上每个人都被「虚无」这个对立面锁住,被迫与对立面一起存在。

尼采推进了陀氏没有推进的一步:陀氏的问题是「人是否可以没有上帝」,尼采的问题是「上帝死后,人还能不能爱」。

陀氏用基督教回答,尼采用超人回答。有后世研究者如此概括这两条路:「尼采预感到了一个顶点;陀思妥耶夫斯基只在那里看到失败。」—— 陀氏 1881 年去世,两人从未谋面,这句「对比」本身就出自后人。

他们是同一起点,两个方向。

第三阶段 —— 加缪:虚无主义的反抗

《西西弗神话》(1942)完成第三步:

既然没有意义,那就用反抗本身赋予意义。 西西弗推到山顶,石头滚下来,他下山,然后再来 —— 荒谬本身被接受,命运被接受,但人没有被碾碎。

加缪与陀氏的区别很清晰:

  • 陀氏:我虽疯,但我的清醒还未丧失
  • 加缪:我不需要答案,我只需要继续推石头

第四阶段 —— 哈耶克:虚无主义的政治经济学

《到奴役之路》(1944)完成最后一步 —— 把虚无主义从个体心理学推进到制度层面。

哈耶克的核心论证:理性不等于知识。 没有人能计算整个社会的所有知识,所以中央计划经济必然失败 —— 而乌托邦式的理性设计,本质上就是「我以为我能算清一切」这种僭妄的放大版。

而《到奴役之路》和《地下室手记》讲的是同一件事的两半:

  • 《地下室手记》:人不能被信任,因为人的理性不足以预测自己的行为
  • 《到奴役之路》:社会不能被设计,因为没有人的理性足以预测整个社会

陀氏和哈耶克是同一只眼睛的两半 —— 一个看到个人内部的裂缝,一个看到社会结构的裂缝。两者合起来,就是那个时代最危险的东西:理性主义乌托邦。

而陀氏身处的 1860 年代,正是这个故事的第一幕:车尔尼雪夫斯基《怎么办?》这样的合理利己主义乌托邦刚刚失败,陀氏随即写出《地下室手记》来拆它。 80 年后哈耶克在西方重做同一件事。

把这条路线串起来:

1
2
3
4
5
6
7
陀氏《地下室手记》        —— 虚无主义诞生(人为何自毁)
↓ 尼采从里面读出了超人
尼采《查拉图斯特拉如是说》 —— 虚无主义自觉(上帝已死,人还能爱吗)
↓ 加缪推进到行动
加缪《西西弗神话》 —— 虚无主义反抗(无意义中如何坚持)
↓ 哈耶克推进到制度
哈耶克《到奴役之路》 —— 虚无主义政治经济学(理性不等于知识)

与 AI agent 时代的关系

写这一段是因为你提到了 AI agent。每一阶段都有一个技术前提:

阶段 当时的焦虑 技术前提
陀氏 1860s 理性主义乌托邦破产,城市是道德真空 工业化 / 铁路 / 巴黎改造
尼采 1870s–80s 价值崩塌,人被虚无吞噬 实证主义 / 生物学视角 / 铁路时代
加缪 1940s 战争、屠杀,人被荒谬定义 现代战争 / 极权
哈耶克 1944 计划经济即将吞没社会 计划经济 / 极权体制

而 AI agent 带来的结构性变化是:第一次,「人的独特性」本身成为可被替代的生产要素。

这是之前四个阶段都没有过的:

  • 陀氏担心的是「人被理性计算」—— 他担心算法会告诉他该怎么活,但他还是那个自己
  • 哈耶克担心的是「经济被计算」—— 他担心社会运转脱离人的自发互动,但决策主体还是人
  • AI agent 的问题不是人会被算清,而是人被算清之后,价值锚点本身失去外部支撑。

所以陀氏的问题仍然有效:如果自由意志不能被证明,人还能不能为「自己的选择」负责? 「地下室人」的反叛逻辑放在 agent 时代是新的:当我的表达被模型生成,我表达的是我还是它?


五、后续书单

按阅读顺序排列,每本标注它在虚无主义谱系中的位置。

第一梯队:先把地基打好

1. 陀思妥耶夫斯基《死屋手记》

  • 为什么读:他思想转变的实证记录。 一个左翼青年在西伯利亚苦役营里被农民囚犯改变 —— 9 岁时农奴马列伊护送他回家的故事,是这个转变的关键线索。
  • 顺序:建议放在《地下室手记》之前读,读完才明白他后来在写什么。

2. 陀思妥耶夫斯基《地下室手记》

  • 生平:1821–1881,俄国。1849 年假死刑后流放西伯利亚四年,1860s 后期转向宗教思想。
  • 为什么读:虚无主义的起点。 1864 年,对理性利己主义的第一次系统反驳。
  • 一句话抓:「我虽是疯子,但我的清醒还未丧失。」

3. 陀思妥耶夫斯基《卡拉马佐夫兄弟》

  • 同一作者。这本是他集大成之作,未完成,第二部是他最看重的作品,去世前他说「作为作家,我有很多缺点……我能表达的只是我想表达的东西的二十分之一」。
  • 为什么读:复调小说的巅峰,宗教大法官、伊凡的「一切都是许可」、阿廖沙的出走都在这里。
  • 一句话抓:「人们不需要面包,他们需要自由。」

第二梯队:虚无主义的自觉与反抗

4. 尼采《偶像的黄昏》(建议先读这一本)

  • 生平:1844–1900,德国。1869 年 24 岁便出任巴塞尔大学语文学教授,1879 年因健康恶化辞职,此后靠遗产与友人资助写作。
  • 为什么读:先读论陀氏的那一章和「永恒轮回」,理解他和陀氏的同源性。只读《查拉图斯特拉》会读成鸡汤。

5. 尼采《查拉图斯特拉如是说》

  • 为什么读:虚无主义的自觉。 上帝已死,人还能不能爱?
  • 一句话抓:「上帝已死,我们必须重估一切价值。」

6. 尼采《道德谱系学》

  • 为什么读:补上「权力与道德」这一维。 解释为什么尼采要区分「主人道德」和「奴隶道德」。陀氏在这里被清算 —— 尼采认为「奴隶造反」的哲学正是《地下室手记》那套东西。

7. 加缪《西西弗神话》

  • 生平:1913–1960,阿尔及利亚出生。1935 年(21 岁)加入法国共产党,两年后退出;1943 年参加抵抗运动,1944 年起任地下报纸《战斗报》主编;1952 年因《反抗者》与萨特决裂,1957 年获诺贝尔文学奖。
  • 为什么读:虚无主义的第三步 —— 反抗而不是建设。
  • 一句话抓:「人们必须设想西西弗是幸福的。」

第三梯队:把虚无主义推进到政治经济学

8. 哈耶克《到奴役之路》

  • 生平:1899–1992,奥地利。20 世纪 30 年代参与维也纳学派论战,1938 年移居英国,1974 年获诺贝尔经济学奖。
  • 为什么读:把「人不可靠」从个体推到制度 —— 理性不等于知识,所以任何中央设计都必然失败。
  • 一句话抓:「通往奴役的路,是由追求善意开始的。」

9. 波普尔《开放社会及其敌人》

  • 生平:1902–1994,奥地利 → 英国。维也纳学派成员,1930 年代定居英国。
  • 为什么读:历史决定论与历史虚无主义的论战,和陀氏关于「历史是否有意义」的问题直接对应。也是对哈耶克的理论前提做批判性补充。

第四梯队:心理学 —— 把虚无主义落回个体

10. 弗洛伊德《陀思妥耶夫斯基与弑父》

  • 生平:1856–1939,奥地利。1902 年创立维也纳精神分析学会(周三心理学会),1938 年逃离纳粹到伦敦。
  • 为什么读:不推荐当结论读,当争议读。 精神分析把陀氏当病人解剖,试图解释他的癫痫和赌瘾 —— 与其说成功,不如说暴露了理论的边界。
  • 一句话抓:「在分析创造家陀思妥耶夫斯基之前,分析必须放下武器。」

11. 阿尔伯特·埃利斯《理性生活指南》(A New Guide to Rational Living)

  • 生平:1913–2007,美国。1942 年创立理性情绪行为疗法(REBT)。
  • 为什么读:现代版的「地下室人」治疗手册 —— 挑战那些「高贵的痛苦」,和陀氏、哈耶克、加缪站在同一边。
  • 一句话抓:「人的不快乐,多来自自我破坏。」

第五梯队:中国语境(陀氏译介的特殊路径)

12. 鲁迅《且介亭杂文二集》中的陀思妥耶夫斯基相关文字

  • 为什么读:鲁迅称陀氏为「人类灵魂的伟大审问者」(《陀思妥夫斯基的事》,1935),但他最看重的不是宗教,是「对于被侮辱与被损害者的同情」——1931 年周起应单独译出《大宗教裁判官》(见 3.2 节),说明它已被当作独立的哲学文本。

13. 李泽厚《美学四讲》/《思想历程》

  • 生平:1930–2021,中国。1950 年代起研究美学,为「美学热」核心人物。
  • 为什么读:李泽厚说自己最爱的两位作家是鲁迅和陀思妥耶夫斯基,理由是「同样能带给人灵魂极其强烈的冲击」。他的「救亡 + 人性」双线结构,本身就是陀氏的中国式接受。

六、最后:你为什么读文学会有用

你说你学文学和心理学经典,是为了思考「前人面对巨大的社会变革、生产力发展时困惑的东西」。这个动机本身就是对的,但我想把它说得更具体一点 —— 文学对你这个职业的价值,不在于「增加人文素养」,而在于它训练了一种技术能力:

在信息不完整、目标不清晰、代价不可逆的情况下,如何判断一个人的动机。

这是 agent 时代最稀缺的能力。因为:

  1. AI agent 的瓶颈不在推理能力,在动机判断。 模型可以推一万步,但你仍然要判断:这一步该不该走、这个「诉求」是真的还是被生成的、这个人的痛苦值不值得投入资源。这些判断在代码里没有函数可调。
  2. 文学是最密集的动机数据库。 陀氏写了六本长篇,本质上是在穷举人类的自欺方式:我是为了自尊而受苦(地下室人)、我是为了理论而杀人(拉斯柯尔尼科夫)、我是为了绝望而放弃(伊凡)。这些模式在系统中依然以 code review 的形式出现。
  3. 哲学给的是边界,不给答案。 陀氏不给答案,这恰恰是他比其他作家更耐用 160 年的原因。当技术能解决所有可计算的问题,剩下的都是不可计算的问题 —— 而那些问题才是你要做的决策。

所以这条阅读路线的实际用途是:当你面对一个 AI 系统、它正在执行某个模糊目标、而你说不清它到底在追求什么的时候,你有一整套隐喻可以调用。 地下室人、卡拉马佐夫三兄弟、宗教大法官 —— 这些不是文学比喻,是一整套诊断工具。

这是把陀氏当思想家读的最高回报。


延伸阅读:Joseph Frank《陀思妥耶夫斯基传》(五卷本,中译本由戴大洪、刘佳林等译);James Miller《陀思妥耶夫斯基与伊凡的信仰》(1986);Ronald D. Powers《陀思妥耶夫斯基的受难者》(1991);巴赫金《陀思妥耶夫斯基诗学诸问题》。

postgre

? list all the commands
\l list databases
\conninfo display information about current connection
\c [DBNAME] connect to new database, e.g., \c template1
\dt list tables of the public schema
\dt .* list tables of certain schema, e.g., \dt public.*
\d table-name
Then you can run SQL statements, e.g., SELECT * FROM my_table;(Note: a statement must be terminated with semicolon ;)

spring-event

SpringApplicatonEvent 启动过程中的事件处理

Event-Process

ApplicationStartingEvent

  • 应用启动开始时,刚完成SpringApplication的实例化
  • 接着完成如下事:
    • 推断webApplicationType
    • 给字段initilizers赋值:通过SPI方式加载spring.factories中的配置
    • 给字段listeners赋值:通过SPI方式加载监听器
  • 发送ApplicationStartingEvent事件,触发对应的监听器的执行
    • 默认是同步执行
    • 默认的监听器是LoggingApplicationListener、BackGroundPreinitializer

ApplicationEnvironmentPreparedEvent

Environment是Spring的基础元数据。
完成大事记:

  • 封装命令行参数到ApplicationArguments
  • 创建ConfigConfigurableEnvironment实例
  • 发送ApplicationEnvironmentPreparedEvent事件,触发对应的监听器的执行
  • 有九个监听器处理该事件
    • todo

ApplicationContextInitializedEvent

ApplicationPreparedEvent

ApplicationStartedEvent

ApplicationReadyEvent

原文连接

jvm

Memory

  • NIO Direct Memory
  • native Memory
  • Java 堆

使用Perftools来分析JVM的native Memory分配情况,查看分配结果,
cat pf2.txt | sort -n -r -k4 | more显示每个函数分配的内存

动态代理

  • 字节码创建方式:JDK动态代理通过JVM实现代理类字节码的创建,cglib通过ASM创建字节码
  • 对被代理对象的要求:JDK动态代理要求被代理对象实现接口,cglib要求被代理对象未被final修饰
  • 代理对象创建速度:JDK动态代理创建代理对象速度比cglib快
  • 代理对象执行速度:JDK动态代理代理对象执行速度比cglib快

Tools to analysis

jmap

创建堆转储文件,可通过MAT工具分析

jstat

maven

Maven 学习笔记

设计目标

  • 项目是可重复使用,易于维护、更容易理解的综合模型
  • 工具插件化、可交互

Maven资源库

  • Maven本地仓库, 默认.m2目录
  • Maven中央存储库,maven下载所有依赖的默认位置
  • Maven远程仓库:在pom中添加repo
  • 搜索仓库的顺序:
    1. 在 Maven 的本地仓库搜索 log4j
    2. 在 Maven 中央存储库搜索 log4j
    3. 在 Maven 远程仓库搜索 log4j(如果在 pom.xml 中定义)
1
2
3
4
5
6
 <repositories>
<repository>
<id>java.net</id>
<url>https://maven.java.net/content/repositories/public/</url>
</repository>
</repositories>

TIPS

不执行测试用例

在使用mvn package进行编译、打包时,Maven会执行src/test/java中的JUnit测试用例,有时为了跳过测试,
会使用参数-DskipTests和-Dmaven.test.skip=true,这两个参数的主要区别是:

  • -DskipTests,不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下。
  • -Dmaven.test.skip=true,不执行测试用例,也不编译测试用例类

Scope

  • compile 最强依赖,传递
  • provided 参与编译,依赖不传递,不打进包里,由运行时系统提供
  • runtime 运行时,如:JDBC
  • test 编译运行测试代码,不会发布
  • system 类似于provided,但是不会再repo里查找,需要引用外部jar

常用命令

  • mvn compile
  • mvn test-compile 编译测试代码
  • mvn test 运行测试
    • 运行指定测试用力
  • mvn site,package,install,clean
  • mvn eclipse:eclipse
  • mvn idea:idea
  • mvn -Dtest pakcage 只打包不测试
  • mvn jar:jar只打jar包
  • mvn eclipse:clean清除eclipse的一些系统设置
  • mvn validate 验证工程所需资源

Java Web

Servlet简介

Servlet是由Web服务器加载,独立于平台和协议的Java应用程序。JSP改变了Servlet提供HTTP服务时的编程方式。

Servlet的生命周期

  • 加载和实例化
    • Servlet容器加载类
    • 通过反射机制创建实例
  • 初始化
    • 调用Servlet的init方法
    • 每个Servlet实例,只会调用一次init
    • 参数为ServletConfig实例
  • 执行
    • 请求到来,容器针对该请求创建ServletRequest和ServletResponse
    • 调用service()方法
    • 从ServletRequest获取请求信息,处理请求,把处理结果放入到ServletResponse
    • 销毁ServletRequest和ServletResponse
    • service()会检查请求的类型,相应的调用doGet、doPost方法
  • 清理
    • Servlet实例需要从服务中移除时,容器调用destroy方法
    • Servlet的整个生命周期,init和destroy只会调用一次,service()

Servlet相关类和接口

  1. Servlet接口: init、service、destroy、getServletConfig
  2. GenericServlet抽象类 和 HttpServlet抽象类
  3. ServletRequest:getAttribte、getContentLength、getInputStream、getParameter
  4. ServletResponse: getOutputStream、getWriter、setBufferSize、setContentLength
  5. HttpServletRequest:getCookies、getSession
  6. HttpServletResponse: addCookie、setStatus
  7. HttpSession: getId、invalidate(使当前会话失效)、setAttributes、getAttributeNames

web.xml配置

  • webApp自定义web.xml路径:WEB-INF/web.xml
1
2
3
4
5
6
<servlet>
<servlet-name>
<servlet-class>
<servlet-mapping>
<servlet-name>
<url-pattern>

web.xml关键元素说明

  • welcome-file-list:tomcat按顺序查找欢迎页
  • listener: 注册监听器,用于初始化Spring框架
  • error-page:根据error-code或者exception-type指定location

struts.properties

  • 放置在src路径下;编译部署后,位于WEB-INF/classes 路径下
  • 设置国际化编码、devMode、locale、theme等

struts.xml

  • 位置同struts.properties
  • 主要用于配置Action和Http请求的关系
  • package:命名空间定义
  • action

AJAX

Struts简介

Struts框架主要有两个版本: 1.x和2.x,都遵循MVC思想。1.x版本性能高效、低松耦合、低侵入,但存在如下缺点:

  • 耦合
  • 麻烦

2.x版本是一个全新的框架,将Struts技术与WebWork技术结合,功能更加灵活。

Struts 2 工作原理

  1. 用户发出HttpServletRequest
  2. 请求经过一些了的Filter过滤器
    • 集成了SiteMesh插件之后,会先经过ActionContextCleanUp过滤器
  3. 调用FilterDispatcher
    • 控制器的核心
    • 询问ActionMapper是否需要调用Action
    • 如果需要,转发给ActionProxy
  4. ActionProxy通过ConfigurationManager,从struts.xml中获取需要调用的Action类
  5. ActionProxy创建ActionInvocation实例,该实例使用命名模式来调用。 该实例根据配置文件,添加相关的拦截器Interceptor
  6. Action执行完成后,ActionInvocation实例根据strtus.xml中的配置,找到相对应的返回结果
    • 返回结果为JSP或者FreeMaker模版
  7. 最后,HttpServletResponse通过web.xml 文件中配置的过滤器返回

Tips

  • FilterDispatcher对应控制层
    • web.xml 中启用过滤器fd
    • fd根据URL和struts.xml中的配置调用对应的action
  • Action对应模型层
    • 模型层处理请求参数,调用效应的业务逻辑,产生视图所需要的数据
  • Result对应视图层
  • 2.x优点:
    1. 集中配置来调度动作类
    2. 提供简单统一的表达式语言来访问内存数据
    3. 提供标准的验证框架、国际化框架
    4. 强大的标签
    5. 良好的Ajax
    6. 明确的错误报告,异常简单明了

持久化

Java中对象持久化的方式有:

  1. 序列化对象,将对象存放到格式化的文件中
  2. 将对象持久化到xml文件中
  3. 持久化到数据库中,一般为关系型数据库

持久化层框架Hibernate

核心接口:Configuration、SessionFactory、Session、Transaction、Query

  1. 初始化创建Configuration对象
    • 读取Hibernate.cfg.xml存放到Configuration
    • 读取mapping元素到Configuration
  2. 创建SessionFactory(SF)
    • Configuration对象创建SF,把配置信息注入SF
    • SF负责创建Session
    • Configuration使命结束
    • SF是全局唯一的,线程安全的
  3. 创建Session,建立数据库连接
    • Session对象操作实体对象
    • 操作转换为DB中的CRUD
    • session实例不是线程安全的,通过ThreadLocal来保证
  4. 创建Transaction,开始一个事务
    • CDU操作必须包含在事务中
  5. 利用Session的持久化方法将实体对象持久化到DB
    • 设计:DAO接口、DAO实现类、DAO工厂类
    • 接口:save、findById、delete、update
    • 实现:session、transaction
    • 工厂:通过接口使用实现类,解耦使用与实现
      • public static UserDAOInterface getUserDAOInstance(){ return new UserDAOImpl();}
    • SQL语句是在运行时由Hibernate自动生成的
  6. 提交结果并结束事务
  7. 关闭Session,与DB断开连接

hibernate.cfg.xml详解

  • JDBC配置
  • C3P0连接池:
    • Hibernate实现类一个插件结构,可以继承第三方连接池
    • Hibernate提供类对C3P0的内嵌支持
    • 可以配置:最大/小连接数、超时时长、缓存statement数量
  • JNDI数据源
  • 二级缓存属性:
    • 一级缓存是Session级,事务范围级的,由Hibernate自动控制
    • 二级缓存是由SessionFactory管理的进程级缓存,可动态加载和卸载
    • 二级缓存属性:hibernate.cache.provider_class,hibernate.cache.use_query_cache,hibernate.cache.query_cache_factory 等
  • 事务属性:
    • Hibernate只实现了对JDBC的轻量级封装,本身未提供事务管理
    • 依赖于JDBC或者JTA事务管理功能
    • hibernate.transaction.factory_class指定工厂类

*.hbm.xml详解

  • 映射文件结构
    • hibernate-mapping是根元素,设置子元素类的共有属性
    • class声明一个持久化类
    • id映射标识属性
      • 经验表明自然主键很容易出现问题,一般使用代理主键
      • uuid:长度32位的16进制字符串,由IP、JVM启动时间(精确到1/4秒)、系统时间、JVM中的唯一计数器值生成
    • property元素映射普通属性
    • 映射集合元素
  • 关联关系映射
    • todo
  • Tips:MiddleGen插件可以自动生成类和mapping文件

spring

Spring框架学习笔记

Spring是Java平台上的开源应用框架,起源于Rod Johnson于2002年所著的《Expert One-on-One:J2EE Design and Development》,2003年Rod公开了框架源码,2004年1.0版Spring正式发布。
框架优势:

  • 有效组织中间层对象
  • 实现面向接口编程的范式
  • 易于进行单元测试
  • 简化了数据库访问层

依赖注入与控制反转

传统上,被调用者实例由调用者创建,Spring框架通过依赖注入,使调用者无需关心实例的常见,实现了面向接口编程,达到了控制反转的目的。

面向切面编程

为分散的对象添加公共的行为,通过AOP,完成公共逻辑的统一管理,减少了系统中的重复代码,降低了模块件间的耦合,提高了系统的可维护性。

  • Aspects模块集成了AspectJ
  • Instrumentation模块提供了class instrumentation和classloader实现

主要模块

核心容器[Core Contrainer]

  • Beans、Core
    提供基本功能:创建、配置和管理Bean
  • Context:扩展BeanFactory,提供上下文信息,添加了I18N、资源加载、校验等功能
  • Expression Language:用于访问和操纵运行时对象

AbstractApplicationContext

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
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;//同步锁
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();//为刷新准备新的Context
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();//刷新所有BeanFacotry子容器
this.prepareBeanFactory(beanFactory);//创建BeanFactory

try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();//刷新由子类实现的方法
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var5) {
this.destroyBeans();
this.cancelRefresh(var5);
throw var5;
}

}
}

BeanFactory详解

1
2
3
4
5
6
7
8
9
//Bean Factory 接口
Boolean containsBean(String name) //check Bean existing
Object getBean(String name)
Class getType(String name)
//Application 子接口,扩展附加功能
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
XmlWebApplicationContext

数据访问

  • ORM模块提供对象关系映射API,集成了JPA、JDO、hibernate、iBatis
  • OXM:提供Object/XML映射抽象
  • JMS模块包含了发布和订阅特性
  • Transaction模块提供了对声明式和编程式事务的支持

Web模块

  • 支持Web、Servlet、Struts、Portlet

Test模块

  • 支持JUnit
  • 支持TestNG

Spring Boot

注解

  • Profile
    • 使用环境中的ActiveProfiles来标明context,从而设置不同的参数
    • @Profile("dev") or @Profile("prod")
    • context.getEnvitonment().setActiveProfiles(“prod”)
  • Application Event
    • 自定义ApplicationEvent
    • 自定义ApplicationListener
    • 使用容器发布事件
  • SpringAware
    • 通过对应的Aware接口,使Bean获取对应的信息
    • BeanNameAware
    • BeanFactoryAware
  • Concurrent
    • ThreadPoolTaskExecutor:实现了一个基于线程池的TaskExecutor
  • Scheduled
    • 定时任务

MyBatis 初探

MyBatis 是一个开源持久化框架,用于简化Java开发中的数据库访问。它支持customer SQL, stored procedures 和 different types of mapping relations。该框架也是一种交互式框架,先期准备好交互的必要条件,然后构建一个交互环境,在交互环境中划分出会话,每次会话也有一个交互环境。当环境准备好了之后,就可以交换数据来(涉及到网络通信,一般都是这种处理方式)。

主要完成两件事:

  • 通过JDBC完成与数据库的连接
  • 通过反射完成Java对象和数据库参数之间的映射关系

通过SQLMap文件获得statement语句,同时形成ParameterMap和ResultMap两个对象,用于处理参数和SQL对象,这样加上数据库连接,就可以执行SQL语句了。

运行原理(iBatis)

  • SqlMapSession->SessionScode->Establish Connection->RequestScope
  • execute SQL
    • 映射Java对象参数到ParameterMap
    • 映射数据库列到ResultMap
  • RequestScope归还到对象池-释放Connection-归还SessionScope-销毁SqlMapSession

Java APIs

  • SqlMapClientImpl
    • ExtendedSqlMapClient
      • SqlMapClient: 定义客户端的CRUD
        • SqlMapExecutor
        • SqlMapTransactionManager
    • SqlMapSessionImpl
      • SqlMapSession: 提供客户端在当前线程的执行环境
        • SqlMapExecutor
        • SqlMapTransactionManager
  • SqlMapExecutorDelegate: 工具类,耦合了操作和执行环境

Statement相关的类

  • DeleteStatement
  • InsertStatement
  • SelectStatement
  • UpdateStatement
    • GeneralStatement
    • BaseStatement
      • MappedStatement
      • ResultMap
      • Sql
      • ParameterMap

SQLSessionFactory

SQLSessionFactory是MyBatis应用的核心,使用对应的Builder和配置文件来实例化。配置文件中包含了如下信息:

  • data source definition
  • transaction manager details
  • mappers of relations between entities

SQLSession

SQLSession 使用SQLSessionFactory(非线程安全)来实例化,。包含了如下操作:

  • performing database operations
  • obtaining mappers
  • managing transactions

SqlSession实现了AutoCloseableinterface,可以使用如下形式:

1
2
3
try(SqlSession session = sqlSessionFactory.openSession()) {
// do work
}

Maven 依赖

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.4</version>
</dependency>

Mappers

  • CRUD注解:调用被注解的方法,执行对应的SQL
    • @Insert
    • @Select
    • @Update
    • @Delete
  • @Results 设置查询结果与Java类的映射关系
    • @Result
  • @Many
    • @One
  • @MapKey
  • @Options 定义全局的开关和配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface PersonMapper {

@Insert("Insert into person(name) values (#{name})")
public Integer save(Person person);

// ...

@Select(
"Select personId, name from Person where personId=#{personId}")
@Results(value = {
@Result(property = "personId", column = "personId"),
@Result(property="name", column = "name"),
@Result(property = "addresses", javaType = List.class,
column = "personId", many=@Many(select = "getAddresses"))
})
public Person getPersonById(Integer personId);

@Insert("Insert into address (streetAddress, personId
values(#{streetAddress}, #{personId})")
@Options(useGeneratedKeys = false, flushCache=true)
public Integer saveAddress(Address address);
// ...
}

Dynamic SQL

动态生成SQL语句,相对于传统JDBC易于调试,不易出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//SQL使用
@SelectProvider(type=MyBatisUtil.class, method="getPersonByName")
public Person getPersonByName(String name);

public class MyBatisUtil {

// SQL定义

public String getPersonByName(String name){
return new SQL() {{
SELECT("*");
FROM("person");
WHERE("name like #{name} || '%'");
}}.toString();
}
}

存储过程的调用

结论

参考资料