什么是世界上最令人討厭的,同時也是最受歡迎的例外?
我敢打賭這是NullPointerException。
NullPointerException可以表示任何東西,從簡單的“ ups,我認為不能為空”到數小時和數天的第三方庫調試(我敢于嘗試使用Dozer進行復雜的轉換)。
有趣的是,擺脫代碼中的所有NullPointerExceptions很簡單。 這種瑣碎性是一種稱為“ 按合同設計 ”的技術的副作用。
我不會詳細介紹該理論,您可以在Wikipedia上找到所需的所有內容,但在簡而言之,按合同設計意味著:
- 每個方法都有一個先決條件(調用前期望的條件)
- 每個方法都有一個后置條件(它保證什么,返回什么)
- 每個類對其狀態都有約束(類不變)
因此,在每種方法的開頭,您都要檢查是否滿足先決條件,最后檢查是否滿足后置條件和不變式,如果出了問題,則拋出異常,指出錯誤之處。
使用Spring的內部靜態方法引發適當的異常(IllegalArgumentException),它看起來可能像這樣:
import static org.springframework.util.Assert.notNull;
import static org.springframework.util.StringUtils.hasText;public class BranchCreator {public Story createNewBranch(Story story, User user, String title) {verifyParameters(story, user, title);Story branch = //... the body of the class returnig an objectverifyRetunedValue(branch);return branch;}private void verifyParameters(Story story, User user, String title) {notNull(story);notNull(user);hasText(title);}private void verifyRetunedValue(Story branch) {notNull(branch);}
}
您還可以使用來自Apache Commons的Validate類,而不是spring的notNull / hasText。
通常,我只檢查先決條件,并為后置條件和約束編寫測試。 但這仍然是所有樣板代碼。 要將其移出類,可以使用許多“按合同設計”庫,例如
SpringContracts或Contract4J 。 無論哪種方式,您最終都會檢查每種公共方法的前提條件。
你猜怎么著? 除了數據傳輸對象和某些設置器外,我編寫的每個公共方法都希望其參數不為空。
因此,為了節省一些編寫此樣板代碼的文字,如何添加一個簡單的方面(將使其在整個應用程序中不可能),將null傳遞給DTO和setter之外的其他事物呢? 沒有任何其他庫(我假設您已經在使用Spring Framework),注釋以及其他功能。
為什么我不想在參數中允許使用空值? 因為我們在現代語言中有方法重載。 認真地說,您希望多久看到一次這樣的事情:
Address address = AddressFactory.create(null, null, null, null);
而且這也不是更好
Microsoft.Office.Interop.Excel.Workbook theWorkbook = ExcelObj.Workbooks.Open(openFileDialog.FileName, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
解決方案
因此,這是一個簡單的解決方案:您將一個類添加到您的項目,并添加幾行spring IoC配置。
類(方面)如下所示:
import org.aspectj.lang.JoinPoint;
import static org.springframework.util.Assert.notNull;public class NotNullParametersAspect {public void throwExceptionIfParametersAreNull(JoinPoint joinPoint) {for(Object argument : joinPoint.getArgs()) {notNull(argument);}}
}
Spring配置在這里(請記住,要更改項目的名稱空間)。
<aop:config proxy-target-class='true'> <aop:aspect ref='notNullParametersAspect'><aop:pointcut expression='execution(public * eu.solidcraft.*..*.*(..))&& !execution(public * eu.solidcraft.*..*Dto.*(..))&& !execution(public * eu.solidcraft.*..*.set*(..))' id='allPublicApplicationOperationsExceptDtoAndSetters'> <aop:before method='throwExceptionIfParametersAreNull' pointcut-ref='allPublicApplicationOperationsExceptDtoAndSetters'></aop:before> </aop:pointcut> <task:annotation-driven><bean class='eu.solidcraft.aspects.NotNullParametersAspect' id='notNullParametersAspect'></bean></task:annotation-driven></aop:aspect>
</aop:config>
“ &&”沒有錯誤,只是在XML中轉義了&&條件。 如果您不了解Aspectj切入點定義語法,這是一些備忘單 。
這是一個測試,告訴我們配置已成功。
public class NotNullParametersAspectIntegrationTest extends AbstractIntegrationTest {@Resource(name = 'userFeedbackFacade')private UserFeedbackFacade userFeedbackFacade;@Test(expected = IllegalArgumentException.class)public void shouldThrowExceptionIfParametersAreNull() {//whenuserFeedbackFacade.sendFeedback(null);//then exception is thrown}@Testpublic void shouldNotThrowExceptionForNullParametersOnDto() {//whenUserBookmarkDto userBookmarkDto = new UserBookmarkDto();userBookmarkDto.withChapter(null);StoryAncestorDto ancestorDto = new StoryAncestorDto(null, null, null, null);//then no exception is thrown}
}
AbstractIntegrationTest是一個簡單的類,用于啟動彈簧測試上下文。 您可以將AbstractTransactionalJUnit4SpringContextTests與@ContextConfiguration(..)結合使用。
抓住
是的,有一個陷阱。 由于spring AOP使用基于接口的J2SE動態代理或aspectj CGLIB代理,因此每個類都將需要接口(用于基于簡單代理的方面編織)或不帶任何參數的構造函數(用于cglib編織)。 好消息是構造函數可以是私有的。
參考: Solid Craft博客上的JCG合作伙伴 Jakub Nabrdalik 通過簡單的spring方面消除了空參數 。
翻譯自: https://www.javacodegeeks.com/2012/11/getting-rid-of-null-parameters-with-a-simple-spring-aspect.html