使用Spring Boot搭建你的第一个应用程序(二)

简介: 使用Spring Boot搭建你的第一个应用程序(二)

Web 页面和Controller


有了Book entity, 我们需要为Book写一个Controller,主要做增删改查的操作,如下所示:


@RestController
@RequestMapping("/api/books")
public class BookController {
    @Autowired
    private BookRepository bookRepository;
    @GetMapping
    public Iterable findAll() {
        return bookRepository.findAll();
    }
    @GetMapping("/title/{bookTitle}")
    public List findByTitle(@PathVariable String bookTitle) {
        return bookRepository.findByTitle(bookTitle);
    }
    @GetMapping("/{id}")
    public Book findOne(@PathVariable Long id) {
        return bookRepository.findById(id)
                .orElseThrow(BookNotFoundException::new);
    }
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book create(@RequestBody Book book) {
        return bookRepository.save(book);
    }
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        bookRepository.findById(id)
                .orElseThrow(BookNotFoundException::new);
        bookRepository.deleteById(id);
    }
    @PutMapping("/{id}")
    public Book updateBook(@RequestBody Book book, @PathVariable Long id) {
        if (book.getId() != id) {
            throw new BookIdMismatchException("ID mismatch!");
        }
        bookRepository.findById(id)
                .orElseThrow(BookNotFoundException::new);
        return bookRepository.save(book);
    }
}


这里我们使用@RestController 注解,表示这个Controller是一个API,不涉及到页面的跳转。


@RestController是@Controller 和 @ResponseBody 的集合。


异常处理


基本上我们的程序已经完成了,但是在Controller中,我们定义了一些自定义的异常:


public class BookNotFoundException extends RuntimeException {
    public BookNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    // ...
}


那么怎么处理这些异常呢?我们可以使用@ControllerAdvice来拦截这些异常:


@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler({ BookNotFoundException.class })
    protected ResponseEntity<Object> handleNotFound(
      Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, "Book not found", 
          new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }
    @ExceptionHandler({ BookIdMismatchException.class, 
      ConstraintViolationException.class, 
      DataIntegrityViolationException.class })
    public ResponseEntity<Object> handleBadRequest(
      Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, ex.getLocalizedMessage(), 
          new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
    }
}


这种异常捕获也叫做全局异常捕获。


测试


我们的Book API已经写好了,接下来我们需要写一个测试程序来测试一下。


这里我们使用@SpringBootTest :


@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class SpringContextTest {
    @Test
    public void contextLoads() {
        log.info("contextLoads");
    }
}


webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT的作用是表示测试时候使用的Spring boot应用程序端口使用自定义在application.properties中的端口。


接下来我们使用RestAssured来测试BookController:


@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class SpringBootBootstrapTest {
    private static final String API_ROOT
            = "http://localhost:8081/api/books";
    private Book createRandomBook() {
        Book book = new Book();
        book.setTitle(randomAlphabetic(10));
        book.setAuthor(randomAlphabetic(15));
        return book;
    }
    private String createBookAsUri(Book book) {
        Response response = RestAssured.given()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .body(book)
                .post(API_ROOT);
        return API_ROOT + "/" + response.jsonPath().get("id");
    }
    @Test
    public void whenGetAllBooks_thenOK() {
        Response response = RestAssured.get(API_ROOT);
        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    }
    @Test
    public void whenGetBooksByTitle_thenOK() {
        Book book = createRandomBook();
        createBookAsUri(book);
        Response response = RestAssured.get(
                API_ROOT + "/title/" + book.getTitle());
        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
        assertTrue(response.as(List.class)
                .size() > 0);
    }
    @Test
    public void whenGetCreatedBookById_thenOK() {
        Book book = createRandomBook();
        String location = createBookAsUri(book);
        Response response = RestAssured.get(location);
        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
        assertEquals(book.getTitle(), response.jsonPath()
                .get("title"));
    }
    @Test
    public void whenGetNotExistBookById_thenNotFound() {
        Response response = RestAssured.get(API_ROOT + "/" + randomNumeric(4));
        assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
    }
    @Test
    public void whenCreateNewBook_thenCreated() {
        Book book = createRandomBook();
        Response response = RestAssured.given()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .body(book)
                .post(API_ROOT);
        assertEquals(HttpStatus.CREATED.value(), response.getStatusCode());
    }
    @Test
    public void whenInvalidBook_thenError() {
        Book book = createRandomBook();
        book.setAuthor(null);
        Response response = RestAssured.given()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .body(book)
                .post(API_ROOT);
        assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode());
    }
    @Test
    public void whenUpdateCreatedBook_thenUpdated() {
        Book book = createRandomBook();
        String location = createBookAsUri(book);
        book.setId(Long.parseLong(location.split("api/books/")[1]));
        book.setAuthor("newAuthor");
        Response response = RestAssured.given()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .body(book)
                .put(location);
        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
        response = RestAssured.get(location);
        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
        assertEquals("newAuthor", response.jsonPath()
                .get("author"));
    }
    @Test
    public void whenDeleteCreatedBook_thenOk() {
        Book book = createRandomBook();
        String location = createBookAsUri(book);
        Response response = RestAssured.delete(location);
        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
        response = RestAssured.get(location);
        assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
    }
}


写好了测试类,运行就行了。


结论


你的第一个Spring Boot程序就完成了,后面的文章我们会继续丰富和改善这个基本框架,欢迎继续关注。


本文章的例子代码可以参考github: bootstrap-sample-app


相关文章
|
3天前
|
Java Maven Docker
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
|
1月前
|
安全 Java 网络安全
当网络安全成为数字生活的守护者:Spring Security,为您的应用筑起坚不可摧的防线
【9月更文挑战第2天】在数字化时代,网络安全至关重要。本文通过在线银行应用案例,详细介绍了Spring Security这一Java核心安全框架的核心功能及其配置方法。从身份验证、授权控制到防御常见攻击,Spring Security提供了全面的解决方案,确保应用安全。通过示例代码展示了如何配置`WebSecurityConfigurerAdapter`及`HttpSecurity`,帮助开发者有效保护应用免受安全威胁。
57 4
|
2月前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
58 0
|
1天前
|
Cloud Native Java C++
Springboot3新特性:开发第一个 GraalVM 本机应用程序(完整教程)
文章介绍如何在Spring Boot 3中利用GraalVM将Java应用程序编译成独立的本机二进制文件,从而提高启动速度、减少内存占用,并实现不依赖JVM运行。
15 1
Springboot3新特性:开发第一个 GraalVM 本机应用程序(完整教程)
|
14天前
|
XML Java 测试技术
spring复习01,IOC的思想和第一个spring程序helloWorld
Spring框架中IOC(控制反转)的思想和实现,通过一个简单的例子展示了如何通过IOC容器管理对象依赖,从而提高代码的灵活性和可维护性。
spring复习01,IOC的思想和第一个spring程序helloWorld
|
6天前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
24 2
|
15天前
|
Java 对象存储 开发者
故障隔离与容错处理:Hystrix在Spring Cloud和Netflix OSS中的应用
故障隔离与容错处理:Hystrix在Spring Cloud和Netflix OSS中的应用
37 3
|
21天前
|
Kubernetes Cloud Native Java
当 Quarkus 遇上 Spring Boot,谁才是现代云原生应用的终极之选?究竟哪款能助你的应用傲视群雄?
Quarkus 和 Spring Boot 均为构建现代云原生应用的热门框架,旨在简化开发流程并提升性能。Spring Boot 依托庞大的 Spring 生态系统,提供开箱即用的体验,适合快速搭建应用。Quarkus 由红帽发起,专为 GraalVM 和 HotSpot 设计,强调性能优化和资源消耗最小化,是云原生环境的理想选择。
20 3
|
1月前
|
IDE Java 开发工具
还在为繁琐的配置头疼吗?一文教你如何用 Spring Boot 快速启动,让开发效率飙升,从此告别加班——打造你的首个轻量级应用!
【9月更文挑战第2天】Spring Boot 是一款基于 Spring 框架的简化开发工具包,采用“约定优于配置”的原则,帮助开发者快速创建独立的生产级应用程序。本文将指导您完成首个 Spring Boot 项目的搭建过程,包括环境配置、项目初始化、添加依赖、编写控制器及运行应用。首先需确保 JDK 版本不低于 8,并安装支持 Spring Boot 的现代 IDE,如 IntelliJ IDEA 或 Eclipse。
92 5
|
2月前
|
安全 NoSQL Java
JeecgBoot应用Spring Authorization Server
Spring Authorizaiton Server, 简称 sas,是一个授权服务器框架,提供 OAuth2.1 与 Open Connect 1.0 认证规范及其他规范的实现,它建立在 Spring Security 之上,为构建 OpenID Connect 1.0 Identity Provider 和 OAuth2 授权服务器产品提供了一个安全、轻量级和可定制的基础
38 2