<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Java Lambda 解析 - category - 沐木</title><link>https://oldletter.cn/categories/java-lambda-%E8%A7%A3%E6%9E%90/</link><description>Java Lambda 解析 - category - 沐木</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Wed, 10 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://oldletter.cn/categories/java-lambda-%E8%A7%A3%E6%9E%90/" rel="self" type="application/rss+xml"/><item><title>Java 为什么不能直接解析 Lambda？从一个 ORM 查询说起</title><link>https://oldletter.cn/posts/java-lambda-parse-01-why/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-lambda-parse-01-why/</guid><description><![CDATA[<blockquote>
<p>📅 2026-06-10 | 🏷️ Java, Lambda, JVM | 📖 阅读约 15 分钟</p></blockquote>
<hr>
<h2 id="问题起源">问题起源</h2>
<p>做 Java ORM 的时候，更让人羡慕的就是 C# 的 LINQ：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// C# — 直接写 Lambda，框架自动转 SQL</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">users</span> <span class="p">=</span> <span class="k">await</span> <span class="n">context</span><span class="p">.</span><span class="n">Users</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">u</span> <span class="p">=&gt;</span> <span class="n">u</span><span class="p">.</span><span class="n">Age</span> <span class="p">&gt;</span> <span class="m">18</span> <span class="p">&amp;&amp;</span> <span class="n">u</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;Tom&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">OrderBy</span><span class="p">(</span><span class="n">u</span> <span class="p">=&gt;</span> <span class="n">u</span><span class="p">.</span><span class="n">Id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">ToListAsync</span><span class="p">();</span></span></span></code></pre></div></div>
<p>我们也想在 Java 里这么写：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 理想中的 Java ORM 写法</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">query</span><span class="p">.</span><span class="na">where</span><span class="p">(</span><span class="n">u</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="na">getAge</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">18</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="na">getName</span><span class="p">().</span><span class="na">equals</span><span class="p">(</span><span class="s">&#34;Tom&#34;</span><span class="p">));</span></span></span></code></pre></div></div>
<p>但试了一下就发现，<strong>Java 运行时核心拿不到 Lambda 里面的结构</strong>。<code>u -&gt; u.getAge() &gt; 18</code> 这个表达式，运行时只能执行它（传一个 User 进去，返回 true/false），但拿不到&quot;age &gt; 18&quot;这个结构信息。</p>]]></description></item><item><title>编译期方案：用 SerializedLambda 和注解处理器提取属性名</title><link>https://oldletter.cn/posts/java-lambda-parse-02-compile-time/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-lambda-parse-02-compile-time/</guid><description><![CDATA[<blockquote>
<p>📅 2026-06-10 | 🏷️ MyBatis Plus, QueryDSL, SerializedLambda, APT | 📖 阅读约 25 分钟</p></blockquote>
<hr>
<h2 id="前言">前言</h2>
<p>上一篇说了 Java 的 Lambda 编译后丢失了 AST 信息，不能类似 C# 一样直接解析。但框架开发者们没有放弃，而是找到了其他办法。</p>
<p>这一篇讲两种<strong>编译期方案</strong>：它们不解析 Lambda 本身，而是利用 Java 的其他机制来提取属性信息。</p>
<ul>
<li><strong>方案 1: SerializedLambda</strong> — MyBatis Plus 用的方式，从方法引用中提取 getter 方法名</li>
<li><strong>方案 2: Annotation Processor</strong> — QueryDSL 用的方式，编译期扫描实体字段，生成元模型类</li>
</ul>
<hr>
<h2 id="方案-1-serializedlambdamybatis-plus-路线">方案 1: SerializedLambda（MyBatis Plus 路线）</h2>
<h3 id="发现过程">发现过程</h3>
<p>这个方案的关键发现是：<strong>当一个 Lambda 表达式实现了 <code>Serializable</code> 接口时，Java 编译器会额外生成一个 <code>writeReplace()</code> 方法</strong>。</p>
<p>这个方法返回一个 <code>SerializedLambda</code> 对象，里面包含了：</p>
<ul>
<li><code>implClass</code> — 实现方法所在的类名</li>
<li><code>implMethodName</code> — 实现方法的方法名</li>
<li><code>implMethodSignature</code> — 方法签名</li>
<li><code>capturedArgCount</code> — 捕获的外部变量数量</li>
</ul>
<p>对于 <code>User::getAge</code> 这种方法引用，<code>implMethodName</code> 就是 <code>&quot;getAge&quot;</code>，这就是我们要的属性信息。</p>]]></description></item><item><title>深入字节码：ASM 和 Javac Plugin 如何真正解析 Lambda</title><link>https://oldletter.cn/posts/java-lambda-parse-03-bytecode-and-ast/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-lambda-parse-03-bytecode-and-ast/</guid><description><![CDATA[<blockquote>
<p>📅 2026-06-10 | 🏷️ ASM, 字节码, Javac Plugin, JVM | 📖 阅读约 30 分钟</p></blockquote>
<hr>
<h2 id="前言">前言</h2>
<p>上一篇的两种方案（SerializedLambda、APT）有一个共同的限制：<strong>不能解析 Lambda 表达式</strong>，只能解析方法引用。</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// ✅ 能解析 — 方法引用，有明确的方法名</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Getter</span><span class="o">&lt;</span><span class="n">User</span><span class="p">,</span><span class="w"> </span><span class="n">Integer</span><span class="o">&gt;</span><span class="w"> </span><span class="n">getter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">User</span><span class="p">::</span><span class="n">getAge</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// ❌ 不能解析 — Lambda 表达式，方法名是 &#34;lambda$main$0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Predicate</span><span class="o">&lt;</span><span class="n">User</span><span class="o">&gt;</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">u</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="na">getAge</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">18</span><span class="p">;</span></span></span></code></pre></div></div>
<p>这一篇的两种方案可以直接解析 <code>u -&gt; u.getAge() &gt; 18</code>，但代价是需要深入 JVM 底层。</p>
<hr>
<h2 id="方案-3-asm-字节码分析">方案 3: ASM 字节码分析</h2>
<h3 id="原理">原理</h3>
<p>Lambda 表达式编译后，编译器会生成一个<strong>私有静态方法</strong>，这个方法包含了 Lambda 的实际逻辑。比如：</p>]]></description></item><item><title>JDK Proxy：不解析 Lambda，但最实用的工程方案</title><link>https://oldletter.cn/posts/java-lambda-parse-04-proxy-and-sql/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-lambda-parse-04-proxy-and-sql/</guid><description><![CDATA[<blockquote>
<p>📅 2026-06-10 | 🏷️ JDK Proxy, DSL, 规则引擎, SQL | 📖 阅读约 20 分钟</p></blockquote>
<hr>
<h2 id="前言">前言</h2>
<p>前四种方案都在想方设法<strong>解析 Lambda 的结构</strong>：</p>
<ul>
<li>SerializedLambda：从方法引用中提取方法名</li>
<li>APT：编译期扫描字段</li>
<li>ASM：读取编译后的字节码</li>
<li>Javac Plugin：拦截编译器的 AST</li>
</ul>
<p>第五种方案换个思路：<strong>不解析 Lambda，用代理对象录制 getter 调用路径</strong>。</p>
<p>这个方案实现入门门槛低，但工程价值反而更高，因为它的 API 设计天然支持链式 DSL，非常适合规则引擎、数据权限、工作流条件等动态场景。</p>
<hr>
<h2 id="核心原理">核心原理</h2>
<p>JDK 的 <code>java.lang.reflect.Proxy</code> 可以动态创建接口的代理对象。代理对象的所有方法调用都会被 <code>InvocationHandler</code> 拦截。</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * JDK Proxy 的核心机制：
</span></span></span><span class="line"><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 1. Proxy.newProxyInstance() 创建一个实现了指定接口的代理对象
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 2. 代理对象的所有方法调用都会转发到 InvocationHandler.invoke()
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 3. 在 invoke() 中，我们可以获取方法名、参数等信息
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 4. 返回一个默认值（让调用方不报错）
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span></span></span></code></pre></div></div>
<h3 id="创建代理对象">创建代理对象</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 创建实体接口的代理。
</span></span></span><span class="line"><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 注意：JDK Proxy 只能代理接口，不能代理具体类。
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 所以实体类需要先提取一个接口（如 UserEntity）。
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@SuppressWarnings</span><span class="p">(</span><span class="s">&#34;unchecked&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="w"> </span><span class="n">T</span><span class="w"> </span><span class="nf">createProxy</span><span class="p">(</span><span class="n">Class</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="w"> </span><span class="n">entityClass</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="w"> </span><span class="n">Proxy</span><span class="p">.</span><span class="na">newProxyInstance</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">entityClass</span><span class="p">.</span><span class="na">getClassLoader</span><span class="p">(),</span><span class="w">          </span><span class="c1">// 类加载器</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"> </span><span class="n">Class</span><span class="o">[]</span><span class="p">{</span><span class="n">entityClass</span><span class="p">},</span><span class="w">              </span><span class="c1">// 要代理的接口</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"> </span><span class="n">RecordingHandler</span><span class="p">()</span><span class="w">                 </span><span class="c1">// 调用处理器</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div></div>
<h3 id="录制-getter-调用">录制 getter 调用</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 代理对象的方法调用处理器。
</span></span></span><span class="line"><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 当代理对象的任何方法被调用时，invoke() 会被触发。
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 我们在这里记录方法名，然后返回一个占位值。
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">class</span> <span class="nc">RecordingHandler</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">InvocationHandler</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">invoke</span><span class="p">(</span><span class="n">Object</span><span class="w"> </span><span class="n">proxy</span><span class="p">,</span><span class="w"> </span><span class="n">Method</span><span class="w"> </span><span class="n">method</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">throws</span><span class="w"> </span><span class="n">Throwable</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">methodName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">method</span><span class="p">.</span><span class="na">getName</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 如果当前正在录制，记录方法名</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">isRecording</span><span class="p">()</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">method</span><span class="p">.</span><span class="na">getParameterCount</span><span class="p">()</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">recordCall</span><span class="p">(</span><span class="n">methodName</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// proxy.getAge() → 记录 &#34;getAge&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 返回对应类型的默认值（让调用方不报 NPE）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 注意：这些返回值不会被使用，只是让 proxy.getAge() 调用能正常完成</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Class</span><span class="o">&lt;?&gt;</span><span class="w"> </span><span class="n">returnType</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">method</span><span class="p">.</span><span class="na">getReturnType</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">returnType</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kt">int</span><span class="p">.</span><span class="na">class</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">returnType</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">Integer</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">returnType</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kt">long</span><span class="p">.</span><span class="na">class</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">returnType</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">Long</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">0L</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">returnType</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kt">boolean</span><span class="p">.</span><span class="na">class</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">returnType</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">Boolean</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">returnType</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">String</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div></div>
<h3 id="录制流程">录制流程</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 使用 ThreadLocal 保证线程安全</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">ThreadLocal</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="n">CALL_PATH</span><span class="w"> </span><span class="o">=</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">new</span><span class="w"> </span><span class="n">ThreadLocal</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 开始录制</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">startRecording</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">CALL_PATH</span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 调用 getter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">UserEntity</span><span class="w"> </span><span class="n">proxy</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">createProxy</span><span class="p">(</span><span class="n">UserEntity</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">proxy</span><span class="p">.</span><span class="na">getAge</span><span class="p">();</span><span class="w">  </span><span class="c1">// InvocationHandler 拦截 → 记录 &#34;getAge&#34; → 返回 0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 停止录制，获取路径</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stopRecording</span><span class="p">();</span><span class="w"> </span><span class="c1">// [&#34;getAge&#34;]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">String</span><span class="w"> </span><span class="n">propName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">resolvePropertyName</span><span class="p">(</span><span class="n">path</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">0</span><span class="p">));</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="s">&#34;age&#34;</span></span></span></code></pre></div></div>
<h3 id="属性名解析">属性名解析</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * getter 方法名 → 属性名。
</span></span></span><span class="line"><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 和 MyBatis Plus 的 PropertyResolver 规则一致：
</span></span></span><span class="line"><span class="cl"><span class="cm"> * - getAge → age
</span></span></span><span class="line"><span class="cl"><span class="cm"> * - isDeleted → deleted
</span></span></span><span class="line"><span class="cl"><span class="cm"> * - getUserName → userName
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">static</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">resolvePropertyName</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">methodName</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">methodName</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">&#34;get&#34;</span><span class="p">)</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">methodName</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">3</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">rest</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">methodName</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">3</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">Character</span><span class="p">.</span><span class="na">toLowerCase</span><span class="p">(</span><span class="n">rest</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">0</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">+</span><span class="w"> </span><span class="n">rest</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">methodName</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">&#34;is&#34;</span><span class="p">)</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">methodName</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">2</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">rest</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">methodName</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">2</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">Character</span><span class="p">.</span><span class="na">toLowerCase</span><span class="p">(</span><span class="n">rest</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">0</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">+</span><span class="w"> </span><span class="n">rest</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">methodName</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div></div>
<hr>
<h2 id="链式-dsl">链式 DSL</h2>
<p>Proxy 路线上限的优势是<strong>天然支持链式 DSL</strong>。这是其他四条路线都不具备的。</p>]]></description></item><item><title>JDK 8 vs 17 vs 26：Lambda 解析性能实测</title><link>https://oldletter.cn/posts/java-lambda-parse-05-benchmark/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-lambda-parse-05-benchmark/</guid><description><![CDATA[<blockquote>
<p>📅 2026-06-10 | 🏷️ JVM, 性能, JMH, JDK | 📖 阅读约 20 分钟</p></blockquote>
<hr>
<h2 id="前言">前言</h2>
<p>前四篇实现了五条 Lambda 解析路线。这一篇用 JMH 在三个 JDK 版本上跑基准测试，看看：</p>
<ol>
<li>五条路线的性能差距到底有多大？</li>
<li>JDK 升级对性能有多大影响？</li>
<li>实际使用中应该怎么选？</li>
</ol>
<hr>
<h2 id="测试环境">测试环境</h2>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>硬件: macOS ARM64 (Apple Silicon)
JDK 8:  BellSoft Liberica JDK 8u422 (aarch64)
JDK 17: Homebrew OpenJDK 17.0.19
JDK 26: Homebrew OpenJDK 26.0.1
JMH:    1.37</code></pre></div>
<p>JMH 参数：</p>
<ul>
<li>Fork = 2（2 个 JVM 进程，避免 JVM 特异性）</li>
<li>Warmup = 5（5 轮预热）</li>
<li>Measurement = 8（8 轮测量）</li>
<li>Mode = AverageTime（平均耗时，越小越好）</li>
</ul>
<hr>
<h2 id="重要前提缓存">重要前提：缓存</h2>
<p><strong>实际使用中，所有框架都会缓存解析结果。</strong></p>]]></description></item></channel></rss>