2017-02-02 47 views
1

在我的春/啓動Java項目我有一組的服務方法,例如像以下之一:春季服務方法和複雜的驗證邏輯/規則

@Override 
public Decision create(String name, String description, String url, String imageUrl, Decision parentDecision, Tenant tenant, User user) { 

    name = StringUtils.trimMultipleSpaces(name); 
    if (org.apache.commons.lang3.StringUtils.isEmpty(name)) { 
     throw new IllegalArgumentException("Decision name can't be blank"); 
    } 
    if (!org.apache.commons.lang3.StringUtils.isEmpty(url) && !urlValidator.isValid(url)) { 
     throw new IllegalArgumentException("Decision url is not valid"); 
    } 
    if (!org.apache.commons.lang3.StringUtils.isEmpty(imageUrl) && !urlValidator.isValid(imageUrl)) { 
     throw new IllegalArgumentException("Decision imageUrl is not valid"); 
    } 

    if (user == null) { 
     throw new IllegalArgumentException("User can't be empty"); 
    } 

    if (tenant != null) { 
     List<Tenant> userTenants = tenantDao.findTenantsForUser(user.getId()); 
     if (!userTenants.contains(tenant)) { 
      throw new IllegalArgumentException("User doesn't belong to this tenant"); 
     } 
    } 

    if (parentDecision != null) { 
     if (tenant == null) { 
      if (findFreeChildDecisionByName(parentDecision.getId(), name) != null) { 
       throw new EntityAlreadyExistsException("Parent decision already contains a child decision with a given name"); 
      } 
     } else { 
      if (findTenantedChildDecisionByName(parentDecision.getId(), name, tenant.getId()) != null) { 
       throw new EntityAlreadyExistsException("Parent decision already contains a child decision with a given name"); 
      } 
     } 

     Tenant parentDecisionTenant = tenantDao.findTenantForDecision(parentDecision.getId()); 
     if (parentDecisionTenant != null) { 
      if (tenant == null) { 
       throw new IllegalArgumentException("Public decision cannot be added as a child to tenanted parent decision"); 
      } 
      if (!parentDecisionTenant.equals(tenant)) { 
       throw new IllegalArgumentException("Decision cannot belong to tenant other than parent decision tenant"); 
      } 
     } else { 
      if (tenant != null) { 
       throw new IllegalArgumentException("Tenanted decision cannot be added as a child to public parent decision"); 
      } 
     } 

    } else { 
     if (tenant == null) { 
      if (findFreeRootDecisionByName(name) != null) { 
       throw new EntityAlreadyExistsException("Root decision with a given name already exists"); 
      } 
     } else { 
      if (findTenantedRootDecisionByName(name, tenant.getId()) != null) { 
       throw new EntityAlreadyExistsException("Root decision with a given name for this tenant already exists"); 
      } 
     } 
    } 

    Decision decision = createOrUpdate(new Decision(name, description, url, imageUrl, parentDecision, user, tenant)); 

    if (parentDecision != null) { 
     parentDecision.addChildDecision(decision); 
    } 

    criterionGroupDao.create(CriterionGroupDaoImpl.DEFAULT_CRITERION_GROUP_NAME, null, decision, user); 
    characteristicGroupDao.create(CharacteristicGroupDaoImpl.DEFAULT_CHARACTERISTIC_GROUP_NAME, null, decision, user); 

    return decision; 
} 

正如你所看到的,大多數的來自這個方法的代碼行被驗證邏輯佔據,我繼續在那裏添加一個新的驗證案例。

我想重構此方法並在更合適的位置移動此方法之外的驗證邏輯。請建議如何用Spring框架來完成。

+3

使用JSR-303對於大多數的這一點,也許有檢查複合logiv一個自定義的驗證類。例如,您的前幾次檢查可以縮減爲'@ NotEmpty'。 – chrylis

回答

2

正如在評論中提到chrylis,可以實現通過使用JSR-303 Bean驗證了這一目標。第一步是創建一個包含你輸入參數類:

public class DecisionInput { 
    private String name; 
    private String description; 
    private String url; 
    private String imageUrl; 
    private Decision parentDecision; 
    private Tenant tenant; 
    private User user; 

    // Constructors, getters, setters, ... 
} 

之後,你就可以開始添加驗證註釋,例如:

public class DecisionInput { 
    @NotEmpty 
    private String name; 
    @NotEmpty 
    private String description; 
    @NotEmpty 
    private String url; 
    @NotEmpty 
    private String imageUrl; 
    private Decision parentDecision; 
    private Tenant tenant; 
    @NotNull 
    private User user; 

    // Constructors, getters, setters, ... 
} 

請注意,@NotEmpty註釋不是一個標準的JSR-303註釋,而是一個Hibernate註釋。如果你更喜歡使用標準的JSR-303,你總是可以創建自己的自定義驗證器。對於您的租戶和您的決定,您當然需要一個自定義驗證器。首先創建一個註釋(例如@ValidTenant)。在您的註記類,一定要添加@Constraint註釋,例如:

@Constraint(validatedBy = TenantValidator.class) // Your validator class 
@Target({ TYPE, ANNOTATION_TYPE }) // Static import from ElementType, change this to METHOD/FIELD if you want to create a validator for a single field (rather than a cross-field validation) 
@Retention(RUNTIME) // Static import from RetentionPolicy 
@Documented 
public @interface ValidTenant { 
    String message() default "{ValidTenant.message}"; 
    Class<?>[] groups() default { }; 
    Class<? extends Payload>[] payload() default { }; 
} 

現在,你必須創建TenantValidator類,並使其實現ConstraintValidator<ValidTenant, DecisionInput>,例如:

@Component 
public class TenantValidator implements ConstraintValidator<ValidTenant, DecisionInput> { 
    @Autowired 
    private TenantDAO tenantDao; 

    @Override 
    public void initialize(ValidTenant annotation) { 
    } 

    @Override 
    public boolean isValid(DecisionInput input, ConstraintValidatorContext context) { 
     List<Tenant> userTenants = tenantDao.findTenantsForUser(input.getUser().getId()); 
     return userTenants.contains(input.getTenant()); 
    } 
} 

同樣可以做父母決定的驗證。現在,你可以重構你的服務的方法是:

public Decision create(@Valid DecisionInput input) { 
    // No more validation logic necessary 
} 

如果你想用你自己的錯誤信息,我建議您閱讀this answer。基本上你創建一個ValidationMessages.properties文件並把你的消息放在那裏。

+0

感謝您的詳細解答!還有一個問題 - 所有的驗證調用是否與服務方法在同一個事務中執行? – alexanoid

+1

@alexanoid我不是100%肯定,但據我所知方面在同一個事務上下文中執行,所以我的猜測是,驗證邏輯也將執行同樣的背景。 – g00glen00b