<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Glader&apos;s Blog - English</title><description>Glader&apos;s Tech Blog - English Articles RSS Feed</description><link>https://blog.mygld.top/</link><language>en-US</language><item><title>Learning the LangChain4j Framework</title><link>https://blog.mygld.top/en/posts/langchain4j/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/langchain4j/</guid><description>Study notes on learning the LangChain4j framework.</description><pubDate>Sun, 28 Dec 2025 15:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Introduction to LangChain4j&lt;/h2&gt;
&lt;h3&gt;Concept&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;LangChain4j&lt;/strong&gt; is an open-source &lt;strong&gt;Java library/framework&lt;/strong&gt; designed to simplify the process of integrating and using &lt;strong&gt;large language models&lt;/strong&gt; in Java applications. Its design goal is to enable Java developers to easily build complex AI applications (such as chatbots, intelligent assistants, retrieval-augmented generation systems, etc.), just like using Python&amp;#39;s LangChain.&lt;/p&gt;
&lt;h3&gt;Features&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;LLM Integration&lt;/strong&gt;: Provides a unified API that allows you to call various mainstream large language models (such as OpenAI, Google, Anthropic, domestic models, etc.) without directly interfacing with their low-level interfaces.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Modularity and Extensibility&lt;/strong&gt;: The framework adopts a modular design where different components can be imported separately, such as model providers, vector databases, memory management, tool invocation, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RAG Support (Retrieval-Augmented Generation)&lt;/strong&gt;: Converts external text data into vectors, combines with vector databases for semantic retrieval, and injects results into model context to improve answer quality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tools and Agents&lt;/strong&gt;: Supports function calling, external tool integration, and agent workflows to build more complex AI task flows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration with Java Ecosystem&lt;/strong&gt;: Can be used in combination with common Java frameworks (such as Spring Boot, Quarkus).&lt;/p&gt;
&lt;h2&gt;2. Integrating LangChain4j with Java&lt;/h2&gt;
&lt;h3&gt;Adding Dependencies&lt;/h3&gt;
&lt;p&gt;First, we create a basic Java project. I&amp;#39;ll use a Maven project as an example.&lt;/p&gt;
&lt;p&gt;First, introduce the LangChain4j core framework dependency and adapter implementation dependency (using OpenAI&amp;#39;s adapter implementation here), as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;dev.langchain4j&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;langchain4j&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;${langchain4j.version}&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;dev.langchain4j&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;langchain4j-open-ai&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;${langchain4j.version}&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For specific version features and dependencies for other interface specifications (such as Alibaba, Ollama, etc.), you can refer to the &lt;a href=&quot;https://github.com/langchain4j/langchain4j&quot;&gt;official open-source repository&lt;/a&gt; or &lt;a href=&quot;https://docs.langchain4j.dev/&quot;&gt;official documentation&lt;/a&gt;. Their usage methods are basically the same as OpenAI&amp;#39;s.&lt;/p&gt;
&lt;p&gt;Then we introduce JUnit dependency for unit testing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.junit.jupiter&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;junit-jupiter&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;5.11.4&amp;lt;/version&amp;gt;
	&amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Text Generation Quick Start Demo&lt;/h3&gt;
&lt;p&gt;Then we can write a unit test to quickly master the usage of LangChain4j:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test
public void test1() {
	ChatModel model = OpenAiChatModel.builder()
				.apiKey(System.getenv(&amp;quot;OPENAI_API_KEY&amp;quot;))
				.modelName(&amp;quot;gpt-4o-mini&amp;quot;)
				.build();
	String answer = model.chat(&amp;quot;Hello&amp;quot;);
	System.out.println(answer);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have an OpenAI API key, you can directly run this test unit and view the results to output the model&amp;#39;s returned result.&lt;/p&gt;
&lt;p&gt;Note that the &lt;code&gt;ChatModel&lt;/code&gt; API was introduced in LangChain4j version 1.0.0-beta4. In previous versions, you should use &lt;code&gt;ChatLanguageModel&lt;/code&gt; as the standard interface for chat models.&lt;/p&gt;
&lt;p&gt;If we use other third-party APIs that also comply with OpenAI&amp;#39;s interface specification, we can use the &lt;code&gt;baseUrl&lt;/code&gt; method in &lt;code&gt;OpenAiChatModel&lt;/code&gt; to fill in the third-party API interface address. The specific interface address format needs to comply with the third-party interface documentation but should not include specific resource paths. For example, if we use the &lt;a href=&quot;https://www.siliconflow.cn/&quot;&gt;SiliconFlow&lt;/a&gt; interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test
public void test1() {
	ChatModel model = OpenAiChatModel.builder()
				.baseUrl(&amp;quot;https://api.siliconflow.cn/v1&amp;quot;)
				.apiKey(System.getenv(&amp;quot;SILICONFLOW_API_KEY&amp;quot;))
				.modelName(&amp;quot;Qwen/Qwen3-30B-A3B-Instruct-2507&amp;quot;)
				.build();
	String answer = model.chat(&amp;quot;Hello&amp;quot;);
	System.out.println(answer);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result after running is as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1766936577698_image-20251228223835701.png&quot; alt=&quot;image-20251228223835701.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, it successfully prints the model&amp;#39;s returned result, along with a very cute emoji 😊.&lt;/p&gt;
&lt;p&gt;The red warning above can be temporarily ignored. This is because the current project has not yet configured a specific logging implementation. From the dependency tree of the LangChain4j core framework, it only introduces &lt;code&gt;slf4j&lt;/code&gt; as a logging facade without binding any specific logging implementation. Later, when integrating with Spring Boot projects, Spring Boot&amp;#39;s default logging implementation will automatically supplement this part:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1766936591197_image-20251228224635498.png&quot; alt=&quot;image-20251228224635498.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you don&amp;#39;t plan to integrate with Spring Boot projects, you can manually introduce Logback as the logging implementation to eliminate this warning. In fact, Spring Boot has already integrated and auto-configured Logback as the logging implementation by default, so you usually won&amp;#39;t encounter this problem in Spring Boot projects.&lt;/p&gt;
&lt;p&gt;But since we&amp;#39;re only doing testing now, it&amp;#39;s fine not to use a logging system.&lt;/p&gt;
&lt;h3&gt;Text-to-Image Quick Start Demo&lt;/h3&gt;
&lt;p&gt;For text-to-image generation, we use &lt;code&gt;ImageModel&lt;/code&gt;. Here I&amp;#39;m using Alibaba&amp;#39;s &lt;code&gt;Qwen-Image&lt;/code&gt; text-to-image model. We directly call the &lt;code&gt;generate&lt;/code&gt; method under &lt;code&gt;ImageModel&lt;/code&gt;, passing in our text description. For example, if I want to generate an image of a &amp;quot;cat-eared cute girl&amp;quot;, we pass in this string. The return is a &lt;code&gt;Response&lt;/code&gt; data, and we can use &lt;code&gt;response.content().url()&lt;/code&gt; to get the direct link to the generated image.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test
public void test2() {
    ImageModel model = OpenAiImageModel.builder()
            .baseUrl(&amp;quot;https://api.siliconflow.cn/v1&amp;quot;)
            .apiKey(System.getenv(&amp;quot;SILICONFLOW_API_KEY&amp;quot;))
            .modelName(&amp;quot;Qwen/Qwen-Image&amp;quot;)
            .build();
    Response&amp;lt;Image&amp;gt; response = model.generate(&amp;quot;cat-eared cute girl&amp;quot;);
    System.out.println(response.content().url());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result after running is as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1766936603775_image-20251228233034445.png&quot; alt=&quot;image-20251228233034445.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;We open the link, download the image, and get the following image:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1766936613495_outputs_3d5c294b-e6e4-4f01-80e6-0ba68f8c6340_3473e7dd60b0066b46ba8e1fef1f4198_ComfyUI_1c512918_00001_.png&quot; alt=&quot;outputs_3d5c294b-e6e4-4f01-80e6-0ba68f8c6340_3473e7dd60b0066b46ba8e1fef1f4198_ComfyUI_1c512918_00001_.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the quality is excellent and meets our requirements very well.&lt;/p&gt;
&lt;p&gt;Based on this, readers can further explore capabilities in other modality directions (such as text-to-speech, image-to-text, image-to-video, etc.) in combination with the API documentation of the selected platform. Note that different vendors have significant differences in interface design for multimodal capabilities: some platforms provide OpenAI-compatible extended endpoints, while others adopt their own multimodal protocols or workflow orchestration interfaces. In actual implementation, it&amp;#39;s recommended to prioritize solutions with stable interfaces, clear documentation, and mature ecosystems.&lt;/p&gt;
&lt;h2&gt;3. Integrating LangChain4j with Spring Boot&lt;/h2&gt;
&lt;h3&gt;Quick Start Demo&lt;/h3&gt;
&lt;p&gt;To integrate LangChain4j with Spring Boot, your Spring Boot version must be 3.2 or above, and the JDK version requirement is 17 or above. &lt;a href=&quot;https://docs.langchain4j.dev/tutorials/spring-boot-integration/&quot;&gt;Original details&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;LangChain4j mainly provides two types of Starters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Integration Starters&lt;/strong&gt;: Used to interface with common model providers, vector databases, Embedding services, and other third-party components, providing corresponding auto-configuration and configuration capabilities for out-of-the-box use in Spring Boot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative &lt;a href=&quot;https://docs.langchain4j.dev/tutorials/ai-services&quot;&gt;AI Services Starter&lt;/a&gt;&lt;/strong&gt;: Used to enable and enhance declarative AI Service capabilities (such as &lt;code&gt;@AiService&lt;/code&gt;), supporting the definition of AI capabilities through interfaces/annotations, with the framework completing implementation generation, dependency injection, and runtime orchestration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Integration Starters&lt;/h4&gt;
&lt;p&gt;This may sound abstract, so let&amp;#39;s first look at the first type of Starter. The unified specific format for this type of Starter is: &lt;code&gt;langchain4j-{integration-name}-spring-boot-starter&lt;/code&gt;, where &lt;code&gt;{integration-name}&lt;/code&gt; represents the specific integration object (such as a large model provider, Embedding service, or vector database, etc.). For example, if I use OpenAI&amp;#39;s unified interface specification, I can introduce the following dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;dev.langchain4j&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;langchain4j-open-ai-spring-boot-starter&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.10.0-beta18&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the &lt;code&gt;{integration-name}&lt;/code&gt; is &lt;code&gt;open-ai&lt;/code&gt;. For the specific name format of other providers, you can refer to the &lt;a href=&quot;https://github.com/langchain4j/langchain4j-spring&quot;&gt;official repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After introducing the dependency, the next step is the exciting stage of writing configuration information. We can write in &lt;code&gt;application.properties&lt;/code&gt; or in files with formats like &lt;code&gt;application.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If writing in &lt;code&gt;application.properties&lt;/code&gt;, you basically need to add the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;langchain4j.open-ai.chat-model.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.chat-model.model-name=gpt-4o
langchain4j.open-ai.chat-model.log-requests=true
langchain4j.open-ai.chat-model.log-responses=true
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why use ellipsis here? These four are the basic configuration items. The first is the key information, the second is the model information, the third enables logging of &lt;strong&gt;request content sent to the model&lt;/strong&gt;, and the fourth enables logging of &lt;strong&gt;response content returned by the model&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;There are also some other configuration items that can be configured. Please refer to the official &lt;a href=&quot;https://docs.langchain4j.dev/apidocs/index.html&quot;&gt;API documentation&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;For example, since I&amp;#39;m now using SiliconFlow&amp;#39;s interface, I also need a &lt;code&gt;base-url&lt;/code&gt; configuration item, so the overall configuration is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;langchain4j.open-ai.chat-model.api-key=${SILICONFLOW_API_KEY}
langchain4j.open-ai.chat-model.model-name=Qwen/Qwen3-30B-A3B-Instruct-2507
langchain4j.open-ai.chat-model.base-url=https://api.siliconflow.cn/v1
langchain4j.open-ai.chat-model.log-requests=true
langchain4j.open-ai.chat-model.log-responses=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we write a test Controller as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RestController
public class ChatController {

    private ChatModel chatModel;

    public ChatController(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    @GetMapping(&amp;quot;/chat&amp;quot;)
    public String model(@RequestParam(value = &amp;quot;message&amp;quot;, defaultValue = &amp;quot;Hello&amp;quot;) String message) {
        return chatModel.chat(message);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the Controller only depends on the interface &lt;code&gt;ChatModel&lt;/code&gt;, with the specific implementation automatically created as an instance &lt;code&gt;OpenAiChatModel&lt;/code&gt; by the starter for auto-configuration and injection, which is very convenient.&lt;/p&gt;
&lt;p&gt;Then we write a test unit for testing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
private ChatController chatController;
@Test
void test4(){
    System.out.println(chatController.model(&amp;quot;Hello, who are you?&amp;quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result after running is as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1766981637702_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, not only do we have our &lt;code&gt;sout&lt;/code&gt; output results, but there are also log printings of request and response content, which is the result of the two &lt;code&gt;log&lt;/code&gt; settings we enabled in the configuration file.&lt;/p&gt;
&lt;p&gt;In actual project development, we can customize utility classes according to our needs, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@AllArgsConstructor
@Component
public class AIUtil {
    private final ChatModel chatModel;

    public String generateText(String content){
        return chatModel.chat(content);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is very elegant. Remember to add the &lt;code&gt;@Component&lt;/code&gt; annotation to hand it over to the &lt;code&gt;IoC&lt;/code&gt; container for management.&lt;/p&gt;
&lt;p&gt;All our previous tests wait for the interface to fully generate the text before getting results and printing them. However, when we use AI for conversations on web pages, its output is often streaming, for example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767012674563_d3vyk-ytvxb.gif&quot; alt=&quot;d3vyk-ytvxb.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, it outputs word by word, rather than returning all content to us after retrieving everything. This output method is usually called &lt;strong&gt;streaming output&lt;/strong&gt;. Its core idea is: during the content generation process, every time the model generates a token (or a text fragment), it immediately returns it to the caller, rather than waiting for the complete response to be generated.&lt;/p&gt;
&lt;p&gt;For AI conversations in web scenarios, streaming output is almost a &amp;quot;standard feature&amp;quot;. It can significantly improve user experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lower first token latency&lt;/strong&gt;: Users can almost immediately see the model start &amp;quot;talking&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More natural interaction&lt;/strong&gt;: Closer to the rhythm of human input&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better suited for long text generation&lt;/strong&gt;: Avoids long blank screen waiting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next, let&amp;#39;s see how to elegantly implement streaming output in the &lt;strong&gt;LangChain4j + Spring Boot&lt;/strong&gt; scenario.&lt;/p&gt;
&lt;p&gt;In LangChain4j, streaming output is not completed through &lt;code&gt;ChatModel&lt;/code&gt;, but through a dedicated &lt;strong&gt;Streaming interface&lt;/strong&gt;, such as &lt;code&gt;StreamingChatModel&lt;/code&gt;. When using interfaces that comply with the corresponding model provider&amp;#39;s specifications, LangChain4j will establish a streaming connection with the model through &lt;strong&gt;SSE (Server-Sent Events)&lt;/strong&gt; at the bottom layer and callback the &lt;strong&gt;token increments&lt;/strong&gt; generated by the model one by one to the caller.&lt;/p&gt;
&lt;p&gt;Fortunately, we don&amp;#39;t need to manually handle these complex low-level details.&lt;/p&gt;
&lt;p&gt;LangChain4j&amp;#39;s Spring Boot Starter has already encapsulated the corresponding auto-configuration capabilities for us and will automatically create and register Beans related to &lt;code&gt;StreamingChatModel&lt;/code&gt; based on the configuration file.&lt;/p&gt;
&lt;p&gt;First, we need to add streaming model configuration in &lt;code&gt;application.properties&lt;/code&gt;. Unlike the &lt;code&gt;chat-model&lt;/code&gt; used for synchronous calls earlier, here we use the &lt;code&gt;streaming-chat-model&lt;/code&gt; prefix, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;langchain4j.open-ai.streaming-chat-model.api-key=${SILICONFLOW_API_KEY}
langchain4j.open-ai.streaming-chat-model.model-name=Qwen/Qwen3-30B-A3B-Instruct-2507
langchain4j.open-ai.streaming-chat-model.base-url=https://api.siliconflow.cn/v1
langchain4j.open-ai.streaming-chat-model.log-requests=true
langchain4j.open-ai.streaming-chat-model.log-responses=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we modify the utility class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@AllArgsConstructor
@Component
public class AIUtil {
    private final StreamingChatModel streamingChatModel;

    public void generateText(String content, StreamingChatResponseHandler handler){
        streamingChatModel.chat(content,handler);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just change the original &lt;code&gt;ChatModel&lt;/code&gt; to &lt;code&gt;StreamingChatModel&lt;/code&gt;, then pass a &lt;strong&gt;callback handler: StreamingChatResponseHandler&lt;/strong&gt; in the &lt;code&gt;chat&lt;/code&gt; method. The changes are very minor.&lt;/p&gt;
&lt;p&gt;Now &lt;strong&gt;Spring Boot no longer requires manually writing SSE details for streaming returns&lt;/strong&gt;. We just need to introduce the &lt;code&gt;Spring WebFlux&lt;/code&gt; dependency, and we can conveniently implement &lt;strong&gt;automatic chunking, real-time pushing, and connection management&lt;/strong&gt; of server data by returning &lt;code&gt;Flux&lt;/code&gt;. Spring will handle the underlying SSE protocol encapsulation and refresh logic.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Spring WebFlux&lt;/code&gt; dependency format is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-webflux&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;#39;s write a &lt;code&gt;Service&lt;/code&gt; and &lt;code&gt;Controller&lt;/code&gt; as quickly as possible for testing, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class ChatServiceImpl implements ChatService {

    @Autowired
    private AIUtil aiUtil;

    @Override
    public Flux&amp;lt;String&amp;gt; chat(String content) {
        return Flux.create(e-&amp;gt;aiUtil.generateText(content, new StreamingChatResponseHandler() {
            @Override
            public void onPartialResponse(String partialResponse) {
                e.next(partialResponse);
            }

            @Override
            public void onCompleteResponse(ChatResponse chatResponse) {
                e.next(&amp;quot; Output complete.&amp;quot;);
                e.complete();
            }

            @Override
            public void onError(Throwable throwable) {
                e.next(throwable.getMessage());
            }
        }));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RestController
public class ChatController {

    @Autowired
    private ChatService chatService;

    @GetMapping(value = &amp;quot;/chat&amp;quot;, produces = &amp;quot;text/event-stream;charset=UTF-8&amp;quot;)
    public Flux&amp;lt;String&amp;gt; chat(@RequestParam(&amp;quot;message&amp;quot;) String content) {
        return chatService.chat(content);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we open ApiFox for testing. We input our test URL: &lt;code&gt;http://localhost:8080/api/chat?message=Hello, who are you&lt;/code&gt;, and we can see the returned results popping out word by word:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767025808832_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;From the running results, we can see that the web side can &lt;strong&gt;receive real-time returned content segment by segment&lt;/strong&gt;, which is precisely the result of the collaborative work of various callback methods of &lt;code&gt;StreamingChatResponseHandler&lt;/code&gt;. Let&amp;#39;s analyze the role of these callbacks in streaming output one by one.&lt;/p&gt;
&lt;p&gt;The several callback methods of &lt;code&gt;StreamingChatResponseHandler&lt;/code&gt; correspond to different stages of the model generation process: when the model has not yet fully generated the result, &lt;code&gt;onPartialResponse&lt;/code&gt; will be triggered multiple times, returning a small piece of content each time. Here we immediately push the data downstream through &lt;code&gt;FluxSink.next()&lt;/code&gt;, thereby achieving real-time output of text on the front-end page; when the model finishes generating, &lt;code&gt;onCompleteResponse&lt;/code&gt; is called back to indicate that this generation is complete. In actual use, the stream is usually ended here; if an exception occurs during the generation process, &lt;code&gt;onError&lt;/code&gt; is triggered to pass the error signal to the front end. It is precisely by converting this callback-based flow into &lt;code&gt;Flux&lt;/code&gt; through &lt;code&gt;Flux.create&lt;/code&gt; that Spring WebFlux can automatically write data continuously into the HTTP response in a streaming manner, ultimately achieving streaming output without manually writing SSE.&lt;/p&gt;
&lt;p&gt;In addition to the three main callback methods mentioned above, there is also an &lt;code&gt;onPartialThinking&lt;/code&gt; method. What is this method for?&lt;/p&gt;
&lt;p&gt;We find that many models now come with a thinking process, which first conducts reasoning. This callback is actually for handling the content returned by the reasoning process. We won&amp;#39;t continue discussing this here. The principle is similar to &lt;code&gt;onPartialResponse&lt;/code&gt;. Interested friends can continue to explore. In fact, we will have &lt;strong&gt;more concise&lt;/strong&gt; callback writing methods later, which we&amp;#39;ll continue to explore later.&lt;/p&gt;
&lt;h4&gt;Declarative AI Services Starter&lt;/h4&gt;
&lt;p&gt;LangChain4j provides a Spring Boot starter for auto-configuring &lt;a href=&quot;https://docs.langchain4j.dev/tutorials/ai-services&quot;&gt;AI Services&lt;/a&gt;, &lt;a href=&quot;https://docs.langchain4j.dev/tutorials/rag&quot;&gt;RAG&lt;/a&gt;, &lt;a href=&quot;https://docs.langchain4j.dev/tutorials/tools&quot;&gt;Tools&lt;/a&gt;, etc.&lt;/p&gt;
&lt;p&gt;We have completed the auto-configuration of the underlying model client, but if we still stay at the level of directly injecting &lt;code&gt;ChatModel&lt;/code&gt; and manually writing Prompt and callback logic, the overall usage is still imperative. The significance of &lt;strong&gt;AI Services Starter&lt;/strong&gt; is to further abstract these boilerplate codes, allowing you to use large model capabilities through declarative interfaces just like writing ordinary Spring Services. You only need to define method signatures and annotations, and LangChain4j will automatically generate implementations at runtime, handling details such as Prompt construction, model calling, and result mapping. Combined with Spring Boot&amp;#39;s auto-configuration, model parameters and keys can be centrally managed in &lt;code&gt;application.yml&lt;/code&gt;, making it convenient to switch between different environments or models. When RAG or tool calling is introduced, the advantages of this approach become even more obvious. The relevant orchestration is almost transparent to business code, keeping the code always simple, clear, and maintainable.&lt;/p&gt;
&lt;p&gt;Next, let&amp;#39;s quickly demonstrate the specific usage in code.&lt;/p&gt;
&lt;p&gt;First, we need to import the &lt;code&gt;langchain4j-spring-boot-starter&lt;/code&gt; dependency as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;dev.langchain4j&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;langchain4j-spring-boot-starter&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.10.0-beta18&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;!--        langchain4j-reactor is LangChain4j&amp;#39;s official adapter layer for Reactor (Mono / Flux),--&amp;gt;
&amp;lt;!--        because we used Flux for streaming output, we need to introduce the adapter layer dependency below.--&amp;gt;
&amp;lt;!--        If streaming output is not needed in your project, you don&amp;#39;t need to introduce the dependency below,--&amp;gt;
&amp;lt;!--        only need to introduce the one above.--&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;dev.langchain4j&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;langchain4j-reactor&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.10.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we comment out or delete &lt;code&gt;ChatServiceImpl&lt;/code&gt;. We don&amp;#39;t need to manually implement such tedious code. We go directly back to the &lt;code&gt;ChatService&lt;/code&gt; interface and add the &lt;code&gt;AiService&lt;/code&gt; annotation to it, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@AiService
public interface ChatService {
    Flux&amp;lt;String&amp;gt; chat(String content);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No other code needs to be changed. Let&amp;#39;s run and test it in ApiFox again:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767150345242_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, as we expected, it can correctly give streaming responses, and we don&amp;#39;t need to write those tedious implementation class codes ourselves.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s go back and look at the source code of &lt;code&gt;@AiService&lt;/code&gt; to see what it actually does for us. The source code is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AiService {
    AiServiceWiringMode wiringMode() default AiServiceWiringMode.AUTOMATIC; // Wiring mode: automatic or manually specify dependencies
    String chatModel() default &amp;quot;&amp;quot;;                 // Synchronous ChatModel Bean name
    String streamingChatModel() default &amp;quot;&amp;quot;;        // Streaming StreamingChatModel Bean name
    String chatMemory() default &amp;quot;&amp;quot;;                // ChatMemory Bean name for conversation memory
    String chatMemoryProvider() default &amp;quot;&amp;quot;;        // ChatMemoryProvider Bean name for creating memory by session
    String contentRetriever() default &amp;quot;&amp;quot;;          // ContentRetriever Bean name for RAG retrieval
    String retrievalAugmentor() default &amp;quot;&amp;quot;;        // RetrievalAugmentor Bean name encapsulating complete RAG enhancement process
    String moderationModel() default &amp;quot;&amp;quot;;           // ModerationModel Bean name for content safety review
    String toolProvider() default &amp;quot;&amp;quot;;               // ToolProvider Bean name for unified tool provision
    String[] tools() default {};                   // Specify list of tool Bean names to inject
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, &lt;code&gt;AiService&lt;/code&gt; also encapsulates the &lt;code&gt;Service&lt;/code&gt; annotation. Therefore, using &lt;code&gt;AiService&lt;/code&gt; is equivalent to using the &lt;code&gt;Service&lt;/code&gt; annotation, so it will be scanned by Spring and managed by IoC. However, when it&amp;#39;s annotated on an interface, what Spring injects is not the interface itself, but a proxy implementation generated by LangChain4j at runtime based on annotation configuration. For knowledge about dynamic proxies, you can refer to another &lt;a href=&quot;https://blog.mygld.top/en/posts/dynamic-proxy/&quot;&gt;blog post&lt;/a&gt; of mine; this proxy will be responsible for Prompt assembly, model selection (chat/streaming), and orchestration of capabilities such as memory/RAG/tools, making interface methods look like ordinary Service calls, but actually calling large models at the bottom layer. Let&amp;#39;s look at its dynamic proxy part of the source code, located under &lt;code&gt;dev.langchain4j.service.DefaultAiServices&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Internal
class DefaultAiServices&amp;lt;T&amp;gt; extends AiServices&amp;lt;T&amp;gt; {
    ...
    public T build() {
        validate();
        Object proxyInstance = Proxy.newProxyInstance(
                context.aiServiceClass.getClassLoader(),
                new Class&amp;lt;?&amp;gt;[]{context.aiServiceClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // Object / default method handling
                        if (method.isDefault()) {
                            return InvocationHandler.invokeDefault(proxy, method, args);
                        }
                        if (method.getDeclaringClass() == Object.class) {
                            ...
                        }
                        // Build an AI call context
                        InvocationContext invocationContext = InvocationContext.builder()
                                .invocationId(UUID.randomUUID())
                                .interfaceName(context.aiServiceClass.getName())
                                .methodName(method.getName())
                                .build();
                        // Core entry: convert interface method call to AI call
                        return invoke(method, args, invocationContext);
                    }
                    public Object invoke(Method method, Object[] args, InvocationContext invocationContext) {
                        // Prompt / Memory / RAG / Guardrails preparation
                        ...
                        // Determine if it&amp;#39;s a streaming return
                        Type returnType = method.getGenericReturnType();
                        boolean streaming =
                                returnType == TokenStream.class || canAdaptTokenStreamTo(returnType);
                        if (streaming) {
                            // Streaming: return TokenStream or adapted type (like Flux)
                            TokenStream tokenStream = new AiServiceTokenStream(...);
                            return returnType == TokenStream.class
                                    ? tokenStream
                                    : adapt(tokenStream, returnType);
                        }
                        // Non-streaming: synchronously call model and parse results
                        ChatResponse response = chatModel.execute(...);
                        return serviceOutputParser.parse(response, returnType);
                    }
                }
        );
        return (T) proxyInstance;
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the same time, we notice that &lt;code&gt;AiService&lt;/code&gt; also provides many annotation properties for us to use. We&amp;#39;re only doing a quick start here. The specific roles of other properties will be explained in detail in separate articles later.&lt;/p&gt;
</content:encoded><category>LangChain4j</category><category>AI</category><category>Java</category><category>Spring Boot</category><author>Glader</author></item><item><title>Interpretation of Differentiable Auxiliary Learning for Sketch Re-Identification</title><link>https://blog.mygld.top/en/posts/sbir-02/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/sbir-02/</guid><description>Paper reading of Differentiable Auxiliary Learning for Sketch Re-Identification.</description><pubDate>Sat, 27 Dec 2025 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This paper is an AAAI-2024 article, titled &amp;quot;Differentiable Auxiliary Learning for Sketch Re-Identification.&amp;quot;&lt;/p&gt;
&lt;p&gt;The paper proposes a network architecture called DALNet. This method generates a &amp;quot;sketch-like&amp;quot; intermediate auxiliary modality by performing background removal and edge detection enhancement on real photos, thereby bridging and aligning the sketch and photo modalities. Since the module responsible for generating this auxiliary modality is trainable and differentiable, supporting end-to-end optimization, the method is named &amp;quot;&lt;strong&gt;D&lt;/strong&gt;ifferentiable &lt;strong&gt;A&lt;/strong&gt;uxiliary &lt;strong&gt;L&lt;/strong&gt;earning&amp;quot; (DAL).&lt;/p&gt;
&lt;h2&gt;1. Motivation&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019243298_f1.png&quot; alt=&quot;f1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown in the figure above, the paper&amp;#39;s motivation is very clear and can be divided into two main points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;inter-modal&lt;/strong&gt; differences between sketches and pedestrian images are too large. Can an intermediate modality be constructed to help bridge the gap between these two modalities?&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;intra-modal&lt;/strong&gt; differences within sketches and pedestrian images are also significant. First, sketches of the same pedestrian may be drawn by different artists with diverse styles and varying levels of abstraction; additionally, photos of the same pedestrian under different cameras are severely affected by background clutter, illumination changes, and viewpoint/pose variations. This causes even photos of the same person to be far apart in the feature space, increasing matching difficulty.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To address the first problem, the paper constructs a &amp;quot;sketch-like&amp;quot; intermediate auxiliary modality as a bridge. This modality is generated through background removal and edge detection enhancement on real photos, effectively assisting in establishing &lt;strong&gt;inter-modal&lt;/strong&gt; feature alignment. During the feature learning stage, the paper incorporates multi-modal collaborative constraints: using cross-modal circle loss to align the overall relationship among sketches, photos, and sketch-like images.&lt;/p&gt;
&lt;p&gt;To address the second problem, the paper introduces &lt;strong&gt;intra-modal&lt;/strong&gt; circle loss to specifically compress the distribution of the same identity within the same modality and increase the distance between different identities. For sketches, this reduces the impact caused by different artist styles; for real images, this reduces feature drift issues caused by illumination, background, and viewpoint/pose changes.&lt;/p&gt;
&lt;h2&gt;2. Method&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019247006_f2.png&quot; alt=&quot;f2.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The overall framework of the model is shown in the figure above. In general, during the training phase, the Dynamic Auxiliary Generator (DAG) module first generates a sketch-like auxiliary modality image (this module is trainable and optimized through the $L_{SR}$ loss to make the generated auxiliary images adaptively approximate the target sketch style). Then, images from three modalities—sketches, auxiliary images, and real images—are encoded to extract features. The auxiliary modality features are then fused into the other two modalities, and through cross-attention mechanisms, shared semantic information between sketches and real images is strengthened to achieve fine-grained interactive fusion. Finally, classification loss and cross-modal/intra-modal circle loss jointly constrain the feature relationships and distributions of the three modalities. Below, I will analyze the details of each component.&lt;/p&gt;
&lt;h3&gt;Dynamic Auxiliary Generator (DAG)&lt;/h3&gt;
&lt;p&gt;This is the dynamic auxiliary generator module, whose function is to input a real image and output a sketch-like auxiliary image. Its principle is specifically shown in the following figure:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019255280_f3.png&quot; alt=&quot;f3.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;First, the real pedestrian image is input into the pre-trained U²-Net (whose network parameters remain frozen) to obtain a foreground segmentation mask matrix; then, this mask is used to filter the original image pixel by pixel, thereby retaining the pedestrian body and removing the background, obtaining a pedestrian image with the background removed. The specific structure of U²-Net is shown on the right side of the figure above. The reason it&amp;#39;s called U²-Net is that it is overall a U-Net-style encoder-decoder structure, but each encoding and decoding stage internally embeds a smaller U-Net (i.e., the RSU module, Residual U-block), equivalent to &amp;quot;U within U,&amp;quot; forming a cascade of two-layer U-shaped structures, hence named with U squared.&lt;/p&gt;
&lt;p&gt;U²-Net generates multiple masks (side maps) from different scale side output layers during the decoding process. These masks correspond to foreground predictions at different resolutions: shallow side outputs focus more on edges and details, while high-level side outputs focus more on overall structure and semantic regions. During training, deep supervision is typically applied to these side outputs to stabilize convergence and enhance multi-scale segmentation capabilities. Finally, the multi-scale side outputs are fused (such as concatenation followed by convolution or weighted fusion) to obtain a final high-quality foreground mask for subsequent background removal.&lt;/p&gt;
&lt;p&gt;After obtaining the pedestrian image with the background removed, it is still an RGB color image. Therefore, it first needs to be converted into a single-channel grayscale image through a 1×1 convolutional block, then passed through a 3×3 convolutional kernel for edge detection to enhance contour lines, making the grayscale image more approximate to a sketch, thereby generating a sketch-like auxiliary modality image. Here, this 3×3 convolutional kernel is trainable, which is also the only trainable and optimizable part of the DAG module, constrained by the loss function $L_{SR}$. The definition of this loss function will be analyzed in detail in the feature extraction module below. The convolutional kernel is initialized with a center value of 9 and surrounding 8 values of -0.8, first providing a stable &amp;quot;edge enhancement&amp;quot; prior, then adaptively adjusting the convolutional kernel weights during training according to downstream retrieval objectives, making the generated Auxiliary more conform to the contour expression of the sketch domain.&lt;/p&gt;
&lt;h3&gt;Feature Extraction&lt;/h3&gt;
&lt;p&gt;Next is the feature extraction module. As shown in the figure below, a three-stream ResNet-50 is used as the backbone to encode features for Photo, Auxiliary, and Sketch modalities respectively: each modality first passes through its own front-end ResBlocks (using the first two stages of ResNet-50) to extract low-level local features, obtaining global representations for the three streams; then the three-stream features are sent to the same set of &amp;quot;weight-shared&amp;quot; subsequent ResBlocks (using the remaining stages of ResNet-50) to learn higher-level, more semantically-oriented shared representations, preparing for subsequent modality interaction alignment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019269713_f4.png&quot; alt=&quot;f4.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Specifically, define $P={x_i^P}_{i=1}^{N_p}$ as the sample set of real images, where $N_p$ represents the number of samples. The $A$ and $S$ sets are similar, representing the sketch-like set and sketch set respectively.&lt;/p&gt;
&lt;p&gt;Now, Photo, Auxiliary, and Sketch are put into their respective front-end ResBlocks to obtain three encoded feature maps. GeM Pooling operation is performed on these feature maps to obtain three pooled feature vectors $I^P$, $I^A$, and $I^S$ respectively. The specific formula is shown as $I_k^u$ in the figure above, where $u$ represents the modality, $k$ represents the current $k$-th channel, $H$ and $W$ represent the height and width of the feature map respectively, and $p$ is a learnable parameter. When $p$ is close to 1, the pooling will be more like &amp;quot;average pooling&amp;quot; (doing more uniform aggregation over the entire feature map); as $p$ gradually increases, the aggregation process will increasingly approach &amp;quot;max pooling&amp;quot; (emphasizing the positions with the strongest responses). The reason for using GeM Pooling is that it adaptively compromises between average pooling and max pooling with a learnable $p$: it can both preserve global structural information and highlight local discriminative regions more critical for pedestrian retrieval (such as clothing textures, contour details, etc.), thereby obtaining more robust and discriminative global representations in cross-modal matching.&lt;/p&gt;
&lt;p&gt;After obtaining the three feature vectors, the paper calculates the style refinement loss function $L_{SR}$ (whose form is similar to InfoNCE), with the specific formula shown in the figure above. The purpose is to pull the Auxiliary generated by DAG from &amp;quot;photo style&amp;quot; toward &amp;quot;sketch style,&amp;quot; thereby optimizing the convolutional kernel parameters of the DAG module, without destroying the human body structure information it inherits from Photo. The specific approach is: using the style feature $I^A$ of the auxiliary modality as the anchor, treating the sketch style feature $I^S$ of the same identity as the only positive sample; in the denominator, only $I^S$ and a set of photo style features ${I_i^P}_{i=1}^{N}$ are included for comparison, without adding other sketch features, because the goal here is not to learn the &amp;quot;separability between sketches&amp;quot; (which belongs to identity discrimination and retrieval loss to handle), but to explicitly distinguish $I^A$ from the Photo domain and make it approach the Sketch domain: if the denominator introduces a large number of &amp;quot;other sketches,&amp;quot; the optimization will become making $I^A$ simultaneously far from these sketches (including sketches with similar styles to the target sketch), which can easily weaken the traction force of &amp;quot;approaching the sketch domain&amp;quot; and even introduce unnecessary identity and style mixing. Therefore, the comparison set of this loss is deliberately designed as &amp;quot;one sketch positive sample + multiple photo negative samples,&amp;quot; maximizing the similarity between $I^A$ and $I^S$ through softmax normalization with temperature coefficient $\xi$, minimizing the similarity between $I^A$ and each $I_i^P$, thereby achieving the style transfer constraint of &amp;quot;de-photo stylization + alignment toward sketch style.&amp;quot; The experimental details indicate that the temperature coefficient $\xi$ is set to 0.07.&lt;/p&gt;
&lt;p&gt;Afterward, the feature maps from the three streams are sent to the same set of &amp;quot;weight-shared&amp;quot; subsequent ResBlocks to learn higher-level, more semantically-oriented shared representations, specifically denoted as $f^P$, $f^A$, and $f^S$, preparing for subsequent modality interaction alignment. Note here that what is sent to the subsequent ResBlocks is the original feature maps generated by the three streams, not the pooled $I^u$.&lt;/p&gt;
&lt;h3&gt;Modality Interactive Attention (MIA)&lt;/h3&gt;
&lt;p&gt;Next is the modality interactive attention module, specifically shown in the figure below. This module consists of two sub-modules: the Bilinear Align module and the Auxiliary Cross-Attention module.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019279011_f5.png&quot; alt=&quot;f5.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Bilinear Align Module (BAM) is shown in the figure above. Its core function is to use the auxiliary modality feature map $f^A$ to calculate similarity weights $S_{uA}$ with the other two modality feature maps $f^P$ and $f^S$ (where $u\in{P,S}$). $S_{uA}$ can be understood as an &amp;quot;attention scoring map&amp;quot;: it characterizes the matching degree between the current modality features and auxiliary modality features at each spatial position and each channel. Positions with high matching will be assigned larger weights, thereby highlighting more reliable and consistent pedestrian structure cues when weighting features subsequently. Specifically, during calculation, $f^A$ and the target modality&amp;#39;s $f^u$ are first concatenated in the channel dimension, then sent to the bilinear alignment unit to model the second-order interaction relationship between the two, obtaining aligned responses. Then, through the sigmoid function $\sigma(\cdot)$, they are normalized to the $[0,1]$ range, forming similarity probability $S_{uA}$, which is used to perform attention weighting enhancement on $f^u$ and output aligned features.&lt;/p&gt;
&lt;p&gt;The specific steps of bilinear alignment are shown in the figure above. First, tensor reshaping is required. The original $f^P$, $f^A$, and $f^S$ are actually tensors of shape $(N,C,H,W)$, where $N$ represents batch size, $C$ represents the number of channels, and $H$ and $W$ represent the height and width of the feature map respectively. First, the shape is adjusted to $(N,C,HW)$, then stacking and concatenation are performed to obtain a $(N,2C,HW)$ result concatenated on channels, as shown in the red box in the figure above. Taking the upper red box&amp;#39;s $f^P$ and $f^A$ as examples, after reshaping, these two feature maps are represented by a series of long strips, where the length extending into the screen can be abstracted as $HW$, and the number in the vertical direction can be represented as the number of channels $C$. When the two feature maps are stacked together, there are a total of $2C$ strips (C orange ones in the upper half, C green ones in the lower half). Then they enter a linear layer to compress the channels to $C/4$. The purpose is to first perform &amp;quot;dimensionality reduction compression&amp;quot; on the channel expression after two-modality concatenation as a lightweight bottleneck transformation without losing key information: because the number of channels becomes $2C$ after concatenation, if bilinear interaction is done directly in high-dimensional space, both parameter and computational costs will be large, and it&amp;#39;s easy to overfit. Therefore, the first linear layer first compresses the channels to $C/4$, which is equivalent to feature screening and information distillation, retaining the relevant components most helpful for two-modality alignment. Then it enters another linear layer to restore the channels back to $C$. The purpose is to remap the compressed &amp;quot;alignment relationship&amp;quot; back to the channel dimension consistent with the original backbone features, facilitating subsequent fusion/element-wise operations with original features, and ensuring that the output dimension matches subsequent modules (such as similarity estimation and attention weighting).&lt;/p&gt;
&lt;p&gt;The calculated $S_{uA}$ will be reshaped back to $(N,C,H,W)$ shape to correspond one-to-one with the original feature map $f^u$ in spatial positions and channel dimensions. Subsequently, features are weighted and enhanced according to the alignment formula $f_{ali}^u = S_{uA}\odot f^u + f^u$: where $S_{uA}$ serves as attention weights, assigning higher responses to regions more consistent with the auxiliary modality and suppressing inconsistent or noisy regions, while preserving original information through the residual term. The finally obtained $f_{ali}^P$ and $f_{ali}^S$ are the Photo and Sketch feature maps guided and aligned by Auxiliary, providing &amp;quot;cleaner&amp;quot; and more alignable representations for subsequent ACA cross-attention interaction.&lt;/p&gt;
&lt;p&gt;Meanwhile, to enable the auxiliary modality to also participate in bidirectional matching with &amp;quot;aligned, cleaner representations&amp;quot; during subsequent ACA cross-attention interaction (for example, using $f_{ali}^A$ as Key/Query to interact with $f_{ali}^P$ or $f_{ali}^S$), the paper also performs the same weighted residual enhancement on the auxiliary modality itself to obtain aligned auxiliary features $f_{ali}^A$. Specifically defined as using the similarity weight $S_{SA}$ between sketch and auxiliary modality to weight $f^A$: $f_{ali}^A = S_{SA}\odot f^A + f^A$.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019284989_150ddc67-7a3d-4eec-af71-15cdf1af13d4.png&quot; alt=&quot;150ddc67-7a3d-4eec-af71-15cdf1af13d4.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The final result is like this, where depth and shallowness represent the degree of attention to features. Deeper means stronger attention, while shallower means weaker. As for why some are dashed boxes, the paper doesn&amp;#39;t explain. I&amp;#39;ve consulted some drawing materials and learned that dashed lines may represent some common features under the three modalities.&lt;/p&gt;
&lt;h4&gt;Auxiliary Cross-Attention Module (ACA)&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019295101_f6.png&quot; alt=&quot;f6.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The purpose of ACA is not to &amp;quot;calculate another similarity score,&amp;quot; but to actually enable information exchange and fusion among the three modalities based on the aligned features obtained from BAM: the paper emphasizes that it &amp;quot;uses the auxiliary modality to guide the model in learning the distribution of modality-shared representations,&amp;quot; and can achieve significant information interaction and fusion between photo and sketch.&lt;/p&gt;
&lt;p&gt;As shown in the figure above, taking &amp;quot;interaction between photo and auxiliary&amp;quot; as an example for specific calculation: the aligned features $f_{ali}^P$ and $f_{ali}^A$ output by BAM are respectively treated as Query and Key (denoted as $Q^P$ and $K^A$), then standard scaled dot-product attention is used to obtain matching weights from photo to auxiliary: $W_{P\rightarrow A} = \text{Softmax}\left(\frac{Q^P (K^A)^T}{\sqrt{d_K}}\right)$, where $d_K$ is the channel dimension of Key.
At the same time, Query and Key are exchanged to obtain reverse matching weights: $W_{A\rightarrow P} = \text{Softmax}\left(\frac{Q^A (K^P)^T}{\sqrt{d_K}}\right)$.&lt;/p&gt;
&lt;p&gt;With bidirectional weights, the paper uses a &amp;quot;round-trip consistency&amp;quot; method to refine photo features: using $V^P=f_{ali}^P$ as Value, multiplying $W_{P\rightarrow A}$ and $W_{A\rightarrow P}$ first, then weighting $V^P$, and finally performing LayerNorm to obtain photo features highlighted by the auxiliary modality: $\hat f^P = \text{Norm}\left(W_{P\rightarrow A} W_{A\rightarrow P} V^P\right),\quad V^P=f_{ali}^P.$
This can be understood as: first using $W_{P\rightarrow A}$ to find which positions or patterns in photo can find correspondence in auxiliary, then using $W_{A\rightarrow P}$ to &amp;quot;map this correspondence back&amp;quot; to confirm consistency, thereby more robustly strengthening the structural semantics shared by both and suppressing noise and inconsistent regions in each.&lt;/p&gt;
&lt;p&gt;Similarly, performing the same bidirectional cross-attention on $f_{ali}^S$ and $f_{ali}^A$ yields the refined sketch features $\hat f^S$ guided by the auxiliary latent representation.&lt;/p&gt;
&lt;h3&gt;Multi-Modality Collaborative Learning&lt;/h3&gt;
&lt;p&gt;Multi-modality collaborative learning. This part mainly revolves around two types of losses: one is category loss, used to ensure that features of the three modalities have clear identity discriminability; the other is the circle loss series of metric learning losses, where cross-modal circle loss is used to constrain samples of different modalities but the same identity to approach each other in the feature space, and different identities to separate from each other, achieving overall inter-modal alignment; meanwhile, intra-modal circle loss is introduced to specifically compress the distribution of same-identity samples within the same modality and increase the distance between different identities, to alleviate intra-class dispersion problems caused by sketch style differences and photo viewpoint/illumination/background changes. The two work together to enable the model to simultaneously complete inter-modal and intra-modal alignment.&lt;/p&gt;
&lt;h4&gt;Category Loss&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019299719_f7.png&quot; alt=&quot;f7.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown in the figure above, the category (identity) loss uses $L_{ID}$. The purpose is to use &amp;quot;identity supervision&amp;quot; to pull the three modalities into the same separable identity space, allowing Photo/Auxiliary/Sketch of the same person to learn consistent identity patterns. The paper writes it as the sum of two parts: $L_{ID}=L_{id}(F)+L_{id}(\hat F)$, where $F={f^P,f^A,f^S}$ represents the set of feature maps output by the three modalities through shared ResBlocks, $\hat F={\hat f^P,\hat f^S}$ represents the set of Photo and Sketch feature maps enhanced through fine-grained interaction in MIA, and $L_{id}$ is the standard cross-entropy classification loss (using real identity labels for softmax classification supervision). The intuitive meaning of this design is: on one hand, directly constraining the three-modality basic representations extracted by the shared backbone to align at the identity level; on the other hand, also constraining the enhanced representations after auxiliary modality-guided interaction to still maintain correct identity discriminability, avoiding attention interaction &amp;quot;aligning&amp;quot; features to the wrong person or introducing identity-irrelevant deviations.&lt;/p&gt;
&lt;h4&gt;Circle Loss&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019307867_f8.png&quot; alt=&quot;f8.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown in the figure above, the paper improves upon circle loss proposed in 2020, making it suitable for sketches and images, and designs two loss functions. One is $L_{CM}$, used for inter-modal alignment; the other is $L_{IM}$, used for intra-modal alignment.&lt;/p&gt;
&lt;p&gt;The paper uses circle loss for metric learning constraints rather than the common triplet loss. The core reason is: Sketch Re-ID simultaneously has &amp;quot;huge cross-modal differences and extremely few samples per identity.&amp;quot; Simply using triplets often only provides local constraints on a small number of triplets, easily resulting in unstable optimization, strong dependence on hard samples, and the phenomenon of only considering cross-modal while ignoring intra-modal distribution. Circle loss, on the other hand, uses a unified form of &amp;quot;pairwise similarity optimization&amp;quot; to incorporate multiple pairs of positive and negative samples in a batch into optimization together, and emphasizes &amp;quot;harder&amp;quot; positive-negative pairs through adaptive weights $\alpha_i^+,\alpha_j^-$, making it more suitable for learning a more robust metric space with limited samples.&lt;/p&gt;
&lt;p&gt;Let me first explain my understanding of the original circle loss. The original formula is shown in the red box in the figure above. It treats &amp;quot;similarity&amp;quot; as the optimization objective: for positive sample pairs formed by the same identity, similarity is denoted as $Z_i^+$ (hoping it to be as large as possible); for negative sample pairs formed by different identities, similarity is denoted as $Z_j^-$ (hoping it to be as small as possible). Therefore, its core is to simultaneously do two things: pull all $Z_i^+$ toward 1 (pull together same classes), while pulling all $Z_j^-$ toward 0 or even smaller (push apart different classes). It puts positive pairs and negative pairs into exponential functions separately for weighted summation: the exponential part of the positive pair term is roughly $-\gamma \alpha_i^+(Z_i^+ - \delta(+))$. When the similarity $Z_i^+$ of a certain positive pair is not large enough and is lower than the expected threshold $\delta(+)$, the bracket is negative, the exponential term becomes larger, contributing more to the loss, thus backpropagation will strongly push this positive pair to become more similar. Conversely, if $Z_i^+$ is already very large, exceeding $\delta(+)$, the contribution of this term becomes smaller, indicating that &amp;quot;easy positive samples&amp;quot; will not be over-optimized. The exponential part of the negative pair term is $\gamma \alpha_j^-(Z_j^- - \delta(-))$. When the similarity $Z_j^-$ of a certain negative pair is high, exceeding the threshold $\delta(-)$, this term will rapidly increase, and the loss will pay more attention to these &amp;quot;hard negative samples,&amp;quot; thereby pushing their similarity down. If the negative pair is already very small (already separated), its contribution will be automatically suppressed. Here, $\delta(+)=1-m$ and $\delta(-)=m$ are controlled by margin $m$, which is equivalent to setting a &amp;quot;passing line&amp;quot; for positive and negative pairs respectively, requiring positive pair similarity to exceed $1-m$ as much as possible, and negative pair similarity to be below $m$ as much as possible. $\gamma$ is the scale coefficient, used to control the &amp;quot;strength/steepness&amp;quot; of optimization. The most critical adaptive weights are $\alpha_i^+=[1+m-Z_i^+]&lt;em&gt;+$ and $\alpha_j^-=[Z_j^-+m]&lt;/em&gt;+$: they will make positive pairs with unsatisfactory similarity ($Z_i^+$ small) and negative pairs most easily confused ($Z_j^-$ large) obtain larger weights, thereby naturally shifting the training focus to &amp;quot;hard sample pairs.&amp;quot; This is why circle loss can simultaneously achieve &amp;quot;pulling together same classes, pushing apart different classes&amp;quot; and be more stable.&lt;/p&gt;
&lt;p&gt;Based on this, the paper further extends the &amp;quot;pairwise similarity optimization&amp;quot; idea of circle loss to the three-modality scenario and designs corresponding solutions for the two core contradictions of sketch Re-ID. First is inter-modal alignment $L_{CM}$: it no longer only constructs positive-negative pairs within the same modality, but directly calculates similarity between different modalities to construct positive-negative pairs, that is, changing $Z_i^+$ and $Z_j^-$ to cross-modal cosine similarities $Z_i^{uv+}(f^u,f^v)$ and $Z_j^{uv-}(f^u,f^v)$ ($u\neq v,;u,v\in{S,P,A}$), and applying circle loss constraints to the three groups of modality pairs $AS$, $AP$, $PS$ and then summing them. The intuitive purpose of doing this is: sketch, photo, and auxiliary of the same identity should be close to each other in the feature space, while different identities should be separated. Moreover, because auxiliary is in the &amp;quot;sketch-like&amp;quot; intermediate state, it simultaneously participates in the alignment of $AS$ and $AP$, playing a bridging role during optimization, indirectly reducing the alignment difficulty of $PS$. The specific formula is shown in the green box in the figure above.&lt;/p&gt;
&lt;p&gt;However, if only $L_{CM}$ is used, the model often focuses its optimization on &amp;quot;cross-modal differences,&amp;quot; resulting in the distribution of the same identity within each modality still possibly being very scattered (for example, photos of the same person from different cameras are still far apart, or sketches by different artists still have large style differences), thereby learning a suboptimal latent space. Therefore, the paper adds intra-modal alignment $L_{IM}$: it still uses the form of circle loss, but constructs positive-negative pairs by constraining within the same modality. To create more powerful supervision with few samples, it pairs features before interaction $f^u$ with features after interaction $\hat f^u$ within the same modality to construct positive sample pairs (the same identity should be consistent), while selecting the most easily confused different-identity pairs as negative sample pairs. In this way, $L_{CM}$ is responsible for pulling different modalities into the same metric space to complete inter-modal alignment, while $L_{IM}$ is responsible for compressing intra-class dispersion and increasing inter-class gaps within each modality. The two work together to simultaneously alleviate both &amp;quot;cross-modal gap&amp;quot; and &amp;quot;large intra-modal differences.&amp;quot; The specific formula is shown in the blue box in the figure above.&lt;/p&gt;
&lt;p&gt;In the experimental details, the parameter settings are $m_{cm}=0.25$, $m_{im}=0.5$, $\gamma=64$.&lt;/p&gt;
&lt;p&gt;The total loss function is the sum of the above four loss functions, namely $L=L_{ID}+L_{CM}+L_{IM}+λL_{SR}$, where $λ = 0.6$.&lt;/p&gt;
&lt;h2&gt;3. Experiments&lt;/h2&gt;
&lt;h3&gt;Dataset Configuration&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019321060_42b8aa22-7bf6-404e-9665-46e9c970f16e.png&quot; alt=&quot;42b8aa22-7bf6-404e-9665-46e9c970f16e.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown in the figure above, the paper uses the above 5 datasets.&lt;/p&gt;
&lt;h3&gt;Ablation Experiments&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019325296_f9.png&quot; alt=&quot;f9.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Table 1 above shows ablation experiments conducted on the PKU-Sketch and ShoeV2 datasets, where B represents Baseline. The paper explains that the Baseline here is &amp;quot;ResNet-50 trained with only identity loss.&amp;quot; Aux. indicates whether the auxiliary modality is introduced. $L_{Cir}$ represents the original circle loss, followed by the loss functions and modules designed by the paper. It can be seen that when using the complete model and loss functions proposed by the paper, all indicators are highest.&lt;/p&gt;
&lt;p&gt;Table 2 above is to verify the impact of whether the DAG module participates in joint training updates on the entire model. $G_f$ represents fixed parameters, and $G_j$ represents parameters participating in training optimization, which refers to the convolutional kernel parameters. $L_{SR}$ refers to the style refinement loss. It can be seen that when using the $L_{SR}$ loss function to optimize the parameters of the DAG module during training, the model achieves the best performance with the highest indicators.&lt;/p&gt;
&lt;h3&gt;Comparison Experiments&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019337408_f10.png&quot; alt=&quot;f10.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tables 3-5 above compare with current SoTA models on different datasets. It can be seen that regardless of which dataset, the model proposed in this paper achieves the highest indicators.&lt;/p&gt;
&lt;h3&gt;Visualization Display&lt;/h3&gt;
&lt;p&gt;Figure 3 performs &amp;quot;attention visualization comparison.&amp;quot; The paper uses XGrad-CAM to draw attention heatmaps for two sketch queries from different viewpoints and their respective top-4 retrieved photo results, marking incorrect and correct retrievals with red and green boxes respectively. The conclusion is intuitive: the Baseline&amp;#39;s attention easily ignores truly relevant regions between sketches and photos, especially being interfered by some similar local features when scenes and poses change. DALNet, on the other hand, can simultaneously focus on more &amp;quot;cross-modally shared&amp;quot; human body cues (such as key facial regions, clothing textures, bags, badges, etc.), so there are more correct retrievals (green boxes).&lt;/p&gt;
&lt;p&gt;Figure 4 performs &amp;quot;visualization of feature distribution alignment process.&amp;quot; The paper randomly selects 10 identities from PKU-Sketch and uses t-SNE to draw the feature distributions of the three modalities at different training epochs (brightness distinguishes different identities). The phenomenon is: at epoch=0, the distributions of photo (orange) and sketch (gray) are very different; as training progresses, auxiliary (green) acts like a &amp;quot;bridge&amp;quot; connecting the two; by epoch=50, photo and sketch gradually converge, with tighter intra-class and wider inter-class separations. Finally, at epoch=100, auxiliary features converge to their respective identity centers, indicating that the model has learned stronger identity discriminability and aligned the three-modality distributions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1767019353003_f11.png&quot; alt=&quot;f11.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This paper addresses the problems of large cross-modal gaps and severe intra-modal variations in sketch-based person re-identification by proposing DALNet: first, DAG generates a &amp;quot;sketch-like&amp;quot; auxiliary modality from real photos as a bridge; then a three-stream shared backbone extracts features, and through MIA, fine-grained cross-modal interactive fusion is achieved under the guidance of the auxiliary modality. Finally, classification loss and improved circle loss (including both cross-modal and intra-modal) are jointly optimized to achieve simultaneous inter-modal and intra-modal alignment.&lt;/p&gt;
&lt;p&gt;The innovation lies in introducing trainable dynamic auxiliary modality generation and style refinement constraints to reduce modality differences, and designing the entire collaborative learning mechanism of &amp;quot;auxiliary-guided interactive attention and cross-modal and intra-modal circle loss,&amp;quot; making feature distribution alignment more stable and retrieval performance stronger.&lt;/p&gt;
</content:encoded><category>SBIR</category><category>Multimodal</category><author>Glader</author></item><item><title>Interpretation of &quot;Modalities collaboration and granularities interaction for fine-grained sketch-based image retrieval&quot;</title><link>https://blog.mygld.top/en/posts/sbir-01/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/sbir-01/</guid><description>Paper reading on modality collaboration and granularity interaction in fine-grained sketch-based image retrieval.</description><pubDate>Sat, 22 Nov 2025 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The paper is titled: Modalities collaboration and granularities interaction for fine-grained sketch-based image retrieval.&lt;/p&gt;
&lt;p&gt;This paper was published in Pattern Recognition 2026.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805776882_image-20251122170327558.png&quot; alt=&quot;image-20251122170327558.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;1. Motivation&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805846546_f1.png&quot; alt=&quot;f1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown in the figure above, the paper has two main motivations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;As shown in figure (a), existing methods only focus on single-modality feature extraction, ignoring the complementarity between sketches (structural outlines) and photos (texture and color). The characteristics of large cross-modal differences within the same instance and small intra-modal differences across different instances make direct alignment difficult. The complementary information between sketches and real images is not fully utilized.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As shown in figure (c), fixed-size patch partitioning produces boundary noise and fragments complete features. Meanwhile, the lack of cross-granularity feature interaction prevents the use of multi-scale contextual information to enhance discriminability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The entire paper aims to solve these two problems.&lt;/p&gt;
&lt;h2&gt;2. Method&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805843159_f2.png&quot; alt=&quot;f2.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The figure above shows the overall framework of the proposed method. The framework is abbreviated as MCGI and can be roughly divided into 3 major modules: CMFL, CMIC, and CGPI. The CGPI module is further divided into three sub-modules: MGPM, CGII, and MGPC.&lt;/p&gt;
&lt;h3&gt;CMFL (Cross-Modality Feature Learning)&lt;/h3&gt;
&lt;p&gt;The cross-modality feature learning module is not the paper&amp;#39;s innovation point; it can be considered a baseline module. This part uses a ViT-B/16 encoder. Sketches or images are divided into tokens, and after adding position encoding and [CLS] token, they are separately input into the L layers (12 layers) of the encoder. The outputs are $f_{cls}^{s,L}$ and $f_{cls}^{p,L}$ (the former represents sketch, the latter represents real image). These two [CLS] tokens are then fed into classifiers $W_s$ and $W_p$ respectively, and cross-entropy losses $L_{ce}^s$ and $L_{ce}^{p}$ are calculated for the classification results. At the same time, basic cross-modal alignment is performed. The paper uses triplet loss with mutual alignment, calculating triplet losses $L_{tri}^s$ and $L_{tri}^{p}$. The specific formulas are shown in the figure below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805848709_f3.png&quot; alt=&quot;f3.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;CMIC (Cross-Modality Information Compensation)&lt;/h3&gt;
&lt;p&gt;The cross-modality information compensation module is the first innovation point proposed in the paper to solve problem 1 in the motivation. The method involves exchanging tokens from the l-th layer output (the paper sets l = 11). The exchange method preserves the [CLS] token without exchanging it, only exchanging the N patch tokens. The token sets before exchange are denoted as $F^{s,l}$ and $F^{p,l}$ (the former for sketch, the latter for real image), and after exchange as $\bar{F}^{s,l}$ and $\bar{F}^{p,l}$. Then the exchanged token sets pass through the remaining $L - l$ layers to obtain outputs. The [CLS] tokens from both sides are extracted, denoted as $\bar{f}&lt;em&gt;{cls}^{s,L}$ and $\bar{f}&lt;/em&gt;{cls}^{p,L}$, and these two tokens are used to calculate cross-entropy losses $L_{ce}^{s,2}$ and $L_{ce}^{p,2}$ following the same approach as CMFL.&lt;/p&gt;
&lt;p&gt;However, there is an issue: while we know the pairing relationship between sketches and images during training, we don&amp;#39;t know the pairing relationship during inference. Therefore, the paper designs a relational knowledge distillation. The two [CLS] tokens output from the CMFL module: $f_{cls}^{s,L}$ and $f_{cls}^{p,L}$ are separately fed into two MLPs to simulate the output after token exchange, and loss functions $L_{rkd}^{s}$ and $L_{rkd}^p$ are designed for distillation. The specific formulas are as follows, where $\psi_D$ represents the distance function, $\mu$ represents the normalization factor for distance, and $l_{\delta}$ represents the Huber loss function.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805849352_f4.png&quot; alt=&quot;f4.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;CGPI (Cross-Granularity Prototype Interaction)&lt;/h2&gt;
&lt;p&gt;The cross-granularity prototype interaction module is the second innovation point of the paper, designed to solve problem 2 in the motivation. This module is further divided into the following three sub-modules.&lt;/p&gt;
&lt;h4&gt;MGPM (Multi-Granularity Prototype Learning Module)&lt;/h4&gt;
&lt;p&gt;The multi-granularity prototype learning module has two abbreviations in the paper: MGPM and MGPL. The paper&amp;#39;s framework diagram uses MGPM, while the text description uses MGPL. For consistency and ease of explanation, I will use MGPM here.&lt;/p&gt;
&lt;p&gt;This module takes the patch token output (excluding [CLS] token) from layer L - 1. The outputs for sketch and real image are $[f_1^{s,L-1},f_2^{s,L-1},...,f_n^{s,L-1}]$ and $[f_1^{p,L-1},f_2^{p,L-1},...,f_n^{p,L-1}]$ respectively. These are then evenly divided into $M$ groups (the paper sets $M = 7$), with each group containing $\frac{n}{M}$ tokens. Then, these groups are progressively aggregated to form feature sequences of different granularities: $[f_1^{s,L-1},f_{1:2}^{s,L-1},...,f_{1:M}^{s,L-1}]$ and $[f_1^{p,L-1},f_{1:2}^{p,L-1},...,f_{1:M}^{p,L-1}]$, where $1 : i$ represents the concatenation of tokens from the first $i$ groups.&lt;/p&gt;
&lt;p&gt;To enhance the discriminability of these multi-granularity features, the paper introduces learnable multi-granularity shared prototypes $P = [P_1, P_2, ..., P_M]$, concatenating each granularity&amp;#39;s prototype with corresponding local tokens: $Z_s = [C(P_1, f_1^{s,L-1}), C(P_2, f_{1:2}^{s,L-1}), ..., C(P_M, f_{1:M}^{s,L-1})]$ and $Z_p = [C(P_1, f_1^{p,L-1}), C(P_2, f_{1:2}^{p,L-1}), ..., C(P_M, f_{1:M}^{p,L-1})]$, where $C(·)$ represents the concatenation operation. These prototypes function similarly to [CLS] tokens, with each prototype $P_i$ responsible for aggregating the global feature representation of the i-th granularity.&lt;/p&gt;
&lt;p&gt;Then $Z_s$ and $Z_p$ are separately fed into the L-th layer Transformer Encoder to obtain $F^s$ and $F^p$. The detailed flow diagram and formulas are shown below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805848921_f5.png&quot; alt=&quot;f5.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;CGII (Cross-granularity Information Interaction)&lt;/h4&gt;
&lt;p&gt;The cross-granularity information interaction module continues to process the output from the previous module. This module introduces two external memory units $M_k$ and $M_v$ as Key and Value, using multi-granularity prototype features $F_s$ and $F_p$ as Query, and calculates cross-granularity attention weights to capture contextual associations between different granularities. Through multi-head attention mechanism, information from different granularities is aggregated, outputting enhanced multi-granularity features $\tilde{F}_s = [\tilde{P}_1^s, \tilde{P}_2^s, ..., \tilde{P}_M^s]$ and $\tilde{F}_p = [\tilde{P}_1^p, \tilde{P}_2^p, ..., \tilde{P}&lt;em&gt;M^p]$. Finally, these are concatenated with the global [CLS] token to obtain complete enhanced feature sets $f_s^{en} = [f&lt;/em&gt;{cls}^{s,L}, \tilde{P}_1^s, \tilde{P}_2^s, ..., \tilde{P}&lt;em&gt;M^s]$ and $f_p^{en} = [f&lt;/em&gt;{cls}^{p,L}, \tilde{P}_1^p, \tilde{P}_2^p, ..., \tilde{P}_M^p]$. The specific process is shown below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805863744_f6.png&quot; alt=&quot;f6.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;MGPC (Multi-Granularity Prototype-aware Contrastive Loss)&lt;/h4&gt;
&lt;p&gt;The multi-granularity prototype-aware contrastive loss module uses the outputs $f_{en}^s$ and $f_{en}^p$ from the previous module to calculate contrastive loss $L_{mgpc}$ for further alignment of cross-modal multi-granularity feature representations. This loss increases the similarity of positive sample pairs (matched sketch-photo pairs) in the multi-granularity feature space while decreasing the similarity of negative sample pairs, promoting feature alignment between sketches and photos at the multi-granularity level. The specific process and formulas are as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805866459_f7.png&quot; alt=&quot;f7.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The total loss is the weighted sum of all the above losses, where $\lambda_1 = 0.6$ and $\lambda_2 = 1.0$. The optimization of these two parameters will be explored in later experiments.&lt;/p&gt;
&lt;h2&gt;3. Experiments&lt;/h2&gt;
&lt;h3&gt;Dataset Settings&lt;/h3&gt;
&lt;p&gt;The experimental setup uses four datasets:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805970138_image-20251122170645389.png&quot; alt=&quot;image-20251122170645389.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;These are the chair dataset, shoe dataset, clothing dataset, and pedestrian dataset.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805863128_f8.png&quot; alt=&quot;f8.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Comparison with State-of-the-Art Methods&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805867741_f9.png&quot; alt=&quot;f9.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The three tables above show comparisons with current state-of-the-art methods on different datasets. The paper&amp;#39;s model demonstrates overall better performance, especially on the chair and shoe datasets, showing improvements across all metrics.&lt;/p&gt;
&lt;h3&gt;Ablation Studies&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805864365_f10.png&quot; alt=&quot;f10.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown above, the first table presents ablation study analysis on the shoe dataset, exploring the role of the four innovative modules proposed in the paper. The results show that metrics are highest only when all modules are used, indicating that each module is indispensable.&lt;/p&gt;
&lt;p&gt;The second table provides ablation analysis for each loss function, also conducted on the shoe dataset. Removing any loss function leads to varying degrees of performance degradation. These results validate the effective contribution of each proposed loss function to the overall model performance.&lt;/p&gt;
&lt;h3&gt;Hyperparameter Analysis&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805869685_f11.png&quot; alt=&quot;f11.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown above, figure (a) demonstrates the impact of token exchange at different layers on model performance. Experimental results show that as layer $l$ increases, accuracy and mAP gradually rise, reaching their peak at $l = 11$, which is why the paper sets $l = 11$.&lt;/p&gt;
&lt;p&gt;Figure (b) shows the impact of multi-granularity number $M$ (i.e., the number of groups mentioned earlier) on model performance. Experimental results indicate that when $M = 7$, overall metrics are best with the highest mAP.&lt;/p&gt;
&lt;p&gt;Figure (c) compares the performance difference between the proposed MGPM module and the traditional fixed patch partitioning strategy (PCB). Experimental results show that MGPL significantly outperforms PCB across all evaluation metrics, validating the effectiveness of the hierarchical granularity partitioning strategy. Therefore, the paper adopts MGPM as the multi-granularity feature learning method.&lt;/p&gt;
&lt;p&gt;The last two line graphs experiment with the impact of $\lambda_1$ and $\lambda_2$ values on the experiments, showing that the best results are achieved when $\lambda_1=0.6$ and $\lambda_2=1.0$.&lt;/p&gt;
&lt;h3&gt;Experimental Visualization&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763805843210_f12.png&quot; alt=&quot;f12.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The figure above shows visualization of retrieval results using the proposed method on four datasets, where green boxes indicate correctly retrieved images and red boxes indicate incorrectly retrieved images. The method successfully ranks correct target images in higher positions. This improvement is evident across different types of datasets including chairs, shoes, clothing, and pedestrians, demonstrating the good generalization capability of the framework.&lt;/p&gt;
&lt;h2&gt;4. Conclusion&lt;/h2&gt;
&lt;p&gt;The MCGI framework addresses fine-grained sketch-based image retrieval by leveraging cross-modal complementary information and cross-granularity contextual associations, thereby bridging modality gaps and enhancing feature discriminability.&lt;/p&gt;
&lt;p&gt;The Cross-Modality Information Compensation (CMIC) module integrates complementary information from sketches and photos through token exchange and knowledge distillation to learn modality-robust feature representations.&lt;/p&gt;
&lt;p&gt;The Cross-Granularity Prototype Interaction (CGPI) module hierarchically extracts multi-granularity features and models their contextual interactions to capture discriminative fine-grained information.&lt;/p&gt;
&lt;p&gt;Extensive experiments on four benchmark datasets demonstrate state-of-the-art performance, achieving 78.6% Rank-1 accuracy on QMUL-Chair-V2, 44.5% on QMUL-Shoe-V2, 96.0% on Clothes-V1, and 91.5% on Sketch Re-ID.&lt;/p&gt;
&lt;p&gt;Ablation studies validate the effectiveness of individual components, where CMIC improves modality robustness and CGPI enhances fine-grained discriminability through hierarchical feature learning and cross-granularity interaction.&lt;/p&gt;
</content:encoded><category>SBIR</category><category>Multimodal</category><author>Glader</author></item><item><title>A Brief Exploration of Spring Transactions</title><link>https://blog.mygld.top/en/posts/transaction/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/transaction/</guid><description>Related knowledge points about transactions in Spring.</description><pubDate>Wed, 19 Nov 2025 16:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. What is a Transaction&lt;/h2&gt;
&lt;h3&gt;Definition of Transaction&lt;/h3&gt;
&lt;p&gt;A transaction typically refers to a group of activities or events that are treated as a &lt;strong&gt;single unit of execution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, in a bank transfer, when Account A transfers 100 yuan to Account B, the following two operations are required:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Account A&amp;#39;s balance decreases by 100 yuan&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Account B&amp;#39;s balance increases by 100 yuan&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two operations must either succeed together or fail together. If Account A&amp;#39;s balance decreases but Account B&amp;#39;s balance doesn&amp;#39;t increase, or if Account A&amp;#39;s balance remains unchanged while Account B&amp;#39;s balance increases, this would result in &lt;strong&gt;data inconsistency issues&lt;/strong&gt;, meaning the banking system has encountered an error and the accounting records are no longer reliable.&lt;/p&gt;
&lt;p&gt;Transactions exist to solve problems like the one described above. This is &lt;strong&gt;the significance of transactions&lt;/strong&gt;: to ensure operations either complete entirely or fail entirely, avoiding data errors caused by partial completion, thereby maintaining data consistency and integrity.&lt;/p&gt;
&lt;h3&gt;Properties of Transactions&lt;/h3&gt;
&lt;p&gt;Transactions have four ACID properties: Atomicity, Consistency, Isolation, and Durability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atomicity&lt;/strong&gt;: Transactions are indivisible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: Data maintains a valid state before and after the transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Isolation&lt;/strong&gt;: Multiple transactions do not interfere with each other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Durability&lt;/strong&gt;: Data is permanently saved after commit.&lt;/p&gt;
&lt;h2&gt;2. Transactions in MySQL&lt;/h2&gt;
&lt;h3&gt;Important Notes&lt;/h3&gt;
&lt;p&gt;Not all storage engines in MySQL support transactions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Supports transactions&lt;/strong&gt;: InnoDB (default)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does not support transactions&lt;/strong&gt;: MyISAM (does not support transactions, data operations cannot be rolled back)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before using transactions, please ensure the table uses the &lt;strong&gt;InnoDB engine&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Basic Transaction Operations&lt;/h3&gt;
&lt;p&gt;In MySQL, we can use &lt;code&gt;START TRANSACTION&lt;/code&gt; or &lt;code&gt;BEGIN&lt;/code&gt; to start a transaction, then execute SQL operations. Then use &lt;code&gt;COMMIT&lt;/code&gt; to commit the transaction or use &lt;code&gt;ROLLBACK&lt;/code&gt; to roll back the transaction.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s use the bank transfer example above. First, create a simplified user balance table and initialize some data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE accounts (
    id CHAR(1) PRIMARY KEY,
    balance DECIMAL(10,2) NOT NULL
) ENGINE=InnoDB;

INSERT INTO accounts (id, balance) VALUES
(&amp;#39;A&amp;#39;, 1000.00),
(&amp;#39;B&amp;#39;, 500.00);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note: In normal projects, primary keys are usually integer types. Here, for the sake of the example above and ease of explanation, it&amp;#39;s set to char type.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763568602728_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Next, we enter &lt;code&gt;START TRANSACTION&lt;/code&gt; in the console to start the transaction.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763568677995_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now we create a transfer operation and execute it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Deduct from Account A&amp;#39;s balance
UPDATE accounts
SET balance = balance - 100
WHERE id = &amp;#39;A&amp;#39;;

-- Add to Account B&amp;#39;s balance
UPDATE accounts
SET balance = balance + 100
WHERE id = &amp;#39;B&amp;#39;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763568758800_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;At this point, we use &lt;code&gt;SELECT * FROM accounts&lt;/code&gt; to view the current table state:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763568836709_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;We can see the operation was successful - the balances of A and B have changed. However, when we now return to IDEA&amp;#39;s database tool panel to view the table data, we find the balances haven&amp;#39;t changed:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763568884300_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;This demonstrates &lt;strong&gt;transaction isolation&lt;/strong&gt;. These modifications are only visible &lt;strong&gt;within the transaction&lt;/strong&gt;. In the &lt;strong&gt;same session&lt;/strong&gt;, modifications to data before the transaction is committed are visible, but for other sessions, these modifications remain invisible until the transaction is committed and take effect externally. When we open the database tool panel in IDEA to view table data, it&amp;#39;s equivalent to opening a new session, which is independent from the previous transaction, so the data hasn&amp;#39;t changed.&lt;/p&gt;
&lt;p&gt;If we now use &lt;code&gt;COMMIT&lt;/code&gt; to commit the transaction, the data is officially written to the database table for persistence - this is the &lt;strong&gt;durability&lt;/strong&gt; of transactions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763569321757_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;At this point, we&amp;#39;ll find that the table data in IDEA&amp;#39;s database tool has been modified:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763569293218_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Similarly, using &lt;code&gt;ROLLBACK&lt;/code&gt; can &lt;strong&gt;undo all uncommitted operations in the transaction&lt;/strong&gt;, restoring the data to the state before the transaction began. This way, even if multiple modifications are made within the transaction, they won&amp;#39;t affect the actual database data, implementing &lt;strong&gt;atomicity&lt;/strong&gt; and &lt;strong&gt;consistency&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763569536600_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763569568256_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;3. Transactions in Spring&lt;/h2&gt;
&lt;h3&gt;Demo Example&lt;/h3&gt;
&lt;p&gt;We write the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Mapper
public interface AccountsMapper {
    @Update(&amp;quot;UPDATE accounts &amp;quot; +
            &amp;quot;SET balance = balance + #{delta} &amp;quot; +
            &amp;quot;WHERE id = #{id}&amp;quot;)
    void updateByDelta(@Param(&amp;quot;id&amp;quot;) Character id, @Param(&amp;quot;delta&amp;quot;) BigDecimal delta);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class AccountsService {
    @Autowired
    private AccountsMapper accountsMapper;

    public void transfer(Character id1, Character id2, BigDecimal delta) {
        accountsMapper.updateByDelta(id1,BigDecimal.ZERO.subtract(delta));
        System.out.println(1 / 0);
        accountsMapper.updateByDelta(id2,delta);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;AccountsService&lt;/code&gt;, we define a simple &lt;code&gt;transfer&lt;/code&gt; method. In this method, we deliberately write &lt;code&gt;1/0&lt;/code&gt; to throw a runtime exception. After running it, we&amp;#39;ll check whether the data in the database has changed after the exception is thrown.&lt;/p&gt;
&lt;p&gt;We create a test unit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@SpringBootTest
class DemoApplicationTests {
    @Autowired
    private AccountsService accountsService;
    @Test
    void  testTransaction(){
        accountsService.transfer(&amp;#39;A&amp;#39;,&amp;#39;B&amp;#39;,new BigDecimal(&amp;quot;100&amp;quot;));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running, we find an exception is thrown:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763605878341_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;When we check the database table, we find there&amp;#39;s a problem:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763606126461_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Account A&amp;#39;s balance decreased by 100 yuan, but Account B&amp;#39;s balance didn&amp;#39;t increase. The total balance of 1500 inexplicably decreased by 100 yuan, causing problems with data consistency and integrity.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;AccountsService&lt;/code&gt; code, &lt;code&gt;accountsMapper.updateByDelta(id1,BigDecimal.ZERO.subtract(delta));&lt;/code&gt; ran successfully first, but after the exception was thrown, the following &lt;code&gt;accountsMapper.updateByDelta(id2,delta);&lt;/code&gt; didn&amp;#39;t execute, causing the above problem. What we want is for both &lt;code&gt;update&lt;/code&gt; operations to either succeed together or fail together. Therefore, we must encapsulate these two &lt;code&gt;update&lt;/code&gt; operations into a single transaction. If one fails, everything should &lt;code&gt;ROLLBACK&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Basic Usage of &lt;code&gt;@Transactional&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In the Spring framework, transaction management is implemented through &lt;strong&gt;Spring&amp;#39;s transaction abstraction&lt;/strong&gt;, which allows us to conveniently manage transactions in business code without directly operating the database&amp;#39;s &lt;code&gt;START TRANSACTION&lt;/code&gt;, &lt;code&gt;COMMIT&lt;/code&gt;, or &lt;code&gt;ROLLBACK&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the Spring framework, we often use the &lt;code&gt;@Transactional&lt;/code&gt; annotation to declare that a method or class needs to execute within a transaction. Spring automatically starts a transaction before method execution, commits the transaction after successful execution, and if a &lt;strong&gt;RuntimeException&lt;/strong&gt; or &lt;strong&gt;Error&lt;/strong&gt; occurs during execution, the transaction automatically rolls back, ensuring the &lt;strong&gt;atomicity&lt;/strong&gt; and &lt;strong&gt;consistency&lt;/strong&gt; of operations.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Transactional&lt;/code&gt; can be added to methods or classes. If added to a method, it only affects that method - the transaction is only opened when executing that method. If added to a class, it applies to all public methods in the class, equivalent to adding transactions to each public method. However, in actual development, it&amp;#39;s usually only added to methods that need it, rather than directly on the class.&lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s add the &lt;code&gt;@Transactional&lt;/code&gt; annotation to the &lt;code&gt;transfer&lt;/code&gt; method in &lt;code&gt;AccountsService&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class AccountsService {
    @Autowired
    private AccountsMapper accountsMapper;

    @Transactional
    public void transfer(Character id1, Character id2, BigDecimal delta) {
        accountsMapper.updateByDelta(id1,BigDecimal.ZERO.subtract(delta));
        System.out.println(1 / 0);
        accountsMapper.updateByDelta(id2,delta);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For comparison, I&amp;#39;ll first restore the database data to 900 and 600.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763569293218_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Run the unit test again, and after the exception is thrown, check the database result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763606647857_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;After refreshing, we can see the data hasn&amp;#39;t changed at all, indicating the transaction successfully rolled back. When the &lt;code&gt;transfer&lt;/code&gt; method executes to &lt;code&gt;1/0&lt;/code&gt; and throws an &lt;code&gt;ArithmeticException&lt;/code&gt;, Spring detects the runtime exception and automatically triggers a transaction rollback, undoing the previous deduction from Account A, ensuring data consistency.&lt;/p&gt;
&lt;p&gt;If we now delete the &lt;code&gt;1/0&lt;/code&gt; code and run again, there are no exceptions, so it will &lt;code&gt;COMMIT&lt;/code&gt; successfully. Refresh the database table as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763606959856_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Advanced Usage of &lt;code&gt;@Transactional&lt;/code&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;code&gt;rollBackFor&lt;/code&gt; Property&lt;/h4&gt;
&lt;p&gt;OK, we just mentioned that it automatically rolls back when encountering runtime exceptions. But what if the exception is not a runtime exception? Will it still automatically roll back? Let&amp;#39;s test it. We modify the &lt;code&gt;transfer&lt;/code&gt; code as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class AccountsService {
    @Autowired
    private AccountsMapper accountsMapper;
    @Transactional
    public void transfer(Character id1, Character id2, BigDecimal delta) throws Exception {
        accountsMapper.updateByDelta(id1,BigDecimal.ZERO.subtract(delta));
        if(true){
            throw new Exception(&amp;quot;Manually thrown exception~&amp;quot;);
        }
        accountsMapper.updateByDelta(id2,delta);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;if(true)&lt;/code&gt; makes the compiler think the following statement might be executed, so it won&amp;#39;t report a compilation error - essentially &amp;quot;tricking&amp;quot; the compiler.&lt;/p&gt;
&lt;p&gt;In the unit test, we catch the exception:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@SpringBootTest
class DemoApplicationTests {
    @Autowired
    private AccountsService accountsService;
    @Test
    void  testTransaction(){
        try {
            accountsService.transfer(&amp;#39;A&amp;#39;,&amp;#39;B&amp;#39;,new BigDecimal(&amp;quot;100&amp;quot;));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running the unit test, refresh and check the database table - we find there&amp;#39;s a problem again:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763607383019_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;It&amp;#39;s the same as the initial situation. Even with &lt;code&gt;@Transactional&lt;/code&gt;, the same problem occurs - A&amp;#39;s balance decreased, B&amp;#39;s balance didn&amp;#39;t increase. Why is this?&lt;/p&gt;
&lt;p&gt;As we just mentioned, &lt;code&gt;@Transactional&lt;/code&gt; by default only automatically rolls back for runtime exceptions. Since &lt;code&gt;ArithmeticException&lt;/code&gt; is a subclass of &lt;code&gt;RuntimeException&lt;/code&gt;, it can automatically roll back, but &lt;code&gt;Exception&lt;/code&gt; is not a runtime exception, so it cannot automatically roll back.&lt;/p&gt;
&lt;p&gt;What can we do? Do we have no way to solve this problem? Let&amp;#39;s immediately look at the source code of &lt;code&gt;@Transactional&lt;/code&gt; to see if there are any properties we can set in this annotation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor(&amp;quot;transactionManager&amp;quot;)
    String value() default &amp;quot;&amp;quot;;

    @AliasFor(&amp;quot;value&amp;quot;)
    String transactionManager() default &amp;quot;&amp;quot;;

    String[] label() default {};

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    String timeoutString() default &amp;quot;&amp;quot;;

    boolean readOnly() default false;

    Class&amp;lt;? extends Throwable&amp;gt;[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class&amp;lt;? extends Throwable&amp;gt;[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We discovered &lt;code&gt;Class&amp;lt;? extends Throwable&amp;gt;[] rollbackFor() default {};&lt;/code&gt;. Looking at the name &lt;code&gt;rollbackFor&lt;/code&gt; - rollback for... - isn&amp;#39;t this exactly what we want? It takes a Class array as input. Let&amp;#39;s immediately return to our &lt;code&gt;transfer&lt;/code&gt; method and add this property:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class AccountsService {
    @Autowired
    private AccountsMapper accountsMapper;

    @Transactional(rollbackFor = {Exception.class})
    public void transfer(Character id1, Character id2, BigDecimal delta) throws Exception {
        accountsMapper.updateByDelta(id1,BigDecimal.ZERO.subtract(delta));
        if(true){
            throw new Exception(&amp;quot;Manually thrown exception~&amp;quot;);
        }
        accountsMapper.updateByDelta(id2,delta);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we restore the database data to 800 and 700, and run the unit test again with the following result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763607945642_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now it&amp;#39;s perfect. The exception was thrown and the data wasn&amp;#39;t modified, indicating a &lt;code&gt;ROLLBACK&lt;/code&gt; occurred.&lt;/p&gt;
&lt;h4&gt;Transaction Propagation Behavior&lt;/h4&gt;
&lt;p&gt;Good, but we have another question. In our projects, we often have business logic for storing operation logs. Let&amp;#39;s create an &lt;code&gt;accounts_log&lt;/code&gt; table:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE accounts_log (
    id BigInt PRIMARY KEY AUTO_INCREMENT,
    message TEXT NOT NULL
) ENGINE=InnoDB;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Please note, this is a simplified log table. In normal project development, it&amp;#39;s not this simple - there are many other fields, such as log type, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then create an entity class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Data
@AllArgsConstructor
@NoArgsConstructor
public class AccountsLog {
    private Long id;
    private String message;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a Mapper:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Mapper
public interface AccountsLogMapper {
    @Insert(&amp;quot;INSERT INTO accounts_log VALUES(#{id},#{message})&amp;quot;)
    void insert(AccountsLog accountsLog);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a Service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class AccountsLogService {
    @Autowired
    private AccountsLogMapper accountsLogMapper;
    
    public void insert(AccountsLog accountsLog) {
        accountsLogMapper.insert(accountsLog);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Refactor the transfer code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class AccountsService {
    @Autowired
    private AccountsMapper accountsMapper;

    @Autowired
    private AccountsLogService accountsLogService;

    @Transactional(rollbackFor = {Exception.class})
    public void transfer(Character id1, Character id2, BigDecimal delta) throws Exception {
        String message = id1 + &amp;quot; initiated transfer to &amp;quot;
                + id2 + &amp;quot; of &amp;quot; + delta + &amp;quot; yuan, &amp;quot;;
        try {
            accountsMapper.updateByDelta(id1,BigDecimal.ZERO.subtract(delta));
            if(true){
                throw new Exception(&amp;quot;Manually thrown exception~&amp;quot;);
            }
            accountsMapper.updateByDelta(id2,delta);
            message = &amp;quot;Succeeded!&amp;quot;;
        } catch (Exception e){
            message += &amp;quot;but failed!&amp;quot;;
            throw e;//continue throwing exception, otherwise @Transactional won&amp;#39;t detect it
        }finally {
            accountsLogService.insert(new AccountsLog(null,message));
        }
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We require that logs be recorded whether the transfer succeeds or fails. If we only write it this way, after running the unit test we find:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763609907018_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The exception was thrown, but there&amp;#39;s no data in the &lt;code&gt;accounts_log&lt;/code&gt; table. This is because although &lt;code&gt;accountsLogService.insert&lt;/code&gt; is called in &lt;code&gt;finally&lt;/code&gt;, the operation is still constrained by the same transaction method. As long as there&amp;#39;s an exception, all modifications will be rolled back, including operations in &lt;code&gt;finally&lt;/code&gt;. So how do we solve this problem?&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;Transactional&lt;/code&gt; source code, we see a property &lt;code&gt;Propagation propagation() default Propagation.REQUIRED;&lt;/code&gt;. This property is used to control &lt;strong&gt;transaction propagation behavior&lt;/strong&gt;, i.e., when a transaction method is called by another transaction method, how to handle the transaction issue.&lt;/p&gt;
&lt;p&gt;Spring defines 7 transaction propagation behaviors:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Propagation Behavior&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REQUIRED&lt;/strong&gt; (default)&lt;/td&gt;
&lt;td&gt;If a transaction exists, join it; if no transaction exists, create a new one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REQUIRES_NEW&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Always create a new transaction; if a transaction exists, suspend the current transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SUPPORTS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If a transaction exists, join it; if no transaction exists, execute non-transactionally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NOT_SUPPORTED&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Execute non-transactionally; if a transaction exists, suspend the current transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MANDATORY&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If a transaction exists, join it; if no transaction exists, throw an exception&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NEVER&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Execute non-transactionally; if a transaction exists, throw an exception&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NESTED&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If a transaction exists, execute within a nested transaction; if no transaction exists, create a new one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;For our logging requirement, we should use &lt;strong&gt;&lt;code&gt;Propagation.REQUIRES_NEW&lt;/code&gt;&lt;/strong&gt;, which creates a &lt;strong&gt;completely independent new transaction&lt;/strong&gt; that is not affected by the outer transaction&amp;#39;s rollback.&lt;/p&gt;
&lt;p&gt;We modify &lt;code&gt;AccountsLogService&lt;/code&gt; as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class AccountsLogService {
    @Autowired
    private AccountsLogMapper accountsLogMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insert(AccountsLog accountsLog) {
        accountsLogMapper.insert(accountsLog);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&amp;#39;s run the unit test again and check the database table result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1763610392093_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Perfect! Our log was added successfully. For this current example, when executing the inner transaction, the outer transaction is suspended and waits for the inner transaction&amp;#39;s lifecycle to end before resuming the outer transaction.&lt;/p&gt;
&lt;p&gt;However, in actual project development, we mostly use the default propagation method (about 85% ~ 90% of scenarios), followed by &lt;code&gt;REQUIRES_NEW&lt;/code&gt; (about 5% ~ 10%). Other propagation methods are used less frequently. Interested readers can explore them on their own.&lt;/p&gt;
</content:encoded><category>Java</category><category>Transaction</category><category>MySQL</category><category>Spring</category><author>Glader</author></item><item><title>Spring Bean Scope and Lifecycle</title><link>https://blog.mygld.top/en/posts/beanpro/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/beanpro/</guid><description>Knowledge points related to Spring Bean scope and lifecycle.</description><pubDate>Mon, 20 Oct 2025 15:31:53 GMT</pubDate><content:encoded>&lt;p&gt;The default scope of Spring Beans is Singleton, but it also supports modification to Prototype. So how do we implement this specifically?&lt;/p&gt;
&lt;h2&gt;Singleton&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s first understand what singleton means.&lt;/p&gt;
&lt;p&gt;Let me give a somewhat inappropriate example. We&amp;#39;ll write a configuration class &lt;code&gt;ArrayListConfig&lt;/code&gt; to put &lt;code&gt;ArrayList&lt;/code&gt; under &lt;code&gt;IOC&lt;/code&gt; container management, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
public class ArrayListConfig {

    @Bean
    public ArrayList&amp;lt;String&amp;gt; arrayList() {
        return new ArrayList&amp;lt;String&amp;gt;();
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note that in practice, when developing projects, we wouldn&amp;#39;t normally have &lt;code&gt;IOC&lt;/code&gt; manage the lifecycle of &lt;code&gt;ArrayList&lt;/code&gt;. This is just for demonstration and learning purposes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alright, now that we&amp;#39;ve created this configuration class, let&amp;#39;s write a unit test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
private ArrayList&amp;lt;String&amp;gt; arrayList1;
@Autowired
private ArrayList&amp;lt;String&amp;gt; arrayList2;
@Test
void test1(){
    arrayList1.add(&amp;quot;1&amp;quot;);
    arrayList2.add(&amp;quot;2&amp;quot;);
    System.out.println(arrayList1);
    System.out.println(arrayList2);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, what do you think the output will be?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1760974541072_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Both output &lt;code&gt;[1, 2]&lt;/code&gt;. This is &lt;code&gt;Singleton&lt;/code&gt;. When the &lt;code&gt;IOC&lt;/code&gt; container starts, it creates a &lt;code&gt;Bean&lt;/code&gt; instance, and after that, all the objects we get through autowiring point to the same &lt;code&gt;Bean&lt;/code&gt; instance, which is why they output the same content.&lt;/p&gt;
&lt;p&gt;So how can we make these two &lt;code&gt;ArrayLists&lt;/code&gt; point to different &lt;code&gt;Bean&lt;/code&gt; instances? This requires us to use &lt;code&gt;Prototype&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Prototype&lt;/h2&gt;
&lt;p&gt;Implementing prototype is simple. We just need to use the &lt;code&gt;@Scope&lt;/code&gt; annotation, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
public class ArrayListConfig {

    @Bean
    @Scope(&amp;quot;prototype&amp;quot;)
    public ArrayList&amp;lt;String&amp;gt; arrayList() {
        return new ArrayList&amp;lt;String&amp;gt;();
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1760975018552_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown above, we&amp;#39;ve now achieved the desired effect. This is prototype scope.&lt;/p&gt;
&lt;p&gt;In fact, singleton is &lt;code&gt;@Scope(&amp;quot;singleton&amp;quot;)&lt;/code&gt;, but we usually omit it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Scope&lt;/code&gt; can also be combined with &lt;code&gt;@Component&lt;/code&gt; and other annotations to directly implement singleton or prototype when defining a class. I won&amp;#39;t demonstrate this here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Note: &lt;strong&gt;Prototype Beans&lt;/strong&gt; are sometimes also called &lt;strong&gt;prototype Beans&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bean Lifecycle&lt;/h2&gt;
&lt;p&gt;I wonder if you&amp;#39;ve ever thought about the lifecycle of Beans.&lt;/p&gt;
&lt;p&gt;For example, when are the Beans managed by the &lt;code&gt;IOC&lt;/code&gt; container created? Like the &lt;code&gt;ArrayList&lt;/code&gt; above, is it created when we first use it, or is it created as soon as the container starts?&lt;/p&gt;
&lt;p&gt;To facilitate testing, let me manually create a class and have &lt;code&gt;IOC&lt;/code&gt; manage it, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
public class Kunkun {
    public Kunkun() {
        System.out.println(&amp;quot;Kunkun was born!&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then let&amp;#39;s write a unit test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test
void test2(){
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1760975922695_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown above, we find that even without using &lt;code&gt;Kunkun&lt;/code&gt; (we didn&amp;#39;t &lt;code&gt;@Autowired&lt;/code&gt; a Kunkun), the &lt;code&gt;Kunkun&lt;/code&gt; constructor was still called. This means that by default, the container initializes singleton Beans when it starts.&lt;/p&gt;
&lt;p&gt;Sometimes, creating all &lt;code&gt;Bean&lt;/code&gt; objects in advance can be a waste of resources. Can we do this: only create the &lt;code&gt;Bean&lt;/code&gt; when we first use it, and then just get it from the &lt;code&gt;IOC&lt;/code&gt; container for subsequent uses? Yes, we can. We just need to add the &lt;code&gt;@Lazy&lt;/code&gt; annotation, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
@Lazy
public class Kunkun {
    public Kunkun() {
        System.out.println(&amp;quot;Kunkun was born!&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we run the unit test &lt;code&gt;Test2&lt;/code&gt; again, there won&amp;#39;t be any output.&lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s modify the unit test as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
private Kunkun kunkun;

@Test
void test2(){
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we run it now, we find:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1760976350481_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;This shows that the &lt;code&gt;Kunkun&lt;/code&gt; &lt;code&gt;Bean&lt;/code&gt; object was created only when it was used.&lt;/p&gt;
&lt;p&gt;Please note that everything we&amp;#39;ve discussed above is for singleton &lt;code&gt;Beans&lt;/code&gt;. What about prototype &lt;code&gt;Beans&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s add &lt;code&gt;@Scope(&amp;quot;prototype&amp;quot;)&lt;/code&gt; to the class, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;

import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(&amp;quot;prototype&amp;quot;)
public class Kunkun {
    public Kunkun() {
        System.out.println(&amp;quot;Kunkun was born!&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&amp;#39;s not declare and create &lt;code&gt;Kunkun&lt;/code&gt;, and see if the constructor appears when starting.&lt;/p&gt;
&lt;p&gt;After testing, we find that there is indeed no output related to &lt;code&gt;Kunkun&lt;/code&gt;&amp;#39;s constructor. This means that for prototype &lt;code&gt;Beans&lt;/code&gt;, they are not initialized and created when the &lt;code&gt;IOC&lt;/code&gt; container starts.&lt;/p&gt;
&lt;p&gt;So let&amp;#39;s &lt;code&gt;@Autowired&lt;/code&gt; two &lt;code&gt;Kunkuns&lt;/code&gt;, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
private Kunkun kunkun1;
@Autowired
private Kunkun kunkun2;
@Test
void test3(){

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running, the output is:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1760977093828_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the constructor was called successfully. We can easily conclude that for prototype &lt;code&gt;Beans&lt;/code&gt;, lazy loading is the default. If you still want to add &lt;code&gt;@Lazy&lt;/code&gt;, it won&amp;#39;t cause an error, but adding it has no real effect.&lt;/p&gt;
&lt;p&gt;Now that we&amp;#39;ve discussed the creation of singleton and prototype &lt;code&gt;Beans&lt;/code&gt;, let&amp;#39;s talk about destruction.&lt;/p&gt;
&lt;p&gt;First, let&amp;#39;s look at singleton &lt;code&gt;Bean&lt;/code&gt; destruction:&lt;/p&gt;
&lt;p&gt;There&amp;#39;s an annotation &lt;code&gt;@PreDestroy&lt;/code&gt;. Methods annotated with it will be automatically executed when the &lt;code&gt;Bean&lt;/code&gt; is destroyed, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
public class Kunkun {
    public Kunkun() {
        System.out.println(&amp;quot;Kunkun was born!&amp;quot;);
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println(&amp;quot;Kunkun is being destroyed~&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;#39;s write a unit test and &lt;code&gt;@Autowired&lt;/code&gt; a &lt;code&gt;Kunkun&lt;/code&gt; to see the effect:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1760977566189_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As we can see, for singleton &lt;code&gt;Beans&lt;/code&gt;, when the container is destroyed, the &lt;code&gt;Bean&lt;/code&gt; instance is also destroyed.&lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s turn it into a prototype &lt;code&gt;Bean&lt;/code&gt; and experiment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;

import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;

@Component
@Scope(&amp;quot;prototype&amp;quot;)
public class Kunkun {
    public Kunkun() {
        System.out.println(&amp;quot;Kunkun was born!&amp;quot;);
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println(&amp;quot;Kunkun is being destroyed~&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the unit test, &lt;code&gt;@Autowired&lt;/code&gt; two &lt;code&gt;Kunkuns&lt;/code&gt; and test:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1760977765546_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;We find that only the constructor was called, but after the container was destroyed, the &lt;code&gt;Bean&lt;/code&gt; objects were not destroyed. This is because for prototype &lt;code&gt;Beans&lt;/code&gt;, the container doesn&amp;#39;t manage their destruction. You must manually force destruction, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
private Kunkun kunkun1;
@Autowired
private Kunkun kunkun2;
@Test
void test3(){
    kunkun1.preDestroy();
    kunkun2.preDestroy();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Default Scope&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Beans are &lt;strong&gt;singleton (Singleton)&lt;/strong&gt; by default, sharing one instance across the entire container.&lt;/li&gt;
&lt;li&gt;You can use &lt;code&gt;@Scope(&amp;quot;prototype&amp;quot;)&lt;/code&gt; for &amp;quot;prototype (Prototype)&amp;quot; to create a new instance each time one is obtained.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creation Timing&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Singleton Bean&lt;/strong&gt;: Created when the container starts (when not &lt;code&gt;@Lazy&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prototype Bean&lt;/strong&gt;: Created each time it&amp;#39;s obtained, lazy loading by default&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Lazy&lt;/code&gt;: Used to delay singleton Bean initialization, has no practical meaning for prototype Beans&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Destruction Mechanism&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Singleton Bean&lt;/strong&gt;: Automatically destroyed when the container closes, supports &lt;code&gt;@PreDestroy&lt;/code&gt; or &lt;code&gt;DisposableBean.destroy()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prototype Bean&lt;/strong&gt;: Not managed by the container for destruction, requires manual invocation of destruction methods&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use Cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Singleton: Globally shared objects, service classes, configuration classes&lt;/li&gt;
&lt;li&gt;Prototype: Temporary objects, short-term use objects, frequently created computation or task objects&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key Points&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Singleton Beans have a long lifecycle, fully managed by the container&lt;/li&gt;
&lt;li&gt;Prototype Beans have a short lifecycle, developers are responsible for resource release&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Lazy&lt;/code&gt; mainly optimizes singleton Bean initialization, reducing startup overhead&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>Java</category><category>Spring</category><category>Bean</category><author>Glader</author></item><item><title>Injecting Third-Party Beans into IoC Container</title><link>https://blog.mygld.top/en/posts/thid-bean/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/thid-bean/</guid><description>Methods for injecting third-party Beans into the IoC container.</description><pubDate>Mon, 13 Oct 2025 16:00:30 GMT</pubDate><content:encoded>&lt;p&gt;During actual development, we often need to integrate various third-party libraries to improve development efficiency or extend system functionality. However, when using the Spring or Spring Boot framework, how to properly register components from these third-party libraries into the IoC container often becomes a problem that needs to be solved.&lt;/p&gt;
&lt;h2&gt;Manual Registration with @Bean&lt;/h2&gt;
&lt;p&gt;Taking the third-party utility package &lt;code&gt;HuTool&lt;/code&gt; as an example, first we introduce the &lt;code&gt;Maven&lt;/code&gt; dependency for &lt;code&gt;HuTool&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;    
    &amp;lt;groupId&amp;gt;cn.hutool&amp;lt;/groupId&amp;gt;    
    &amp;lt;artifactId&amp;gt;hutool-all&amp;lt;/artifactId&amp;gt;    
    &amp;lt;version&amp;gt;5.8.41&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;HuTool&lt;/code&gt;, if we want to use the &lt;code&gt;cn.hutool.http.HttpUtil&lt;/code&gt; utility class, we hope to use it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
private HttpUtil httpUtil;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But obviously, writing it directly like this will result in an error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;No qualifying bean of type &amp;#39;cn.hutool.http.HttpUtil&amp;#39; available
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because all classes in &lt;code&gt;Hutool&lt;/code&gt; use static methods and are not registered in the &lt;code&gt;Spring&lt;/code&gt; container.&lt;/p&gt;
&lt;p&gt;We can manually create a Bean in a configuration class using the &lt;code&gt;@Bean&lt;/code&gt; annotation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
public class HutoolConfig {
    @Bean
    public HttpUtil getHttpUtil() {
        return new HttpUtil();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we perform a unit test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
private HttpUtil httpUtil;
@Test
void test1(){
    System.out.println(httpUtil.get(&amp;quot;https://www.baidu.com&amp;quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1760370708748_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;When creating a Bean in a configuration class, if you need to pass parameters, and if the &lt;code&gt;IoC&lt;/code&gt; container has this parameter type, it can be auto-injected. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
public class SecurityConfig {
    @Bean
    public AuthenticationManager getAuthenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                   .build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is equivalent to omitting:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
HttpSecurity http;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@ComponentScan — Extending Scan Paths&lt;/h2&gt;
&lt;p&gt;If certain classes in a third-party library already use annotations like &lt;code&gt;@Component&lt;/code&gt;, &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Repository&lt;/code&gt;, but these classes are not under our default scan path, we can extend the scan range through &lt;code&gt;@ComponentScan&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@SpringBootApplication
@ComponentScan(basePackages = {
    &amp;quot;com.example.myapp&amp;quot;,           // Your project package path
    &amp;quot;com.baomidou.mybatisplus.extension.plugins&amp;quot;  // Third-party library package path
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;basePackages is an array. Once &lt;code&gt;@ComponentScan&lt;/code&gt; is used, the default scan path in &lt;code&gt;@SpringBootApplication&lt;/code&gt; becomes ineffective, and we must configure our project&amp;#39;s package path in basePackages, then add the third-party library&amp;#39;s package path.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;@Import to Directly Import Configuration Classes&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;@Import&lt;/code&gt; can directly import configuration classes or regular classes into the Spring container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Third-party configuration class
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
              .setAddress(&amp;quot;redis://127.0.0.1:6379&amp;quot;)
              .setPassword(&amp;quot;your-password&amp;quot;);
        return Redisson.create(config);
    }
}

// Import in main configuration class
@Configuration
@Import(RedissonConfig.class)
public class ApplicationConfig {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or use it directly on the startup class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@SpringBootApplication
@Import({RedissonConfig.class, OtherConfig.class})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Importing Regular Classes&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@Import&lt;/code&gt; can also directly import regular classes without any annotations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Third-party regular class
public class DataConverter {
    public String convert(Object data) {
        return JSON.toJSONString(data);
    }
}

// Import configuration
@Configuration
@Import(DataConverter.class)
public class ConverterConfig {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After importing, it can be directly injected and used:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class DataService {
    @Autowired
    private DataConverter dataConverter;
    
    public String processData(Object data) {
        return dataConverter.convert(data);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ImportSelector for Dynamic Bean Registration&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ImportSelector&lt;/code&gt; allows us to dynamically select which configuration classes to import based on conditions, which is very useful when we need to register Beans dynamically based on environment or configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Custom ImportSelector
public class ElasticsearchImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // Can dynamically decide which configuration classes to import based on annotation attributes, environment variables, etc.
        Map&amp;lt;String, Object&amp;gt; attributes = importingClassMetadata
            .getAnnotationAttributes(EnableElasticsearch.class.getName());
        
        String mode = (String) attributes.get(&amp;quot;mode&amp;quot;);
        
        if (&amp;quot;rest&amp;quot;.equals(mode)) {
            return new String[]{
                &amp;quot;com.example.config.ElasticsearchRestClientConfig&amp;quot;
            };
        } else if (&amp;quot;transport&amp;quot;.equals(mode)) {
            return new String[]{
                &amp;quot;com.example.config.ElasticsearchTransportConfig&amp;quot;
            };
        }
        
        // Default configuration
        return new String[]{
            &amp;quot;com.example.config.ElasticsearchDefaultConfig&amp;quot;
        };
    }
}

// Configuration class
public class ElasticsearchRestClientConfig {
    @Bean
    public ElasticsearchClient elasticsearchClient() {
        RestClient restClient = RestClient.builder(
            new HttpHost(&amp;quot;localhost&amp;quot;, 9200)
        ).build();
        
        ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper()
        );
        
        return new ElasticsearchClient(transport);
    }
}

// Use in configuration class
@Configuration
@Import(ElasticsearchImportSelector.class)
public class SearchConfig {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;More Complex Example: Conditional Import&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ConditionalImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        List&amp;lt;String&amp;gt; imports = new ArrayList&amp;lt;&amp;gt;();
        
        // Check if a certain class exists in the classpath
        if (ClassUtils.isPresent(&amp;quot;com.example.ThirdPartyClass&amp;quot;, null)) {
            imports.add(&amp;quot;com.example.config.ThirdPartyConfig&amp;quot;);
        }
        
        // Check system properties
        if (&amp;quot;true&amp;quot;.equals(System.getProperty(&amp;quot;enable.cache&amp;quot;))) {
            imports.add(&amp;quot;com.example.config.CacheConfig&amp;quot;);
        }
        
        // Check environment variables
        String profile = System.getenv(&amp;quot;SPRING_PROFILES_ACTIVE&amp;quot;);
        if (&amp;quot;prod&amp;quot;.equals(profile)) {
            imports.add(&amp;quot;com.example.config.ProdConfig&amp;quot;);
        }
        
        return imports.toArray(new String[0]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Using DeferredImportSelector&lt;/h3&gt;
&lt;p&gt;If you need to perform imports after all &lt;code&gt;@Configuration&lt;/code&gt; classes have been processed, you can implement the &lt;code&gt;DeferredImportSelector&lt;/code&gt; interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class DeferredElasticsearchImportSelector implements DeferredImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        // Deferred import logic
        return new String[]{&amp;quot;com.example.config.ElasticsearchConfig&amp;quot;};
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@EnableXxx Composite Annotations&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@EnableXxx&lt;/code&gt; is a high-level encapsulated configuration approach that typically combines &lt;code&gt;@Import&lt;/code&gt; with other annotations, providing users with a simple switch to enable a certain functional module.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@EnableXxx&lt;/code&gt; can be placed on the startup class or on a configuration class.&lt;/p&gt;
</content:encoded><category>Bean</category><category>Java</category><author>Glader</author></item><item><title>Spring AOP</title><link>https://blog.mygld.top/en/posts/aop/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/aop/</guid><description>Knowledge points about AOP in Spring.</description><pubDate>Wed, 08 Oct 2025 11:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Spring AOP Annotation Explanation&lt;/h2&gt;
&lt;h3&gt;@Before&lt;/h3&gt;
&lt;p&gt;Executed before the target method runs. Commonly used for logging, parameter validation, permission checks, etc.&lt;br&gt;When the defined pointcut expression is matched, Spring executes this method before calling the target method.&lt;/p&gt;
&lt;h3&gt;@Around&lt;/h3&gt;
&lt;p&gt;An around advice wraps the entire execution process of the target method.&lt;br&gt;By calling &lt;code&gt;ProceedingJoinPoint.proceed()&lt;/code&gt; explicitly, it can add custom logic before and after the method execution.&lt;br&gt;This is the most flexible advice type — it can control whether the method executes, modify parameters, or process return values.&lt;/p&gt;
&lt;h3&gt;@After&lt;/h3&gt;
&lt;p&gt;Executed after the target method finishes, regardless of whether an exception occurred.&lt;br&gt;Similar to a &lt;code&gt;finally&lt;/code&gt; block, it’s often used for resource cleanup or general logging.&lt;/p&gt;
&lt;h3&gt;@AfterReturning&lt;/h3&gt;
&lt;p&gt;Executed only when the target method returns normally.&lt;br&gt;If the method throws an exception, this advice will not run.&lt;br&gt;Often used to process the return value or log successful execution.&lt;/p&gt;
&lt;h3&gt;@AfterThrowing&lt;/h3&gt;
&lt;p&gt;Executed when the target method throws an exception.&lt;br&gt;Useful for logging exception details, sending alerts, or collecting error statistics.&lt;/p&gt;
&lt;h3&gt;@Pointcut&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@Pointcut&lt;/code&gt; is used to &lt;strong&gt;extract pointcut expressions&lt;/strong&gt;, which define where advices apply.&lt;br&gt;Instead of writing &lt;code&gt;execution(...)&lt;/code&gt; repeatedly, you can define it once as a method and reuse it across multiple advices for better readability and maintainability.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Pointcut(&amp;quot;execution(* top.mygld.demo.service.impl..*(..))&amp;quot;)
public void serviceMethods() {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then reuse it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Before(&amp;quot;serviceMethods()&amp;quot;)
@After(&amp;quot;serviceMethods()&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Spring AOP Example (Using @Pointcut Extraction)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class TestAspect {

    // Extracted pointcut expression matching all methods under service.impl package
    @Pointcut(&amp;quot;execution(* top.mygld.demo.service.impl..*(..))&amp;quot;)
    public void serviceMethods() {}

    @Before(&amp;quot;serviceMethods()&amp;quot;)
    public void before() {
        log.info(&amp;quot;before ....&amp;quot;);
    }

    @Around(&amp;quot;serviceMethods()&amp;quot;)
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info(&amp;quot;around ... before ....&amp;quot;);
        Object result = joinPoint.proceed();
        log.info(&amp;quot;around ... after ....&amp;quot;);
        return result;
    }

    @After(&amp;quot;serviceMethods()&amp;quot;)
    public void after() {
        log.info(&amp;quot;after ....&amp;quot;);
    }

    @AfterReturning(&amp;quot;serviceMethods()&amp;quot;)
    public void afterReturning() {
        log.info(&amp;quot;afterReturning ....&amp;quot;);
    }

    @AfterThrowing(&amp;quot;serviceMethods()&amp;quot;)
    public void afterThrowing() {
        log.info(&amp;quot;afterThrowing ....&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Execution order when the method runs normally:
&lt;code&gt;@Around → @Before → method execution → @AfterReturning → @After → @Around&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Execution order when the method throws an exception:
&lt;code&gt;@Around → @Before → method execution → @AfterThrowing → @After → @Around&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Using these annotations together allows injecting logic at different stages — ideal for unified logging, monitoring, or security.
&lt;code&gt;@Pointcut&lt;/code&gt; makes the structure cleaner by allowing multiple advices to share the same matching rules.&lt;/p&gt;
&lt;p&gt;If different methods need their own pointcut expressions, they can be written separately, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@AfterReturning(&amp;quot;execution(* top.mygld.demo.dao.impl..*(..))&amp;quot;)
public void afterReturning(){
    log.info(&amp;quot;afterReturning ....&amp;quot;);
}

@AfterThrowing(&amp;quot;execution(* top.mygld.demo.service.impl..*(..))&amp;quot;)
public void afterThrowing(){
    log.info(&amp;quot;afterThrowing ....&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Multiple Advice Execution Order&lt;/h2&gt;
&lt;p&gt;In Spring AOP, the &lt;strong&gt;execution order of multiple advices&lt;/strong&gt; follows these rules:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Within the same aspect class:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default natural order:
&lt;code&gt;@Around → @Before → target method → @AfterReturning/@AfterThrowing → @After → @Around&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If there are multiple advices of the same type (e.g., several &lt;code&gt;@Before&lt;/code&gt;), they execute in &lt;strong&gt;alphabetical order&lt;/strong&gt; of method names.&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;@Before&lt;/code&gt;, smaller alphabetical order executes &lt;strong&gt;earlier&lt;/strong&gt;; for &lt;code&gt;@After&lt;/code&gt;, smaller alphabetical order executes &lt;strong&gt;later&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Between different aspect classes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;By default, class names determine the order (alphabetical).&lt;/li&gt;
&lt;li&gt;You can control priority using &lt;code&gt;@Order&lt;/code&gt; or implementing &lt;code&gt;Ordered&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A smaller &lt;code&gt;@Order&lt;/code&gt; value means higher priority — it executes &lt;code&gt;@Before&lt;/code&gt; earlier and &lt;code&gt;@After&lt;/code&gt; later.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Aspect
@Component
@Order(5)
public class LogAspect1 {}

@Aspect
@Component
@Order(7)
public class LogAspect2 {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Execution overview:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Outer aspect @Around
    ↓
  Inner aspect @Around
      ↓
    @Before (outer to inner)
        ↓
     Target method
        ↓
    @AfterReturning / @AfterThrowing (inner to outer)
        ↓
    @After (inner to outer)
    ↓
  Inner @Around ends
↓
Outer @Around ends
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This mechanism ensures predictable and precisely controlled AOP execution order when multiple aspects or advices exist.&lt;/p&gt;
&lt;h2&gt;4. Pointcut Expressions&lt;/h2&gt;
&lt;h3&gt;execution&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;execution&lt;/code&gt; matches based on method return type, package name, class name, method name, and parameters.&lt;/p&gt;
&lt;p&gt;Syntax:
&lt;code&gt;execution(modifier? return-type package.class.?method(parameters) throws exception?)&lt;/code&gt;
Parts marked with &lt;code&gt;?&lt;/code&gt; are optional.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modifier: optional (e.g., public, protected)&lt;/li&gt;
&lt;li&gt;Package/class: optional but recommended&lt;/li&gt;
&lt;li&gt;&lt;code&gt;throws&lt;/code&gt;: optional (refers to declared exceptions, not runtime ones)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;*&lt;/code&gt; matches any single element — return type, package, class, method, or parameter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;execution(* top.*.service.*.update*(*))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;..&lt;/code&gt; matches multiple consecutive elements — nested packages or any number of parameters.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;execution(* top.mygld..UserService.*(..))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;annotation&lt;/h3&gt;
&lt;p&gt;Matches methods annotated with a specific annotation.
Example annotation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applied to a method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@LogOperation
@Override
public List&amp;lt;User&amp;gt; findAll() {
    return userDao.findAll();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Used in an aspect:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Before(&amp;quot;@annotation(top.mygld.demo.anno.LogOperation)&amp;quot;)
public void before(){
    log.info(&amp;quot;before&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In IDEs like IntelliJ, the &lt;code&gt;m&lt;/code&gt; icon can confirm the match visually.&lt;/p&gt;
&lt;h2&gt;5. JoinPoint&lt;/h2&gt;
&lt;p&gt;In Spring, &lt;code&gt;JoinPoint&lt;/code&gt; abstracts a connection point, allowing access to method execution details such as class name, method name, and arguments.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;@Around&lt;/code&gt;, use &lt;code&gt;ProceedingJoinPoint&lt;/code&gt;;
for the other four types, use &lt;code&gt;JoinPoint&lt;/code&gt;, which is the parent of &lt;code&gt;ProceedingJoinPoint&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Before(&amp;quot;@annotation(top.mygld.demo.anno.LogOperation)&amp;quot;)
public void before(JoinPoint jp){
    log.info(&amp;quot;before&amp;quot;);
    // 1. Get target object
    Object target = jp.getTarget();
    log.info(&amp;quot;target: {}&amp;quot;, target);
        
    // 2. Get target class
    String name = target.getClass().getName();
    log.info(&amp;quot;name: {}&amp;quot;, name);
        
    // 3. Get target method
    String methodName = jp.getSignature().getName();
    log.info(&amp;quot;methodName: {}&amp;quot;, methodName);
        
    // 4. Get method arguments
    Object[] args = jp.getArgs();
    log.info(&amp;quot;args: {}&amp;quot;, args);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ProceedingJoinPoint&lt;/code&gt; can execute the target method, while &lt;code&gt;JoinPoint&lt;/code&gt; cannot.
The former is a subclass of the latter.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Java</category><category>AOP</category><category>Spring</category><author>Glader</author></item><item><title>volatile Keyword</title><link>https://blog.mygld.top/en/posts/volatile/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/volatile/</guid><description>Key concepts about the volatile keyword in Java.</description><pubDate>Wed, 01 Oct 2025 16:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;volatile keyword&lt;/h2&gt;
&lt;p&gt;Now we have the following two pieces of code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class MyThread extends Thread{
    public static int a = 0;
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(&amp;quot;Thread starts executing, current a = &amp;quot; + a);
        a = 1;
        System.out.println(&amp;quot;Thread finishes executing, current a = &amp;quot; + a);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
        while(true){
            if(MyThread.a == 1){
                System.out.println(&amp;quot;Detected a = 1&amp;quot;);
                break;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, if we run the main class, what do you think the result will be?&lt;/p&gt;
&lt;p&gt;Based on our prior knowledge, we would expect that the program first creates thread &lt;code&gt;t&lt;/code&gt;, then starts it. Thread &lt;code&gt;t&lt;/code&gt; executes concurrently with the main thread. The main thread enters an infinite loop, waiting and monitoring the shared member &lt;code&gt;a&lt;/code&gt; in &lt;code&gt;MyThread&lt;/code&gt;. After thread &lt;code&gt;t&lt;/code&gt; sleeps for 1 second and then sets &lt;code&gt;a = 1&lt;/code&gt;, the main thread detects that &lt;code&gt;a&lt;/code&gt; has changed and breaks out of the loop, ending the program. In other words, the expected sequential output would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Thread starts executing, current a = 0
Thread finishes executing, current a = 1
Detected a = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the program ends. This is what we would reason from the code, but is this really what happens? Let&amp;#39;s try it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1759329721308_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;We find that the program only prints the content from thread &lt;code&gt;t&lt;/code&gt;, but after that it does not terminate—it keeps running indefinitely, even if we wait for a long time. Why is that?&lt;/p&gt;
&lt;p&gt;This problem is called &lt;strong&gt;visibility&lt;/strong&gt; in multithreading, which refers to whether a thread can immediately see changes made to a shared variable by another thread.&lt;/p&gt;
&lt;p&gt;There are many causes for visibility issues. In this case, it is mainly because modern CPUs often cache variable values in registers or local caches rather than reading from main memory each time.&lt;/p&gt;
&lt;p&gt;In simple terms, for a &lt;code&gt;static&lt;/code&gt; shared variable, to avoid multiple threads modifying main memory directly and causing problems, Java first copies the shared variable into the thread&amp;#39;s working memory when reading it. If the thread does not modify this copy, it does not fetch the latest value from main memory. Only when the thread modifies its copy does it synchronize the change back to the shared variable in main memory.&lt;/p&gt;
&lt;p&gt;So the problem above can be explained as follows: thread &lt;code&gt;t&lt;/code&gt; sleeps for 1 second first, which allows the main thread to execute &lt;code&gt;if(MyThread.a == 1)&lt;/code&gt; first. The main thread gets a copy of &lt;code&gt;a&lt;/code&gt;, whose value is &lt;code&gt;0&lt;/code&gt;. When thread &lt;code&gt;t&lt;/code&gt; wakes up and sets &lt;code&gt;a = 1&lt;/code&gt;, even though it is synchronized to the shared variable, the main thread has not modified its copy, so it never fetches the latest value and keeps using the original copy with &lt;code&gt;a = 0&lt;/code&gt;. This leads to an infinite loop in the main thread.&lt;/p&gt;
&lt;p&gt;One might think: we just need the main thread to refresh the value of &lt;code&gt;a&lt;/code&gt;. This is possible, and there are several ways to do it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            Thread.sleep(1);
            if(MyThread.a == 1){
                System.out.println(&amp;quot;Detected a = 1&amp;quot;);
                break;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, adding &lt;code&gt;Thread.sleep(milliseconds)&lt;/code&gt; inside the &lt;code&gt;while&lt;/code&gt; loop causes the main thread to block. Once it resumes, it fetches the latest value of the shared variable. The output is:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1759331150903_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The program now behaves as expected and terminates normally. Other methods include:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            synchronized (Main.class) {}
            if(MyThread.a == 1){
                System.out.println(&amp;quot;Detected a = 1&amp;quot;);
                break;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            Thread.currentThread().resume();
            if(MyThread.a == 1){
                System.out.println(&amp;quot;Detected a = 1&amp;quot;);
                break;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These methods use the fact that exiting a synchronized block or forcing a thread refresh will fetch the latest value of the shared variable. Note that the second method is deprecated.&lt;/p&gt;
&lt;p&gt;Is there a direct way to solve the shared variable inconsistency? Yes, the &lt;code&gt;volatile&lt;/code&gt; keyword. When a shared variable is declared &lt;code&gt;volatile&lt;/code&gt;, writes are immediately flushed to main memory, and reads are forced to fetch the latest value from main memory into the thread&amp;#39;s working memory (copy), rather than using an old local copy.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class MyThread extends Thread{
    public static volatile int a = 0;
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(&amp;quot;Thread starts executing, current a = &amp;quot; + a);
        a = 1;
        System.out.println(&amp;quot;Thread finishes executing, current a = &amp;quot; + a);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            if(MyThread.a == 1){
                System.out.println(&amp;quot;Detected a = 1&amp;quot;);
                break;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1759331150903_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, it works as intended.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;volatile&lt;/code&gt; also works for non-&lt;code&gt;static&lt;/code&gt; instance variables.&lt;/p&gt;
&lt;p&gt;In addition, &lt;code&gt;volatile&lt;/code&gt; can prevent instruction reordering. Although reordering has less impact than the randomness of multithreading, this effect is still worth noting.&lt;/p&gt;
</content:encoded><category>Java</category><category>volatile</category><author>Glader</author></item><item><title>Vocabulary and Idiom Accumulation</title><link>https://blog.mygld.top/en/posts/idioms/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/idioms/</guid><description>gwy exam vocabulary and idiom accumulation.</description><pubDate>Tue, 30 Sep 2025 09:00:50 GMT</pubDate><content:encoded>&lt;p&gt;The current article does not support English. Please switch to &lt;strong&gt;Chinese&lt;/strong&gt; to view it.&lt;/p&gt;
</content:encoded><category>Vocabulary</category><category>Idioms</category><category>gwy</category><author>Glader</author></item><item><title>Thread Pool</title><link>https://blog.mygld.top/en/posts/thread-pool/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/thread-pool/</guid><description>Java thread pool summary.</description><pubDate>Mon, 29 Sep 2025 07:07:53 GMT</pubDate><content:encoded>&lt;h2&gt;Core Principles of Thread Pool&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Create a pool, the pool is initially empty&lt;/li&gt;
&lt;li&gt;When submitting tasks, the pool creates new thread objects. After task completion, threads are returned to the pool. When submitting tasks again, there&amp;#39;s no need to create new threads, existing threads can be reused directly&lt;/li&gt;
&lt;li&gt;However, if there are no idle threads in the pool when submitting tasks, and no new threads can be created, tasks will queue and wait&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Creating Thread Pools&lt;/h2&gt;
&lt;p&gt;Thread pool creation mainly uses two methods from the &lt;code&gt;Executors&lt;/code&gt; utility class: &lt;code&gt;newCachedThreadPool&lt;/code&gt; and &lt;code&gt;newFixedThreadPool&lt;/code&gt;. The former creates a thread pool with no upper limit, while the latter creates a thread pool with an upper limit. Both return an &lt;code&gt;ExecutorService&lt;/code&gt; object:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
ExecutorService e2 = Executors.newFixedThreadPool(3); //Create a thread pool that can contain at most 5 threads
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Submitting Tasks to Thread Pool&lt;/h2&gt;
&lt;p&gt;Next, we&amp;#39;ll test whether the thread pool can really achieve the expected effect. Common methods for submitting tasks are &lt;code&gt;submit&lt;/code&gt; or &lt;code&gt;execute&lt;/code&gt;. Usually, &lt;code&gt;execute(Runnable)&lt;/code&gt; submits tasks for execution only without returning results, while &lt;code&gt;submit(Runnable)&lt;/code&gt; returns a &lt;code&gt;Future&lt;/code&gt; that can be used to get execution status or exception information.&lt;/p&gt;
&lt;p&gt;We perform the following test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args){
        //Create threads
        ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //Create a thread pool that can contain at most 5 threads
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &amp;quot; is running.&amp;quot;);
            }
        };
        e1.submit(r);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1759131804943_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown above, the thread is already executing. Notice that the program doesn&amp;#39;t end, so we need to destroy the thread pool when it&amp;#39;s no longer needed, although generally thread pools don&amp;#39;t need to be closed.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s test with more threads:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args){
        //Create threads
        ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //Create a thread pool that can contain at most 5 threads
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &amp;quot; is running.&amp;quot;);
            }
        };
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1759132017346_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown, we added 5 tasks. For further testing, we&amp;#39;ll add a short sleep before adding tasks to verify whether thread objects that have finished execution in the thread pool can be reused:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        //Create threads
        ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //Create a thread pool that can contain at most 5 threads
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &amp;quot; is running.&amp;quot;);
            }
        };
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1759132181483_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown above, thread 1 is reused multiple times after completion.&lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s test &lt;code&gt;newFixedThreadPool&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        //Create threads
        //ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
        ExecutorService e2 = Executors.newFixedThreadPool(3); //Create a thread pool that can contain at most 5 threads
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &amp;quot; is running.&amp;quot;);
            }
        };
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1759132350198_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Perfect! As shown above, at most 3 threads can be used.&lt;/p&gt;
&lt;h2&gt;Thread Pool Destruction&lt;/h2&gt;
&lt;p&gt;For unused thread pools, simply use the &lt;code&gt;shutdown&lt;/code&gt; method to destroy them. No demonstration here.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Custom Thread Pool&lt;/h2&gt;
&lt;p&gt;Besides using factory methods provided by &lt;code&gt;Executors&lt;/code&gt;, we can also create a more flexible and controllable thread pool through &lt;code&gt;ThreadPoolExecutor&lt;/code&gt;. Its constructor is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public ThreadPoolExecutor(
        int corePoolSize,        // Core thread count
        int maximumPoolSize,     // Maximum thread count
        long keepAliveTime,      // Maximum survival time for non-core threads
        TimeUnit unit,           // Time unit for survival time
        BlockingQueue&amp;lt;Runnable&amp;gt; workQueue, // Task queue
        ThreadFactory threadFactory,       // Thread factory for creating new threads
        RejectedExecutionHandler handler   // Rejection policy
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Parameter Meanings&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;corePoolSize&lt;/strong&gt;
 Core thread count, the number of threads the thread pool will always maintain, even if these threads are idle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;maximumPoolSize&lt;/strong&gt;
 Maximum thread count, the maximum number of threads the thread pool can expand to when there are too many tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;keepAliveTime &amp;amp; unit&lt;/strong&gt;
 Survival time for non-core threads (beyond &lt;code&gt;corePoolSize&lt;/code&gt;) when idle, they will be recycled after timeout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;workQueue&lt;/strong&gt;
 Blocking queue for storing tasks waiting to be executed. Common choices:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ArrayBlockingQueue&lt;/code&gt; (bounded queue, array implementation)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LinkedBlockingQueue&lt;/code&gt; (unbounded queue, linked list implementation)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SynchronousQueue&lt;/code&gt; (direct task submission, no storage)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;threadFactory&lt;/strong&gt;
 Defines how threads are created, such as setting thread names, whether they are daemon threads, etc. Default factory is sufficient.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;handler&lt;/strong&gt;
 Rejection policy when both thread pool and queue are full:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AbortPolicy&lt;/code&gt; (default, throws exception)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CallerRunsPolicy&lt;/code&gt; (caller thread executes the task)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DiscardPolicy&lt;/code&gt; (directly discard task)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DiscardOldestPolicy&lt;/code&gt; (discard oldest task in queue)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Example: Custom Thread Pool&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        // Custom thread pool
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,                      // Core thread count
                5,                      // Maximum thread count
                10, TimeUnit.SECONDS,   // Non-core thread idle survival time
                new ArrayBlockingQueue&amp;lt;&amp;gt;(3),  // Bounded task queue, stores at most 3 tasks
                Executors.defaultThreadFactory(), // Default thread factory
                new ThreadPoolExecutor.AbortPolicy() // Rejection policy: throw exception directly
        );

        Runnable task = () -&amp;gt; {
            System.out.println(Thread.currentThread().getName() + &amp;quot; is executing task&amp;quot;);
            try {
                Thread.sleep(2000); // Simulate time-consuming task
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        // Submit 10 tasks, observe how thread pool schedules them
        for (int i = 1; i &amp;lt;= 10; i++) {
            try {
                pool.execute(task);
            } catch (RejectedExecutionException e) {
                System.out.println(&amp;quot;Task &amp;quot; + i + &amp;quot; was rejected!&amp;quot;);
            }
        }

        pool.shutdown(); // Close thread pool
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1759135722020_c11effa6-8ac4-4dab-acd1-bf8f0085253c.png&quot; alt=&quot;c11effa6-8ac4-4dab-acd1-bf8f0085253c.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Execution Flow Explanation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The first two tasks will directly start &lt;strong&gt;core threads&lt;/strong&gt; for processing.&lt;/li&gt;
&lt;li&gt;The next three tasks will be placed in the &lt;strong&gt;task queue&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;When the queue is full, the thread pool will continue creating new threads (up to &lt;code&gt;maximumPoolSize=5&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If both thread pool and queue are full, the 9th and 10th tasks will trigger the &lt;strong&gt;rejection policy&lt;/strong&gt;, throwing exceptions directly and indicating rejection.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Maximum Parallelism&lt;/h2&gt;
&lt;h3&gt;Meaning of Maximum Parallelism&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU Level&lt;/strong&gt;:
 For compute-intensive tasks, maximum parallelism is usually limited by the number of CPU cores (including hyperthreading). For example, if your CPU has 8 cores (16 threads), theoretically executing 16 computing tasks simultaneously is optimal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thread Pool Level&lt;/strong&gt;:
 For Java&amp;#39;s &lt;code&gt;ExecutorService&lt;/code&gt; or &lt;code&gt;ThreadPoolExecutor&lt;/code&gt;, maximum parallelism is the value of the thread pool&amp;#39;s &lt;code&gt;maximumPoolSize&lt;/code&gt;. The thread pool controls the number of simultaneously active threads based on this value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operating System Limitations&lt;/strong&gt;:
 JVM thread count is also limited by the operating system. Usually, too many threads can cause memory overflow or excessive scheduling overhead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maximum parallelism can be obtained through the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args) {
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println(i);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Appropriate Thread Pool Size&lt;/h2&gt;
&lt;h3&gt;CPU-Intensive Computing&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Maximum parallelism + 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;I/O-Intensive Computing&lt;/h3&gt;
&lt;p&gt;$Maximum\ parallelism \times Expected\ CPU\ utilization \times \frac{Total\ time(CPU\ compute\ time + wait\ time)}{CPU\ compute\ time}$&lt;/p&gt;
&lt;h2&gt;Interview Key Points&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;volatile&lt;/li&gt;
&lt;li&gt;JMM&lt;/li&gt;
&lt;li&gt;Pessimistic locks, optimistic locks, CAS&lt;/li&gt;
&lt;li&gt;Atomicity&lt;/li&gt;
&lt;li&gt;Concurrency utilities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These topics are frequently asked in interviews and will be organized later.&lt;/p&gt;
</content:encoded><category>Thread Pool</category><category>Java</category><author>Glader</author></item><item><title>Java Dynamic Proxy</title><link>https://blog.mygld.top/en/posts/dynamic-proxy/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/dynamic-proxy/</guid><description>Knowledge points related to Java dynamic proxy.</description><pubDate>Sun, 14 Sep 2025 05:40:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Concept&lt;/h2&gt;
&lt;p&gt;Java dynamic proxy, in my personal understanding, is essentially creating a temporary implementation class for an interface through reflection mechanism and returning an instantiated object.&lt;/p&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Can only proxy interfaces&lt;/strong&gt;: JDK dynamic proxy can only create proxies for interfaces, not for concrete classes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reflection invocation&lt;/strong&gt;: Every method call is forwarded to InvocationHandler through reflection mechanism&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temporary class&lt;/strong&gt;: Proxy class is generated at runtime, exists in memory, and is not persisted to disk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To put it simply, suppose Kun Kun is a big star who wants to hold a concert. Kun Kun is only responsible for singing and dancing, so setting up the stage and other tasks are definitely not done by Kun Kun himself, so we need an agent to do these things.&lt;/p&gt;
&lt;p&gt;So we can do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;

public class KunKun {
    public String sing(String name){
        System.out.println(&amp;quot;Kun Kun sang: &amp;quot; + name);
        return &amp;quot;Kun Kun finished singing&amp;quot;;
    }
    public void dance(String name){
        System.out.println(&amp;quot;Kun Kun danced: &amp;quot; + name + &amp;quot; dance!&amp;quot;);
    }
}
package top.mygld.demo.test;

public class ProxyPeople {
    KunKun kunKun = new KunKun();
    public String sing(String name){
        System.out.println(&amp;quot;Agent sets up the venue.&amp;quot; );
        System.out.println(&amp;quot;Agent finished setting up the venue.&amp;quot; );
        return kunKun.sing(name);
    }
    public void dance(String name){
        System.out.println(&amp;quot;Kun Kun danced: &amp;quot; + name + &amp;quot; dance!&amp;quot;);
        kunKun.dance(name);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we just need to run in the main class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;ProxyPeople proxyPeople = new ProxyPeople();
proxyPeople.sing(&amp;quot;Just Because You&amp;#39;re Too Beautiful&amp;quot;);
proxyPeople.dance(&amp;quot;Ballet&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&amp;#39;s it.&lt;/p&gt;
&lt;p&gt;But writing this way is obviously not elegant enough, so you thought of using polymorphism:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;

public interface People {
    String sing(String name);
    void dance(String name);
}
package top.mygld.demo.test;

public class KunKun implements People{
    @Override
    public String sing(String name){
        System.out.println(&amp;quot;Kun Kun sang: &amp;quot; + name);
        return &amp;quot;Kun Kun finished singing&amp;quot;;
    }
    @Override
    public void dance(String name){
        System.out.println(&amp;quot;Kun Kun danced: &amp;quot; + name + &amp;quot; dance!&amp;quot;);
    }
}
package top.mygld.demo.test;

public class ProxyPeople implements People{
    KunKun kunKun = new KunKun();
    @Override
    public String sing(String name){
        System.out.println(&amp;quot;Agent sets up the venue.&amp;quot; );
        System.out.println(&amp;quot;Agent finished setting up the venue.&amp;quot; );
        return kunKun.sing(name);
    }
    @Override
    public void dance(String name){
        System.out.println(&amp;quot;Agent sets up the venue.&amp;quot; );
        System.out.println(&amp;quot;Agent finished setting up the venue.&amp;quot; );
        kunKun.dance(name);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is much more elegant, but it&amp;#39;s still not enough. This way we need to write a separate implementation class &lt;code&gt;ProxyPeople&lt;/code&gt;, which seems somewhat redundant, because our statements are relatively simple. Is there a way to avoid writing a separate implementation class? Dynamic proxy is designed for this purpose.&lt;/p&gt;
&lt;h2&gt;2. Proxy Class&lt;/h2&gt;
&lt;p&gt;In the &lt;code&gt;java.lang.reflect&lt;/code&gt; package, there is a &lt;code&gt;Proxy&lt;/code&gt; class, which has a &lt;code&gt;newProxyInstance(ClassLoader loader, Class&amp;lt;?&amp;gt;[] interfaces, InvocationHandler h)&lt;/code&gt; method that can temporarily generate implementation classes and return an object.&lt;/p&gt;
&lt;p&gt;Among them, the first parameter of &lt;code&gt;newProxyInstance&lt;/code&gt; is a class loader, the second is a Class array of interfaces, and the third parameter is used to specify what the proxy object should do, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyUtil {
    public static People createProxyPeople(KunKun kunKun) {
        return (People) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{People.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                        System.out.println(&amp;quot;Agent sets up the venue.&amp;quot; );
                        System.out.println(&amp;quot;Agent finished setting up the venue.&amp;quot; );
                        if (method.getName().equals(&amp;quot;sing&amp;quot;)) {
                            return method.invoke(kunKun, objects);
                        }
                        else{
                            method.invoke(kunKun,objects);
                            return null;
                        }
                    }
                });
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the &lt;code&gt;KunKun&lt;/code&gt; class doesn&amp;#39;t need to be changed, just run directly in the main class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;People proxyPeople = ProxyUtil.createProxyPeople(new KunKun());
System.out.println(proxyPeople.sing(&amp;quot;Just Because You&amp;#39;re Too Beautiful&amp;quot;));
proxyPeople.dance(&amp;quot;Ballet&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757831269327_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s a supplement: why is the second parameter of &lt;code&gt;newProxyInstance&lt;/code&gt; an array? Because &lt;strong&gt;a proxy object can implement multiple interfaces simultaneously&lt;/strong&gt;, for example:&lt;/p&gt;
&lt;p&gt;Suppose you have multiple interfaces:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;interface UserService {
    void saveUser(String name);
}

interface LogService {
    void log(String message);
}

interface CacheService {
    void cache(String key, Object value);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can create a proxy object that &lt;strong&gt;implements all three interfaces simultaneously&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(&amp;quot;Calling method: &amp;quot; + method.getName());
        
        // Handle differently based on method name or interface type
        if (method.getName().equals(&amp;quot;saveUser&amp;quot;)) {
            System.out.println(&amp;quot;Save user: &amp;quot; + args[0]);
        } else if (method.getName().equals(&amp;quot;log&amp;quot;)) {
            System.out.println(&amp;quot;Log message: &amp;quot; + args[0]);
        } else if (method.getName().equals(&amp;quot;cache&amp;quot;)) {
            System.out.println(&amp;quot;Cache data: &amp;quot; + args[0] + &amp;quot; -&amp;gt; &amp;quot; + args[1]);
        }
        
        return null;
    }
};

// Implement multiple interfaces simultaneously
Object proxy = Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class, LogService.class, CacheService.class},  // Multiple interfaces
    handler
);

// Can be cast to any interface for use
UserService userService = (UserService) proxy;
LogService logService = (LogService) proxy;
CacheService cacheService = (CacheService) proxy;

userService.saveUser(&amp;quot;Zhang San&amp;quot;);
logService.log(&amp;quot;Operation completed&amp;quot;);
cacheService.cache(&amp;quot;user:1&amp;quot;, &amp;quot;Zhang San&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you pass in multiple interfaces, the JVM will generate a proxy class that implements all interfaces:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// The dynamically generated proxy class will implement all interfaces
public final class $Proxy0 extends Proxy 
    implements UserService, LogService, CacheService {
    
    // Implement UserService methods
    public void saveUser(String name) throws Throwable {
        super.h.invoke(this, saveUserMethod, new Object[]{name});
    }
    
    // Implement LogService methods
    public void log(String message) throws Throwable {
        super.h.invoke(this, logMethod, new Object[]{message});
    }
    
    // Implement CacheService methods
    public void cache(String key, Object value) throws Throwable {
        super.h.invoke(this, cacheMethod, new Object[]{key, value});
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Actual Use Cases&lt;/h2&gt;
&lt;p&gt;This design is common in frameworks, such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Spring AOP&lt;/strong&gt;: Proxy objects need to implement original interface + additional enhancement interfaces&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transaction management&lt;/strong&gt;: Business interface + transaction management interface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache proxy&lt;/strong&gt;: Business interface + cache management interface&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So the array design is for supporting the flexibility of &lt;strong&gt;multiple interface implementation&lt;/strong&gt;. Even if you only pass in one interface, you still need to use array form &lt;code&gt;new Class[]{YourInterface.class}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s it. The above are the knowledge points related to Java dynamic proxy.&lt;/p&gt;
</content:encoded><category>Java</category><category>Dynamic Proxy</category><author>Glader</author></item><item><title>Java Reflection Review</title><link>https://blog.mygld.top/en/posts/java-fanshe/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/java-fanshe/</guid><description>Java reflection review knowledge point compilation.</description><pubDate>Thu, 11 Sep 2025 12:03:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. The Concept of Java Reflection&lt;/h2&gt;
&lt;p&gt;Java&amp;#39;s reflection mechanism refers to the ability during program runtime to construct objects of any class, understand the class to which any object belongs, understand the member variables and methods of any class, and call the properties and methods of any object. This capability of dynamically obtaining program information and dynamically calling objects is called Java&amp;#39;s reflection mechanism. Reflection is considered key to dynamic languages.&lt;/p&gt;
&lt;h2&gt;2. Reflection API&lt;/h2&gt;
&lt;h3&gt;(1) Commonly Used Classes in Reflection&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;java.lang.Class&lt;/code&gt;: Classes in Java can also be considered special &lt;strong&gt;objects&lt;/strong&gt;, and Class represents class objects.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java.lang.reflect.Constructor&lt;/code&gt;: Class constructors.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java.lang.reflect.Field&lt;/code&gt;: Class member variables.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java.lang.reflect.Method&lt;/code&gt;: Class member methods.&lt;/p&gt;
&lt;h3&gt;(2) Three Methods to Get Class Objects&lt;/h3&gt;
&lt;p&gt;Now I&amp;#39;ll create a &lt;code&gt;User&lt;/code&gt; class in the &lt;code&gt;top.mygld.demo.pojo&lt;/code&gt; package as follows for demonstration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.pojo;
public class User {
    private String name;
    private int age;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I&amp;#39;ll demonstrate getting class objects in the main class.&lt;/p&gt;
&lt;p&gt;i) Get directly through &lt;code&gt;ClassName.class&lt;/code&gt;, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Class c = User.class;   //Assuming User is our encapsulated class
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ii) Get through &lt;code&gt;Class.forName(&amp;quot;full class name&amp;quot;)&lt;/code&gt;, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Class c = Class.forName(&amp;quot;top.mygld.demo.pojo.User&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;iii) Get through &lt;code&gt;specific object.getClass()&lt;/code&gt;, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;User user = new User(&amp;quot;Xiao Ming&amp;quot;,30);
Class c = user.getClass();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The object &lt;code&gt;c&lt;/code&gt; obtained through the above three methods is the class object corresponding to the &lt;code&gt;User&lt;/code&gt; class. Next, we can call some methods in &lt;code&gt;c&lt;/code&gt; to obtain internal information of the &lt;code&gt;User&lt;/code&gt; class and then manipulate the internal information, which is Java reflection.&lt;/p&gt;
&lt;h3&gt;(3) Basic Information of Class Objects&lt;/h3&gt;
&lt;p&gt;In step (2), we obtained the class object &lt;code&gt;c&lt;/code&gt;. What would we get if we directly print and output &lt;code&gt;c&lt;/code&gt;? Let&amp;#39;s try it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
public class ReflectionDemo {
    public static void main(String[] args){
        Class c = User.class;
        System.out.println(c);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757600592486_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown above, it outputs &lt;code&gt;class + full class name&lt;/code&gt;. This shows that the &lt;code&gt;Class&lt;/code&gt; class overrides the &lt;code&gt;toString&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Additionally, the &lt;code&gt;Class&lt;/code&gt; class provides two main member methods: &lt;code&gt;getName&lt;/code&gt; and &lt;code&gt;getSimpleName&lt;/code&gt;, used to get the &lt;strong&gt;full class name&lt;/strong&gt; and &lt;strong&gt;simple class name&lt;/strong&gt; of the class object respectively, with effects as follows.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
public class ReflectionDemo {
    public static void main(String[] args){
        Class c = User.class;
        System.out.println(c.getName());
        System.out.println(c.getSimpleName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757600943657_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;(4) Getting Constructor Information of Target Class Through Reflection&lt;/h3&gt;
&lt;p&gt;Through the reflection mechanism, we can obtain constructor information of the target class, such as the number of constructors, constructor parameter situations (no-arg constructor, parameterized constructor), number of parameters in parameterized constructors, parameter types, access modifiers, etc.&lt;/p&gt;
&lt;p&gt;To facilitate testing, I&amp;#39;ll rewrite the &lt;code&gt;User&lt;/code&gt; class as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.pojo;
public class User {
    private String name;
    private int age;

    public User(){}

    private User(String name){
        this.name = name;
    }

    protected User(int age){
        this.age = age;
    }

    public User(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return &amp;quot;User{&amp;quot; +
                &amp;quot;name=&amp;#39;&amp;quot; + name + &amp;#39;\&amp;#39;&amp;#39; +
                &amp;quot;, age=&amp;quot; + age +
                &amp;#39;}&amp;#39;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the &lt;code&gt;getDeclaredConstructors()&lt;/code&gt; method in &lt;code&gt;Class&lt;/code&gt; can get an array of constructor objects for all constructors in the target class. Using &lt;code&gt;getParameterCount()&lt;/code&gt; in &lt;code&gt;Constructor&lt;/code&gt; can get the number of parameters in the corresponding constructor, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;

import java.lang.reflect.Constructor;

public class ReflectionDemo {
    public static void main(String[] args){
        Class c = User.class;
        Constructor[] cons = c.getDeclaredConstructors();
        System.out.println(&amp;quot;Total &amp;quot; + cons.length + &amp;quot; constructors&amp;quot;) ;
        for(Constructor con : cons){
            System.out.println(con + &amp;quot; Parameter count: &amp;quot; + con.getParameterCount());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757602300065_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Of course, if you only want to get constructors modified by &lt;code&gt;public&lt;/code&gt;, use the &lt;code&gt;getConstructors()&lt;/code&gt; method in &lt;code&gt;Class&lt;/code&gt;, which won&amp;#39;t be demonstrated here.&lt;/p&gt;
&lt;p&gt;So there&amp;#39;s a question: if I want to get a single specified constructor object, how do I get it? Actually, you just need to call the overloaded method &lt;code&gt;getDeclaredConstructor(Class&amp;lt;?&amp;gt;... parameterTypes)&lt;/code&gt; of &lt;code&gt;getDeclaredConstructor&lt;/code&gt;, where &lt;code&gt;parameterTypes&lt;/code&gt; only needs to pass in &lt;code&gt;ClassName.class&lt;/code&gt; or &lt;code&gt;primitive type.class&lt;/code&gt;, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;

public class ReflectionDemo {
    public static void main(String[] args) throws NoSuchMethodException {
        Class c = User.class;
        System.out.println(c.getDeclaredConstructor(int.class));
        System.out.println(c.getDeclaredConstructor(String.class, int.class));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757602790702_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Of course, if you want to get a constructor modified by &lt;code&gt;public&lt;/code&gt;, you can also use &lt;code&gt;getConstructor(Class&amp;lt;?&amp;gt;... parameterTypes)&lt;/code&gt; in &lt;code&gt;Class&lt;/code&gt; to get it, where its parameter can be empty, indicating getting a no-arg constructor modified by &lt;code&gt;public&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Note: &lt;code&gt;getDeclaredConstructors&lt;/code&gt; and &lt;code&gt;getDeclaredConstructor&lt;/code&gt; are not limited by access modifiers, while &lt;code&gt;getConstructors&lt;/code&gt; and &lt;code&gt;getConstructor&lt;/code&gt; can only get constructors modified by &lt;code&gt;public&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;(5) Creating Target Class Instance Objects Through Reflection&lt;/h3&gt;
&lt;p&gt;We&amp;#39;ve already obtained the constructor object of the target class. At this point, someone might wonder what we can do with this constructor object. Actually, we can do a lot.&lt;/p&gt;
&lt;p&gt;First, by calling the &lt;code&gt;newInstance(Object... initargs)&lt;/code&gt; method in &lt;code&gt;Constructor&lt;/code&gt;, we can instantiate an object of the target class, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Constructor con = c.getDeclaredConstructor(String.class,int.class);
        User kun = (User) con.newInstance(&amp;quot;Kun Kun&amp;quot;, 18);
        System.out.println(kun);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757603302511_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Someone might ask, isn&amp;#39;t this unnecessary? Couldn&amp;#39;t I just do &lt;code&gt;new User(&amp;quot;Kun Kun&amp;quot;,18)&lt;/code&gt; directly? Why make it so complicated?&lt;/p&gt;
&lt;p&gt;Now I&amp;#39;ll talk about something interesting. We observe that in the &lt;code&gt;User&lt;/code&gt; class, there&amp;#39;s a constructor modified by &lt;code&gt;private&lt;/code&gt;. Executing the following code would definitely cause an error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;User user = new User(&amp;quot;Kun Kun&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because &lt;code&gt;private User(String name)&lt;/code&gt; is private and cannot be directly accessed externally. However, through reflection, we can temporarily bypass access permissions to call this constructor. Call &lt;code&gt;setAccessible(boolean)&lt;/code&gt; in &lt;code&gt;Constructor&lt;/code&gt;, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Constructor con = c.getDeclaredConstructor(String.class);
        con.setAccessible(true);
        System.out.println(con.newInstance(&amp;quot;Kun Kun&amp;quot;));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note: Using &lt;code&gt;con.setAccessible(true);&lt;/code&gt; only temporarily modifies access permissions and doesn&amp;#39;t affect the target class. The constructor remains &lt;code&gt;private&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;(6) Getting Member Variables of Target Class Through Reflection&lt;/h3&gt;
&lt;p&gt;Since we can get constructor information of the target class through reflection, we can certainly also get member variable (Field) information of the target class.&lt;/p&gt;
&lt;p&gt;Actually, similar to getting constructors, getting member variables is basically similar:&lt;/p&gt;
&lt;p&gt;Calling the &lt;code&gt;getDeclaredFields()&lt;/code&gt; method in &lt;code&gt;Field&lt;/code&gt; can get all member variable information under the target class; calling &lt;code&gt;getDeclaredField(String name)&lt;/code&gt; in &lt;code&gt;Field&lt;/code&gt; can get the specified variable with variable name &lt;code&gt;name&lt;/code&gt; among member variables; calling the &lt;code&gt;getFields()&lt;/code&gt; method in &lt;code&gt;Field&lt;/code&gt; can get all member variable information modified by &lt;code&gt;public&lt;/code&gt; under the target class; calling &lt;code&gt;getField(String name)&lt;/code&gt; in &lt;code&gt;Field&lt;/code&gt; can get the specified variable with variable name &lt;code&gt;name&lt;/code&gt; among member variables modified by &lt;code&gt;public&lt;/code&gt;, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        System.out.println(c.getConstructor());

        Field[] fields = c.getDeclaredFields();
        for (Field f : fields) {
            System.out.println(f);
        }

        Field name = c.getDeclaredField(&amp;quot;name&amp;quot;);
        System.out.println(name);

        System.out.println(c.getFields().length);
        //Since no public member variables are set in User, the getField() method won&amp;#39;t be demonstrated here, you can try it yourself
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note: The parameter lists of &lt;code&gt;getDeclaredField&lt;/code&gt; and &lt;code&gt;getField&lt;/code&gt; methods cannot be empty.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;(7) Modifying and Getting Values of Member Variables of Target Class Objects Through Reflection&lt;/h3&gt;
&lt;p&gt;In step (6), we obtained member variable information of the target class. Can we modify or get the values of member variables of target class objects based on these member variable objects? The answer is yes. We just need to use the &lt;code&gt;set(Object obj, Object value)&lt;/code&gt; and &lt;code&gt;get(Object obj)&lt;/code&gt; methods in &lt;code&gt;Field&lt;/code&gt;, as follows (since member variables in &lt;code&gt;User&lt;/code&gt; are all &lt;code&gt;private&lt;/code&gt; and cannot be directly modified, we need to first call &lt;code&gt;setAccessible(true)&lt;/code&gt; in &lt;code&gt;Field&lt;/code&gt; to temporarily bypass access permissions):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Field;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Field name = c.getDeclaredField(&amp;quot;name&amp;quot;);
        name.setAccessible(true);

        User user = new User(&amp;quot;Kun Kun&amp;quot;,18);
        System.out.println(user);

        name.set(user,&amp;quot;Ge Ge&amp;quot;);
        System.out.println(user);
        System.out.println(name.get(user));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757605971267_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;(9) Getting Member Methods of Target Class Through Reflection&lt;/h3&gt;
&lt;p&gt;If you&amp;#39;ve read the above 8 sections carefully, you should be able to guess that getting member method (Method) information also mainly has four methods:&lt;/p&gt;
&lt;p&gt;They are &lt;code&gt;getDeclaredMethods&lt;/code&gt;, &lt;code&gt;getDeclaredMethod&lt;/code&gt;, &lt;code&gt;getMethods&lt;/code&gt;, and &lt;code&gt;getMethod&lt;/code&gt; in &lt;code&gt;Method&lt;/code&gt;. Their functions and precautions should be self-evident. To facilitate demonstration, I&amp;#39;ll first add several methods to &lt;code&gt;User&lt;/code&gt;, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.pojo;
public class User {
    private String name;
    private int age;

    public User(){}

    private User(String name){
        this.name = name;
    }

    protected User(int age){
        this.age = age;
    }

    public User(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return &amp;quot;User{&amp;quot; +
                &amp;quot;name=&amp;#39;&amp;quot; + name + &amp;#39;\&amp;#39;&amp;#39; +
                &amp;quot;, age=&amp;quot; + age +
                &amp;#39;}&amp;#39;;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void hello(){
        System.out.println(&amp;quot;Hello, User&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I&amp;#39;ll test in the main class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Method[] declaredMethods = c.getDeclaredMethods();
        for(Method m : declaredMethods){
            System.out.println(m);
        }

        System.out.println(c.getMethod(&amp;quot;setName&amp;quot;, String.class));

        Method[] methods = c.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }

        System.out.println(c.getDeclaredMethod(&amp;quot;hello&amp;quot;));

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757606750629_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Note: In &lt;code&gt;getDeclaredMethod&lt;/code&gt; and &lt;code&gt;getMethod&lt;/code&gt;, the first parameter is the name of the member method, and from the second parameter onward, it represents the parameter types of this member method&amp;#39;s parameter list. If there&amp;#39;s no parameter list, just write the first parameter for the above methods.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;(10) Calling Member Methods of Target Class Objects Through Reflection&lt;/h3&gt;
&lt;p&gt;Through reflection, we can also call the &lt;code&gt;invoke(target class object, parameter list)&lt;/code&gt; method in &lt;code&gt;Method&lt;/code&gt; to call member methods in target class objects, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Method;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Method setName = c.getMethod(&amp;quot;setName&amp;quot;, String.class);

        User user = (User) c.newInstance();
        System.out.println(user);

        setName.invoke(user, &amp;quot;Kun Kun&amp;quot;);
        System.out.println(user);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757607221301_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;3. The Role of Reflection&lt;/h2&gt;
&lt;p&gt;i) Basic role: Can get all components of a class and then operate on them. (Already demonstrated in the above steps)&lt;/p&gt;
&lt;p&gt;ii) Can break encapsulation. (Because it can modify access permissions of internal members of a class, it breaks class encapsulation)&lt;/p&gt;
&lt;p&gt;iii) Can bypass generic constraints, for example, the execution result of the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        ArrayList&amp;lt;String&amp;gt; a = new ArrayList&amp;lt;&amp;gt;();
        a.add(&amp;quot;hello&amp;quot;);
        Class c = a.getClass();
        Method m = c.getDeclaredMethod(&amp;quot;add&amp;quot;, Object.class);
        m.invoke(a, 123);
        System.out.println(a);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757646014301_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;4. Summary&lt;/h2&gt;
&lt;p&gt;In summary, the core knowledge points of Java reflection mainly include four aspects: &lt;strong&gt;class objects&lt;/strong&gt; (getting &lt;code&gt;Class&lt;/code&gt; objects and their information), &lt;strong&gt;constructors&lt;/strong&gt; (getting and operating class construction methods), &lt;strong&gt;member variables&lt;/strong&gt; (getting and operating class fields), and &lt;strong&gt;member methods&lt;/strong&gt; (getting and calling class methods).&lt;/p&gt;
&lt;p&gt;Many frameworks are implemented through reflection methods, such as &lt;code&gt;Spring Boot&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Through reflection, we can also design some frameworks ourselves. If I want to get member variable information of any class, I can write a relatively simple framework function, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package top.mygld.demo.test;
import top.mygld.demo.pojo.User;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        User user = new User(&amp;quot;Kun Kun&amp;quot;,18);
        getInformation(user);
    }
    public static void getInformation(Object object) throws IllegalAccessException {
        Class c = object.getClass();
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            System.out.println(field.getName() + &amp;quot;:&amp;quot; + field.get(object));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1757646240966_image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Of course, the above is just an example. This function code might not be applicable to some other classes.&lt;/p&gt;
&lt;p&gt;Why is &amp;quot;reflection&amp;quot; called &amp;quot;reflection&amp;quot;? I think that normally calling a member property or member method of an object follows the format &lt;code&gt;object.member&lt;/code&gt;, while in reflection the format is basically &lt;code&gt;member.method(object)&lt;/code&gt;, which is the reverse of the normal format, so it&amp;#39;s called &lt;code&gt;reflection&lt;/code&gt;. Of course, this is just my personal understanding. If you&amp;#39;re interested in why it&amp;#39;s really called &amp;quot;reflection&amp;quot;, you can look up some materials~&lt;/p&gt;
</content:encoded><category>reflection</category><category>Java</category><author>Glader</author></item><item><title>AI Grand Judge</title><link>https://blog.mygld.top/en/posts/aipg/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/aipg/</guid><description>A simple AI judge game based on frontend technology.</description><pubDate>Mon, 19 May 2025 07:31:53 GMT</pubDate><content:encoded>&lt;p&gt;I created a simple AI judge game purely based on frontend technology, a fun little game: &lt;a href=&quot;https://glader-judge.netlify.app/&quot;&gt;Game Link&lt;/a&gt; &lt;/p&gt;
</content:encoded><category>AI</category><category>Entertainment</category><author>Glader</author></item><item><title>Hastily Completed Graduation Project</title><link>https://blog.mygld.top/en/posts/bysj/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/bysj/</guid><description>A hastily completed graduation project.</description><pubDate>Fri, 28 Mar 2025 15:31:53 GMT</pubDate><content:encoded>&lt;p&gt;Because I needed to submit the first draft of my undergraduate thesis, I hastily completed this intelligent questionnaire system based on Vue3 + SpringBoot. Everyone is welcome to test it.&lt;/p&gt;
&lt;p&gt;The AI interface uses the balance from Silicon Flow&amp;#39;s invitation gift, which may run out at any time. If the AI generation stops working, it&amp;#39;s likely due to insufficient balance.&lt;/p&gt;
&lt;p&gt;Since it was written in a hurry, there are many bugs, &lt;del&gt;which is normal...&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;Test website: &lt;a href=&quot;http://zhw.mygld.top&quot;&gt;Graduation Project Test Site&lt;/a&gt; &lt;/p&gt;
</content:encoded><category>Graduation Project</category><category>Vue</category><category>SpringBoot</category><author>Glader</author></item><item><title>Building a Static Blog with Netlify and Astro</title><link>https://blog.mygld.top/en/posts/netlify-astro-blog/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/netlify-astro-blog/</guid><description>Building a simple static blog based on Netlify and Astro.</description><pubDate>Sun, 26 Jan 2025 17:07:51 GMT</pubDate><content:encoded>&lt;h2&gt;1. Registering for Netlify&lt;/h2&gt;
&lt;p&gt;First, open Netlify&amp;#39;s official website &lt;a href=&quot;https://app.netlify.com/&quot;&gt;Click here to visit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Choose a method to register and log in. Here I&amp;#39;ll choose GitHub login, which is the first option.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421447708_image-20250126141222970.png&quot; alt=&quot;image-20250126141222970&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then we authorize the GitHub login.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421469705_image-20250126141411824.png&quot; alt=&quot;image-20250126141411824&quot;&gt;&lt;/p&gt;
&lt;p&gt;Afterward, you&amp;#39;ll see pages asking for information including name, plan, role, etc. Fill them out roughly and check the tags.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421498412_image-20250126142026081.png&quot; alt=&quot;image-20250126142026081&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you selected the &lt;code&gt;Personal&lt;/code&gt; option in &lt;code&gt;How are you planning to use Netlify?&lt;/code&gt;, you&amp;#39;ll be asked to create a project name at the end, which will be used as an identifier in the &lt;code&gt;URL&lt;/code&gt;. You can customize this according to your preferences.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421519660_image-20250126142309986.png&quot; alt=&quot;image-20250126142352840&quot;&gt;&lt;/p&gt;
&lt;p&gt;After completion, click &lt;code&gt;Continue to deploy&lt;/code&gt;. A prompt will appear asking you to set up your first project. Click &lt;code&gt;Skip this step for now&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421520957_image-20250126142514856.png&quot; alt=&quot;image-20250126142514856&quot;&gt;&lt;/p&gt;
&lt;p&gt;After completing the above steps, you&amp;#39;ve successfully registered for Netlify.&lt;/p&gt;
&lt;h2&gt;2. Installing Astro&lt;/h2&gt;
&lt;p&gt;First, visit Astro&amp;#39;s official documentation website &lt;a href=&quot;https://docs.astro.build/zh-cn/getting-started/&quot;&gt;Click here to visit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then click on &amp;quot;Install Astro&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421542376_image-20250126150134573.png&quot; alt=&quot;image-20250126150134573&quot;&gt;&lt;/p&gt;
&lt;p&gt;On the new page, three prerequisites are shown:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421547221_image-20250126150232186.png&quot; alt=&quot;image-20250126150232186&quot;&gt;&lt;/p&gt;
&lt;p&gt;According to the documentation prerequisites, we need a Node.js environment, a text editor, and a terminal.&lt;/p&gt;
&lt;p&gt;For Node.js environment installation and configuration, you can refer to the following tutorial:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/m0_74825135/article/details/145189303&quot;&gt;Node.js Installation Tutorial, click to visit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can use the system&amp;#39;s built-in tools for the latter two. For a more comfortable coding environment, we can download and use VSCode as our text editor. Installation instructions can be found in the following tutorial:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/ZHOUPUYU/article/details/143952444&quot;&gt;VSCode Installation Tutorial, click to visit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After completing the above prerequisites, we can officially begin installing Astro.&lt;/p&gt;
&lt;p&gt;First, create a new blank folder.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421549982_image-20250126151529771.png&quot; alt=&quot;image-20250126151529771&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then type &lt;code&gt;cmd&lt;/code&gt; in the address bar and press Enter.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421556736_image-20250126151626715.png&quot; alt=&quot;image-20250126151626715&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the terminal that appears, enter the following command and type &lt;code&gt;y&lt;/code&gt; when prompted:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421574744_image-20250126152541823.png&quot; alt=&quot;image-20250126152541823&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-npm&quot;&gt;npm create astro@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After waiting a while, you&amp;#39;ll be prompted to choose a directory for the new project. You can enter &lt;code&gt;./glader-blog&lt;/code&gt;, which will create a new folder called &lt;code&gt;glader-blog&lt;/code&gt; in the current directory to store the project.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421584326_image-20250126152711786.png&quot; alt=&quot;image-20250126152711786&quot;&gt;&lt;/p&gt;
&lt;p&gt;Next, press Enter 3 times to wait for project initialization.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421582577_image-20250126152753352.png&quot; alt=&quot;image-20250126152753352&quot;&gt;&lt;/p&gt;
&lt;p&gt;When you see the following message, the project initialization is complete. You&amp;#39;ll now see a &lt;code&gt;glader-blog&lt;/code&gt; directory in your folder containing the project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421596917_image-20250126153013217.png&quot; alt=&quot;image-20250126153013217&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421596281_image-20250126153020024.png&quot; alt=&quot;image-20250126153020024&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then enter the created project and use &lt;code&gt;npm install&lt;/code&gt; to install dependencies, as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421621324_image-20250126153505274.png&quot; alt=&quot;image-20250126153505274&quot;&gt;&lt;/p&gt;
&lt;p&gt;Astro has a built-in server. After the installation is complete, run the command &lt;code&gt;npm run dev&lt;/code&gt; to start the server.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421623642_image-20250126160811452.png&quot; alt=&quot;image-20250126160811452&quot;&gt;&lt;/p&gt;
&lt;p&gt;Enter &lt;code&gt;http://localhost:4321&lt;/code&gt; in your browser to access the preview.&lt;/p&gt;
&lt;p&gt;After entering this URL in the browser, the result is as follows, indicating that the Astro project has been successfully installed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421630379_image-20250126161025204.png&quot; alt=&quot;image-20250126161025204&quot;&gt;&lt;/p&gt;
&lt;h2&gt;3. Getting Familiar with the Astro Development Environment&lt;/h2&gt;
&lt;p&gt;For convenient code writing, we&amp;#39;ll use the &lt;code&gt;VSCode&lt;/code&gt; text editor. First, open &lt;code&gt;VSCode&lt;/code&gt; and use it to open the newly created project, &lt;code&gt;glader-blog&lt;/code&gt;, as shown below. A prompt to install the Astro extension will appear in the bottom right corner - click install:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421632300_image-20250126161445199.png&quot; alt=&quot;image-20250126161445199&quot;&gt;&lt;/p&gt;
&lt;p&gt;If the prompt doesn&amp;#39;t appear, you can click the extensions icon and manually search and install:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421635189_image-20250126161611608.png&quot; alt=&quot;image-20250126161611608&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421638243_image-20250126161657370.png&quot; alt=&quot;image-20250126161657370&quot;&gt;&lt;/p&gt;
&lt;p&gt;After installation, return to the project directory. You&amp;#39;ll find a new &lt;code&gt;npm&lt;/code&gt; scripts option in the lower left corner. Clicking on items here will help us run npm scripts. For example, there&amp;#39;s a &lt;code&gt;dev&lt;/code&gt; option - clicking it is equivalent to running &lt;code&gt;npm run dev&lt;/code&gt; in the project directory.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421647329_image-20250126161756711.png&quot; alt=&quot;image-20250126161756711&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421654641_image-20250126162817282.png&quot; alt=&quot;image-20250126162817282&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421668628_image-20250126162829988.png&quot; alt=&quot;image-20250126162829988&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the effect is the same as entering the command directly in the command window.&lt;/p&gt;
&lt;h2&gt;4. Writing a Simple Blog&lt;/h2&gt;
&lt;p&gt;In the project directory under &lt;code&gt;src/pages/&lt;/code&gt;, there&amp;#39;s an &lt;code&gt;index.astro&lt;/code&gt; file, which is the website&amp;#39;s homepage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421671425_image-20250126163327161.png&quot; alt=&quot;image-20250126163327161&quot;&gt;&lt;/p&gt;
&lt;p&gt;We can try deleting all the code in this file and rewrite a piece of code as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
    &amp;lt;link rel=&amp;quot;icon&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; href=&amp;quot;/favicon.svg&amp;quot; /&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width&amp;quot; /&amp;gt;
    &amp;lt;meta name=&amp;quot;generator&amp;quot; content={Astro.generator} &amp;gt;
    &amp;lt;title&amp;gt;Astro&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Astro&amp;lt;/h1&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can see in the browser that the webpage content has changed to what we wrote in the code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421684815_image-20250126163611792.png&quot; alt=&quot;image-20250126163611792&quot;&gt;&lt;/p&gt;
&lt;p&gt;Astro is essentially a static site generator that is compatible with native HTML code. For demonstration purposes, let&amp;#39;s write a rough blog homepage. The code is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;
    &amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;ie=edge&amp;quot;&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/styles.css&amp;quot;&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;!-- Navigation Bar --&amp;gt;
    &amp;lt;header&amp;gt;
      &amp;lt;nav&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/&amp;quot;&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/about&amp;quot;&amp;gt;About Me&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/posts&amp;quot;&amp;gt;Blog Posts&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/contact&amp;quot;&amp;gt;Contact Us&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/nav&amp;gt;
    &amp;lt;/header&amp;gt;

    &amp;lt;!-- Main Content Area --&amp;gt;
    &amp;lt;main&amp;gt;
      &amp;lt;section class=&amp;quot;intro&amp;quot;&amp;gt;
        &amp;lt;h1&amp;gt;Welcome to My Blog!&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;Here I record some thoughts and shares about programming, technology, and life.&amp;lt;/p&amp;gt;
      &amp;lt;/section&amp;gt;

      &amp;lt;!-- Latest Articles List --&amp;gt;
      &amp;lt;section class=&amp;quot;posts&amp;quot;&amp;gt;
        &amp;lt;h2&amp;gt;Latest Articles&amp;lt;/h2&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;&amp;quot;&amp;gt;Test Article 1&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/section&amp;gt;
    &amp;lt;/main&amp;gt;

    &amp;lt;!-- Footer --&amp;gt;
    &amp;lt;footer&amp;gt;
      &amp;lt;p&amp;gt;&amp;amp;copy; 2025 My Blog. All Rights Reserved.&amp;lt;/p&amp;gt;
    &amp;lt;/footer&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421690090_image-20250126164447301.png&quot; alt=&quot;image-20250126165954940&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now, if we want to write a blog in &lt;code&gt;Markdown&lt;/code&gt; format, create a new folder called &lt;code&gt;posts&lt;/code&gt; under &lt;code&gt;src/pages/&lt;/code&gt; to store our articles. Then create &lt;code&gt;post-1.md&lt;/code&gt; under &lt;code&gt;src/pages/posts/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421752871_image-20250126170422874.png&quot; alt=&quot;image-20250126170422874&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# My First Blog Post

Published: 2022-07-01

Welcome to my new blog about learning Astro! Here, I&amp;#39;ll share my learning journey in building a new website.

## What I Did

1. **Installed Astro**: First, I created a new Astro project and set up my online accounts.

2. **Made Pages**: Then I learned how to make pages by creating new `.astro` files and saving them in the `src/pages/` folder.

3. **Published Blog Posts**: This is my first blog post! I now have pages written with Astro and articles written in Markdown!

## Next Steps

I will complete the Astro tutorial and then continue writing more content. Follow me for more information.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After writing the blog, add a hyperlink to &amp;quot;Test Article 1&amp;quot; in &lt;code&gt;index.astro&lt;/code&gt; that links to the markdown article we just created:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421762744_image-20250126170355396.png&quot; alt=&quot;image-20250126170355396&quot;&gt;&lt;/p&gt;
&lt;p&gt;Note: Don&amp;#39;t add .md, just write the file name. That is, don&amp;#39;t write &lt;code&gt;/posts/post-1.md&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now, clicking on &lt;code&gt;Test Article 1&lt;/code&gt; in the webpage will display our blog content:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421778675_image-20250126170559846.png&quot; alt=&quot;image-20250126170559846&quot;&gt;&lt;/p&gt;
&lt;p&gt;For more syntax, components, and related content, readers can learn on their own at the &lt;a href=&quot;https://docs.astro.build/zh-cn/tutorial/0-introduction/&quot;&gt;Astro Development Documentation Official Website&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;5. Uploading the Project to GitHub&lt;/h2&gt;
&lt;p&gt;To upload the project to GitHub, you first need to download and install &lt;code&gt;Git&lt;/code&gt;. The installation method can be found in the following tutorial:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/weixin_63701294/article/details/143861827&quot;&gt;Git Installation Tutorial&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After installing Git, we need to configure Git for VSCode and upload the project we just wrote to a GitHub repository. The specific tutorial can be found here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/m0_51185558/article/details/126181439&quot;&gt;VSCode Git Configuration and Code Upload to GitHub Tutorial&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then click publish branch, and then click upload to private repository.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421782733_image-20250126172016705.png&quot; alt=&quot;image-20250126172016705&quot;&gt;&lt;/p&gt;
&lt;p&gt;After uploading, we&amp;#39;ll have such a private repository on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421791351_image-20250126173832736.png&quot; alt=&quot;image-20250126173832736&quot;&gt;&lt;/p&gt;
&lt;p&gt;Additionally, when we need to modify our code, we can also commit changes here:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421794872_image-20250126174055339.png&quot; alt=&quot;image-20250126174055339&quot;&gt;&lt;/p&gt;
&lt;p&gt;At this point, we can edit our blog locally and then use Git to save our code to GitHub in real-time.&lt;/p&gt;
&lt;h2&gt;6. Hosting the Code on Netlify&lt;/h2&gt;
&lt;p&gt;Open the Netlify website again and log in to the account we just registered. Click &lt;code&gt;Import from Git&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421803647_image-20250126174502965.png&quot; alt=&quot;image-20250126174502965&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then select GitHub:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421801345_image-20250126174530132.png&quot; alt=&quot;image-20250126174530132&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the new window that appears, authorize the operation and select the repository to upload:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421811147_image-20250126174725799.png&quot; alt=&quot;image-20250126174725799&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then click &lt;code&gt;Install&lt;/code&gt; to install. A password prompt will appear - enter your GitHub password and click confirm.&lt;/p&gt;
&lt;p&gt;Now as shown below, the upload is successful. Click on the repository to configure it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421813119_image-20250126174935190.png&quot; alt=&quot;image-20250126174935190&quot;&gt;&lt;/p&gt;
&lt;p&gt;We just need to give it a name in Site name. This name will later be included in the domain we visit. Leave the rest blank and click &lt;code&gt;Deploy&lt;/code&gt; directly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421817068_image-20250126175747946.png&quot; alt=&quot;image-20250126175747946&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then we just wait for deployment, which may take a few minutes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421825570_image-20250126180043018.png&quot; alt=&quot;image-20250126180043018&quot;&gt;&lt;/p&gt;
&lt;p&gt;After deployment is complete, the result is as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421825129_image-20250126180215411.png&quot; alt=&quot;image-20250126180215411&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now we can visit the website &lt;code&gt;https://glader.netlify.app&lt;/code&gt; to display our blog.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.mygld.top/file/1765421878678_image-20250126180340011.png&quot; alt=&quot;image-20250126180340011&quot;&gt;&lt;/p&gt;
&lt;p&gt;At this point, our blog deployment is complete.&lt;/p&gt;
&lt;p&gt;Additionally, on GitHub, many people have already developed many easy-to-use blog themes based on the Astro framework. The operations we described above are merely to learn the basic usage of Astro. In actual blog creation, we can go to GitHub to choose a blog theme template we like and deploy it directly.&lt;/p&gt;
</content:encoded><category>NetLify</category><category>Astro</category><category>Blog</category><author>Glader</author></item><item><title>Domain Migration</title><link>https://blog.mygld.top/en/posts/update-domain/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/update-domain/</guid><description>Migrated the blog to: blog.mygld.top.</description><pubDate>Mon, 13 Jan 2025 02:34:50 GMT</pubDate><content:encoded>&lt;p&gt;Migrated the blog to: &lt;a href=&quot;https://blog.mygld.top&quot;&gt;blog.mygld.top&lt;/a&gt;. &lt;/p&gt;
</content:encoded><category>Domain</category><author>Glader</author></item><item><title>SpringBoot Knowledge Points Summary (Part 2)</title><link>https://blog.mygld.top/en/posts/springboot-02/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/springboot-02/</guid><description>SpringBoot knowledge points summary (Part 2)</description><pubDate>Wed, 01 Jan 2025 18:10:10 GMT</pubDate><content:encoded>&lt;h2&gt;YAML Configuration&lt;/h2&gt;
&lt;p&gt;In addition to the content summarized in Part 1, YAML has other syntax. For example, you can continue parameter references in YAML, as shown in the following example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Bob
person:
  name: ${name}
  age: 20
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Value(&amp;quot;${person.name}&amp;quot;)
private String name;
@Value(&amp;quot;${person.age}&amp;quot;)
private int age;

@Test
void test2(){
	System.out.println(name + &amp;quot;:&amp;quot; + age);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running, the output result is &lt;code&gt;Bob:20&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This shows that in YAML, you can continue parameter references. &lt;code&gt;person.name&lt;/code&gt; is assigned the value of &lt;code&gt;name&lt;/code&gt;, which is &lt;code&gt;Bob&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, arrays in YAML can also be written in inline array format, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;list: [hello,world]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;YAML also has an important knowledge point: scalars. For example, if we now define the following parameters in YAML:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;msg1: &amp;#39;hello\n world&amp;#39; #Does not recognize escape characters, outputs as-is
msg2: &amp;quot;hello\n world&amp;quot; #Recognizes escape characters
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only difference between the above parameters is that &lt;code&gt;msg1&lt;/code&gt; uses single quotes, while &lt;code&gt;msg2&lt;/code&gt; uses double quotes. We write the following code for testing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Value(&amp;quot;${msg1}&amp;quot;)
private String msg1;
@Value(&amp;quot;${msg2}&amp;quot;)
private String msg2;

@Test
void test3(){
	System.out.println(msg1);
	System.out.println(msg2);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output result is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tex&quot;&gt;hello\n world
hello 
 world
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows that if we use single quotes to quote strings, escape characters are not recognized and output as-is; if we use double quotes, escape characters are recognized.&lt;/p&gt;
&lt;p&gt;So think about it, if we define:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;msg3: hello\n world
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What effect will it have? After my experiment, the effect is actually the same as adding single quotes - escape characters are not recognized.&lt;/p&gt;
&lt;p&gt;Additionally, even if we don&amp;#39;t use the &lt;code&gt;@Value&lt;/code&gt; and &lt;code&gt;@ConfigurationProperties&lt;/code&gt; annotations, there are other ways to get parameters from the configuration in the code at any time. We can use the &lt;code&gt;getProperty(parameter_name)&lt;/code&gt; method in the &lt;code&gt;Environment&lt;/code&gt; class, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired
private Environment env; //import org.springframework.core.env.Environment;

@Test
void test4(){
	System.out.println(env.getProperty(&amp;quot;msg1&amp;quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The parameter name can be used directly as a string, without adding &lt;code&gt;$&lt;/code&gt; or &lt;code&gt;{}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Environment&lt;/code&gt; should import &lt;code&gt;org.springframework.core.env.Environment;&lt;/code&gt; - don&amp;#39;t import the wrong one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Profile Usage&lt;/h2&gt;
&lt;p&gt;When developing SpringBoot applications, the same set of programs is usually installed in different environments, such as: development, testing, production, etc. The database address, server port, and other configurations are all different. If the configuration file has to be modified every time it&amp;#39;s packaged, it would be very troublesome. The profile function is used for dynamic configuration switching.&lt;/p&gt;
&lt;h3&gt;Multiple Profile Files Method&lt;/h3&gt;
&lt;p&gt;As shown in the figure below, I now create the following three YAML files:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i1.wp.com/dev.ruom.top/i/2025/02/16/531394.webp&quot; alt=&quot;image-20250216225659712&quot;&gt;&lt;/p&gt;
&lt;p&gt;I write in the &lt;code&gt;application-dev.yml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;server:
  port: 8082
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I write in the &lt;code&gt;application-test.yml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;server:
  port: 8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I don&amp;#39;t write anything in &lt;code&gt;application.yml&lt;/code&gt;, the result after running is as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i1.wp.com/dev.ruom.top/i/2025/02/16/609475.webp&quot; alt=&quot;image-20250216225901594&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can see that no Profile is currently set, and it&amp;#39;s running on port 8080.&lt;/p&gt;
&lt;p&gt;If I then write the following statement in &lt;code&gt;application.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spring:
  profiles:
    active: dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result after running is as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i1.wp.com/dev.ruom.top/i/2025/02/16/342994.webp&quot; alt=&quot;image-20250216230054561&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can see that the Profile is now set, and &lt;code&gt;dev&lt;/code&gt; is currently active. The project is running on port 8082, which means the configuration in &lt;code&gt;application-dev.yml&lt;/code&gt; has taken effect. Similarly, if I change &lt;code&gt;application.yml&lt;/code&gt; to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spring:
  profiles:
    active: test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the project will run on port 8081, switching to activate the &lt;code&gt;test&lt;/code&gt; Profile, meaning the configuration in &lt;code&gt;application-test.yml&lt;/code&gt; takes effect.&lt;/p&gt;
&lt;p&gt;Through this method, we can quickly switch configurations suitable for different environments.&lt;/p&gt;
&lt;h3&gt;YAML Multi-Document Method&lt;/h3&gt;
&lt;p&gt;In the multiple Profile files method, we found that we need to create multiple &lt;code&gt;yml&lt;/code&gt; or &lt;code&gt;properties&lt;/code&gt; files, which is somewhat troublesome. So can we write configurations for different environments in the same configuration file, separated by special symbols? The answer is yes, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
server:
  port: 8081
spring:
  profiles: test
---
server:
  port: 8082
spring:
  profiles: dev
---
spring:
  profiles:
    active: dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Different configuration files only need to be separated by &lt;code&gt;---&lt;/code&gt;. &lt;/p&gt;
</content:encoded><category>SpringBoot</category><category>yml</category><category>profile</category><author>Glader</author></item><item><title>SpringBoot Knowledge Points Summary (Part 1)</title><link>https://blog.mygld.top/en/posts/springboot-01/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/springboot-01/</guid><description>SpringBoot knowledge points summary (Part 1)</description><pubDate>Fri, 27 Dec 2024 06:18:10 GMT</pubDate><content:encoded>&lt;h2&gt;YAML Configuration and Retrieval in SpringBoot&lt;/h2&gt;
&lt;p&gt;In the &lt;code&gt;application.yml&lt;/code&gt; file, custom configurations can be written as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;student:
  name: Xiao Kun
  age: 18
  hobbies:
    - Singing
    - Dancing
    - Rap
    - Basketball
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note: Indentation between levels must be aligned, and there must be a space after the colon before writing the value. If it&amp;#39;s an array, each line after a line break is a value, preceded by &lt;code&gt;-&lt;/code&gt;, and there must also be a space after &lt;code&gt;-&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Method 1 to retrieve configuration: Use &lt;code&gt;@Value(&amp;quot;${property}&amp;quot;)&lt;/code&gt;, applied in the &lt;code&gt;pojo&lt;/code&gt; entity class as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Data
@NoArgsConstructor
@Component
public class Student {
    @Value(&amp;quot;${student.name}&amp;quot;)
    private String name;
    @Value(&amp;quot;${student.age}&amp;quot;)
    private Integer age;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Method 2 to retrieve configuration: Use &lt;code&gt;@ConfigurationProperties(prefix = &amp;quot;prefix&amp;quot;)&lt;/code&gt;, applied in the &lt;code&gt;pojo&lt;/code&gt; entity class as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Data
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = &amp;quot;student&amp;quot;)
public class Student {
    private String name;
    private Integer age;
    private List&amp;lt;String&amp;gt; hobbies;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note: If using Method 2, the property names in the &lt;code&gt;pojo&lt;/code&gt; entity class must be exactly the same as the names in the &lt;code&gt;yml&lt;/code&gt; configuration file.&lt;/li&gt;
&lt;li&gt;Note: &lt;code&gt;@Value&lt;/code&gt; only supports simple string parsing and cannot parse complex data structures like &lt;code&gt;List&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SpringBoot Integration with MyBatis&lt;/h2&gt;
&lt;p&gt;The MyBatis starter dependency is as follows, add it to &lt;code&gt;pom.xml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
     &amp;lt;groupId&amp;gt;org.mybatis.spring.boot&amp;lt;/groupId&amp;gt;
     &amp;lt;artifactId&amp;gt;mybatis-spring-boot-starter&amp;lt;/artifactId&amp;gt;
     &amp;lt;version&amp;gt;3.0.3&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also introduce the MySQL driver dependency as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.mysql&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;mysql-connector-j&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Write the data source configuration in the &lt;code&gt;application.yml&lt;/code&gt; configuration file as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/{database_name}
    username: {username}
    password: {password}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suppose we now have a database named &lt;code&gt;mybatis&lt;/code&gt; with a table &lt;code&gt;user&lt;/code&gt; as follows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;age&lt;/th&gt;
&lt;th&gt;gender&lt;/th&gt;
&lt;th&gt;phone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Xiao Kun&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;18800000001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Xiao Xu&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;18800000002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Xiao Cai&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;18800000003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Xiao Cai&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;18800000004&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Xiao Ji&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;18800000005&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Xiao Mei&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;18800000006&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Next, we need to create a &lt;code&gt;pojo&lt;/code&gt; entity class &lt;code&gt;User&lt;/code&gt; with the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Short age;
    private Integer gender;
    private String phone;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then create an interface &lt;code&gt;UserMapper&lt;/code&gt; under the &lt;code&gt;mapper&lt;/code&gt; package as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Mapper
public interface UserMapper {
    @Select(&amp;quot;select * from user where id = #{id}&amp;quot;)
    public User selectUserById(Integer id);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note: Don&amp;#39;t forget to add &lt;code&gt;@Mapper&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, create an interface &lt;code&gt;UserService&lt;/code&gt; under the &lt;code&gt;service&lt;/code&gt; package as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface UserService {
    public User selectUserById(Integer id);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, under the &lt;code&gt;impl&lt;/code&gt; package in the &lt;code&gt;service&lt;/code&gt; package, create an implementation class &lt;code&gt;UserServiceImpl&lt;/code&gt; that implements the &lt;code&gt;UserService&lt;/code&gt; interface and add the &lt;code&gt;@Service&lt;/code&gt; annotation to hand it over to the &lt;code&gt;IOC&lt;/code&gt; container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User selectUserById(Integer id) {
        return userMapper.selectUserById(id);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, create &lt;code&gt;UserController&lt;/code&gt; under the &lt;code&gt;controller&lt;/code&gt; package with the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RestController
public class UserController {
    
    @Autowired
    private UserService userService;

    @RequestMapping(&amp;quot;/selectUserById&amp;quot;)
    public User selectUserById(Integer id) {
        return userService.selectUserById(id);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note: The above operations belong to the MVC architecture.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, run the project and enter &lt;code&gt;http://localhost:8080/selectUserById?id=1&lt;/code&gt; in the browser to see the effect. The browser will output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&amp;quot;id&amp;quot;:1,&amp;quot;name&amp;quot;:&amp;quot;Xiao Kun&amp;quot;,&amp;quot;age&amp;quot;:19,&amp;quot;gender&amp;quot;:1,&amp;quot;phone&amp;quot;:&amp;quot;18800000001&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>SpringBoot</category><category>MyBatis</category><author>Glader</author></item><item><title>Testing LaTeX Rendering</title><link>https://blog.mygld.top/en/posts/test-latex/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/test-latex/</guid><description>Testing if LaTeX formulas can render properly</description><pubDate>Thu, 26 Dec 2024 07:22:30 GMT</pubDate><content:encoded>&lt;p&gt;This article is only used to test if LaTeX formulas can render properly. The test content is generated by AI:&lt;/p&gt;
&lt;h2&gt;1. Inline Formulas&lt;/h2&gt;
&lt;p&gt;For example, we can write the famous Einstein mass-energy formula: $E=mc^2$, which describes the relationship between energy and mass.&lt;/p&gt;
&lt;h2&gt;2. Display Formulas&lt;/h2&gt;
&lt;p&gt;For quadratic equations:
$$
ax^2 + bx + c = 0
$$
The quadratic formula is:
$$
x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}
$$&lt;/p&gt;
&lt;h2&gt;3. Summation and Limits&lt;/h2&gt;
&lt;p&gt;Classic result of infinite series:
$$
\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}
$$
Also, let&amp;#39;s test the limit formula:
$$
\lim_{x\to 0} \frac{\sin x}{x} = 1
$$&lt;/p&gt;
&lt;h2&gt;4. Matrices and Special Formulas&lt;/h2&gt;
&lt;p&gt;Here&amp;#39;s an example of a 2x2 matrix:
$$
\mathbf{A} =
\begin{bmatrix}
a &amp;amp; b \
c &amp;amp; d
\end{bmatrix}
$$
And the world-famous Euler&amp;#39;s formula:
$$
e^{i\pi} + 1 = 0
$$ &lt;/p&gt;
</content:encoded><category>test</category><category>latex</category><author>Glader</author></item><item><title>Script Development for Converting Markdown and LaTeX to Images</title><link>https://blog.mygld.top/en/posts/markdown-to-image/</link><guid isPermaLink="true">https://blog.mygld.top/en/posts/markdown-to-image/</guid><description>A script to convert Markdown formatted text generated by ChatGPT into images.</description><pubDate>Mon, 09 Dec 2024 23:54:53 GMT</pubDate><content:encoded>&lt;p&gt;A few days ago, I wrote a QQ bot for fun and connected it to a third-party AI interface. Although there are plugins in LiteLoaderQQNT that can render Markdown and LaTeX formats, most people do not have these plugins. So, I wondered if I could write a script to convert text containing Markdown and LaTeX into images, and here is what I did.&lt;/p&gt;
&lt;h2&gt;Implementation Idea&lt;/h2&gt;
&lt;p&gt;After researching, I found that the following steps can be used:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Convert the generated text into an HTML page;&lt;/li&gt;
&lt;li&gt;Use MathJax to render the LaTeX format in it;&lt;/li&gt;
&lt;li&gt;Take a screenshot of the page and save it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;JavaScript Script: Webpage Screenshot&lt;/h2&gt;
&lt;p&gt;First, write a JS script to take a screenshot of a given HTML page. The code is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const puppeteer = require(&amp;#39;puppeteer&amp;#39;);
const path = require(&amp;#39;path&amp;#39;);
const fs = require(&amp;#39;fs&amp;#39;);

// Get input and output paths from command line arguments
const args = process.argv.slice(2);
const inputPath = args[0];
const outputPath = args[1];

// Check if arguments are provided
if (!inputPath || !outputPath) {
  console.error(&amp;#39;Usage: node script.js &amp;lt;input path&amp;gt; &amp;lt;output path&amp;gt;&amp;#39;);
  process.exit(1);
}

// Resolve input path to absolute path
const resolvedInputPath = path.resolve(__dirname, inputPath);

// Check if input file exists
if (!fs.existsSync(resolvedInputPath)) {
  console.error(&amp;#39;Input file not found:&amp;#39;, resolvedInputPath);
  process.exit(1);
}

(async () =&amp;gt; {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setViewport({ width: 1200, height: 100 });

  // Open the page
  await page.goto(&amp;#39;file://&amp;#39; + resolvedInputPath, { waitUntil: &amp;#39;networkidle0&amp;#39; });

  // Try to wait for MathJax to finish rendering, wait up to 2 seconds
  try {
    await page.waitForFunction(
      &amp;#39;window.MathJax &amp;amp;&amp;amp; window.MathJax.Hub &amp;amp;&amp;amp; window.MathJax.Hub.getAllJax().length &amp;gt; 0&amp;#39;,
      { timeout: 2000 }
    );
  } catch (e) {
    console.log(&amp;#39;MathJax not fully loaded, but continue to generate image...&amp;#39;);
  }

  // Take a screenshot and save to the specified output path
  const resolvedOutputPath = path.resolve(__dirname, outputPath);
  await page.screenshot({ path: resolvedOutputPath, fullPage: true });

  console.log(&amp;#39;Screenshot Successfully:&amp;#39;, resolvedOutputPath);

  await browser.close();
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the above code as &lt;code&gt;screenshot.js&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Environment Setup&lt;/h3&gt;
&lt;p&gt;The above code depends on Node.js. First, configure the Node.js environment, and then install the Puppeteer package in the same folder with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install puppeteer
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Python Script: Markdown to HTML&lt;/h2&gt;
&lt;p&gt;Create a new Python program &lt;code&gt;md2img.py&lt;/code&gt; in the same folder to convert Markdown and LaTeX to images.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import markdown
from markdown.extensions import Extension
from markdown.treeprocessors import Treeprocessor

# MathJax
class MathJaxExtension(Extension):
    def extendMarkdown(self, md):
        md.treeprocessors.register(MathJaxProcessor(md), &amp;#39;mathjax&amp;#39;, 175)

class MathJaxProcessor(Treeprocessor):
    def run(self, root):
        for element in root.iter():
            if element.tag == &amp;#39;span&amp;#39; and &amp;#39;class&amp;#39; in element.attrib and &amp;#39;math&amp;#39; in element.attrib[&amp;#39;class&amp;#39;]:
                element.tag = &amp;#39;script&amp;#39;
                element.attrib[&amp;#39;type&amp;#39;] = &amp;#39;math/tex&amp;#39;
                element.text = element.text

def replace_brackets(input_string):
    input_string = input_string.replace(&amp;#39;\\[&amp;#39;, &amp;#39;?gzl?&amp;#39;)
    input_string = input_string.replace(&amp;#39;\\]&amp;#39;, &amp;#39;?gzr?&amp;#39;)
    input_string = input_string.replace(&amp;#39;\\(&amp;#39;, &amp;#39;?gxl?&amp;#39;)
    input_string = input_string.replace(&amp;#39;\\)&amp;#39;, &amp;#39;?gxr?&amp;#39;)
    return input_string

def covert_brackets(input_string):
    input_string = input_string.replace(&amp;#39;?gzl?&amp;#39;, &amp;#39;\\[&amp;#39;)
    input_string = input_string.replace(&amp;#39;?gzr?&amp;#39;, &amp;#39;\\]&amp;#39;)
    input_string = input_string.replace(&amp;#39;?gxl?&amp;#39;, &amp;#39;\\(&amp;#39;)
    input_string = input_string.replace(&amp;#39;?gxr?&amp;#39;, &amp;#39;\\)&amp;#39;)
    return input_string

def convert_markdown_to_html_with_latex(md_text):
    md = markdown.Markdown(extensions=[&amp;#39;codehilite&amp;#39;, &amp;#39;fenced_code&amp;#39;, MathJaxExtension()])
    html = md.convert(md_text)
    html = covert_brackets(html)
    return f&amp;quot;&amp;quot;&amp;quot;
    &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;
        &amp;lt;script type=\&amp;quot;text/javascript\&amp;quot; src=\&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML\&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;link rel=\&amp;quot;stylesheet\&amp;quot; href=\&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css\&amp;quot;&amp;gt;
        &amp;lt;script src=\&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js\&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script&amp;gt;hljs.highlightAll();&amp;lt;/script&amp;gt;
        &amp;lt;style&amp;gt;body {{ font-size: 30px; }}&amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        {html}
    &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
    &amp;quot;&amp;quot;&amp;quot;

def markdown_to_html(md_text, html_out_path):
    html_output = convert_markdown_to_html_with_latex(replace_brackets(md_text))
    with open(html_out_path, &amp;#39;w&amp;#39;, encoding=&amp;#39;utf-8&amp;#39;) as f:
        f.write(html_output)
    print(&amp;quot;HTML has finished.&amp;quot;)

def html_to_image(html_path, img_path):
    import subprocess
    import os

    html_path = os.path.abspath(html_path)
    img_path = os.path.abspath(img_path)

    if not os.path.exists(html_path):
        raise FileNotFoundError(f&amp;quot;HTML file not found: {html_path}&amp;quot;)

    node_command = [&amp;quot;node&amp;quot;, &amp;quot;screenshot.js&amp;quot;, html_path, img_path]

    try:
        result = subprocess.run(
            node_command,
            check=True,
            text=True,
            capture_output=True
        )
        print(&amp;quot;screenshot.js output:&amp;quot;, result.stdout)
    except subprocess.CalledProcessError as e:
        print(&amp;quot;screenshot.js failed:&amp;quot;, e.stderr)
        raise

    if not os.path.exists(img_path):
        raise FileNotFoundError(f&amp;quot;Image generation failed: {img_path}&amp;quot;)

    print(f&amp;quot;Image generated successfully: {img_path}&amp;quot;)

import os
import time

def markdown_to_image(md_text, img_out_path):
    tmp_dir = os.path.join(os.getcwd(), &amp;#39;tmp&amp;#39;, &amp;#39;cache&amp;#39;)
    os.makedirs(tmp_dir, exist_ok=True)
    timestamp = int(time.time())
    html_file_path = os.path.join(tmp_dir, f&amp;quot;temp_{timestamp}.html&amp;quot;)
    markdown_to_html(md_text, html_file_path)
    try:
        html_to_image(html_file_path, img_out_path)
        print(f&amp;quot;Image generated successfully: {img_out_path}&amp;quot;)
    except Exception as e:
        print(f&amp;quot;Image generation failed: {e}&amp;quot;)
        raise
    finally:
        if os.path.exists(html_file_path):
            os.remove(html_file_path)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Environment Setup&lt;/h3&gt;
&lt;p&gt;The above Python program requires the &lt;code&gt;markdown&lt;/code&gt; package. Install it with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install markdown
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Usage Example&lt;/h2&gt;
&lt;p&gt;Create a new &lt;code&gt;test.py&lt;/code&gt; and call &lt;code&gt;md2img.markdown_to_image(md_text, img_out_path)&lt;/code&gt; in it to convert Markdown and LaTeX to an image.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parameter description:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;md_text&lt;/code&gt; means the text containing Markdown and LaTeX;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;img_out_path&lt;/code&gt; means the output path of the generated image.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Markdown</category><category>LaTeX</category><category>Image</category><author>Glader</author></item></channel></rss>