Java中无需DI框架的演示

本文将带您踏上一段有趣的旅程,穿越鲜为人知的 Java 开发之路,揭开手动控制组件创建和依赖关系的细微差别。

准备好进入简单性与有效性相结合的领域,我们将演示复制和理解 DI 框架通常处理的功能的实践方法。让冒险开始吧!

手动依赖管理的概念
Java 中的手动依赖管理与 Spring 等框架的自动化便利性形成鲜明对比。从本质上讲,该方法涉及显式定义和管理 Java 应用程序中不同组件之间的关系和依赖关系。与 Spring 的自动依赖注入将组件无缝连接在一起不同,手动管理需要更深入地了解这些组件如何交互和相互依赖。

这种方法需要对每个组件的生命周期(从创建到销毁)进行精细控制。开发人员必须有意识地设计和实现创建实例、处理依赖关系以及管理应用程序中每个组件的生命周期的逻辑。这个过程需要仔细规划并彻底了解应用程序的体系结构以及其中每个组件的角色和职责。

选择手动依赖关系管理通常会导致更多的样板代码和管理组件关系的实践方法,从而使开发人员能够更清晰地了解其应用程序的内部工作原理。这种方法在定制控制至关重要的场景中或者在使用 Spring 等成熟框架不可行或不必要的环境中大放异彩。

现实世界示例:注册用户
为了本文的目的并演示不使用依赖注入的概念,我们从这个用例开始:在这个场景中,我们通过提供电子邮件和个人信息探索用户在系统中注册的过程。系统会验证此信息,确保其唯一性,并在成功注册后向用户发送确认电子邮件。该用例涉及用户和系统的直接参与,详细描述了从初始数据提交到完成注册的分步流程。

描述
用户可以通过提供电子邮件和个人信息进行注册。注册成功后,系统向用户发送一封确认电子邮件。
主要流程

  1. 用户提供电子邮件和个人信息。
  2. 系统验证并检查重复的电子邮件地址。
  3. 如果有效且唯一:
    • A。系统创建一个新的用户帐户。
    • b. 系统发送一封带有验证链接的确认电子邮件。
  • 用户收到电子邮件,单击链接并确认他们的电子邮件。
  • 用户已注册并可以登录。

    了解用户注册
    没有依赖注入 (DI) 框架的 Java 应用程序中的用户注册过程可能会很复杂,涉及多个组件之间的交互。序列图有助于可视化此过程的流程。

    时序图说明

    • 参与者(用户):代表发起注册过程的最终用户或客户端。
    • 参与者(UserService):这是协调用户注册过程的服务层。它充当注册期间所需的各种行动的主要协调者。
    • 参与者 (UserDAO):用户的数据访问对象 (DAO)。它负责处理所有与数据相关的操作,例如在数据库中创建和存储用户详细信息。
    • 参与者(EmailService):专门处理电子邮件操作的服务。作为注册过程的一部分,调用它来发送确认电子邮件。
    • 参与者(EmailSender):这代表用于发送电子邮件的实际机制或服务,例如 SMTP 服务器或第三方电子邮件服务。

    事件顺序
    1. User 到 UserService:该序列从“User”发起对UserService 的registerUser(User)调用开始。
    2. UserService 到 UserDAO:然后UserService与UserDAO交互,通过调用createUser(user)创建新用户。
    3. UserDAO 操作:UserDAO处理请求,创建用户详细信息并将其存储在数据库中。
    4. UserService 到 EmailService:创建用户后,UserService调用EmailService通过方法informEmailAddress(user)确认用户的电子邮件地址。
    5. EmailService 到 EmailSender:EmailService将发送注册电子邮件的任务委托给EmailSender,它可以是内部或外部电子邮件调度服务。
    6. UserDAO 到 UserService:最后,UserDAO将创建的用户返回给UserService,完成注册过程。

    该序列图有效地说明了 Java 应用程序中用户注册过程中不同组件之间的操作和交互流程,特别是
    手动管理依赖关系的一种。该图强调了各个组件之间清晰沟通和协调的必要性,强调了系统每个部分中明确定义的接口和职责的重要性。

    通过此交互流程,该图展示了应用程序如何处理用户注册请求,从接收初始用户数据到将其保存在数据库中并发送确认电子邮件。它还反映了 Java 应用程序中常用的分层架构,其中服务层 (UserService) 充当表示层(由 User actor 表示)和数据访问层 (UserDAO) 之间的中介,并提供诸如 EmailService 之类的附加服务,提供特定的服务。功能。

    这种可视化表示有助于理解流程中涉及的复杂性和依赖关系,从而更容易识别潜在的优化或重构区域,特别是在不使用依赖注入的设置中。通过将流程分解为离散的步骤并明确每个组件的作用,开发人员可以确保系统的每个部分高效且一致地运行,从而在应用程序中实现强大且可靠的用户注册功能。

    创建应用程序上下文
    Java 中的ApplicationContext类对于管理应用程序中各个组件的生命周期和依赖关系至关重要,尤其是在没有像 Spring 这样的依赖注入 (DI) 框架的情况下。此类充当 DI 框架的定制替代方案,允许手动控制和实例化应用程序组件。

    结构与功能
    在ApplicationContext的更新实现中,定义了几个关键组件,每个组件在应用程序的功能中都发挥着至关重要的作用:

    1. UserService ( UserServiceImpl ):这个单例服务负责与用户相关的操作,例如用户管理以及与数据访问层的交互。
    2. EmailService ( EmailServiceImpl ):另一个单例服务,该组件处理与电子邮件相关的功能,包括通过 EmailSender 准备和发送电子邮件。
    3. UserDAO ( UserDAOImpl ):定义为原型,UserDAO 负责数据访问操作,特别是用户数据。作为原型意味着每当请求时都会创建UserDAOImpl的新实例。
    4. EmailSender ( EmailSenderImpl ):也是一个原型,该组件是发送电子邮件的实际机制,例如与 SMTP 服务器或其他电子邮件调度系统的接口。

    public class ApplicationContext {

        private static class Holder {
            private static final ApplicationContext CTX = new ApplicationContext();
            private static final UserService USER_SERVICE = new UserServiceImpl();
            private static final EmailService EMAIL_SERVICE = new EmailServiceImpl();
        }

        public static ApplicationContext getInstance() {
            return Holder.CTX;
        }

        public UserService userService() {
            return Holder.USER_SERVICE;
        }

        public EmailService emailService() {
            return Holder.EMAIL_SERVICE; 
        }

        // Prototype
        public UserDAO userDAO() {
            return new UserDAOImpl();
        }

        // Prototype
        public EmailSender emailSender() {
            return new EmailSenderImpl();
        }

    }

    实施细节
    ApplicationContext使用静态嵌套类Holder来初始化和保存这些组件。静态嵌套类模式的使用确保了UserService和EmailService的单例实例是惰性创建的,并且是线程安全的,无需同步。

    getInstance ()方法提供对ApplicationContext 的全局访问点,确保在整个应用程序中一致使用同一实例。

    方法userService()、emailService()、userDAO()和emailSender()提供对相应组件的访问。单例组件和原型作用域组件之间的区别在这里很明显。虽然UserService和EmailService是单例,但UserDAO和EmailSender

    每次调用都会重新创建,遵循原型模式。这种区别对于管理这些组件的状态和生命周期至关重要:

    • 单例组件: UserService和EmailService的单个实例可确保这些服务在整个应用程序中保持其状态,为依赖于它们的所有组件提供一致的交互点。
    • 原型组件: UserDAO和EmailSender的原型范围允许在每次请求时创建一个新实例。这种方法对于需要无状态的组件或每个操作都需要新实例以避免共享状态问题的组件是有益的。

    实际使用
    在实践中,ApplicationContext支持对核心组件的创建和配置进行集中控制,这与自动化依赖关系管理的依赖注入 (DI) 框架形成鲜明对比,减少了手动控制,但简化了流程。例如,当应用程序的一部分需要访问UserService时,它​​会调用ApplicationContext.getInstance().userService() ,确保它与整个应用程序中的同一UserService实例进行交互。

    类似地,当需要UserDAO或EmailSender的新实例时,应用程序将分别调用ApplicationContext上的userDAO()或emailSender()方法。这种对组件实例化和依赖项处理的手动管理可以让您清楚地了解应用程序的流程和依赖项,从而提供比自动化 DI 框架更实用的方法。

    因此,更新后的ApplicationContext结构成为应用程序中的关键元素,展示了管理 Java 应用程序中的依赖关系和组件生命周期的有效方法,特别是在不首选或不可行使用 Spring 等 DI 框架的情况下。

    简化依赖关系解析
    Java 中ApplicationContextSupport接口的实现在简化应用程序内的依赖项解析过程中发挥着关键作用,特别是在没有传统依赖项注入 (DI) 框架的环境中。该接口配备了默认方法,显着简化了访问依赖对象的任务。

    设计与实用
    ApplicationContextSupport接口旨在提供直接与ApplicationContext交互的默认方法。这些方法充当访问各种组件(如UserDAO、EmailSender、UserService和EmailService )的快捷方式。通过这样做,它消除了获取ApplicationContext实例然后调用特定方法来获取所需对象的重复任务。

    public interface ApplicationContextSupport {

        default ApplicationContext applicationContext() {
            return ApplicationContext.getInstance();
        }

        default UserDAO userDao() {
            return applicationContext().userDAO();
        }

        default EmailSender emailSender() {
            return applicationContext().emailSender();
        }

        default UserService userService() {
            return applicationContext().userService();
        }

        default EmailService emailService() {
            return applicationContext().emailService();
        }

    }

    简化访问示例
    在给定的示例中,实现ApplicationContextSupport 的类可以直接访问依赖项。例如:

    // UserServiceImpl implements ApplicationContextSupport
    @Override
    public User findUser(String email) throws UserNotFoundException {
        return userDao().findUserByEmail(email));
    }

    在这种情况下,findUser方法利用ApplicationContextSupport中的userDao()来访问UserDAO。此方法抽象了获取ApplicationContext和UserDAO的底层复杂性。该方法调用被简洁地简化为userDao().findUserByEmail(email) ,而不是编写ApplicationContext.getInstance().userDAO().findUserByEmail(email)。

    好处

    1. 代码清晰度:实现此接口使代码更加清晰。开发人员可以专注于业务逻辑,而不是处理获取依赖项的复杂问题。
    2. 减少样板代码:它显着减少了样板代码,因为无需重复调用ApplicationContext.getInstance()。
    3. 增强可读性:代码变得更具可读性和可维护性,提高整体开发效率。
    4. 灵活性:这种方法在管理依赖关系和

    如果需要的话,可以很容易地调整或扩展附加组件。
    1. 一致性:通过标准化整个应用程序中组件的访问方式,ApplicationContextSupport确保了依赖关系解析的一致方法。

    总而言之,ApplicationContextSupport接口及其默认方法提供了一种方便且有效的方法来访问和管理 Java 应用程序中的依赖项。这种设计不仅简化了依赖关系解析的过程,而且还与干净且可维护的代码的最佳实践很好地结合在一起,特别是在选择不使用传统 DI 框架的环境中。