2024年11月

Spring IOC(Inversion of Control,控制反转)依赖注入是 Spring 框架的核心特性之一,旨在实现对象之间的松耦合,提升代码的可维护性、可测试性和可扩展性。下面我们将从以下几个方面深入探讨 Spring IOC 依赖注入的机制和实现原理。

一、基本概念

  1. 控制反转(Inversion of Control)

    控制反转是一种设计原则,指的是将对象的创建和管理职责从应用程序中转移到框架中。传统上,应用程序直接创建依赖对象,而在 IOC 中,控制权由容器掌握,应用只需声明所需的依赖。

  2. 依赖注入(Dependency Injection)

    依赖注入是实现控制反转的一种具体方式,通过构造函数、setter 方法或字段注入,将依赖的对象传递给使用它的对象。

二、依赖注入的实现方式

Spring 提供了三种主要的依赖注入方式:

  1. 构造器注入

    通过构造函数传入依赖对象,通常适用于需要强制依赖的情况。

    示例:

    public class UserService {
        private final UserRepository userRepository;
    
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    
  2. Setter 注入

    通过 setter 方法设置依赖对象,适合可选依赖的场景。

    示例:

    public class UserService {
        private UserRepository userRepository;
    
        public void setUserRepository(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    
  3. 字段注入

    直接在字段上使用
    @Autowired
    注解,Spring 会自动注入依赖。此方式适用于简单场景,但不利于单元测试。

    示例:

    public class UserService {
        @Autowired
        private UserRepository userRepository;
    }
    

三、Spring IOC 容器的工作原理

  1. Bean 定义

    在 Spring 中,每个被管理的对象称为 Bean。Bean 的定义通常通过 XML 配置文件或 Java 注解(如
    @Component
    ,
    @Service
    ,
    @Repository
    ,
    @Controller
    等)来描述。

  2. 容器初始化

    当 Spring 容器启动时,它会加载所有的 Bean 定义,创建 Bean 实例,并解析其依赖关系。依赖关系解析的过程主要分为以下几步:


    • Bean 创建
      :通过反射机制创建 Bean 实例。
    • 依赖解析
      :根据配置确定 Bean 的依赖关系,并实例化其依赖 Bean。
    • 注入依赖
      :将依赖对象注入到目标 Bean 中。
  3. 生命周期管理

    Spring 容器管理 Bean 的生命周期,包括创建、初始化、销毁等过程。开发者可以通过实现
    InitializingBean

    DisposableBean
    接口或使用
    @PostConstruct

    @PreDestroy
    注解来控制 Bean 的生命周期。

四、依赖注入的优势

  1. 松耦合

    通过依赖注入,类之间不再直接依赖于具体实现,而是依赖于接口或抽象类,这减少了类之间的耦合度。

  2. 可测试性

    由于依赖关系通过构造函数或 setter 方法注入,方便进行单元测试。在测试时可以使用模拟对象(Mock)来替代真实的依赖。

  3. 灵活性和可扩展性

    可以通过更改配置来替换实现,而不需要修改使用该依赖的代码,增强了系统的灵活性。

五、依赖注入的使用场景

  1. 服务层与数据访问层

    在 Web 应用中,服务层通常依赖于数据访问层的接口,通过依赖注入,可以轻松实现服务与数据访问之间的解耦。

  2. 配置与环境管理

    可以将环境相关的配置(如数据库连接、消息队列等)抽象为 Bean,通过依赖注入使不同环境下的配置可以灵活切换。

  3. 增强模块化

    在大型企业应用中,通过依赖注入可以实现模块间的清晰分离,使得各模块可以独立开发和测试。

六、示例代码

以下是一个使用 Spring IOC 依赖注入的简单示例:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

@Component
class UserRepository {
    public void save() {
        System.out.println("User saved!");
    }
}

@Component
class UserService {
    private final UserRepository userRepository;

    // 构造器注入
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void registerUser() {
        userRepository.save();
    }
}

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("com.example"); // 包名
        UserService userService = context.getBean(UserService.class);
        userService.registerUser(); // 输出 "User saved!"
    }
}

七、深入的设计考虑

  1. AOP(面向切面编程)集成

    Spring IOC 依赖注入与 AOP 结合,可以在不修改业务逻辑的情况下,为 Bean 增加横切关注点(如事务管理、日志记录等)。

  2. Bean 的作用域

    Spring 支持多种 Bean 作用域,如单例(singleton)、原型(prototype)、请求(request)、会话(session)等,开发者可以根据需要选择合适的作用域。

  3. 条件注入

    Spring 允许通过条件注解(如
    @Conditional
    )来实现基于环境或条件的依赖注入,提高配置的灵活性。

  4. 性能考虑

    虽然依赖注入带来了灵活性,但在高性能场景下,要注意对象创建的开销。可以使用单例 Bean 或者通过静态工厂方法来优化性能。

2023年10月份写过一篇《
本计划在 .NET 8 中推出的 WASI 推迟到 .NET 9
》[1],根据
此问题
[2],在 .NET 9 RTM 中似乎不会有wasi-experimental,仅使用
componentize-dotnet
[3]的项目才能工作/将被更新,WASI 实验正在 NET 10 中继续进行。

2024年9月份字节码联盟发布了一篇文章《
使用 componentize-dotnet 为 .NET/C# 开发人员简化组件
》[4],文章总结了 componentize-dotnet 项目,这是一个为.NET/C#开发者提供的工具,使得将代码编译为WebAssembly组件变得简单。这个由Bytecode Alliance发起的项目是一个NuGet包,允许从.NET应用程序创建完全AOT编译的组件,为.NET开发者提供了与Rust和TinyGo相当的组件体验。文章详细介绍了如何使用.NET 9 Preview 7和 componentize-dotnet 开始构建组件,包括安装必要的软件、配置NuGet以引用实验性包源、添加 BytecodeAlliance.Componentize.DotNet.Wasm.SDK 包到项目中,并展示了如何构建和运行一个简单的WebAssembly组件。此外,文章还探讨了如何简化组件工作流程,特别是如何使用WebAssembly Interface Type (WIT)定义来实现组件间的互操作性。最后,文章提到了.NET 9最终发布时将支持通过Mono编译器生成组件,以及 componentize-dotnet 项目将如何为用户提供选择 NativeAOT-LLVM 或Mono编译器的便利。文章鼓励有兴趣的开发者加入Bytecode Alliance社区并参与 componentize-dotnet 项目。

根据这篇文章的最新的适用于 .NET 9 RC 2 和 Wasmtime 26.0.0的代码示例:
https://github.com/henrikrxn/webassembly-experiments/blob/main/dotnet-9/componentize-dotnet/README.md[5
]

相关链接:

git-commit-id-plugin
是一个 Maven 插件,用于在 Maven 项目的构建过程中自动获取 git 仓库的信息,如最后一次提交的 ID、分支名称、构建时间等,并将这些信息注入到项目的属性文件中。这对于跟踪项目版本和构建状态非常有用。

以下是如何在 Maven 项目中使用
git-commit-id-plugin
的基本步骤:

1 添加插件到 pom.xml 文件中

在你的 Maven 项目的
pom.xml
文件中,添加
git-commit-id-plugin

<plugins>
部分。

  <build>
    <plugins>
      <plugin>
        <!-- https://mvnrepository.com/artifact/pl.project13.maven/git-commit-id-plugin -->
        <groupId>pl.project13.maven</groupId>
        <artifactId>git-commit-id-plugin</artifactId>
        <version>4.0.5</version>
        <executions>
          <execution>
            <goals>
              <goal>revision</goal>
            </goals>
          </execution>
        </executions>
        <!-- 配置选项 -->
        <configuration>
          <dotGitDirectory>${project.basedir}/../.git</dotGitDirectory>
          <generateGitPropertiesFile>true</generateGitPropertiesFile>
          <failOnNoGitDirectory>false</failOnNoGitDirectory>
          <skipPoms>false</skipPoms>
          <generateGitPropertiesFilename>
            ${project.build.outputDirectory}/git.properties
          </generateGitPropertiesFilename>
          <gitDescribe>
            <!-- don't generate the describe property -->
            <skip>false</skip>
            <!-- abbrev commit id length -->
            <abbrev>8</abbrev>
          </gitDescribe>
          <includeOnlyProperties>
            <includeOnlyProperty>^git.branch$</includeOnlyProperty>
            <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>
            <includeOnlyProperty>^git.commit.(id|time)$</includeOnlyProperty>
            <includeOnlyProperty>^git.commit.id.abbrev$</includeOnlyProperty>
          </includeOnlyProperties>
        </configuration>
      </plugin>
    </plugins>
  </build>

2 配置插件


<configuration>
标签中,你可以配置多种选项,例如生成的属性文件的位置、包含哪些 git 属性等。默认情况下,插件会生成一个
git.properties
文件在
target/classes
目录下。

   <configuration>
       <dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
       <generateGitPropertiesFile>true</generateGitPropertiesFile>
       <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
       <format>properties</format>
   </configuration>
  1. 构建项目:

使用 Maven 命令构建项目,例如
mvn clean install
。在构建过程中,
git-commit-id-plugin
插件会自动运行,并在指定的位置生成包含 git 信息的
git.properties
文件。

4 查看 git.properties 文件

打开生成的
git.properties
文件,你将看到类似以下的内容,其中包含了提交时间、提交记录、分支等信息:

#Generated by Git-Commit-Id-Plugin
git.branch=main
git.build.host=heal-mac
git.build.time=2024-10-30T15\:04\:10+0800
git.build.version=1.0.0-SNAPSHOT

git.commit.id=abcdef1234567890
git.commit.id.abbrev=abcdef1
git.commit.time=2024-10-28T12\:34\:56+0800

git.commit.message.full=Initial commit
git.commit.message.short=Initial commit

git.commit.user.name=Your Name
git.commit.user.email=your.email@example.com

git.commit.author.time=2024-10-26T21\:11\:39+0800
git.commit.committer.time=2024-10-26T21\:11\:39+0800

5 在应用中使用这些信息

你可以在应用程序中读取
git.properties
文件,并使用这些信息,例如显示当前版本的 Git 分支和提交 ID。

通过这种方式,你可以利用
git-commit-id-plugin
插件来自动获取和使用 Git 的提交时间、提交记录、分支等信息,从而帮助跟踪和管理你的项目版本。

在Java中,对于基本类型可以使用“="来进行克隆,此时两个变量除了相等是没有任何关系的。

而对于
引用类型却不能简单地使用”=“进行克隆
,这与java的内存空间使用有关。

Java将内存空间分成两块,即栈和堆。

在栈中保存基本类型和引用变量;

在堆中保存对象。

对于引用变量而言,使用”=“将修改引用,而不是复制堆中的对象。

此时两个引用变量将指向同一个对象。

因此,如果一个变量对其进行修改则会改变另一个变量。

在克隆对象时,如果对象的成员变量是基本类型,则使用浅克隆即可完成。

如果对象的成员变量包括可变引用类型,则需要使用深克隆。

如果引用类型是不可变的,如String类的对象,则不必进行深克隆。

在很多程序中,封装常用消息对话框有很多好处,尤其是在 GUI 应用程序中,本篇随笔结合.net 开发Winform界面的经验,对使用wxpython开发中 wx.MessageDialog 和 wx.lib.agw.genericmessagedialog.GenericMessageDialog 两种不同消息对话框的处理,对其进行简单封装,以适应程序开发的需要。

1、消息对话框的封装处理的优势

对常用消息对话框的封装处理,具有以下是一些主要的优点:

1.
代码复用

封装消息对话框可以避免重复代码。你可以定义一个统一的函数或类来处理所有消息对话框,从而在多个地方复用这段代码。

2.
一致性

通过封装,你可以确保所有消息对话框的外观和行为一致。这有助于提高用户体验,使用户在应用程序中获得统一的交互方式。

3.
简化调用

封装可以简化调用过程。你可以将常用的参数设置(如标题、图标、按钮类型等)预先定义好,从而在调用时减少参数输入。

4.
易于维护

当需要更改对话框的行为或样式时,只需在封装函数中进行修改,而不必在应用程序中的每个调用点进行更改。这使得维护变得更加简单和高效。

5.
增强可读性

通过使用封装的函数或类,代码变得更易读。其他开发者可以一眼看出对话框的作用,而不必深入了解其具体实现。

6.
集中管理

封装有助于集中管理对话框的逻辑,比如处理用户输入、响应用户选择等。这样可以更方便地进行逻辑更新或错误处理。

7.
扩展性

如果将来需要增加新的对话框或修改现有对话框的逻辑,封装使得扩展更加容易。你可以在封装的基础上进行扩展,而不影响现有的代码结构。

我在早期开发Winform的时候,对消息对话框进行了一些简单的封装,在随笔《
厚积薄发,丰富的公用类库积累,助你高效进行系统开发(2)----常用操作
》中有介绍。

封装的消息提示对话框包括个各种常用的对话框,如下所示:

2、对使用wxpython开发中常用消息对话框的封装

为了方便,我们先写一个页面来测试相关消息对话框的封装处理,如下界面所示。

wxpython开发中 wx.MessageDialog 和 wx.lib.agw.genericmessagedialog.GenericMessageDialog 都时跨平台支持的,GenericMessageDialog
是 wxPython 的一个扩展库,提供了一个通用的消息对话框类,用于在不同平台上显示消息框。这个类是跨平台的,支持以下主要平台:

  1. Windows
    :在 Windows 操作系统上,
    GenericMessageDialog
    会使用系统样式来渲染对话框。
  2. macOS
    :在 macOS 上,它会遵循 Cocoa 的界面风格。
  3. Linux
    :在各种 Linux 发行版上,它会适应 GTK 或 Qt(如果 wxPython 是基于这些库构建的)风格。

MessageDialog  和
GenericMessageDialog
旨在提供一致的用户体验,无论在哪个平台上运行。

MessageDialog和GenericMessageDialog 的差别

wx.MessageDialog

wx.lib.agw.genericmessagedialog.GenericMessageDialog
是 wxPython 中用于显示消息对话框的两种不同类,它们之间有一些主要的差别:

1) 类别及实现方式

  • wx.MessageDialog
    :


    • 是 wxPython 的内置类,使用系统原生的对话框实现。
    • 适应系统的外观和风格,因此在不同平台上看起来会有所不同。
    • 通常用于显示简单的消息、确认或警告对话框。
  • wx.lib.agw.genericmessagedialog.GenericMessageDialog
    :


    • 是 wxPython 的 AGW(Advanced Generic Widgets)库中的一部分,提供了一个更灵活的通用消息对话框实现。
    • 允许更多的自定义选项和更复杂的布局,适合需要更多控制和自定义的场景。
    • 可以在所有平台上保持一致的外观,因为它不依赖于系统原生对话框。

2)自定义能力

  • wx.MessageDialog
    :


    • 自定义选项有限,主要集中在按钮、图标和消息文本。
    • 不支持复杂的布局或多种控件的组合。
  • wx.lib.agw.genericmessagedialog.GenericMessageDialog
    :


    • 提供更多的自定义选项,如设置按钮图标、对话框的最小尺寸和布局。
    • 可以添加更多控件(如文本框、图片等),适合更复杂的用户交互需求。

3) 使用场景

  • wx.MessageDialog
    :


    • 适用于简单的确认消息、警告或信息提示场景。
    • 更适合于需要快速实现标准对话框的情况。
  • wx.lib.agw.genericmessagedialog.GenericMessageDialog
    :


    • 更适合于需要更高自定义和灵活性的应用程序。
    • 适用于复杂的对话框场景,比如需要显示额外信息或允许用户输入的情况。

如果你的需求简单,只需显示消息并获取用户确认,使用 wx.MessageDialog 是更简单的选择。
如果你需要更多的自定义功能或希望在多个平台上保持一致的外观,可以考虑使用 GenericMessageDialog。

消息对话框的常用代码如下所示。

    defShowMessageDialog(self):#创建消息对话框
        dlg =wx.MessageDialog(self,"This is a message dialog example.\nWould you like to proceed?","Confirmation", 
wx.YES_NO
| wx.CANCEL |wx.ICON_QUESTION)#显示对话框并处理用户选择 result =dlg.ShowModal()if result ==wx.ID_YES:
wx.MessageBox(
"You clicked Yes!", "Info")elif result ==wx.ID_NO:
wx.MessageBox(
"You clicked No!", "Info")elif result ==wx.ID_CANCEL:
wx.MessageBox(
"You clicked Cancel!", "Info")

dlg.Destroy()
#销毁对话框

以及

    defon_show_dialog(self, event):#创建 GenericMessageDialog
        dlg =GMD.GenericMessageDialog(self,"This is a message.","Message Title",
style
=wx.OK |wx.CANCEL)#设置图标(可选) dlg.SetYesNoCancelBitmaps(wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_MESSAGE_BOX))#显示对话框 result =dlg.ShowModal()if result ==wx.ID_OK:print("OK button clicked")elif result ==wx.ID_CANCEL:print("Cancel button clicked")

dlg.Destroy()

如果每次这样调用,我们需要了解很多相关的参数信息,一般我们只需要传入一些简单的信息提示即可,因此需要对它们进行简单的封装。

我们定义一个类,提供最原始简单的显示消息的处理函数,然后再在基础上进行特殊的简化封装即可。

classMessageUtil:"""封装了常用的消息对话框,以方便使用常用对话框消息。
包括提示信息、警告信息、错误信息、确认信息、询问信息、输入信息、
选择信息、多选信息、文件选择信息、目录选择信息、字体选择信息、颜色选择信息、进度条信息等。
"""@staticmethoddefshow_message(parent, message, caption, style):"""显示消息对话框-使用GenericMessageDialog实现"""dlg=GMD.GenericMessageDialog(parent, message, caption, style)
dlg.SetIcon(images.appIcon.GetIcon())
#设置窗口图标 result=dlg.ShowModal()
dlg.Destroy()
returnresult

@staticmethod
defshow_message2(parent, message, caption, style):"""显示消息对话框-使用wx.MessageDialog 实现"""dlg=wx.MessageDialog(parent, message, caption, style)

result
=dlg.ShowModal()
dlg.Destroy()
return result

其中 show_message 和show_message2 是针对两者不同消息类的封装,我们可以根据实际需要替换来用即可。如对于常规的提示消息、告警、错误消息框,简单设置一下参数即可。

    #常用消息对话框的标题
    CAPTION_TIPS = "提示信息"CAPTION_WARNING= "警告信息"CAPTION_ERROR= "错误信息"CAPTION_CONFIRM= "确认信息"@staticmethoddef show_tips(parent, message, caption=CAPTION_TIPS):"""显示一般的提示信息"""
        returnMessageUtil.show_message(
parent, message, caption, wx.OK
|wx.ICON_INFORMATION
)
@staticmethod
def show_warning(parent, message, caption=CAPTION_WARNING):"""显示警告信息""" returnMessageUtil.show_message(
parent, message, caption, wx.OK
|wx.ICON_WARNING
)

@staticmethod
def show_error(parent, message, caption=CAPTION_ERROR):"""显示错误信息""" return MessageUtil.show_message(parent, message, caption, wx.OK | wx.ICON_ERROR)

而对于提供更多按钮的,也是设置参数即可,如下所述。

@staticmethoddef show_yes_no_tips(parent, message, caption=CAPTION_TIPS):"""显示询问用户信息,并显示提示标志"""
        returnMessageUtil.show_message(
parent, message, caption, wx.YES_NO
|wx.ICON_INFORMATION
)

@staticmethod
def show_yes_no_warning(parent, message, caption=CAPTION_WARNING):"""显示询问用户信息,并显示警告标志""" returnMessageUtil.show_message(
parent, message, caption, wx.YES_NO
|wx.ICON_WARNING
)

@staticmethod
def show_yes_no_error(parent, message, caption=CAPTION_ERROR):"""显示询问用户信息,并显示错误标志""" returnMessageUtil.show_message(
parent, message, caption, wx.YES_NO
|wx.ICON_ERROR
)

另外,wxpython还提供了TextEntryDialog、SingleChoiceDialog、MultiChoiceDialog等对话框,我们也可以简单封装一下使用。

@staticmethoddefshow_input_dialog(parent, message, caption, default_value):"""显示输入对话框"""dlg=wx.TextEntryDialog(parent, message, caption, default_value)
result
=dlg.ShowModal()if result ==wx.ID_OK:returndlg.GetValue()else:returnNone

@staticmethod
defshow_choice_dialog(parent, message, caption, choices):"""显示选择对话框"""dlg=wx.SingleChoiceDialog(parent, message, caption, choices)
result
=dlg.ShowModal()if result ==wx.ID_OK:returndlg.GetStringSelection()else:returnNone

@staticmethod
defshow_multi_choice_dialog(parent, message, caption, choices):"""显示多选对话框"""dlg=wx.MultiChoiceDialog(parent, message, caption, choices)
result
=dlg.ShowModal()if result ==wx.ID_OK:returndlg.GetSelections()else:return None

最后如下效果所示。

这样我们在程序里面统一调用就会有相同的效果,而且简化了很多不必要的参数。

MessageUtil.show_tips(None, "This is a test program.", "关于")

以上就是一些日常开发的函数处理和抽象处理,主要就是为了简化实际开发的时候的一些复杂度,并提供统一的界面效果。