<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>代码是我心中的一首诗</title>
    <description>本站是Taketoday的技术分享博客。内容涵盖Java核心技术、Java测试工具、Spring生态、微服务、响应式编程、设计模式、Gradle、Maven、GraphQL等相关的研究与知识分享。</description>
    <link>http://foundry4j.github.io/</link>
    <atom:link href="http://foundry4j.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 12 Nov 2026 16:14:14 +0000</pubDate>
    <lastBuildDate>Thu, 12 Nov 2026 16:14:14 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>使用Spring AI和OAuth2进行MCP授权</title>
        <description>&lt;h2 id=&quot;1-概述&quot;&gt;1. 概述&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.baeldung.com/spring-ai-model-context-protocol-mcp&quot;&gt;模型上下文协议(MCP)&lt;/a&gt;允许AI模型通过安全的API访问业务数据，构建处理敏感信息的MCP服务器时，我们需要适当的授权来控制哪些人可以访问哪些数据。&lt;a href=&quot;https://www.baeldung.com/spring-security-oauth-auth-server&quot;&gt;OAuth2&lt;/a&gt;提供了基于令牌的安全性，可以与MCP系统完美兼容。我们无需构建自定义身份验证，而是可以使用OAuth2标准来保护MCP服务器并管理客户端访问。&lt;/p&gt;

&lt;p&gt;在本文中，我们将了解如何使用&lt;a href=&quot;https://www.baeldung.com/spring-ai&quot;&gt;Spring AI&lt;/a&gt;和OAuth2保护MCP服务器和客户端的安全。我们将构建一个包含三个组件的完整示例：授权服务器、带有计算器工具的受保护MCP服务器，以及处理用户和系统请求的客户端。&lt;/p&gt;

&lt;h2 id=&quot;2-mcp安全架构&quot;&gt;2. MCP安全架构&lt;/h2&gt;

&lt;p&gt;为了确保MCP服务器的安全，了解如何在MCP服务器之前集成授权服务器非常重要。&lt;/p&gt;

&lt;p&gt;我们的系统包含一个授权服务器，用于签发具有适当权限的JWT令牌；一个MCP服务器，用于验证令牌并控制对计算器工具的访问。此外，还有一个MCP客户端，用于获取令牌并管理不同请求类型的身份验证：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/springai/springaioath2mcpauthorization01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP服务器充当OAuth2资源服务器，它们在处理任何操作之前都会检查请求标头中的JWT令牌&lt;/strong&gt;。这将安全问题与业务逻辑区分开来，客户端从OAuth2授权服务器获取访问令牌。然后，客户端将这些令牌包含在MCP请求中。最后，MCP服务器在允许操作之前验证令牌。&lt;/p&gt;

&lt;h2 id=&quot;3-构建授权服务器&quot;&gt;3. 构建授权服务器&lt;/h2&gt;

&lt;p&gt;我们将从授权服务器开始，因为其他组件都依赖于它。&lt;/p&gt;

&lt;h3 id=&quot;31-添加依赖&quot;&gt;3.1 添加依赖&lt;/h3&gt;

&lt;p&gt;我们需要添加&lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-authorization-server&quot;&gt;OAuth2授权服务器&lt;/a&gt;依赖才能使用它：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-oauth2-authorization-server&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-web&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;32-配置授权服务器&quot;&gt;3.2 配置授权服务器&lt;/h3&gt;

&lt;p&gt;让我们在application.yml中配置服务器：&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;9000&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;security&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;user&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;password&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;oauth2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;authorizationserver&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;oidc-client&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                        &lt;span class=&quot;na&quot;&gt;registration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                            &lt;span class=&quot;na&quot;&gt;client-id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mcp-client&quot;&lt;/span&gt;
                            &lt;span class=&quot;na&quot;&gt;client-secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{noop}mcp-secret&quot;&lt;/span&gt;
                            &lt;span class=&quot;na&quot;&gt;client-authentication-methods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;client_secret_basic&quot;&lt;/span&gt;
                            &lt;span class=&quot;na&quot;&gt;authorization-grant-types&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;authorization_code&quot;&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;client_credentials&quot;&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;refresh_token&quot;&lt;/span&gt;
                            &lt;span class=&quot;na&quot;&gt;redirect-uris&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://localhost:8080/authorize/oauth2/code/authserver&quot;&lt;/span&gt;
                            &lt;span class=&quot;na&quot;&gt;scopes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;openid&quot;&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;profile&quot;&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;calc.read&quot;&lt;/span&gt;
                                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;calc.write&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;这会在端口9000上设置一个授权服务器，其客户端支持授权码流(针对用户)和客户端凭证流(针对系统)&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;4-保护mcp服务器&quot;&gt;4. 保护MCP服务器&lt;/h2&gt;

&lt;p&gt;现在我们将创建一个需要OAuth2令牌并提供计算器工具的MCP服务器。&lt;/p&gt;

&lt;h3 id=&quot;41-配置mcp服务器依赖&quot;&gt;4.1 配置MCP服务器依赖&lt;/h3&gt;

&lt;p&gt;现在，让我们添加&lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-resource-server&quot;&gt;OAuth2资源服务器&lt;/a&gt;支持：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-mcp-server-webmvc&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0-M7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-oauth2-resource-server&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;42-服务器配置&quot;&gt;4.2 服务器配置&lt;/h3&gt;

&lt;p&gt;让我们将MCP服务器配置为OAuth2资源服务器：&lt;/p&gt;

&lt;div class=&quot;language-properties highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;py&quot;&gt;server.port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;8090&lt;/span&gt;

&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.resourceserver.jwt.issuer-uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://localhost:9000&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;￼&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.ai.mcp.server.enabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.ai.mcp.server.name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mcp-calculator-server&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.ai.mcp.server.version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1.0.0&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.ai.mcp.server.stdio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当我们设置颁发者URI时，Spring Boot会自动处理JWT验证。&lt;strong&gt;现在，每个发送到MCP服务器的请求都需要在Authorization标头中携带有效的JWT令牌&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&quot;43-创建mcp工具&quot;&gt;4.3 创建MCP工具&lt;/h3&gt;

&lt;p&gt;LLM学生通常数学不好，因此，我们需要为他们提供一些工具，让他们能够根据需求计算结果：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Add two numbers&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CalculationResult&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@ToolParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;First number&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@ToolParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Second number&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CalculationResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;addition&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Multiply two numbers&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CalculationResult&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@ToolParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;First number&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@ToolParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Second number&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CalculationResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;multiplication&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;安全配置会自动保护所有MCP工具，没有有效令牌的请求将被拒绝。这些工具会被添加到上下文中，并在每次用户查询时提供给LLM&lt;/strong&gt;。然后，LLM会决定使用哪个工具来响应用户查询。&lt;/p&gt;

&lt;h2 id=&quot;5-构建mcp客户端&quot;&gt;5. 构建MCP客户端&lt;/h2&gt;

&lt;p&gt;现在，我们需要客户端处理最复杂的部分，因为它需要处理用户请求和系统初始化。&lt;/p&gt;

&lt;h3 id=&quot;51-客户端依赖&quot;&gt;5.1 客户端依赖&lt;/h3&gt;

&lt;p&gt;我们首先添加&lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-mcp-client-webflux&quot;&gt;mcp-client&lt;/a&gt;和&lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-client&quot;&gt;oauth2-client&lt;/a&gt;依赖：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-mcp-client-webflux&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-oauth2-client&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;52-客户端配置&quot;&gt;5.2 客户端配置&lt;/h3&gt;

&lt;p&gt;现在，我们需要在application.properties中配置两个OAuth2客户端注册：&lt;/p&gt;

&lt;div class=&quot;language-properties highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;py&quot;&gt;server.port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;

&lt;span class=&quot;py&quot;&gt;spring.ai.mcp.client.sse.connections.server1.url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://localhost:8090&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.ai.mcp.client.type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SYNC&lt;/span&gt;

&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.provider.authserver.issuer-uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://localhost:9000&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# OAuth2 Client for User-Initiated Requests (Authorization Code Grant)
&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver.client-id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mcp-client&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver.client-secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mcp-secret&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver.authorization-grant-type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;authorization_code&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver.provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;authserver&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver.scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;openid,profile,mcp.read,mcp.write&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver.redirect-uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{baseUrl}/authorize/oauth2/code/{registrationId}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# OAuth2 Client for Machine-to-Machine Requests (Client Credentials Grant)
&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver-client-credentials.client-id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mcp-client&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver-client-credentials.client-secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mcp-secret&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver-client-credentials.authorization-grant-type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;client_credentials&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver-client-credentials.provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;authserver&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;spring.security.oauth2.client.registration.authserver-client-credentials.scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mcp.read,mcp.write&lt;/span&gt;

&lt;span class=&quot;py&quot;&gt;spring.ai.anthropic.api-key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;${ANTHROPIC_API_KEY}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们需要两个注册来对应不同的身份验证流程，authserver注册使用授权码流程来处理用户发起的请求，authserver-client-credentials注册使用客户端凭据流程来处理系统启动。&lt;/p&gt;

&lt;h3 id=&quot;53-安全配置&quot;&gt;5.3 安全配置&lt;/h3&gt;

&lt;p&gt;现在让我们设置Spring Security来处理OAuth2：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;WebClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;webClientBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;McpSyncClientExchangeFilterFunction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WebClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;SecurityFilterChain&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;securityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorizeHttpRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anyRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;permitAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;oauth2Client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Customizer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withDefaults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;csrf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;CsrfConfigurer:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;disable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;54-选择正确的token&quot;&gt;5.4 选择正确的Token&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;这里的全部挑战在于为每个请求选择正确的令牌&lt;/strong&gt;，我们需要一个自定义的ExchangeFilterFunction实现来检测请求上下文：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;McpSyncClientExchangeFilterFunction&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExchangeFilterFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientCredentialsOAuth2AuthorizedClientProvider&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientCredentialTokenProvider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientCredentialsOAuth2AuthorizedClientProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServletOAuth2AuthorizedClientExchangeFilterFunction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientRegistrationRepository&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientRegistrationRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AUTHORIZATION_CODE_CLIENT_REGISTRATION_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;authserver&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CLIENT_CREDENTIALS_CLIENT_REGISTRATION_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;authserver-client-credentials&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;McpSyncClientExchangeFilterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OAuth2AuthorizedClientManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                                               &lt;span class=&quot;nc&quot;&gt;ClientRegistrationRepository&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientRegistrationRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServletOAuth2AuthorizedClientExchangeFilterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setDefaultClientRegistrationId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AUTHORIZATION_CODE_CLIENT_REGISTRATION_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clientRegistrationRepository&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientRegistrationRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExchangeFunction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RequestContextHolder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRequestAttributes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServletRequestAttributes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getClientCredentialsAccessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requestWithToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setBearerAuth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;exchange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;requestWithToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getClientCredentialsAccessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientRegistration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clientRegistrationRepository&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;findByRegistrationId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CLIENT_CREDENTIALS_CLIENT_REGISTRATION_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2AuthorizationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withClientRegistration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientRegistration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;principal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AnonymousAuthenticationToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client-credentials-client&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;client-credentials-client&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;nc&quot;&gt;AuthorityUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createAuthorityList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ROLE_ANONYMOUS&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clientCredentialTokenProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAccessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTokenValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Consumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;WebClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;defaultRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;defaultRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;过滤器会检查是否存在活动的Web请求&lt;/strong&gt;，如果存在，则使用用户的授权码令牌；如果不存在(例如在应用启动时)，则使用客户端凭据。&lt;/p&gt;

&lt;h2 id=&quot;6-使用安全的mcp系统&quot;&gt;6. 使用安全的MCP系统&lt;/h2&gt;

&lt;p&gt;现在我们已经涵盖了所有组件，让我们看看如何有效地使用安全的MCP系统。&lt;/p&gt;

&lt;h3 id=&quot;61-创建聊天客户端&quot;&gt;6.1 创建聊天客户端&lt;/h3&gt;

&lt;p&gt;让我们用ChatClient将所有内容连接在一起：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;ChatClient&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;chatClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ChatClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chatClientBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;McpSyncClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chatClientBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;defaultToolCallbacks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mcpClients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;62-发起请求&quot;&gt;6.2 发起请求&lt;/h3&gt;

&lt;p&gt;现在我们可以正常使用ChatClient了，安全机制会自动生效：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/calculate&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@RequestParam&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@RegisteredOAuth2AuthorizedClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;authserver&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2AuthorizedClient&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authorizedClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Please calculate the following mathematical expression using the available calculator tools: %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chatClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在启动过程中，MCP客户端初始化使用客户端凭证令牌。当用户通过Web界面发出请求时，MCP客户端会使用其授权码令牌。&lt;/p&gt;

&lt;h2 id=&quot;7-验证设置&quot;&gt;7. 验证设置&lt;/h2&gt;

&lt;p&gt;为了理解该应用程序的工作原理，我们需要研究它产生的结果。&lt;strong&gt;在启动任何应用程序之前，我们必须为LLM设置所需的环境变量&lt;/strong&gt;。设置完成后，我们首先在端口9000上启动授权服务器。这一点很重要，因为所有其他模块都依赖于授权服务器。然后，我们在端口8090上启动MCP服务器，接着在端口8080上启动MCP客户端。&lt;/p&gt;

&lt;p&gt;测试整个流程变得简单，我们需要访问MCP客户端，然后尝试以下操作：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http://{base_url}:8080/calculate?expression=15+25
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;客户端将从授权服务器获取令牌，使用该令牌调用MCP服务器，并返回计算结果，我们必须确保使用配置文件中指定的凭据登录授权服务器。&lt;/p&gt;

&lt;h2 id=&quot;8-总结&quot;&gt;8. 总结&lt;/h2&gt;

&lt;p&gt;在本教程中，我们探讨了OAuth2如何通过基于标准令牌的授权为MCP系统提供强大的安全性。Spring Security的OAuth2支持能够以最少的配置提供良好的保护。通过分离授权服务器、MCP服务器和MCP客户端，我们创建了一个架构，每个组件都专注于各自的职责，从而提高了灵活性。&lt;/p&gt;
</description>
        <pubDate>Tue, 30 Sep 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/springai/2025/09/30/spring-ai-oath2-mcp-authorization.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/springai/2025/09/30/spring-ai-oath2-mcp-authorization.html</guid>
        
        
        <category>springai</category>
        
      </item>
    
      <item>
        <title>什么是看板</title>
        <description>&lt;h2 id=&quot;1-简介&quot;&gt;1. 简介&lt;/h2&gt;

&lt;p&gt;在本教程中，我们将探索看板(一种重要的项目管理工具)的动态。我们将剖析其核心概念，并探索其在软件开发过程中的实际应用。此外，我们还将深入分析看板带来的好处。&lt;/p&gt;

&lt;h2 id=&quot;2-什么是看板&quot;&gt;2. 什么是看板？&lt;/h2&gt;

&lt;p&gt;看板是一种项目管理工具，它以直观的方式在面板上呈现工作流程。它起源于制造业，尤其是在丰田，彻底改变了生产效率。随着时间的推移，它无缝地融入了计算和&lt;a href=&quot;https://www.baeldung.com/cs/sdlc-vs-stlc&quot;&gt;软件开发领域&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;在科技界，&lt;a href=&quot;https://www.atlassian.com/agile/kanban&quot;&gt;看板&lt;/a&gt;在可视化面板上组织任务，确保团队专注于关键任务。这种方法增强了协作性和灵活性，使其成为现代软件开发的热门选择。&lt;/p&gt;

&lt;h2 id=&quot;3-看板的核心概念&quot;&gt;3. 看板的核心概念&lt;/h2&gt;

&lt;p&gt;看板基于三个关键概念进行运作：可视化工作、限制在制品(WIP)和管理流程。&lt;/p&gt;

&lt;h3 id=&quot;31-通过看板实现工作可视化&quot;&gt;3.1 通过看板实现工作可视化&lt;/h3&gt;

&lt;p&gt;看板通过看板，以动态的视觉方式呈现工作流程。这些看板被划分成代表不同工作流程阶段的列，从而提供整个流程的透明视图。&lt;/p&gt;

&lt;p&gt;工作项以卡片形式呈现，无缝衔接于这些列中，记录从启动到完成的整个过程。这种可视化方式有助于团队轻松跟踪和管理任务。&lt;/p&gt;

&lt;h3 id=&quot;32-限制在制品wip以提高效率&quot;&gt;3.2 限制在制品(WIP)以提高效率&lt;/h3&gt;

&lt;p&gt;看板的一个关键概念涉及实施在制品(WIP)限制，从战略上限制每列允许的任务数量。&lt;/p&gt;

&lt;p&gt;这种刻意的限制可以防止团队过度劳累，从而促进更专注、更高效的工作流程。管理正在进行的任务可以帮助团队精准定位瓶颈，提高整体生产力，并保持一致的工作节奏。&lt;/p&gt;

&lt;h3 id=&quot;33-使用连续和拉动系统管理流程&quot;&gt;3.3 使用连续和拉动系统管理流程&lt;/h3&gt;

&lt;p&gt;看板强调了系统中工作流程的连续性，它通过拉动系统实现，即只有当团队做好准备时，工作才会被拉入每个工作流阶段。这种方法可以缓解拥堵，并确保无缝且可预测的任务流。&lt;/p&gt;

&lt;p&gt;通过熟练地管理流程，看板可以提高适应性并提高项目管理流程的整体效率。&lt;/p&gt;

&lt;h2 id=&quot;4-看板组件&quot;&gt;4. 看板组件&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.atlassian.com/agile/kanban/boards&quot;&gt;看板&lt;/a&gt;是看板方法的视觉神经中枢，将工作流程分解为提供透明度、问责制和效率的关键组件：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/kanban01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;41-积压&quot;&gt;4.1 积压&lt;/h3&gt;

&lt;p&gt;待办事项列表(定义优先级和即将开展的工作)代表着看板旅程的起点，在此部分，团队识别并确定即将开展的任务或用户故事的优先级。这些项目已在雷达上，但尚未进入活动工作流。&lt;/p&gt;

&lt;p&gt;待办事项列表是潜在工作的储存库，可让团队规划和准备未来的迭代。&lt;/p&gt;

&lt;h3 id=&quot;42-就绪&quot;&gt;4.2 就绪&lt;/h3&gt;

&lt;p&gt;“就绪”(从待办事项列表过渡到行动)列表示任务的关键过渡点，当任务列表明确定义并准备好采取行动时，它们就会从待办事项列表移至“就绪”列。&lt;/p&gt;

&lt;p&gt;此步骤确保进入活动工作流的工作经过彻底检查并准备执行，它充当检查点，在任务执行之前确保其质量和清晰度。&lt;/p&gt;

&lt;h3 id=&quot;43-进行中&quot;&gt;4.3 进行中&lt;/h3&gt;

&lt;p&gt;“进行中”(跟踪正在进行的工作)列是看板的动态核心，代表团队正在积极开展的任务。随着工作项进入此列，团队将共同专注于执行和完成这些任务。&lt;/p&gt;

&lt;p&gt;该专栏提供了团队当前工作量的实时快照，并强调了限制在制品(WIP)以增强专注度和效率的重要性。&lt;/p&gt;

&lt;h3 id=&quot;44-完成&quot;&gt;4.4 完成&lt;/h3&gt;

&lt;p&gt;看板的最终目的地是“完成”(完成并反思)列，已完成的任务在此结束。它作为已完成工作的视觉提示，并为团队提供了反思其成就的机会。&lt;/p&gt;

&lt;p&gt;这种反思对于持续改进非常有价值，因为团队可以分析已完成的任务以确定未来工作流程中需要优化的领域。&lt;/p&gt;

&lt;h2 id=&quot;5-实施&quot;&gt;5. 实施&lt;/h2&gt;

&lt;p&gt;实施看板涉及一个战略性和迭代过程，以优化工作流程、提高效率并促进持续改进。&lt;/p&gt;

&lt;h3 id=&quot;51-评估当前进程&quot;&gt;5.1 评估当前进程&lt;/h3&gt;

&lt;p&gt;实施看板的第一步是彻底评估现有工作流程，这包括识别当前流程、瓶颈和需要改进的领域。&lt;/p&gt;

&lt;p&gt;团队需要了解他们的工作动态，包括任务类型、依赖关系以及从启动到完成的工作流程，这项评估为根据团队的具体需求定制看板奠定了基础。&lt;/p&gt;

&lt;h3 id=&quot;52-设计看板&quot;&gt;5.2 设计看板&lt;/h3&gt;

&lt;p&gt;根据评估所得的见解，下一步是设计看板。这涉及创建工作流程的可视化表示，其中的列代表不同的阶段，例如待办事项、就绪、进行中和完成。&lt;/p&gt;

&lt;p&gt;设计应该直观，反映团队独特的流程，并方便轻松跟踪每个阶段的任务进展。&lt;/p&gt;

&lt;h3 id=&quot;53-引入wip限制&quot;&gt;5.3 引入WIP限制&lt;/h3&gt;

&lt;p&gt;看板的显著特点之一是对在制品(WIP)数量进行限制，限制每列允许的任务数量可以防止超载，并鼓励更专注的工作流程。&lt;/p&gt;

&lt;p&gt;引入WIP限制可确保团队在开展新工作之前优先完成任务，最终提高效率并减少多任务处理。&lt;/p&gt;

&lt;h3 id=&quot;54-迭代实施和持续改进&quot;&gt;5.4 迭代实施和持续改进&lt;/h3&gt;

&lt;p&gt;看板实施并非一次性事件，而是一个持续迭代的过程，团队应该秉持持续改进的Kaizen理念。&lt;/p&gt;

&lt;p&gt;定期审查看板，收集团队成员的反馈，并相应地调整流程。这种迭代方法可以使看板适应不断变化的项目需求，并持续提高团队效率。&lt;/p&gt;

&lt;h3 id=&quot;55-常见挑战和解决方案&quot;&gt;5.5 常见挑战和解决方案&lt;/h3&gt;

&lt;p&gt;在实施阶段，团队可能会遇到诸如抵制变革、怀疑或难以设置和维护WIP限制等挑战。&lt;/p&gt;

&lt;p&gt;应对这些挑战需要有效的沟通、培训以及对看板原则的坚持。应鼓励团队表达关切，并积极参与转型过程。&lt;/p&gt;

&lt;h3 id=&quot;56-看板在不同行业的集成&quot;&gt;5.6 看板在不同行业的集成&lt;/h3&gt;

&lt;p&gt;虽然看板起源于制造业并在软件开发中得到广泛应用，但其原理适用于各个行业。&lt;/p&gt;

&lt;p&gt;在医疗保健、营销或金融等不同环境中实施看板，凸显了其在优化软件开发之外的工作流程方面的多功能性和有效性。&lt;/p&gt;

&lt;h2 id=&quot;6-软件开发中的看板&quot;&gt;6. 软件开发中的看板&lt;/h2&gt;

&lt;p&gt;Kanban在软件开发中的应用具有增强敏捷性、改善协作和简化&lt;a href=&quot;https://www.baeldung.com/cs/agile-programming&quot;&gt;开发生命周期&lt;/a&gt;的能力：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/kanban02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;61-看板&quot;&gt;6.1 看板&lt;/h3&gt;

&lt;p&gt;在软件开发中，看板是工作流程的可视化呈现。&lt;strong&gt;看板通常包含待办事项、就绪、进行中和完成等列。每个任务都用一张卡片表示，并在这些列中移动&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;例如，假设一个软件开发团队正在开发一个Web应用程序，待办事项列表可能包含用户故事和功能请求。当某个特定功能定义明确并准备好实现时，它会移至“就绪”列。随着开发人员开始着手处理，该任务会通过“进行中”列进行处理。一旦该功能完成编码、测试和批准，它就会移至“完成”列，表示其已完成。&lt;/p&gt;

&lt;h3 id=&quot;62-限制在制品wip以提高软件效率&quot;&gt;6.2 限制在制品(WIP)以提高软件效率&lt;/h3&gt;

&lt;p&gt;看板的WIP限制在软件开发中尤其重要，&lt;strong&gt;通过限制正在进行的任务数量，团队可以避免负担过重，并保持专注的工作流程&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;例如，如果“进行中”列的WIP限制为3，则团队只有在现有任务少于3个时才会开始处理新任务。这可以防止团队一次性承担过多任务，从而加快任务完成速度，并实现更顺畅的工作流程。&lt;/p&gt;

&lt;h3 id=&quot;63-在软件开发中使用持续交付来管理流程&quot;&gt;6.3 在软件开发中使用持续交付来管理流程&lt;/h3&gt;

&lt;p&gt;在软件开发中，流程管理对于实现持续交付至关重要。&lt;strong&gt;看板中的拉动系统确保只有当团队准备就绪时，任务才会被拉入工作流的每个阶段&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;例如，当团队完成“进行中”列中的任务时，他们会根据容量和优先级从“就绪”列中提取新任务。这确保团队保持一致的开发速度，并能够有效适应不断变化的优先级。&lt;/p&gt;

&lt;h3 id=&quot;64-软件开发的持续改进&quot;&gt;6.4 软件开发的持续改进&lt;/h3&gt;

&lt;p&gt;看板通过定期审查指标和绩效来鼓励持续改进，&lt;strong&gt;团队会分析交付周期、周期时间和其他关键指标，以发现瓶颈和需要改进的领域&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;例如，软件开发团队可能会审查一些指标，例如任务从“就绪”到“完成”所需的时间(周期时间)，或任务积极执行所需的时间(前置时间)。如果周期时间比预期的要长，团队可以调查并实施变更，以简化流程。&lt;/p&gt;

&lt;h2 id=&quot;7-好处&quot;&gt;7. 好处&lt;/h2&gt;

&lt;p&gt;看板提供了一系列优势，可以简化工作流程、增强协作并推动持续改进：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;益处&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;提高可见性和透明度&lt;/td&gt;
      &lt;td&gt;看板以可视化的方式呈现工作流程，使任务状态透明化，让团队成员轻松了解工作进度&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;增强协作与沟通&lt;/td&gt;
      &lt;td&gt;看板通过提供共享的实时工作流程视图来促进协作。团队成员可以轻松查看每个人的工作内容，从而促进更顺畅的沟通和交接&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;通过WIP限制实现高效的工作负载管理&lt;/td&gt;
      &lt;td&gt;WIP限制可防止超负荷，专注于完成任务后再引入新任务。这将提高工作流程效率，减少多任务处理，并加快任务完成速度&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;更快的交付和上市时间&lt;/td&gt;
      &lt;td&gt;Kanban强调连续流动和管理正在进行的工作，有助于更快地交付任务、缩短周期时间并加快产品或功能的上市时间&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;灵活性和适应性&lt;/td&gt;
      &lt;td&gt;看板的灵活性使团队能够快速适应不断变化的优先级或意外问题。它非常适合需求快速变化的环境，使团队能够快速响应客户需求或市场变化&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;通过指标持续改进&lt;/td&gt;
      &lt;td&gt;定期审查性能指标(交货时间、周期时间、吞吐量)使团队能够确定需要优化的领域并做出数据驱动的决策以增强流程&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;减少浪费并优化资源&lt;/td&gt;
      &lt;td&gt;看板有助于识别和消除瓶颈、不必要的任务和低效流程，减少浪费并优化资源的使用&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;8-总结&quot;&gt;8. 总结&lt;/h2&gt;

&lt;p&gt;在本文中，我们探讨了看板作为一种动态项目管理方法，深入探讨了其运营流程、实施方法及其相关优势。&lt;strong&gt;看板已被证明是计算领域一种强大且用途广泛的方法，它为团队提供了一种灵活且可视化驱动的项目和工作流管理方法&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;Kanban的原理源自制造业，无缝过渡到软件开发的动态领域，为团队提供了一个有价值的框架，以提高效率、促进协作并推动持续改进。&lt;/p&gt;
</description>
        <pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/programming/2025/05/06/kanban.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/programming/2025/05/06/kanban.html</guid>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>控制反转</title>
        <description>&lt;h2 id=&quot;1-简介&quot;&gt;1. 简介&lt;/h2&gt;

&lt;p&gt;在软件开发中，控制反转(IoC)是一个改变控制流管理的关键概念。&lt;strong&gt;IoC指的是反转传统的程序控制结构，以提高灵活性并简化应用程序的可测试性&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在本教程中，我们将研究IoC、它的基础知识及其对现代软件开发的影响。&lt;/p&gt;

&lt;h2 id=&quot;2-基础知识&quot;&gt;2. 基础知识&lt;/h2&gt;

&lt;p&gt;在软件开发中，控制反转(IoC)是一种修改应用程序控制流的基本思想。此外，&lt;strong&gt;IoC通过控制反转将控制责任从应用程序转移到外部框架或容器&lt;/strong&gt;。因此，这种控制切换使程序员能够编写更加模块化、灵活且更易于测试的代码。&lt;/p&gt;

&lt;p&gt;通常，软件应用程序始终具有分层控制流，其中主应用程序代码定义执行顺序。但是，有了IoC，这种控制流就颠倒了。它不再由应用程序控制执行方式，而是将权力移交给外部程序，通常称为IoC容器。&lt;/p&gt;

&lt;p&gt;IoC原则的实现需要使用IoC容器，&lt;strong&gt;IoC容器负责处理应用程序中的对象实例化、&lt;a href=&quot;https://www.baeldung.com/cs/process-lifecycle&quot;&gt;生命周期&lt;/a&gt;以及&lt;a href=&quot;https://www.baeldung.com/cs/dependency-injection-vs-service-locator&quot;&gt;依赖注入&lt;/a&gt;&lt;/strong&gt;。通过使用IoC容器，开发人员无需进行任何手动连接即可实现控制反转：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/ioc01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;3-关键概念&quot;&gt;3. 关键概念&lt;/h2&gt;

&lt;p&gt;在塑造软件开发的众多原则中，控制反转(IoC)是一项至关重要的原则，它改变了应用程序的构建和设计方式。让我们探讨IoC背后的一些关键概念，并评估它们如何有助于构建健壮且&lt;a href=&quot;https://www.baeldung.com/cs/maintainability-killers&quot;&gt;可维护&lt;/a&gt;的软件解决方案。&lt;/p&gt;

&lt;h3 id=&quot;31-解耦组件&quot;&gt;3.1 解耦组件&lt;/h3&gt;

&lt;p&gt;在应用程序中，控制反转(IoC)的目标之一是解耦组件，以最小化依赖关系并增强模块化。传统软件系统通常将组件紧密耦合，这意味着它们严重依赖于彼此的具体实现。因此，这会导致系统脆弱，难以更改或扩展。&lt;/p&gt;

&lt;p&gt;尽管如此，&lt;strong&gt;通过IoC，目标是用松散耦合取代紧密耦合，允许组件通过明确定义的&lt;a href=&quot;https://www.baeldung.com/cs/program-to-interface&quot;&gt;接口&lt;/a&gt;而不是具体的实现进行交互&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;通过解耦其组成组件，IoC确保软件系统变得更加灵活、&lt;a href=&quot;https://baeldung.com/cs/scaling-horizontally-vertically&quot;&gt;可扩展&lt;/a&gt;且更易于将来维护。&lt;/p&gt;

&lt;h3 id=&quot;32-依赖管理&quot;&gt;3.2 依赖管理&lt;/h3&gt;

&lt;p&gt;依赖管理是控制反转的另一个潜在因素，在传统的软件开发中，组件通常由其依赖项创建和管理，这导致代码紧密耦合。&lt;/p&gt;

&lt;p&gt;相比之下，IoC依赖管理则通过其所倡导的IoC容器或框架进行外部化。此外，&lt;strong&gt;IoC容器会负责对象的创建和生命周期，并在必要时解析并注入其依赖项&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;因此，通过依赖管理的集中化，IoC使得构建和配置复杂的软件系统变得更加容易，但同时也提高了可重用性和可测试性。&lt;/p&gt;

&lt;h3 id=&quot;33-框架和容器&quot;&gt;3.3 框架和容器&lt;/h3&gt;

&lt;p&gt;软件应用程序可以通过使用IoC特定的&lt;a href=&quot;https://www.baeldung.com/cs/framework-vs-library&quot;&gt;框架&lt;/a&gt;和容器轻松实现控制反转。它们充满了预定义的模式、约定和机制，用于管理依赖关系、协调组件生命周期并支持组件间通信。&lt;/p&gt;

&lt;p&gt;一些示例包括Java中的Spring框架、.NET中的ASP.NET Core以及TypeScript的Angular依赖注入，&lt;strong&gt;通过使用IoC容器或框架，开发人员可以加快开发时间并提高代码质量，同时构建健壮且可扩展的应用程序&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;4-ioc与传统流程&quot;&gt;4. IoC与传统流程&lt;/h2&gt;

&lt;p&gt;将控制反转与传统控制流进行比较，需要理解这两种方法在软件设计中如何管理控制流和依赖关系。在控制反转中，框架或容器控制应用程序的流程，调用开发人员编写的代码。&lt;/p&gt;

&lt;p&gt;这种方法通常被比作“好莱坞原则”-“不要打电话给我们，我们会打电话给你。”另一方面，传统流程需要应用程序代码控制流程，明确创建和管理对象及其生命周期。&lt;/p&gt;

&lt;h3 id=&quot;41-依赖管理&quot;&gt;4.1 依赖管理&lt;/h3&gt;

&lt;p&gt;在依赖管理方面，IoC涉及通过外部控制器或容器(通常在运行时)将依赖项注入到类中。这将类与其依赖项解耦，从而实现更灵活、更模块化的设计。&lt;/p&gt;

&lt;p&gt;相反，&lt;strong&gt;在传统流程中，类通常直接实例化其依赖项，导致组件之间紧密耦合&lt;/strong&gt;。这会降低系统的灵活性，并增加维护难度，尤其是在系统规模和复杂性不断增长的情况下。&lt;/p&gt;

&lt;h3 id=&quot;42-可测试性&quot;&gt;4.2 可测试性&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;IoC还具有&lt;a href=&quot;https://www.baeldung.com/cs/software-quality#7-testability&quot;&gt;可测试性&lt;/a&gt;方面的优势，因为它可以更轻松地用&lt;a href=&quot;https://www.baeldung.com/cs/faking-mocking-stubbing&quot;&gt;Mock或存根&lt;/a&gt;替换实际依赖项&lt;/strong&gt;，这是因为依赖项是外部提供的，因此可以更轻松地隔离代码单元以进行测试。&lt;/p&gt;

&lt;p&gt;另一方面，由于组件紧密耦合，传统流程中的测试可能更具挑战性，这使得隔离单元进行测试变得更加困难。&lt;/p&gt;

&lt;h3 id=&quot;43-模块化和可维护性&quot;&gt;4.3 模块化和可维护性&lt;/h3&gt;

&lt;p&gt;在模块化和可维护性方面，IoC通过解耦组件来促进更模块化的设计，使系统更加灵活，并且随着时间的推移更易于维护。然而，&lt;strong&gt;引入IoC容器或框架可能会增加项目的复杂性，对于不熟悉该概念的人来说，学习起来会比较困难&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;相比之下，在传统的控制流系统中，模块化是指将代码组织成独立的功能单元。然而，组件之间的紧密耦合使得维护变得具有挑战性。一个部分的变更通常需要跨多个模块进行修改，这增加了&lt;a href=&quot;https://www.baeldung.com/cs/exceptions-runtime-errors&quot;&gt;出错&lt;/a&gt;的风险，并使更新变得复杂。&lt;/p&gt;

&lt;h3 id=&quot;44-配置开销&quot;&gt;4.4 配置开销&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;IoC和传统控制流都有各自的配置和开销&lt;/strong&gt;，管理IoC容器可能会引入配置开销，具体取决于方法(例如，注解、XML配置)。&lt;/p&gt;

&lt;p&gt;相反，传统流程需要较少的外部配置，因为依赖项和控制流直接在代码中管理。最终，&lt;strong&gt;在IoC和传统流程之间进行选择取决于项目的具体需求和约束，以及团队对这些概念的熟悉程度&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;5-优点和缺点&quot;&gt;5. 优点和缺点&lt;/h2&gt;

&lt;p&gt;控制反转是一种设计原则，它反转软件架构中的控制流，从而提供一种细致入微的依赖关系管理方法。以下讨论了它的优缺点：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;优点&lt;/th&gt;
      &lt;th&gt;缺点&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;依赖关系可以在运行时动态绑定，增强配置灵活性&lt;/td&gt;
      &lt;td&gt;利用IoC可能需要学习新的框架或库，这会使较小的项目或团队变得复杂&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;代码管理和更新更加容易，需要的系统更改更少&lt;/td&gt;
      &lt;td&gt;IoC会模糊执行流程并使系统操作难以掌握&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;注入依赖项简化了测试，允许模拟对象替换真实的实现&lt;/td&gt;
      &lt;td&gt;一些IoC框架提供动态配置更改，但限制直接依赖性改变&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;促进类之间的松散耦合，从而形成更加模块化的系统&lt;/td&gt;
      &lt;td&gt;一些框架使用反射进行动态实例化，从而产生潜在的性能成本&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;6-总结&quot;&gt;6. 总结&lt;/h2&gt;

&lt;p&gt;在本文中，我们讨论了控制反转的概念，以揭示它如何增强软件开发。值得注意的是，控制反转不仅有助于构建更模块化、更易于测试的代码，而且还可以通过依赖管理实现更好的灵活性。&lt;/p&gt;

&lt;p&gt;尽管IoC本身比较复杂，且初期学习难度较高，但它以支持组件解耦、简化开发流程而闻名。尽管如此，我们仍然应该权衡其优缺点，例如项目复杂性的增加和直接控制的减少。通过专注于项目需求而非目标，实施IoC可能会带来颠覆性的改变。&lt;/p&gt;
</description>
        <pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/programming/2025/05/06/ioc.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/programming/2025/05/06/ioc.html</guid>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>高级设计与低级设计</title>
        <description>&lt;h2 id=&quot;1-概述&quot;&gt;1. 概述&lt;/h2&gt;

&lt;p&gt;在本教程中，我们将讨论软件工程中的低级和高级设计类型。&lt;/p&gt;

&lt;h2 id=&quot;2-什么是高级设计&quot;&gt;2. 什么是高级设计？&lt;/h2&gt;

&lt;p&gt;建筑物的鸟瞰图展示了其整体结构、住宅楼层、商业空间和配套设施，以及它们如何无缝连接。同样，高层设计(HLD)就像软件系统的鸟瞰图，它定义了整体架构、主要组件及其交互方式，我们可以将其视为奠定软件架构基础的蓝图。&lt;/p&gt;

&lt;p&gt;因此，HLD是设计过程初始阶段的成果，在此阶段，架构师、系统设计师或项目经理会在高层次上讨论系统的结构和组件。&lt;strong&gt;HLD展示了系统的整体架构和功能，而无需深入探讨实现细节&lt;/strong&gt;。要了解更多关于系统架构的信息，请参阅我们关于&lt;a href=&quot;https://www.baeldung.com/cs/layered-architecture&quot;&gt;分层架构&lt;/a&gt;的文章。&lt;/p&gt;

&lt;p&gt;让我们深入了解HLD的一些主要特征：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;它指定数据库设计、系统、服务、平台和模块之间的关系&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;它包含硬件、软件接口以及用户界面的描述&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;由解决方案架构师创建&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;它详细说明了用户的典型流程以及性能规格&lt;/li&gt;
  &lt;li&gt;它描述了系统的重大限制和权衡&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;就像建筑师在关注建筑预算和工期的同时，也会关注高层设计一样，在这个阶段，我们会权衡软件架构设计方案的成本和收益。虽然关注功能性和可扩展性很重要，但忽视财务和资源方面的影响可能会导致开发周期后期效率低下和预算超支。为了使团队能够做出符合项目目标和约束条件的明智决策，他们还必须考虑这些因素。这涉及开发时间、维护复杂性、基础设施要求以及潜在的可扩展性问题。&lt;/p&gt;

&lt;h2 id=&quot;3-什么是低级设计&quot;&gt;3. 什么是低级设计？&lt;/h2&gt;

&lt;p&gt;进入建筑内部，我们深入了解具体的布局、材料、管道、电线等等。同样，低级设计(LLD)会充实HLD中指定的每个组件的实现细节，例如类图、&lt;a href=&quot;https://www.baeldung.com/cs/category/algorithms&quot;&gt;算法&lt;/a&gt;、&lt;a href=&quot;https://www.baeldung.com/cs/category/core-concepts/tag/databases&quot;&gt;数据库&lt;/a&gt;模式和&lt;a href=&quot;https://www.baeldung.com/cs/exceptions-runtime-errors&quot;&gt;错误处理机制&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;简单来说，&lt;strong&gt;LLD是设计过程的下一个阶段，设计师和开发人员将高级设计转化为详细的技术规范&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;LLD的一些主要特征是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;它由开发经理和设计师创建&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;它涉及所有系统组件的实际逻辑&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;它包括系统实施的分步计划&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;它可以作为开发人员&lt;a href=&quot;https://www.baeldung.com/solid-principles&quot;&gt;编码&lt;/a&gt;过程中的指南，确保系统功能准确、高效&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们可以说LLD深入研究了技术细节，将高层设计转化为详细的实施计划：&lt;/p&gt;

&lt;h2 id=&quot;4-hld与lld&quot;&gt;4. HLD与LLD&lt;/h2&gt;

&lt;p&gt;因此，我们有两种设计类型：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/hldlld01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们来总结一下它们的区别：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;方面&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;低级设计&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;高级设计&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;重点&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;详细实施细节&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;总体系统架构与设计&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;涵盖的组件&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;单个组件/模块&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;主要系统组件及其相互作用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;设计工件&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;类图、数据结构、算法、数据库模式、错误处理&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;架构图、组件图、&lt;a href=&quot;https://www.baeldung.com/cs/systems-design-interface-segregation-principle#:~:text=In%20computer%20science%2C%20an%20interface,tied%20to%20a%20specific%20implementation&quot;&gt;接口&lt;/a&gt;定义、数据流图&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;输入&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;高级设计(HLD)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;业务需求、约束和目标&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;输出&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;代码级实现&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;高级设计(HLD)计划&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;实施时间&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;HLD之后，在编码阶段&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;软件开发生命周期的早期&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;决策示例&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;数据结构选择、算法设计、数据库规范化&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;架构风格(例如&lt;a href=&quot;https://www.baeldung.com/cs/microservices&quot;&gt;微服务&lt;/a&gt;、单体架构)、技术栈、集成点&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;另称&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;宏观设计&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;微观设计&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;结果&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;评审记录、功能设计、数据库设计&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;程序规范和单元测试计划&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;HLD设定软件系统的总体架构和功能，提供满足需求的开发路线图，而LLD则处理实现方面的问题。&lt;a href=&quot;https://www.baeldung.com/cs/requirements-functional-vs-non-functional&quot;&gt;我们关于需求的文章&lt;/a&gt;详细介绍了如何区分功能性需求和非功能性需求。&lt;/p&gt;

&lt;h2 id=&quot;5-总结&quot;&gt;5. 总结&lt;/h2&gt;

&lt;p&gt;在本文中，我们解释了低级和高级软件设计。HLD提供了愿景，而LLD提供了详细的执行蓝图，了解如何创建这两种计划对于成功的软件开发至关重要。&lt;/p&gt;
</description>
        <pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/programming/2025/05/06/hld-lld.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/programming/2025/05/06/hld-lld.html</guid>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>停机问题简介</title>
        <description>&lt;h2 id=&quot;1-概述&quot;&gt;1. 概述&lt;/h2&gt;

&lt;p&gt;停机问题是计算机科学中一个著名的基础理论，它突出了可计算性的边界，并提供了一种识别无法在有限时间内解决的算法问题的方法。&lt;/p&gt;

&lt;p&gt;在本教程中，&lt;strong&gt;我们将通过示例和正式证明详细探讨停机理论&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;2-停机问题&quot;&gt;2. 停机问题&lt;/h2&gt;

&lt;h3 id=&quot;21-问题陈述&quot;&gt;2.1 问题陈述&lt;/h3&gt;

&lt;p&gt;当我们在计算机上执行不同的程序时，&lt;strong&gt;可以根据各自的停止行为将它们分为三类&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/haltingproblem01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第一类程序会在我们输入任何输入后迅速停止，此外，有些程序可能需要更长的时间才能完成执行，但最终还是会停止。此外，有些程序根本不会停止，而是继续&lt;a href=&quot;https://en.wikipedia.org/wiki/Infinity&quot;&gt;无限期地&lt;/a&gt;运行。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Halting_problem&quot;&gt;停机问题&lt;/a&gt;有助于理解&lt;a href=&quot;https://en.wikipedia.org/wiki/Computation&quot;&gt;计算&lt;/a&gt;限制，并确定我们无法在有限的时间内解决所有程序。&lt;/p&gt;

&lt;p&gt;现在，我们来讨论正式的问题陈述。假设我们有一个任意程序Prog，此外，该程序的给定输入表示为input_data，停机问题询问是否有办法确定程序Prog是否会因给定输入input_data而停止。&lt;/p&gt;

&lt;p&gt;此外，1936年，&lt;strong&gt;艾伦·图灵证明停机问题是&lt;a href=&quot;https://en.wikipedia.org/wiki/Solvable&quot;&gt;不可解&lt;/a&gt;的，这意味着没有任何方法或算法可以告诉我们对于给定输入的程序是否会停机&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&quot;22-示例&quot;&gt;2.2 示例&lt;/h3&gt;

&lt;p&gt;现在，我们了解了停机问题的基本理论。此外，我们还将探索一些示例以获得实际的理解。&lt;/p&gt;

&lt;p&gt;我们先来看第一个例子：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;sample_prog1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in_num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in_num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Line {i + 1}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Program halts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里，in_num是一个正整数。&lt;strong&gt;在这个例子中，对于任何输入，程序都会确实停止&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;此外，我们来看另一个例子：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;sample_prog2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in_num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在这种情况下，程序不使用用户提供的输入。此外，&lt;strong&gt;while循环确保程序无限期运行，无论提供的输入是什么&lt;/strong&gt;。因此，该程序不会因为任何给定的输入而停止。&lt;/p&gt;

&lt;h2 id=&quot;3-形式证明&quot;&gt;3. 形式证明&lt;/h2&gt;

&lt;p&gt;我们已经知道，不可能确定任意程序是否会因给定输入而停止。我们需要证明没有算法或程序能够解决停机问题，才能证明停机理论。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;我们将通过反证法来证明这一点，因此，我们首先假设存在这样的算法&lt;/strong&gt;。然而，我们最终通过反证法证明，编写这样的算法是不可能的。&lt;/p&gt;

&lt;p&gt;假设存在一个用于解决停机问题的算法Halt_Testing，该算法接收两个输入：程序Prog和输入in_num。&lt;/p&gt;

&lt;p&gt;此外，我们假设算法能够判断程序Prog是否会因输入in_number而停止运行。因此，如果程序在给定输入in_num时停止运行，则Halt_Testing(Prog, in_num)返回True，否则返回False：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/haltingproblem02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;现在，我们定义一个以单个程序作为输入的新函数：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Reverse_Halt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Halt_Testing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;forever&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在此函数中，我们提供一个程序作为输入。如果Halt_Testing算法对输入程序Prog返回True，则函数Reverse_Halt将进入无限循环，永不停止。另一方面，如果Halt_Testing算法对输入程序Prog返回False，则函数Reverse_Halt将立即停止。&lt;/p&gt;

&lt;p&gt;此外，我们定义了函数Reverse_Halt，使其仅接收一个程序作为其输入。然而，在本例中，我们将程序Prog同时用作Halt_Testing算法的程序和输入，以创建一个自引用结构，创建自引用结构对于通过反证法证明停机问题是必要的。&lt;/p&gt;

&lt;p&gt;此外，让我们探讨一下调用函数Reverse_Halt的情况：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Reverse_Halt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Reverse_Halt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Halt_Testing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Reverse_Halt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Reverse_Halt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;forever&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因此，如果Halt_Testing算法对于给定的输入程序返回True，则函数Reverse_Halt会停止。然而，根据函数Reverse_Halt的定义，当Halt_Testing算法返回True时，它应该进入无限循环而不会停止，这引出了矛盾。&lt;/p&gt;

&lt;p&gt;类似地，当Halt_Testing算法返回False时，函数Reverse_Halt会永远运行。同样，根据 其定义，当Halt_Testing算法返回False时，Reverse_Halt应该停止。这又带来了矛盾。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;因此，基于这些矛盾，我们证明了我们最初假设的能够确定程序在给定输入下是否停止的算法是错误的&lt;/strong&gt;。因此，没有算法能够确定程序是否会因给定输入而停止。&lt;/p&gt;

&lt;h2 id=&quot;4-总结&quot;&gt;4. 总结&lt;/h2&gt;

&lt;p&gt;在本文中，我们讨论了计算机科学中的一个基本问题：停机问题，我们解释了该问题的基本概念并结合了一些示例。&lt;/p&gt;

&lt;p&gt;最后，我们通过反证法证明，我们无法设计一种算法来确定程序是否会因给定的输入而停止。&lt;/p&gt;
</description>
        <pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/programming/2025/05/06/halting-problem.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/programming/2025/05/06/halting-problem.html</guid>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Faking、Mocking和Stubbing之间的区别</title>
        <description>&lt;h2 id=&quot;1-概述&quot;&gt;1. 概述&lt;/h2&gt;

&lt;p&gt;在测试软件应用程序或产品时，我们可能会创建一些不真实的对象或组件，我们需要它们来模拟对日常场景和极端情况的响应，我们通常将此过程称为创建Fakes或&lt;a href=&quot;https://en.wikipedia.org/wiki/Test_double&quot;&gt;测试替身&lt;/a&gt;。当我们采用测试库或框架来自动化软件测试流程时，我们也可以参考Mock或Stub。&lt;/p&gt;

&lt;p&gt;在本教程中，我们将了解如何使用虚构数据，例如Fake、Mock和Stub。&lt;/p&gt;

&lt;h2 id=&quot;2-为什么我们需要fakemock和stub&quot;&gt;2. 为什么我们需要Fake、Mock和Stub&lt;/h2&gt;

&lt;p&gt;测试有很多种类型，例如&lt;a href=&quot;https://www.baeldung.com/cs/unit-vs-integration-testing&quot;&gt;单元测试或集成测试&lt;/a&gt;。但是，我们可以将测试序列简化为以下几个步骤：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/fakingmockingstubbing01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;每当我们构建测试时，都应该根据业务需求创建场景。然后，我们可以模拟所有不同的情况，并断言我们根据测试的实际结果获得了预期的结果。常见的方法是使用&lt;a href=&quot;https://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;TDD&lt;/a&gt;来测试代码覆盖率，或使用&lt;a href=&quot;https://en.wikipedia.org/wiki/Behavior-driven_development&quot;&gt;BDD&lt;/a&gt;来向用户解释应用程序的行为。&lt;/p&gt;

&lt;p&gt;然而，我们需要为测试套件提供一些输入，获取真实数据可能非常耗时。此外，还可能存在一些问题，例如，为测试实例中不可用的服务添加覆盖范围。&lt;/p&gt;

&lt;p&gt;让我们看看我们可以在测试环境中应用的数据类型和技术。&lt;/p&gt;

&lt;h2 id=&quot;3-fake&quot;&gt;3. Fake&lt;/h2&gt;

&lt;p&gt;我们可以将任何与生产环境中找不到的数据、对象或实现归类为Fake。因此，无论我们使用何种具体技术，Fake都是我们用来模拟实际应用中行为的东西。&lt;strong&gt;它通常只是一个类或模块的副本，其中包含预先计算好的响应&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&quot;31-伪造对象&quot;&gt;3.1 伪造对象&lt;/h3&gt;

&lt;p&gt;通常，我们可以将Fake视为具有有限功能的类对象的副本或克隆。例如，假设我们有一个UserRepository类实现了一个接口：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/fakingmockingstubbing02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们可能需要我们的Repository返回一个虚假值，在这种情况下，Fake将提供自定义响应，例如，一个静态值或内存值，而不是实际的计算结果。&lt;/p&gt;

&lt;h3 id=&quot;32-fake缺点&quot;&gt;3.2 Fake缺点&lt;/h3&gt;

&lt;p&gt;这种方法也有一些缺点，&lt;strong&gt;例如，我们不能直接伪造接口。因此，我们每次都需要实现一个伪类&lt;/strong&gt;。此外，我们需要将对象保存到内存映射中，以便提前测试。这种方法可能不够灵活，让我们看看编程语言和我们使用的库如何使Mock或Stub成为Fake更合适的替代品。&lt;/p&gt;

&lt;h2 id=&quot;4-mock&quot;&gt;4. Mock&lt;/h2&gt;

&lt;p&gt;我们将Mock称为代表现实的对象，尽管其功能有限。此外，在开始测试之前，我们会对Mock设定预期，并指示它在发生某些事情时保持确定性。这样，我们就可以枚举应用程序特定流程中遇到的所有不同状态，并构建一系列测试。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;我们可以将Mock视为一个木偶或牵线木偶，我们可以操纵它在某些条件下按照我们需要的方式行事&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&quot;41-mock对象&quot;&gt;4.1 Mock对象&lt;/h3&gt;

&lt;p&gt;我们可以Mock一个&lt;a href=&quot;https://www.baeldung.com/cs/interfaces-vs-base-classes&quot;&gt;类或接口&lt;/a&gt;，&lt;strong&gt;Mock对象的实际实现不会被调用，即使Mock接口尚未实现也没关系。请注意，Mock类的属性将返回默认值&lt;/strong&gt;。例如，对于字符串或列表，我们将返回null或false作为布尔值。&lt;/p&gt;

&lt;h3 id=&quot;42-mock和验证交互&quot;&gt;4.2 Mock和验证交互&lt;/h3&gt;

&lt;p&gt;让我们举一个Mock用法的例子，假设我们有AuthenticationService，该服务使用&lt;a href=&quot;https://www.baeldung.com/cs/dip&quot;&gt;依赖倒置&lt;/a&gt;依赖于UserRepository，让我们看看如何Mock Repository：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/fakingmockingstubbing03.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Mock操作默认会遍历对象的依赖注入层级，如果没有特别指定，我们将获得一个UserRepository的Mock实例和一个数据库连接Mock。需要注意的是，对于findUserByName()方法，其返回值默认为空User，但我们可以添加自定义响应。&lt;/p&gt;

&lt;p&gt;对于单元测试和隔离类的不同工作单元而言，Mock可以节省我们创建数据库连接的时间，我们甚至可以在无需实际实现的情况下进行接口Mock。因此，无论数据库实现如何，我们都可以断言业务逻辑能够正常工作。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;最后，Mock的另一个相关特性是可以验证是否发生所需的交互&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;让我们用一张图来概括这一切：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.baeldung.com/wp-content/uploads/sites/4/2023/08/test_auth-4.png&quot; alt=&quot;测试身份验证服务&quot; /&gt;&lt;/p&gt;

&lt;p&gt;此流程描述了服务如何工作，无论其实现方式如何。它应该涵盖所有可能的情况，并将转换为具体的测试。如果逻辑发生变化，我们也预期测试会相应地失败。&lt;/p&gt;

&lt;h3 id=&quot;43-mock实践&quot;&gt;4.3 Mock实践&lt;/h3&gt;

&lt;p&gt;在实际应用中，Mock由特定的库或框架管理。开发人员通常依赖库来将其插入到应用程序中，这些库将构建测试基础架构并管理我们想要Mock的对象的生命周期。开发人员负责将业务需求转化为测试覆盖率，让我们来看看一些流行的Mock库/框架：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;编程语言&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;库/框架&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Java&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Mockito是一个流行的库，Spock是一个使用given/when/then语法的测试框架&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.NET&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;NUnit和XUnit&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;JavaScript&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Jest是一个强大的测试工具&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;5-stub&quot;&gt;5. Stub&lt;/h2&gt;

&lt;p&gt;Stub是Mock的轻量级版本，它拥有Mock的结构，&lt;strong&gt;但无法记录交互或返回多个值&lt;/strong&gt;。因此，当我们需要一个伪造的对象，但又缺乏Mock的灵活性时，应该使用Stub。&lt;/p&gt;

&lt;h3 id=&quot;51-stub对象&quot;&gt;5.1 Stub对象&lt;/h3&gt;

&lt;p&gt;例如，假设我们构建了一个包含多个模块的应用程序。但是，其中一个模块尚未准备好。我们可能需要使用一个简化版本，临时提供预设的响应。让我们看看如何对AuthenticationService进行存根：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/fakingmockingstubbing04.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们看到，每次尝试身份验证时都会返回一个默认用户。这将帮助我们完成应用程序流程，直到实际服务准备就绪。这可以用于测试，也可以在构建模块时作为临时替代方案。&lt;/p&gt;

&lt;p&gt;注意，我们可以将Stub描述为Mock的一个特定用例。具体来说，当它系统地返回默认值时，我们无需验证交互。另请注意，这种方法类似于伪造Repository，尽管我们通常可以对接口进行stub。&lt;/p&gt;

&lt;h3 id=&quot;52-stub实践&quot;&gt;5.2 Stub实践&lt;/h3&gt;

&lt;p&gt;许多库或框架通常认为Stub与Mock类似，因此从软件测试的角度来看，它有时可以与Mock互换。&lt;/p&gt;

&lt;h2 id=&quot;6-fakemock和stub的区别&quot;&gt;6. Fake、Mock和Stub的区别&lt;/h2&gt;

&lt;p&gt;我们来总结一下Fake、Mock、Stub的区别：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Fake&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Mock&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Stub&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;功能有限的自定义对象&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;具有默认返回的类实例或方法&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;具有预设响应的类实例或方法&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;无法验证测试中的交互&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;可以验证测试中的交互&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;无法验证测试中的交互&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;测试期间缺乏灵活性&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;允许方法返回多个值&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;灵活性有限&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;预先计算的结果&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;与假货的精细互动&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;预先计算的结果&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;通常与库或框架无关&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;库和框架的坚实基础&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;通常是Mock库的一部分&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;7-总结&quot;&gt;7. 总结&lt;/h2&gt;

&lt;p&gt;在本教程中，我们描述了什么是Fake，以及为什么我们需要非真实的数据或对象，我们还了解了如何使用Mock或Stub来实现Fake。&lt;/p&gt;

&lt;p&gt;我们了解了Mock为何比Stub更灵活，以及为什么它有更好的用途。此外，我们还注意到，在通用语言中，Fake、Mock或Stub的含义可以相同。&lt;/p&gt;
</description>
        <pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/test/2025/05/06/faking-mocking-stubbing.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/test/2025/05/06/faking-mocking-stubbing.html</guid>
        
        
        <category>test</category>
        
      </item>
    
      <item>
        <title>什么是探索性测试</title>
        <description>&lt;h2 id=&quot;1-简介&quot;&gt;1. 简介&lt;/h2&gt;

&lt;p&gt;在整个软件开发生命周期中，&lt;a href=&quot;https://www.baeldung.com/java-functional-vs-non-functional-testing&quot;&gt;软件测试&lt;/a&gt;对于降低风险、保证质量和识别错误非常重要。&lt;/p&gt;

&lt;p&gt;在本教程中，我们将讨论探索性测试、它的优点以及何时使用它。&lt;/p&gt;

&lt;h2 id=&quot;2-为什么要进行探索性测试&quot;&gt;2. 为什么要进行探索性测试？&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;在脚本测试中，测试开始前必须准备好并完成所需的文档&lt;/strong&gt;。因此，开发团队根据记录的需求创建测试用例，以涵盖系统的每个功能，测试团队必须在测试过程开始之前制定全面的测试策略。&lt;/p&gt;

&lt;p&gt;另一方面，在探索性测试中，测试人员不仅仅依赖于预先定义的测试用例，而是采用更灵活、更具适应性的方法来发现潜在问题。重点在于测试人员的直觉、经验和创造力，使他们能够动态地设计和执行测试场景。&lt;/p&gt;

&lt;p&gt;探索性测试是对其他方法的补充，但不会取代它们。它尤其有助于在开发生命周期的早期发现问题，加深对系统的理解，并为迭代改进提供宝贵的反馈。&lt;/p&gt;

&lt;h2 id=&quot;3-定义和特征&quot;&gt;3. 定义和特征&lt;/h2&gt;

&lt;p&gt;探索性测试是一种侧重于同时进行学习、测试设计和执行的测试方法。它是一种动态且非脚本化的方法，测试人员可以探索软件系统以发现缺陷、评估其行为并更深入地了解其优缺点：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/exploratorytesting01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在这种方法中，测试人员可以自由使用他们认为合适的任何测试方法。这种方法在&lt;a href=&quot;https://www.baeldung.com/cs/agile-programming&quot;&gt;敏捷环境&lt;/a&gt;中非常有用，因为它与敏捷开发方法论中灵活迭代的开发理念相一致。为了验证产品是否满足客户的需求，敏捷团队通常依赖于短反馈循环。&lt;/p&gt;

&lt;p&gt;探索性测试使测试人员能够发现问题并实时修改测试策略，从而使测试变得更加容易。&lt;/p&gt;

&lt;p&gt;当利益相关者需要快速输入信息，而又没有太多时间了解产品或项目时，这种方法很有帮助。此外，它比遵循一套预先设定的流程更注重情境驱动。&lt;/p&gt;

&lt;p&gt;以下是探索性测试的几个关键特征。&lt;/p&gt;

&lt;h3 id=&quot;31-适应性&quot;&gt;3.1 适应性&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;它鼓励测试人员利用他们的领域知识、直觉和创造力来设计和执行测试&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;测试人员可以自由探索脚本测试用例可能未涵盖的不同场景、功能和边缘情况，这种适应性使他们能够发现不可预见的缺陷和潜在风险。&lt;/p&gt;

&lt;h3 id=&quot;32-动态特性&quot;&gt;3.2 动态特性&lt;/h3&gt;

&lt;p&gt;在这种方法中，&lt;strong&gt;测试人员在与被测系统交互时不断从中学习&lt;/strong&gt;，确定关注的领域，并据此设计新的测试。&lt;/p&gt;

&lt;p&gt;这种动态特性使测试人员能够发现脚本测试可能遗漏的复杂问题。&lt;/p&gt;

&lt;h3 id=&quot;33-快速反馈&quot;&gt;3.3 快速反馈&lt;/h3&gt;

&lt;p&gt;它还可以帮助测试人员&lt;strong&gt;快速识别软件中的缺陷和潜在问题&lt;/strong&gt;，因为他们不必等待脚本测试用例覆盖特定区域。&lt;/p&gt;

&lt;h3 id=&quot;34-以用户为中心的方法&quot;&gt;3.4 以用户为中心的方法&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;它专注于模仿现实世界的用户交互和场景&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;测试人员模拟用户行为并以更自然和不可预测的方式探索应用程序，这可以发现脚本测试可能遗漏的问题。&lt;/p&gt;

&lt;p&gt;这也提供了对不同条件下的可用性、用户体验和系统行为的洞察。&lt;/p&gt;

&lt;h2 id=&quot;4-如何进行探索性测试&quot;&gt;4. 如何进行探索性测试&lt;/h2&gt;

&lt;p&gt;有几个步骤：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/exploratorytesting02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;我们将缺陷划分为不同的组别或类别，从而创建缺陷分类法&lt;/strong&gt;。这有助于根据缺陷的特征、影响和受影响的功能对其进行组织和分类。例如，我们可以将其分为可用性缺陷、功能性缺陷、性能问题和安全漏洞。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;然后，我们创建一份测试章程&lt;/strong&gt;。测试章程通常包含测试环节的目标概要，并定义其范围。它还可以指定测试人员应测试的特性、功能和组件，并识别任何超出范围的区域。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;我们还需要指定一个时间框&lt;/strong&gt;，这是一个预先定义的时间段，在此期间，测试人员可以进行探索性测试活动，探索软件，发现缺陷，并深入了解其行为。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;我们通过查看测试参数来审查结果&lt;/strong&gt;，这些参数可以评估测试方法的有效性，识别系统问题，并生成有关产品优缺点的宝贵信息。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;汇报会是指对测试工作进行评估并将结果传达给利益相关者的过程&lt;/strong&gt;，为了帮助利益相关者就项目方向做出更明智的决策，测试人员会在汇报会上讨论他们的观察结果和建议，以改进即将开展的项目或周期的测试流程。&lt;/p&gt;

&lt;h2 id=&quot;5-好处&quot;&gt;5. 好处&lt;/h2&gt;

&lt;p&gt;当严格的测试技术无法捕获所有错误时，探索性测试可以帮助我们发现软件缺陷。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;在处理不明确、不完整或频繁变化的需求时，这种方法尤其有用&lt;/strong&gt;。它不需要大量的测试用例准备和文档，而是一种更灵活、更灵活的临时方法。测试人员可以快速开始探索软件，而无需预先创建详细的测试脚本，这可以节省时间和成本。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;在探索性测试中，我们以最终用户的心态来测试软件，重点关注可用性、直观性和整体用户体验&lt;/strong&gt;。通过从用户的角度探索软件，测试人员可以发现潜在的可用性问题、令人困惑的工作流程或可能影响最终用户满意度的方面。&lt;/p&gt;

&lt;h2 id=&quot;6-挑战&quot;&gt;6. 挑战&lt;/h2&gt;

&lt;p&gt;探索性测试有很多好处，但也存在一些缺陷：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;探索性测试通常不太注重详尽的文档记录，基于自由形式的探索和实验&lt;/strong&gt;，测试人员通常不会提供测试用例和测试过程中遵循的步骤的详细文档，这使得问题重现或长期跟踪测试进度变得十分困难。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;探索性测试固有的时间限制&lt;/strong&gt;，因为测试人员参与动态测试设计和执行，并根据在可用测试时间范围内出现的信息和洞察调整其方法。测试人员需要平衡他们的工作，并确保为关键领域分配足够的时间。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;有效的探索性测试需要经验丰富的测试人员&lt;/strong&gt;，他们对被测系统、领域以及各种测试技术有着深入的理解。找到具备相应专业知识的测试人员可能并非易事，而且新测试人员可能需要一定的学习时间才能熟练掌握探索性测试。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;由于探索性测试不遵循预先定义的脚本或测试计划，因此存在遗漏某些测试区域或功能的风险&lt;/strong&gt;。有必要管理和监控测试覆盖率，以确保充分探索软件的关键方面。&lt;/p&gt;

&lt;h2 id=&quot;7-总结&quot;&gt;7. 总结&lt;/h2&gt;

&lt;p&gt;探索性测试既有优点，也有缺点，以下是简要概述。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;优点&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;缺点&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;它使测试人员能够快速适应不断变化的需求和条件&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;通常依赖于测试人员的专业知识和知识，与脚本测试相比，文档较少&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;它可以帮助发现细微、复杂且难以重现的错误&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;由于没有预定义的测试用例，探索性测试期间可能会错过某些区域或场景&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;它可以在开发周期的早期执行，当应用程序仍在发展时&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;这是一个反复且耗时的过程&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;它促进创造性思维和解决问题的能力&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;由于探索性测试基于临时探索，因此很难重现测试期间遵循的确切步骤&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;8-总结&quot;&gt;8. 总结&lt;/h2&gt;

&lt;p&gt;在本文中，我们讨论了探索性测试。&lt;/p&gt;

&lt;p&gt;它最好被描述为动态测试，其中测试人员不遵循预先定义的测试脚本，而是根据自己的意愿设计和执行它们。&lt;/p&gt;
</description>
        <pubDate>Sat, 03 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/test/2025/05/03/exploratory-testing.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/test/2025/05/03/exploratory-testing.html</guid>
        
        
        <category>test</category>
        
      </item>
    
      <item>
        <title>什么是鸭子类型</title>
        <description>&lt;h2 id=&quot;1-简介&quot;&gt;1. 简介&lt;/h2&gt;

&lt;p&gt;“鸭子测试”是一种口语化的推理形式，通常用来表示一个人只需观察未知事物的独特特征就能识别它：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种推理并不总是正确的，但它经常在很多情况下使用，包括鸭子类型。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;更准确地说，在计算机编程和类型理论中，鸭子类型是一种&lt;a href=&quot;https://www.baeldung.com/cs/programming-types-comparison&quot;&gt;类型&lt;/a&gt;技术，其中对象被赋予一种类型当且仅当它定义了该类型所需的所有属性&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在本教程中，我们将扩展上述定义，并通过一些示例了解鸭子类型是什么。&lt;/p&gt;

&lt;h2 id=&quot;2-定义和示例&quot;&gt;2. 定义和示例&lt;/h2&gt;

&lt;p&gt;我们上面看到的“鸭子类型”解释在类型论中是合理的,然而，它有更简单、更直观的定义。换句话说，鸭子类型意味着我们根据对象能做什么来定义它，而不是根据它是什么。&lt;strong&gt;对象的类型在这里并不重要，它的结构也不重要：我们唯一感兴趣的是对象能做什么&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;更正式地说，鸭子类型语言在&lt;a href=&quot;https://www.baeldung.com/cs/runtime-vs-compile-time&quot;&gt;编译时或运行时&lt;/a&gt;都不会执行任何类型检查。相反，它会在运行时尝试调用指定的方法。如果成功，那就很好。否则，它会抛出某种形式的运行时错误。&lt;/p&gt;

&lt;p&gt;让我们看一个鸭子类型语言&lt;a href=&quot;https://docs.python.org/3/&quot;&gt;Python&lt;/a&gt;中的例子：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Duck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The duck is swimming&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The duck is flying&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pelican&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The pelican is swimming&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The pelican is flying&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Penguin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The penguin is swimming&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        
&lt;span class=&quot;n&quot;&gt;duck&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Duck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pelican&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pelican&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;penguin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Penguin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;duck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;duck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;pelican&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pelican&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;penguin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;penguin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在上面的例子中，我们定义了三个不同的类：Duck、Pelican和Penguin，并分别实现了fly和swim的方法。然而，Penguin不会飞。因此，我们的Penguin类不会实现任何fly()方法。&lt;/p&gt;

&lt;p&gt;在类定义之后，我们为每个类创建了一个实例，并在每个实例上调用swim()和fly()方法，输出如下：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;The duck is swimming
The duck is flying
The pelican is swimming
The pelican is flying
The penguin is swimming
Traceback (most recent call last):
  File &quot;main.py&quot;, line 31, in &amp;lt;module&amp;gt;
    penguin.fly()
AttributeError: &apos;Penguin&apos; object has no attribute &apos;fly&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出清晰地展现了鸭子类型的作用，Python中没有&lt;a href=&quot;https://www.baeldung.com/cs/how-compilers-work&quot;&gt;编译器&lt;/a&gt;预先告诉我们penguin.fly()是一个错误。相反，我们在运行时会得到一个AttributeError。&lt;/p&gt;

&lt;h3 id=&quot;21-多态性&quot;&gt;2.1 多态性&lt;/h3&gt;

&lt;p&gt;简而言之，“多态性”的意思是“具有多种形态”。&lt;strong&gt;在类型论和计算机科学中，多态性是编程语言的一种属性，允许我们使用一个符号来表示多种类型&lt;/strong&gt;。换句话说，当需要超类型时，我们可以使用子类型的实例。&lt;/p&gt;

&lt;p&gt;让我们看一个&lt;a href=&quot;https://www.java.com/en/&quot;&gt;Java&lt;/a&gt;中的例子：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SwimmingAnimal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Duck&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SwimmingAnimal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The duck is swimming&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SwimmingAnimal&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;swim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Duck&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在上面的例子中，test()方法输入了一个SwimmingAnimal类型的参数。因此，我们可以创建一个Duck的实例(实现了SwimmingAnimal接口)，并将其作为test()的参数。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;鸭子类型是多态性的一种形式&lt;/strong&gt;，正如我们上面所见，它允许我们交替使用不同类型的对象，前提是它们实现了某些行为(也就是它们的接口)。它与其他形式的多态性(例如Java的多态性)的主要区别在于，对象不必继承自相同的超类/接口。&lt;/p&gt;

&lt;h2 id=&quot;3-鸭子类型的优缺点&quot;&gt;3. 鸭子类型的优缺点&lt;/h2&gt;

&lt;p&gt;正如我们上面所看到的，在鸭子类型语言中，我们没有编译器在运行代码之前用类型标记表达式。这有几个优点，但也是有代价的：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;优点&lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;轻松原型&lt;/td&gt;
      &lt;td&gt;我们可以创建类似上面的代码片段，而不必担心定义类型或接口&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;代码重用&lt;/td&gt;
      &lt;td&gt;使用鸭子类型，无需创建复杂的类层次结构来重用代码&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;灵活性&lt;/td&gt;
      &lt;td&gt;我们可以根据对象的行为互换使用它们，而不必担心用类型标记它们&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;缺点&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;运行时错误&lt;/td&gt;
      &lt;td&gt;在上面的Python示例中，编译器会阻止我们运行在运行时会失败的代码片段&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;可读性&lt;/td&gt;
      &lt;td&gt;代码不太明确，因为我们没有任何类型来记录特定函数所需的行为&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;调试与维护&lt;/td&gt;
      &lt;td&gt;更难阅读的代码也更难调试和推理&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;4-适用性&quot;&gt;4. 适用性&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;一般来说，鸭子类型对于原型设计和脚本编写非常有用&lt;/strong&gt;。如果我们编写的应用程序是一个简单的脚本，那么在代码中不显式地定义类型可以加快开发过程(同样，代价是增加了运行时错误的风险)。对于原型设计也是如此：概念验证的实现可能会避开类型的形式化，而专注于解决特定问题的业务逻辑。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;鸭子类型另一个相关的应用是序列化/反序列化&lt;/strong&gt;，例如，为每个JSON对象定义类型可能很麻烦，鸭子类型让我们假设发送/接收的对象存在某些属性，从而简化代码。&lt;/p&gt;

&lt;h2 id=&quot;5-总结&quot;&gt;5. 总结&lt;/h2&gt;

&lt;p&gt;在本文中，我们深入探讨了鸭子类型，并从不同的角度对其进行了定义。此外，我们还通过代码示例演示了它的工作原理及其优缺点。最后，我们探讨了鸭子类型与多态性之间的关系。&lt;/p&gt;
</description>
        <pubDate>Sat, 03 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/programming/2025/05/03/duck-typing.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/programming/2025/05/03/duck-typing.html</guid>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>何时使用左折叠和右折叠</title>
        <description>&lt;h2 id=&quot;1-简介&quot;&gt;1. 简介&lt;/h2&gt;

&lt;p&gt;在本教程中，我们将研究集合的“左折叠”和“右折叠”操作，我们将探讨它们之间的区别以及分别应该使用的情况。&lt;/p&gt;

&lt;h2 id=&quot;2-什么是折叠&quot;&gt;2. 什么是折叠？&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;在函数式编程中，折叠是一种标准操作，可用于将集合折叠为单个结果&lt;/strong&gt;。它的工作原理是遍历集合并应用相同的累加器函数，将当前累积的结果与集合中的下一个结果组合起来。&lt;/p&gt;

&lt;p&gt;一个常见的例子是对一组数字求和，在这种情况下，我们要对集合中的数字应用的累加函数是plus，期望的结果是：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result &amp;lt;- 1 + 2 + 3;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们可以将其描述为使用plus函数折叠集合：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;plus &amp;lt;- (acc, next) =&amp;gt; acc + next;
result &amp;lt;- [1, 2, 3].fold(plus);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这相当于：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result &amp;lt;- plus(plus(1, 2), 3);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们可以清楚地看到结果与我们期望的结果相同。&lt;/p&gt;

&lt;h2 id=&quot;3-折叠-vs-归约&quot;&gt;3. 折叠 vs 归约&lt;/h2&gt;

&lt;p&gt;值得注意的是，&lt;strong&gt;我们有时会看到用归约来代替折叠&lt;/strong&gt;。根据上下文，这可能只是同一操作的另一种说法。但是，在其他情况下，它可能表示具有给定初始值的折叠操作。&lt;/p&gt;

&lt;p&gt;让我们看一个例子：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result &amp;lt;- [1, 2, 3].reduce(plus, 4);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这与以下内容相同：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result &amp;lt;- plus(plus(plus(4, 1), 2), 3);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;这里最大的优势在于，我们不再需要让累加器函数的输出与集合条目的类型相同&lt;/strong&gt;。在我们之前的折叠示例中，累加器函数需要有两个参数和一个返回值，它们都是同一类型，这些参数和返回值也需要与我们正在处理的集合中的条目类型相同。&lt;/p&gt;

&lt;p&gt;通过归约操作，累加器函数的输出现在可以与集合条目具有不同的类型，只要它与提供的初始值和累加器函数的第一个参数相同即可：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;concat &amp;lt;- (acc, next) =&amp;gt; acc + next.toString();
result &amp;lt;- [1, 2, 3].reduce(concat, &quot;&quot;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这相当于：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里，结果将是一个字符串，而集合条目是整数。&lt;/p&gt;

&lt;h2 id=&quot;4-左折叠-vs-右折叠&quot;&gt;4. 左折叠 vs 右折叠&lt;/h2&gt;

&lt;p&gt;在讨论折叠操作时，我们经常会看到它们被称为“左折叠”或“右折叠”，&lt;strong&gt;这决定了我们应该从集合的左端还是右端开始应用累加器函数&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;如果没有指定方向，通常表示向左折叠。在本例中，我们希望从集合左侧开始合并值。&lt;/p&gt;

&lt;p&gt;这正是我们已经看到的：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result &amp;lt;- [1, 2, 3, 4].foldLeft(plus);
result &amp;lt;- plus(plus(plus(1, 2), 3), 4);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在这里，我们将集合中的前两个元素与累加器函数合并。接下来，我们将这个结果与集合中的下一个元素合并。如此循环，直到到达集合末尾。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;右折叠意味着我们开始组合集合右侧的值&lt;/strong&gt;：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result &amp;lt;- [1, 2, 3, 4].foldRight(plus);
result &amp;lt;- plus(1, plus(2, plus(3, 4)));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在这种情况下，我们将集合中的第一个条目与折叠集合其余部分的结果合并。然后重复此步骤，直到到达集合的末尾。&lt;/p&gt;

&lt;h2 id=&quot;5-运算顺序&quot;&gt;5. 运算顺序&lt;/h2&gt;

&lt;p&gt;左折叠和右折叠看起来非常相似，那么它们之间有什么区别呢？&lt;strong&gt;最显著的区别在于操作的执行顺序&lt;/strong&gt;，在某些情况下，这完全没有区别。然而，在其他情况下，这却可能至关重要。&lt;/p&gt;

&lt;p&gt;如果我们的累加函数具有&lt;a href=&quot;https://en.wikipedia.org/wiki/Associative_property&quot;&gt;结合律&lt;/a&gt;，那么左折叠和右折叠将产生完全相同的结果，我们可以直接从结合律的定义中看到这一点：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(x + y) + z = x + (y + z) for all x, y, z in S
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;等式左边对应于“左折叠”的操作方式，右边对应于“右折叠”的操作方式。因此，如果运算符合结合律，则“左折叠”和“右折叠”的结果相同。&lt;/p&gt;

&lt;p&gt;但是，&lt;strong&gt;如果运算不满足结合律，我们就会得到不同的结果&lt;/strong&gt;。例如，我们先不看加法，而是看subtract函数：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;leftResult &amp;lt;- [1, 2, 3].foldLeft(subtract); // (1 - 2) - 3 = -4
rightResult &amp;lt;- [1, 2, 3].foldRight(subtract); // 1 - (2 - 3) = 2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因此，如果我们知道我们的累加器函数不具有结合性，那么选择正确的折叠方向就变得很重要。但是，如果我们的累加器函数具有结合性，那么我们需要探索选择向左折叠还是向右折叠的其他原因。&lt;/p&gt;

&lt;h2 id=&quot;6-实现效率&quot;&gt;6. 实现效率&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;另一个需要考虑的因素是实现的效率&lt;/strong&gt;，传统的左折叠和右折叠定义是递归的：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;algorithm FoldLeft(collection, initial, fn):
    if collection.empty:
        return initial;

    head &amp;lt;- collection[0]
    tail &amp;lt;- collection.slice(1)

    return FoldLeft(tail, fn(initial, head), fn)

algorithm FoldRight(collection, initial, fn):
    if collection.empty:
        return initial;

    head &amp;lt;- collection[0]
    tail &amp;lt;- collection.slice(1)

    return fn(head, FoldRight(tail, initial, fn))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;值得注意的是，这些示例都提供了初始值，但这只是为了方便理解。同样，不使用初始值也可以实现这两个示例。&lt;/p&gt;

&lt;p&gt;这两个算法非常相似，唯一的区别在于递归调用的方式。然而，从效率的角度来看，这种差异非常重要。具体来说，&lt;strong&gt;左折叠是&lt;a href=&quot;https://www.baeldung.com/cs/tail-vs-non-tail-recursion&quot;&gt;尾递归&lt;/a&gt;的，而右折叠不是&lt;/strong&gt;。因此，&lt;strong&gt;我们可以轻松地将左折叠重写为迭代的。这样一来，时间和内存使用效率都会更高&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;```text FoldLeft(collection, initial, fn):
    result &amp;lt;- initial&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for next in collection:
    result &amp;lt;- fn(result, next)

return result ```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将右折叠重写为可迭代的要困难得多，在某些情况下，这可能是不可能的——它要求集合是可逆迭代的，但并非所有集合都是如此。&lt;/p&gt;

&lt;p&gt;这意味着，对于同一个集合，左折叠可能比右折叠更高效。然而，正如我们之前所见，这假设该操作是结合的。此外，效率提升可能只在非常大的集合中才会显著。&lt;/p&gt;

&lt;h2 id=&quot;7-总结&quot;&gt;7. 总结&lt;/h2&gt;

&lt;p&gt;在本文中，我们了解了函数式编程中折叠的含义。我们探讨了左折叠和右折叠之间的一些区别，以及如何在它们之间进行选择。&lt;/p&gt;
</description>
        <pubDate>Sat, 03 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/programming/2025/05/03/collections-fold-left-right.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/programming/2025/05/03/collections-fold-left-right.html</guid>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>软件测试：边界值分析</title>
        <description>&lt;h2 id=&quot;1-概述&quot;&gt;1. 概述&lt;/h2&gt;

&lt;p&gt;在本教程中，&lt;strong&gt;我们将讨论一种有趣的软件测试方法-边界值分析(BVA)&lt;/strong&gt;。此外，我们将了解测试的通用概念以及软件测试方法的层次结构。&lt;/p&gt;

&lt;h2 id=&quot;2-测试&quot;&gt;2. 测试&lt;/h2&gt;

&lt;p&gt;在&lt;a href=&quot;https://www.baeldung.com/cs/sdlc-vs-stlc&quot;&gt;软件开发生命周期&lt;/a&gt;中，软件在开发完成之后、部署之前，需要经过一系列的测试程序。此外，我们进行测试以确保软件没有错误并满足用户需求。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/bva01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;21-测试层次结构&quot;&gt;2.1 测试层次结构&lt;/h3&gt;

&lt;p&gt;我们可以通过测试流程的层次结构来学习测试流程，以便有条理地分析软件，让我们看看测试流程的层次结构：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/bva02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一般来说，测试流程的主要分类取决于我们拥有的资源和测试目的。基本上，我们可以使用&lt;a href=&quot;https://en.wikipedia.org/wiki/TestComplete&quot;&gt;TestComplete&lt;/a&gt;、&lt;a href=&quot;https://www.baeldung.com/cucumber-rest-api-testing&quot;&gt;Cucumber&lt;/a&gt;和&lt;a href=&quot;https://en.wikipedia.org/wiki/Selenium_(software)&quot;&gt;Selenium&lt;/a&gt;等自动化工具来测试软件。我们可以使用各种技术进行自动化测试，例如&lt;a href=&quot;https://www.baeldung.com/cs/ad-hoc-testing&quot;&gt;临时测试&lt;/a&gt;、&lt;a href=&quot;https://www.baeldung.com/cs/exploratory-testing&quot;&gt;探索性测试&lt;/a&gt;和&lt;a href=&quot;https://www.baeldung.com/cs/smoke-testing&quot;&gt;冒烟测试&lt;/a&gt;。但是，手动测试对半熟练劳动力的限制较少。&lt;/p&gt;

&lt;p&gt;在手动测试中，我们可以选择测试方式。&lt;a href=&quot;https://www.baeldung.com/cs/testing-white-box-vs-black-box&quot;&gt;白盒测试&lt;/a&gt;涉及测试代码及其结构，通常，我们将代码交给测试人员，由他们确保代码正确并满足用户需求。黑盒测试则不同，它涉及测试每种输入情况的相应输出，黑盒测试旨在检查代码是否针对任何给定输入产生所需的输出。&lt;/p&gt;

&lt;p&gt;此外，我们可以将黑盒测试分为功能性测试和非功能性测试。功能性测试旨在检查代码的功能是否正确，这涉及各种测试方法的子类别，例如&lt;a href=&quot;https://www.baeldung.com/cs/unit-vs-integration-testing&quot;&gt;单元测试&lt;/a&gt;、集成测试和系统测试。而非功能性测试则分析性能、兼容性和安全性因素。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BVA技术是系统测试方法之一&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;3-边界值分析&quot;&gt;3. 边界值分析&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;边界值分析是对软件特性或项目进行功能测试的一种形式&lt;/strong&gt;，构建检索用户数据并做出相应响应的交互式软件是一种常见的做法。&lt;/p&gt;

&lt;p&gt;值得注意的是，我们从用户那里获取的详细信息必须遵循一些约束，才能在数据库中正确存储。因此，作为测试任何功能功能的一种形式，我们设置了边界值。此外，我们还会检查软件是否获取了符合特定边界的数据。&lt;/p&gt;

&lt;p&gt;我们通过检查软件如何响应各种输入测试用例来进行测试，通过指定最小值和最大值来设置边界。此外，我们期望软件能够识别边界内的有效值。此外，软件会将边界外的值标记为无效。&lt;/p&gt;

&lt;p&gt;让我们直观地看一下测试用例：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/bva03.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;重要的是，我们通过测试的输出来验证软件是否满足用户需求。此外，这确保了无论输入如何，我们测试的功能都是正确的。&lt;/p&gt;

&lt;h3 id=&quot;31-示例&quot;&gt;3.1 示例&lt;/h3&gt;

&lt;p&gt;让我们通过一个例子来理解边界值分析：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2025/programming/bva04.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;例如，某些应用程序要求用户以表格形式填写某些个人信息&lt;/strong&gt;。这些详细信息有一个先决条件，即用户提供的数据必须符合某些约束条件。在给定的示例中，表单中的姓名信息将接收从最小1个字符到最大16个字符的值。同样，年龄信息也需要属于一定范围的数据，最小值为18，最大值为60。&lt;/p&gt;

&lt;h3 id=&quot;32-优点&quot;&gt;3.2 优点&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;BVA的主要优点是它确保软件从用户那里接收正确的数据&lt;/strong&gt;，此外，这还可以避免存储数据时可能出现的混乱。&lt;/p&gt;

&lt;p&gt;在部署之前进行软件测试可以提高软件可靠性和无错误率，此外，这可以测试项目，从而降低测试成本。&lt;/p&gt;

&lt;h3 id=&quot;33-缺点&quot;&gt;3.3 缺点&lt;/h3&gt;

&lt;p&gt;BVA的功能存在缺陷，因为它没有涵盖所有类型的约束设置，主要是它不支持检查布尔值。此外，它无法为单个输入设置多个条件。&lt;/p&gt;

&lt;h2 id=&quot;4-总结&quot;&gt;4. 总结&lt;/h2&gt;

&lt;p&gt;在本文中，我们讨论了软件测试的理念和一些技术。此外，我们还详细探讨了边界值分析。此外，我们还了解了它如何应用于实时应用程序。&lt;/p&gt;
</description>
        <pubDate>Sat, 03 May 2025 00:00:00 +0000</pubDate>
        <link>http://foundry4j.github.io/test/2025/05/03/bva.html</link>
        <guid isPermaLink="true">http://foundry4j.github.io/test/2025/05/03/bva.html</guid>
        
        
        <category>test</category>
        
      </item>
    
  </channel>
</rss>
