【问题标题】:How to handle inheritance of Spring Beans in MVC projects如何处理 MVC 项目中 Spring Bean 的继承
【发布时间】:2017-12-01 03:17:44
【问题描述】:

我有一个项目有很多相互依赖的部分:

myProject
  - core
  - core_web (depends on core)
  - app1_core (depends on core)
  - app1_web (depends on app1_core and core_web)
  - app2_core (depends on core)
  - app2_web (depends on app2_core and core_web)

我在 corecore_web 中定义了典型的 MVC 部分:服务、控制器、存储库、JPA 实体等。

app1 项目按原样使用所有这些核心内容——除了一些使用 application.properties 的特定配置外,我将能够使用一些 @Configuration 魔法来解决。

但是,app2 项目将Date 字段和List 字段添加到实体。加法的影响无处不在:DTO、工厂、控制器、服务等变成了混乱的继承混乱,从设计的角度来看只是感觉很恶心。有没有更好、更干、更符合最佳实践的方法来处理这个问题,还是我只需要从我的核心代码中继承几乎每个类,只是为了向一个实体添加几个字段?

作为一个简单的例子(这里使用Lombok 是为了简洁和令人敬畏),这是来自core@Entity

package com.example.core;

@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ACCOUNT")
@ToString(of = {"id", "emailAddress", "name"})
@EqualsAndHashCode
public class Account implements Serializable {

    private static final long serialVersionUID = 1L;

    @Embedded
    private Address address;

    @Id
    @GenericGenerator(
            name = "SEQ_ACCOUNT_ID",
            strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
            parameters = {
                    @Parameter(name = "sequence_name", value = "SEQ_ACCOUNT_ID"),
                    @Parameter(name = "initial_value", value = "1"),
                    @Parameter(name = "increment_size", value = "1")
            }
    )
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_ACCOUNT_ID")
    @Column(name = "ACCOUNT_ID", updatable = false, nullable = false, unique = true)
    private Long id;

    @Column(name = "EMAIL", nullable = false, length = 200, unique = true)
    private String emailAddress;

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = StringUtils.lowerCase(emailAddress);
    }

    @JsonIgnore
    @Column(name = "PASSWORD_HASH", nullable = false, length = 256)
    private String passwordHash;

    @Column(name = "NAME", nullable = false, length = 200)
    private String name;

    @Column(name = "PHONE", nullable = false, length = 30)
    private String phoneNumber;

    @Column(name = "URL")
    private String url;
}

现在我在app2 中添加额外的字段

package com.example.app2;

@Data
@NoArgsConstructor
@Table(name = "ACCOUNT")
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Bride extends Account {
    @Column(name = "WEDDING_DATE")
    private Date weddingDate;
}

我预计会有这么多的工作。它体面、优雅、不重复,只要我告诉 Spring 不要扫描两个类中的实体,一切都很好。当我开始添加服务代码时,它开始变得乏味,例如:

package com.example.core;

@Service
public class AccountService {

    private AccountRepository accountRepository;

    @Autowired
    public AccountService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    /* the command object here would mirror Account, but contain some code
     * to handle input validation and the like. I know I could just use the
     * entity class, but I've been bitten by using entity classes as DTOs or
     * command objects before, so I'd like to keep them separate.
     */
    public Account createAccount(CreateAccountCommand createAccountCommand) {
        Account account = new Account();
        account.setEmailAddress(createAccountCommand.getEmailAddress());
        account.setPasswordHash(createPasswordHash(createAccountCommand.getPassword()));
        account.setName(createAccountCommand.getName());
        account.setPhoneNumber(createAccountCommand.getPhoneNumber());
        account.setUrl(createAccountCommand.getUrl());
    }
}

现在的主要问题是:(阅读:应该)我如何处理Bride 中的额外字段?创建一个名为BrideService 的新类,它扩展AccountService 并调用super.createAccount()?那么我现在也需要一个包含额外字段的命令对象吗?然后验证器需要处理额外的字段,所以我继承自AccountValidator 类?正如你所看到的,我得到了这种混乱的、继承的类的滑坡,这些类只用于将一个或两个字段的逻辑添加到所有应该能够在某些时候通用处理它们的类的目的。我的意思是,这就是多态性的目的,对吧?这样我就可以在所有地方和服务等地方使用Account。“只是工作?”在这个纠结的混乱中,适配器模式是否可以工作?提前感谢您的任何建议。

【问题讨论】:

    标签: java spring spring-mvc inheritance spring-boot


    【解决方案1】:

    继承是一个滑坡。我建议你考虑不要扩展Account,毕竟你只是添加一个字段,不值得为此创建这么多类。 Bride 对我来说并不像 Account,也许 BrideAccount 会是一个更好的名字。但是你可以只使用组合吗:

    @Entity
    public class BrideAccount {
    
        @ID
        Integer id;
    
        @Column(name = "WEDDING_DATE")
        private Date weddingDate;
    
        Account account;
    }
    

    您不必再扩展所有这些其他控制器等,但可以重复使用它们。

    另一种选择是创建一个 Account 接口,让所有这些控制器使用该接口并仅扩展您需要的内容。

    但我强烈建议您不要在不需要时添加类。如果您可以在Account 类中添加一个字段,这只是每个帐户都不使用的字段,就像在超集类中一样,那是更可取的 IMO。您只需要非常小心正在使用哪些字段,哪些不使用。另一个字段,例如AccountType,可能需要用于告诉您它是什么类型的帐户,然后您会知道婚礼字段是否正在使用中。 CreateAccountCommand 也需要这样做。我在这里回答了一个类似的问题:Dynamically constructing composition object。一般来说,我默认不创建更多类,因为我对复杂类型层次结构的经验产生了偏见,这会产生比解决的问题更多的问题。

    【讨论】:

    • 很棒的答案,正是我正在寻找的那种东西。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-06
    • 2011-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-05
    相关资源
    最近更新 更多