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

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


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

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

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

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

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

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

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

&lt;/beans&gt;
</code></pre>
<pre><code>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("Bean后处理器的before方法执行，即将开始初始化");
        return bean;
    }

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

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

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

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

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

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

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

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

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("5.afterPropertiesSet执行");
    }
}
</code></pre>
<pre><code>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("4.Bean后处理器的before方法执行，即将开始初始化");
        return bean;
    }

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

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

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

    &lt;context:component-scan base-package="com.powernode.spring6.bean3" use-default-filters="false"&gt;
        &lt;context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/&gt;
    &lt;/context:component-scan&gt;
    
&lt;/beans&gt;
</code></pre>
<h3>3.3 负责注入的注解</h3>
<p>1️⃣ <strong>@Value</strong>：当属性是简单类型时可使用</p>
<pre><code>&lt;!-- 不要忘记打开包扫描 --&gt;
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="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"&gt;
    &lt;context:component-scan base-package="com.powernode.spring6.bean4"/&gt;
&lt;/beans&gt;
</code></pre>
<pre><code>@toString
@Commponent
public class User {
    @Value(value = "zhangsan")
    private String name;
    @Value("20")
    private int age;
}
</code></pre>
<p>2️⃣ <strong>@Autowired</strong>：可以用来注入非简单类型，单独使用@Autowrite注解时，默认根据类型装配
源码：</p>
<pre><code>@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;
}
</code></pre>
<p>3️⃣ <strong>@Qualifier</strong>：@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配，在@Qualifier注解中指定Bean名称。</p>
<pre><code>/*
 * UserDao的子类
 */

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

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

    private UserDao userDao;

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

    public void save(){
        userDao.insert();
    }
}
</code></pre>
<p>4️⃣ <strong>@Resource</strong>：与@Autowired一样可用于完成非简单类型的注解，两者之间的差异</p>
<ul>
<li>@Resource注解是JDK的一部分，@Autowired注解是Spring框架，前者比后者更具有通用性</li>
<li><strong>@Resource注解默认根据名称装配byName，未指定name时，使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。</strong>；@Autowired注解默认根据类型装配byType，如果想根据名称装配，需要配合@Qualifier注解一起用。</li>
<li>@Resource注解用在属性、setter方法上；@Autowrite注解用在属性、setter方法、构造方法、构造方法参数上。</li>
</ul>
<h2>4. 全注解开发</h2>
<p>使用注解代替配置文件</p>
<pre><code>@Configuration
@ComponentScan({"com.powernode.spring6.dao", "com.powernode.spring6.service"})
public class Spring6Configuration {
}
</code></pre>
<p>编写测试程序：不在new ClassPathXmlApplicationContext()对象</p>
<pre><code>@Test
public void testNoXml(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.save();
}
</code></pre>
<h2>4. Spring IoC容器底层实现原理</h2>
<h3>4.1 核心架构设计</h3>
<h4>容器接口层次结构</h4>
<pre><code>BeanFactory (顶层接口)
    ├── ListableBeanFactory (可列举Bean)
    ├── HierarchicalBeanFactory (分层Bean工厂)
    ├── ConfigurableBeanFactory (可配置Bean工厂)
    └── ApplicationContext (应用上下文)
            ├── ConfigurableApplicationContext
            └── AbstractApplicationContext (抽象实现)
</code></pre>
<p><strong>核心接口职责：</strong></p>
<ul>
<li><code>BeanFactory</code>：基础容器，定义了getBean()等基本方法</li>
<li><code>ApplicationContext</code>：高级容器，提供事件发布、国际化等企业功能</li>
<li><code>ConfigurableApplicationContext</code>：可配置的应用上下文</li>
</ul>
<h3>4.2 底层实现机制</h3>
<h4>4.2.1 Bean定义注册机制</h4>
<p><strong>BeanDefinition数据结构</strong></p>
<pre><code>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;       // 销毁方法
}
</code></pre>
<p><strong>注册流程</strong></p>
<pre><code>// 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);
}
</code></pre>
<h4>4.2.2 Bean实例化核心流程</h4>
<p><strong>AbstractAutowireCapableBeanFactory.createBean()方法</strong></p>
<pre><code>protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 解析Bean类型
    Class&lt;?&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;
}
</code></pre>
<p><strong>doCreateBean()详细实现</strong></p>
<pre><code>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; 
        this.allowCircularReferences &amp;&amp; isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -&gt; getEarlyBeanReference(beanName, mbd, bean));
    }
    
    // 3. 填充属性（依赖注入）
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化Bean
    Object exposedObject = initializeBean(beanName, bean, mbd);
    
    return exposedObject;
}
</code></pre>
<h4>4.2.3 三级缓存解决循环依赖</h4>
<p><strong>DefaultSingletonBeanRegistry缓存结构</strong></p>
<pre><code>public class DefaultSingletonBeanRegistry {
    // 一级缓存：完成初始化的单例Bean
    private final Map&lt;String, Object&gt; singletonObjects = new ConcurrentHashMap&lt;&gt;(256);
    
    // 二级缓存：完成实例化但未完成初始化的Bean
    private final Map&lt;String, Object&gt; earlySingletonObjects = new ConcurrentHashMap&lt;&gt;(16);
    
    // 三级缓存：Bean工厂对象，用于解决循环依赖
    private final Map&lt;String, ObjectFactory&lt;?&gt;&gt; singletonFactories = new HashMap&lt;&gt;(16);
    
    // 正在创建的Bean名称
    private final Set&lt;String&gt; singletonsCurrentlyInCreation = 
        Collections.newSetFromMap(new ConcurrentHashMap&lt;&gt;(16));
}
</code></pre>
<p><strong>获取单例Bean流程</strong></p>
<pre><code>protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    
    // 2. 一级缓存没有且正在创建中
    if (singletonObject == null &amp;&amp; isSingletonCurrentlyInCreation(beanName)) {
        // 从二级缓存获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        
        // 3. 二级缓存也没有且允许早期引用
        if (singletonObject == null &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&lt;?&gt; singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            // 移到二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}
</code></pre>
<h4>4.2.4. 依赖注入实现机制</h4>
<p><strong>AutowiredAnnotationBeanPostProcessor核心逻辑</strong></p>
<pre><code>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);
        }
    }
}
</code></pre>
<h4>4.2.5 Bean生命周期管理</h4>
<p><strong>AbstractAutowireCapableBeanFactory.initializeBean()方法</strong></p>
<pre><code>protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
    // 1. 调用Aware接口方法
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged(() -&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;
}
</code></pre>
<h4>4.2.6 容器启动流程</h4>
<p><strong>AbstractApplicationContext.refresh()方法</strong></p>
<pre><code>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;
        }
    }
}
</code></pre>
<h3>4.3 关键技术实现</h3>
<h4>1. 反射机制运用</h4>
<ul>
<li>使用<code>Class.forName()</code>加载类</li>
<li>通过<code>Constructor.newInstance()</code>创建实例</li>
<li>使用<code>Method.invoke()</code>调用方法</li>
<li>通过<code>Field.set()</code>设置字段值</li>
</ul>
<h4>2. 工厂模式实现</h4>
<ul>
<li>BeanFactory作为顶层工厂接口</li>
<li>不同实现提供不同的Bean创建策略</li>
<li>ObjectFactory用于延迟创建和解决循环依赖</li>
</ul>
<h4>3. 策略模式应用</h4>
<ul>
<li>InstantiationStrategy：实例化策略</li>
<li>AutowireCapableBeanFactory：自动装配策略</li>
<li>BeanPostProcessor：Bean后处理策略</li>
</ul>
<h4>4. 观察者模式</h4>
<ul>
<li>ApplicationEvent事件机制</li>
<li>ApplicationListener监听器</li>
<li>ApplicationEventMulticaster事件多播器</li>
</ul>
<p>Spring IoC容器的底层实现充分体现了面向对象设计原则，通过精巧的架构设计和多种设计模式的综合运用，实现了高度灵活和可扩展的依赖注入容器。</p>
]]></content>
    <author><name>mio</name></author>
    <category term="后端"/>
  </entry>
  <entry>
    <title>Java中的注解</title>
    <link href="https://17356085.github.io/posts/2025/java%E4%B8%AD%E7%9A%84%E6%B3%A8%E8%A7%A3/" rel="alternate" type="text/html"/>
    <id>https://17356085.github.io/posts/2025/java%E4%B8%AD%E7%9A%84%E6%B3%A8%E8%A7%A3/</id>
    <published>2025-08-19T00:00:00.000Z</published>
    <updated>2025-08-19T00:00:00.000Z</updated>
    <summary>了解Spring AOP的铺垫知识</summary>
    <content type="html"><![CDATA[<blockquote>
<p>Java中的注解的作用，主要有以下几个方面：</p>
<ol>
<li>生成文档，通过代码里标识的元数据生成javadoc文档。</li>
<li>编译检查，通过代码里标识的元数据让编译器在编译期间进行检查验证。</li>
<li>编译时动态处理，编译时通过代码里标识的元数据动态处理，例如动态生成代码。</li>
<li>运行时动态处理，运行时通过代码里标识的元数据动态处理，例如使用反射注入实例。</li>
</ol>
</blockquote>
<h2>1. 基本语法</h2>
<p><strong>组成元素</strong>：</p>
<ul>
<li><code>@</code> 符号：标识这是一个注解</li>
<li>注解名：注解的类型名称</li>
<li>参数列表：可选，用括号包含的键值对</li>
</ul>
<pre><code>@注解名(参数列表) public class MyClass { // 类内容 }
</code></pre>
<h2>2. Java中的内置注解</h2>
<blockquote>
<p>这些内置注解在实际开发中有什么用？</p>
</blockquote>
<h3>2.1 标准注解</h3>
<p>1️⃣ <code>@Override</code> ：用于标记重写父类，编译器会检查是否真正重写了父类方法</p>
<pre><code>public class Child extends Parent {
    @Override
    public void method() {
        // 重写父类方法
        System.out.println("Child implementation");
    }
}
</code></pre>
<p>2️⃣ <code>@Deprecated</code>：标记已过时的程序元素，编译器会产生警告。</p>
<pre><code>public class OldClass {
    @Deprecated
    public void oldMethod() {
        System.out.println("This method is deprecated");
    }
    
    public void newMethod() {
        System.out.println("Use this method instead");
    }
}
</code></pre>
<p>3️⃣<code>@SuppressWarnings</code>：抑制编译器警告。</p>
<pre><code>public class WarningExample {
    @SuppressWarnings("unchecked")
    public void method() {
        List list = new ArrayList(); // 原本会有unchecked警告
        list.add("item");
    }
    
    @SuppressWarnings({"deprecation", "unused"})
    public void multipleWarnings() {
        OldClass old = new OldClass();
        old.oldMethod(); // 使用了deprecated方法
        String unused = "never used"; // 未使用的变量
    }
}
</code></pre>
<p>4️⃣<code>SafeVarargs</code>：用于可变参数方法，抑制堆污染警告。</p>
<pre><code>public class VarargsExample {
    @SafeVarargs
    public static &lt;T&gt; void printAll(T... items) {
        for (T item : items) {
            System.out.println(item);
        }
    }
}
</code></pre>
<p>5️⃣<code>@FunctionalInterface</code>：标记函数式接口，确保接口只有一个抽象方法。</p>
<pre><code>@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
    
    // 可以有默认方法
    default void printResult(int result) {
        System.out.println("Result: " + result);
    }
    
    // 可以有静态方法
    static void info() {
        System.out.println("This is a calculator interface");
    }
}
</code></pre>
<h3>2.2 元注解</h3>
<p>元注解是用来注解其他注解的注解。</p>
<p>1️⃣<code>@Target</code></p>
<p>用来指定注解可以用在哪些程序元素上。</p>
<pre><code>import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

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

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

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

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

@InheritableAnnotation("parent")
public class Parent {
}

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

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

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

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

// 缓存切面处理器
@Component
@Aspect
public class CacheAspect {
    
    private Map&lt;String, CacheItem&gt; cache = new ConcurrentHashMap&lt;&gt;();
    
    // 拦截所有带@Cacheable注解的方法
    @Around("@annotation(cacheable)")
    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; !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() &gt; expireTime;
        }
        
        public Object getValue() {
            return value;
        }
    }
}

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

// 日志切面处理器
@Component
@Aspect
public class LogAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    
    @Around("@annotation(logExecution)")
    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("方法 {} 开始执行，参数: {}", methodName, Arrays.toString(args));
        } else {
            logger.info("方法 {} 开始执行", methodName);
        }
        
        try {
            Object result = joinPoint.proceed();
            
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            if (logExecution.logResult()) {
                logger.info("方法 {} 执行完成，耗时: {}ms，结果: {}", methodName, executionTime, result);
            } else {
                logger.info("方法 {} 执行完成，耗时: {}ms", methodName, executionTime);
            }
            
            return result;
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            logger.error("方法 {} 执行异常，耗时: {}ms", methodName, executionTime, e);
            throw e;
        }
    }
}

// 使用日志注解
@Service
public class OrderService {
    
    @LogExecution(value = "创建订单", logParams = true, logResult = true)
    public Order createOrder(OrderDto orderDto) {
        // 业务逻辑
        Order order = new Order();
        order.setOrderNumber(generateOrderNumber());
        order.setAmount(orderDto.getAmount());
        return orderRepository.save(order);
    }
}
</code></pre>
]]></content>
    <author><name>mio</name></author>
    <category term="后端"/>
  </entry>
  <entry>
    <title>Java反射机制与动态代理</title>
    <link href="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/" rel="alternate" type="text/html"/>
    <id>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/</id>
    <published>2025-08-18T00:00:00.000Z</published>
    <updated>2025-08-18T00:00:00.000Z</updated>
    <summary>对Spring框架底层实现不理解，回来恶补基础（😭</summary>
    <content type="html"><![CDATA[<blockquote>
<ul>
<li>如何获取class字节码文件的对象</li>
<li>利用反射如何获取构造方法（创建对象）</li>
<li>利用反射如何获取成员变量（赋值，获取值）</li>
<li>利用反射如何获取成员方法（运行）</li>
</ul>
</blockquote>
<h2>1. 概述</h2>
<ul>
<li><strong>动态获取</strong>：在<strong>运行状态中</strong>，能知道<strong>任意一个类</strong>的<em><strong>属性和方法</strong></em></li>
<li><strong>动态调用</strong>：对于任意一个对象，都可以<strong>调用</strong>它的<em><strong>属性和方法</strong></em></li>
</ul>
<h2>2. 获取字节码文件对象</h2>
<blockquote>
<p>Java文件：自己编写的Java代码
字节码文件：编译之后的class文件
字节码文件对象：class文件加载到内存后，由虚拟机自动创建的对象。</p>
</blockquote>
<h3>2.1 方式一：类名.class(类字面量)</h3>
<p><strong>特点：</strong></p>
<ul>
<li>编译时确定，类型安全</li>
<li><strong>不会触发类的初始化（静态代码块不执行）</strong></li>
<li>性能最高，推荐使用</li>
<li><strong>支持基本数据类型</strong></li>
</ul>
<pre><code>public class ClassLiteralExample {
    public static void main(String[] args) {
        // 获取基本类型的Class对象
        Class&lt;Integer&gt; intClass = int.class;
        Class&lt;Double&gt; doubleClass = double.class;
        
        // 获取引用类型的Class对象
        Class&lt;String&gt; stringClass = String.class;
        Class&lt;List&gt; listClass = List.class;
        
        System.out.println("int.class: " + intClass.getName());
        System.out.println("String.class: " + stringClass.getName());
    }
}
</code></pre>
<h3>2.2 方式二：对象.getClass()</h3>
<p><strong>特点：</strong></p>
<ul>
<li>运行时确定，需要先有对象实例</li>
<li>会触发类的初始化</li>
<li>返回对象的实际类型（多态时很重要）</li>
<li>不能获取基本数据类型的Class对象</li>
</ul>
<pre><code>public class GetClassExample {
    static {
        System.out.println("User类被初始化");
    }
    
    public static void main(String[] args) {
        // 必须先创建对象
        String str = "Hello World";
        User user = new User();
        
        Class&lt;?&gt; stringClass = str.getClass();
        Class&lt;?&gt; userClass = user.getClass();
        
        // 注意：对于继承关系，返回的是实际对象的类型
        Animal animal = new Dog();
        Class&lt;?&gt; animalClass = animal.getClass(); // 返回Dog.class，不是Animal.class
        
        System.out.println("实际类型: " + animalClass.getName()); // 输出: Dog
    }
}

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

class UserDto {
    private String name;
    private int age;
    
    @Override
    public String toString() {
        return "UserDto{name='" + name + "', age=" + age + "}";
    }
}
</code></pre>
<h3>4.2 方法调用拦截器</h3>
<pre><code>public class MethodInterceptor {
    public static void interceptAllMethods(Object target) {
        Class&lt;?&gt; clazz = target.getClass();
        
        System.out.println("=== 拦截对象 " + clazz.getSimpleName() + " 的所有方法 ===");
        
        // 获取所有public方法
        Method[] methods = clazz.getMethods();
        
        for (Method method : methods) {
            // 过滤掉Object类的方法
            if (method.getDeclaringClass() == Object.class) {
                continue;
            }
            
            System.out.println("方法: " + method.getName());
            System.out.println("  参数类型: " + Arrays.toString(method.getParameterTypes()));
            System.out.println("  返回类型: " + method.getReturnType().getSimpleName());
            System.out.println("  修饰符: " + Modifier.toString(method.getModifiers()));
            
            // 检查注解
            if (method.isAnnotationPresent(Deprecated.class)) {
                System.out.println("  ⚠️ 此方法已过时");
            }
            
            System.out.println();
        }
    }
    
    public static void main(String[] args) {
        List&lt;String&gt; list = new ArrayList&lt;&gt;();
        interceptAllMethods(list);
    }
}
</code></pre>
<h3>4.3 构造器选择器</h3>
<pre><code>public class ConstructorSelector {
    public static &lt;T&gt; T createInstance(Class&lt;T&gt; clazz, Object... args) throws Exception {
        Constructor&lt;?&gt;[] constructors = clazz.getDeclaredConstructors();
        
        // 根据参数数量和类型选择合适的构造器
        for (Constructor&lt;?&gt; constructor : constructors) {
            Class&lt;?&gt;[] paramTypes = constructor.getParameterTypes();
            
            if (paramTypes.length == args.length) {
                boolean match = true;
                for (int i = 0; i &lt; paramTypes.length; i++) {
                    if (args[i] != null &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("没有找到匹配的构造器");
    }
    
    private static boolean isCompatibleType(Class&lt;?&gt; paramType, Class&lt;?&gt; argType) {
        // 处理基本类型和包装类型的兼容性
        if (paramType == int.class &amp;&amp; argType == Integer.class) return true;
        if (paramType == long.class &amp;&amp; argType == Long.class) return true;
        if (paramType == double.class &amp;&amp; argType == Double.class) return true;
        // ... 其他基本类型
        return false;
    }
    
    public static void main(String[] args) throws Exception {
        // 测试创建不同类型的对象
        String str = createInstance(String.class, "Hello World");
        Integer num = createInstance(Integer.class, 42);
        
        System.out.println("创建的字符串: " + str);
        System.out.println("创建的整数: " + num);
    }
}
</code></pre>
<h2>5. 面试题练习（重点）</h2>
<h3>5.1 反射和配置文件结合动态获取</h3>
<p>求: 利用反射根据文件中的不同类名和方法名，创建不同的对象并调用方法。</p>
<p>分析:</p>
<p>①通过Properties加载配置文件</p>
<p>②得到类名和方法名</p>
<p>③通过类名反射得到Class对象</p>
<p>④通过Class对象创建一个对象</p>
<p>⑤通过Class对象得到方法</p>
<p>⑥调用方法</p>
<p>代码示例：</p>
<pre><code>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("day14-code\\prop.properties");
        prop.load(fis);  // 加载配置文件内容到Properties对象
        fis.close();
        
        System.out.println(prop);  // 打印配置信息
        
        // 从配置中获取类名和方法名
        String classname = prop.get("classname") + "";    // 转换为String
        String methodname = prop.get("methodname") + "";  // 转换为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
</code></pre>
<h3>5.2 利用发射保存对象中的信息</h3>
<pre><code>public class MyReflectDemo {
    public static void main(String[] args) throws IllegalAccessException, IOException {
    /*
        对于任意一个对象，都可以把对象所有的字段名和值，保存到文件中去
    */
       Student s = new Student("小A",23,'女',167.5,"睡觉");
       Teacher t = new Teacher("播妞",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("myreflect\\a.txt"));
        //3. 获取所有的成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            //获取成员变量的名字
            String name = field.getName();
            //获取成员变量的值
            Object value = field.get(obj);
            //写出数据
            bw.write(name + "=" + value);
            bw.newLine();
        }

        bw.close();

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

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

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

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

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

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

</code></pre>
<p>3️⃣核心组件</p>
<ol>
<li><strong><code>InvocationHandler</code>接口</strong></li>
</ol>
<pre><code>public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
</code></pre>
<ul>
<li>
<p><code>proxy</code>：生成的代理对象本身（通常用得少，避免直接调用它的方法导致死循环）。</p>
</li>
<li>
<p><code>method</code>：当前调用的方法对象（<code>java.lang.reflect.Method</code>），可以获取方法名、参数类型等。</p>
</li>
<li>
<p><code>args</code>：调用方法时传进来的实参数组，如果没有参数则为 <code>null</code>。</p>
</li>
<li>
<p>返回值：<strong>必须返回一个对象</strong>，这个返回值会作为代理方法调用的返回值。</p>
</li>
</ul>
<ol>
<li><strong><code>Proxy.newProxyInstance</code>方法</strong>：这是创建代理对象的入口。
参数说明：
<ul>
<li><code>ClassLoader loader</code>：指定代理对象的类加载器，一般传 <code>目标类.getClass().getClassLoader()</code>。</li>
<li><code>Class&lt;?&gt;[] interfaces</code>：代理类需要实现的接口数组</li>
<li><code>InvocationHandler h</code>：方法调用的处理器，用来定义增强逻辑。</li>
</ul>
</li>
</ol>
<h4>6.2.2 实现</h4>
<blockquote>
<p><em>困惑点：<code>Object result = method.invoke(target, args);</code>这里是如何调用对象的方法？</em>
<strong>Java 反射调用</strong> 的核心一行。它的作用等价于<strong>在运行时</strong>对 <code>target</code> 这个对象“执行一个名叫 <code>method</code> 的方法，参数是 <code>args</code>”，就像手写的：<code>target.foo("abc", 123);</code>
ps：假设 method 代表的是接口里的 foo(String x, int y)</p>
</blockquote>
<p><strong>调用链路（重点理解）</strong>：调用方 → 代理对象（<code>$Proxy0</code>…）→ <strong><code>InvocationHandler.invoke</code></strong> →（你自定义的前置/后置/环绕逻辑）→ 反射调用<strong>目标对象</strong>方法（或你自行处理）→ 返回值/异常回传。</p>
<pre><code>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("添加用户: " + name);
    }
    
    @Override
    public String getUser(int id) {
        System.out.println("获取用户: " + id);
        return "User-" + id;
    }
    
    @Override
    public void deleteUser(int id) {
        System.out.println("删除用户: " + 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("=== 方法调用开始 ===");
        System.out.println("调用方法: " + method.getName());
        System.out.println("方法参数: " + java.util.Arrays.toString(args));
        long startTime = System.currentTimeMillis();
        
        try {
            // 调用目标方法
            Object result = method.invoke(target, args);
            
            // 后置增强
            long endTime = System.currentTimeMillis();
            System.out.println("方法执行成功");
            System.out.println("执行耗时: " + (endTime - startTime) + "ms");
            System.out.println("返回结果: " + result);
            System.out.println("=== 方法调用结束 ===\n");
            
            return result;
        } catch (Exception e) {
            System.out.println("方法执行异常: " + 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("张三");
        String user = proxy.getUser(1);
        proxy.deleteUser(1);
        
        // 验证代理对象的类型
        System.out.println("代理对象类型: " + proxy.getClass().getName());
        System.out.println("是否为Proxy的实例: " + (proxy instanceof Proxy));
        System.out.println("实现的接口: " + java.util.Arrays.toString(proxy.getClass().getInterfaces()));
    }
}
</code></pre>
<p><strong>代理对象执行</strong>：<code>proxy.addUser("张三")</code> → 代理类方法调用 → 统一进入 <code>InvocationHandler.invoke()</code> → 在 <code>invoke()</code> 里通过反射执行目标对象方法 → 返回结果并可做增强逻辑。</p>
<h4>6.2.3 JDK动态代理核心API</h4>
<pre><code>public class ProxyAPIDemo {
    public static void demonstrateProxyAPI() {
        UserService target = new UserServiceImpl();
        
        // 1. Proxy.newProxyInstance() - 创建代理实例
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            new Class&lt;?&gt;[]{UserService.class},
            new LoggingInvocationHandler(target)
        );
        
        // 2. Proxy.getProxyClass() - 获取代理类
        Class&lt;?&gt; proxyClass = Proxy.getProxyClass(
            UserService.class.getClassLoader(),
            UserService.class
        );
        System.out.println("代理类: " + proxyClass.getName());
        
        // 3. Proxy.isProxyClass() - 检查是否为代理类
        boolean isProxy = Proxy.isProxyClass(proxy.getClass());
        System.out.println("是否为代理类: " + isProxy);
        
        // 4. Proxy.getInvocationHandler() - 获取调用处理器
        InvocationHandler handler = Proxy.getInvocationHandler(proxy);
        System.out.println("调用处理器: " + handler.getClass().getName());
    }
}
</code></pre>
<h4>6.2.4 高级JDK代理应用</h4>
<p>1️⃣通用代理工厂</p>
<pre><code>public class ProxyFactory {
    
    /**
     * 创建带日志功能的代理
     */
    public static &lt;T&gt; T createLoggingProxy(T target) {
        return createProxy(target, new LoggingInvocationHandler(target));
    }
    
    /**
     * 创建带性能监控的代理
     */
    public static &lt;T&gt; T createPerformanceProxy(T target) {
        return createProxy(target, new PerformanceInvocationHandler(target));
    }
    
    /**
     * 创建带事务管理的代理
     */
    public static &lt;T&gt; T createTransactionProxy(T target) {
        return createProxy(target, new TransactionInvocationHandler(target));
    }
    
    /**
     * 创建多重代理（支持多个增强功能）
     */
    public static &lt;T&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("unchecked")
    private static &lt;T&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("⏱️ 方法 %s 执行耗时: %.2f ms%n", 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("🔄 开始事务");
        
        try {
            Object result = method.invoke(target, args);
            
            // 提交事务
            System.out.println("✅ 提交事务");
            return result;
            
        } catch (Exception e) {
            // 回滚事务
            System.out.println("❌ 回滚事务");
            throw e;
        }
    }
}
</code></pre>
<p>2️⃣多层代理示例</p>
<pre><code>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("=== 多重代理测试 ===");
        multiProxy.addUser("李四");
    }
}
</code></pre>
<h3>6.3 CGLIB动态代理</h3>
<blockquote>
<p><strong>CGLIB</strong>（Code Generation Library）是一种基于 <strong>继承</strong> 的动态代理实现方式。  它通过 <strong>字节码生成技术</strong>（ASM 字节码框架）在运行时动态生成一个<strong>目标类的子类</strong>，并在子类中<strong>重写目标类的方法</strong>，从而在方法调用前后插入增强逻辑。</p>
</blockquote>
<h4>6.3.1 与 <strong>JDK 动态代理</strong> 不同：</h4>
<ul>
<li><strong>JDK 动态代理</strong>：必须有<strong>接口</strong>，生成的代理类实现接口。</li>
<li><strong>CGLIB 动态代理</strong>：不需要接口，可以直接代理普通类（但类不能是 <code>final</code>、方法不能是 <code>final</code>/<code>static</code>）。</li>
</ul>
<h4>6.3.2 工作原理</h4>
<p>CGLIB 通过 <code>net.sf.cglib.proxy.Enhancer</code> 类来创建代理对象：</p>
<ol>
<li>
<p>指定 <strong>要代理的目标类</strong>（不是接口）。</p>
</li>
<li>
<p>CGLIB 会生成一个 <strong>目标类的子类</strong>（字节码级别创建）。</p>
</li>
<li>
<p>在子类方法中嵌入调用 <strong>MethodInterceptor 的 intercept 方法</strong>。</p>
</li>
<li>
<p>在 <strong>intercept 方法</strong> 中执行前置/后置逻辑，再通过 <code>methodProxy.invokeSuper()</code> 调用父类（目标类）的真实方法。</p>
</li>
</ol>
<h4>6.3.3 实现</h4>
<pre><code>// 注意：需要添加CGLIB依赖
// &lt;dependency&gt;
//     &lt;groupId&gt;cglib&lt;/groupId&gt;
//     &lt;artifactId&gt;cglib&lt;/artifactId&gt;
//     &lt;version&gt;3.3.0&lt;/version&gt;
// &lt;/dependency&gt;

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

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

// CGLIB方法拦截器
class UserManagerInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB前置增强: " + method.getName());
        
        // 调用父类方法（原始方法）
        Object result = proxy.invokeSuper(obj, args);
        
        System.out.println("CGLIB后置增强: " + 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("王五");
        String user = proxy.getUser(2);
        
        System.out.println("代理对象类型: " + proxy.getClass().getName());
        System.out.println("父类: " + proxy.getClass().getSuperclass().getName());
    }
}
</code></pre>
<h4>6.3.4 高级特性（了解即可）</h4>
<pre><code>// 方法过滤器
class SelectiveMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 只代理特定方法
        if (method.getName().startsWith("add")) {
            System.out.println("拦截add方法: " + method.getName());
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("add方法执行完成");
            return result;
        }
        
        // 其他方法直接执行
        return proxy.invokeSuper(obj, args);
    }
}

// 回调过滤器
class MyCallbackFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        // 根据方法返回不同的回调索引
        if (method.getName().startsWith("add")) {
            return 0; // 使用第一个回调
        } else if (method.getName().startsWith("get")) {
            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("赵六");    // 会被第一个拦截器处理
        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("方法 " + method.getName() + " 执行时间: " + (end - start) + "ms");
        
        return result;
    }
}
</code></pre>
<h3>6.4 动态代理在框架中的应用</h3>
<h4>6.4.1 SpringBootAOP实现</h4>
<pre><code>// 切面注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface LogExecution {
    String value() default "";
}

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

// 业务服务
class OrderService {
    @LogExecution("订单创建")
    @Transactional
    public void createOrder(String orderId) {
        System.out.println("创建订单: " + orderId);
        // 模拟可能的异常
        if (orderId.equals("ERROR")) {
            throw new RuntimeException("订单创建失败");
        }
    }
    
    @LogExecution("订单查询")
    @Transactional(readOnly = true)
    public String getOrder(String orderId) {
        System.out.println("查询订单: " + orderId);
        return "Order-" + 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("📝 [LOG] 开始执行: " + logAnnotation.value());
        }
        
        // 处理@Transactional注解
        Transactional txAnnotation = method.getAnnotation(Transactional.class);
        boolean inTransaction = false;
        
        if (txAnnotation != null) {
            System.out.println("🔄 [TX] 开启事务 (只读: " + txAnnotation.readOnly() + ")");
            inTransaction = true;
        }
        
        try {
            // 执行目标方法
            Object result = method.invoke(target, args);
            
            // 事务提交
            if (inTransaction) {
                System.out.println("✅ [TX] 事务提交");
            }
            
            // 日志记录
            if (logAnnotation != null) {
                System.out.println("📝 [LOG] 执行成功: " + logAnnotation.value());
            }
            
            return result;
            
        } catch (Exception e) {
            // 事务回滚
            if (inTransaction) {
                System.out.println("❌ [TX] 事务回滚");
            }
            
            // 错误日志
            if (logAnnotation != null) {
                System.out.println("📝 [LOG] 执行失败: " + logAnnotation.value() + " - " + e.getCause().getMessage());
            }
            
            throw e;
        }
    }
}

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

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

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

import org.aspectj.lang.annotation.Aspect;

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

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

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

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

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

}
</code></pre>
<p><strong>缺点</strong>：切点表达式重复，如果切点表达式需要维护，需要修改多处。</p>
<p>2️⃣ <strong>应该这么做</strong></p>
<p>将切点表达式用@Pointcut注解单独的定义出来，在需要的位置引入即可。</p>
<pre><code>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("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void pointcut(){}

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

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

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

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

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

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

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

    &lt;!--aop配置--&gt;
    &lt;aop:config&gt;
        &lt;!--切点表达式--&gt;
        &lt;aop:pointcut id="p" expression="execution(* com.powernode.spring6.service.VipService.*(..))"/&gt;
        &lt;!--切面--&gt;
        &lt;aop:aspect ref="timerAspect"&gt;
            &lt;!--切面=通知 + 切点--&gt;
            &lt;aop:around method="time" pointcut-ref="p"/&gt;
        &lt;/aop:aspect&gt;
    &lt;/aop:config&gt;
&lt;/beans&gt;
</code></pre>
]]></content>
    <author><name>mio</name></author>
    <category term="后端"/>
  </entry>
  <entry>
    <title>CI/CD的基本概念</title>
    <link href="https://17356085.github.io/posts/2025/cicd%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/" rel="alternate" type="text/html"/>
    <id>https://17356085.github.io/posts/2025/cicd%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/</id>
    <published>2025-08-17T00:00:00.000Z</published>
    <updated>2025-08-17T00:00:00.000Z</updated>
    <summary>通过在GitHubActions自动编译，了解到了CI/CD的基本概念</summary>
    <content type="html"><![CDATA[<h2>CI/CD的全称和基本概念</h2>
<p><strong>CI</strong> = Continuous Integration（持续集成） <strong>CD</strong> = Continuous Delivery/Continuous Deployment（持续交付/持续部署）</p>
<h2>CI - 持续集成（Continuous Integration）</h2>
<h3>定义</h3>
<p>持续集成是指开发团队频繁地将代码变更合并到主分支，每次合并都会触发自动化的构建和测试过程。</p>
<h3>核心流程</h3>
<ol>
<li><strong>代码提交</strong>：开发者将代码推送到版本控制系统（如Git）</li>
<li><strong>自动构建</strong>：系统自动拉取最新代码并进行编译构建</li>
<li><strong>自动测试</strong>：运行单元测试、集成测试等</li>
<li><strong>结果反馈</strong>：将构建和测试结果通知给开发团队</li>
</ol>
<h3>主要优势</h3>
<ul>
<li>尽早发现代码集成问题</li>
<li>减少手动构建的工作量</li>
<li>提高代码质量</li>
<li>加快问题定位速度</li>
</ul>
<h2>CD - 持续交付和持续部署</h2>
<h3>持续交付（Continuous Delivery）</h3>
<p>代码通过CI流程后，自动部署到测试环境或预生产环境，但需要手动触发生产环境部署。</p>
<h3>持续部署（Continuous Deployment）</h3>
<p>代码通过所有自动化测试后，自动部署到生产环境，无需人工干预。</p>
<h3>CD流程包括</h3>
<ul>
<li>自动化部署脚本</li>
<li>环境配置管理</li>
<li>数据库迁移</li>
<li>服务重启和健康检查</li>
</ul>
<h2>CI/CD的完整流程</h2>
<pre><code>代码开发 → 代码提交 → 自动构建 → 自动测试 → 部署到测试环境 → 手动/自动部署到生产环境
</code></pre>
<h2>常用的CI/CD工具</h2>
<h3>云端服务</h3>
<ul>
<li><strong>GitHub Actions</strong>：与GitHub深度集成</li>
<li><strong>GitLab CI/CD</strong>：GitLab内置的CI/CD功能</li>
<li><strong>Travis CI</strong>：老牌的CI服务</li>
<li><strong>CircleCI</strong>：快速且功能强大</li>
<li><strong>Azure DevOps</strong>：微软的DevOps平台</li>
</ul>
<h3>自部署工具</h3>
<ul>
<li><strong>Jenkins</strong>：开源且功能丰富的自动化服务器</li>
<li><strong>TeamCity</strong>：JetBrains开发的CI/CD工具</li>
<li><strong>Bamboo</strong>：Atlassian的CI/CD解决方案</li>
</ul>
<h3>现代化工具</h3>
<ul>
<li><strong>Docker</strong>：容器化部署</li>
<li><strong>Kubernetes</strong>：容器编排</li>
<li><strong>Ansible/Terraform</strong>：基础设施即代码</li>
</ul>
<h2>CI/CD配置文件示例</h2>
<h3>GitHub Actions示例</h3>
<pre><code>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: '16'
    - name: Install dependencies
      run: npm install
    - name: Run tests
      run: npm test
    
  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
    - name: Deploy to production
      run: echo "Deploying to production server"
</code></pre>
<h2>CI/CD的最佳实践</h2>
<h3>代码管理</h3>
<ul>
<li>使用版本控制系统</li>
<li>采用分支策略（如Git Flow）</li>
<li>代码审查流程</li>
</ul>
<h3>自动化测试</h3>
<ul>
<li>单元测试覆盖率要求</li>
<li>集成测试和端到端测试</li>
<li>性能测试和安全测试</li>
</ul>
<h3>环境管理</h3>
<ul>
<li>开发、测试、生产环境一致性</li>
<li>配置文件分离</li>
<li>数据库版本管理</li>
</ul>
<h3>监控和回滚</h3>
<ul>
<li>部署后的健康检查</li>
<li>应用性能监控</li>
<li>快速回滚机制</li>
</ul>
<h2>实施CI/CD的好处</h2>
<h3>对开发团队</h3>
<ul>
<li>减少手动操作，降低出错率</li>
<li>提高开发效率</li>
<li>更快的反馈循环</li>
<li>提升代码质量</li>
</ul>
<h3>对业务</h3>
<ul>
<li>更快的产品迭代速度</li>
<li>降低发布风险</li>
<li>提高系统稳定性</li>
<li>更好的用户体验</li>
</ul>
<h2>挑战和注意事项</h2>
<h3>技术挑战</h3>
<ul>
<li>初期搭建需要投入时间</li>
<li>需要学习新的工具和概念</li>
<li>测试用例的编写和维护</li>
</ul>
<h3>团队协作</h3>
<ul>
<li>需要团队共识和配合</li>
<li>代码规范的建立</li>
<li>流程标准化</li>
</ul>
<h3>安全考虑</h3>
<ul>
<li>部署权限管理</li>
<li>敏感信息保护</li>
<li>审计日志记录</li>
</ul>
<p>CI/CD已经成为现代软件开发的标准实践，它不仅仅是工具的使用，更是一种开发文化和思维方式的转变。通过合理实施CI/CD，可以显著提升开发团队的效率和产品质量。</p>
]]></content>
    <author><name>mio</name></author>
    <category term="学习记录"/>
  </entry>
  <entry>
    <title>Java异常处理</title>
    <link href="https://17356085.github.io/posts/2025/java%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/" rel="alternate" type="text/html"/>
    <id>https://17356085.github.io/posts/2025/java%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/</id>
    <published>2025-08-17T00:00:00.000Z</published>
    <updated>2025-08-17T00:00:00.000Z</updated>
    <summary>Java异常处理</summary>
    <content type="html"><![CDATA[<h2>1. 异常体系结构</h2>
<pre><code>Throwable
├── Error (错误)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── NoClassDefFoundError
└── Exception (异常)
    ├── RuntimeException (运行时异常/非检查异常)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── IllegalArgumentException
    │   └── ClassCastException
    └── 检查异常 (Checked Exception)
        ├── IOException
        ├── SQLException
        ├── ClassNotFoundException
        └── InterruptedException
</code></pre>
<h2>2 异常分类</h2>
<p><strong>检查异常 (Checked Exception)</strong></p>
<ul>
<li>编译时必须处理的异常</li>
<li>必须使用try-catch或throws声明</li>
<li>常见：IOException、SQLException、ClassNotFoundException</li>
</ul>
<p><strong>非检查异常 (Unchecked Exception)</strong></p>
<ul>
<li>运行时异常，编译时不强制处理</li>
<li>包括RuntimeException及其子类</li>
<li>常见：NullPointerException、ArrayIndexOutOfBoundsException</li>
</ul>
<p><strong>错误 (Error)</strong></p>
<ul>
<li>JVM系统级错误，程序无法处理</li>
<li>常见：OutOfMemoryError、StackOverflowError</li>
</ul>
<h2>3 异常处理语法</h2>
<h4>try-catch-finally语句</h4>
<pre><code>try {
    // 可能出现异常的代码
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 处理特定异常
    System.out.println("除零异常：" + e.getMessage());
} catch (Exception e) {
    // 处理其他异常
    System.out.println("其他异常：" + e.getMessage());
} finally {
    // 无论是否有异常都会执行
    System.out.println("清理资源");
}
</code></pre>
<h4>try-with-resources (Java 7+)</h4>
<pre><code>// 自动关闭资源
try (FileInputStream fis = new FileInputStream("file.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    return br.readLine();
} catch (IOException e) {
    e.printStackTrace();
}
</code></pre>
<h4>throws声明异常</h4>
<pre><code>public void readFile(String fileName) throws IOException {
    FileInputStream fis = new FileInputStream(fileName);
    // 不在此方法中处理异常，向上抛出
}
</code></pre>
<h4>throw抛出异常</h4>
<pre><code>public void setAge(int age) {
    if (age &lt; 0) {
        throw new IllegalArgumentException("年龄不能为负数");
    }
    this.age = age;
}
</code></pre>
<h2>4 自定义异常</h2>
<pre><code>// 自定义检查异常
public class CustomCheckedException extends Exception {
    public CustomCheckedException(String message) {
        super(message);
    }
}

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

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