<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://azh3ng.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://azh3ng.com/" rel="alternate" type="text/html" hreflang="zh" /><updated>2026-10-09T10:38:00+08:00</updated><id>https://azh3ng.com/feed.xml</id><title type="html">azh3ng</title><subtitle>azh3ng&apos;s blog
</subtitle><author><name>azh3ng</name></author><entry><title type="html">Adobe 破解版下载：m0nkrus 与 Photoshop 下载记录</title><link href="https://azh3ng.com/2026/10/08/Adobe-%E7%A0%B4%E8%A7%A3%E7%89%88%E4%B8%8B%E8%BD%BD.html" rel="alternate" type="text/html" title="Adobe 破解版下载：m0nkrus 与 Photoshop 下载记录" /><published>2026-10-08T00:00:00+08:00</published><updated>2026-10-08T00:00:00+08:00</updated><id>https://azh3ng.com/2026/10/08/Adobe%20%E7%A0%B4%E8%A7%A3%E7%89%88%E4%B8%8B%E8%BD%BD</id><content type="html" xml:base="https://azh3ng.com/2026/10/08/Adobe-%E7%A0%B4%E8%A7%A3%E7%89%88%E4%B8%8B%E8%BD%BD.html"><![CDATA[<p>记录一个 Adobe 软件的下载来源：<strong>m0nkrus</strong>。以 Photoshop 为例，下载页提供了好几个入口，我试了一下，<strong>PB.WTF 不用注册就能下载种子</strong>。</p>

<h2 id="m0nkrus">m0nkrus</h2>

<p>m0nkrus 是俄语软件分享社区的软件修改、打包发布者，发布的内容包括 Photoshop、Premiere Pro、After Effects 等 Adobe 软件，也有 Autodesk 软件。他的这款 Photoshop 安装包已经整合了破解处理，按作者的说明，安装后即可使用。</p>

<p>官网：<a href="https://monkrus.ws/">https://monkrus.ws/</a>，目前会跳转到 <a href="https://w18.monkrus.ws/">https://w18.monkrus.ws/</a>。也可以在他的 <a href="https://t.me/real_monkrus">Telegram 频道</a>查看更新。</p>

<p><img src="/assets/images/adobe-monkrus/monkrus-home.png" alt="m0nkrus 官网首页" /></p>

<p><em>官网首页，2026 年 10 月 8 日截图。</em></p>

<h2 id="photoshop-2026">Photoshop 2026</h2>

<p>2026 年 10 月 8 日查看时，最新版本是 <strong>Photoshop 2026 v27.11</strong>，具体版本号为 <strong>27.11.0.26</strong>。这是 Windows 64 位的多语言版，包含简体中文和繁体中文。</p>

<p>m0nkrus 在评论区回复确认，目前还没有提供 ARM 版本。</p>

<p><a href="https://w18.monkrus.ws/2026/10/adobe-photoshop-2026-v2711-multilingual.html">Photoshop 2026 v27.11 发布页</a></p>

<p><img src="/assets/images/adobe-monkrus/photoshop-2026-v27-11.png" alt="Photoshop 2026 v27.11 发布页" /></p>

<p><em>Photoshop 发布页，2026 年 10 月 8 日截图。</em></p>

<p>页面下面有多个下载入口，<strong>我试过 PB.WTF，不需要注册就能下载种子文件</strong>，用 qBittorrent 打开即可。</p>

<p><a href="https://pb.wtf/t/414054/">PB.WTF 下载页面</a></p>

<p>另外，<a href="https://korsars.pro/viewtopic.php?t=145957">发布帖</a>里提到，生成式 AI 功能仍需要付费订阅，别以为装好后所有功能都能用。</p>

<p>毕竟是第三方修改版，安全性自行判断。</p>]]></content><author><name>azh3ng</name></author><category term="Windows" /><category term="software" /><category term="Adobe" /><category term="Photoshop" /><summary type="html"><![CDATA[记录一个 Adobe 软件的下载来源：m0nkrus。以 Photoshop 为例，下载页提供了好几个入口，我试了一下，PB.WTF 不用注册就能下载种子。 m0nkrus m0nkrus 是俄语软件分享社区的软件修改、打包发布者，发布的内容包括 Photoshop、Premiere Pro、After Effects 等 Adobe 软件，也有 Autodesk 软件。他的这款 Photoshop 安装包已经整合了破解处理，按作者的说明，安装后即可使用。 官网：https://monkrus.ws/，目前会跳转到 https://w18.monkrus.ws/。也可以在他的 Telegram 频道查看更新。 官网首页，2026 年 10 月 8 日截图。 Photoshop 2026 2026 年 10 月 8 日查看时，最新版本是 Photoshop 2026 v27.11，具体版本号为 27.11.0.26。这是 Windows 64 位的多语言版，包含简体中文和繁体中文。 m0nkrus 在评论区回复确认，目前还没有提供 ARM 版本。 Photoshop 2026 v27.11 发布页 Photoshop 发布页，2026 年 10 月 8 日截图。 页面下面有多个下载入口，我试过 PB.WTF，不需要注册就能下载种子文件，用 qBittorrent 打开即可。 PB.WTF 下载页面 另外，发布帖里提到，生成式 AI 功能仍需要付费订阅，别以为装好后所有功能都能用。 毕竟是第三方修改版，安全性自行判断。]]></summary></entry><entry><title type="html">Windows 软件</title><link href="https://azh3ng.com/2026/05/05/Windows-%E8%BD%AF%E4%BB%B6.html" rel="alternate" type="text/html" title="Windows 软件" /><published>2026-05-05T00:00:00+08:00</published><updated>2026-05-05T00:00:00+08:00</updated><id>https://azh3ng.com/2026/05/05/Windows%20%E8%BD%AF%E4%BB%B6</id><content type="html" xml:base="https://azh3ng.com/2026/05/05/Windows-%E8%BD%AF%E4%BB%B6.html"><![CDATA[<p>分享win使用的经验或软件</p>

<h1 id="效率软件">效率软件</h1>
<p>在用(好用)的软件</p>

<h2 id="anki"><a href="https://apps.ankiweb.net/">Anki</a></h2>
<p>主要用来背单词</p>

<h2 id="autohotkey"><a href="https://www.autohotkey.com/">AutoHotKey</a></h2>
<p>修改系统快捷键，或者绑定某些快捷键完成自动化操作</p>

<h3 id="capslockplus"><a href="https://azh3ng.com/2021/06/13/CapsLockPlus.html">CapsLockPlus</a></h3>
<p>我开源的一个 AutoHotKey 脚本，对键盘的 CapsLock 键增强，极大的提升了键盘效率。</p>

<h2 id="bandizip"><a href="https://www.bandisoft.com/bandizip/">Bandizip</a></h2>
<p>用起来最顺手的压缩软件</p>

<h2 id="everything"><a href="https://www.voidtools.com/zh-cn/">Everything</a></h2>
<p>全局文件搜索工具, 内存占用小, 搜索速度快。<br />
在功能上与 Listary 有重合</p>
<ul>
  <li>一般搜索文件我会使用 Listary 直接在资源管理器中输入就能找到目标文件.</li>
  <li>当要对某种命名规则(前缀/后缀等)的文件做批量处理时, 会用 Everything, 搜索到目标文件, 批量选中操作</li>
</ul>

<h2 id="nomacs"><a href="https://nomacs.org/">nomacs</a></h2>
<p>看图软件，启动速度快，支持常见图片格式，适合快速翻看文件夹里的图片<br />
比系统自带图片查看器更轻量，配合方向键连续浏览很顺手</p>

<h2 id="firefox"><a href="https://www.mozilla.org/firefox/">FireFox</a></h2>
<p>常用浏览器</p>

<h2 id="listary"><a href="https://www.listary.com/">Listary</a></h2>
<p>使用频率最高的软件<br />
全局文件搜索工具, 内存小速度快, 支持中文搜索, <a href="https://www.iplaysoft.com/">异次元</a> 的 <a href="https://www.iplaysoft.com/listary.html">这篇文章</a> 介绍的很详细<br />
我最常用的方式是把启动快捷键改为 <code class="language-plaintext highlighter-rouge">Ctrl + Space</code>，使用快捷键调起输入框，直接输入文件名，即可打开文件或者定位到文件夹</p>

<h2 id="geek-uninstaller"><a href="https://geekuninstaller.com/">Geek Uninstaller</a></h2>
<p>软件卸载工具，软件卸载后自动检测垃圾文件及注册表，然后一键清除</p>

<h2 id="notepad"><a href="https://notepad-plus-plus.org/">NotePad++</a></h2>
<p>NotePad++ 是一个开源的轻量级文本编辑器, 启动速度快, 内存占用小, 文本编辑功能强大, 支持代码高亮, 适合轻量的文本编辑场景.</p>

<h2 id="logseq"><a href="https://logseq.com/">Logseq</a></h2>
<p>本地优先的双向链接笔记软件，适合用大纲的方式整理笔记</p>

<h2 id="obsidian"><a href="https://obsidian.md/">Obsidian</a></h2>
<p>双向链接笔记软件</p>

<h2 id="openhashtab"><a href="https://github.com/namazso/OpenHashTab">OpenHashTab</a></h2>
<p>文件哈希校验工具，安装后会集成到文件属性页里<br />
需要校验 MD5、SHA-1、SHA-256 等哈希值时，不用单独打开软件，右键文件查看属性即可</p>

<h2 id="onenote"><a href="https://www.onenote.com/"><del>OneNote</del></a></h2>
<p><del>微软出品的笔记软件</del><br />
已经不使用，笔记主要迁移到 Obsidian</p>

<h2 id="1remote"><a href="https://github.com/1Remote/1Remote">1Remote</a></h2>
<p>远程连接管理工具，支持 RDP、SSH 等协议</p>

<h2 id="alt-tab-terminator"><a href="https://www.ntwind.com/software/alttabter.html">Alt-Tab Terminator</a></h2>
<p>替换系统自带的 Alt-Tab 窗口切换，窗口列表更直观</p>

<h2 id="windowsvirtualdesktophelper"><a href="https://github.com/dankrusi/WindowsVirtualDesktopHelper">WindowsVirtualDesktopHelper</a></h2>
<p>虚拟桌面增强工具，可以给不同虚拟桌面设置独立壁纸<br />
对于经常用多个桌面区分工作场景的情况会更顺手，也更容易一眼分辨当前所在桌面</p>

<h2 id="aquile-reader"><a href="https://apps.microsoft.com/detail/9p08t4jltqnk">Aquile Reader</a></h2>
<p>电子书阅读器，界面干净，适合在 Windows 上看 EPUB</p>

<h2 id="potplayer"><a href="https://www.potplayer.org">PotPlayer</a></h2>
<p>全能视频播放器, 启动速度快, 内存占用小, 配置项丰富, 功能齐全, 免费无广告</p>

<h2 id="quicklook"><a href="https://github.com/QL-Win/QuickLook">QuickLook</a></h2>
<p>文件管理器增强工具，按空格即可预览文件</p>

<h2 id="pixpin"><a href="https://pixpinapp.com/">PixPin</a></h2>
<p>最常用的截图软件，支持截图、贴图、长截图、OCR、标注等功能<br />
操作顺手，功能也更完整，日常截图和临时贴图都够用</p>

<h2 id="sumatra-pdf"><a href="https://www.sumatrapdfreader.org/">Sumatra PDF</a></h2>
<p>一款轻量级的 PDF 阅读器。除了支持 PDF 阅读，还支持 EPUB、MOBI 格式的电子书，以及 XPS、DjVu、CHM 等多种格式。<br />
内存占用小, 启动速度极快。Sumatra PDF 开发者表示「会优先让用户界面更干净」，足以说明它的开发方向。<br />
适合不需要太多复杂的功能，只想看 PDF 文档或电子书的场景</p>

<h2 id="syncthing"><a href="https://syncthing.net/">Syncthing</a></h2>
<p>开源文件同步工具，适合多台设备之间同步文件</p>

<h2 id="tailscale"><a href="https://tailscale.com/">tailscale</a></h2>
<p>基于 WireGuard 的组网工具，配置简单，适合远程访问自己的设备</p>

<h2 id="火绒"><a href="https://www.huorong.cn/">火绒</a></h2>
<p>系统安全工具</p>

<h2 id="v2rayn"><a href="https://github.com/2dust/v2rayN">V2rayN</a></h2>
<p>网络代理软件</p>

<h2 id="trafficmonitor"><a href="https://github.com/zhongyang219/TrafficMonitor">TrafficMonitor</a></h2>
<p>Traffic Monitor 是一款用于 Windows 平台的网速监控悬浮窗软件，可以显示当前网速、CPU及内存利用率，支持嵌入到任务栏显示，支持更换皮肤、历史流量统计等功能。</p>

<h2 id="chrome"><a href="https://www.google.com/intl/zh-CN/chrome/">chrome</a></h2>
<p>浏览器</p>

<p>下面是一些好用的 Chrome 插件</p>
<h3 id="adblock-plus---免费的广告拦截器"><a href="https://adblockplus.org/">Adblock Plus - 免费的广告拦截器</a></h3>
<p>免费网页广告拦截</p>
<h3 id="autopagerize"><a href="https://chromewebstore.google.com/detail/autopagerize/igiofjhpmpihnifddepnpngfjhkfenbp">AutoPagerize</a></h3>
<p>自动加载分页网页, 无需点击”下一页”</p>
<h3 id="awesome-screenshot-截图与录屏"><a href="https://www.awesomescreenshot.com/">Awesome Screenshot 截图与录屏</a></h3>
<p>截图和录屏2合1的工具，支持截取整个页面，快速分享屏幕。</p>
<h3 id="enhancer-for-youtube"><a href="https://www.mrfdev.com/enhancer-for-youtube">Enhancer for YouTube</a></h3>
<p>海量自定义功能提升您的 YouTube™ 用户体验。</p>
<h3 id="gitako---github-file-tree"><a href="https://github.com/EnixCoda/Gitako">Gitako - GitHub file tree</a></h3>
<p>树形展示 Github 项目结构<br />
替代[[Octotree]] - GitHub code tree</p>

<h3 id="noscript"><a href="https://noscript.net/">NoScript</a></h3>
<p>Maximum protection for your browser: NoScript allows active content only for trusted domains of your choice to prevent exploitation.</p>
<h3 id="onenote-web-clipper"><a href="https://www.onenote.com/clipper">OneNote Web Clipper</a></h3>
<p>支持将网页内容剪切到 OneNote 中</p>
<h3 id="onetab"><a href="https://www.one-tab.com/">OneTab</a></h3>
<p>一键将网页保存到后台列表, 等需要再打开, 节约内存</p>
<h3 id="screenity---屏幕录像和标注工具"><a href="https://screenity.io/">Screenity - 屏幕录像和标注工具</a></h3>
<p>屏幕录像工具</p>
<h3 id="selenium-ide"><a href="https://www.selenium.dev/selenium-ide/">Selenium IDE</a></h3>
<p>Selenium Record and Playback tool for ease of getting acquainted with Selenium WebDriver.</p>
<h3 id="vimium"><a href="https://vimium.github.io/">Vimium</a></h3>
<p>Chrome 的键盘增强插件, 仅通过键盘即可完成网页滚动/页面打开关闭/标签页切换等等操作</p>
<h3 id="彩云小译"><a href="https://fanyi.caiyunapp.com/">彩云小译</a></h3>
<p>彩云小译双语对照网页翻译插件，针对谷歌浏览器开发的一款网页翻译工具，一键高效获取母语阅读体验。<br />
网页整页对比翻译, 避免谷歌翻译那种只能看到译文不能看到原文的尴尬</p>
<h3 id="沙拉查词"><a href="https://saladict.crimx.com/">沙拉查词</a></h3>
<p>浏览器查词插件, 支持丰富的词典, 划词翻译等功能</p>
<h3 id="简悦"><a href="http://ksria.com/simpread/">简悦</a></h3>
<p>将文字类网页内容生成适合阅读的排版, 支持阅读样式的自定义, 支持对内容导出为各种文件格式, 及导出到各类笔记软件</p>
<h3 id="user-agent-switcher-for-chrome"><a href="https://chromewebstore.google.com/detail/user-agent-switcher-for-c/djflhoibgkdhkhhcedjiklpkjnoahfmg">User-Agent Switcher for Chrome</a></h3>
<p>修改访问网页时的User-Agent值, 可以伪装为其他浏览器</p>
<h3 id="tampermonkey"><a href="https://www.tampermonkey.net/">Tampermonkey</a></h3>
<p>Tampermonkey 是一款免费的浏览器扩展和最为流行的用户脚本管理器，它适用于 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。
可以对特定网页执行特定脚本, 以满足屏蔽广告, 转换网页样式, 网页增强等功能</p>
<ul>
  <li>知乎网页助手
  1、知乎站外链接直接跳转至目标网址；2、自动展开问题全部信息，同时展示所有回答；3、去除知乎网页中的广告；4、知乎网页中短视频下载；5、解除知乎复制限制-划词复制（鼠标左键划词自动添加到剪切板）；6、去除烦人的登录提示；7、自动高清图片显示【注：支持Tampermonke4.0以上版本】【这可能是功能最全面的知乎助手了】</li>
  <li>护眼脚本
  修改网页背景色，让网页背景色偏白的部分变成乡土黄、豆沙绿，浅色灰还有淡橄榄，更加护眼。默认护眼色是乡土黄。</li>
  <li>去繁留简-繁简转换
  将页面繁体字转换成简体字显示</li>
  <li>博客阅读美化（去广告|自动全文|简洁）
  优化特定博客网站的阅读体验: 修改页面布局, 屏蔽广告</li>
  <li>Bilibili Evolved
  哔哩哔哩增强脚本: 下载视频, 音乐, 封面, 弹幕 / 简化直播间, 评论区, 首页 / 自定义顶栏, 删除广告, 夜间模式 / 触屏设备支持</li>
  <li>AC-baidu:重定向优化百度搜狗谷歌必应搜索_favicon_双列
  绕过百度、搜狗、谷歌、好搜搜索結果中的自己的跳转连接，直接访问原始网页</li>
</ul>

<h1 id="开发向软件">开发向软件</h1>

<h2 id="git"><a href="https://git-scm.com/">Git</a></h2>
<p>版本管理工具</p>

<h2 id="windows-terminal"><a href="https://github.com/microsoft/terminal">Windows Terminal</a></h2>
<p>Windows Terminal 是微软开源的终端软件，美观且实用</p>

<h2 id="wsl2"><a href="https://learn.microsoft.com/windows/wsl/">WSL2</a></h2>
<p>WSL（Windows Subsystem for Linux）是可以运行在 Windows 上的 Linux 环境，省去了虚拟机的开销</p>

<h2 id="uv"><a href="https://github.com/astral-sh/uv">uv</a></h2>
<p>Python 包管理工具，速度很快，可以替代 pip、venv、pip-tools 等工具<br />
现在更倾向用 uv 管理 Python 项目环境，命令简单，依赖安装和虚拟环境创建都很快</p>

<h2 id="jetbrains-toolbox"><a href="https://www.jetbrains.com/toolbox-app/">JetBrains Toolbox</a></h2>

<h2 id="idea"><a href="https://www.jetbrains.com/idea/">IDEA</a></h2>
<p>Java 最强 IDE</p>

<p>下面是一些常用的插件</p>
<h3 id="ignore"><a href="https://plugins.jetbrains.com/plugin/7495--ignore">.ignore</a></h3>
<p>.ignore 文件插件</p>
<h3 id="camelcase"><a href="https://plugins.jetbrains.com/plugin/7160-camelcase">CamelCase</a></h3>
<p>在kebab-case，SNAKE_CASE，PascalCase，camelCase，snake_case或空格之间轻松切换。快捷键 <code class="language-plaintext highlighter-rouge">⇧+⌥+ U</code> 或 <code class="language-plaintext highlighter-rouge">Shift + Alt +U</code>。允许禁用某些转换或在首选项中更改其顺序。</p>
<h3 id="codeglance"><a href="https://plugins.jetbrains.com/plugin/7275-codeglance">CodeGlance</a></h3>
<p>以整个文档的微型图替代侧边滚动条</p>
<h3 id="gittoolbox"><a href="https://plugins.jetbrains.com/plugin/7499-gittoolbox">GitToolBox</a></h3>
<p>使用其他功能扩展Git集成状态显示，自动获取，内联责备注释，提交对话框完成，通知后等…</p>
<h3 id="lombok"><a href="https://plugins.jetbrains.com/plugin/6317-lombok">Lombok</a></h3>
<h3 id="markdown-image-kit"><a href="https://plugins.jetbrains.com/plugin/17080-markdown-image-kit">Markdown Image Kit</a></h3>
<p>Markdown Image Kit 是一款在 IDEA 中方便高效得管理 Markdown 文档图片的插件.</p>
<h3 id="mybatis-log"><a href="https://plugins.jetbrains.com/plugin/10065-mybatis-log">Mybatis Log</a></h3>
<p>将提取的SQL语句的动作日志记录切换到可执行的SQL语句。<br />
默认快捷键<code class="language-plaintext highlighter-rouge">ctrl + shift + H</code></p>
<h3 id="mybatiscodehelperpro"><a href="https://plugins.jetbrains.com/plugin/9837-mybatiscodehelperpro">MyBatisCodeHelperPro</a></h3>
<p>收费但功能强大的 MyBatis 插件</p>
<h3 id="restfultoolkit"><a href="https://plugins.jetbrains.com/plugin/10292-restfultoolkit">RestfulToolKit</a></h3>
<p>一套 RESTful 服务开发辅助工具集。</p>
<ol>
  <li>根据 URL 直接跳转到对应的方法定义 ( Ctrl \ or Ctrl Alt N );</li>
  <li>提供了一个 Services tree 的显示窗口;</li>
  <li>一个简单的 http 请求工具;</li>
  <li>在请求方法上添加了有用功能: 复制生成 URL;,复制方法参数…</li>
  <li>其他功能: java 类上添加 Convert to JSON 功能，格式化 json 数据 ( Windows: Ctrl + Enter; Mac: Command + Enter )。
    <h3 id="translation"><a href="https://plugins.jetbrains.com/plugin/8579-translation">Translation</a></h3>
    <p>翻译插件，快捷键<code class="language-plaintext highlighter-rouge">Ctrl + Shift + Y</code></p>
  </li>
</ol>

<h3 id="generateallsetter"><a href="https://plugins.jetbrains.com/plugin/9360-generateallsetter">generateAllSetter</a></h3>
<p>快捷生成对象的所有 setter 方法，方便编写测试代码</p>

<h2 id="navicat"><a href="https://www.navicat.com/"><del>Navicat</del></a></h2>
<p><del>数据库图形管理工具</del><br />
更多使用JetBrians 的 DataGrip</p>

<h2 id="visual-studio-code"><a href="https://code.visualstudio.com/">Visual Studio Code</a></h2>
<p>轻量级代码编辑工具</p>

<h2 id="xshell"><a href="https://www.netsarang.com/en/xshell/">Xshell</a></h2>
<p>ssh 工具</p>

<h2 id="mobaxterm"><a href="https://mobaxterm.mobatek.net/">MobaXTerm</a></h2>
<p>免费 ssh 工具</p>]]></content><author><name>azh3ng</name></author><category term="Windows" /><category term="software" /><summary type="html"><![CDATA[分享win使用的经验或软件 效率软件 在用(好用)的软件 Anki 主要用来背单词 AutoHotKey 修改系统快捷键，或者绑定某些快捷键完成自动化操作 CapsLockPlus 我开源的一个 AutoHotKey 脚本，对键盘的 CapsLock 键增强，极大的提升了键盘效率。 Bandizip 用起来最顺手的压缩软件 Everything 全局文件搜索工具, 内存占用小, 搜索速度快。 在功能上与 Listary 有重合 一般搜索文件我会使用 Listary 直接在资源管理器中输入就能找到目标文件. 当要对某种命名规则(前缀/后缀等)的文件做批量处理时, 会用 Everything, 搜索到目标文件, 批量选中操作 nomacs 看图软件，启动速度快，支持常见图片格式，适合快速翻看文件夹里的图片 比系统自带图片查看器更轻量，配合方向键连续浏览很顺手 FireFox 常用浏览器 Listary 使用频率最高的软件 全局文件搜索工具, 内存小速度快, 支持中文搜索, 异次元 的 这篇文章 介绍的很详细 我最常用的方式是把启动快捷键改为 Ctrl + Space，使用快捷键调起输入框，直接输入文件名，即可打开文件或者定位到文件夹 Geek Uninstaller 软件卸载工具，软件卸载后自动检测垃圾文件及注册表，然后一键清除 NotePad++ NotePad++ 是一个开源的轻量级文本编辑器, 启动速度快, 内存占用小, 文本编辑功能强大, 支持代码高亮, 适合轻量的文本编辑场景. Logseq 本地优先的双向链接笔记软件，适合用大纲的方式整理笔记 Obsidian 双向链接笔记软件 OpenHashTab 文件哈希校验工具，安装后会集成到文件属性页里 需要校验 MD5、SHA-1、SHA-256 等哈希值时，不用单独打开软件，右键文件查看属性即可 OneNote 微软出品的笔记软件 已经不使用，笔记主要迁移到 Obsidian 1Remote 远程连接管理工具，支持 RDP、SSH 等协议 Alt-Tab Terminator 替换系统自带的 Alt-Tab 窗口切换，窗口列表更直观 WindowsVirtualDesktopHelper 虚拟桌面增强工具，可以给不同虚拟桌面设置独立壁纸 对于经常用多个桌面区分工作场景的情况会更顺手，也更容易一眼分辨当前所在桌面 Aquile Reader 电子书阅读器，界面干净，适合在 Windows 上看 EPUB PotPlayer 全能视频播放器, 启动速度快, 内存占用小, 配置项丰富, 功能齐全, 免费无广告 QuickLook 文件管理器增强工具，按空格即可预览文件 PixPin 最常用的截图软件，支持截图、贴图、长截图、OCR、标注等功能 操作顺手，功能也更完整，日常截图和临时贴图都够用 Sumatra PDF 一款轻量级的 PDF 阅读器。除了支持 PDF 阅读，还支持 EPUB、MOBI 格式的电子书，以及 XPS、DjVu、CHM 等多种格式。 内存占用小, 启动速度极快。Sumatra PDF 开发者表示「会优先让用户界面更干净」，足以说明它的开发方向。 适合不需要太多复杂的功能，只想看 PDF 文档或电子书的场景 Syncthing 开源文件同步工具，适合多台设备之间同步文件 tailscale 基于 WireGuard 的组网工具，配置简单，适合远程访问自己的设备 火绒 系统安全工具 V2rayN 网络代理软件 TrafficMonitor Traffic Monitor 是一款用于 Windows 平台的网速监控悬浮窗软件，可以显示当前网速、CPU及内存利用率，支持嵌入到任务栏显示，支持更换皮肤、历史流量统计等功能。 chrome 浏览器 下面是一些好用的 Chrome 插件 Adblock Plus - 免费的广告拦截器 免费网页广告拦截 AutoPagerize 自动加载分页网页, 无需点击”下一页” Awesome Screenshot 截图与录屏 截图和录屏2合1的工具，支持截取整个页面，快速分享屏幕。 Enhancer for YouTube 海量自定义功能提升您的 YouTube™ 用户体验。 Gitako - GitHub file tree 树形展示 Github 项目结构 替代[[Octotree]] - GitHub code tree NoScript Maximum protection for your browser: NoScript allows active content only for trusted domains of your choice to prevent exploitation. OneNote Web Clipper 支持将网页内容剪切到 OneNote 中 OneTab 一键将网页保存到后台列表, 等需要再打开, 节约内存 Screenity - 屏幕录像和标注工具 屏幕录像工具 Selenium IDE Selenium Record and Playback tool for ease of getting acquainted with Selenium WebDriver. Vimium Chrome 的键盘增强插件, 仅通过键盘即可完成网页滚动/页面打开关闭/标签页切换等等操作 彩云小译 彩云小译双语对照网页翻译插件，针对谷歌浏览器开发的一款网页翻译工具，一键高效获取母语阅读体验。 网页整页对比翻译, 避免谷歌翻译那种只能看到译文不能看到原文的尴尬 沙拉查词 浏览器查词插件, 支持丰富的词典, 划词翻译等功能 简悦 将文字类网页内容生成适合阅读的排版, 支持阅读样式的自定义, 支持对内容导出为各种文件格式, 及导出到各类笔记软件 User-Agent Switcher for Chrome 修改访问网页时的User-Agent值, 可以伪装为其他浏览器 Tampermonkey Tampermonkey 是一款免费的浏览器扩展和最为流行的用户脚本管理器，它适用于 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。 可以对特定网页执行特定脚本, 以满足屏蔽广告, 转换网页样式, 网页增强等功能 知乎网页助手 1、知乎站外链接直接跳转至目标网址；2、自动展开问题全部信息，同时展示所有回答；3、去除知乎网页中的广告；4、知乎网页中短视频下载；5、解除知乎复制限制-划词复制（鼠标左键划词自动添加到剪切板）；6、去除烦人的登录提示；7、自动高清图片显示【注：支持Tampermonke4.0以上版本】【这可能是功能最全面的知乎助手了】 护眼脚本 修改网页背景色，让网页背景色偏白的部分变成乡土黄、豆沙绿，浅色灰还有淡橄榄，更加护眼。默认护眼色是乡土黄。 去繁留简-繁简转换 将页面繁体字转换成简体字显示 博客阅读美化（去广告|自动全文|简洁） 优化特定博客网站的阅读体验: 修改页面布局, 屏蔽广告 Bilibili Evolved 哔哩哔哩增强脚本: 下载视频, 音乐, 封面, 弹幕 / 简化直播间, 评论区, 首页 / 自定义顶栏, 删除广告, 夜间模式 / 触屏设备支持 AC-baidu:重定向优化百度搜狗谷歌必应搜索_favicon_双列 绕过百度、搜狗、谷歌、好搜搜索結果中的自己的跳转连接，直接访问原始网页 开发向软件 Git 版本管理工具 Windows Terminal Windows Terminal 是微软开源的终端软件，美观且实用 WSL2 WSL（Windows Subsystem for Linux）是可以运行在 Windows 上的 Linux 环境，省去了虚拟机的开销 uv Python 包管理工具，速度很快，可以替代 pip、venv、pip-tools 等工具 现在更倾向用 uv 管理 Python 项目环境，命令简单，依赖安装和虚拟环境创建都很快 JetBrains Toolbox IDEA Java 最强 IDE 下面是一些常用的插件 .ignore .ignore 文件插件 CamelCase 在kebab-case，SNAKE_CASE，PascalCase，camelCase，snake_case或空格之间轻松切换。快捷键 ⇧+⌥+ U 或 Shift + Alt +U。允许禁用某些转换或在首选项中更改其顺序。 CodeGlance 以整个文档的微型图替代侧边滚动条 GitToolBox 使用其他功能扩展Git集成状态显示，自动获取，内联责备注释，提交对话框完成，通知后等… Lombok Markdown Image Kit Markdown Image Kit 是一款在 IDEA 中方便高效得管理 Markdown 文档图片的插件. Mybatis Log 将提取的SQL语句的动作日志记录切换到可执行的SQL语句。 默认快捷键ctrl + shift + H MyBatisCodeHelperPro 收费但功能强大的 MyBatis 插件 RestfulToolKit 一套 RESTful 服务开发辅助工具集。 根据 URL 直接跳转到对应的方法定义 ( Ctrl \ or Ctrl Alt N ); 提供了一个 Services tree 的显示窗口; 一个简单的 http 请求工具; 在请求方法上添加了有用功能: 复制生成 URL;,复制方法参数… 其他功能: java 类上添加 Convert to JSON 功能，格式化 json 数据 ( Windows: Ctrl + Enter; Mac: Command + Enter )。 Translation 翻译插件，快捷键Ctrl + Shift + Y generateAllSetter 快捷生成对象的所有 setter 方法，方便编写测试代码 Navicat 数据库图形管理工具 更多使用JetBrians 的 DataGrip Visual Studio Code 轻量级代码编辑工具 Xshell ssh 工具 MobaXTerm 免费 ssh 工具]]></summary></entry><entry><title type="html">订阅 Google AI Pro 会员教程</title><link href="https://azh3ng.com/2025/12/27/subscribe-google-ai-pro.html" rel="alternate" type="text/html" title="订阅 Google AI Pro 会员教程" /><published>2025-12-27T00:00:00+08:00</published><updated>2025-12-27T00:00:00+08:00</updated><id>https://azh3ng.com/2025/12/27/subscribe-google-ai-pro</id><content type="html" xml:base="https://azh3ng.com/2025/12/27/subscribe-google-ai-pro.html"><![CDATA[<p>近期 Google 在 X 平台（原 Twitter）的 <a href="https://x.com/GeminiApp/status/2003573036818342260?s=20">@GeminiApp</a> 官方账号宣布了一项限时优惠活动：Google AI Pro 年度计划享受 50% 折扣，年费仅需 $99.99（原价 $199.99）。</p>

<p>记录一下在国内环境下成功订阅 Google AI Pro 年度会员的完整流程。</p>

<h2 id="遇到的问题">遇到的问题</h2>

<p>最初尝试使用美区 Google 账户直接在网页端购买，但在添加支付方式时遇到了问题：<strong>添加国内招商银行 Visa 卡失败</strong>。</p>

<p>后偶然发现可以通过 App Store 进行支付，从而绕过直接绑定信用卡的限制。</p>

<h2 id="解决方案">解决方案</h2>

<p>通过 Apple 生态系统完成订阅，具体流程如下：</p>

<h3 id="1-准备美区-apple-账号余额">1. 准备美区 Apple 账号余额</h3>

<p>由于需要在美区 App Store 进行支付，首先需要为美区 Apple 账号充值。</p>

<p>访问 <a href="https://www.apple.com/">Apple 官网</a>购买 <strong>Apple ID 礼品卡</strong>（Gift Card），购买了 $100 的礼品卡，账单信息填写免税州。</p>

<p>购买成功后，使用礼品卡兑换码在美区 Apple 账号中充值。</p>

<blockquote>
  <p>参考教程：<a href="https://zhuanlan.zhihu.com/p/476434200">如何购买和使用美区 Apple ID 礼品卡</a></p>
</blockquote>

<h3 id="2-下载-google-one-app">2. 下载 Google One APP</h3>

<p>使用美区 Apple 账号登录 App Store，搜索并下载 <strong>Google One</strong> 应用。</p>

<p>注意：不是 Gemini APP，而是 Google One APP，Google AI Pro 订阅服务包含在 Google One 会员体系中。</p>

<h3 id="3-订阅-google-ai-pro">3. 订阅 Google AI Pro</h3>

<ol>
  <li>打开 Google One APP，登录 Google 账号</li>
  <li>在 APP 中找到 <strong>Google AI Pro</strong> 订阅选项</li>
  <li>选择年度订阅计划，此时可以看到折扣价格：<strong>$99.99/year</strong></li>
  <li>点击订阅，支付方式会默认使用 <strong>App Store 付款</strong>（之前充值的 Apple 账号余额）</li>
  <li>确认支付，完成订阅</li>
</ol>

<h3 id="4-验证订阅状态">4. 验证订阅状态</h3>

<p>订阅成功后，重新打开 <strong>Gemini</strong> 网页版或 APP，看到账户已经升级为 <strong>Pro 账户</strong>，正常 Gemini 3、Nano Banana Pro 等。</p>

<h2 id="注意事项">注意事项</h2>

<ol>
  <li><strong>地区限制</strong>：Google AI Pro 服务需要在支持的地区使用，建议配合网络工具使用</li>
  <li><strong>Apple 账号</strong>：需要提前准备好美区 Apple 账号，如果没有可以自行注册</li>
  <li><strong>汇率与税费</strong>：实际支付金额可能因汇率和税费略有差异，建议礼品卡充值时多准备一些余额</li>
  <li><strong>订阅管理</strong>：订阅后可以在 App Store 的订阅管理中查看和管理 Google AI Pro 订阅</li>
  <li><strong>续费提醒</strong>：年度订阅到期后会自动续费，如需取消请提前在 App Store 中关闭自动续费</li>
</ol>]]></content><author><name>azh3ng</name></author><category term="Google" /><category term="AI" /><category term="Gemini" /><category term="订阅" /><summary type="html"><![CDATA[近期 Google 在 X 平台（原 Twitter）的 @GeminiApp 官方账号宣布了一项限时优惠活动：Google AI Pro 年度计划享受 50% 折扣，年费仅需 $99.99（原价 $199.99）。 记录一下在国内环境下成功订阅 Google AI Pro 年度会员的完整流程。 遇到的问题 最初尝试使用美区 Google 账户直接在网页端购买，但在添加支付方式时遇到了问题：添加国内招商银行 Visa 卡失败。 后偶然发现可以通过 App Store 进行支付，从而绕过直接绑定信用卡的限制。 解决方案 通过 Apple 生态系统完成订阅，具体流程如下： 1. 准备美区 Apple 账号余额 由于需要在美区 App Store 进行支付，首先需要为美区 Apple 账号充值。 访问 Apple 官网购买 Apple ID 礼品卡（Gift Card），购买了 $100 的礼品卡，账单信息填写免税州。 购买成功后，使用礼品卡兑换码在美区 Apple 账号中充值。 参考教程：如何购买和使用美区 Apple ID 礼品卡 2. 下载 Google One APP 使用美区 Apple 账号登录 App Store，搜索并下载 Google One 应用。 注意：不是 Gemini APP，而是 Google One APP，Google AI Pro 订阅服务包含在 Google One 会员体系中。 3. 订阅 Google AI Pro 打开 Google One APP，登录 Google 账号 在 APP 中找到 Google AI Pro 订阅选项 选择年度订阅计划，此时可以看到折扣价格：$99.99/year 点击订阅，支付方式会默认使用 App Store 付款（之前充值的 Apple 账号余额） 确认支付，完成订阅 4. 验证订阅状态 订阅成功后，重新打开 Gemini 网页版或 APP，看到账户已经升级为 Pro 账户，正常 Gemini 3、Nano Banana Pro 等。 注意事项 地区限制：Google AI Pro 服务需要在支持的地区使用，建议配合网络工具使用 Apple 账号：需要提前准备好美区 Apple 账号，如果没有可以自行注册 汇率与税费：实际支付金额可能因汇率和税费略有差异，建议礼品卡充值时多准备一些余额 订阅管理：订阅后可以在 App Store 的订阅管理中查看和管理 Google AI Pro 订阅 续费提醒：年度订阅到期后会自动续费，如需取消请提前在 App Store 中关闭自动续费]]></summary></entry><entry><title type="html">Proxmox VE 9.1.1 屏蔽订阅弹窗提示</title><link href="https://azh3ng.com/2025/12/17/Proxmox-VE-disable-subscription-notice.html" rel="alternate" type="text/html" title="Proxmox VE 9.1.1 屏蔽订阅弹窗提示" /><published>2025-12-17T00:00:00+08:00</published><updated>2025-12-17T00:00:00+08:00</updated><id>https://azh3ng.com/2025/12/17/Proxmox-VE-disable-subscription-notice</id><content type="html" xml:base="https://azh3ng.com/2025/12/17/Proxmox-VE-disable-subscription-notice.html"><![CDATA[<p>Proxmox VE (PVE) 是一款优秀的开源虚拟化管理平台，基于 Debian 并集成了 KVM 和 LXC 虚拟化技术。然而，在使用社区版时，每次登录 Web 管理界面都会弹出订阅提示窗口，虽然不影响功能使用，但频繁出现确实会影响使用体验。</p>

<p>本文将介绍如何在 Proxmox VE 9.1.1 版本中屏蔽这个登录后的订阅提示弹窗。</p>

<h2 id="操作步骤">操作步骤</h2>

<h3 id="1-备份原始文件">1. 备份原始文件</h3>

<p>在进行任何修改之前，强烈建议先备份原始的 JavaScript 文件，以便在出现问题时能够快速恢复：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> /usr/share/pve-manager/js/pvemanagerlib.js <span class="se">\</span>
   /usr/share/pve-manager/js/pvemanagerlib.js.bak
</code></pre></div></div>

<p>这条命令会在同目录下创建一个 <code class="language-plaintext highlighter-rouge">.bak</code> 后缀的备份文件。</p>

<h3 id="2-修改订阅检查代码">2. 修改订阅检查代码</h3>

<p>使用 <code class="language-plaintext highlighter-rouge">sed</code> 命令直接修改 JavaScript 文件中的订阅检查逻辑：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sed</span> <span class="nt">-i</span> <span class="se">\</span>
<span class="s1">'s/Proxmox.Utils.checked_command(Ext.emptyFn);/Ext.emptyFn();\/\* subscription check disabled \*\//g'</span> <span class="se">\</span>
/usr/share/pve-manager/js/pvemanagerlib.js
</code></pre></div></div>

<p>这条命令的作用是将订阅检查函数替换为一个空函数，从而跳过弹窗提示。修改后的代码会添加注释 <code class="language-plaintext highlighter-rouge">/* subscription check disabled */</code> 以便识别。</p>

<h3 id="3-重启-web-服务">3. 重启 Web 服务</h3>

<p>修改完成后，需要重启 PVE 的 Web 代理服务使更改生效：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart pveproxy
</code></pre></div></div>

<p>等待服务重启完成后，清除浏览器缓存（或使用 Ctrl+F5 强制刷新），重新登录 Proxmox VE 管理界面，订阅提示窗口将不再出现。</p>

<h2 id="注意事项">注意事项</h2>

<ol>
  <li>
    <p><strong>系统更新影响</strong>：当 Proxmox VE 进行系统更新时，<code class="language-plaintext highlighter-rouge">pve-manager</code> 包可能会被更新，导致修改被覆盖。此时需要重新执行上述步骤。</p>
  </li>
  <li>
    <p><strong>版本兼容性</strong>：本方法适用于 Proxmox VE 9.1.1 版本，其他版本的文件路径或代码结构可能有所不同，请根据实际情况调整。</p>
  </li>
  <li>
    <p><strong>官方订阅支持</strong>：如果你在生产环境中使用 Proxmox VE，建议购买官方订阅以获得企业级支持和稳定的更新源。</p>
  </li>
  <li>
    <p><strong>恢复方法</strong>：如果修改后出现异常，可以使用以下命令恢复原始文件：</p>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> /usr/share/pve-manager/js/pvemanagerlib.js.bak <span class="se">\</span>
   /usr/share/pve-manager/js/pvemanagerlib.js
systemctl restart pveproxy
</code></pre></div>    </div>
  </li>
</ol>]]></content><author><name>azh3ng</name></author><category term="PVE" /><category term="Proxmox" /><category term="Linux" /><category term="虚拟化" /><summary type="html"><![CDATA[Proxmox VE (PVE) 是一款优秀的开源虚拟化管理平台，基于 Debian 并集成了 KVM 和 LXC 虚拟化技术。然而，在使用社区版时，每次登录 Web 管理界面都会弹出订阅提示窗口，虽然不影响功能使用，但频繁出现确实会影响使用体验。 本文将介绍如何在 Proxmox VE 9.1.1 版本中屏蔽这个登录后的订阅提示弹窗。 操作步骤 1. 备份原始文件 在进行任何修改之前，强烈建议先备份原始的 JavaScript 文件，以便在出现问题时能够快速恢复： cp /usr/share/pve-manager/js/pvemanagerlib.js \ /usr/share/pve-manager/js/pvemanagerlib.js.bak 这条命令会在同目录下创建一个 .bak 后缀的备份文件。 2. 修改订阅检查代码 使用 sed 命令直接修改 JavaScript 文件中的订阅检查逻辑： sed -i \ 's/Proxmox.Utils.checked_command(Ext.emptyFn);/Ext.emptyFn();\/\* subscription check disabled \*\//g' \ /usr/share/pve-manager/js/pvemanagerlib.js 这条命令的作用是将订阅检查函数替换为一个空函数，从而跳过弹窗提示。修改后的代码会添加注释 /* subscription check disabled */ 以便识别。 3. 重启 Web 服务 修改完成后，需要重启 PVE 的 Web 代理服务使更改生效： systemctl restart pveproxy 等待服务重启完成后，清除浏览器缓存（或使用 Ctrl+F5 强制刷新），重新登录 Proxmox VE 管理界面，订阅提示窗口将不再出现。 注意事项 系统更新影响：当 Proxmox VE 进行系统更新时，pve-manager 包可能会被更新，导致修改被覆盖。此时需要重新执行上述步骤。 版本兼容性：本方法适用于 Proxmox VE 9.1.1 版本，其他版本的文件路径或代码结构可能有所不同，请根据实际情况调整。 官方订阅支持：如果你在生产环境中使用 Proxmox VE，建议购买官方订阅以获得企业级支持和稳定的更新源。 恢复方法：如果修改后出现异常，可以使用以下命令恢复原始文件： cp /usr/share/pve-manager/js/pvemanagerlib.js.bak \ /usr/share/pve-manager/js/pvemanagerlib.js systemctl restart pveproxy]]></summary></entry><entry><title type="html">kiss-translator(简约翻译)配置 Gemini AI Studio</title><link href="https://azh3ng.com/2025/08/12/kiss-translator.html" rel="alternate" type="text/html" title="kiss-translator(简约翻译)配置 Gemini AI Studio" /><published>2025-08-12T00:00:00+08:00</published><updated>2025-08-12T00:00:00+08:00</updated><id>https://azh3ng.com/2025/08/12/kiss-translator</id><content type="html" xml:base="https://azh3ng.com/2025/08/12/kiss-translator.html"><![CDATA[<p>由于<a href="https://immersivetranslate.com/zh-Hans/">沉浸式翻译</a> 最近出现了许多负面消息，于是尝试寻找替代品。</p>

<p>看到有推荐 <a href="https://github.com/fishjar/kiss-translator">kiss-translator</a> 于是尝试使用。</p>

<p>由于默认的 Gemini 配置与 Gemini AI Studio 不兼容，于是摸索在自定义接口中配置。</p>

<p>在【接口设置】中，点开一个Custom，接口名称填一个易于分别的名字。</p>

<p>URL填写：https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent</p>

<p>如果是自搭建的 GeminiBalance，填写 http://ip:port/v1beta/models/gemini-2.5-flash-lite:generateContent</p>

<p>模型这里选择的是 gemini-2.5-flash-lite，翻译效果足够，速度快，不容易触发请求限制。</p>

<p>Key填写自己的 API Key</p>

<p>重点是 Request Hook 和 Response Hook</p>

<p>Request Hook：</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nx">text</span><span class="p">,</span> <span class="k">from</span><span class="p">,</span> <span class="nx">to</span><span class="p">,</span> <span class="nx">url</span><span class="p">,</span> <span class="nx">key</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">[</span>
  <span class="nx">url</span><span class="p">,</span>
  <span class="p">{</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="dl">"</span><span class="s2">Content-type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">x-goog-api-key</span><span class="dl">"</span><span class="p">:</span> <span class="nx">key</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span>
      <span class="na">contents</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span>
          <span class="na">parts</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span>
              <span class="na">text</span><span class="p">:</span> <span class="s2">`Translate the following source text from </span><span class="p">${</span><span class="k">from</span><span class="p">}</span><span class="s2"> to </span><span class="p">${</span><span class="nx">to</span><span class="p">}</span><span class="s2">.
              Output translation directly without any additional text.
              Source Text: </span><span class="p">${</span><span class="nx">text</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
            <span class="p">}</span>
          <span class="p">]</span>
        <span class="p">}</span>
      <span class="p">]</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">];</span>
</code></pre></div></div>
<p>Response Hook:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nx">res</span><span class="p">,</span> <span class="nx">text</span><span class="p">,</span> <span class="k">from</span><span class="p">,</span> <span class="nx">to</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">res</span><span class="p">[</span><span class="dl">'</span><span class="s1">candidates</span><span class="dl">'</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="dl">'</span><span class="s1">content</span><span class="dl">'</span><span class="p">][</span><span class="dl">'</span><span class="s1">parts</span><span class="dl">'</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="dl">'</span><span class="s1">text</span><span class="dl">'</span><span class="p">],</span> <span class="nx">to</span> <span class="o">===</span> <span class="nx">res</span><span class="p">.</span><span class="nx">src</span><span class="p">]</span>
</code></pre></div></div>
<p>点击【点击测试】按钮，提示测试成功表示配置正确</p>]]></content><author><name>azh3ng</name></author><category term="Windows" /><category term="software" /><category term="浏览器插件" /><summary type="html"><![CDATA[由于沉浸式翻译 最近出现了许多负面消息，于是尝试寻找替代品。 看到有推荐 kiss-translator 于是尝试使用。 由于默认的 Gemini 配置与 Gemini AI Studio 不兼容，于是摸索在自定义接口中配置。 在【接口设置】中，点开一个Custom，接口名称填一个易于分别的名字。 URL填写：https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent 如果是自搭建的 GeminiBalance，填写 http://ip:port/v1beta/models/gemini-2.5-flash-lite:generateContent 模型这里选择的是 gemini-2.5-flash-lite，翻译效果足够，速度快，不容易触发请求限制。 Key填写自己的 API Key 重点是 Request Hook 和 Response Hook Request Hook： (text, from, to, url, key) =&gt; [ url, { headers: { "Content-type": "application/json", "x-goog-api-key": key, }, method: "POST", body: JSON.stringify({ contents: [ { parts: [ { text: `Translate the following source text from ${from} to ${to}. Output translation directly without any additional text. Source Text: ${text}`, } ] } ] }) } ]; Response Hook: (res, text, from, to) =&gt; [res['candidates'][0]['content']['parts'][0]['text'], to === res.src] 点击【点击测试】按钮，提示测试成功表示配置正确]]></summary></entry><entry><title type="html">【理解Spring】系列前言</title><link href="https://azh3ng.com/2022/02/02/Spring-introduction.html" rel="alternate" type="text/html" title="【理解Spring】系列前言" /><published>2022-02-02T00:00:00+08:00</published><updated>2022-02-02T00:00:00+08:00</updated><id>https://azh3ng.com/2022/02/02/Spring-introduction</id><content type="html" xml:base="https://azh3ng.com/2022/02/02/Spring-introduction.html"><![CDATA[<p>在开发中，Spring 是我使用最多的框架，故而对它进行更深入的学习，可以更快的实现想要的功能，同时写出更优雅的代码。<br />
基于 <a href="https://github.com/spring-projects/spring-framework/tree/v5.3.10">Spring Framework 5.3.10-SNAPSHOT</a> 学习，后续博文内容对应此版本</p>

<h2 id="核心原理">核心原理</h2>
<ul>
  <li>[[Spring核心接口]]
    <ul>
      <li><a href="/2022/01/02/Spring-BeanDefinition.html">BeanDefinition</a></li>
      <li><a href="/2022/01/08/Spring-AbstractBeanDefinition.html">抽象 BeanDefinition</a></li>
      <li><a href="/2022/01/03/Spring-BeanFactory.html">BeanFactory</a></li>
      <li><a href="/2022/01/04/Spring-ApplicationContext.html">ApplicationContext</a></li>
      <li><a href="/2022/01/05/Spring-BeanPostProcessor.html">BeanPostProcessor</a></li>
      <li><a href="/2022/01/05/Spring-FactoryBean.html">FactoryBean</a></li>
    </ul>
  </li>
  <li><a href="/2022/01/06/Spring-startup.html">Spring启动</a>
    <ul>
      <li><a href="/2022/01/07/Spring-scan.html">Spring扫描</a></li>
      <li><a href="/2022/01/08/Spring-initializes-non-lazy-singleton-beans.html">Spring初始化所有非懒加载单例Bean</a>
        <ul>
          <li><a href="/2022/01/08/Spring-merge-BeanDefinition.html">合并 BeanDefinition</a></li>
          <li><a href="/2022/01/10/Spring-BeanFactory-getBean.html">BeanFactory.getBean()</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="/2022/01/12/Spring-Bean-lifecycle.html">Bean生命周期</a></li>
  <li><a href="/2022/01/13/Spring-infer-constructor.html">Spring推断构造方法</a></li>
  <li><a href="/2022/01/14/Spring-Dependency-Injection.html">依赖注入</a></li>
  <li><a href="/2022/01/15/Spring-resolve-circular-dependencies.html">解决循环依赖</a></li>
  <li><a href="/2022/01/16/Spring-AOP.html">Spring AOP</a>
    <ul>
      <li><a href="/2022/01/16/AOP.html">AOP</a></li>
      <li><a href="/2022/01/16/Proxy-Pattern.html">代理模式</a></li>
      <li><a href="/2022/01/16/JDK-Proxy.html">JDK动态代理</a></li>
      <li><a href="/2022/01/16/CGLIB-Proxy.html">CGLIB动态代理</a></li>
      <li><a href="/2022/01/16/ProxyFactory.html">ProxyFactory</a></li>
      <li><a href="/2022/01/16/TargetSource.html">TargetSource</a></li>
    </ul>
  </li>
  <li><a href="/2022/01/17/Spring-Transaction.html">Spring事务</a></li>
</ul>

<h2 id="实战相关">实战相关</h2>
<ul>
  <li><a href="/2022/01/30/Spring-use-of-@Qualifier.html">@Qualifier使用</a></li>
  <li><a href="/2022/02/01/Spring-dynamic-getBean.html">Spring动态获取Bean</a></li>
</ul>]]></content><author><name>azh3ng</name></author><category term="Spring" /><summary type="html"><![CDATA[在开发中，Spring 是我使用最多的框架，故而对它进行更深入的学习，可以更快的实现想要的功能，同时写出更优雅的代码。 基于 Spring Framework 5.3.10-SNAPSHOT 学习，后续博文内容对应此版本 核心原理 [[Spring核心接口]] BeanDefinition 抽象 BeanDefinition BeanFactory ApplicationContext BeanPostProcessor FactoryBean Spring启动 Spring扫描 Spring初始化所有非懒加载单例Bean 合并 BeanDefinition BeanFactory.getBean() Bean生命周期 Spring推断构造方法 依赖注入 解决循环依赖 Spring AOP AOP 代理模式 JDK动态代理 CGLIB动态代理 ProxyFactory TargetSource Spring事务 实战相关 @Qualifier使用 Spring动态获取Bean]]></summary></entry><entry><title type="html">【Spring实战】动态获取Bean</title><link href="https://azh3ng.com/2022/02/01/Spring-dynamic-getBean.html" rel="alternate" type="text/html" title="【Spring实战】动态获取Bean" /><published>2022-02-01T00:00:00+08:00</published><updated>2022-02-01T00:00:00+08:00</updated><id>https://azh3ng.com/2022/02/01/Spring-dynamic-getBean</id><content type="html" xml:base="https://azh3ng.com/2022/02/01/Spring-dynamic-getBean.html"><![CDATA[<p>在开发中，常常会定义接口，后对接口进行实现。实现的方式可能有一种，也可能有多种，在不同的场景，需要调用不同的实现。</p>

<p>例如</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Azh3ngService</span> <span class="o">{</span>
    <span class="kt">void</span> <span class="nf">foo</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AaAzh3ngServiceImpl</span> <span class="kd">implements</span> <span class="nc">Azh3ngService</span> <span class="o">{</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">foo</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"aa"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BbAzh3ngServiceImpl</span> <span class="kd">implements</span> <span class="nc">Azh3ngService</span> <span class="o">{</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">foo</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"bb"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在实际业务中，往往根据某些参数区分调用实现。<br />
在 Spring 中可以使用以下几种方式完成。</p>

<h2 id="ifelse方式">if/else方式</h2>
<p>假设需要根据参数<code class="language-plaintext highlighter-rouge">code</code>区分，调用<code class="language-plaintext highlighter-rouge">Aa/BbAzh3ngServiceImpl</code>的具体实现
简单做法是</p>
<ol>
  <li>直接注入两种不同的实现的 Bean</li>
  <li>使用<code class="language-plaintext highlighter-rouge">if{}else{}</code>区分调用具体 Bean
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Azh3ngController</span> <span class="o">{</span>
    
 <span class="nd">@Resource</span> <span class="kd">private</span> <span class="nc">Azh3ngService</span> <span class="n">aaAzh3ngServiceImpl</span><span class="o">;</span>
 <span class="nd">@Resource</span> <span class="kd">private</span> <span class="nc">Azh3ngService</span> <span class="n">bbAzh3ngServiceImpl</span><span class="o">;</span>
    
 <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">(</span><span class="nc">String</span> <span class="n">code</span><span class="o">)</span> <span class="o">{</span>
     <span class="k">if</span> <span class="o">(</span><span class="s">"a"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">code</span><span class="o">))</span> <span class="o">{</span>
         <span class="k">return</span> <span class="n">aaAzh3ngServiceImpl</span><span class="o">.</span><span class="na">foo</span><span class="o">();</span>
     <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="s">"b"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">code</span><span class="o">))</span> <span class="o">{</span>
         <span class="k">return</span> <span class="n">bbAzh3ngServiceImpl</span><span class="o">.</span><span class="na">foo</span><span class="o">();</span>
     <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
         <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"System Exception"</span><span class="o">);</span>
     <span class="o">}</span>
 <span class="o">}</span>
<span class="o">}</span>
</code></pre></div>    </div>
    <p>这种方式简单粗暴，但是缺点也非常明显，假如新增了一个实现类，所有用这种方式使用 Azh3ngService 的代码都需要修改。</p>
  </li>
</ol>

<h2 id="使用map接收spring的自动注入">使用Map接收Spring的自动注入</h2>
<p>Spring 在自动注入时，会判断注入的对象是否为 <code class="language-plaintext highlighter-rouge">Collection</code> 或 <code class="language-plaintext highlighter-rouge">Map</code>，如果是，会继续判断其指定的泛型类型，进而找出所有匹配类型的 Bean 注入 <code class="language-plaintext highlighter-rouge">Collection</code> 或 <code class="language-plaintext highlighter-rouge">Map</code> 中。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Azh3ngController</span> <span class="o">{</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Azh3ngService</span><span class="o">&gt;</span> <span class="n">azh3ngServiceMap</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">(</span><span class="nc">String</span> <span class="n">code</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Azh3ngService</span> <span class="n">azh3ngService</span> <span class="o">=</span> <span class="n">azh3ngServiceMap</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">code</span> <span class="o">+</span> <span class="s">"Azh3ngServiceImpl"</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">azh3ngService</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// throw Exception or return</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="n">azh3ngService</span><span class="o">.</span><span class="na">foo</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>当新增了一个实现类，无需再到所有使用的地方进行注入和添加 <code class="language-plaintext highlighter-rouge">if/else</code>，但缺点是：当参数异常导致无法获取到准确的 Bean 时，无法统一控制后续的逻辑。</p>

<h2 id="applicationcontext动态获取bean">ApplicationContext动态获取Bean</h2>

<p>Spring提供了<code class="language-plaintext highlighter-rouge">ApplicationContext</code>可以实现动态获取 Bean，无需到所有使用的地方进行注入和添加 <code class="language-plaintext highlighter-rouge">if/else</code>，也可以统一处理参数异常导致的 Bean 获取失败的情况</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">org.springframework.context.ApplicationContext</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.ApplicationContextAware</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Component</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>

<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ServiceFactory</span> <span class="kd">implements</span> <span class="nc">ApplicationContextAware</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="nc">ApplicationContext</span> <span class="n">applicationContext</span><span class="o">;</span>

    <span class="cm">/**
     * 实现 ApplicationContextAware 接口的回调方法，设置上下文环境
     *
     * @param applicationContext applicationContext
     */</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setApplicationContext</span><span class="o">(</span><span class="nc">ApplicationContext</span> <span class="n">applicationContext</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">ServiceFactory</span><span class="o">.</span><span class="na">applicationContext</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">getBean</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">clazz</span><span class="o">,</span> <span class="nc">String</span> <span class="n">code</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">clazz</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="nc">String</span> <span class="n">targetServiceName</span> <span class="o">=</span> <span class="n">code</span> <span class="o">+</span> <span class="n">clazz</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"Impl"</span><span class="o">;</span>
        <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="n">beansOfType</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBeansOfType</span><span class="o">(</span><span class="n">clazz</span><span class="o">);</span>
        <span class="no">T</span> <span class="n">t</span> <span class="o">=</span> <span class="n">beansOfType</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">targetServiceName</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">t</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">NullPointerException</span><span class="o">(</span><span class="s">"System Exception"</span><span class="o">);</span>
            <span class="c1">// 或者返回默认实现</span>
            <span class="c1">// return beansOfType.get("default" + clazz.getSimpleName() + "Impl");</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="n">t</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Azh3ngController</span> <span class="o">{</span>
    
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">(</span><span class="nc">String</span> <span class="n">code</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Azh3ngService</span> <span class="n">azh3ngService</span> <span class="o">=</span> <span class="nc">ServiceFactory</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="nc">Azh3ngService</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">code</span><span class="o">);</span>
        <span class="n">azh3ngService</span><span class="o">.</span><span class="na">foo</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="使用代理对象区分并执行具体实现">使用代理对象区分并执行具体实现</h2>

<p>当参数存储在 <code class="language-plaintext highlighter-rouge">ThreadLocal</code> 或者某种缓存中，可以使用代理对象对参数进行判断并获取具体实现类，并执行相应的逻辑
假设参数存储在 Session 中：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Azh3ngConfig</span> <span class="o">{</span>
    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">ProxyFactoryBean</span> <span class="nf">azh3ngServiceProxy</span><span class="o">(</span><span class="nc">BeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">ProxyFactoryBean</span> <span class="n">proxyFactoryBean</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProxyFactoryBean</span><span class="o">();</span>
        <span class="n">proxyFactoryBean</span><span class="o">.</span><span class="na">setTargetSource</span><span class="o">(</span><span class="k">new</span> <span class="nc">TargetSource</span><span class="o">()</span> <span class="o">{</span>
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">getTargetClass</span><span class="o">()</span> <span class="o">{</span>
                <span class="k">return</span> <span class="nc">Azh3ngService</span><span class="o">.</span><span class="na">class</span><span class="o">;</span>
            <span class="o">}</span>

            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isStatic</span><span class="o">()</span> <span class="o">{</span>
                <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
            <span class="o">}</span>

            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">getTarget</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
                <span class="nc">HttpServletRequest</span> <span class="n">httpServletRequest</span> <span class="o">=</span> <span class="o">((</span><span class="nc">ServletRequestAttributes</span><span class="o">)</span> <span class="nc">RequestContextHolder</span><span class="o">.</span><span class="na">getRequestAttributes</span><span class="o">()).</span><span class="na">getRequest</span><span class="o">();</span>
                <span class="nc">Object</span> <span class="n">code</span> <span class="o">=</span> <span class="n">httpServletRequest</span><span class="o">.</span><span class="na">getSession</span><span class="o">().</span><span class="na">getAttribute</span><span class="o">(</span><span class="s">"code"</span><span class="o">);</span>
                <span class="n">code</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="s">"default"</span> <span class="o">:</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">code</span><span class="o">;</span>
                <span class="nc">Azh3ngService</span> <span class="n">azh3ngService</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Azh3ngService</span><span class="o">)</span> <span class="n">beanFactory</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="n">code</span> <span class="o">+</span> <span class="n">getTargetClass</span><span class="o">().</span><span class="na">getSimpleName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"Impl"</span><span class="o">);</span>
                <span class="k">return</span> <span class="n">azh3ngService</span><span class="o">;</span>
            <span class="o">}</span>

            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">releaseTarget</span><span class="o">(</span><span class="nc">Object</span> <span class="n">target</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
            <span class="o">}</span>
        <span class="o">});</span>
        <span class="n">proxyFactoryBean</span><span class="o">.</span><span class="na">addInterface</span><span class="o">(</span><span class="nc">Azh3ngService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">proxyFactoryBean</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Azh3ngController</span> <span class="o">{</span>
    
    <span class="nd">@Resource</span> <span class="kd">private</span> <span class="nc">Azh3ngService</span> <span class="n">azh3ngServiceProxy</span><span class="o">;</span>
    
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">azh3ngServiceProxy</span><span class="o">.</span><span class="na">foo</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>]]></content><author><name>azh3ng</name></author><category term="Spring" /><summary type="html"><![CDATA[在开发中，常常会定义接口，后对接口进行实现。实现的方式可能有一种，也可能有多种，在不同的场景，需要调用不同的实现。 例如 public interface Azh3ngService { void foo(); } public class AaAzh3ngServiceImpl implements Azh3ngService { @Override public void foo() { System.out.println("aa"); } } public class BbAzh3ngServiceImpl implements Azh3ngService { @Override public void foo() { System.out.println("bb"); } } 在实际业务中，往往根据某些参数区分调用实现。 在 Spring 中可以使用以下几种方式完成。 if/else方式 假设需要根据参数code区分，调用Aa/BbAzh3ngServiceImpl的具体实现 简单做法是 直接注入两种不同的实现的 Bean 使用if{}else{}区分调用具体 Bean @Component public class Azh3ngController { @Resource private Azh3ngService aaAzh3ngServiceImpl; @Resource private Azh3ngService bbAzh3ngServiceImpl; public void test(String code) { if ("a".equals(code)) { return aaAzh3ngServiceImpl.foo(); } else if ("b".equals(code)) { return bbAzh3ngServiceImpl.foo(); } else { throw new RuntimeException("System Exception"); } } } 这种方式简单粗暴，但是缺点也非常明显，假如新增了一个实现类，所有用这种方式使用 Azh3ngService 的代码都需要修改。 使用Map接收Spring的自动注入 Spring 在自动注入时，会判断注入的对象是否为 Collection 或 Map，如果是，会继续判断其指定的泛型类型，进而找出所有匹配类型的 Bean 注入 Collection 或 Map 中。 @Component public class Azh3ngController { @Resource private Map&lt;String, Azh3ngService&gt; azh3ngServiceMap; public void test(String code) { Azh3ngService azh3ngService = azh3ngServiceMap.get(code + "Azh3ngServiceImpl"); if (azh3ngService == null) { // throw Exception or return return; } azh3ngService.foo(); } } 当新增了一个实现类，无需再到所有使用的地方进行注入和添加 if/else，但缺点是：当参数异常导致无法获取到准确的 Bean 时，无法统一控制后续的逻辑。 ApplicationContext动态获取Bean Spring提供了ApplicationContext可以实现动态获取 Bean，无需到所有使用的地方进行注入和添加 if/else，也可以统一处理参数异常导致的 Bean 获取失败的情况 import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.Map; @Component public class ServiceFactory implements ApplicationContextAware { private static ApplicationContext applicationContext; /** * 实现 ApplicationContextAware 接口的回调方法，设置上下文环境 * * @param applicationContext applicationContext */ @Override public void setApplicationContext(ApplicationContext applicationContext) { ServiceFactory.applicationContext = applicationContext; } public static &lt;T&gt; T getBean(Class&lt;T&gt; clazz, String code) { if (clazz == null) { return null; } String targetServiceName = code + clazz.getSimpleName() + "Impl"; Map&lt;String, T&gt; beansOfType = applicationContext.getBeansOfType(clazz); T t = beansOfType.get(targetServiceName); if (t == null) { throw new NullPointerException("System Exception"); // 或者返回默认实现 // return beansOfType.get("default" + clazz.getSimpleName() + "Impl"); } return t; } } @Component public class Azh3ngController { public void test(String code) { Azh3ngService azh3ngService = ServiceFactory.getBean(Azh3ngService.class, code); azh3ngService.foo(); } } 使用代理对象区分并执行具体实现 当参数存储在 ThreadLocal 或者某种缓存中，可以使用代理对象对参数进行判断并获取具体实现类，并执行相应的逻辑 假设参数存储在 Session 中： @Configuration public class Azh3ngConfig { @Bean public ProxyFactoryBean azh3ngServiceProxy(BeanFactory beanFactory) { ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTargetSource(new TargetSource() { @Override public Class&lt;?&gt; getTargetClass() { return Azh3ngService.class; } @Override public boolean isStatic() { return false; } @Override public Object getTarget() throws Exception { HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); Object code = httpServletRequest.getSession().getAttribute("code"); code == null ? "default" : (String) code; Azh3ngService azh3ngService = (Azh3ngService) beanFactory.getBean(code + getTargetClass().getSimpleName() + "Impl"); return azh3ngService; } @Override public void releaseTarget(Object target) throws Exception { } }); proxyFactoryBean.addInterface(Azh3ngService.class); return proxyFactoryBean; } } @Component public class Azh3ngController { @Resource private Azh3ngService azh3ngServiceProxy; public void test() { azh3ngServiceProxy.foo(); } }]]></summary></entry><entry><title type="html">【Spring实战】@Qualifier 注解的使用</title><link href="https://azh3ng.com/2022/01/30/Spring-use-of-@Qualifier.html" rel="alternate" type="text/html" title="【Spring实战】@Qualifier 注解的使用" /><published>2022-01-30T00:00:00+08:00</published><updated>2022-01-30T00:00:00+08:00</updated><id>https://azh3ng.com/2022/01/30/Spring-use-of-@Qualifier</id><content type="html" xml:base="https://azh3ng.com/2022/01/30/Spring-use-of-@Qualifier.html"><![CDATA[<p>在使用 Spring 的过程中，当需要进行依赖注入时，往往会选用 <code class="language-plaintext highlighter-rouge">@Autowired</code> 注解进行自动装配，如果被  <code class="language-plaintext highlighter-rouge">@Autowired</code> 注解的类在 Spring 中有多个 Bean，则通过变量名称选取合适的 Bean，例如：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> 
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">FooService</span> <span class="kd">implements</span> <span class="nc">BaseService</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Component</span> 
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BarService</span> <span class="kd">implements</span> <span class="nc">BaseService</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SomeBean</span> <span class="o">{</span>
    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">BaseService</span> <span class="n">barService</span><span class="o">;</span> <span class="c1">// 根据变量名称会找到 BarService</span>
<span class="o">}</span>
</code></pre></div></div>
<p>但是当 Spring 无法通过变量名确定一个 Bean 时，将会抛出 <code class="language-plaintext highlighter-rouge">NoUniqueBeanDefinitionException</code> 异常，如下：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SomeBean</span> <span class="o">{</span>
    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">BaseService</span> <span class="n">service</span><span class="o">;</span> <span class="c1">// Spring 启动抛出异常 NoUniqueBeanDefinitionException</span>
<span class="o">}</span>
</code></pre></div></div>
<p>通过 <code class="language-plaintext highlighter-rouge">@Qualifier</code> 注解的使用，可以解决此问题</p>
<h2 id="基础使用">基础使用</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> 
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"foo"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">FooService</span> <span class="kd">implements</span> <span class="nc">BaseService</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Component</span> 
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"bar"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BarService</span> <span class="kd">implements</span> <span class="nc">BaseService</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SomeBean</span> <span class="o">{</span>
    <span class="nd">@Autowired</span>
    <span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"bar"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="nc">BaseService</span> <span class="n">service</span><span class="o">;</span> <span class="c1">// 根据 @Qualifier("bar") 会找到 BarService</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="进阶使用">进阶使用</h2>
<ol>
  <li>定义两个注解：</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Target</span><span class="o">({</span><span class="nc">ElementType</span><span class="o">.</span><span class="na">TYPE</span><span class="o">,</span> <span class="nc">ElementType</span><span class="o">.</span><span class="na">FIELD</span><span class="o">})</span>
<span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"foo"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">Foo</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Target</span><span class="o">({</span><span class="nc">ElementType</span><span class="o">.</span><span class="na">TYPE</span><span class="o">,</span> <span class="nc">ElementType</span><span class="o">.</span><span class="na">FIELD</span><span class="o">})</span>
<span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"bar"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">Bar</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>

<ol>
  <li>定义一个接口和两个实现类，表示负载均衡：</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> 
<span class="nd">@Foo</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">FooService</span> <span class="kd">implements</span> <span class="nc">BaseService</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Component</span> 
<span class="nd">@Bar</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BarService</span> <span class="kd">implements</span> <span class="nc">BaseService</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>

<ol>
  <li>使用：</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SomeBean</span> <span class="o">{</span>
    <span class="nd">@Autowired</span>
    <span class="nd">@Bar</span>
    <span class="kd">private</span> <span class="nc">BaseService</span> <span class="n">service</span><span class="o">;</span> <span class="c1">// 通过注解 @Bar 找到 BarService</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="参考">参考</h2>
<p><a href="https://www.baeldung.com/spring-qualifier-annotation">https://www.baeldung.com/spring-qualifier-annotation</a></p>]]></content><author><name>azh3ng</name></author><category term="Spring" /><summary type="html"><![CDATA[在使用 Spring 的过程中，当需要进行依赖注入时，往往会选用 @Autowired 注解进行自动装配，如果被 @Autowired 注解的类在 Spring 中有多个 Bean，则通过变量名称选取合适的 Bean，例如： @Component public class FooService implements BaseService { } @Component public class BarService implements BaseService { } @Component public class SomeBean { @Autowired private BaseService barService; // 根据变量名称会找到 BarService } 但是当 Spring 无法通过变量名确定一个 Bean 时，将会抛出 NoUniqueBeanDefinitionException 异常，如下： @Component public class SomeBean { @Autowired private BaseService service; // Spring 启动抛出异常 NoUniqueBeanDefinitionException } 通过 @Qualifier 注解的使用，可以解决此问题 基础使用 @Component @Qualifier("foo") public class FooService implements BaseService { } @Component @Qualifier("bar") public class BarService implements BaseService { } @Component public class SomeBean { @Autowired @Qualifier("bar") private BaseService service; // 根据 @Qualifier("bar") 会找到 BarService } 进阶使用 定义两个注解： @Target({ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Qualifier("foo") public @interface Foo { } @Target({ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Qualifier("bar") public @interface Bar { } 定义一个接口和两个实现类，表示负载均衡： @Component @Foo public class FooService implements BaseService { } @Component @Bar public class BarService implements BaseService { } 使用： @Component public class SomeBean { @Autowired @Bar private BaseService service; // 通过注解 @Bar 找到 BarService } 参考 https://www.baeldung.com/spring-qualifier-annotation]]></summary></entry><entry><title type="html">【理解Spring】事务</title><link href="https://azh3ng.com/2022/01/17/Spring-Transaction.html" rel="alternate" type="text/html" title="【理解Spring】事务" /><published>2022-01-17T00:00:00+08:00</published><updated>2022-01-17T00:00:00+08:00</updated><id>https://azh3ng.com/2022/01/17/Spring-Transaction</id><content type="html" xml:base="https://azh3ng.com/2022/01/17/Spring-Transaction.html"><![CDATA[<p>在 Spring 中，事务有两种实现方式：</p>
<ol>
  <li><strong>编程式事务管理</strong>：编程式事务管理使用 TransactionTemplate 可实现细粒度的事务控制</li>
  <li><strong>声明式事务管理</strong>：基于 Spring AOP 实现。其本质是对方法前后进行拦截，然后在目标方法开始之前创建或者加入一个事务，在执行完目标方法之后根据执行情况提交或者回滚事务</li>
</ol>

<p>开发中更常使用声明式事务，因为不会入侵代码，通过 <code class="language-plaintext highlighter-rouge">@Transactional</code> 注解即可以完成事务管理<br />
声明式事务基于 Spring AOP 实现：向 Spring 中添加 BeanPostProcessor 扫描被 <code class="language-plaintext highlighter-rouge">@Transactional</code> 注解的类，生成代理对象，通过代理对象完成事务管理</p>

<h2 id="使用示例">使用示例</h2>

<h3 id="编程式事务使用示例">编程式事务使用示例</h3>
<p>使用 <code class="language-plaintext highlighter-rouge">TransactionTemplate</code> 进行编程式事务管理的示例代码：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">TransactionTemplate</span> <span class="n">transactionTemplate</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testTransaction</span><span class="o">()</span> <span class="o">{</span>
    <span class="n">transactionTemplate</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="k">new</span> <span class="nc">TransactionCallbackWithoutResult</span><span class="o">()</span> <span class="o">{</span>
        <span class="nd">@Override</span>
        <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">doInTransactionWithoutResult</span><span class="o">(</span><span class="nc">TransactionStatus</span> <span class="n">transactionStatus</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">try</span> <span class="o">{</span>
                <span class="c1">// ....  业务代码</span>
            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">){</span>
                <span class="c1">//回滚</span>
                <span class="n">transactionStatus</span><span class="o">.</span><span class="na">setRollbackOnly</span><span class="o">();</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">});</span>
<span class="o">}</span>
</code></pre></div></div>

<p>使用 <code class="language-plaintext highlighter-rouge">TransactionManager</code> 进行编程式事务管理的示例代码：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">PlatformTransactionManager</span> <span class="n">transactionManager</span><span class="o">;</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testTransaction</span><span class="o">()</span> <span class="o">{</span>

  <span class="nc">TransactionStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">transactionManager</span><span class="o">.</span><span class="na">getTransaction</span><span class="o">(</span><span class="k">new</span> <span class="nc">DefaultTransactionDefinition</span><span class="o">());</span>
          <span class="k">try</span> <span class="o">{</span>
               <span class="c1">// ....  业务代码</span>
              <span class="n">transactionManager</span><span class="o">.</span><span class="na">commit</span><span class="o">(</span><span class="n">status</span><span class="o">);</span>
          <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
              <span class="n">transactionManager</span><span class="o">.</span><span class="na">rollback</span><span class="o">(</span><span class="n">status</span><span class="o">);</span>
          <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="声明式事务使用示例">声明式事务使用示例</h3>

<p><code class="language-plaintext highlighter-rouge">@Transactional</code> 注解标注在类上，则相当于类中每个方法都添加了此注解；当方法上标注了 <code class="language-plaintext highlighter-rouge">@Transactional</code> 注解，则方法的事务属性会覆盖类中的事务属性</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestTransactional</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testTransactional1</span><span class="o">()</span> <span class="o">{}</span>
    <span class="nd">@Transactional</span><span class="o">(</span><span class="n">propagation</span> <span class="o">=</span> <span class="nc">Propagation</span><span class="o">.</span><span class="na">REQUIRES_NEW</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testTransactional2</span><span class="o">()</span> <span class="o">{}</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="核心接口类">核心接口/类</h2>

<h3 id="transactionattributesource">TransactionAttributeSource</h3>
<p><code class="language-plaintext highlighter-rouge">TransactionAttributeSource</code> 接口用于判断类是否被事务管理，常见实现类为 <code class="language-plaintext highlighter-rouge">AnnotationTransactionAttributeSource</code>，解析 <code class="language-plaintext highlighter-rouge">@Transactional</code> 注解得到相应的事务属性。</p>

<h3 id="transactionmanager">TransactionManager</h3>
<p><code class="language-plaintext highlighter-rouge">TransactionManager</code> 是 Spring 事务事务管理的主要接口，常见子类包括 <code class="language-plaintext highlighter-rouge">PlatformTransactionManager</code> 和 <code class="language-plaintext highlighter-rouge">ReactiveTransactionManager</code>。<br />
<code class="language-plaintext highlighter-rouge">PlatformTransactionManager</code> 的常见实现类为 <code class="language-plaintext highlighter-rouge">DataSourceTransactionManager</code>，提供了获取 DataSource、获取事务、开启、挂起、恢复、提交、回滚等各类事务操作的具体实现。</p>

<h3 id="transactionsynchronizationmanager">TransactionSynchronizationManager</h3>
<p><code class="language-plaintext highlighter-rouge">TransactionSynchronizationManager</code> 是一个事务管理的核心类，通过 <code class="language-plaintext highlighter-rouge">TransactionSynchronizationManager</code> 可以管理当前线程的事务，可以获取当前事务的信息，包括事务名，事务传播机制等。  <br />
还可以在事务结束或者开始之前实现自定义逻辑（<a href="#transactionsynchronization">TransactionSynchronization</a>）<br />
代码示例：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Return the name of the current transaction, or {@code null} if none set.</span>
<span class="nc">TransactionSynchronizationManager</span><span class="o">.</span><span class="na">getCurrentTransactionName</span><span class="o">();</span>
<span class="c1">// Return the isolation level for the current transaction, if any.</span>
<span class="nc">TransactionSynchronizationManager</span><span class="o">.</span><span class="na">getCurrentTransactionIsolationLevel</span><span class="o">();</span>
</code></pre></div></div>

<h3 id="transactionsynchronization">TransactionSynchronization</h3>
<p>TransactionSynchronization 可以监听当前 Spring 事务所处于的状态，在事务提交，回滚、挂起、恢复时执行自定义逻辑<br />
代码示例：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserService</span> <span class="o">{</span>
    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">JdbcTemplate</span> <span class="n">jdbcTemplate</span><span class="o">;</span>
    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">UserService</span> <span class="n">userService</span><span class="o">;</span>

    <span class="nd">@Transactional</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">TransactionSynchronizationManager</span><span class="o">.</span><span class="na">registerSynchronization</span><span class="o">(</span>
                <span class="k">new</span> <span class="nf">TransactionSynchronization</span><span class="o">()</span> <span class="o">{</span>
                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">suspend</span><span class="o">()</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test被挂起了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">resume</span><span class="o">()</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test被恢复了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">beforeCommit</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">readOnly</span><span class="o">)</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test准备要提交了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">beforeCompletion</span><span class="o">()</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test准备要提交或回滚了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterCommit</span><span class="o">()</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test提交成功了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterCompletion</span><span class="o">(</span><span class="kt">int</span> <span class="n">status</span><span class="o">)</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test提交或回滚成功了"</span><span class="o">);</span>
                    <span class="o">}</span>
                <span class="o">});</span>
        <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="s">"insert into t1 values(1,1,1,1,'1')"</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span>
        <span class="n">userService</span><span class="o">.</span><span class="na">a</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Transactional</span><span class="o">(</span><span class="n">propagation</span> <span class="o">=</span> <span class="nc">Propagation</span><span class="o">.</span><span class="na">REQUIRES_NEW</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">a</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">TransactionSynchronizationManager</span><span class="o">.</span><span class="na">registerSynchronization</span><span class="o">(</span>
                <span class="k">new</span> <span class="nf">TransactionSynchronization</span><span class="o">()</span> <span class="o">{</span>
                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">suspend</span><span class="o">()</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"a被挂起了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">resume</span><span class="o">()</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"a被恢复了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">beforeCommit</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">readOnly</span><span class="o">)</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"a准备要提交了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">beforeCompletion</span><span class="o">()</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"a准备要提交或回滚了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterCommit</span><span class="o">()</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"a提交成功了"</span><span class="o">);</span>
                    <span class="o">}</span>

                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterCompletion</span><span class="o">(</span><span class="kt">int</span> <span class="n">status</span><span class="o">)</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"a提交或回滚成功了"</span><span class="o">);</span>
                    <span class="o">}</span>
                <span class="o">});</span>
        <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="s">"insert into t1 values(2,2,2,2,'2')"</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"a"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="事务传播机制">事务传播机制</h2>

<p>在 Spring 中对于事务的传播行为定义了七种类型分别是：REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED<br />
Spring 源码中这七种类型的枚举定义在 <code class="language-plaintext highlighter-rouge">org.springframework.transaction.annotation.Propagation</code></p>

<h3 id="required">REQUIRED</h3>
<p>Propagation.REQUIRED 是 Spring 默认的事务传播机制；如果当前存在事务，则加入当前事务，如果不存在则创建新事务。<br />
<strong>注意</strong>：当事务方法 1 Propagation.REQUIRED 调用事务方法 2 Propagation.REQUIRED 时，如果事务方法 2 抛出异常，在事务方法 1 中捕获异常后不向外抛出，但整个事务依旧会回滚。<br />
因为在事务方法 2 回滚时，会判断 globalRollbackOnParticipationFailure = true(默认)，则会在数据库连接上<strong>设置回滚标记</strong>，当事务方法 1 执行完成后，执行<a href="#提交事务">提交事务</a> 时，会<strong>判断回滚标记</strong>，如果为 ture，则执行回滚事务</p>

<h3 id="requires_new">REQUIRES_NEW</h3>
<p>不论当前是否存在事务，总是会新建一个事务，如果存在当前事务，则挂起当前事务<br />
创建的新事务独立存在，独立提交和回滚不会影响其他事务</p>

<h3 id="nested">NESTED</h3>
<p>如果当前事务存在，则在开启一个嵌套事务，在嵌套事务中执行，否则开启一个事务<br />
<strong>注意</strong>：REQUIRES_NEW 是新建一个事务并且新开启的这个事务与原有事务无关，而 NESTED 则是当前存在事务时（暂且把当前事务称之为父事务）会开启一个嵌套事务（称之为一个子事务）。<br />
在 NESTED 情况下父事务回滚时，子事务也会回滚，而在 REQUIRES_NEW 情况下，原有事务回滚，不会影响新开启的事务。<br />
同时 NESTED 提供 savepoint 机制，当数据库支持 savepoint 则可以实现内嵌的事务失败时，仅回滚到 savepoint，而不是回滚整个事务</p>

<h3 id="supports">SUPPORTS</h3>
<p>如果当前存在事务，则加入事务；如果当前不存在事务，则以非事务方式运行，这个和不写效果一样</p>

<h3 id="mandatory">MANDATORY</h3>
<p>当前存在事务，则加入当前事务，如果当前事务不存在，则抛出异常。</p>

<h3 id="not_supported">NOT_SUPPORTED</h3>
<p>始终以非事务方式执行，如果当前存在事务，则挂起当前事务</p>

<h3 id="never">NEVER</h3>
<p>不使用事务，如果当前事务存在，则抛出异常</p>

<h2 id="spring-整合事务">Spring 整合事务</h2>
<p>在 Spring 中使用声明式事务，往往是添加 <code class="language-plaintext highlighter-rouge">@EnableTransactionManagement</code> 注解启用 Spring 事务。<br />
<code class="language-plaintext highlighter-rouge">@EnableTransactionManagement</code> 注解向 Spring 中添加了两个 Bean：</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">AutoProxyRegistrar</code></li>
  <li><code class="language-plaintext highlighter-rouge">ProxyTransactionManagementConfiguration</code></li>
</ol>

<h3 id="autoproxyregistrar">AutoProxyRegistrar</h3>
<p><code class="language-plaintext highlighter-rouge">org.springframework.context.annotation.AutoProxyRegistrar</code><br />
<code class="language-plaintext highlighter-rouge">AutoProxyRegistrar</code> 向 Spring 容器中注册了 <code class="language-plaintext highlighter-rouge">InfrastructureAdvisorAutoProxyCreator</code> 类型的 Bean。<br />
<code class="language-plaintext highlighter-rouge">InfrastructureAdvisorAutoProxyCreator</code> 继承 <a href="/2022/01/16/Spring-AOP.html#abstractadvisorautoproxycreator">Spring AOP#AbstractAdvisorAutoProxyCreator</a>，也即它是个 BeanPostProcessor，并且相当于开启了 Spring AOP。</p>

<h3 id="proxytransactionmanagementconfiguration">ProxyTransactionManagementConfiguration</h3>
<p><code class="language-plaintext highlighter-rouge">org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration</code><br />
<code class="language-plaintext highlighter-rouge">ProxyTransactionManagementConfiguration</code> 是一个配置类，内部定义了三个 Bean：</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">BeanFactoryTransactionAttributeSourceAdvisor</code> ：一个 <a href="/2022/01/16/Spring-AOP.html#advisor">Spring AOP#Advisor</a></li>
  <li><code class="language-plaintext highlighter-rouge">AnnotationTransactionAttributeSource</code> ：相当于 <code class="language-plaintext highlighter-rouge">BeanFactoryTransactionAttributeSourceAdvisor</code> 中的 <a href="/2022/01/16/Spring-AOP.html#pointcut">Spring AOP#Pointcut</a></li>
  <li><code class="language-plaintext highlighter-rouge">TransactionInterceptor</code> ：相当于 <code class="language-plaintext highlighter-rouge">BeanFactoryTransactionAttributeSourceAdvisor</code> 中的 <a href="/2022/01/16/Spring-AOP.html#advice">Spring AOP#Advice</a></li>
</ol>

<h3 id="annotationtransactionattributesource">AnnotationTransactionAttributeSource</h3>
<p><code class="language-plaintext highlighter-rouge">AnnotationTransactionAttributeSource</code> 可以判断类或者方法上是否存在 <code class="language-plaintext highlighter-rouge">@Transactional</code> 注解</p>

<h3 id="transactioninterceptor">TransactionInterceptor</h3>
<p><code class="language-plaintext highlighter-rouge">TransactionInterceptor</code> 是 Spring 定义的事务方法拦截器，也就是事务的代理逻辑<br />
当某个类中存在 <code class="language-plaintext highlighter-rouge">@Transactional</code> 注解时，会产生创建代理对象作为 Bean，代理对象在执行某个方法时，会进入到 <code class="language-plaintext highlighter-rouge">TransactionInterceptor.invoke()</code> 方法</p>

<h2 id="spring-事务详细执行流程">Spring 事务详细执行流程</h2>
<ul>
  <li><code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionInterceptor#invoke</code>
    <ul>
      <li>（事务执行详细）<code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction</code>
        <ul>
          <li>（获取 TransactionManager）<code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionAspectSupport#determineTransactionManager</code></li>
          <li>（开启事务）<code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary</code></li>
          <li>执行目标对象的方法</li>
          <li>（回滚事务）<code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing</code></li>
          <li>（清除事务信息）<code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionAspectSupport#cleanupTransactionInfo</code></li>
          <li>（提交事务）<code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning</code></li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<h3 id="开启事务">开启事务</h3>
<ul>
  <li><code class="language-plaintext highlighter-rouge">TransactionAspectSupport.createTransactionIfNecessary()</code>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">AbstractPlatformTransactionManager.getTransaction()</code>
        <ul>
          <li><code class="language-plaintext highlighter-rouge">AbstractPlatformTransactionManager.startTransaction()</code></li>
        </ul>
      </li>
      <li><code class="language-plaintext highlighter-rouge">TransactionAspectSupport.prepareTransactionInfo()</code></li>
    </ul>
  </li>
</ul>

<p><strong>简述</strong>：</p>
<ul>
  <li>判断当前是否存在事务
    <ul>
      <li>是则根据事务传播机制做出相应处理；</li>
      <li>否则继续判断事务传播机制，或抛出异常，或挂起并新建事务</li>
    </ul>
  </li>
  <li>将挂起的事务缓存到新建的事务中</li>
</ul>

<p><strong>详述</strong>：</p>
<ol>
  <li>调用 <code class="language-plaintext highlighter-rouge">TransactionManager.getTransaction()</code>
    <ol>
      <li>尝试获取当前线程的事务（<code class="language-plaintext highlighter-rouge">DataSourceTransactionManager.doGetTransaction()</code>）</li>
      <li>判断当前线程是否存在事务(<code class="language-plaintext highlighter-rouge">DataSourceTransactionManager.isExistingTransaction()</code>)
        <ol>
          <li>如果有，判断当前事务的传播机制（<code class="language-plaintext highlighter-rouge">AbstractPlatformTransactionManager.handleExistingTransaction()</code>）做出相应处理，并返回结果</li>
          <li>否则继续执行</li>
        </ol>
      </li>
      <li>判断事务传播机制
        <ol>
          <li>如果是 <a href="#mandatory">MANDATORY</a>，抛出异常</li>
          <li>如果是 <a href="#required">REQUIRED</a> 或 <a href="#requires_new">REQUIRES_NEW</a> 或 <a href="#nested">NESTED</a>
            <ol>
              <li><a href="#挂起事务">挂起空事务</a></li>
              <li>开启事务（ <code class="language-plaintext highlighter-rouge">TransactionManager.startTransaction()</code>）
                <ol>
                  <li>缓存事务的状态信息（当前事务信息、挂起的事务信息等)</li>
                  <li>创建数据库连接（<code class="language-plaintext highlighter-rouge">DataSourceTransactionManager.doBegin()</code>））
                    <ol>
                      <li>如果当前线程中所使用的 DataSource 还没有创建过数据库连接，则<strong>新建</strong>数据库连接</li>
                      <li>设置事务隔离级别</li>
                      <li>设置 readOnly 属性</li>
                      <li>设置 AutoCommit 为 false</li>
                      <li>设置数据库连接的过期时间</li>
                      <li>把<strong>新建</strong>的数据库连接缓存到 TreadLocal （TransactionSynchronizationManager）中</li>
                    </ol>
                  </li>
                  <li>将<strong>新建的</strong>数据库连接信息（当前事务名、readOnly、隔离级别、wasActive）缓存到 TreadLocal（<code class="language-plaintext highlighter-rouge">TransactionSynchronizationManager</code>）中（<code class="language-plaintext highlighter-rouge">AbstractPlatformTransactionManager.prepareSynchronization()</code>）</li>
                </ol>
              </li>
            </ol>
          </li>
        </ol>
      </li>
    </ol>
  </li>
  <li>构建 TransactionInfo 并返回（<code class="language-plaintext highlighter-rouge">TransactionAspectSupport.prepareTransactionInfo()</code>）
    <ol>
      <li>新建 TransactionInfo（包含事务管理器、事务属性、连接点信息(方法名)）</li>
      <li>TransactionInfo 设值 transactionStatus</li>
      <li>组装事务链
        <ol>
          <li>将当前线程中的 TransactionInfo 设值到新建的 TransactionInfo.oldTransactionInfo</li>
          <li>将新建的 TransactionInfo 设置到当前线程中</li>
        </ol>
      </li>
    </ol>
  </li>
</ol>

<h3 id="挂起事务">挂起事务</h3>
<p><code class="language-plaintext highlighter-rouge">org.springframework.transaction.support.AbstractPlatformTransactionManager#suspend</code><br />
<strong>简述</strong>：<br />
如果存在 <code class="language-plaintext highlighter-rouge">TransactionSynchronization</code>（代表存在事务），则执行其 <code class="language-plaintext highlighter-rouge">suspend()</code> 方法，并挂起当前事务，将被挂起的事务信息打包并返回</p>

<p><strong>详述</strong>：</p>
<ol>
  <li>判断 <code class="language-plaintext highlighter-rouge">TransactionSynchronization</code> 是否处于激活状态，如果是：
    <ol>
      <li>执行所有事务同步器的 <code class="language-plaintext highlighter-rouge">suspend()</code> 方法（<code class="language-plaintext highlighter-rouge">AbstractPlatformTransactionManager.doSuspendSynchronization()</code>）
        <ol>
          <li><strong>获取</strong>并<strong>清空</strong>线程中的 <code class="language-plaintext highlighter-rouge">List&lt;TransactionSynchronization&gt;</code></li>
          <li>返回 <code class="language-plaintext highlighter-rouge">List&lt;TransactionSynchronization&gt;</code></li>
        </ol>
      </li>
      <li>挂起事务（<code class="language-plaintext highlighter-rouge">DataSourceTransactionManager.doSuspend()</code>）
        <ol>
          <li>移除并返回线程中（TransactionSynchronizationManager）中的数据库连接</li>
        </ol>
      </li>
      <li><strong>获取</strong>并<strong>清空</strong>当前线程中（TransactionSynchronizationManager）的设置(当前事务名、readOnly、隔离级别、wasActive)</li>
      <li>将被挂起的事务属性打包成 <code class="language-plaintext highlighter-rouge">SuspendedResourcesHolder</code></li>
      <li>返回</li>
    </ol>
  </li>
  <li>如果不存在事务同步器，但存在事务
    <ol>
      <li>挂起当前事务（<code class="language-plaintext highlighter-rouge">DataSourceTransactionManager.doSuspend()</code>）
        <ol>
          <li>移除并返回线程中（TransactionSynchronizationManager）中的数据库连接</li>
        </ol>
      </li>
      <li>将被挂起的事务属性打包成 SuspendedResourcesHolder</li>
      <li>返回</li>
    </ol>
  </li>
  <li>如果即不存在事务同步器，又不存在事务
    <ol>
      <li>返回空</li>
    </ol>
  </li>
</ol>

<h3 id="提交事务">提交事务</h3>
<p><code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning</code><br />
<strong>简述</strong>：判断事务是否需要回滚，如果是，则执行 <a href="#回滚事务">回滚事务</a>，根据事务状态执行事务提交、关闭数据库连接、将被挂起的事务恢复</p>

<p><strong>详述</strong>：</p>
<ul>
  <li>判断事务是否被设置为强制回滚，是则执行 <a href="#回滚事务">回滚事务</a>，返回</li>
  <li>判断事务是否被标记为需要回滚，是则执行 <a href="#回滚事务">回滚事务</a>，返回</li>
  <li>判断如果存在 TransactionSynchronization，执行其 <code class="language-plaintext highlighter-rouge">beforeCommit()</code> 和 <code class="language-plaintext highlighter-rouge">beforeCompletion()</code> 方法</li>
  <li>判断事务是否有 savepoint，如果有则释放 savepoint</li>
  <li>否则判断是否为新事务
    <ul>
      <li>如果是，获取数据库连接，执行提交（<code class="language-plaintext highlighter-rouge">DataSourceTransactionManager#doCommit</code>）</li>
    </ul>
  </li>
  <li>判断如果存在 TransactionSynchronization，执行其 <code class="language-plaintext highlighter-rouge">afterCompletion()</code> 方法</li>
</ul>

<h3 id="回滚事务">回滚事务</h3>
<p><code class="language-plaintext highlighter-rouge">org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing</code><br />
<strong>简述</strong>：判断抛出的异常是否与回滚异常相同，相同则执行回滚，根据事务传播机制判断执行回滚 savepoint 或 回滚事务 或 标记事务需要回滚</p>

<p><strong>详述</strong>：</p>
<ul>
  <li>判断抛出的异常是否与回滚异常相同
    <ul>
      <li>是则执行回滚（<code class="language-plaintext highlighter-rouge">AbstractPlatformTransactionManager.rollback()</code> -&gt; <code class="language-plaintext highlighter-rouge">AbstractPlatformTransactionManager.processRollback()</code>）
        <ul>
          <li>判断
            <ul>
              <li>存在 savepoint，则回滚至 savepoint</li>
              <li>是新事务，则执行回滚</li>
              <li>否则将事务标记为需要回滚，在事务提交时执行回滚</li>
            </ul>
          </li>
          <li>如果存在 <code class="language-plaintext highlighter-rouge">TransactionSynchronization</code>，执行其 <code class="language-plaintext highlighter-rouge">afterCompletion()</code> 方法</li>
        </ul>
      </li>
      <li>否则执行提交（<code class="language-plaintext highlighter-rouge">AbstractPlatformTransactionManager.commit()</code>）</li>
    </ul>
  </li>
</ul>

<h2 id="spring-事务强制回滚">Spring 事务强制回滚</h2>
<p>如果在方法中增加这一行代码：<br />
<code class="language-plaintext highlighter-rouge">TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();</code><br />
在提交事务时会做判断，如果为 true，则会执行事务回滚操作</p>]]></content><author><name>azh3ng</name></author><category term="Spring" /><summary type="html"><![CDATA[在 Spring 中，事务有两种实现方式： 编程式事务管理：编程式事务管理使用 TransactionTemplate 可实现细粒度的事务控制 声明式事务管理：基于 Spring AOP 实现。其本质是对方法前后进行拦截，然后在目标方法开始之前创建或者加入一个事务，在执行完目标方法之后根据执行情况提交或者回滚事务 开发中更常使用声明式事务，因为不会入侵代码，通过 @Transactional 注解即可以完成事务管理 声明式事务基于 Spring AOP 实现：向 Spring 中添加 BeanPostProcessor 扫描被 @Transactional 注解的类，生成代理对象，通过代理对象完成事务管理 使用示例 编程式事务使用示例 使用 TransactionTemplate 进行编程式事务管理的示例代码： @Autowired private TransactionTemplate transactionTemplate; public void testTransaction() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { try { // .... 业务代码 } catch (Exception e){ //回滚 transactionStatus.setRollbackOnly(); } } }); } 使用 TransactionManager 进行编程式事务管理的示例代码： @Autowired private PlatformTransactionManager transactionManager; public void testTransaction() { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { // .... 业务代码 transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); } } 声明式事务使用示例 @Transactional 注解标注在类上，则相当于类中每个方法都添加了此注解；当方法上标注了 @Transactional 注解，则方法的事务属性会覆盖类中的事务属性 @Transactional public class TestTransactional { public void testTransactional1() {} @Transactional(propagation = Propagation.REQUIRES_NEW) public void testTransactional2() {} } 核心接口/类 TransactionAttributeSource TransactionAttributeSource 接口用于判断类是否被事务管理，常见实现类为 AnnotationTransactionAttributeSource，解析 @Transactional 注解得到相应的事务属性。 TransactionManager TransactionManager 是 Spring 事务事务管理的主要接口，常见子类包括 PlatformTransactionManager 和 ReactiveTransactionManager。 PlatformTransactionManager 的常见实现类为 DataSourceTransactionManager，提供了获取 DataSource、获取事务、开启、挂起、恢复、提交、回滚等各类事务操作的具体实现。 TransactionSynchronizationManager TransactionSynchronizationManager 是一个事务管理的核心类，通过 TransactionSynchronizationManager 可以管理当前线程的事务，可以获取当前事务的信息，包括事务名，事务传播机制等。 还可以在事务结束或者开始之前实现自定义逻辑（TransactionSynchronization） 代码示例： // Return the name of the current transaction, or {@code null} if none set. TransactionSynchronizationManager.getCurrentTransactionName(); // Return the isolation level for the current transaction, if any. TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); TransactionSynchronization TransactionSynchronization 可以监听当前 Spring 事务所处于的状态，在事务提交，回滚、挂起、恢复时执行自定义逻辑 代码示例： @Component public class UserService { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private UserService userService; @Transactional public void test() { TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronization() { @Override public void suspend() { System.out.println("test被挂起了"); } @Override public void resume() { System.out.println("test被恢复了"); } @Override public void beforeCommit(boolean readOnly) { System.out.println("test准备要提交了"); } @Override public void beforeCompletion() { System.out.println("test准备要提交或回滚了"); } @Override public void afterCommit() { System.out.println("test提交成功了"); } @Override public void afterCompletion(int status) { System.out.println("test提交或回滚成功了"); } }); jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')"); System.out.println("test"); userService.a(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void a() { TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronization() { @Override public void suspend() { System.out.println("a被挂起了"); } @Override public void resume() { System.out.println("a被恢复了"); } @Override public void beforeCommit(boolean readOnly) { System.out.println("a准备要提交了"); } @Override public void beforeCompletion() { System.out.println("a准备要提交或回滚了"); } @Override public void afterCommit() { System.out.println("a提交成功了"); } @Override public void afterCompletion(int status) { System.out.println("a提交或回滚成功了"); } }); jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')"); System.out.println("a"); } } 事务传播机制 在 Spring 中对于事务的传播行为定义了七种类型分别是：REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED Spring 源码中这七种类型的枚举定义在 org.springframework.transaction.annotation.Propagation REQUIRED Propagation.REQUIRED 是 Spring 默认的事务传播机制；如果当前存在事务，则加入当前事务，如果不存在则创建新事务。 注意：当事务方法 1 Propagation.REQUIRED 调用事务方法 2 Propagation.REQUIRED 时，如果事务方法 2 抛出异常，在事务方法 1 中捕获异常后不向外抛出，但整个事务依旧会回滚。 因为在事务方法 2 回滚时，会判断 globalRollbackOnParticipationFailure = true(默认)，则会在数据库连接上设置回滚标记，当事务方法 1 执行完成后，执行提交事务 时，会判断回滚标记，如果为 ture，则执行回滚事务 REQUIRES_NEW 不论当前是否存在事务，总是会新建一个事务，如果存在当前事务，则挂起当前事务 创建的新事务独立存在，独立提交和回滚不会影响其他事务 NESTED 如果当前事务存在，则在开启一个嵌套事务，在嵌套事务中执行，否则开启一个事务 注意：REQUIRES_NEW 是新建一个事务并且新开启的这个事务与原有事务无关，而 NESTED 则是当前存在事务时（暂且把当前事务称之为父事务）会开启一个嵌套事务（称之为一个子事务）。 在 NESTED 情况下父事务回滚时，子事务也会回滚，而在 REQUIRES_NEW 情况下，原有事务回滚，不会影响新开启的事务。 同时 NESTED 提供 savepoint 机制，当数据库支持 savepoint 则可以实现内嵌的事务失败时，仅回滚到 savepoint，而不是回滚整个事务 SUPPORTS 如果当前存在事务，则加入事务；如果当前不存在事务，则以非事务方式运行，这个和不写效果一样 MANDATORY 当前存在事务，则加入当前事务，如果当前事务不存在，则抛出异常。 NOT_SUPPORTED 始终以非事务方式执行，如果当前存在事务，则挂起当前事务 NEVER 不使用事务，如果当前事务存在，则抛出异常 Spring 整合事务 在 Spring 中使用声明式事务，往往是添加 @EnableTransactionManagement 注解启用 Spring 事务。 @EnableTransactionManagement 注解向 Spring 中添加了两个 Bean： AutoProxyRegistrar ProxyTransactionManagementConfiguration AutoProxyRegistrar org.springframework.context.annotation.AutoProxyRegistrar AutoProxyRegistrar 向 Spring 容器中注册了 InfrastructureAdvisorAutoProxyCreator 类型的 Bean。 InfrastructureAdvisorAutoProxyCreator 继承 Spring AOP#AbstractAdvisorAutoProxyCreator，也即它是个 BeanPostProcessor，并且相当于开启了 Spring AOP。 ProxyTransactionManagementConfiguration org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration ProxyTransactionManagementConfiguration 是一个配置类，内部定义了三个 Bean： BeanFactoryTransactionAttributeSourceAdvisor ：一个 Spring AOP#Advisor AnnotationTransactionAttributeSource ：相当于 BeanFactoryTransactionAttributeSourceAdvisor 中的 Spring AOP#Pointcut TransactionInterceptor ：相当于 BeanFactoryTransactionAttributeSourceAdvisor 中的 Spring AOP#Advice AnnotationTransactionAttributeSource AnnotationTransactionAttributeSource 可以判断类或者方法上是否存在 @Transactional 注解 TransactionInterceptor TransactionInterceptor 是 Spring 定义的事务方法拦截器，也就是事务的代理逻辑 当某个类中存在 @Transactional 注解时，会产生创建代理对象作为 Bean，代理对象在执行某个方法时，会进入到 TransactionInterceptor.invoke() 方法 Spring 事务详细执行流程 org.springframework.transaction.interceptor.TransactionInterceptor#invoke （事务执行详细）org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction （获取 TransactionManager）org.springframework.transaction.interceptor.TransactionAspectSupport#determineTransactionManager （开启事务）org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary 执行目标对象的方法 （回滚事务）org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing （清除事务信息）org.springframework.transaction.interceptor.TransactionAspectSupport#cleanupTransactionInfo （提交事务）org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning 开启事务 TransactionAspectSupport.createTransactionIfNecessary() AbstractPlatformTransactionManager.getTransaction() AbstractPlatformTransactionManager.startTransaction() TransactionAspectSupport.prepareTransactionInfo() 简述： 判断当前是否存在事务 是则根据事务传播机制做出相应处理； 否则继续判断事务传播机制，或抛出异常，或挂起并新建事务 将挂起的事务缓存到新建的事务中 详述： 调用 TransactionManager.getTransaction() 尝试获取当前线程的事务（DataSourceTransactionManager.doGetTransaction()） 判断当前线程是否存在事务(DataSourceTransactionManager.isExistingTransaction()) 如果有，判断当前事务的传播机制（AbstractPlatformTransactionManager.handleExistingTransaction()）做出相应处理，并返回结果 否则继续执行 判断事务传播机制 如果是 MANDATORY，抛出异常 如果是 REQUIRED 或 REQUIRES_NEW 或 NESTED 挂起空事务 开启事务（ TransactionManager.startTransaction()） 缓存事务的状态信息（当前事务信息、挂起的事务信息等) 创建数据库连接（DataSourceTransactionManager.doBegin()）） 如果当前线程中所使用的 DataSource 还没有创建过数据库连接，则新建数据库连接 设置事务隔离级别 设置 readOnly 属性 设置 AutoCommit 为 false 设置数据库连接的过期时间 把新建的数据库连接缓存到 TreadLocal （TransactionSynchronizationManager）中 将新建的数据库连接信息（当前事务名、readOnly、隔离级别、wasActive）缓存到 TreadLocal（TransactionSynchronizationManager）中（AbstractPlatformTransactionManager.prepareSynchronization()） 构建 TransactionInfo 并返回（TransactionAspectSupport.prepareTransactionInfo()） 新建 TransactionInfo（包含事务管理器、事务属性、连接点信息(方法名)） TransactionInfo 设值 transactionStatus 组装事务链 将当前线程中的 TransactionInfo 设值到新建的 TransactionInfo.oldTransactionInfo 将新建的 TransactionInfo 设置到当前线程中 挂起事务 org.springframework.transaction.support.AbstractPlatformTransactionManager#suspend 简述： 如果存在 TransactionSynchronization（代表存在事务），则执行其 suspend() 方法，并挂起当前事务，将被挂起的事务信息打包并返回 详述： 判断 TransactionSynchronization 是否处于激活状态，如果是： 执行所有事务同步器的 suspend() 方法（AbstractPlatformTransactionManager.doSuspendSynchronization()） 获取并清空线程中的 List&lt;TransactionSynchronization&gt; 返回 List&lt;TransactionSynchronization&gt; 挂起事务（DataSourceTransactionManager.doSuspend()） 移除并返回线程中（TransactionSynchronizationManager）中的数据库连接 获取并清空当前线程中（TransactionSynchronizationManager）的设置(当前事务名、readOnly、隔离级别、wasActive) 将被挂起的事务属性打包成 SuspendedResourcesHolder 返回 如果不存在事务同步器，但存在事务 挂起当前事务（DataSourceTransactionManager.doSuspend()） 移除并返回线程中（TransactionSynchronizationManager）中的数据库连接 将被挂起的事务属性打包成 SuspendedResourcesHolder 返回 如果即不存在事务同步器，又不存在事务 返回空 提交事务 org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning 简述：判断事务是否需要回滚，如果是，则执行 回滚事务，根据事务状态执行事务提交、关闭数据库连接、将被挂起的事务恢复 详述： 判断事务是否被设置为强制回滚，是则执行 回滚事务，返回 判断事务是否被标记为需要回滚，是则执行 回滚事务，返回 判断如果存在 TransactionSynchronization，执行其 beforeCommit() 和 beforeCompletion() 方法 判断事务是否有 savepoint，如果有则释放 savepoint 否则判断是否为新事务 如果是，获取数据库连接，执行提交（DataSourceTransactionManager#doCommit） 判断如果存在 TransactionSynchronization，执行其 afterCompletion() 方法 回滚事务 org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing 简述：判断抛出的异常是否与回滚异常相同，相同则执行回滚，根据事务传播机制判断执行回滚 savepoint 或 回滚事务 或 标记事务需要回滚 详述： 判断抛出的异常是否与回滚异常相同 是则执行回滚（AbstractPlatformTransactionManager.rollback() -&gt; AbstractPlatformTransactionManager.processRollback()） 判断 存在 savepoint，则回滚至 savepoint 是新事务，则执行回滚 否则将事务标记为需要回滚，在事务提交时执行回滚 如果存在 TransactionSynchronization，执行其 afterCompletion() 方法 否则执行提交（AbstractPlatformTransactionManager.commit()） Spring 事务强制回滚 如果在方法中增加这一行代码： TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 在提交事务时会做判断，如果为 true，则会执行事务回滚操作]]></summary></entry><entry><title type="html">【理解Spring】ProxyFactory</title><link href="https://azh3ng.com/2022/01/16/ProxyFactory.html" rel="alternate" type="text/html" title="【理解Spring】ProxyFactory" /><published>2022-01-16T02:00:00+08:00</published><updated>2022-01-16T02:00:00+08:00</updated><id>https://azh3ng.com/2022/01/16/ProxyFactory</id><content type="html" xml:base="https://azh3ng.com/2022/01/16/ProxyFactory.html"><![CDATA[<p><a href="/2099/01/01/Spring-introduction.html">Spring</a> 对 <a href="/2022/01/16/JDK-Proxy.html">JDK动态代理</a> 和 <a href="/2022/01/16/CGLIB-Proxy.html">CGLIB动态代理</a> 进行了一定程度的封装，提供一个类 ProxyFactory，更加方便的创建代理对象。</p>

<h2 id="创建代理对象">创建代理对象</h2>
<p>代码示例：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Azh3ngInterface</span> <span class="o">{</span>
    <span class="kt">void</span> <span class="nf">test</span><span class="o">();</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Azh3ngInterfaceImpl</span> <span class="kd">implements</span> <span class="nc">Azh3ngInterface</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test..."</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestProxyFactory</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Azh3ngInterfaceImpl</span> <span class="n">target</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Azh3ngInterfaceImpl</span><span class="o">();</span>
        <span class="nc">ProxyFactory</span> <span class="n">proxyFactory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProxyFactory</span><span class="o">();</span>
        <span class="n">proxyFactory</span><span class="o">.</span><span class="na">setTarget</span><span class="o">(</span><span class="n">target</span><span class="o">);</span>
        <span class="n">proxyFactory</span><span class="o">.</span><span class="na">addAdvice</span><span class="o">(</span><span class="k">new</span> <span class="nc">MethodInterceptor</span><span class="o">()</span> <span class="o">{</span>
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">invoke</span><span class="o">(</span><span class="nc">MethodInvocation</span> <span class="n">invocation</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Throwable</span> <span class="o">{</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"before..."</span><span class="o">);</span>
                <span class="nc">Object</span> <span class="n">result</span> <span class="o">=</span> <span class="n">invocation</span><span class="o">.</span><span class="na">proceed</span><span class="o">();</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"after..."</span><span class="o">);</span>
                <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">});</span>
        <span class="nc">Azh3ngInterface</span> <span class="n">azh3ngService</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Azh3ngInterface</span><span class="o">)</span> <span class="n">proxyFactory</span><span class="o">.</span><span class="na">getProxy</span><span class="o">();</span>
        <span class="n">azh3ngService</span><span class="o">.</span><span class="na">test</span><span class="o">();</span>
        <span class="cm">/*
          output:
          before...
          test...
          after...
         */</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>通过使用 ProxyFactory，可以不再关心用 CGlib 还是 jdk 动态代理，ProxyFactory 会自动判断，如果 <code class="language-plaintext highlighter-rouge">Azh3ngInterfaceImpl</code> 实现了接口，那么 ProxyFactory 底层就会用 JDK 动态代理，如果没有实现接口，就会用 CGlib 技术，上面的代码，就是由于 <code class="language-plaintext highlighter-rouge">Azh3ngInterfaceImpl</code> 实现了 <code class="language-plaintext highlighter-rouge">Azh3ngInterface</code> 接口，所以最后产生的代理对象是 <code class="language-plaintext highlighter-rouge">Azh3ngInterface</code> 类型</p>

<h2 id="advice">Advice</h2>
<p>Advice 可以理解为自定义的代理逻辑。
在调用 <code class="language-plaintext highlighter-rouge">proxyFactory.addAdvice()</code> 时可以传入各式的 Advice，可以分类为：</p>
<ul>
  <li>Before Advice（<code class="language-plaintext highlighter-rouge">org.springframework.aop.MethodBeforeAdvice</code>）：方法之前执行</li>
  <li>After returning advice（<code class="language-plaintext highlighter-rouge">com.tuling.aop.advice.Azh3ngAfterReturningAdvice</code>）：方法 return 后执行</li>
  <li>After throwing advice（<code class="language-plaintext highlighter-rouge">org.springframework.aop.ThrowsAdvice</code>）：方法抛异常后执行
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ThrowsAdvice</code> 接口没有定义方法，需要开发者根据规范自定义方法，可以区分控制不同的异常处理不同逻辑</li>
    </ul>
  </li>
  <li>After (finally) advice（``）：方法执行完 finally 之后执行，这是最后的，比 return 更后</li>
  <li>Around advice（<code class="language-plaintext highlighter-rouge">org.aopalliance.intercept.MethodInterceptor</code>）：功能最强大的 Advice，可以自定义执行顺序
在添加 Advice 后，ProxyFactory 生成的代理对象，会根据添加的顺序执行所有 Advice（[[责任链模式]]）</li>
</ul>

<h2 id="pointcut">Pointcut</h2>
<p>Pointcut 中文译为切点，可以理解为通过一些过滤条件，筛选出需要被代理的方法</p>

<h2 id="advisor">Advisor</h2>
<p>Advisor 是 <a href="#pointcut">Pointcut</a> 和 <a href="#advice">Advice</a> 的组合，通过 Pointcut 指定（筛选）被代理的方法，通过 Advice 自定义代理逻辑<br />
在单独使用 Advice 时无法细粒度的控制不同方法执行不同的逻辑，Advisor 可以做到。<br />
假设一个 <code class="language-plaintext highlighter-rouge">Azh3ngInterfaceImpl</code> 类中有两个方法，按上面的例子，这两个方法都会被代理和增强，通过使用 Advisor，可以控制到具体代理某个或某些方法，比如：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Azh3ngInterfaceImpl</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test1</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test1..."</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test2</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test2..."</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestProxyFactory</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Azh3ngInterfaceImpl</span> <span class="n">target</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Azh3ngInterfaceImpl</span><span class="o">();</span>
        <span class="nc">ProxyFactory</span> <span class="n">proxyFactory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProxyFactory</span><span class="o">();</span>
        <span class="n">proxyFactory</span><span class="o">.</span><span class="na">setTarget</span><span class="o">(</span><span class="n">target</span><span class="o">);</span>
        <span class="n">proxyFactory</span><span class="o">.</span><span class="na">addAdvisor</span><span class="o">(</span><span class="k">new</span> <span class="nc">PointcutAdvisor</span><span class="o">()</span> <span class="o">{</span>
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="nc">Pointcut</span> <span class="nf">getPointcut</span><span class="o">()</span> <span class="o">{</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nf">StaticMethodMatcherPointcut</span><span class="o">()</span> <span class="o">{</span>
                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">matches</span><span class="o">(</span><span class="nc">Method</span> <span class="n">method</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">targetClass</span><span class="o">)</span> <span class="o">{</span>
                        <span class="c1">// 仅当方法名为 “test1” 时，才执行 Advice</span>
                        <span class="k">return</span> <span class="n">method</span><span class="o">.</span><span class="na">getName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="s">"test1"</span><span class="o">);</span>
                    <span class="o">}</span>
                <span class="o">};</span>
            <span class="o">}</span>

            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="nc">Advice</span> <span class="nf">getAdvice</span><span class="o">()</span> <span class="o">{</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nf">MethodInterceptor</span><span class="o">()</span> <span class="o">{</span>
                    <span class="nd">@Override</span>
                    <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">invoke</span><span class="o">(</span><span class="nc">MethodInvocation</span> <span class="n">invocation</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Throwable</span> <span class="o">{</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"before..."</span><span class="o">);</span>
                        <span class="nc">Object</span> <span class="n">result</span> <span class="o">=</span> <span class="n">invocation</span><span class="o">.</span><span class="na">proceed</span><span class="o">();</span>
                        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"after..."</span><span class="o">);</span>
                        <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
                    <span class="o">}</span>
                <span class="o">};</span>
            <span class="o">}</span>

            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isPerInstance</span><span class="o">()</span> <span class="o">{</span>
                <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">});</span>
        <span class="nc">Azh3ngInterfaceImpl</span> <span class="n">azh3ngInterfaceImpl</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Azh3ngInterfaceImpl</span><span class="o">)</span> <span class="n">proxyFactory</span><span class="o">.</span><span class="na">getProxy</span><span class="o">();</span>
        <span class="n">azh3ngInterfaceImpl</span><span class="o">.</span><span class="na">test1</span><span class="o">();</span>
        <span class="n">azh3ngInterfaceImpl</span><span class="o">.</span><span class="na">test2</span><span class="o">();</span>
        <span class="cm">/*
           output:
           before...
           test1...
           after...
           test2
         */</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="选择-cglib-或-jdk-动态代理">选择 cglib 或 jdk 动态代理</h2>
<p>ProxyFactory 在生成代理对象之前需要决定使用 JDK 动态代理还是 CGLIB 技术</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">org.springframework.aop.framework.ProxyFactory#getProxy()</code>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">org.springframework.aop.framework.ProxyCreatorSupport#createAopProxy</code>
        <ul>
          <li><code class="language-plaintext highlighter-rouge">org.springframework.aop.framework.AopProxyFactory#createAopProxy</code>
            <ul>
              <li><code class="language-plaintext highlighter-rouge">org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy</code></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// config就是ProxyFactory对象</span>
<span class="c1">// optimize为true,或proxyTargetClass为true,或用户没有给ProxyFactory对象添加interface</span>
<span class="k">if</span> <span class="o">(</span><span class="n">config</span><span class="o">.</span><span class="na">isOptimize</span><span class="o">()</span> <span class="o">||</span> <span class="n">config</span><span class="o">.</span><span class="na">isProxyTargetClass</span><span class="o">()</span> <span class="o">||</span> <span class="n">hasNoUserSuppliedProxyInterfaces</span><span class="o">(</span><span class="n">config</span><span class="o">))</span> <span class="o">{</span>
    <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">targetClass</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">getTargetClass</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">targetClass</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">AopConfigException</span><span class="o">(</span><span class="s">"TargetSource cannot determine target class: "</span> <span class="o">+</span> <span class="s">"Either an interface or a target is required for proxy creation."</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="c1">// targetClass是接口，直接使用Jdk动态代理</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">targetClass</span><span class="o">.</span><span class="na">isInterface</span><span class="o">()</span> <span class="o">||</span> <span class="nc">Proxy</span><span class="o">.</span><span class="na">isProxyClass</span><span class="o">(</span><span class="n">targetClass</span><span class="o">))</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">JdkDynamicAopProxy</span><span class="o">(</span><span class="n">config</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="c1">// 使用Cglib</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">ObjenesisCglibAopProxy</span><span class="o">(</span><span class="n">config</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
    <span class="c1">// 使用Jdk动态代理</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">JdkDynamicAopProxy</span><span class="o">(</span><span class="n">config</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">createAopProxy(AdvisedSupport config)</code> 方法接受的参数类型为 <code class="language-plaintext highlighter-rouge">AdvisedSupport extends ProxyConfig</code>，同时 <code class="language-plaintext highlighter-rouge">ProxyFactory extends ProxyCreatorSupport extends AdvisedSupport</code>，实际传入的也是 ProxyFactory。</p>

<p><strong>1. 选择 CGLIB：</strong></p>
<ul>
  <li>运行环境不是 GraalVM</li>
  <li>并且
    <ul>
      <li>ProxyFactory 的 <code class="language-plaintext highlighter-rouge">optimize</code> 为 true（是否优化，默认为 false。在以前 CGLIB 代理方式运行效率比 JDK 动态代理高，当设置了 optimize = true 则使用 CGLIB，但随着 JDK 版本升级，效率逐渐提升）</li>
      <li>或</li>
      <li>ProxyFactory 的 <code class="language-plaintext highlighter-rouge">proxyTargetClass</code> 为 true（是否<strong>直接代理指定类</strong>，默认为 false，当设置为 true，则不再关心当前类是否有实现接口）</li>
      <li>或</li>
      <li><code class="language-plaintext highlighter-rouge">hasNoUserSuppliedProxyInterfaces(config) == true</code>（开发者没有具体指定代理的接口，通常为 true）</li>
    </ul>
  </li>
  <li>并且被代理的类不是接口</li>
  <li>并且被代理的类不是由 JDK 生成的代理对象</li>
</ul>

<p><strong>2. 选择 JDK 动态代理</strong></p>
<ul>
  <li>运行环境是 GraalVM</li>
  <li>或</li>
  <li><code class="language-plaintext highlighter-rouge">optimize == false &amp;&amp; proxyTargetClass == false &amp;&amp; hasNoUserSuppliedProxyInterfaces(config) == false</code></li>
  <li>或</li>
  <li>被代理的类是接口</li>
  <li>或被代理的类是由 JDK 生成的代理对象</li>
</ul>

<p><strong>小结：</strong> 除非被代理的类是接口，或者被代理的类是由 JDK 生成的代理对象，否则 ProxyFactory 默认会选择使用 CGLIB 代理方式生成代理对象。</p>]]></content><author><name>azh3ng</name></author><category term="Spring" /><category term="Proxy" /><summary type="html"><![CDATA[Spring 对 JDK动态代理 和 CGLIB动态代理 进行了一定程度的封装，提供一个类 ProxyFactory，更加方便的创建代理对象。 创建代理对象 代码示例： public interface Azh3ngInterface { void test(); } public static class Azh3ngInterfaceImpl implements Azh3ngInterface { public void test() { System.out.println("test..."); } } public class TestProxyFactory { public static void main(String[] args) { Azh3ngInterfaceImpl target = new Azh3ngInterfaceImpl(); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAdvice(new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before..."); Object result = invocation.proceed(); System.out.println("after..."); return result; } }); Azh3ngInterface azh3ngService = (Azh3ngInterface) proxyFactory.getProxy(); azh3ngService.test(); /* output: before... test... after... */ } } 通过使用 ProxyFactory，可以不再关心用 CGlib 还是 jdk 动态代理，ProxyFactory 会自动判断，如果 Azh3ngInterfaceImpl 实现了接口，那么 ProxyFactory 底层就会用 JDK 动态代理，如果没有实现接口，就会用 CGlib 技术，上面的代码，就是由于 Azh3ngInterfaceImpl 实现了 Azh3ngInterface 接口，所以最后产生的代理对象是 Azh3ngInterface 类型 Advice Advice 可以理解为自定义的代理逻辑。 在调用 proxyFactory.addAdvice() 时可以传入各式的 Advice，可以分类为： Before Advice（org.springframework.aop.MethodBeforeAdvice）：方法之前执行 After returning advice（com.tuling.aop.advice.Azh3ngAfterReturningAdvice）：方法 return 后执行 After throwing advice（org.springframework.aop.ThrowsAdvice）：方法抛异常后执行 ThrowsAdvice 接口没有定义方法，需要开发者根据规范自定义方法，可以区分控制不同的异常处理不同逻辑 After (finally) advice（``）：方法执行完 finally 之后执行，这是最后的，比 return 更后 Around advice（org.aopalliance.intercept.MethodInterceptor）：功能最强大的 Advice，可以自定义执行顺序 在添加 Advice 后，ProxyFactory 生成的代理对象，会根据添加的顺序执行所有 Advice（[[责任链模式]]） Pointcut Pointcut 中文译为切点，可以理解为通过一些过滤条件，筛选出需要被代理的方法 Advisor Advisor 是 Pointcut 和 Advice 的组合，通过 Pointcut 指定（筛选）被代理的方法，通过 Advice 自定义代理逻辑 在单独使用 Advice 时无法细粒度的控制不同方法执行不同的逻辑，Advisor 可以做到。 假设一个 Azh3ngInterfaceImpl 类中有两个方法，按上面的例子，这两个方法都会被代理和增强，通过使用 Advisor，可以控制到具体代理某个或某些方法，比如： public class Azh3ngInterfaceImpl { public void test1() { System.out.println("test1..."); } public void test2() { System.out.println("test2..."); } } public class TestProxyFactory { public static void main(String[] args) { Azh3ngInterfaceImpl target = new Azh3ngInterfaceImpl(); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAdvisor(new PointcutAdvisor() { @Override public Pointcut getPointcut() { return new StaticMethodMatcherPointcut() { @Override public boolean matches(Method method, Class&lt;?&gt; targetClass) { // 仅当方法名为 “test1” 时，才执行 Advice return method.getName().equals("test1"); } }; } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before..."); Object result = invocation.proceed(); System.out.println("after..."); return result; } }; } @Override public boolean isPerInstance() { return false; } }); Azh3ngInterfaceImpl azh3ngInterfaceImpl = (Azh3ngInterfaceImpl) proxyFactory.getProxy(); azh3ngInterfaceImpl.test1(); azh3ngInterfaceImpl.test2(); /* output: before... test1... after... test2 */ } } 选择 cglib 或 jdk 动态代理 ProxyFactory 在生成代理对象之前需要决定使用 JDK 动态代理还是 CGLIB 技术 org.springframework.aop.framework.ProxyFactory#getProxy() org.springframework.aop.framework.ProxyCreatorSupport#createAopProxy org.springframework.aop.framework.AopProxyFactory#createAopProxy org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy // config就是ProxyFactory对象 // optimize为true,或proxyTargetClass为true,或用户没有给ProxyFactory对象添加interface if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class&lt;?&gt; targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // targetClass是接口，直接使用Jdk动态代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 使用Cglib return new ObjenesisCglibAopProxy(config); } else { // 使用Jdk动态代理 return new JdkDynamicAopProxy(config); } createAopProxy(AdvisedSupport config) 方法接受的参数类型为 AdvisedSupport extends ProxyConfig，同时 ProxyFactory extends ProxyCreatorSupport extends AdvisedSupport，实际传入的也是 ProxyFactory。 1. 选择 CGLIB： 运行环境不是 GraalVM 并且 ProxyFactory 的 optimize 为 true（是否优化，默认为 false。在以前 CGLIB 代理方式运行效率比 JDK 动态代理高，当设置了 optimize = true 则使用 CGLIB，但随着 JDK 版本升级，效率逐渐提升） 或 ProxyFactory 的 proxyTargetClass 为 true（是否直接代理指定类，默认为 false，当设置为 true，则不再关心当前类是否有实现接口） 或 hasNoUserSuppliedProxyInterfaces(config) == true（开发者没有具体指定代理的接口，通常为 true） 并且被代理的类不是接口 并且被代理的类不是由 JDK 生成的代理对象 2. 选择 JDK 动态代理 运行环境是 GraalVM 或 optimize == false &amp;&amp; proxyTargetClass == false &amp;&amp; hasNoUserSuppliedProxyInterfaces(config) == false 或 被代理的类是接口 或被代理的类是由 JDK 生成的代理对象 小结： 除非被代理的类是接口，或者被代理的类是由 JDK 生成的代理对象，否则 ProxyFactory 默认会选择使用 CGLIB 代理方式生成代理对象。]]></summary></entry></feed>