<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>mioの小窝</title><description>四季正与你们擦肩而过</description><link>https://17356085.github.io/</link><language>zh_CN</language><item><title>黄醪小记</title><link>https://17356085.github.io/posts/2026/%E9%BB%84%E9%86%AA%E5%B0%8F%E8%AE%B0/</link><guid isPermaLink="true">https://17356085.github.io/posts/2026/%E9%BB%84%E9%86%AA%E5%B0%8F%E8%AE%B0/</guid><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;天下之饮，酒为至古。酒之品殊，而人之所嗜亦异。世俗多尚烧酒，以烈为雄，以善饮为豪；觥筹交错之间，或以杯中多寡量情，或以酩酊沉醉为诚。予独不然。&lt;/p&gt;
&lt;p&gt;予尝饮烧酒，自三十度至五十有三度，上及名酿，下逮村醪，虽香味各殊，而入口灼然，回味犹烈。人或称其醇厚，或誉其馥郁，予终不能悦。惟黄酒、米醴，入口温和，不迫喉舌；既咽之后，余香徐来，若故人款语，不疾不徐，颇协予意。&lt;/p&gt;
&lt;p&gt;且醴酒之兴，其来久矣。会稽善酿，自昔有闻；姑苏之醪，亦涵吴中风物。予虽未履山阴之陌，未泛姑苏之水，而素慕吴越：曲巷深园，短桥流水，吴音欸乃，旧迹文心。今羁旅南粤，偶得江左一酌，虽未亲临其境，亦足稍慰平生之想。&lt;/p&gt;
&lt;p&gt;然予之不喜烧酒，非独恶其味也，尤不取世俗以酒相强之风。&lt;/p&gt;
&lt;p&gt;昔予供职旧处，有故人与长者，皆尝有德于予。或引予入门，使得其所；或与予共事，耐烦相助；又有主其曹者，屡加指授，予皆敬之。某夕，其人从长者宴宾，凡六人，共饮五十三度烈酒一升。&lt;/p&gt;
&lt;p&gt;翌日见之，伏几不起，面色憔然，时闻呕逆之声；俄而仓皇趋厕，良久乃返。及神思稍定，方言昨夕饮酒之状。&lt;/p&gt;
&lt;p&gt;予闻而窃叹：&lt;/p&gt;
&lt;p&gt;昨夜杯前，谓之尽礼；&lt;br /&gt;
今朝案上，不过伏而欲呕耳。&lt;/p&gt;
&lt;p&gt;酒本所以助欢，奈何反以致苦？情本发乎自然，奈何必借杯中之物而明之？&lt;/p&gt;
&lt;p&gt;彼酌我饮，我酌彼饮；一觞既尽，复进一觞。直至神昏气惫，言语失次，举止失仪，然后相顾而曰：“今日欢甚。”&lt;/p&gt;
&lt;p&gt;予窃以为：此岂欢哉？特相苦耳。&lt;/p&gt;
&lt;p&gt;尤可笑者，情本未厚，而欲借烈酒厚之；交本未深，而欲借沉醉深之。&lt;/p&gt;
&lt;p&gt;或曰：“不饮，是慢我也。”&lt;br /&gt;
或曰：“不尽，是情未至也。”&lt;/p&gt;
&lt;p&gt;于是杯盏遂若权衡，可以量人情之轻重；酒量竟如关津，可以验人之顺逆。&lt;/p&gt;
&lt;p&gt;噫，亦异矣！&lt;/p&gt;
&lt;p&gt;予以为，酒可见性情，不可强性情；可助人情，不可逼人情。&lt;/p&gt;
&lt;p&gt;善饮者不以能饮凌人，知止者不以少饮为耻。受人一饭之恩，则久而不忘；承人一言之善，则常存于心。至若杯中之物，不过助兴耳，岂可反使之为人情之券、交游之质乎？&lt;/p&gt;
&lt;p&gt;故予爱黄酒，非独爱其色味也。&lt;/p&gt;
&lt;p&gt;爱其温而不猛，缓而不迫；初饮无凌人口舌之势，既饮有绵长余韵。若得知己二三，相与小酌，临水倚窗，听吴音之婉转，谈故人旧事；酒至微醺，兴到而止，人与情俱在，岂不快哉？&lt;/p&gt;
&lt;p&gt;若乃举觞相逼，以多饮为豪，以沉醉为诚；彼不欲饮而强之，彼已不胜而复进之，直至狼狈失仪，犹称“尽欢”，则非酒之过，实人情之陋耳。&lt;/p&gt;
&lt;p&gt;嗟夫！&lt;/p&gt;
&lt;p&gt;烧酒之烈，世多尚之；&lt;br /&gt;
黄醪之和，予独爱之。&lt;/p&gt;
&lt;p&gt;情若必待醉而后见，则其情亦薄矣；&lt;br /&gt;
交若必待从而后成，则其交亦浅矣。&lt;/p&gt;
&lt;p&gt;酒所以适性，非所以困人；&lt;br /&gt;
杯所以通欢，非所以验心。&lt;/p&gt;
&lt;p&gt;斯言而已。&lt;/p&gt;
</content:encoded></item><item><title>站在冬天里重现夏天：影像、记忆与消失者的回信</title><link>https://17356085.github.io/posts/2026/%E7%AB%99%E5%9C%A8%E5%86%AC%E5%A4%A9%E9%87%8C%E9%87%8D%E7%8E%B0%E5%A4%8F%E5%A4%A9%E5%BD%B1%E5%83%8F%E8%AE%B0%E5%BF%86%E4%B8%8E%E6%B6%88%E5%A4%B1%E8%80%85%E7%9A%84%E5%9B%9E%E4%BF%A1/</link><guid isPermaLink="true">https://17356085.github.io/posts/2026/%E7%AB%99%E5%9C%A8%E5%86%AC%E5%A4%A9%E9%87%8C%E9%87%8D%E7%8E%B0%E5%A4%8F%E5%A4%A9%E5%BD%B1%E5%83%8F%E8%AE%B0%E5%BF%86%E4%B8%8E%E6%B6%88%E5%A4%B1%E8%80%85%E7%9A%84%E5%9B%9E%E4%BF%A1/</guid><description>从《橙路》的旧画面出发，谈影像、记忆、死亡与那个再也回不去的夏天。</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;原视频：&lt;a href=&quot;https://www.bilibili.com/video/BV1Xx411L7PS/&quot;&gt;《橙路：那个再也回不去的夏天》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;文中六张画面均截取自这段视频；播放器可以在下方直接加载。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;::bilibili{bvid=&quot;BV1Xx411L7PS&quot; title=&quot;《橙路：那个再也回不去的夏天》&quot; p=1 preload=&quot;auto&quot;}&lt;/p&gt;
&lt;p&gt;“更多的人，似乎没有更多的消息。不过有的时候，没有消息，其实是最好的消息。”&lt;/p&gt;
&lt;p&gt;旁白说完这句话，声音忽然停了下来。&lt;/p&gt;
&lt;p&gt;画面中的鲇川圆仍然年轻。她站在被柔光笼罩的走廊深处，仍然拥有三十年前的面容。随后，一则讣告无声地浮现：为她赋予声音的鹤弘美，已于2017年离世。紧接着，《何処にいるの》的高潮响起，旁白重新开口：&lt;/p&gt;
&lt;p&gt;“所以说，爱情这个东西，她总是在夏天开始，在冬天结束。”&lt;/p&gt;
&lt;p&gt;从表面上看，这只是一段关于《橙路》的老番回顾。它介绍角色、声优、音乐家和动画创作者，追溯作品对后世青春恋爱题材的影响，也调侃那些只有资深动画观众才能会心一笑的典故。但当旁白在讣告之前突然沉默时，视频的性质已经发生了变化。&lt;/p&gt;
&lt;p&gt;它不再只是在介绍一部动画。&lt;/p&gt;
&lt;p&gt;它变成了一篇关于时间、记忆和死亡的影像散文；一封寄给1987年的信；一场由旧画面、旧声音和迟到三十年的目光共同完成的悼念。&lt;/p&gt;
&lt;p&gt;它真正讲述的，不是《橙路》的故事，而是一个被影像保存下来的夏天，如何在多年以后，成为站在冬天里的人们面前的一件遗物。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/images/posts/orange-road/frame-01.jpg&quot; alt=&quot;鲇川圆站在夏日的风里&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;一、当老番解说突然变成挽歌&lt;/h2&gt;
&lt;p&gt;这段视频的前半部分始终保持着一种轻快甚至略显轻佻的语气。&lt;/p&gt;
&lt;p&gt;它谈论三角恋、黑长直、超能力、不良少女、摇滚、咖啡和约会；它把丸户史明、《白色相簿2》、梶浦由记、石原立也等后来者串联起来；它不断使用动画文化内部的梗，使整段讲述带着一种老观众之间心照不宣的亲密感。&lt;/p&gt;
&lt;p&gt;这种轻松并非悲伤的反面。相反，它是悲伤得以发生的前提。&lt;/p&gt;
&lt;p&gt;如果一开始便以庄严、肃穆的方式讲述死亡，观众很快便会意识到自己正在进入一场被预先设计好的感动。情绪会被命名，被解释，被引导。然而，这段视频先让观众放松下来，让人误以为这只是一次怀旧与考据。于是，当旁白忽然中止，原先熟练而活泼的语言也随之失效。&lt;/p&gt;
&lt;p&gt;这个沉默并不是文案的空缺，而是文案最重要的一句话。&lt;/p&gt;
&lt;p&gt;在那一刻，叙述者仿佛终于遇到了一个无法继续用玩笑和知识加以处理的事实。他不再解释，也不再评价，而是把说话的位置让给画面、字幕和音乐。&lt;/p&gt;
&lt;p&gt;旁白的退出，构成了一次叙述权的交接。&lt;/p&gt;
&lt;p&gt;此前，是叙述者替过去命名；
此后，是过去本身以遗像般的方式返回。&lt;/p&gt;
&lt;p&gt;因此，鹤弘美的讣告并不是附加在视频末尾的一条资料。它是一道裂缝。正是通过这道裂缝，现实中的死亡突然进入了动画永恒的夏天。&lt;/p&gt;
&lt;p&gt;角色仍然年轻，声优却已离去；画面仍然能够运动，现实中的生命却已经停止。动画和死亡第一次被迫放在同一个平面上，彼此映照。&lt;/p&gt;
&lt;p&gt;于是，这段关于青春爱情的叙事，悄然变成了一首挽歌。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/images/posts/orange-road/frame-02.jpg&quot; alt=&quot;《橙路》中的两人&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、沙滩上的脸：时间如何抹去一个人&lt;/h2&gt;
&lt;p&gt;福柯曾以“海边沙滩上的一张脸”比喻“人”的消失。这个判断原本有其特定的思想史语境，并不只是对个体死亡的感慨。但一旦把这一意象带入这段视频，它便获得了另一层近乎直观的意义。&lt;/p&gt;
&lt;p&gt;视频里反复出现一种句式：&lt;/p&gt;
&lt;p&gt;“那一年，他三十岁。”&lt;/p&gt;
&lt;p&gt;“那一年，她二十七岁。”&lt;/p&gt;
&lt;p&gt;“从动画开播到现在，正好三十年。”&lt;/p&gt;
&lt;p&gt;这些年龄与年份看似只是资料，实际上却不断提醒观众：那些后来被写进动画史的人，当时也只是年轻人。他们站在时间的某个位置上，并不知道未来会怎样展开。&lt;/p&gt;
&lt;p&gt;松本泉结束《橙路》连载时还很年轻，本来似乎拥有漫长的创作人生，疾病却使这条道路逐渐中断。鹤弘美为鲇川圆赋予声音时，也无法知道三十年后的观众会在一则讣告中重新听见她。&lt;/p&gt;
&lt;p&gt;作品完成以后，角色被固定了下来，创作者却没有。&lt;/p&gt;
&lt;p&gt;鲇川圆不会衰老。她每一次出现在画面中，都依然拥有当年的目光和姿态。红帽子每次都会重新被风吹起，夏天每次都会再次开始。但为这个世界画线、写作、演奏和发声的人，却继续被现实时间带走。&lt;/p&gt;
&lt;p&gt;因此，动画的永恒并不是对时间的胜利，反而使时间显得更加残酷。&lt;/p&gt;
&lt;p&gt;角色之所以能够永远年轻，正是因为她已经失去了继续生活的可能；影像之所以可以反复播放，正是因为它所保存的是一个已经完成的瞬间。真实的人拥有未来，所以也必须承担衰老、疾病和死亡；虚构人物没有未来，因此获得了一种幽灵般的永恒。&lt;/p&gt;
&lt;p&gt;福柯的海水正在现实中不断漫上来，抹去姓名背后的身体，抹去一个时代的生活细节，也抹去观众当初观看时的处境。&lt;/p&gt;
&lt;p&gt;最后留下来的，往往只是一些被媒介固定的轮廓。&lt;/p&gt;
&lt;p&gt;一个名字。&lt;/p&gt;
&lt;p&gt;一段配音。&lt;/p&gt;
&lt;p&gt;一顶红帽子。&lt;/p&gt;
&lt;p&gt;一张永远不会变老的脸。&lt;/p&gt;
&lt;h2&gt;三、重现的时光：艺术并不让过去复活&lt;/h2&gt;
&lt;p&gt;然而，这段视频并不只是关于抹除。&lt;/p&gt;
&lt;p&gt;倘若一切都只是消失，那么怀旧影像便只能成为遗迹的展示。但当《何処にいるの》响起，当鲇川圆重新回头，当红帽子再次进入蓝天时，已经消失的时间似乎又在当下获得了某种触感。&lt;/p&gt;
&lt;p&gt;这正接近普鲁斯特所谓“重现的时光”。&lt;/p&gt;
&lt;p&gt;过去当然不能真正返回。人不能重新进入已经结束的人生，也不能把某一天从时间中取出，再完整地生活一次。所谓“重现”，并不是历史的复原，而是在一个偶然的感官瞬间里，过去忽然以超越普通回忆的强度重新降临。&lt;/p&gt;
&lt;p&gt;一段旋律、一种光线、某个熟悉的声音，突然使早已消失的时间再次具有温度。&lt;/p&gt;
&lt;p&gt;在这段视频中，《何処にいるの》承担的正是这种功能。&lt;/p&gt;
&lt;p&gt;当年听见它时，观众也许只觉得旋律动人。多年以后，它紧接着鹤弘美的讣告响起，歌名中“你在哪里”的意味突然被重新打开。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/images/posts/orange-road/frame-04.jpg&quot; alt=&quot;走廊尽头与讣告&quot; /&gt;&lt;/p&gt;
&lt;p&gt;它仿佛在问：&lt;/p&gt;
&lt;p&gt;鲇川圆在哪里？&lt;/p&gt;
&lt;p&gt;鹤弘美在哪里？&lt;/p&gt;
&lt;p&gt;1987年在哪里？&lt;/p&gt;
&lt;p&gt;那个曾经相信夏天不会结束的自己，又在哪里？&lt;/p&gt;
&lt;p&gt;作品并没有改变。改变的是观看作品的人，以及作品与观看者之间不断增加的时间距离。正是死亡、年龄和失去，为旧画面赋予了当年不可能具有的意义。&lt;/p&gt;
&lt;p&gt;年轻时，我们观看的是故事；多年以后，我们观看的则是时间在故事上留下的阴影。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/images/posts/orange-road/frame-03.jpg&quot; alt=&quot;夏日海滨与年轻的身影&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但这种“重现”从来不是复活。&lt;/p&gt;
&lt;p&gt;恰恰因为鹤弘美的声音还能继续播放，我们才更清楚她不会再说出新的句子；恰恰因为鲇川圆永远年轻，我们才更加清楚现实中的人无法停留；恰恰因为那个夏天在画面中保存得如此完整，我们才意识到自己已不可能重新回到其中。&lt;/p&gt;
&lt;p&gt;所以，艺术的力量并不在于取消失去，而在于使失去重新可见。&lt;/p&gt;
&lt;p&gt;它让过去回来一次，同时也让我们更深地认识到：这一次回来，只能发生在记忆、影像和音乐之中。&lt;/p&gt;
&lt;p&gt;每一次重现，都同时宣告了一次不可重现。&lt;/p&gt;
&lt;h2&gt;四、寄往不存在地址的信&lt;/h2&gt;
&lt;p&gt;岩井俊二的《情书》提供了一个理解这种影像经验的恰当比喻。&lt;/p&gt;
&lt;p&gt;渡边博子将信寄往已经去世的恋人曾经生活过的地址。她在理性上并不真正相信死者会回信。寄信本身更接近一种仪式：明知对方不在，仍然想把自己的话送往他曾经存在过的地方。&lt;/p&gt;
&lt;p&gt;可是，信意外地获得了回应。&lt;/p&gt;
&lt;p&gt;这个回应并非来自死者，而来自另一个同名者，来自少年时代的记忆、旧学校、借书记录，以及多年以后才被理解的一幅肖像。&lt;/p&gt;
&lt;p&gt;过去没有复活，死者也没有真正回信。但在书信的往返中，一个从未被完整认识的人重新显形，一段当年未曾被理解的感情，也在多年以后获得了意义。&lt;/p&gt;
&lt;p&gt;这段《橙路》视频所做的事情与此相似。&lt;/p&gt;
&lt;p&gt;它也在向一个已经不存在的地址寄信。那个地址叫作1987年，收信人则是当年年轻的创作者、声优、角色和观众。&lt;/p&gt;
&lt;p&gt;视频列出姓名、年龄和年份，重新剪辑旧动画，播放保存下来的音乐，仿佛在询问：&lt;/p&gt;
&lt;p&gt;你们还在那里吗？&lt;/p&gt;
&lt;p&gt;那个夏天还存在吗？&lt;/p&gt;
&lt;p&gt;然而，回信不可能来自过去本身。真正给予回答的，是过去留下的媒介痕迹。&lt;/p&gt;
&lt;p&gt;鲇川圆的回头，是回信。&lt;/p&gt;
&lt;p&gt;鹤弘美留下的声音，是回信。&lt;/p&gt;
&lt;p&gt;《何処にいるの》的旋律，是回信。&lt;/p&gt;
&lt;p&gt;观众在多年以后忽然被唤醒的情绪，也是回信。&lt;/p&gt;
&lt;p&gt;只是，这封回信越清晰，收信人的缺席便越无法掩饰。&lt;/p&gt;
&lt;p&gt;《情书》中的“你好吗”，与《何処にいるの》所暗示的“你在哪里”，本质上都是向无法回答者提出的问题。询问者其实知道不会得到真正的回应，却仍然必须问。&lt;/p&gt;
&lt;p&gt;因为呼唤并不是为了使死者回答，而是为了使生者最终听见自己的回声。&lt;/p&gt;
&lt;p&gt;我们问“你在哪里”，最后发现真正失去位置的，也许是曾经的自己。&lt;/p&gt;
&lt;h2&gt;五、允许遗忘：比纪念更深的温柔&lt;/h2&gt;
&lt;p&gt;罗大佑的《歌》，则把记忆与死亡的问题推向了更幽深的地方。&lt;/p&gt;
&lt;p&gt;这首歌中的死者并不要求宏大的悼念。不要悲伤的歌，不必在坟上安放蔷薇，也无需用柏树象征永恒。青草、雨水和露珠已经足够。&lt;/p&gt;
&lt;p&gt;最重要的是：&lt;/p&gt;
&lt;p&gt;愿意记得，就记得；甘心忘记，也可以忘记。&lt;/p&gt;
&lt;p&gt;通常，纪念总带有某种道德要求。我们仿佛必须记住死者，必须证明某段关系没有因死亡而终结。遗忘常常被理解为背叛，仿佛只要一个人不再被想起，他就会遭遇第二次死亡。&lt;/p&gt;
&lt;p&gt;可是《歌》取消了这种命令。&lt;/p&gt;
&lt;p&gt;它并非否认爱情，而是不愿让自己的缺席成为生者永久的负担。它仿佛在说：我曾经爱你，但我不要求你余生都生活在我的死亡之中。&lt;/p&gt;
&lt;p&gt;这种允许遗忘的姿态，与《橙路》视频形成了奇妙的对话。&lt;/p&gt;
&lt;p&gt;《歌》说，你可以忘记我。&lt;/p&gt;
&lt;p&gt;视频却说，我们又一次想起了你。&lt;/p&gt;
&lt;p&gt;而更动人之处在于，《歌》本身请求人们不要唱悲伤的歌，却最终成为一首被不断传唱的歌；它允许人们忘记，却因此一次次被重新记起。&lt;/p&gt;
&lt;p&gt;这构成了纪念最美的悖论：&lt;/p&gt;
&lt;p&gt;越不强迫记忆，记忆反而越可能真正发生。&lt;/p&gt;
&lt;p&gt;鹤弘美的讣告之所以动人，也正因为视频没有命令观众悲伤。旁白没有高声宣布“请记住她”，没有用过度激烈的语言形容失去。它只是停下来，让讣告出现，让音乐响起。&lt;/p&gt;
&lt;p&gt;正因为没有命令，观众才真正开始回忆。&lt;/p&gt;
&lt;p&gt;纪念在这里不再是一种责任，而成为一个偶然降临的瞬间：某一天，旧歌再次响起，旧画面重新发亮，一个已经离开的人毫无预兆地回到意识之中。&lt;/p&gt;
&lt;p&gt;她没有复活，却又确实在这一刻重新来过。&lt;/p&gt;
&lt;h2&gt;六、夏天是生活，冬天是理解&lt;/h2&gt;
&lt;p&gt;“爱情这个东西，她总是在夏天开始，在冬天结束。”&lt;/p&gt;
&lt;p&gt;这句话之所以能够超越《橙路》的具体爱情故事，是因为夏天与冬天在这里已经不再只是自然季节。&lt;/p&gt;
&lt;p&gt;夏天是人尚未意识到时间存在时的生活。&lt;/p&gt;
&lt;p&gt;人在夏天里相遇、爱恋、犹豫、挥霍时间，以为眼前的一切还拥有无限的可能。身处青春之中的人，很少能够真正理解青春；身处爱情中的人，也很少能够立刻知道这一段关系会在自己的生命中留下什么。&lt;/p&gt;
&lt;p&gt;冬天则是回望发生的时刻。&lt;/p&gt;
&lt;p&gt;只有当夏天结束以后，人才能真正理解夏天。只有青春远去以后，才知道当年的轻浮、犹豫和热烈曾经多么珍贵。只有一个声音不会再产生新的话语以后，才意识到那声音曾经怎样参与了自己的成长。&lt;/p&gt;
&lt;p&gt;因此，人在夏天里经历爱情，却往往只能在冬天里理解爱情。&lt;/p&gt;
&lt;p&gt;第一次观看《橙路》时，观众看到的是鲇川圆；多年以后再次观看，观众看见的却不仅是她，也看见了松本泉、鹤弘美、1987年，以及第一次看见她的那个自己。&lt;/p&gt;
&lt;p&gt;角色没有改变，改变的是目光。&lt;/p&gt;
&lt;p&gt;于是，红帽子不再只是相遇的象征，也成了回不去的证明；旧音乐不再只是青春的配乐，也成了失去之人的声音；那条白色走廊不再只是动画场景，也变成了一种不可抵达的彼岸。&lt;/p&gt;
&lt;p&gt;真正使旧作品获得重量的，不只是作品本身，而是观看者此后经历的一生。&lt;/p&gt;
&lt;p&gt;年轻时，我们把影像当作娱乐。&lt;/p&gt;
&lt;p&gt;多年以后才发现，影像一直在替我们保存那些当时尚不懂得珍惜的东西。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/images/posts/orange-road/frame-05.jpg&quot; alt=&quot;三十年后的城市风景&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;七、艺术无法阻止潮水，但能重新描画一次&lt;/h2&gt;
&lt;p&gt;最终，这段视频并没有战胜时间。&lt;/p&gt;
&lt;p&gt;它不能使松本泉免于疾病，不能让鹤弘美重新回来，也不能把观众送回第一次看见鲇川圆的那一天。福柯所说的潮水仍然会漫上来，人的面孔、声音和姓名，终将逐渐退出现实。&lt;/p&gt;
&lt;p&gt;艺术无法阻止这种抹除。&lt;/p&gt;
&lt;p&gt;但艺术能够使抹除本身变得可见。&lt;/p&gt;
&lt;p&gt;它能够在潮水退去之后，重新描画一次那张脸。重新描出的当然已经不是原来的脸，而是经过记忆、失去和当下目光改写过的形象。可正是这一次重新描画，构成了纪念。&lt;/p&gt;
&lt;p&gt;普鲁斯特告诉我们，失去的时间可以在一个感官瞬间中重新显现；《情书》告诉我们，即使收信者已经不在，人仍然会向不存在的地址寄信；《歌》则告诉我们，即使有一天记忆消失，曾经发生的爱也并不会因此变成虚假。&lt;/p&gt;
&lt;p&gt;而这段视频把它们汇聚在同一个瞬间：&lt;/p&gt;
&lt;p&gt;旁白停止。&lt;/p&gt;
&lt;p&gt;讣告出现。&lt;/p&gt;
&lt;p&gt;音乐响起。&lt;/p&gt;
&lt;p&gt;鲇川圆仍然站在那个夏天里。&lt;/p&gt;
&lt;p&gt;此时，作品所保存的已经不只是角色的青春，也包括青春已经结束的证据。影像让缺席者重新出现，却无法取消她的缺席；音乐使过去重新获得声音，却使现实的沉默更加清晰。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/images/posts/orange-road/frame-06.jpg&quot; alt=&quot;《橙路》片尾的旧日图像&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所以，这个视频表面上讲述的是《橙路》，实际上讲述的是所有人与旧作品之间最终都会发生的关系：&lt;/p&gt;
&lt;p&gt;我们曾经以为自己在观看别人的青春，直到多年以后才发现，那些画面也在替我们保存自己的时间。&lt;/p&gt;
&lt;p&gt;我们曾经以为夏天只是故事发生的季节，直到站在冬天回望，才知道夏天之所以明亮，正因为它终究不能停留。&lt;/p&gt;
&lt;p&gt;人会被时间抹去。&lt;/p&gt;
&lt;p&gt;艺术不能让人真正回来。&lt;/p&gt;
&lt;p&gt;但在某一段旋律重新响起的时刻，在某一张年轻的脸重新出现的瞬间，已经消失的人和已经失去的时间，会短暂地重新获得形状。&lt;/p&gt;
&lt;p&gt;那并不是复活。&lt;/p&gt;
&lt;p&gt;而是一场迟到的重逢。&lt;/p&gt;
</content:encoded></item><item><title>计算机网络性能计算：三道题串起分组、时延与流水线</title><link>https://17356085.github.io/posts/2026/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E6%80%A7%E8%83%BD%E8%AE%A1%E7%AE%97%E4%B8%89%E9%81%93%E9%A2%98%E4%B8%B2%E8%B5%B7%E5%88%86%E7%BB%84%E6%97%B6%E5%BB%B6%E4%B8%8E%E6%B5%81%E6%B0%B4%E7%BA%BF/</link><guid isPermaLink="true">https://17356085.github.io/posts/2026/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E6%80%A7%E8%83%BD%E8%AE%A1%E7%AE%97%E4%B8%89%E9%81%93%E9%A2%98%E4%B8%B2%E8%B5%B7%E5%88%86%E7%BB%84%E6%97%B6%E5%BB%B6%E4%B8%8E%E6%B5%81%E6%B0%B4%E7%BA%BF/</guid><description>从 2010 年题 34、1-10 和 1-11 出发，整理分组交换、时延、流水线、首部开销与最优分组长度。</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这三道题表面上分别在问传输时间、交换方式比较和最优分组长度，实际上考的是同一条知识链：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;数据从一个大文件变成分组，再变成链路上的 bit；分组经过多条链路时，如何计算时延，以及如何在首部开销和流水线效率之间取得平衡。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这篇笔记按照“先讲题目解法，再汇总知识点”的顺序整理。每道题都会把当时最容易混淆的地方单独挑出来，重点解释为什么这样建模，而不是只把公式和答案列出来。&lt;/p&gt;
&lt;h2&gt;一、题目解法&lt;/h2&gt;
&lt;h3&gt;1. 2010 年题 34：存储-转发与流水线&lt;/h3&gt;
&lt;h4&gt;原题&lt;/h4&gt;
&lt;p&gt;这道题的原题直接保留原图，不再把选择题题面重新抄一遍：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/images/posts/networking/2010-networking-question-34.png&quot; alt=&quot;2010 年题 34：存储-转发分组交换原题&quot; /&gt;&lt;/p&gt;
&lt;p&gt;从题目中提取计算所需的条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件大小：980000B；&lt;/li&gt;
&lt;li&gt;一个完整分组大小：1000B；&lt;/li&gt;
&lt;li&gt;每个分组首部：20B；&lt;/li&gt;
&lt;li&gt;链路速率：100Mbps；&lt;/li&gt;
&lt;li&gt;最短路径包含 3 条链路；&lt;/li&gt;
&lt;li&gt;不考虑分组拆装时间和传播时延。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;第一步：求一个分组真正携带的文件数据&lt;/h4&gt;
&lt;p&gt;一个分组总共有 1000B，但其中 20B 是首部，真正属于文件内容的只有：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1000B - 20B = 980B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里最容易误解的是：题目说的“分组大小”是完整分组的大小，不能把首部从后续的发送时延中丢掉。首部虽然不是文件内容，却同样需要占用链路。&lt;/p&gt;
&lt;h4&gt;第二步：求分组数量&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;N = 980000B / 980B = 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以文件会被切成 1000 个分组。&lt;/p&gt;
&lt;p&gt;这里的 980B 是“每个分组携带的有效数据”，不是分组在链路上的完整长度。两个量要分开记：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 980B 求分组数量；&lt;/li&gt;
&lt;li&gt;用 1000B 求一个分组的发送时延。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;第三步：求一个分组经过一条链路的发送时延&lt;/h4&gt;
&lt;p&gt;发送时延的公式是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;d_trans = L / R
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分组大小必须包括首部：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1000B = 8000bit
100Mbps = 100 × 10^6bit/s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;d_trans = 8000 / (100 × 10^6) s
         = 0.00008s
         = 0.08ms
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;第四步：理解存储-转发到底在等什么&lt;/h4&gt;
&lt;p&gt;存储-转发不是收到第一个 bit 就立刻把它送到下一条链路，而是要先把一个完整分组接收下来，再转发这个完整分组。&lt;/p&gt;
&lt;p&gt;也就是说，交换设备等待的是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;分组的最后一个 bit 到达
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而不是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首部刚到达；&lt;/li&gt;
&lt;li&gt;第一个 bit 刚到达；&lt;/li&gt;
&lt;li&gt;整个文件全部到达。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是“分组”这个概念必须稳定下来的原因：存储-转发的基本单位是完整分组。&lt;/p&gt;
&lt;h4&gt;第五步：利用流水线计算总时间&lt;/h4&gt;
&lt;p&gt;第一个分组要依次经过 3 条链路，所以需要：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3 × 0.08ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但第二个分组不需要等第一个分组到达终点后才开始。第一个分组进入第二条链路时，第二个分组已经可以进入第一条链路；之后不同分组会同时占用不同链路。&lt;/p&gt;
&lt;p&gt;可以把过程想成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;链路 1：分组1 分组2 分组3 ……
链路 2：      分组1 分组2 ……
链路 3：            分组1 ……
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，1000 个分组经过 3 条链路的总时间不是 &lt;code&gt;1000 × 3 × 0.08ms&lt;/code&gt;，而是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T = (N + K - 1) × d_trans
  = (1000 + 3 - 1) × 0.08ms
  = 80.16ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;80.16ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;N&lt;/code&gt; 是分组数量，&lt;code&gt;K&lt;/code&gt; 是链路数量。题目问的是经过几条链路，不是中间有几个交换机。&lt;/p&gt;
&lt;h3&gt;2. 1-10：电路交换和分组交换比较&lt;/h3&gt;
&lt;h4&gt;题目&lt;/h4&gt;
&lt;p&gt;设要传送的报文长度为 &lt;code&gt;x bit&lt;/code&gt;，从源点到终点共经过 &lt;code&gt;k&lt;/code&gt; 条链路，每条链路的传播时延为 &lt;code&gt;d s&lt;/code&gt;，链路速率为 &lt;code&gt;b bit/s&lt;/code&gt;，电路交换建立连接的时间为 &lt;code&gt;s s&lt;/code&gt;。采用分组交换时，分组的数据部分长度为 &lt;code&gt;p bit&lt;/code&gt;，且 &lt;code&gt;x &amp;gt; p&lt;/code&gt;，忽略各结点的排队等待时间。问：在什么条件下，分组交换的时延比电路交换更小？&lt;/p&gt;
&lt;p&gt;下面按题目常见的理想模型，先假设 &lt;code&gt;x&lt;/code&gt; 可以被 &lt;code&gt;p&lt;/code&gt; 整除。&lt;/p&gt;
&lt;h4&gt;电路交换的时延&lt;/h4&gt;
&lt;p&gt;电路交换的过程是：先建立连接，再连续发送完整报文：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T_c = s + x / b + kd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; 是建立连接的时间；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x / b&lt;/code&gt; 是把整个报文推出链路的发送时间；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kd&lt;/code&gt; 是经过 &lt;code&gt;k&lt;/code&gt; 条链路的传播时延。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;分组交换的时延&lt;/h4&gt;
&lt;p&gt;分组数量为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;N = x / p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个分组的数据部分长度为 &lt;code&gt;p bit&lt;/code&gt;，所以在本题的简化模型中，一个分组的一次发送时延写成 &lt;code&gt;p / b&lt;/code&gt;。采用存储-转发并形成流水线后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T_p = (x / p + k - 1) × p / b + kd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;展开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T_p = x / b + (k - 1)p / b + kd
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;比较两种方式&lt;/h4&gt;
&lt;p&gt;要求分组交换更快：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T_p &amp;lt; T_c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代入两式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x / b + (k - 1)p / b + kd &amp;lt; s + x / b + kd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两边都有的 &lt;code&gt;x / b&lt;/code&gt; 和 &lt;code&gt;kd&lt;/code&gt; 可以抵消，得到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(k - 1)p / b &amp;lt; s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以写成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p &amp;lt; sb / (k - 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;这道题真正容易卡住的地方&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;第一，为什么 &lt;code&gt;x / b&lt;/code&gt; 两边都有？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为不管采用哪种交换方式，最终都必须把这份报文中的数据发送出去。分组交换虽然拆成了很多分组，但所有分组的数据总量仍然是 &lt;code&gt;x bit&lt;/code&gt;，所以公共发送时间仍然是 &lt;code&gt;x / b&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，为什么只剩下 &lt;code&gt;(k - 1)p / b&lt;/code&gt;？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一个分组需要沿着额外的链路逐段填充流水线。后续分组可以重叠传输，因此分组交换相对连续发送多出来的不是 &lt;code&gt;k&lt;/code&gt; 倍时间，而是 &lt;code&gt;k - 1&lt;/code&gt; 个分组发送时延。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三，为什么传播时延 &lt;code&gt;kd&lt;/code&gt; 也能抵消？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为两种方式都要经过同一条路径，且题目给出的每条链路传播时延相同。真正需要比较的是：电路交换的连接建立时间，和分组交换的流水线填充时间谁更大。&lt;/p&gt;
&lt;h3&gt;3. 1-11：为什么存在最佳分组长度&lt;/h3&gt;
&lt;h4&gt;题目&lt;/h4&gt;
&lt;p&gt;在上一题的分组交换网络中，报文长度为 &lt;code&gt;x bit&lt;/code&gt;。一个分组的总长度为 &lt;code&gt;(p + h) bit&lt;/code&gt;，其中 &lt;code&gt;p&lt;/code&gt; 是分组的数据部分，&lt;code&gt;h&lt;/code&gt; 是每个分组固定携带的控制信息长度。通信经过 &lt;code&gt;k&lt;/code&gt; 条链路，链路速率为 &lt;code&gt;b bit/s&lt;/code&gt;，忽略结点排队时间。若希望总时延最小，分组的数据部分长度 &lt;code&gt;p&lt;/code&gt; 应取多大？&lt;/p&gt;
&lt;h4&gt;建立总时延模型&lt;/h4&gt;
&lt;p&gt;每个分组真正携带 &lt;code&gt;p bit&lt;/code&gt; 的报文数据，所以分组数量为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;N = x / p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但一个完整分组还要加上 &lt;code&gt;h bit&lt;/code&gt; 的首部或控制信息，因此一次发送的完整长度是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p + h
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个分组经过一条链路的发送时延是 &lt;code&gt;(p + h) / b&lt;/code&gt;。使用存储-转发流水线后，总时延为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T = (x / p + k - 1) × (p + h) / b + kd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;展开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T = [x + xh / p + (k - 1)p + (k - 1)h] / b + kd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和 &lt;code&gt;p&lt;/code&gt; 无关的部分可以先看作常数，所以真正需要优化的是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;f(p) = xh / p + (k - 1)p
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;分组太小会怎样&lt;/h4&gt;
&lt;p&gt;当 &lt;code&gt;p&lt;/code&gt; 很小时，同样大小的报文需要切成更多分组，首部也会重复更多次：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xh / p 变大
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这意味着有效数据比例下降，首部开销占比上升。&lt;/p&gt;
&lt;h4&gt;分组太大会怎样&lt;/h4&gt;
&lt;p&gt;当 &lt;code&gt;p&lt;/code&gt; 很大时，每个分组的发送时间变长，经过多条链路时，流水线开始阶段需要等待更久：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(k - 1)p 变大
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以分组也不是越大越好。&lt;/p&gt;
&lt;h4&gt;求两种代价的平衡点&lt;/h4&gt;
&lt;p&gt;对 &lt;code&gt;f(p)&lt;/code&gt; 求导：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;f&apos;(p) = -xh / p^2 + (k - 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;令导数为 0：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-xh / p^2 + (k - 1) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p^2 = xh / (k - 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此最优的数据部分长度为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p* = √[xh / (k - 1)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个结果也可以理解成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xh / p = (k - 1)p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;左边是分组太小带来的首部开销，右边是分组太大带来的流水线填充开销。最优点就是两种代价达到平衡的位置。&lt;/p&gt;
&lt;p&gt;这里得到的是连续取值、忽略 MTU、整数分组数和最后一个不足分组等现实限制时的理论答案。实际网络设计还要在这个理论值的基础上考虑协议上限和分组格式。&lt;/p&gt;
&lt;h2&gt;二、题目讲完后的知识点汇总&lt;/h2&gt;
&lt;h3&gt;1. 文件、报文、分组、帧和 bit&lt;/h3&gt;
&lt;p&gt;一个文件不会直接以“整个文件”的形式一次性通过网络。可以先固定下面这张图：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;文件 / 报文
    ↓ 切分
分组
    ↓ 封装
帧
    ↓ 串行发送
bit
    ↓
链路
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;报文&lt;/strong&gt;：应用层要发送的完整数据；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分组&lt;/strong&gt;：网络层把报文切成的小块，是分组交换中的基本单位；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;帧&lt;/strong&gt;：数据链路层在一条链路上的传输单位；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bit&lt;/strong&gt;：物理链路上真正依次传输的 0 和 1。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这次最核心的薄弱点是：以前容易把“文件”“分组”和“bit”直接混在一起。只要这几个层次没有分开，后面的分组数、发送时延和流水线公式都会跟着混乱。&lt;/p&gt;
&lt;h3&gt;2. 不同交换方式在等待什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;交换方式&lt;/th&gt;
&lt;th&gt;主要过程或等待单位&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;电路交换&lt;/td&gt;
&lt;td&gt;先建立一条端到端的线路&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;报文交换&lt;/td&gt;
&lt;td&gt;收到完整报文后再转发&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分组交换&lt;/td&gt;
&lt;td&gt;收到完整分组后再转发&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因此，存储-转发设备等待的是一个完整分组，不是首部刚到达，也不是整个文件全部到达。&lt;/p&gt;
&lt;h3&gt;3. 发送时延和传播时延&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;发送时延&lt;/strong&gt;：把长度为 &lt;code&gt;L bit&lt;/code&gt; 的数据完整推入链路需要的时间：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;d_trans = L / R
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;传播时延&lt;/strong&gt;：信号从链路一端跑到另一端需要的时间：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;d_prop = D / V
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以用两个问题区分它们：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;L / R&lt;/code&gt;：把这串数据完整推出链路入口，需要多久？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;D / V&lt;/code&gt;：信号从链路一端跑到另一端，需要多久？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数据越大，主要影响发送时延；链路距离越长，主要影响传播时延。题目没有给出某种时延，就不要凭直觉额外添加一项“接收时延”。接收端陆续收到 bit 的过程，已经包含在发送与传播的时间关系中。&lt;/p&gt;
&lt;h3&gt;4. 单位换算必须先做&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;1 B = 8 bit
1 Mbps = 10^6 bit/s
1 s = 1000 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特别注意大小写：&lt;code&gt;b&lt;/code&gt; 通常表示 bit，&lt;code&gt;B&lt;/code&gt; 表示 Byte；&lt;code&gt;Mb&lt;/code&gt; 是兆比特，&lt;code&gt;MB&lt;/code&gt; 是兆字节，二者相差 8 倍。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1000B = 8000bit
100Mbps = 100 × 10^6bit/s
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 分组数量和完整分组长度不是一回事&lt;/h3&gt;
&lt;p&gt;这是三道题反复出现的区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;求分组数量时，看每个分组能装多少有效数据；&lt;/li&gt;
&lt;li&gt;求发送时延时，看完整分组有多长，首部也要算进去。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 2010 年题 34 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;有效数据：1000B - 20B = 980B
完整分组：1000B = 8000bit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 1-11 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;有效数据：p bit
完整分组：p + h bit
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 存储-转发为什么会出现流水线&lt;/h3&gt;
&lt;p&gt;错误模型是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;分组 1 到终点 → 分组 2 开始
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正确模型是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;链路 1：分组1 分组2 分组3
链路 2：      分组1 分组2
链路 3：            分组1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不同分组可以同时占用不同链路，所以总时间是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(分组数 + 链路数 - 1) × 一个分组的一次发送时延
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是 &lt;code&gt;N + K - 1&lt;/code&gt;，而不是 &lt;code&gt;N × K&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;7. 这次暴露出的个人薄弱点&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;知识点&lt;/th&gt;
&lt;th&gt;当前状态&lt;/th&gt;
&lt;th&gt;后续练习&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;发送时延 &lt;code&gt;L/R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;已基本理解&lt;/td&gt;
&lt;td&gt;继续练单位统一&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;传播时延 &lt;code&gt;D/V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;已基本理解&lt;/td&gt;
&lt;td&gt;和发送时延放在同一时间轴比较&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B/bit 转换&lt;/td&gt;
&lt;td&gt;需要强化&lt;/td&gt;
&lt;td&gt;单独做单位换算题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分组概念&lt;/td&gt;
&lt;td&gt;刚建立&lt;/td&gt;
&lt;td&gt;反复画“文件→分组→帧→bit”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;存储-转发&lt;/td&gt;
&lt;td&gt;基本理解&lt;/td&gt;
&lt;td&gt;练不同链路数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;流水线&lt;/td&gt;
&lt;td&gt;理解中&lt;/td&gt;
&lt;td&gt;重点区分 &lt;code&gt;N+K-1&lt;/code&gt; 与 &lt;code&gt;N×K&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;三种交换方式&lt;/td&gt;
&lt;td&gt;需要重新整理&lt;/td&gt;
&lt;td&gt;用“交换单位”做对照&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;首部开销&lt;/td&gt;
&lt;td&gt;已理解&lt;/td&gt;
&lt;td&gt;和分组数量一起分析&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;最优分组长度&lt;/td&gt;
&lt;td&gt;能理解推导&lt;/td&gt;
&lt;td&gt;练从总时延式中找变量项&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这些问题的共同根源不是“不会移项”，而是还需要先把题目翻译成一张时间图：数据是什么、被切成什么、每一跳在等什么、哪些时间是公共的、哪些时间才是交换方式带来的额外开销。&lt;/p&gt;
&lt;h3&gt;8. 做题时的固定检查清单&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;题目中的数据量到底是 B 还是 bit？&lt;/li&gt;
&lt;li&gt;速率是否已经统一成 bit/s？&lt;/li&gt;
&lt;li&gt;分组数量使用的是有效数据长度，还是完整分组长度？&lt;/li&gt;
&lt;li&gt;求发送时延时，首部是否已经算进完整分组长度？&lt;/li&gt;
&lt;li&gt;路径上是几条链路，而不是几个交换机？&lt;/li&gt;
&lt;li&gt;题目是否采用存储-转发，是否允许流水线？&lt;/li&gt;
&lt;li&gt;传播时延、排队时延、处理时延是否真的给出？没有给出就不要擅自添加。&lt;/li&gt;
&lt;li&gt;最后把结果从秒换成题目要求的毫秒，并检查数量级。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;结语：先建立画面，再使用公式&lt;/h2&gt;
&lt;p&gt;这三道题暴露的主要问题不是“不会计算”，而是还没有完全建立这张图：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;文件
  ↓ 切分
分组
  ↓ 封装
帧
  ↓ 串行发送
bit
  ↓ 经过多条链路
发送时延 + 传播时延 + 存储-转发流水线
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当这张图稳定下来，公式就不再是孤立的符号：&lt;code&gt;L/R&lt;/code&gt; 表示把数据推出去，&lt;code&gt;D/V&lt;/code&gt; 表示信号跑过去，&lt;code&gt;N+K-1&lt;/code&gt; 表示分组在链路之间形成流水线，&lt;code&gt;√[xh/(k-1)]&lt;/code&gt; 则表示首部浪费和流水线等待之间的平衡。&lt;/p&gt;
</content:encoded></item><item><title>SpringBoot底层实现原理</title><link>https://17356085.github.io/posts/2025/springboot%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/springboot%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</guid><description>SpringBoot底层实现原理</description><pubDate>Fri, 05 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;自动配置的核心机制&lt;/h2&gt;
&lt;h3&gt;1. @SpringBootApplication注解&lt;/h3&gt;
&lt;p&gt;这是自动配置的起点，它实际上是三个注解的组合：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@SpringBootConfiguration  // 标识为配置类
@EnableAutoConfiguration  // 启用自动配置
@ComponentScan           // 组件扫描
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. @EnableAutoConfiguration的工作原理&lt;/h3&gt;
&lt;p&gt;这个注解是自动配置的关键，它通过&lt;code&gt;@Import&lt;/code&gt;导入了&lt;code&gt;AutoConfigurationImportSelector&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. AutoConfigurationImportSelector的实现&lt;/h3&gt;
&lt;p&gt;这个类负责选择并加载自动配置类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class AutoConfigurationImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 获取所有自动配置类
        List&amp;lt;String&amp;gt; configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 去重、排除、过滤
        configurations = removeDuplicates(configurations);
        configurations = getExcluded(configurations);
        configurations = filter(configurations, autoConfigurationMetadata);
        // 返回需要导入的配置类
        return configurations.toArray(new String[0]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 加载自动配置类的过程&lt;/h3&gt;
&lt;p&gt;Spring Boot通过&lt;code&gt;SpringFactoriesLoader&lt;/code&gt;加载配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected List&amp;lt;String&amp;gt; getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List&amp;lt;String&amp;gt; configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(), 
        getBeanClassLoader()
    );
    return configurations;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它会扫描所有jar包下的&lt;code&gt;META-INF/spring.factories&lt;/code&gt;文件，查找key为&lt;code&gt;org.springframework.boot.autoconfigure.EnableAutoConfiguration&lt;/code&gt;的配置。&lt;/p&gt;
&lt;h3&gt;5. 条件注解的使用&lt;/h3&gt;
&lt;p&gt;自动配置类通常使用条件注解来决定是否生效：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@ConditionalOnClass&lt;/strong&gt;: 当classpath中存在指定类时&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@ConditionalOnMissingBean&lt;/strong&gt;: 当容器中不存在指定Bean时&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@ConditionalOnProperty&lt;/strong&gt;: 当配置文件中有指定属性时&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@ConditionalOnWebApplication&lt;/strong&gt;: 当是Web应用时&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = &quot;spring.datasource&quot;, name = &quot;url&quot;)
public class DataSourceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        // 创建数据源
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 配置属性绑定&lt;/h3&gt;
&lt;p&gt;通过&lt;code&gt;@ConfigurationProperties&lt;/code&gt;将配置文件中的属性绑定到Java对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@ConfigurationProperties(prefix = &quot;spring.datasource&quot;)
public class DataSourceProperties {
    private String url;
    private String username;
    private String password;
    // getters and setters
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. 自动配置的执行顺序&lt;/h3&gt;
&lt;p&gt;使用以下注解控制加载顺序：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@AutoConfigureAfter&lt;/strong&gt;: 在指定配置类之后配置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@AutoConfigureBefore&lt;/strong&gt;: 在指定配置类之前配置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@AutoConfigureOrder&lt;/strong&gt;: 指定配置顺序&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8. 自动配置报告&lt;/h3&gt;
&lt;p&gt;可以通过以下方式查看哪些自动配置生效了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;启动时添加&lt;code&gt;--debug&lt;/code&gt;参数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在配置文件中设置&lt;code&gt;debug=true&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用Actuator的&lt;code&gt;/conditions&lt;/code&gt;端点&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自动配置的完整流程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;启动应用&lt;/strong&gt; → SpringApplication.run()&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;解析@SpringBootApplication&lt;/strong&gt; → 发现@EnableAutoConfiguration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;导入AutoConfigurationImportSelector&lt;/strong&gt; → 执行selectImports()&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;加载spring.factories&lt;/strong&gt; → 获取所有自动配置类列表&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;应用条件过滤&lt;/strong&gt; → 根据@Conditional注解决定是否加载&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建配置Bean&lt;/strong&gt; → 将符合条件的配置类注册到容器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;属性绑定&lt;/strong&gt; → 将配置文件中的值绑定到配置类&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Web自动配置的核心组件&lt;/h2&gt;
&lt;h3&gt;1. WebMvcAutoConfiguration&lt;/h3&gt;
&lt;p&gt;这是Spring MVC的核心自动配置类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    // Web MVC相关配置
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 内嵌服务器的自动配置&lt;/h3&gt;
&lt;h4&gt;ServletWebServerFactoryAutoConfiguration&lt;/h4&gt;
&lt;p&gt;负责配置内嵌的Servlet容器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    
    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(
            ServerProperties serverProperties) {
        return new ServletWebServerFactoryCustomizer(serverProperties);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;内嵌Tomcat的配置&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
    
    @Bean
    TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ObjectProvider&amp;lt;TomcatConnectorCustomizer&amp;gt; connectorCustomizers,
            ObjectProvider&amp;lt;TomcatContextCustomizer&amp;gt; contextCustomizers,
            ObjectProvider&amp;lt;TomcatProtocolHandlerCustomizer&amp;lt;?&amp;gt;&amp;gt; protocolHandlerCustomizers) {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        // 配置Tomcat
        return factory;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. DispatcherServlet的自动配置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
    
    @Configuration(proxyBeanMethods = false)
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
    protected static class DispatcherServletConfiguration {
        
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, 
                WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            // 其他配置...
            return dispatcherServlet;
        }
    }
    
    @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {
        
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(
                DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties,
                ObjectProvider&amp;lt;MultipartConfigElement&amp;gt; multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
                    dispatcherServlet, webMvcProperties.getServlet().getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Web MVC核心组件的配置&lt;/h3&gt;
&lt;h4&gt;视图解析器配置&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = &quot;viewResolver&quot;, value = ContentNegotiatingViewResolver.class)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
    
    @Bean
    @ConditionalOnBean(ViewResolver.class)
    @ConditionalOnMissingBean(name = &quot;viewResolver&quot;, value = ContentNegotiatingViewResolver.class)
    public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
        ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
        resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
        resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return resolver;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;静态资源处理&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug(&quot;Default resource handling disabled&quot;);
        return;
    }
    // 配置静态资源路径
    addResourceHandler(registry, &quot;/webjars/**&quot;, &quot;classpath:/META-INF/resources/webjars/&quot;);
    addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), 
        this.resourceProperties.getStaticLocations());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. HttpMessageConverters自动配置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpMessageConverter.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class })
@Import({ JacksonHttpMessageConvertersConfiguration.class,
        GsonHttpMessageConvertersConfiguration.class })
public class HttpMessageConvertersAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider&amp;lt;HttpMessageConverter&amp;lt;?&amp;gt;&amp;gt; converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. Web服务器启动流程&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// SpringApplication.run() 最终会调用
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null &amp;amp;&amp;amp; servletContext == null) {
        // 获取ServletWebServerFactory
        ServletWebServerFactory factory = getWebServerFactory();
        // 创建WebServer
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
}

// Tomcat的实现
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir(&quot;tomcat&quot;);
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. 自动配置的属性配置&lt;/h3&gt;
&lt;p&gt;通过&lt;code&gt;application.properties&lt;/code&gt;或&lt;code&gt;application.yml&lt;/code&gt;配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server:
  port: 8080
  servlet:
    context-path: /app
  tomcat:
    max-threads: 200
    connection-timeout: 20000

spring:
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
    static-path-pattern: /static/**
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Web自动配置的完整流程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;应用启动&lt;/strong&gt; → 检测到Web环境（通过classpath中的Servlet类）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;加载Web相关自动配置&lt;/strong&gt; → ServletWebServerFactoryAutoConfiguration等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建内嵌服务器工厂&lt;/strong&gt; → 如TomcatServletWebServerFactory&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置DispatcherServlet&lt;/strong&gt; → 通过DispatcherServletAutoConfiguration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置Web MVC组件&lt;/strong&gt; → 视图解析器、消息转换器、静态资源等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建并启动Web服务器&lt;/strong&gt; → 调用factory.getWebServer()&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;注册Servlet和Filter&lt;/strong&gt; → 通过ServletContextInitializer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;服务器就绪&lt;/strong&gt; → 开始接收HTTP请求&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>RESTFul编程风格</title><link>https://17356085.github.io/posts/2025/restful%E7%BC%96%E7%A8%8B%E9%A3%8E%E6%A0%BC/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/restful%E7%BC%96%E7%A8%8B%E9%A3%8E%E6%A0%BC/</guid><description>RESTFul编程风格</description><pubDate>Wed, 20 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;RESTFul是WEB服务器接口的一种设计风格。
RESTFul对URL的约束和规范的核心：通过采用&lt;strong&gt;不同的请求方法&lt;/strong&gt;+&lt;strong&gt;URL&lt;/strong&gt;来确定WEB服务中的资源&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;RESTful 的英文全称是 Representational State Transfer（表述性状态转移）。简称REST。&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;表述性（Representational）是：URI + 请求方式。&lt;/li&gt;
&lt;li&gt;状态（State）是：服务器端的数据。&lt;/li&gt;
&lt;li&gt;转移（Transfer）是：变化。&lt;/li&gt;
&lt;li&gt;表述性状态转移是指：通过 URI + 请求方式 来控制服务器端数据的变化。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. 常见RESTFul设计模式&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;GET /users # 获取所有用户 
GET /users/123 # 获取特定用户 
POST /users # 创建新用户 
PUT /users/123 # 更新特定用户 
DELETE /users/123 # 删除特定用户
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 核心原则&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 无状态性（Stateless）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个请求都包含处理该请求所需的全部信息&lt;/li&gt;
&lt;li&gt;服务器不存储客户端的状态信息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 客户端-服务器架构&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;明确分离用户界面和数据存储的关注点&lt;/li&gt;
&lt;li&gt;提高系统的可移植性和可扩展性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 统一接口&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用标准的HTTP方法：GET、POST、PUT、DELETE等&lt;/li&gt;
&lt;li&gt;通过URI唯一标识资源&lt;/li&gt;
&lt;li&gt;使用标准的状态码和响应格式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 资源导向&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将数据和功能视为资源，通过URI进行标识&lt;/li&gt;
&lt;li&gt;例如：&lt;code&gt;/users/123&lt;/code&gt; 表示ID为123的用户资源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. 分层系统&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;允许在客户端和服务器之间使用中间层（如代理、网关）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. RESFul风格与传统方式对比&lt;/h2&gt;
&lt;p&gt;传统的URL是基于动作的，而 RESTful URL 是基于资源和状态的，因此 RESTful URL 更加清晰和易于理解&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;传统的 URL&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;RESTful URL&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET /getUserById?id=1&lt;/td&gt;
&lt;td&gt;GET /user/1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET /getAllUser&lt;/td&gt;
&lt;td&gt;GET /user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST /addUser&lt;/td&gt;
&lt;td&gt;POST /user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST /modifyUser&lt;/td&gt;
&lt;td&gt;PUT /user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET /deleteUserById?id=1&lt;/td&gt;
&lt;td&gt;DELETE /user/1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;4. RESTful的优势&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;简单性&lt;/strong&gt;：使用标准HTTP协议，易于理解和实现&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可扩展性&lt;/strong&gt;：无状态特性使系统易于扩展&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;互操作性&lt;/strong&gt;：标准化接口便于不同系统间的集成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存友好&lt;/strong&gt;：GET请求可以被缓存，提高性能&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Spring IOC</title><link>https://17356085.github.io/posts/2025/spring-ioc/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/spring-ioc/</guid><description>Spring IOC</description><pubDate>Wed, 20 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Spring IOC（Inversion of Control，&lt;strong&gt;控制反转&lt;/strong&gt;），也被称为依赖注入（Dependency Injection，DI）。
&lt;strong&gt;核心概念&lt;/strong&gt;：&lt;strong&gt;控制反转&lt;/strong&gt;将对象的创建、依赖关系的管理等（例如对象的创建、维护权）控制权从应用程序代码转移给外部容器（Spring容器）的设计模式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. 依赖注入&lt;/h2&gt;
&lt;p&gt;依赖注入用于实现对象之间的解耦。通过依赖注入，对象不在自己创建或查找依赖对象，而是由外部容器负责将依赖的对象注入进来。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// UserDao
public class UserDao {

    public void insert(){
        System.out.println(&quot;正在保存用户数据。&quot;);
    }
}


// UserService
public class UserService {

    private UserDao userDao;
    
    // 使用构造器注入，必须提供构造方法
    public UserService(UserDao userDao) {
	    this.userDao = userDao;
    } 
    
    // 使用set方式注入，必须提供set方法。
    // 反射机制要调用这个方法给属性赋值的。
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save(){
        userDao.insert();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在Spring中依赖注入主要有以下三种方式：&lt;/p&gt;
&lt;p&gt;1️⃣ 构造器注入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;userDaoBean&quot; class=&quot;com.powernode.spring6.dao.UserDao&quot;/&amp;gt;
&amp;lt;bean id=&quot;userServiceBean&quot; class=&quot;com.powernode.spring6.service.UserService&quot;&amp;gt;
  &amp;lt;!--index=&quot;0&quot;表示构造方法的第一个参数，将userDaoBean对象传递给构造方法的第一个参数。--&amp;gt;
  &amp;lt;constructor-arg index=&quot;0&quot; ref=&quot;userDaoBean&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣ setter注入&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现原理&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过property标签获取到属性名：userDao&lt;/li&gt;
&lt;li&gt;通过属性名推断出set方法名：setUserDao&lt;/li&gt;
&lt;li&gt;通过反射机制调用setUserDao()方法给属性赋值&lt;/li&gt;
&lt;li&gt;property标签的name是属性名。&lt;/li&gt;
&lt;li&gt;property标签的ref是要注入的bean对象的id。&lt;strong&gt;(通过ref属性来完成bean的装配，这是bean最简单的一种装配方式。装配指的是：创建系统组件之间关联的动作)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&amp;gt;

    &amp;lt;bean id=&quot;userDaoBean&quot; class=&quot;com.powernode.spring6.dao.UserDao&quot;/&amp;gt;

    &amp;lt;bean id=&quot;userServiceBean&quot; class=&quot;com.powernode.spring6.service.UserService&quot;&amp;gt;
        &amp;lt;property name=&quot;userDao&quot; ref=&quot;userDaoBean&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;

&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3️⃣ 字段注入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class UserService {
	
	@Autowired
    private UserDao userDao;
    
    // 使用构造器注入，必须提供构造方法
    public UserService(UserDao userDao) {
	    this.userDao = userDao;
    } 
    
    // 使用set方式注入，必须提供set方法。
    // 反射机制要调用这个方法给属性赋值的。
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save(){
        userDao.insert();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Bean&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Bean是由Spring IOC容器管理的对象，构成应用程序骨干并由Spring IOC容器管理的对象。Bean是一个被思力华、组装和管理的对象&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.1 配置方式&lt;/h3&gt;
&lt;p&gt;1️⃣ XML配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;userService&quot; class=&quot;com.example.UserService&quot;&amp;gt;
    &amp;lt;property name=&quot;userDao&quot; ref=&quot;userDao&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣ 注解配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Component
@Service
@Repository
@Controller
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3️⃣ Java配置类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 Bean的作用域&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;你可以通过在bean标签中指定scope属性值的方式，指定Bean的作用范围。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;scope属性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;singleton&lt;/strong&gt;（默认）：每个IoC容器只有一个Bean实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;prototype&lt;/strong&gt;：每次获取都创建新实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;request&lt;/strong&gt;：Web环境下，每个HTTP请求一个实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;session&lt;/strong&gt;：Web环境下，每个HTTP Session一个实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;application&lt;/strong&gt;：Web环境下，每个ServletContext一个实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;websocket&lt;/strong&gt;：一个websocket生命周期对应一个Bean。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;global session&lt;/strong&gt;：&lt;strong&gt;portlet应用中专用的&lt;/strong&gt;。如果在Servlet的WEB应用中使用global session的话，和session一个效果。（portlet和servlet都是规范。servlet运行在servlet容器中，例如Tomcat。portlet运行在portlet容器中。）&lt;/li&gt;
&lt;li&gt;自定义scope：了解即可&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&amp;gt;

    &amp;lt;bean id=&quot;sb&quot; class=&quot;com.powernode.spring6.beans.SpringBean&quot; scope=&quot;prototype&quot; /&amp;gt;

&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 Bean的生命周期&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Bean生命周期可大致分为如下七步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;实例化：容器创建Bean实例子&lt;/li&gt;
&lt;li&gt;Bean属性注入：设置Bean的属性值和依赖&lt;/li&gt;
&lt;li&gt;&lt;em&gt;初始化前处理：BeanPostProcessor.postProcessBeforeInitialization()&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;初始化：调用初始化方法（@PostConstruct或init-method）&lt;/li&gt;
&lt;li&gt;&lt;em&gt;初始化后处理：BeanPostProcessor.postProcessAfterInitialization()&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;使用：Bean可以被应用程序使用&lt;/li&gt;
&lt;li&gt;销毁：容器关闭是调用销毁方法（@PreDestroy或destroy-method）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：如果你还想在初始化前和初始化后添加代码，可以加入“Bean后处理器”。 编写一个类实现BeanPostProcessor类，并且重写before和after方法：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/spring-ioc/ioc-01.png&quot; alt=&quot;Spring IOC 容器示意图&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;2.3.1 代码示例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&amp;gt;

    &amp;lt;!--
    init-method属性指定初始化方法。
    destroy-method属性指定销毁方法。
    --&amp;gt;
    &amp;lt;bean id=&quot;userBean&quot; class=&quot;com.powernode.spring6.bean.User&quot; init-method=&quot;initBean&quot; destroy-method=&quot;destroyBean&quot;&amp;gt;
        &amp;lt;property name=&quot;name&quot; value=&quot;zhangsan&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;

&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author 动力节点
 * @version 1.0
 * @className LogBeanPostProcessor
 * @since 1.0
 **/
public class LogBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(&quot;Bean后处理器的before方法执行，即将开始初始化&quot;);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(&quot;Bean后处理器的after方法执行，已完成初始化&quot;);
        return bean;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;一定要注意，在Spring.xml文件中配置的Bean后处理器将作用于所有的Bean。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!--配置Bean后处理器。这个后处理器将作用于当前配置文件中所有的bean。--&amp;gt;
&amp;lt;bean class=&quot;com.powernode.spring6.bean.LogBeanPostProcessor&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.3.2 Bean生命周期的“十步”&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/spring-ioc/ioc-02.png&quot; alt=&quot;Spring Bean 生命周期&quot; /&gt;Aware相关的接口包括：BeanNameAware、BeanClassLoaderAware、BeanFactoryAware&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当Bean实现了BeanNameAware，Spring会将Bean的名字传递给Bean。&lt;/li&gt;
&lt;li&gt;当Bean实现了BeanClassLoaderAware，Spring会将加载该Bean的类加载器传递给Bean。&lt;/li&gt;
&lt;li&gt;当Bean实现了BeanFactoryAware，Spring会将Bean工厂对象传递给Bean。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以让User类实现五个接口，并实现所有方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BeanNameAware&lt;/li&gt;
&lt;li&gt;BeanClassLoaderAware&lt;/li&gt;
&lt;li&gt;BeanFactoryAware&lt;/li&gt;
&lt;li&gt;InitializingBean&lt;/li&gt;
&lt;li&gt;DisposableBean&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;

public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
    private String name;

    public User() {
        System.out.println(&quot;1.实例化Bean&quot;);
    }

    public void setName(String name) {
        this.name = name;
        System.out.println(&quot;2.Bean属性赋值&quot;);
    }

    public void initBean(){
        System.out.println(&quot;6.初始化Bean&quot;);
    }

    public void destroyBean(){
        System.out.println(&quot;10.销毁Bean&quot;);
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println(&quot;3.类加载器：&quot; + classLoader);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println(&quot;3.Bean工厂：&quot; + beanFactory);
    }

    @Override
    public void setBeanName(String name) {
        System.out.println(&quot;3.bean名字：&quot; + name);
    }

    @Override
    public void destroy() throws Exception {
        System.out.println(&quot;9.DisposableBean destroy&quot;);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(&quot;5.afterPropertiesSet执行&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class LogBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(&quot;4.Bean后处理器的before方法执行，即将开始初始化&quot;);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(&quot;7.Bean后处理器的after方法执行，已完成初始化&quot;);
        return bean;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 Bean的循环依赖问题&lt;/h3&gt;
&lt;h4&gt;2.4.1 什么是循环依赖？&lt;/h4&gt;
&lt;p&gt;在实例化 A 时需要先创建 B，而在创建 B 时又需要 A，于是无限递归，无法完成。
&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/spring-ioc/ioc-03.png&quot; alt=&quot;Spring 依赖注入流程&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;2.4.2 为什么会出现循环依赖问题？&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;设计不合理的对象关系&lt;/strong&gt;&lt;br /&gt;
例如两个类职责不清，互相调用彼此的服务，而不是通过中间层解耦。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;过度使用 @Autowired&lt;/strong&gt;&lt;br /&gt;
无论是字段注入还是构造函数注入，如果没有注意对象间的依赖方向，很容易不小心就写成了环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;层次划分不够清晰&lt;/strong&gt;
比如 Controller → Service → DAO 的分层模式中，Service A 调用 Service B，而 Service B 又反过来调用 Service A。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;2.4.3 如何解决循环依赖问题&lt;/h4&gt;
&lt;p&gt;采用&lt;strong&gt;set注入+singleton模式&lt;/strong&gt;可有效解决循环依赖问题，示例代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&amp;gt;

    &amp;lt;bean id=&quot;husbandBean&quot; class=&quot;com.powernode.spring6.bean.Husband&quot; scope=&quot;singleton&quot;&amp;gt;
        &amp;lt;property name=&quot;name&quot; value=&quot;张三&quot;/&amp;gt;
        &amp;lt;property name=&quot;wife&quot; ref=&quot;wifeBean&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;
    &amp;lt;bean id=&quot;wifeBean&quot; class=&quot;com.powernode.spring6.bean.Wife&quot; scope=&quot;singleton&quot;&amp;gt;
        &amp;lt;property name=&quot;name&quot; value=&quot;小花&quot;/&amp;gt;
        &amp;lt;property name=&quot;husband&quot; ref=&quot;husbandBean&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。 实例化Bean的时候：调用无参数构造方法来完成。&lt;strong&gt;此时可以先不给属性赋值，可以提前将该Bean对象“曝光”给外界。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给Bean属性赋值的时候：调用setter方法来完成。&lt;/p&gt;
&lt;p&gt;两个步骤是完全可以分离开去完成的，并且这两步不要求在同一个时间点上完成。 也就是说，Bean都是单例的，我们可以先把所有的单例Bean实例化出来，放到一个集合当中（我们可以称之为缓存），所有的单例Bean全部实例化完成之后，以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spring框架底层实现：&lt;/strong&gt;
&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/spring-ioc/ioc-04.png&quot; alt=&quot;Spring BeanFactory 结构&quot; /&gt;
在以上类中包含三个重要的属性： _&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cache of singleton objects: bean name to bean instance.&lt;/strong&gt;_ &lt;strong&gt;单例对象的缓存：key存储bean名称，value存储Bean对象【一级缓存】&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache of early singleton objects: bean name to bean instance.&lt;/strong&gt;_ **早期单例对象的缓存：key存储bean名称，value存储早期的Bean对象【二级缓存】&lt;/li&gt;
&lt;li&gt;** &lt;em&gt;&lt;strong&gt;Cache of singleton factories: bean name to ObjectFactory.&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;单例工厂缓存：key存储bean名称，value存储该Bean对应的ObjectFactory对象【三级缓存】&lt;/strong&gt; 这三个缓存其实本质上是三个Map集合。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这三个缓存本质上是三个Map集合。
我们再来看，在该类中有这样一个方法addSingletonFactory()，这个方法的作用是：将创建Bean对象的ObjectFactory对象提前曝光。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/spring-ioc/ioc-05.png&quot; alt=&quot;Spring ApplicationContext 结构&quot; /&gt;
再分析下面的源码
&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/spring-ioc/ioc-06.png&quot; alt=&quot;Spring IOC 组件关系&quot; /&gt;
从源码中可以看到，spring会先从一级缓存中获取Bean，如果获取不到，则从二级缓存中获取Bean，如果二级缓存还是获取不到，则从三级缓存中获取之前曝光的ObjectFactory对象，通过ObjectFactory对象获取Bean实例，这样就解决了循环依赖的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;总结：&lt;/strong&gt;
&lt;strong&gt;Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB，ClassB又依赖ClassA，形成依赖闭环。Spring在创建ClassA对象后，不需要等给属性赋值，直接将其曝光到bean缓存当中。在解析ClassA的属性时，又发现依赖于ClassB，再次去获取ClassB，当解析ClassB的属性时，又发现需要ClassA的属性，但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中，则无需创建新的的ClassA的实例，直接从缓存中获取即可。从而解决循环依赖问题。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;3. Spring IOC注解式开发&lt;/h2&gt;
&lt;p&gt;负责声明Bean的注解，常见的有：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;@Component&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
    String value();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;@Controller&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default &quot;&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;@Service&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    @AliasFor(
        annotation = Component.class
    )
    String value() default &quot;&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;@Repository&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
    @AliasFor(
        annotation = Component.class
    )
    String value() default &quot;&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过上述源码，我们可以知道@Controller、@Service、@Repository这三个注解都是@Component注解的别名。为了程序的可读性，建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;控制器类上使用：Controller&lt;/li&gt;
&lt;li&gt;service类上使用：Service&lt;/li&gt;
&lt;li&gt;dao类上使用：Repository&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.1 在Spring中使用注解&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 在配置文件中添加context命名空间 --&amp;gt;
&amp;lt;!-- 配置文件中指定要扫描的包 --&amp;gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&amp;gt;
    &amp;lt;context:component-scan base-package=&quot;com.powernode.spring6.bean&quot;/&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 在Bean类上使用注解
@Component(value = &quot;userBean&quot;)
public class User {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 选择性实例化Bean&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;use-default-filters=&quot;true&quot; 表示：使用spring默认的规则，只要有Component、Controller、Service、Repository中的任意一个注解标注，则进行实例化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;use-default-filters=&quot;false&quot;&lt;/strong&gt; 表示：不再spring默认实例化规则，即使有Component、Controller、Service、Repository这些注解标注，也不再实例化。&lt;/li&gt;
&lt;li&gt;&amp;lt;context:include-filter type=&quot;annotation&quot; expression=&quot;org.springframework.stereotype.Controller&quot;/&amp;gt; 表示只有Controller进行实例化。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&amp;gt;

    &amp;lt;context:component-scan base-package=&quot;com.powernode.spring6.bean3&quot; use-default-filters=&quot;false&quot;&amp;gt;
        &amp;lt;context:include-filter type=&quot;annotation&quot; expression=&quot;org.springframework.stereotype.Controller&quot;/&amp;gt;
    &amp;lt;/context:component-scan&amp;gt;
    
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 负责注入的注解&lt;/h3&gt;
&lt;p&gt;1️⃣ &lt;strong&gt;@Value&lt;/strong&gt;：当属性是简单类型时可使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 不要忘记打开包扫描 --&amp;gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&amp;gt;
    &amp;lt;context:component-scan base-package=&quot;com.powernode.spring6.bean4&quot;/&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;@toString
@Commponent
public class User {
    @Value(value = &quot;zhangsan&quot;)
    private String name;
    @Value(&quot;20&quot;)
    private int age;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣ &lt;strong&gt;@Autowired&lt;/strong&gt;：可以用来注入非简单类型，单独使用@Autowrite注解时，默认根据类型装配
源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
	/* required属性值默认为true，表示注入的Bean必须存在，否则报错
	 * required属性值为false时，表示注入Bean存不存在都不会报错，存在则注入，不存在也不报错
	 */
	boolean required() default true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3️⃣ &lt;strong&gt;@Qualifier&lt;/strong&gt;：@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配，在@Qualifier注解中指定Bean名称。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*
 * UserDao的子类
 */

@Repository // 这里没有给bean起名，默认名字是：userDaoForOracle
public class UserDaoForOracle implements UserDao{
    @Override
    public void insert() {
        System.out.println(&quot;正在向Oracle数据库插入User数据&quot;);
    }
}

/*
 * UserService
 */
@Service
public class UserService {

    private UserDao userDao;

    @Autowired
    @Qualifier(&quot;userDaoForOracle&quot;) // 这个是bean的名字。
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save(){
        userDao.insert();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4️⃣ &lt;strong&gt;@Resource&lt;/strong&gt;：与@Autowired一样可用于完成非简单类型的注解，两者之间的差异&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Resource注解是JDK的一部分，@Autowired注解是Spring框架，前者比后者更具有通用性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;@Resource注解默认根据名称装配byName，未指定name时，使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。&lt;/strong&gt;；@Autowired注解默认根据类型装配byType，如果想根据名称装配，需要配合@Qualifier注解一起用。&lt;/li&gt;
&lt;li&gt;@Resource注解用在属性、setter方法上；@Autowrite注解用在属性、setter方法、构造方法、构造方法参数上。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 全注解开发&lt;/h2&gt;
&lt;p&gt;使用注解代替配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
@ComponentScan({&quot;com.powernode.spring6.dao&quot;, &quot;com.powernode.spring6.service&quot;})
public class Spring6Configuration {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编写测试程序：不在new ClassPathXmlApplicationContext()对象&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Test
public void testNoXml(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
    UserService userService = applicationContext.getBean(&quot;userService&quot;, UserService.class);
    userService.save();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Spring IoC容器底层实现原理&lt;/h2&gt;
&lt;h3&gt;4.1 核心架构设计&lt;/h3&gt;
&lt;h4&gt;容器接口层次结构&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;BeanFactory (顶层接口)
    ├── ListableBeanFactory (可列举Bean)
    ├── HierarchicalBeanFactory (分层Bean工厂)
    ├── ConfigurableBeanFactory (可配置Bean工厂)
    └── ApplicationContext (应用上下文)
            ├── ConfigurableApplicationContext
            └── AbstractApplicationContext (抽象实现)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;核心接口职责：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BeanFactory&lt;/code&gt;：基础容器，定义了getBean()等基本方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ApplicationContext&lt;/code&gt;：高级容器，提供事件发布、国际化等企业功能&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ConfigurableApplicationContext&lt;/code&gt;：可配置的应用上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 底层实现机制&lt;/h3&gt;
&lt;h4&gt;4.2.1 Bean定义注册机制&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;BeanDefinition数据结构&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class GenericBeanDefinition implements BeanDefinition {
    private String beanClassName;           // Bean类名
    private String scope = SCOPE_SINGLETON; // 作用域
    private boolean lazyInit = false;       // 是否懒加载
    private ConstructorArgumentValues constructorArgumentValues; // 构造参数
    private MutablePropertyValues propertyValues; // 属性值
    private String initMethodName;          // 初始化方法
    private String destroyMethodName;       // 销毁方法
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注册流程&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// DefaultListableBeanFactory核心注册方法
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
    // 1. 验证BeanDefinition
    if (beanDefinition instanceof AbstractBeanDefinition) {
        ((AbstractBeanDefinition) beanDefinition).validate();
    }
    
    // 2. 检查是否已存在
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
        // 处理覆盖逻辑
    }
    
    // 3. 注册到容器
    this.beanDefinitionMap.put(beanName, beanDefinition);
    this.beanDefinitionNames.add(beanName);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.2.2 Bean实例化核心流程&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;AbstractAutowireCapableBeanFactory.createBean()方法&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 解析Bean类型
    Class&amp;lt;?&amp;gt; resolvedClass = resolveBeanClass(mbd, beanName);
    
    // 2. 准备方法覆盖
    mbd.prepareMethodOverrides();
    
    // 3. 给BeanPostProcessor机会返回代理对象
    Object bean = resolveBeforeInstantiation(beanName, mbd);
    if (bean != null) {
        return bean;
    }
    
    // 4. 实际创建Bean
    Object beanInstance = doCreateBean(beanName, mbd, args);
    return beanInstance;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;doCreateBean()详细实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    BeanWrapper instanceWrapper = null;
    
    // 1. 创建Bean实例
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    
    Object bean = instanceWrapper.getWrappedInstance();
    
    // 2. 解决循环依赖 - 提前暴露Bean
    boolean earlySingletonExposure = (mbd.isSingleton() &amp;amp;&amp;amp; 
        this.allowCircularReferences &amp;amp;&amp;amp; isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -&amp;gt; getEarlyBeanReference(beanName, mbd, bean));
    }
    
    // 3. 填充属性（依赖注入）
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化Bean
    Object exposedObject = initializeBean(beanName, bean, mbd);
    
    return exposedObject;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.2.3 三级缓存解决循环依赖&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;DefaultSingletonBeanRegistry缓存结构&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class DefaultSingletonBeanRegistry {
    // 一级缓存：完成初始化的单例Bean
    private final Map&amp;lt;String, Object&amp;gt; singletonObjects = new ConcurrentHashMap&amp;lt;&amp;gt;(256);
    
    // 二级缓存：完成实例化但未完成初始化的Bean
    private final Map&amp;lt;String, Object&amp;gt; earlySingletonObjects = new ConcurrentHashMap&amp;lt;&amp;gt;(16);
    
    // 三级缓存：Bean工厂对象，用于解决循环依赖
    private final Map&amp;lt;String, ObjectFactory&amp;lt;?&amp;gt;&amp;gt; singletonFactories = new HashMap&amp;lt;&amp;gt;(16);
    
    // 正在创建的Bean名称
    private final Set&amp;lt;String&amp;gt; singletonsCurrentlyInCreation = 
        Collections.newSetFromMap(new ConcurrentHashMap&amp;lt;&amp;gt;(16));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;获取单例Bean流程&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    
    // 2. 一级缓存没有且正在创建中
    if (singletonObject == null &amp;amp;&amp;amp; isSingletonCurrentlyInCreation(beanName)) {
        // 从二级缓存获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        
        // 3. 二级缓存也没有且允许早期引用
        if (singletonObject == null &amp;amp;&amp;amp; allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // 双重检查
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 4. 从三级缓存获取ObjectFactory
                        ObjectFactory&amp;lt;?&amp;gt; singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            // 移到二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.2.4. 依赖注入实现机制&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;AutowiredAnnotationBeanPostProcessor核心逻辑&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public PropertyValues postProcessPropertyValues(PropertyValues pvs, 
    PropertyDescriptor[] pds, Object bean, String beanName) {
    
    // 1. 查找需要注入的元数据
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    
    // 2. 执行注入
    metadata.inject(bean, beanName, pvs);
    return pvs;
}

// 字段注入实现
private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
    protected void inject(Object bean, String beanName, PropertyValues pvs) {
        Field field = (Field) this.member;
        Object value;
        
        // 解析依赖
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        
        // 反射设置字段值
        if (value != null) {
            ReflectionUtils.makeAccessible(field);
            field.set(bean, value);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.2.5 Bean生命周期管理&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;AbstractAutowireCapableBeanFactory.initializeBean()方法&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
    // 1. 调用Aware接口方法
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged(() -&amp;gt; {
            invokeAwareMethods(beanName, bean);
            return null;
        });
    } else {
        invokeAwareMethods(beanName, bean);
    }
    
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 2. 调用BeanPostProcessor的postProcessBeforeInitialization
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    
    // 3. 调用初始化方法
    invokeInitMethods(beanName, wrappedBean, mbd);
    
    if (mbd == null || !mbd.isSynthetic()) {
        // 4. 调用BeanPostProcessor的postProcessAfterInitialization
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    
    return wrappedBean;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.2.6 容器启动流程&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;AbstractApplicationContext.refresh()方法&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备刷新上下文
        prepareRefresh();
        
        // 2. 获取BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // 3. 准备BeanFactory
        prepareBeanFactory(beanFactory);
        
        try {
            // 4. 允许子类对BeanFactory进行后处理
            postProcessBeanFactory(beanFactory);
            
            // 5. 调用BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);
            
            // 6. 注册BeanPostProcessor
            registerBeanPostProcessors(beanFactory);
            
            // 7. 初始化MessageSource
            initMessageSource();
            
            // 8. 初始化事件多播器
            initApplicationEventMulticaster();
            
            // 9. 子类特定的刷新操作
            onRefresh();
            
            // 10. 注册事件监听器
            registerListeners();
            
            // 11. 实例化所有非懒加载的单例Bean
            finishBeanFactoryInitialization(beanFactory);
            
            // 12. 完成刷新
            finishRefresh();
        } catch (BeansException ex) {
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 关键技术实现&lt;/h3&gt;
&lt;h4&gt;1. 反射机制运用&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;Class.forName()&lt;/code&gt;加载类&lt;/li&gt;
&lt;li&gt;通过&lt;code&gt;Constructor.newInstance()&lt;/code&gt;创建实例&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;Method.invoke()&lt;/code&gt;调用方法&lt;/li&gt;
&lt;li&gt;通过&lt;code&gt;Field.set()&lt;/code&gt;设置字段值&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. 工厂模式实现&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;BeanFactory作为顶层工厂接口&lt;/li&gt;
&lt;li&gt;不同实现提供不同的Bean创建策略&lt;/li&gt;
&lt;li&gt;ObjectFactory用于延迟创建和解决循环依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. 策略模式应用&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;InstantiationStrategy：实例化策略&lt;/li&gt;
&lt;li&gt;AutowireCapableBeanFactory：自动装配策略&lt;/li&gt;
&lt;li&gt;BeanPostProcessor：Bean后处理策略&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4. 观察者模式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;ApplicationEvent事件机制&lt;/li&gt;
&lt;li&gt;ApplicationListener监听器&lt;/li&gt;
&lt;li&gt;ApplicationEventMulticaster事件多播器&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Spring IoC容器的底层实现充分体现了面向对象设计原则，通过精巧的架构设计和多种设计模式的综合运用，实现了高度灵活和可扩展的依赖注入容器。&lt;/p&gt;
</content:encoded></item><item><title>Java中的注解</title><link>https://17356085.github.io/posts/2025/java%E4%B8%AD%E7%9A%84%E6%B3%A8%E8%A7%A3/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/java%E4%B8%AD%E7%9A%84%E6%B3%A8%E8%A7%A3/</guid><description>了解Spring AOP的铺垫知识</description><pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Java中的注解的作用，主要有以下几个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;生成文档，通过代码里标识的元数据生成javadoc文档。&lt;/li&gt;
&lt;li&gt;编译检查，通过代码里标识的元数据让编译器在编译期间进行检查验证。&lt;/li&gt;
&lt;li&gt;编译时动态处理，编译时通过代码里标识的元数据动态处理，例如动态生成代码。&lt;/li&gt;
&lt;li&gt;运行时动态处理，运行时通过代码里标识的元数据动态处理，例如使用反射注入实例。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. 基本语法&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;组成元素&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@&lt;/code&gt; 符号：标识这是一个注解&lt;/li&gt;
&lt;li&gt;注解名：注解的类型名称&lt;/li&gt;
&lt;li&gt;参数列表：可选，用括号包含的键值对&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@注解名(参数列表) public class MyClass { // 类内容 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Java中的内置注解&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这些内置注解在实际开发中有什么用？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.1 标准注解&lt;/h3&gt;
&lt;p&gt;1️⃣ &lt;code&gt;@Override&lt;/code&gt; ：用于标记重写父类，编译器会检查是否真正重写了父类方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Child extends Parent {
    @Override
    public void method() {
        // 重写父类方法
        System.out.println(&quot;Child implementation&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣ &lt;code&gt;@Deprecated&lt;/code&gt;：标记已过时的程序元素，编译器会产生警告。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class OldClass {
    @Deprecated
    public void oldMethod() {
        System.out.println(&quot;This method is deprecated&quot;);
    }
    
    public void newMethod() {
        System.out.println(&quot;Use this method instead&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3️⃣&lt;code&gt;@SuppressWarnings&lt;/code&gt;：抑制编译器警告。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class WarningExample {
    @SuppressWarnings(&quot;unchecked&quot;)
    public void method() {
        List list = new ArrayList(); // 原本会有unchecked警告
        list.add(&quot;item&quot;);
    }
    
    @SuppressWarnings({&quot;deprecation&quot;, &quot;unused&quot;})
    public void multipleWarnings() {
        OldClass old = new OldClass();
        old.oldMethod(); // 使用了deprecated方法
        String unused = &quot;never used&quot;; // 未使用的变量
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4️⃣&lt;code&gt;SafeVarargs&lt;/code&gt;：用于可变参数方法，抑制堆污染警告。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class VarargsExample {
    @SafeVarargs
    public static &amp;lt;T&amp;gt; void printAll(T... items) {
        for (T item : items) {
            System.out.println(item);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5️⃣&lt;code&gt;@FunctionalInterface&lt;/code&gt;：标记函数式接口，确保接口只有一个抽象方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
    
    // 可以有默认方法
    default void printResult(int result) {
        System.out.println(&quot;Result: &quot; + result);
    }
    
    // 可以有静态方法
    static void info() {
        System.out.println(&quot;This is a calculator interface&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 元注解&lt;/h3&gt;
&lt;p&gt;元注解是用来注解其他注解的注解。&lt;/p&gt;
&lt;p&gt;1️⃣&lt;code&gt;@Target&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;用来指定注解可以用在哪些程序元素上。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.FIELD})
public @interface MyAnnotation {
    String value();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ElementType枚举值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TYPE&lt;/code&gt;：类、接口、枚举&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FIELD&lt;/code&gt;：字段&lt;/li&gt;
&lt;li&gt;&lt;code&gt;METHOD&lt;/code&gt;：方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PARAMETER&lt;/code&gt;：方法参数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CONSTRUCTOR&lt;/code&gt;：构造器&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LOCAL_VARIABLE&lt;/code&gt;：局部变量&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ANNOTATION_TYPE&lt;/code&gt;：注解类型&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PACKAGE&lt;/code&gt;：包&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2️⃣&lt;code&gt;@Retention&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;指定注解的保留策略&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
    String value();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RetentionPolicy枚举值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SOURCE&lt;/code&gt;：只在源码中保留，编译时丢弃&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLASS&lt;/code&gt;：编译到class文件中，运行时不可获取（默认）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RUNTIME&lt;/code&gt;：运行时可以通过反射获取&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3️⃣ &lt;code&gt;@Documented&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;表示注解应该被javadoc记录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.annotation.Documented;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DocumentedAnnotation {
    String author();
    String version() default &quot;1.0&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4️⃣ &lt;code&gt;@Inherited&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;表示注解可以被子类继承&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.annotation.Inherited;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InheritableAnnotation {
    String value();
}

@InheritableAnnotation(&quot;parent&quot;)
public class Parent {
}

// Child类会自动继承@InheritableAnnotation注解
public class Child extends Parent {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 自定义注解&lt;/h2&gt;
&lt;h3&gt;3.1 定义&lt;/h3&gt;
&lt;p&gt;⚠️注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注解元素只能是基本类型、String、Class、enum、注解类型，以及这些类型的数组&lt;/li&gt;
&lt;li&gt;不能有参数列表&lt;/li&gt;
&lt;li&gt;不能有throws子句&lt;/li&gt;
&lt;li&gt;返回值不能是void&lt;/li&gt;
&lt;li&gt;可以有默认值，使用default关键字&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCustomAnnotation {
    // 注解元素（属性）
    String value();                    // 必需参数
    String name() default &quot;unknown&quot;;   // 可选参数，有默认值
    int priority() default 0;
    String[] tags() default {};        // 数组类型
    Class&amp;lt;?&amp;gt; targetClass() default Object.class; // Class类型
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 注解处理&lt;/h2&gt;
&lt;p&gt;1️⃣ 运行时注解处理（反射）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.reflect.Method;

public class AnnotationProcessor {
    
    public static void processClass(Class&amp;lt;?&amp;gt; clazz) {
        // 检查类上的注解
        if (clazz.isAnnotationPresent(MyCustomAnnotation.class)) {
            MyCustomAnnotation annotation = clazz.getAnnotation(MyCustomAnnotation.class);
            System.out.println(&quot;Class annotation:&quot;);
            System.out.println(&quot;  Value: &quot; + annotation.value());
            System.out.println(&quot;  Name: &quot; + annotation.name());
            System.out.println(&quot;  Priority: &quot; + annotation.priority());
            System.out.println(&quot;  Tags: &quot; + String.join(&quot;, &quot;, annotation.tags()));
        }
        
        // 检查方法上的注解
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyCustomAnnotation.class)) {
                MyCustomAnnotation annotation = method.getAnnotation(MyCustomAnnotation.class);
                System.out.println(&quot;Method &quot; + method.getName() + &quot; annotation:&quot;);
                System.out.println(&quot;  Value: &quot; + annotation.value());
            }
        }
    }
    
    public static void main(String[] args) {
        processClass(TestClass.class);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣编译时注解处理器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.lang.model.SourceVersion;
import java.util.Set;

@SupportedAnnotationTypes(&quot;com.example.MyCustomAnnotation&quot;)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
    
    @Override
    public boolean process(Set&amp;lt;? extends TypeElement&amp;gt; annotations, 
                          RoundEnvironment roundEnv) {
        
        for (Element element : roundEnv.getElementsAnnotatedWith(MyCustomAnnotation.class)) {
            MyCustomAnnotation annotation = element.getAnnotation(MyCustomAnnotation.class);
            
            // 在编译时处理注解
            processingEnv.getMessager().printMessage(
                Diagnostic.Kind.NOTE,
                &quot;Processing element: &quot; + element.getSimpleName() + 
                &quot; with value: &quot; + annotation.value()
            );
        }
        
        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 实际应用场景&lt;/h2&gt;
&lt;h3&gt;5.1 Spring框架中的注解&lt;/h3&gt;
&lt;p&gt;1️⃣依赖注入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Component
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Value(&quot;${app.name}&quot;)
    private String appName;
    
    public User findById(Long id) {
        return userRepository.findById(id);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣Web开发&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api/users&quot;)
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&amp;lt;User&amp;gt; getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
    
    @PostMapping
    @Transactional
    public ResponseEntity&amp;lt;User&amp;gt; createUser(@RequestBody @Valid User user) {
        User savedUser = userService.save(user);
        return ResponseEntity.ok(savedUser);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 数据库映射（JPA）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@Entity
@Table(name = &quot;users&quot;)
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = &quot;username&quot;, unique = true, nullable = false)
    private String username;
    
    @Column(name = &quot;email&quot;)
    @Email
    private String email;
    
    @OneToMany(mappedBy = &quot;user&quot;, cascade = CascadeType.ALL)
    private List&amp;lt;Order&amp;gt; orders;
    
    // getters and setters
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 数据验证&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class UserDto {
    
    @NotNull(message = &quot;用户名不能为空&quot;)
    @Size(min = 3, max = 20, message = &quot;用户名长度必须在3-20之间&quot;)
    private String username;
    
    @Email(message = &quot;邮箱格式不正确&quot;)
    @NotBlank(message = &quot;邮箱不能为空&quot;)
    private String email;
    
    @Min(value = 18, message = &quot;年龄不能小于18&quot;)
    @Max(value = 100, message = &quot;年龄不能大于100&quot;)
    private Integer age;
    
    // getters and setters
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.4 测试注解&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @MockBean
    private UserRepository userRepository;
    
    @Test
    @DisplayName(&quot;测试根据ID查找用户&quot;)
    public void testFindById() {
        // 准备测试数据
        User mockUser = new User();
        mockUser.setId(1L);
        mockUser.setUsername(&quot;testuser&quot;);
        
        // 模拟行为
        when(userRepository.findById(1L)).thenReturn(mockUser);
        
        // 执行测试
        User result = userService.findById(1L);
        
        // 验证结果
        assertThat(result).isNotNull();
        assertThat(result.getUsername()).isEqualTo(&quot;testuser&quot;);
    }
    
    @Test
    @Timeout(value = 2, unit = TimeUnit.SECONDS)
    public void testPerformance() {
        // 性能测试
        userService.performHeavyOperation();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 实战示例&lt;/h2&gt;
&lt;p&gt;1️⃣ 缓存注解&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 定义缓存注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
	// 缓存的键
    String key();
    // 过期时间（秒），默认5分钟
    int expireTime() default 300; 
}

// 缓存切面处理器
@Component
@Aspect
public class CacheAspect {
    
    private Map&amp;lt;String, CacheItem&amp;gt; cache = new ConcurrentHashMap&amp;lt;&amp;gt;();
    
    // 拦截所有带@Cacheable注解的方法
    @Around(&quot;@annotation(cacheable)&quot;)
    public Object handleCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
	    // 1. 获取缓存键
        String key = cacheable.key();
        
        // 2. 检查缓存是否存在且未过期
        CacheItem item = cache.get(key);
        if (item != null &amp;amp;&amp;amp; !item.isExpired()) {
            return item.getValue();
        }
        
        // 3. 缓存不存在或已过期，执行原方法
        Object result = joinPoint.proceed();
        
        // 4. 将结果存入缓存
        cache.put(key, new CacheItem(result, cacheable.expireTime()));
        
        return result;
    }
    
    private static class CacheItem {
	    // 缓存的值
        private Object value;
        // 过期时间戳
        private long expireTime;
        
        public CacheItem(Object value, int expireSeconds) {
            this.value = value;
            this.expireTime = System.currentTimeMillis() + expireSeconds * 1000L;
        }
        
        // 检查是否过期
        public boolean isExpired() {
            return System.currentTimeMillis() &amp;gt; expireTime;
        }
        
        public Object getValue() {
            return value;
        }
    }
}

// 使用缓存注解
@Service
public class UserService {
    // 缓存10分钟
    @Cacheable(key = &quot;user_#{id}&quot;, expireTime = 600)
    public User findById(Long id) {
        // 模拟数据库查询
        return userRepository.findById(id);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣ 日志注解&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 定义日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
    String value() default &quot;&quot;;
    boolean logParams() default false;
    boolean logResult() default false;
}

// 日志切面处理器
@Component
@Aspect
public class LogAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    
    @Around(&quot;@annotation(logExecution)&quot;)
    public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecution logExecution) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        
        // 记录方法开始执行
        if (logExecution.logParams()) {
	        // 获取方法参数
            Object[] args = joinPoint.getArgs();
            logger.info(&quot;方法 {} 开始执行，参数: {}&quot;, methodName, Arrays.toString(args));
        } else {
            logger.info(&quot;方法 {} 开始执行&quot;, methodName);
        }
        
        try {
            Object result = joinPoint.proceed();
            
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            if (logExecution.logResult()) {
                logger.info(&quot;方法 {} 执行完成，耗时: {}ms，结果: {}&quot;, methodName, executionTime, result);
            } else {
                logger.info(&quot;方法 {} 执行完成，耗时: {}ms&quot;, methodName, executionTime);
            }
            
            return result;
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            logger.error(&quot;方法 {} 执行异常，耗时: {}ms&quot;, methodName, executionTime, e);
            throw e;
        }
    }
}

// 使用日志注解
@Service
public class OrderService {
    
    @LogExecution(value = &quot;创建订单&quot;, logParams = true, logResult = true)
    public Order createOrder(OrderDto orderDto) {
        // 业务逻辑
        Order order = new Order();
        order.setOrderNumber(generateOrderNumber());
        order.setAmount(orderDto.getAmount());
        return orderRepository.save(order);
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Java反射机制与动态代理</title><link>https://17356085.github.io/posts/2025/java%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6%E4%B8%8E%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/java%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6%E4%B8%8E%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86/</guid><description>对Spring框架底层实现不理解，回来恶补基础（😭</description><pubDate>Mon, 18 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;如何获取class字节码文件的对象&lt;/li&gt;
&lt;li&gt;利用反射如何获取构造方法（创建对象）&lt;/li&gt;
&lt;li&gt;利用反射如何获取成员变量（赋值，获取值）&lt;/li&gt;
&lt;li&gt;利用反射如何获取成员方法（运行）&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. 概述&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动态获取&lt;/strong&gt;：在&lt;strong&gt;运行状态中&lt;/strong&gt;，能知道&lt;strong&gt;任意一个类&lt;/strong&gt;的&lt;em&gt;&lt;strong&gt;属性和方法&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态调用&lt;/strong&gt;：对于任意一个对象，都可以&lt;strong&gt;调用&lt;/strong&gt;它的&lt;em&gt;&lt;strong&gt;属性和方法&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 获取字节码文件对象&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Java文件：自己编写的Java代码
字节码文件：编译之后的class文件
字节码文件对象：class文件加载到内存后，由虚拟机自动创建的对象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.1 方式一：类名.class(类字面量)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编译时确定，类型安全&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不会触发类的初始化（静态代码块不执行）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;性能最高，推荐使用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持基本数据类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class ClassLiteralExample {
    public static void main(String[] args) {
        // 获取基本类型的Class对象
        Class&amp;lt;Integer&amp;gt; intClass = int.class;
        Class&amp;lt;Double&amp;gt; doubleClass = double.class;
        
        // 获取引用类型的Class对象
        Class&amp;lt;String&amp;gt; stringClass = String.class;
        Class&amp;lt;List&amp;gt; listClass = List.class;
        
        System.out.println(&quot;int.class: &quot; + intClass.getName());
        System.out.println(&quot;String.class: &quot; + stringClass.getName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 方式二：对象.getClass()&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行时确定，需要先有对象实例&lt;/li&gt;
&lt;li&gt;会触发类的初始化&lt;/li&gt;
&lt;li&gt;返回对象的实际类型（多态时很重要）&lt;/li&gt;
&lt;li&gt;不能获取基本数据类型的Class对象&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class GetClassExample {
    static {
        System.out.println(&quot;User类被初始化&quot;);
    }
    
    public static void main(String[] args) {
        // 必须先创建对象
        String str = &quot;Hello World&quot;;
        User user = new User();
        
        Class&amp;lt;?&amp;gt; stringClass = str.getClass();
        Class&amp;lt;?&amp;gt; userClass = user.getClass();
        
        // 注意：对于继承关系，返回的是实际对象的类型
        Animal animal = new Dog();
        Class&amp;lt;?&amp;gt; animalClass = animal.getClass(); // 返回Dog.class，不是Animal.class
        
        System.out.println(&quot;实际类型: &quot; + animalClass.getName()); // 输出: Dog
    }
}

class Animal {}
class Dog extends Animal {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 方式三：Class.forName(&quot;全限定类名&quot;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;动态加载，可以根据字符串&lt;strong&gt;动态获取&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;默认会触发类的初始化&lt;/li&gt;
&lt;li&gt;需要处理ClassNotFoundException异常&lt;/li&gt;
&lt;li&gt;支持完整的类加载控制&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class ForNameExample {
    public static void main(String[] args) {
        try {
            // 基本使用
            Class&amp;lt;?&amp;gt; stringClass = Class.forName(&quot;java.lang.String&quot;);
            
            // 数组类型
            Class&amp;lt;?&amp;gt; arrayClass = Class.forName(&quot;[Ljava.lang.String;&quot;); // String[]
            Class&amp;lt;?&amp;gt; intArrayClass = Class.forName(&quot;[I&quot;); // int[]
            
            // 内部类
            Class&amp;lt;?&amp;gt; innerClass = Class.forName(&quot;com.example.Outer$Inner&quot;);
            
            // 控制是否初始化类
            Class&amp;lt;?&amp;gt; clazz1 = Class.forName(&quot;com.example.User&quot;, true, 
                                          Thread.currentThread().getContextClassLoader());
            Class&amp;lt;?&amp;gt; clazz2 = Class.forName(&quot;com.example.User&quot;, false, 
                                          Thread.currentThread().getContextClassLoader());
            
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 选择原则&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;编译时已知类型&lt;/strong&gt; → 使用 &lt;code&gt;类名.class&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要获取对象实际类型&lt;/strong&gt; → 使用 &lt;code&gt;对象.getClass()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态加载未知类&lt;/strong&gt; → 使用 &lt;code&gt;Class.forName()&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;3. 获取方法和成员变量&lt;/h2&gt;
&lt;p&gt;*&lt;strong&gt;访问权限对比&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;访问范围&lt;/th&gt;
&lt;th&gt;包含继承&lt;/th&gt;
&lt;th&gt;访问权限&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;getMethod()&lt;/td&gt;
&lt;td&gt;public方法&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;仅public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;getDeclaredMethod()&lt;/td&gt;
&lt;td&gt;当前类所有方法&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;所有权限&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;getMethods()&lt;/td&gt;
&lt;td&gt;所有public方法&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;仅public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;getDeclaredMethods()&lt;/td&gt;
&lt;td&gt;当前类所有方法&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;所有权限&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;*&lt;strong&gt;选择原则&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;获取public成员且需要包含继承&lt;/strong&gt; → 使用 &lt;code&gt;getMethod()&lt;/code&gt;、&lt;code&gt;getField()&lt;/code&gt;、&lt;code&gt;getMethods()&lt;/code&gt;、&lt;code&gt;getFields()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;获取当前类所有成员&lt;/strong&gt; → 使用 &lt;code&gt;getDeclaredXxx()&lt;/code&gt; 系列方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要访问私有成员&lt;/strong&gt; → 必须使用 &lt;code&gt;getDeclaredXxx()&lt;/code&gt; + &lt;code&gt;setAccessible(true)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能敏感场景&lt;/strong&gt; → 使用缓存机制&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.1 获取单个方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class MethodReflectionExample {
    public static void main(String[] args) throws Exception {
        Class&amp;lt;String&amp;gt; stringClass = String.class;
        
        // 方式1：getMethod() - 获取public方法（包括继承的）
        Method method1 = stringClass.getMethod(&quot;substring&quot;, int.class);
        Method method2 = stringClass.getMethod(&quot;length&quot;); // 无参方法
        
        // 方式2：getDeclaredMethod() - 获取当前类声明的方法（所有访问权限）
        Method method3 = stringClass.getDeclaredMethod(&quot;substring&quot;, int.class);
        
        System.out.println(&quot;getMethod获取: &quot; + method1.getName());
        System.out.println(&quot;getDeclaredMethod获取: &quot; + method3.getName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 获取所有方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class AllMethodsExample {
    public static void main(String[] args) {
        Class&amp;lt;String&amp;gt; stringClass = String.class;
        
        // 方式1：getMethods() - 获取所有public方法（包括继承的）
        Method[] publicMethods = stringClass.getMethods();
        System.out.println(&quot;public方法数量（包括继承）: &quot; + publicMethods.length);
        
        // 方式2：getDeclaredMethods() - 获取当前类声明的所有方法
        Method[] declaredMethods = stringClass.getDeclaredMethods();
        System.out.println(&quot;当前类声明的方法数量: &quot; + declaredMethods.length);
        
        // 打印部分方法信息
        System.out.println(&quot;\n部分public方法:&quot;);
        for (int i = 0; i &amp;lt; Math.min(5, publicMethods.length); i++) {
            Method method = publicMethods[i];
            System.out.println(method.getName() + &quot; - 参数个数: &quot; + method.getParameterCount());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 获取构造方法&lt;/h3&gt;
&lt;p&gt;*&lt;strong&gt;获取构造方法并创建对象&lt;/strong&gt;：&lt;code&gt;newInstance()&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ConstructorReflectionExample {
    public static void main(String[] args) throws Exception {
        Class&amp;lt;String&amp;gt; stringClass = String.class;
        
        // 方式1：getConstructor() - 获取指定参数的public构造器
        Constructor&amp;lt;String&amp;gt; constructor1 = stringClass.getConstructor(String.class);
        Constructor&amp;lt;String&amp;gt; constructor2 = stringClass.getConstructor(); // 无参构造器
        
        // 方式2：getDeclaredConstructor() - 获取指定参数的构造器（所有访问权限）
        Constructor&amp;lt;String&amp;gt; constructor3 = stringClass.getDeclaredConstructor(char[].class);
        
        // 方式3：getConstructors() - 获取所有public构造器
        Constructor&amp;lt;?&amp;gt;[] publicConstructors = stringClass.getConstructors();
        System.out.println(&quot;public构造器数量: &quot; + publicConstructors.length);
        
        // 方式4：getDeclaredConstructors() - 获取所有构造器
        Constructor&amp;lt;?&amp;gt;[] allConstructors = stringClass.getDeclaredConstructors();
        System.out.println(&quot;所有构造器数量: &quot; + allConstructors.length);
        
        // 使用构造器创建对象
        String str1 = constructor1.newInstance(&quot;Hello&quot;);
        String str2 = constructor3.newInstance(&quot;World&quot;.toCharArray());
        
        System.out.println(&quot;创建的字符串1: &quot; + str1);
        System.out.println(&quot;创建的字符串2: &quot; + str2);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 获取成员变量&lt;/h3&gt;
&lt;p&gt;临时修饰访问权限：&lt;code&gt;setAccessible(true)&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;void set(Object obj, Object value）&lt;/td&gt;
&lt;td&gt;赋值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object get(Object obj)&lt;/td&gt;
&lt;td&gt;获取值&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;public class FieldReflectionExample {
    private String privateField = &quot;私有字段&quot;;
    public String publicField = &quot;公共字段&quot;;
    protected String protectedField = &quot;保护字段&quot;;
    String packageField = &quot;包私有字段&quot;;
    
    public static void main(String[] args) throws Exception {
        Class&amp;lt;FieldReflectionExample&amp;gt; clazz = FieldReflectionExample.class;
        FieldReflectionExample instance = new FieldReflectionExample();
        
        // 方式1：getField() - 获取public字段（包括继承的）
        Field publicField = clazz.getField(&quot;publicField&quot;);
        System.out.println(&quot;public字段值: &quot; + publicField.get(instance));
        
        // 方式2：getDeclaredField() - 获取当前类声明的字段（所有访问权限）
        Field privateField = clazz.getDeclaredField(&quot;privateField&quot;);
        privateField.setAccessible(true); // 设置可访问
        System.out.println(&quot;private字段值: &quot; + privateField.get(instance));
        
        // 方式3：getFields() - 获取所有public字段（包括继承的）
        Field[] publicFields = clazz.getFields();
        System.out.println(&quot;public字段数量: &quot; + publicFields.length);
        
        // 方式4：getDeclaredFields() - 获取当前类声明的所有字段
        Field[] allFields = clazz.getDeclaredFields();
        System.out.println(&quot;所有字段数量: &quot; + allFields.length);
        
        // 遍历所有字段
        System.out.println(&quot;\n所有字段信息:&quot;);
        for (Field field : allFields) {
            field.setAccessible(true);
            System.out.println(field.getName() + &quot; = &quot; + field.get(instance) + 
                             &quot; (类型: &quot; + field.getType().getSimpleName() + &quot;)&quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 实际应用示例&lt;/h2&gt;
&lt;h3&gt;4.1 通用对象复制工具&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ObjectCopyUtil {
    public static &amp;lt;T&amp;gt; T copyObject(T source, Class&amp;lt;T&amp;gt; targetClass) throws Exception {
        // 创建目标对象
        T target = targetClass.getDeclaredConstructor().newInstance();
        
        // 获取所有字段
        Field[] fields = source.getClass().getDeclaredFields();
        
        for (Field field : fields) {
            field.setAccessible(true);
            
            // 获取源对象字段值
            Object value = field.get(source);
            
            try {
                // 在目标类中查找同名字段
                Field targetField = targetClass.getDeclaredField(field.getName());
                targetField.setAccessible(true);
                
                // 类型匹配则复制
                if (targetField.getType().equals(field.getType())) {
                    targetField.set(target, value);
                }
            } catch (NoSuchFieldException e) {
                // 目标类没有该字段，跳过
                System.out.println(&quot;跳过字段: &quot; + field.getName());
            }
        }
        
        return target;
    }
    
    // 测试用例
    public static void main(String[] args) throws Exception {
        User user = new User(&quot;张三&quot;, 25);
        UserDto userDto = copyObject(user, UserDto.class);
        
        System.out.println(&quot;原对象: &quot; + user);
        System.out.println(&quot;复制对象: &quot; + userDto);
    }
}

class User {
    private String name;
    private int age;
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return &quot;User{name=&apos;&quot; + name + &quot;&apos;, age=&quot; + age + &quot;}&quot;;
    }
}

class UserDto {
    private String name;
    private int age;
    
    @Override
    public String toString() {
        return &quot;UserDto{name=&apos;&quot; + name + &quot;&apos;, age=&quot; + age + &quot;}&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 方法调用拦截器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class MethodInterceptor {
    public static void interceptAllMethods(Object target) {
        Class&amp;lt;?&amp;gt; clazz = target.getClass();
        
        System.out.println(&quot;=== 拦截对象 &quot; + clazz.getSimpleName() + &quot; 的所有方法 ===&quot;);
        
        // 获取所有public方法
        Method[] methods = clazz.getMethods();
        
        for (Method method : methods) {
            // 过滤掉Object类的方法
            if (method.getDeclaringClass() == Object.class) {
                continue;
            }
            
            System.out.println(&quot;方法: &quot; + method.getName());
            System.out.println(&quot;  参数类型: &quot; + Arrays.toString(method.getParameterTypes()));
            System.out.println(&quot;  返回类型: &quot; + method.getReturnType().getSimpleName());
            System.out.println(&quot;  修饰符: &quot; + Modifier.toString(method.getModifiers()));
            
            // 检查注解
            if (method.isAnnotationPresent(Deprecated.class)) {
                System.out.println(&quot;  ⚠️ 此方法已过时&quot;);
            }
            
            System.out.println();
        }
    }
    
    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        interceptAllMethods(list);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 构造器选择器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ConstructorSelector {
    public static &amp;lt;T&amp;gt; T createInstance(Class&amp;lt;T&amp;gt; clazz, Object... args) throws Exception {
        Constructor&amp;lt;?&amp;gt;[] constructors = clazz.getDeclaredConstructors();
        
        // 根据参数数量和类型选择合适的构造器
        for (Constructor&amp;lt;?&amp;gt; constructor : constructors) {
            Class&amp;lt;?&amp;gt;[] paramTypes = constructor.getParameterTypes();
            
            if (paramTypes.length == args.length) {
                boolean match = true;
                for (int i = 0; i &amp;lt; paramTypes.length; i++) {
                    if (args[i] != null &amp;amp;&amp;amp; !paramTypes[i].isAssignableFrom(args[i].getClass())) {
                        // 检查基本类型包装类
                        if (!isCompatibleType(paramTypes[i], args[i].getClass())) {
                            match = false;
                            break;
                        }
                    }
                }
                
                if (match) {
                    constructor.setAccessible(true);
                    return (T) constructor.newInstance(args);
                }
            }
        }
        
        throw new IllegalArgumentException(&quot;没有找到匹配的构造器&quot;);
    }
    
    private static boolean isCompatibleType(Class&amp;lt;?&amp;gt; paramType, Class&amp;lt;?&amp;gt; argType) {
        // 处理基本类型和包装类型的兼容性
        if (paramType == int.class &amp;amp;&amp;amp; argType == Integer.class) return true;
        if (paramType == long.class &amp;amp;&amp;amp; argType == Long.class) return true;
        if (paramType == double.class &amp;amp;&amp;amp; argType == Double.class) return true;
        // ... 其他基本类型
        return false;
    }
    
    public static void main(String[] args) throws Exception {
        // 测试创建不同类型的对象
        String str = createInstance(String.class, &quot;Hello World&quot;);
        Integer num = createInstance(Integer.class, 42);
        
        System.out.println(&quot;创建的字符串: &quot; + str);
        System.out.println(&quot;创建的整数: &quot; + num);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 面试题练习（重点）&lt;/h2&gt;
&lt;h3&gt;5.1 反射和配置文件结合动态获取&lt;/h3&gt;
&lt;p&gt;求: 利用反射根据文件中的不同类名和方法名，创建不同的对象并调用方法。&lt;/p&gt;
&lt;p&gt;分析:&lt;/p&gt;
&lt;p&gt;①通过Properties加载配置文件&lt;/p&gt;
&lt;p&gt;②得到类名和方法名&lt;/p&gt;
&lt;p&gt;③通过类名反射得到Class对象&lt;/p&gt;
&lt;p&gt;④通过Class对象创建一个对象&lt;/p&gt;
&lt;p&gt;⑤通过Class对象得到方法&lt;/p&gt;
&lt;p&gt;⑥调用方法&lt;/p&gt;
&lt;p&gt;代码示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ReflectDemo9 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, 
            NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        
        // 第1步：读取配置文件信息
        Properties prop = new Properties();
        FileInputStream fis = new FileInputStream(&quot;day14-code\\prop.properties&quot;);
        prop.load(fis);  // 加载配置文件内容到Properties对象
        fis.close();
        
        System.out.println(prop);  // 打印配置信息
        
        // 从配置中获取类名和方法名
        String classname = prop.get(&quot;classname&quot;) + &quot;&quot;;    // 转换为String
        String methodname = prop.get(&quot;methodname&quot;) + &quot;&quot;;  // 转换为String
        
        // 第2步：获取字节码文件对象
        Class clazz = Class.forName(classname);  // 动态加载类
        
        // 第3步：创建类的实例
        Constructor con = clazz.getDeclaredConstructor();  // 获取无参构造器
        con.setAccessible(true);  // 设置可访问（处理private构造器）
        Object o = con.newInstance();  // 创建实例
        System.out.println(o);
        
        // 第4步：获取方法对象
        Method method = clazz.getDeclaredMethod(methodname);  // 获取指定方法
        method.setAccessible(true);  // 设置可访问（处理private方法）
        
        // 第5步：调用方法
        method.invoke(o);  // 在对象o上调用方法
    }
}

配置文件中的信息：
classname=com.itheima.a02reflectdemo1.Student
methodname=sleep
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 利用发射保存对象中的信息&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class MyReflectDemo {
    public static void main(String[] args) throws IllegalAccessException, IOException {
    /*
        对于任意一个对象，都可以把对象所有的字段名和值，保存到文件中去
    */
       Student s = new Student(&quot;小A&quot;,23,&apos;女&apos;,167.5,&quot;睡觉&quot;);
       Teacher t = new Teacher(&quot;播妞&quot;,10000);
       saveObject(s);
    }

    //把对象里面所有的成员变量名和值保存到本地文件中
    public static void saveObject(Object obj) throws IllegalAccessException, IOException {
        //1.获取字节码文件的对象
        Class clazz = obj.getClass();
        //2. 创建IO流
        BufferedWriter bw = new BufferedWriter(new FileWriter(&quot;myreflect\\a.txt&quot;));
        //3. 获取所有的成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            //获取成员变量的名字
            String name = field.getName();
            //获取成员变量的值
            Object value = field.get(obj);
            //写出数据
            bw.write(name + &quot;=&quot; + value);
            bw.newLine();
        }

        bw.close();

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 动态代理&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;什么是动态代理？
动态代理是在运行时动态生成代理类的技术，无需事先编写代理类代码。代理对象可以在&lt;strong&gt;不修改目标对象&lt;/strong&gt;的情况下，&lt;strong&gt;对目标对象的功能进行增强&lt;/strong&gt;。
它的常见用途包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**权限控制&lt;/li&gt;
&lt;li&gt;日志记录&lt;/li&gt;
&lt;li&gt;事务处理&lt;/li&gt;
&lt;li&gt;方法调用前后增强&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;6.1 理解代理模式&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/java-reflection/dynamic-proxy.png&quot; alt=&quot;Java 动态代理示意图&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 抽象主题接口
interface Subject {
    void request();
}

// 真实主题类
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println(&quot;RealSubject: 处理请求&quot;);
    }
}

// 静态代理类
class StaticProxy implements Subject {
    private RealSubject realSubject;
    
    public StaticProxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }
    
    @Override
    public void request() {
        System.out.println(&quot;代理前置处理&quot;);
        realSubject.request();
        System.out.println(&quot;代理后置处理&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;静态代理和动态代理&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;静态代理&lt;/th&gt;
&lt;th&gt;动态代理&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;创建时机&lt;/td&gt;
&lt;td&gt;编译时&lt;/td&gt;
&lt;td&gt;运行时&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;代理类数量&lt;/td&gt;
&lt;td&gt;一对一&lt;/td&gt;
&lt;td&gt;一个代理类可代理多个目标类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;维护成本&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;性能&lt;/td&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;td&gt;略差（有反射开销）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;灵活性&lt;/td&gt;
&lt;td&gt;差&lt;/td&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;接下来我们需要了解的JDK动态代理和CGLIB动态代理&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;JDK动态代理&lt;/th&gt;
&lt;th&gt;CGLIB动态代理&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;代理对象&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;只能代理接口&lt;/td&gt;
&lt;td&gt;可以代理类和接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;实现方式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;基于反射机制&lt;/td&gt;
&lt;td&gt;基于ASM字节码生成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;性能&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;反射调用较慢&lt;/td&gt;
&lt;td&gt;字节码执行较快&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;依赖&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JDK内置&lt;/td&gt;
&lt;td&gt;需要额外jar包&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;生成代理&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;运行时生成&lt;/td&gt;
&lt;td&gt;运行时生成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;final方法&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;无法代理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;构造器&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;无要求&lt;/td&gt;
&lt;td&gt;需要无参构造器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;包大小&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;无额外依赖&lt;/td&gt;
&lt;td&gt;增加包体积&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;6.2 JDK动态代理&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;JDK动态代理基于接口实现，通过&lt;code&gt;java.lang.reflect.Proxy&lt;/code&gt;类和&lt;code&gt;InvocationHandler&lt;/code&gt;接口实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;6.2.1 &lt;code&gt;java.lang.reflect.Proxy&lt;/code&gt;类&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1️⃣作用&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动态生成代理类&lt;/strong&gt;：在运行时为指定接口创建代理对象，而不需要手动编写代理类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方法拦截&lt;/strong&gt;：代理对象的方法调用会被转发到一个 &lt;strong&gt;&lt;code&gt;InvocationHandler&lt;/code&gt;&lt;/strong&gt;，由它决定如何处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用场景&lt;/strong&gt;：AOP（面向切面编程）、事务处理、权限控制、日志记录、RPC 框架（如 Dubbo）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2️⃣基本结构&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Proxy implements java.io.Serializable {
    // 生成代理类实例
    public static Object newProxyInstance(
        ClassLoader loader,        // 类加载器
        Class&amp;lt;?&amp;gt;[] interfaces,     // 代理类需要实现的接口
        InvocationHandler h        // 方法调用处理器
    ) throws IllegalArgumentException;

    // 判断某个类是否是代理类
    public static boolean isProxyClass(Class&amp;lt;?&amp;gt; cl);

    // 获取某个代理类的 InvocationHandler
    public static InvocationHandler getInvocationHandler(Object proxy);

    // 获取代理类的 Class 对象
    public static Class&amp;lt;?&amp;gt; getProxyClass(ClassLoader loader, Class&amp;lt;?&amp;gt;... interfaces);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3️⃣核心组件&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;InvocationHandler&lt;/code&gt;接口&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;proxy&lt;/code&gt;：生成的代理对象本身（通常用得少，避免直接调用它的方法导致死循环）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;method&lt;/code&gt;：当前调用的方法对象（&lt;code&gt;java.lang.reflect.Method&lt;/code&gt;），可以获取方法名、参数类型等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;args&lt;/code&gt;：调用方法时传进来的实参数组，如果没有参数则为 &lt;code&gt;null&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;返回值：&lt;strong&gt;必须返回一个对象&lt;/strong&gt;，这个返回值会作为代理方法调用的返回值。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Proxy.newProxyInstance&lt;/code&gt;方法&lt;/strong&gt;：这是创建代理对象的入口。
参数说明：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ClassLoader loader&lt;/code&gt;：指定代理对象的类加载器，一般传 &lt;code&gt;目标类.getClass().getClassLoader()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Class&amp;lt;?&amp;gt;[] interfaces&lt;/code&gt;：代理类需要实现的接口数组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;InvocationHandler h&lt;/code&gt;：方法调用的处理器，用来定义增强逻辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;6.2.2 实现&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;困惑点：&lt;code&gt;Object result = method.invoke(target, args);&lt;/code&gt;这里是如何调用对象的方法？&lt;/em&gt;
&lt;strong&gt;Java 反射调用&lt;/strong&gt; 的核心一行。它的作用等价于&lt;strong&gt;在运行时&lt;/strong&gt;对 &lt;code&gt;target&lt;/code&gt; 这个对象“执行一个名叫 &lt;code&gt;method&lt;/code&gt; 的方法，参数是 &lt;code&gt;args&lt;/code&gt;”，就像手写的：&lt;code&gt;target.foo(&quot;abc&quot;, 123);&lt;/code&gt;
ps：假设 method 代表的是接口里的 foo(String x, int y)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;调用链路（重点理解）&lt;/strong&gt;：调用方 → 代理对象（&lt;code&gt;$Proxy0&lt;/code&gt;…）→ &lt;strong&gt;&lt;code&gt;InvocationHandler.invoke&lt;/code&gt;&lt;/strong&gt; →（你自定义的前置/后置/环绕逻辑）→ 反射调用&lt;strong&gt;目标对象&lt;/strong&gt;方法（或你自行处理）→ 返回值/异常回传。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.reflect.*;

// 1. 定义业务接口
interface UserService {
    void addUser(String name);
    String getUser(int id);
    void deleteUser(int id);
}

// 2. 实现类
class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println(&quot;添加用户: &quot; + name);
    }
    
    @Override
    public String getUser(int id) {
        System.out.println(&quot;获取用户: &quot; + id);
        return &quot;User-&quot; + id;
    }
    
    @Override
    public void deleteUser(int id) {
        System.out.println(&quot;删除用户: &quot; + id);
    }
}

// 3. 调用处理器
class LoggingInvocationHandler implements InvocationHandler {
    private Object target; // 被代理的目标对象
    
    public LoggingInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强
        System.out.println(&quot;=== 方法调用开始 ===&quot;);
        System.out.println(&quot;调用方法: &quot; + method.getName());
        System.out.println(&quot;方法参数: &quot; + java.util.Arrays.toString(args));
        long startTime = System.currentTimeMillis();
        
        try {
            // 调用目标方法
            Object result = method.invoke(target, args);
            
            // 后置增强
            long endTime = System.currentTimeMillis();
            System.out.println(&quot;方法执行成功&quot;);
            System.out.println(&quot;执行耗时: &quot; + (endTime - startTime) + &quot;ms&quot;);
            System.out.println(&quot;返回结果: &quot; + result);
            System.out.println(&quot;=== 方法调用结束 ===\n&quot;);
            
            return result;
        } catch (Exception e) {
            System.out.println(&quot;方法执行异常: &quot; + e.getMessage());
            throw e;
        }
    }
}

// 4. 测试动态代理
public class JDKProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService target = new UserServiceImpl();
        
        // 创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),    // 类加载器
            target.getClass().getInterfaces(),     // 接口数组
            new LoggingInvocationHandler(target)   // 调用处理器
        );
        
        // 使用代理对象
        proxy.addUser(&quot;张三&quot;);
        String user = proxy.getUser(1);
        proxy.deleteUser(1);
        
        // 验证代理对象的类型
        System.out.println(&quot;代理对象类型: &quot; + proxy.getClass().getName());
        System.out.println(&quot;是否为Proxy的实例: &quot; + (proxy instanceof Proxy));
        System.out.println(&quot;实现的接口: &quot; + java.util.Arrays.toString(proxy.getClass().getInterfaces()));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;代理对象执行&lt;/strong&gt;：&lt;code&gt;proxy.addUser(&quot;张三&quot;)&lt;/code&gt; → 代理类方法调用 → 统一进入 &lt;code&gt;InvocationHandler.invoke()&lt;/code&gt; → 在 &lt;code&gt;invoke()&lt;/code&gt; 里通过反射执行目标对象方法 → 返回结果并可做增强逻辑。&lt;/p&gt;
&lt;h4&gt;6.2.3 JDK动态代理核心API&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;public class ProxyAPIDemo {
    public static void demonstrateProxyAPI() {
        UserService target = new UserServiceImpl();
        
        // 1. Proxy.newProxyInstance() - 创建代理实例
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            new Class&amp;lt;?&amp;gt;[]{UserService.class},
            new LoggingInvocationHandler(target)
        );
        
        // 2. Proxy.getProxyClass() - 获取代理类
        Class&amp;lt;?&amp;gt; proxyClass = Proxy.getProxyClass(
            UserService.class.getClassLoader(),
            UserService.class
        );
        System.out.println(&quot;代理类: &quot; + proxyClass.getName());
        
        // 3. Proxy.isProxyClass() - 检查是否为代理类
        boolean isProxy = Proxy.isProxyClass(proxy.getClass());
        System.out.println(&quot;是否为代理类: &quot; + isProxy);
        
        // 4. Proxy.getInvocationHandler() - 获取调用处理器
        InvocationHandler handler = Proxy.getInvocationHandler(proxy);
        System.out.println(&quot;调用处理器: &quot; + handler.getClass().getName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6.2.4 高级JDK代理应用&lt;/h4&gt;
&lt;p&gt;1️⃣通用代理工厂&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ProxyFactory {
    
    /**
     * 创建带日志功能的代理
     */
    public static &amp;lt;T&amp;gt; T createLoggingProxy(T target) {
        return createProxy(target, new LoggingInvocationHandler(target));
    }
    
    /**
     * 创建带性能监控的代理
     */
    public static &amp;lt;T&amp;gt; T createPerformanceProxy(T target) {
        return createProxy(target, new PerformanceInvocationHandler(target));
    }
    
    /**
     * 创建带事务管理的代理
     */
    public static &amp;lt;T&amp;gt; T createTransactionProxy(T target) {
        return createProxy(target, new TransactionInvocationHandler(target));
    }
    
    /**
     * 创建多重代理（支持多个增强功能）
     */
    public static &amp;lt;T&amp;gt; T createMultiProxy(T target, InvocationHandler... handlers) {
        Object proxy = target;
        
        // 逐层包装
        for (InvocationHandler handler : handlers) {
            proxy = Proxy.newProxyInstance(
                proxy.getClass().getClassLoader(),
                proxy.getClass().getInterfaces(),
                handler
            );
        }
        
        return (T) proxy;
    }
    
    /**
     * 通用代理创建方法
     */
    @SuppressWarnings(&quot;unchecked&quot;)
    private static &amp;lt;T&amp;gt; T createProxy(T target, InvocationHandler handler) {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            handler
        );
    }
}

// 性能监控处理器
class PerformanceInvocationHandler implements InvocationHandler {
    private Object target;
    
    public PerformanceInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.nanoTime();
        
        Object result = method.invoke(target, args);
        
        long end = System.nanoTime();
        double duration = (end - start) / 1_000_000.0; // 转换为毫秒
        
        System.out.printf(&quot;⏱️ 方法 %s 执行耗时: %.2f ms%n&quot;, method.getName(), duration);
        
        return result;
    }
}

// 事务管理处理器
class TransactionInvocationHandler implements InvocationHandler {
    private Object target;
    
    public TransactionInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 开启事务
        System.out.println(&quot;🔄 开始事务&quot;);
        
        try {
            Object result = method.invoke(target, args);
            
            // 提交事务
            System.out.println(&quot;✅ 提交事务&quot;);
            return result;
            
        } catch (Exception e) {
            // 回滚事务
            System.out.println(&quot;❌ 回滚事务&quot;);
            throw e;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣多层代理示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MultiProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService target = new UserServiceImpl();
        
        // 创建多重代理：日志 + 性能监控 + 事务管理
        UserService multiProxy = ProxyFactory.createMultiProxy(
            target,
            new LoggingInvocationHandler(target),
            new PerformanceInvocationHandler(target),
            new TransactionInvocationHandler(target)
        );
        
        System.out.println(&quot;=== 多重代理测试 ===&quot;);
        multiProxy.addUser(&quot;李四&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.3 CGLIB动态代理&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CGLIB&lt;/strong&gt;（Code Generation Library）是一种基于 &lt;strong&gt;继承&lt;/strong&gt; 的动态代理实现方式。  它通过 &lt;strong&gt;字节码生成技术&lt;/strong&gt;（ASM 字节码框架）在运行时动态生成一个&lt;strong&gt;目标类的子类&lt;/strong&gt;，并在子类中&lt;strong&gt;重写目标类的方法&lt;/strong&gt;，从而在方法调用前后插入增强逻辑。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;6.3.1 与 &lt;strong&gt;JDK 动态代理&lt;/strong&gt; 不同：&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JDK 动态代理&lt;/strong&gt;：必须有&lt;strong&gt;接口&lt;/strong&gt;，生成的代理类实现接口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CGLIB 动态代理&lt;/strong&gt;：不需要接口，可以直接代理普通类（但类不能是 &lt;code&gt;final&lt;/code&gt;、方法不能是 &lt;code&gt;final&lt;/code&gt;/&lt;code&gt;static&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;6.3.2 工作原理&lt;/h4&gt;
&lt;p&gt;CGLIB 通过 &lt;code&gt;net.sf.cglib.proxy.Enhancer&lt;/code&gt; 类来创建代理对象：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;指定 &lt;strong&gt;要代理的目标类&lt;/strong&gt;（不是接口）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CGLIB 会生成一个 &lt;strong&gt;目标类的子类&lt;/strong&gt;（字节码级别创建）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在子类方法中嵌入调用 &lt;strong&gt;MethodInterceptor 的 intercept 方法&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 &lt;strong&gt;intercept 方法&lt;/strong&gt; 中执行前置/后置逻辑，再通过 &lt;code&gt;methodProxy.invokeSuper()&lt;/code&gt; 调用父类（目标类）的真实方法。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;6.3.3 实现&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 注意：需要添加CGLIB依赖
// &amp;lt;dependency&amp;gt;
//     &amp;lt;groupId&amp;gt;cglib&amp;lt;/groupId&amp;gt;
//     &amp;lt;artifactId&amp;gt;cglib&amp;lt;/artifactId&amp;gt;
//     &amp;lt;version&amp;gt;3.3.0&amp;lt;/version&amp;gt;
// &amp;lt;/dependency&amp;gt;

import net.sf.cglib.proxy.*;
import java.lang.reflect.Method;

// 目标类（无需实现接口）
class UserManager {
    public void addUser(String name) {
        System.out.println(&quot;UserManager: 添加用户 &quot; + name);
    }
    
    public String getUser(int id) {
        System.out.println(&quot;UserManager: 获取用户 &quot; + id);
        return &quot;User-&quot; + id;
    }
    
    // final方法无法被代理
    public final void finalMethod() {
        System.out.println(&quot;这是final方法&quot;);
    }
    
    // private方法无法被代理
    private void privateMethod() {
        System.out.println(&quot;这是private方法&quot;);
    }
}

// CGLIB方法拦截器
class UserManagerInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(&quot;CGLIB前置增强: &quot; + method.getName());
        
        // 调用父类方法（原始方法）
        Object result = proxy.invokeSuper(obj, args);
        
        System.out.println(&quot;CGLIB后置增强: &quot; + method.getName());
        return result;
    }
}

// CGLIB代理演示
public class CGLibProxyDemo {
    public static void main(String[] args) {
        // 创建增强器
        Enhancer enhancer = new Enhancer();
        
        // 设置父类
        enhancer.setSuperclass(UserManager.class);
        
        // 设置回调
        enhancer.setCallback(new UserManagerInterceptor());
        
        // 创建代理对象
        UserManager proxy = (UserManager) enhancer.create();
        
        // 使用代理对象
        proxy.addUser(&quot;王五&quot;);
        String user = proxy.getUser(2);
        
        System.out.println(&quot;代理对象类型: &quot; + proxy.getClass().getName());
        System.out.println(&quot;父类: &quot; + proxy.getClass().getSuperclass().getName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6.3.4 高级特性（了解即可）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 方法过滤器
class SelectiveMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 只代理特定方法
        if (method.getName().startsWith(&quot;add&quot;)) {
            System.out.println(&quot;拦截add方法: &quot; + method.getName());
            Object result = proxy.invokeSuper(obj, args);
            System.out.println(&quot;add方法执行完成&quot;);
            return result;
        }
        
        // 其他方法直接执行
        return proxy.invokeSuper(obj, args);
    }
}

// 回调过滤器
class MyCallbackFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        // 根据方法返回不同的回调索引
        if (method.getName().startsWith(&quot;add&quot;)) {
            return 0; // 使用第一个回调
        } else if (method.getName().startsWith(&quot;get&quot;)) {
            return 1; // 使用第二个回调
        }
        return 2; // 使用第三个回调（NoOp表示不代理）
    }
}

public class AdvancedCGLibDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserManager.class);
        
        // 设置多个回调
        enhancer.setCallbacks(new Callback[]{
            new SelectiveMethodInterceptor(),           // 索引0：add方法的拦截器
            new PerformanceMethodInterceptor(),         // 索引1：get方法的拦截器
            NoOp.INSTANCE                               // 索引2：不做任何处理
        });
        
        // 设置回调过滤器
        enhancer.setCallbackFilter(new MyCallbackFilter());
        
        UserManager proxy = (UserManager) enhancer.create();
        
        proxy.addUser(&quot;赵六&quot;);    // 会被第一个拦截器处理
        proxy.getUser(3);        // 会被第二个拦截器处理
    }
}

// 性能监控拦截器
class PerformanceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        long start = System.currentTimeMillis();
        
        Object result = proxy.invokeSuper(obj, args);
        
        long end = System.currentTimeMillis();
        System.out.println(&quot;方法 &quot; + method.getName() + &quot; 执行时间: &quot; + (end - start) + &quot;ms&quot;);
        
        return result;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.4 动态代理在框架中的应用&lt;/h3&gt;
&lt;h4&gt;6.4.1 SpringBootAOP实现&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 切面注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface LogExecution {
    String value() default &quot;&quot;;
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Transactional {
    boolean readOnly() default false;
}

// 业务服务
class OrderService {
    @LogExecution(&quot;订单创建&quot;)
    @Transactional
    public void createOrder(String orderId) {
        System.out.println(&quot;创建订单: &quot; + orderId);
        // 模拟可能的异常
        if (orderId.equals(&quot;ERROR&quot;)) {
            throw new RuntimeException(&quot;订单创建失败&quot;);
        }
    }
    
    @LogExecution(&quot;订单查询&quot;)
    @Transactional(readOnly = true)
    public String getOrder(String orderId) {
        System.out.println(&quot;查询订单: &quot; + orderId);
        return &quot;Order-&quot; + orderId;
    }
}

// AOP代理处理器
class AOPInvocationHandler implements InvocationHandler {
    private Object target;
    
    public AOPInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 处理@LogExecution注解
        LogExecution logAnnotation = method.getAnnotation(LogExecution.class);
        if (logAnnotation != null) {
            System.out.println(&quot;📝 [LOG] 开始执行: &quot; + logAnnotation.value());
        }
        
        // 处理@Transactional注解
        Transactional txAnnotation = method.getAnnotation(Transactional.class);
        boolean inTransaction = false;
        
        if (txAnnotation != null) {
            System.out.println(&quot;🔄 [TX] 开启事务 (只读: &quot; + txAnnotation.readOnly() + &quot;)&quot;);
            inTransaction = true;
        }
        
        try {
            // 执行目标方法
            Object result = method.invoke(target, args);
            
            // 事务提交
            if (inTransaction) {
                System.out.println(&quot;✅ [TX] 事务提交&quot;);
            }
            
            // 日志记录
            if (logAnnotation != null) {
                System.out.println(&quot;📝 [LOG] 执行成功: &quot; + logAnnotation.value());
            }
            
            return result;
            
        } catch (Exception e) {
            // 事务回滚
            if (inTransaction) {
                System.out.println(&quot;❌ [TX] 事务回滚&quot;);
            }
            
            // 错误日志
            if (logAnnotation != null) {
                System.out.println(&quot;📝 [LOG] 执行失败: &quot; + logAnnotation.value() + &quot; - &quot; + e.getCause().getMessage());
            }
            
            throw e;
        }
    }
}

// 简化版IoC容器
class SimpleContainer {
    public static &amp;lt;T&amp;gt; T getBean(Class&amp;lt;T&amp;gt; clazz) {
        try {
            T target = clazz.getDeclaredConstructor().newInstance();
            
            // 检查是否需要AOP增强
            boolean needAOP = hasAOPAnnotations(clazz);
            
            if (needAOP) {
                // 返回代理对象
                return createAOPProxy(target);
            }
            
            return target;
        } catch (Exception e) {
            throw new RuntimeException(&quot;创建Bean失败&quot;, e);
        }
    }
    
    private static boolean hasAOPAnnotations(Class&amp;lt;?&amp;gt; clazz) {
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(LogExecution.class) || 
                method.isAnnotationPresent(Transactional.class)) {
                return true;
            }
        }
        return false;
    }
    
    @SuppressWarnings(&quot;unchecked&quot;)
    private static &amp;lt;T&amp;gt; T createAOPProxy(T target) {
        // 这里简化处理，实际Spring会根据情况选择JDK或CGLIB
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces().length &amp;gt; 0 ? 
                target.getClass().getInterfaces() : 
                new Class[]{target.getClass()},
            new AOPInvocationHandler(target)
        );
    }
}

// 测试简化版AOP
public class SimpleAOPDemo {
    public static void main(String[] args) {
        OrderService orderService = SimpleContainer.getBean(OrderService.class);
        
        System.out.println(&quot;=== 正常订单创建 ===&quot;);
        orderService.createOrder(&quot;ORDER001&quot;);
        
        System.out.println(&quot;\n=== 订单查询 ===&quot;);
        String order = orderService.getOrder(&quot;ORDER001&quot;);
        
        System.out.println(&quot;\n=== 异常订单创建 ===&quot;);
        try {
            orderService.createOrder(&quot;ERROR&quot;);
        } catch (Exception e) {
            System.out.println(&quot;捕获异常: &quot; + e.getCause().getMessage());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Spring AOP</title><link>https://17356085.github.io/posts/2025/spring-aop/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/spring-aop/</guid><description>敲项目的时候，偶遇切面代码，拼尽全力无法战胜，遂回来恶补基础</description><pubDate>Mon, 18 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AOP&lt;/strong&gt;：AOP（Aspect Oriented Programming）：面向切面编程，面向方面编程。（AOP是一种编程技术） AOP是对OOP的补充延伸。（&lt;em&gt;AOP底层是通过动态代理来实现的&lt;/em&gt;）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spring的AOP的动态代理&lt;/strong&gt;：JDK动态代理+CGLIB动态代理技术。Spring在这两种动态代理中灵活切换，如果是代理接口，会默认使用JDK动态代理，如果要代理某个类，这个类没有实现接口，就会切换使用CGLIB。当然，你也可以强制通过一些配置让Spring只使用CGLIB。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. 为什么要使用AOP？&lt;/h2&gt;
&lt;p&gt;一般一个系统当中都会有一些系统服务，例如：日志、事务管理、安全等。这些系统服务被称为：&lt;strong&gt;交叉业务&lt;/strong&gt; 这些&lt;strong&gt;交叉业务&lt;/strong&gt;几乎是通用的，不管你是做银行账户转账，还是删除用户数据。日志、事务管理、安全，这些都是需要做的。 如果在每一个业务处理过程当中，都掺杂这些交叉业务代码进去的话，存在两方面问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一：交叉业务代码在多个业务流程中反复出现，显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话，需要修改多处。&lt;/li&gt;
&lt;li&gt;第二：程序员无法专注核心业务代码的编写，在编写核心业务代码的同时还需要处理这些交叉业务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用AOP可以很轻松的解决以上问题。
&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/spring-aop/aop-01.png&quot; alt=&quot;Spring AOP 核心概念示意图&quot; /&gt;
1️⃣ &lt;strong&gt;使用AOP的好处&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;代码复用性增强&lt;/li&gt;
&lt;li&gt;代码易维护&lt;/li&gt;
&lt;li&gt;使开发者更关注业务逻辑&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2️⃣ 总结
&lt;strong&gt;将与核心业务无关的代码独立的抽取出来，形成一个独立的组件，然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;2. AOP相关概念&lt;/h2&gt;
&lt;h3&gt;2.1 术语&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-1d883de109ca4cc6b2cd88abca8c4330.r2.dev/blog/posts/spring-aop/aop-02.png&quot; alt=&quot;Spring AOP 切面执行流程&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连接点 Joinpoint&lt;/strong&gt;：在程序的整个执行流程中，&lt;strong&gt;可以织入&lt;/strong&gt;切面的位置。方法的执行前后，异常抛出之后等位置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;切点 Pointcut&lt;/strong&gt;：在程序执行流程中，&lt;strong&gt;真正织入&lt;/strong&gt;切面的方法。（一个切点对应多个连接点）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;通知 Advice&lt;/strong&gt;：通知又叫增强，就是具体你要织入的代码。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包括
&lt;ul&gt;
&lt;li&gt;前置通知&lt;/li&gt;
&lt;li&gt;后置通知&lt;/li&gt;
&lt;li&gt;环绕通知&lt;/li&gt;
&lt;li&gt;异常通知&lt;/li&gt;
&lt;li&gt;最终通知&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;切面 Aspect&lt;/strong&gt;：&lt;strong&gt;切点 + 通知就是切面。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;织入 Weaving：把通知应用到目标对象上的过程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代理对象 Proxy：一个目标对象被织入通知后产生的新对象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;目标对象 Target：被织入通知的对象。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 切点表达式&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;可以使用切点表达式定义通知（Advice）往哪些方法上切入。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;execution([访问控制权限修饰符] 返回值类型 [权限定类名] 方法名 (形式参数列表) [列表])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 在Spring中使用AOP&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Spring对AOP的实现包括以下3种方式：&lt;/p&gt;
&lt;p&gt;**第一种方式：Spring框架结合AspectJ框架实现的AOP，基于注解方式。&lt;/p&gt;
&lt;p&gt;第二种方式：Spring框架结合AspectJ框架实现的AOP，基于XML方式。&lt;/p&gt;
&lt;p&gt;第三种方式：Spring框架自己实现的AOP，基于XML配置方式。（不常用了解即可）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.1 配置&lt;/h3&gt;
&lt;p&gt;1️⃣使用Spring+Aspectj的AOP需要引入如下依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!--spring context依赖--&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-context&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;6.0.0-M2&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;!--spring aop依赖--&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-aop&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;6.0.0-M2&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;!--spring aspects依赖--&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-aspects&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;6.0.0-M2&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣ Spring配置文件中添加context和aop命名空间&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xmlns:aop=&quot;http://www.springframework.org/schema/aop&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd&quot;&amp;gt;

&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 AOP注解开发&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;使用Spring中的通知：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;前置通知：@Before 目标方法执行之前的通知&lt;/li&gt;
&lt;li&gt;后置通知：@AfterReturning 目标方法执行之后的通知&lt;/li&gt;
&lt;li&gt;环绕通知：@Around 目标方法之前添加通知，同时目标方法执行之后添加通知。&lt;/li&gt;
&lt;li&gt;异常通知：@AfterThrowing 发生异常之后执行的通知&lt;/li&gt;
&lt;li&gt;最终通知：@After 放在finally语句块中的通知&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4&gt;3.2.1 实现步骤&lt;/h4&gt;
&lt;p&gt;1️⃣ 定义目标类以及目标方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.service;

// 目标类
public class OrderService {
    // 目标方法
    public void generate(){
        System.out.println(&quot;订单已生成！&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2️⃣ 定义切面类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.service;

import org.aspectj.lang.annotation.Aspect;

// 切面类
@Aspect
public class MyAspect {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3️⃣ 目标类和切面类都纳入Spring bean管理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在目标类OrderService上添加@Component注解。&lt;/li&gt;
&lt;li&gt;在切面类MyAspect类上添加@Component注解。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;4️⃣ 在Spring配置文件分钟添加组件扫描&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xmlns:aop=&quot;http://www.springframework.org/schema/aop&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd&quot;&amp;gt;
    &amp;lt;!--开启组件扫描--&amp;gt;
    &amp;lt;context:component-scan base-package=&quot;com.powernode.spring6.service&quot;/&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5️⃣ 在切面类添加通知&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.service;

import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Aspect;

// 切面类
@Aspect
@Component
public class MyAspect {
    // 这就是需要增强的代码（通知）
    public void advice(){
        System.out.println(&quot;我是一个通知&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;6️⃣ 在通知上添加切点表达式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.service;

import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Aspect;

// 切面类
@Aspect
@Component
public class MyAspect {
    
    // 切点表达式
    @Before(&quot;execution(* com.powernode.spring6.service.OrderService.*(..))&quot;)
    // 这就是需要增强的代码（通知）
    public void advice(){
        System.out.println(&quot;我是一个通知&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;7️⃣ 在Spring配置文件中启用自动代理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;aop:aspectj-autoproxy proxy-target-class=&quot;true&quot;/&amp;gt; 开启自动代理之后，凡事带有@Aspect注解的bean都会生成代理对象。&lt;/li&gt;
&lt;li&gt;proxy-target-class=&quot;true&quot; 表示采用cglib动态代理。&lt;/li&gt;
&lt;li&gt;proxy-target-class=&quot;false&quot; 表示采用jdk动态代理。默认值是false。即使写成false，当没有接口的时候，也会自动选择cglib生成代理类。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xmlns:aop=&quot;http://www.springframework.org/schema/aop&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd&quot;&amp;gt;
    &amp;lt;!--开启组件扫描--&amp;gt;
    &amp;lt;context:component-scan base-package=&quot;com.powernode.spring6.service&quot;/&amp;gt;
    &amp;lt;!--开启自动代理--&amp;gt;
    &amp;lt;aop:aspectj-autoproxy proxy-target-class=&quot;true&quot;/&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.2.2 切面的先后顺序&lt;/h4&gt;
&lt;p&gt;业务流程当中不一定只有一个切面，可能有的切面控制事务，有的记录日志，有的进行安全控制，如果多个切面的话，顺序如何控制：&lt;strong&gt;可以使用@Order注解来标识切面类，为@Order注解的value指定一个整数型的数字，数字越小，优先级越高&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;3.2.3 提高切面的可复用性&lt;/h4&gt;
&lt;p&gt;1️⃣&lt;strong&gt;反面示例&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

// 切面类
@Component
@Aspect
@Order(2)
public class MyAspect {

    @Around(&quot;execution(* com.powernode.spring6.service.OrderService.*(..))&quot;)
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println(&quot;环绕通知开始&quot;);
        // 执行目标方法。
        proceedingJoinPoint.proceed();
        System.out.println(&quot;环绕通知结束&quot;);
    }

    @Before(&quot;execution(* com.powernode.spring6.service.OrderService.*(..))&quot;)
    public void beforeAdvice(){
        System.out.println(&quot;前置通知&quot;);
    }

    @AfterReturning(&quot;execution(* com.powernode.spring6.service.OrderService.*(..))&quot;)
    public void afterReturningAdvice(){
        System.out.println(&quot;后置通知&quot;);
    }

    @AfterThrowing(&quot;execution(* com.powernode.spring6.service.OrderService.*(..))&quot;)
    public void afterThrowingAdvice(){
        System.out.println(&quot;异常通知&quot;);
    }

    @After(&quot;execution(* com.powernode.spring6.service.OrderService.*(..))&quot;)
    public void afterAdvice(){
        System.out.println(&quot;最终通知&quot;);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：切点表达式重复，如果切点表达式需要维护，需要修改多处。&lt;/p&gt;
&lt;p&gt;2️⃣ &lt;strong&gt;应该这么做&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;将切点表达式用@Pointcut注解单独的定义出来，在需要的位置引入即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

// 切面类
@Component
@Aspect
@Order(2)
public class MyAspect {
    
    @Pointcut(&quot;execution(* com.powernode.spring6.service.OrderService.*(..))&quot;)
    public void pointcut(){}

    @Around(&quot;pointcut()&quot;)
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println(&quot;环绕通知开始&quot;);
        // 执行目标方法。
        proceedingJoinPoint.proceed();
        System.out.println(&quot;环绕通知结束&quot;);
    }

    @Before(&quot;pointcut()&quot;)
    public void beforeAdvice(){
        System.out.println(&quot;前置通知&quot;);
    }

    @AfterReturning(&quot;pointcut()&quot;)
    public void afterReturningAdvice(){
        System.out.println(&quot;后置通知&quot;);
    }

    @AfterThrowing(&quot;pointcut()&quot;)
    public void afterThrowingAdvice(){
        System.out.println(&quot;异常通知&quot;);
    }

    @After(&quot;pointcut()&quot;)
    public void afterAdvice(){
        System.out.println(&quot;最终通知&quot;);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.2.4 全注解开发AOP&lt;/h4&gt;
&lt;p&gt;Spring配置类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.powernode.spring6.service;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(&quot;com.powernode.spring6.service&quot;)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Spring6Configuration {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试程序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Test
public void testAOPWithAllAnnotation(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
    OrderService orderService = applicationContext.getBean(&quot;orderService&quot;, OrderService.class);
    orderService.generate();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 基于XML配置方式的AOP（了解即可）&lt;/h3&gt;
&lt;p&gt;1️⃣编写目标类（略）&lt;/p&gt;
&lt;p&gt;2️⃣ 编写切面类，并编写通知（略）&lt;/p&gt;
&lt;p&gt;3️⃣编写Spring配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xmlns:aop=&quot;http://www.springframework.org/schema/aop&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd&quot;&amp;gt;

    &amp;lt;!--纳入spring bean管理--&amp;gt;
    &amp;lt;bean id=&quot;vipService&quot; class=&quot;com.powernode.spring6.service.VipService&quot;/&amp;gt;
    &amp;lt;bean id=&quot;timerAspect&quot; class=&quot;com.powernode.spring6.service.TimerAspect&quot;/&amp;gt;

    &amp;lt;!--aop配置--&amp;gt;
    &amp;lt;aop:config&amp;gt;
        &amp;lt;!--切点表达式--&amp;gt;
        &amp;lt;aop:pointcut id=&quot;p&quot; expression=&quot;execution(* com.powernode.spring6.service.VipService.*(..))&quot;/&amp;gt;
        &amp;lt;!--切面--&amp;gt;
        &amp;lt;aop:aspect ref=&quot;timerAspect&quot;&amp;gt;
            &amp;lt;!--切面=通知 + 切点--&amp;gt;
            &amp;lt;aop:around method=&quot;time&quot; pointcut-ref=&quot;p&quot;/&amp;gt;
        &amp;lt;/aop:aspect&amp;gt;
    &amp;lt;/aop:config&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>CI/CD的基本概念</title><link>https://17356085.github.io/posts/2025/cicd%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/cicd%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/</guid><description>通过在GitHubActions自动编译，了解到了CI/CD的基本概念</description><pubDate>Sun, 17 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;CI/CD的全称和基本概念&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CI&lt;/strong&gt; = Continuous Integration（持续集成） &lt;strong&gt;CD&lt;/strong&gt; = Continuous Delivery/Continuous Deployment（持续交付/持续部署）&lt;/p&gt;
&lt;h2&gt;CI - 持续集成（Continuous Integration）&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;持续集成是指开发团队频繁地将代码变更合并到主分支，每次合并都会触发自动化的构建和测试过程。&lt;/p&gt;
&lt;h3&gt;核心流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;代码提交&lt;/strong&gt;：开发者将代码推送到版本控制系统（如Git）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动构建&lt;/strong&gt;：系统自动拉取最新代码并进行编译构建&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动测试&lt;/strong&gt;：运行单元测试、集成测试等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果反馈&lt;/strong&gt;：将构建和测试结果通知给开发团队&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;主要优势&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;尽早发现代码集成问题&lt;/li&gt;
&lt;li&gt;减少手动构建的工作量&lt;/li&gt;
&lt;li&gt;提高代码质量&lt;/li&gt;
&lt;li&gt;加快问题定位速度&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CD - 持续交付和持续部署&lt;/h2&gt;
&lt;h3&gt;持续交付（Continuous Delivery）&lt;/h3&gt;
&lt;p&gt;代码通过CI流程后，自动部署到测试环境或预生产环境，但需要手动触发生产环境部署。&lt;/p&gt;
&lt;h3&gt;持续部署（Continuous Deployment）&lt;/h3&gt;
&lt;p&gt;代码通过所有自动化测试后，自动部署到生产环境，无需人工干预。&lt;/p&gt;
&lt;h3&gt;CD流程包括&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;自动化部署脚本&lt;/li&gt;
&lt;li&gt;环境配置管理&lt;/li&gt;
&lt;li&gt;数据库迁移&lt;/li&gt;
&lt;li&gt;服务重启和健康检查&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CI/CD的完整流程&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;代码开发 → 代码提交 → 自动构建 → 自动测试 → 部署到测试环境 → 手动/自动部署到生产环境
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常用的CI/CD工具&lt;/h2&gt;
&lt;h3&gt;云端服务&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;：与GitHub深度集成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitLab CI/CD&lt;/strong&gt;：GitLab内置的CI/CD功能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Travis CI&lt;/strong&gt;：老牌的CI服务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CircleCI&lt;/strong&gt;：快速且功能强大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure DevOps&lt;/strong&gt;：微软的DevOps平台&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;自部署工具&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Jenkins&lt;/strong&gt;：开源且功能丰富的自动化服务器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TeamCity&lt;/strong&gt;：JetBrains开发的CI/CD工具&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bamboo&lt;/strong&gt;：Atlassian的CI/CD解决方案&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;现代化工具&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt;：容器化部署&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;：容器编排&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ansible/Terraform&lt;/strong&gt;：基础设施即代码&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CI/CD配置文件示例&lt;/h2&gt;
&lt;h3&gt;GitHub Actions示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;name: CI/CD Pipeline
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: &apos;16&apos;
    - name: Install dependencies
      run: npm install
    - name: Run tests
      run: npm test
    
  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == &apos;refs/heads/main&apos;
    steps:
    - name: Deploy to production
      run: echo &quot;Deploying to production server&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;CI/CD的最佳实践&lt;/h2&gt;
&lt;h3&gt;代码管理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用版本控制系统&lt;/li&gt;
&lt;li&gt;采用分支策略（如Git Flow）&lt;/li&gt;
&lt;li&gt;代码审查流程&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;自动化测试&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;单元测试覆盖率要求&lt;/li&gt;
&lt;li&gt;集成测试和端到端测试&lt;/li&gt;
&lt;li&gt;性能测试和安全测试&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;环境管理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;开发、测试、生产环境一致性&lt;/li&gt;
&lt;li&gt;配置文件分离&lt;/li&gt;
&lt;li&gt;数据库版本管理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;监控和回滚&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;部署后的健康检查&lt;/li&gt;
&lt;li&gt;应用性能监控&lt;/li&gt;
&lt;li&gt;快速回滚机制&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实施CI/CD的好处&lt;/h2&gt;
&lt;h3&gt;对开发团队&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;减少手动操作，降低出错率&lt;/li&gt;
&lt;li&gt;提高开发效率&lt;/li&gt;
&lt;li&gt;更快的反馈循环&lt;/li&gt;
&lt;li&gt;提升代码质量&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;对业务&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;更快的产品迭代速度&lt;/li&gt;
&lt;li&gt;降低发布风险&lt;/li&gt;
&lt;li&gt;提高系统稳定性&lt;/li&gt;
&lt;li&gt;更好的用户体验&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;挑战和注意事项&lt;/h2&gt;
&lt;h3&gt;技术挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;初期搭建需要投入时间&lt;/li&gt;
&lt;li&gt;需要学习新的工具和概念&lt;/li&gt;
&lt;li&gt;测试用例的编写和维护&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;团队协作&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;需要团队共识和配合&lt;/li&gt;
&lt;li&gt;代码规范的建立&lt;/li&gt;
&lt;li&gt;流程标准化&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全考虑&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;部署权限管理&lt;/li&gt;
&lt;li&gt;敏感信息保护&lt;/li&gt;
&lt;li&gt;审计日志记录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CI/CD已经成为现代软件开发的标准实践，它不仅仅是工具的使用，更是一种开发文化和思维方式的转变。通过合理实施CI/CD，可以显著提升开发团队的效率和产品质量。&lt;/p&gt;
</content:encoded></item><item><title>Java异常处理</title><link>https://17356085.github.io/posts/2025/java%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/java%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/</guid><description>Java异常处理</description><pubDate>Sun, 17 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 异常体系结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Throwable
├── Error (错误)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── NoClassDefFoundError
└── Exception (异常)
    ├── RuntimeException (运行时异常/非检查异常)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── IllegalArgumentException
    │   └── ClassCastException
    └── 检查异常 (Checked Exception)
        ├── IOException
        ├── SQLException
        ├── ClassNotFoundException
        └── InterruptedException
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2 异常分类&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;检查异常 (Checked Exception)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编译时必须处理的异常&lt;/li&gt;
&lt;li&gt;必须使用try-catch或throws声明&lt;/li&gt;
&lt;li&gt;常见：IOException、SQLException、ClassNotFoundException&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;非检查异常 (Unchecked Exception)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行时异常，编译时不强制处理&lt;/li&gt;
&lt;li&gt;包括RuntimeException及其子类&lt;/li&gt;
&lt;li&gt;常见：NullPointerException、ArrayIndexOutOfBoundsException&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;错误 (Error)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JVM系统级错误，程序无法处理&lt;/li&gt;
&lt;li&gt;常见：OutOfMemoryError、StackOverflowError&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3 异常处理语法&lt;/h2&gt;
&lt;h4&gt;try-catch-finally语句&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;try {
    // 可能出现异常的代码
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 处理特定异常
    System.out.println(&quot;除零异常：&quot; + e.getMessage());
} catch (Exception e) {
    // 处理其他异常
    System.out.println(&quot;其他异常：&quot; + e.getMessage());
} finally {
    // 无论是否有异常都会执行
    System.out.println(&quot;清理资源&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;try-with-resources (Java 7+)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 自动关闭资源
try (FileInputStream fis = new FileInputStream(&quot;file.txt&quot;);
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    return br.readLine();
} catch (IOException e) {
    e.printStackTrace();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;throws声明异常&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;public void readFile(String fileName) throws IOException {
    FileInputStream fis = new FileInputStream(fileName);
    // 不在此方法中处理异常，向上抛出
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;throw抛出异常&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;public void setAge(int age) {
    if (age &amp;lt; 0) {
        throw new IllegalArgumentException(&quot;年龄不能为负数&quot;);
    }
    this.age = age;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4 自定义异常&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 自定义检查异常
public class CustomCheckedException extends Exception {
    public CustomCheckedException(String message) {
        super(message);
    }
}

// 自定义运行时异常
public class CustomRuntimeException extends RuntimeException {
    public CustomRuntimeException(String message) {
        super(message);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5 异常处理最佳实践&lt;/h2&gt;
&lt;p&gt;5.1 &lt;strong&gt;不要忽略异常&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 错误做法
try {
    // some code
} catch (Exception e) {
    // 什么都不做
}

// 正确做法
try {
    // some code
} catch (Exception e) {
    logger.error(&quot;处理失败&quot;, e);
    // 或者重新抛出
    throw new ServiceException(&quot;业务处理失败&quot;, e);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5.2 &lt;strong&gt;具体化异常处理&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 好的做法：从具体到抽象
try {
    // some code
} catch (FileNotFoundException e) {
    // 处理文件未找到
} catch (IOException e) {
    // 处理其他IO异常
} catch (Exception e) {
    // 处理其他异常
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5.3 &lt;strong&gt;在合适的层级处理异常&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DAO层：技术异常转换为业务异常&lt;/li&gt;
&lt;li&gt;Service层：业务异常处理&lt;/li&gt;
&lt;li&gt;Controller层：异常统一处理和响应格式化&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6 异常处理建议&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;尽早发现异常，就近处理&lt;/li&gt;
&lt;li&gt;不要忽略异常，至少要记录日志&lt;/li&gt;
&lt;li&gt;自定义异常要有意义，包含足够的上下文信息&lt;/li&gt;
&lt;li&gt;在finally或try-with-resources中释放资源&lt;/li&gt;
&lt;li&gt;避免在finally块中抛出异常&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Git笔记</title><link>https://17356085.github.io/posts/2025/git%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://17356085.github.io/posts/2025/git%E7%AC%94%E8%AE%B0/</guid><description>一些git常用的指令。</description><pubDate>Wed, 13 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以下是 Git 最常用的核心指令清单，分为 &lt;strong&gt;基础操作&lt;/strong&gt;、&lt;strong&gt;分支管理&lt;/strong&gt;、&lt;strong&gt;撤销操作&lt;/strong&gt; 和 &lt;strong&gt;远程协作&lt;/strong&gt; 四类，附带通俗解释：&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;一、基础操作（每天必用）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;初始化新仓库&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git clone &amp;lt;url&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;克隆远程仓库&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git clone https://github.com/user/repo.git&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git add &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;添加文件到暂存区&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git add index.html&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git add .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;添加&lt;strong&gt;所有改动&lt;/strong&gt;（含新增/修改/删除）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git add .&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git commit -m &quot;msg&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;提交暂存区内容&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git commit -m &quot;修复登录bug&quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看工作区状态&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看提交历史&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log --oneline&lt;/code&gt;（简洁版）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;二、分支管理（多人协作核心）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git branch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看本地分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git branch&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git branch &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建新分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git branch feature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git checkout &amp;lt;branch&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;切换分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git checkout main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git checkout -b &amp;lt;branch&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建并切换分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git checkout -b hotfix&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git merge &amp;lt;branch&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;合并分支到当前分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git merge feature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git branch -d &amp;lt;branch&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git branch -d feature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;三、撤销操作（救命必备）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git restore &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;丢弃&lt;strong&gt;工作区&lt;/strong&gt;修改&lt;/td&gt;
&lt;td&gt;改乱了文件想复原&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git restore --staged &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将文件移出&lt;strong&gt;暂存区&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git add&lt;/code&gt; 后反悔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --soft HEAD^&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;撤销 commit（保留改动到暂存区）&lt;/td&gt;
&lt;td&gt;提交信息写错了&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --hard HEAD^&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;彻底&lt;/strong&gt;回退到上一版本（慎用！）&lt;/td&gt;
&lt;td&gt;想完全放弃最近提交&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;四、远程协作（GitHub 同步）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git remote -v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看远程仓库地址&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote -v&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;拉取远程更新（= fetch + merge）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git pull origin main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git push&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;推送到远程仓库&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git push origin main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git fetch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;仅下载远程变更（不自动合并）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git fetch origin&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;🚀 高效组合技&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;一键提交&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add . &amp;amp;&amp;amp; git commit -m &quot;更新&quot; &amp;amp;&amp;amp; git push
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;紧急修复&lt;/strong&gt;（跳过暂存）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -am &quot;紧急修复&quot; &amp;amp;&amp;amp; git push
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;撤销未提交的改动&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git restore .  # 丢弃所有工作区修改
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;⚠️ 注意避坑&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git add .&lt;/code&gt; 会添加所有文件（包括临时文件），建议用 &lt;code&gt;.gitignore&lt;/code&gt; 过滤&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git push -f&lt;/code&gt; （强制推送）会覆盖远程历史，团队协作时禁用！&lt;/li&gt;
&lt;li&gt;提交前一定用 &lt;code&gt;git status&lt;/code&gt; 确认文件状态&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 建议配置别名简化操作（在 &lt;code&gt;~/.gitconfig&lt;/code&gt; 添加）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[alias]
  co = checkout
  br = branch
  st = status
  ci = commit
  df = diff
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
</content:encoded></item></channel></rss>