什么是世界上最令人讨厌的,同时也是最受欢迎的例外?
我敢打赌这是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