Spring MVC 项目结构

Posted

技术标签:

【中文标题】Spring MVC 项目结构【英文标题】:Spring MVC project structure 【发布时间】:2021-02-19 20:34:56 【问题描述】:

我正在决定一个小型 RESTful 服务的项目结构。至于现在,我看到了two designs。首先是功能。很多tutorials、another tutorial也采用这种方式:

example
+- controller
|  +- CustomerController
|  +- OrderController
+- model
|  +- Customer
|  +- Order
+- repository
|  +- ...
+- security
+- service

Spring 文档推荐另一种方法,按对象分组。但是,我并不经常看到它:

example
+- customer
|  +- Customer
|  +- CustomerController
|  +- CustomerRepository
|  +- CustomerService
+- order
|  +- ...

您能否详细说明何时选择其中之一?还是仅仅是一种偏好?特别是对于 RESTful 和 RESTless 应用程序。另外,如果我按对象对包进行分组,我应该将未公开的安全相关类放在哪里?

另外,根据answers之一的说法,按功能分组会在以后引起问题。有评论,但自 2014 年以来没有得到答复。

其次,对于实现类,有一个建议,也在这个page 上,避免使用Impl 后缀。为什么会这样? Spring 是否没有明确要求 Impl 并将其用作 Spring Data JPA 存储库的默认值?

CustomerRepository implements JpaRepository, ComplexCustomerRepository ...

ComplexCustomerRepositoryImpl implements ComplexCustomerRepository
  // Specifications and Examples and custom queries here

如果这是一种反模式,那么解决方法是什么?我是不是误会了什么?

谢谢。

【问题讨论】:

【参考方案1】:

有 1000+1 个解决方案,有很多人或公司有实施。没有关于它应该是什么样子的文献。这取决于设计、想法和项目计划。

我的 Rest 基础结构没有模板。 Ofc,对于 API,MVC 不是很有效。因为 View 本身就是响应模型。

src
+- http (Things about http)
|  +- controller (where catch request, additional folders can be categories)
|  +- model (request and response models)
|  +- errors (tipical Exceptions for HTTP errors what handle by @RestControllerAdvice)
|  +- validators (extended annotations - https://www.baeldung.com/spring-mvc-custom-validator)
+- database
|  +- cassandra
|  |  +- repositories
|  |  +- models
|  +- redis
|  |  +- ...
|  ...
+- models (general model in program what I can pass to another component easily)
+- services
|  +- utils
|  +- authentication
|  ... (and so on)
+- email
+- helper
+- configs (tipical Spring Configuration & environment properties)
+- consts (example enums or static classes)
... (and more what you want)

补充

如果你使用@Service注解,不需要接口。我就是这样使用它的:

@Service
public class AnyService  ... 

使用接口只是因为看到任何地方但从不引用,这是不必要的,也不是必需的。如果您有 2 个不同的模型使用相同的方法(例如 getUuid()),并且您想将两者都用于一个参数,那么您需要给它一个接口。

基本上有2+1的名字识别方法。 它们只是名称。他们没有定义任何东西!只有当您使用文件管理器查看它时,您才会看到它是什么。 IDE 显然知道并在文件夹树中显示。

接口以大 I 开头,但实现有一个正常的名称。或者另一个,接口有一个普通的名字,但实现最后有 Impl。也有不区分的情况(不加“I”或“Impl”),但IDE显示接口或类,见IntelliJ或Eclipse。

【讨论】:

【参考方案2】:

请注意,我从未使用过 Spring。

一些原则:

MVC separates the concerns,例如presentation logic (V & C) 来自 business logic (M)。 基于 MVC 的应用程序的核心是 域模型。不是数据库,不是某些功能或模块结构。 DM 对为其开发应用程序的业务进行抽象、建模。因此,DM 组件之间的交互实现了业务逻辑。 多个应用程序应该可以同时访问一个 DM,无论它们有多么不同。换句话说,DM 组件应该是独立于应用程序的。例如。特定应用程序的任何代码都不应驻留在 DM 中。 只能通过特定于应用程序的应用程序服务访问 DM(作为应用程序的一部分并在 DM 之外定义)。所以,不是直接由控制器。不过,您也可以拥有独立于应用程序的域服务(作为 DM 的一部分),可由应用程序服务调用。 DM 应该(大部分)由抽象组成(接口或抽象类)作为组件。它们的实现应该驻留在 DM 之外的某个地方(例如,在 “Infrastructure” 文件夹/命名空间中)。 repositories 是 DM 的一部分。更准确地说,如上所述,它们的抽象。另一方面,data mappers(也称为 DAO,例如数据访问对象)是基础架构的一部分。这样,存储库将域模型与数据访问或持久层分离。模型对此一无所知。

结构示例:

考虑到上述情况,想象一下您自己的结构类似于下面的结构(基于 php;可能过于详细),描述了域模型与任何应用程序的解耦。然后您会看到,您提出的第二种方法(“按对象分组”)有什么不完全正确的地方。例如,如果您想在多个模块中使用同一个实体(此处为:Customer),您会怎么做?

您的第一种方法也不太正确,因为域模型不仅仅由实体组成。除非您将文件夹 model 重命名为 entity 并将文件夹 entityrepository 视为 DM 的一部分。

+ path/to/domain
    + src
        + Infrastructure [namespace: "Domain\Infrastructure"]
            + Mapper [data mappers; as defined by Martin Fowler at https://www.martinfowler.com/eaaCatalog/dataMapper.html]
                + Customer
                    + PdoCustomerMapper.php [implements "CustomerMapper"; access to db through PDO]
                    + CustomerMapper.php [interface; used by "Domain\Model\Customer\CustomerCollection"]
            + Repository [repositories]
                + Customer
                    + CustomerCollection.php [repository; implementation of "Domain\Model\Customer\CustomerCollection" interface]
            + Service [various services; implementations of the interfaces defined in "Domain\Model" (email, security, payment, etc)]
                + Email
                    + SMTPSender.php [email sender; implementation of "Domain\Model\Email\EmailSender"; sends emails through SMTP]
                + Security
                    + AbcAuthentication.php [authentication; an implementation of "Domain\Model\Security\Authentication"]
                    + DefAuthentication.php [authentication; another implementation of "Domain\Model\Security\Authentication"]
                    + XyzAuthorization.php [authorization; an implementation of "Domain\Model\Security\Authorization"]
        + Model [this is DM (the domain model, the core of your application(s)); namespace: "Domain\Model"]
            + Customer
                + Customer.php [entity; implementation]
                + Name.php [value object; implementation; "<first name>, <last name>"]
                + CustomerCollection.php [interface; repository (envisioned as a Customer collection); uses "Domain\Infrastructure\Mapper\Customer\CustomerMapper"; as defined by Martin Fowler at https://www.martinfowler.com/eaaCatalog/repository.html]
                + CustomerService.php [implementation; domain service, application-independent]
            + Email
                + EmailSender.php [interface; domain service, application-independent; its implementation must not reside in "Infrastructure" folder (it can, for ex., be an external library loaded by a dependency manager in a "path/to/myapp_1/vendor" directory)]
            + Security
                + Authentication [interface; domain service, application-independent; its implementation must not reside... dito]
                + Authorization [interface; domain service, application-independent; its implementation must not reside... dito]


+ path/to/myapp_1
    + config
        + dependencies [the dependency injection container definitions]
        + parameters [arguments for the DI container definitions]
        + routes [route definitions]
    + public
        + assets
            + css
                + ... [files resulting from the compilation of the .scss files]
            + js
            + images
        + index.php [the entry point of the application. All user requests are handled by this file]
    + resources
        + fonts [the font files loaded by the @font-face css rules]
        + scss (with Dart Sass as Sass engine)
        + templates [with Twig as templating engine]
            + layouts
                + Primary.html.twig
            + templates [their content is embedded into "Primary.html.twig" layout]
                + Dashboard.html.twig
                + Customers
                    + AddCustomer.html.twig
                    + ListCustomers.html.twig
        + vendor
            + node_modules [all client-side libraries (Bootstrap, jQuery, etc), loaded through a package manager, like npm]
            + package.json
    + src
        + Controller
        + View
        + Service [application services, application-specific; they use the components of "Domain\Model" to read/update the DM]
            + Customer
                + AddCustomer [adds a customer]
                    + Exception
                        + CustomerExists.php
                    + AddCustomer.php [uses "Domain\Model\Customer\CustomerCollection"]
                + ListCustomers.php [list specified customers; uses "Domain\Model\Customer\CustomerCollection"]
            + Order
                + ...
    + storage
        + cache
            + router
                + routes.cache [compiled routes]
            + container [compiled DI container]
        + sessions [opened sessions]
    + tests
    + vendor [all server-side libraries (FastRoute, PSR-7, etc), loaded through a dependency manager, like Composer. Including an eventual used framework (like Slim Framework)]
    + composer.json
    + .env [default values for the environment variables corresponding to a system in production. The values can be overwritten by the ones found in other .env.* files, like ".env.local". This file must be git commited to the production server]
    + .env.local [values for the environment variables, which overwrite the default ones in the file ".env". This file should be created locally, by the developer, on her/his machine, and must not be git commited to the production server]


+ path/to/myapp_2 [this application can look a lot different from myapp_1. Though, it can have access to the same domain model ("Domain\Model")]
    + ...

不过,考虑到您只开发一个基于 MVC 的应用程序,您可以将“domain”文件夹放入“path/to/myapp/src”,相应地更改命名空间(Java 中的包)。

+ path/to/myapp
    + config
    + public
    + resources
    + src
        + Controller
        + View
        + Service [application services, application-specific]
            + Customer
                + AddCustomer
                    + Exception
                        + CustomerExists.php
                    + AddCustomer.php
                + ListCustomers.php
            + Order
                + ...
        + Domain
            + Infrastructure
                + Mapper
                    + Customer
                        + PdoCustomerMapper.php
                        + CustomerMapper.php
                + Repository [repositories]
                    + Customer
                        + CustomerCollection.php
                + Service
                    + Email
                        + SMTPSender.php
                    + Security
                        + AbcAuthentication.php
                        + DefAuthentication.php
                        + XyzAuthorization.php
            + Model [the DM]
                + Customer
                    + Customer.php
                    + Name.php
                    + CustomerCollection.php
                    + CustomerService.php [domain service, application-independent]
                + Email
                    + EmailSender.php [domain service, application-independent]
                + Security
                    + Authentication [domain service, application-independent]
                    + Authorization [domain service, application-independent]
    + storage
    + tests
    + vendor
    + composer.json
    + .env
    + .env.local

【讨论】:

以上是关于Spring MVC 项目结构的主要内容,如果未能解决你的问题,请参考以下文章

使用 Maven 的具有多个子项目的 Spring MVC 项目的包结构

Spring Web源码之MVC主体结构

Spring Web源码之MVC主体结构

Spring Web源码之MVC主体结构

如何在 Intellij IDEA 13.1 中创建具有 Maven 结构的 Spring MVC 应用程序?

Spring MVC 简单项目 - 404