点击关注公众号,Java干货及时送达
来源:www.cnblogs.com/liululee/p/11235999.html
1. 错误一:太过关注底层
2. 错误二:内部结构 “泄露”
@Entity
@NoArgsConstructor
@Getter
public class TopTalentEntity {
@Id
@GeneratedValue
private Integer id;
@Column
private String name;
public TopTalentEntity(String name) {
this.name = name;
}
}
TopTalentEntity
数据。返回TopTalentEntity
实例可能很诱人,但更灵活的解决方案是创建一个新的类来表示 API 端点上的TopTalentEntity
数据。@AllArgsConstructor
@NoArgsConstructor
@Getter
public class TopTalentData {
private String name;
}
TopTalentEntity
中添加一个 “password” 字段来存储数据库中用户密码的 Hash 值 —— 如果没有TopTalentData
之类的连接器,忘记更改服务前端,将会意外地暴露一些不必要的秘密信息。3. 错误三:缺乏关注点分离
TopTalentData
。@RestController
public class TopTalentController {
private final TopTalentRepository topTalentRepository;
@RequestMapping("/toptal/get")
public List<TopTalentData> getTopTalent() {
return topTalentRepository.findAll()
.stream()
.map(this::entityToData)
.collect(Collectors.toList());
}
private TopTalentData entityToData(TopTalentEntity topTalentEntity) {
return new TopTalentData(topTalentEntity.getName());
}
}
TopTalentEntity
实例检索出来的TopTalentData
的 List。TopTalentController
实际上在此做了些事情;也就是说,它将请求映射到特定端点,从数据库检索数据,并将从TopTalentRepository
接收的实体转换为另一种格式。一个“更干净” 的解决方案是将这些关注点分离到他们自己的类中。看起来可能是这个样子的:@RestController
@RequestMapping("/toptal")
@AllArgsConstructor
public class TopTalentController {
private final TopTalentService topTalentService;
@RequestMapping("/get")
public List<TopTalentData> getTopTalent() {
return topTalentService.getTopTalent();
}
}
@AllArgsConstructor
@Service
public class TopTalentService {
private final TopTalentRepository topTalentRepository;
private final TopTalentEntityConverter topTalentEntityConverter;
public List<TopTalentData> getTopTalent() {
return topTalentRepository.findAll()
.stream()
.map(topTalentEntityConverter::toResponse)
.collect(Collectors.toList());
}
}
@Component
public class TopTalentEntityConverter {
public TopTalentData toResponse(TopTalentEntity topTalentEntity) {
return new TopTalentData(topTalentEntity.getName());
}
}
4. 错误四:缺乏异常处理或处理不当
@Value
public class ErrorResponse {
private Integer errorCode;
private String errorMessage;
}
@ExceptionHandler
注解来完成(注解案例可见于第六章)。5. 错误五:多线程处理不当
5.1. 避免全局状态
5.2. 避免可变性
5.3. 记录关键数据
5.4. 复用现存实现
6. 错误六:不使用基于注解的验证
@RequestMapping("/put")
public void addTopTalent(@RequestBody TopTalentData topTalentData) {
boolean nameNonExistentOrHasInvalidLength =
Optional.ofNullable(topTalentData)
.map(TopTalentData::getName)
.map(name -> name.length() == 10)
.orElse(true);
if (nameNonExistentOrInvalidLength) {
// throw some exception
}
topTalentService.addTopTalent(topTalentData);
}
@RequestMapping("/put")
public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) {
topTalentService.addTopTalent(topTalentData);
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) {
// handle validation exception
}
// 此外,我们还必须指出我们想要在 TopTalentData 类中验证什么属性:
public class TopTalentData {
@Length(min = 10, max = 10)
@NotNull
private String name;
}
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { MyAnnotationValidator.class })
public @interface MyAnnotation {
String message() default "String length does not match expected";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int value();
}
@Component
public class MyAnnotationValidator implements ConstraintValidator<MyAnnotation, String> {
private int expectedLength;
@Override
public void initialize(MyAnnotation myAnnotation) {
this.expectedLength = myAnnotation.value();
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
return s == null || s.length() == this.expectedLength;
}
}
isValid
方法中的s == null
),如果这是属性的附加要求,则使用@NotNull
注解。public class TopTalentData {
@MyAnnotation(value = 10)
@NotNull
private String name;
}
7. 错误七:(依旧)使用基于xml的配置
@SpringBootApplication
复合注解中做了声明,如下所示:@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Component
(TopTalentConverter
,MyAnnotationValidator
)@RestController
(TopTalentController
)@Repository
(TopTalentRepository
)@Service
(TopTalentService
) 类
@Configuration
注解类,它们也会检查基于 Java 的配置。8. 错误八:忽略 profile
8.1. APPLICATION.YAML 文件
# set default profile to 'dev'
spring.profiles.active: dev
# production database details
spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal'
spring.datasource.username: root
spring.datasource.password:
8.2. APPLICATION-DEV.YAML 文件
spring.datasource.url: 'jdbc:h2:mem:'
spring.datasource.platform: h2
-Dspring.profiles.active=prod
参数给 JVM 来手动覆盖配置文件。另外,还可将操作系统的环境变量设置为所需的默认 profile。9. 错误九:无法接受依赖项注入
public class TopTalentController {
private final TopTalentService topTalentService;
public TopTalentController() {
this.topTalentService = new TopTalentService();
}
}
public class TopTalentController {
private final TopTalentService topTalentService;
public TopTalentController(TopTalentService topTalentService) {
this.topTalentService = topTalentService;
}
}
TopTalentService
行为正确的前提下测试控制器。我们可以通过提供一个单独的配置类来插入一个模拟对象来代替实际的服务实现:@Configuration
public class SampleUnitTestConfig {
@Bean
public TopTalentService topTalentService() {
TopTalentService topTalentService = Mockito.mock(TopTalentService.class);
Mockito.when(topTalentService.getTopTalent()).thenReturn(
Stream.of("Mary", "Joel").map(TopTalentData::new).collect(Collectors.toList()));
return topTalentService;
}
}
SampleUnitTestConfig
作为它的配置类来注入模拟对象:@ContextConfiguration(classes = { SampleUnitTestConfig.class })
10. 错误十:缺乏测试,或测试不当
DispatcherServlet
,并查看当收到一个实际的HttpServletRequest
时会发生什么(使它成为一个 “集成” 测试,处理验证、序列化等)。@RunWith(SpringJUnit4Cla***unner.class)
@ContextConfiguration(classes = {
Application.class,
SampleUnitTestConfig.class
})
public class RestAssuredTestDemonstration {
@Autowired
private TopTalentController topTalentController;
@Test
public void shouldGetMaryAndJoel() throws Exception {
// given
MockMvcRequestSpecification givenRestAssuredSpecification = RestAssuredMockMvc.given()
.standaloneSetup(topTalentController);
// when
MockMvcResponse response = givenRestAssuredSpecification.when().get("/toptal/get");
// then
response.then().statusCode(200);
response.then().body("name", hasItems("Mary", "Joel"));
}
}
SampleUnitTestConfig
类将TopTalentService
的模拟实现连接到TopTalentController
中,而所有的其他类都是通过扫描应用类所在包的下级包目录来推断出的标准配置。RestAssuredMockMvc
只是用来设置一个轻量级环境,并向/toptal/get
端点发送一个GET
请求。