Spring Boot是Spring平台的约定式的应用框架,使用Spring Boot可以更加方便简洁的开发基于Spring的应用程序,本篇文章通过一个实际的例子,来一步一步的演示如何创建一个基本的Spring Boot程序。

依赖配置

本例子使用Maven来做包的依赖管理,在pom.xml文件中我们需要添加Spring boot依赖:

    <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

同时我们要构建一个web应用程序,所以需要添加web依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

OOM框架,我们使用spring自带的jpa,数据库使用内存数据库H2:

 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> <dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

main程序配置

接下来我们需要创建一个应用程序的主类:

@SpringBootApplication
public class App { public static void main(String[] args) {
SpringApplication.run(App.class, args);
} }

这里我们使用了注解: @SpringBootApplication。 它等同于三个注解:@Configuration, @EnableAutoConfiguration, 和 @ComponentScan同时使用。

最后,我们需要在resources目录中添加属性文件:application.properties。 在其中我们定义程序启动的端口:

server.port=8081

MVC配置

spring MVC可以配合很多模板语言使用,这里我们使用Thymeleaf。

首先需要添加依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

然后在application.properties中添加如下配置:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html spring.application.name=Bootstrap Spring Boot

然后创建一个home页面:

<html>
<head><title>Home Page</title></head>
<body>
<h1>Hello !</h1>
<p>Welcome to <span th:text="${appName}">Our App</span></p>
</body>
</html>

最后创建一个Controller指向这个页面:

@Controller
public class SimpleController {
@Value("${spring.application.name}")
String appName; @GetMapping("/")
public String homePage(Model model) {
model.addAttribute("appName", appName);
return "home";
}
}

安全配置

本例主要是搭一个基本完整的框架,所以必须的安全访问控制也是需要的。我们使用Spring Security来做安全控制,加入依赖如下:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

当spring-boot-starter-security加入依赖之后,应用程序所有的入库会被默认加入权限控制,在本例中,我们还用不到这些权限控制,所以需要自定义SecurityConfig,放行所有的请求:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.permitAll()
.and().csrf().disable();
}
}

上例中,我们permit all请求。

后面我又会详细的关于Spring Security的教程。这里先不做深入讨论。

存储

本例中,我们定义一个Book类,那么需要定义相应的Entity类:

@Entity
public class Book { @Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id; @Column(nullable = false, unique = true)
private String title; @Column(nullable = false)
private String author;
}

和相应的Repository类:

public interface BookRepository extends CrudRepository<Book, Long> {
List<Book> findByTitle(String title);
}

最后,我们需要让应用程序发现我们配置的存储类,如下:

@EnableJpaRepositories("com.flydean.learn.repository")
@EntityScan("com.flydean.learn.entity")
@SpringBootApplication
public class App { public static void main(String[] args) {
SpringApplication.run(App.class, args);
} }

这里,我们使用@EnableJpaRepositories 来扫描repository类。

使用@EntityScan来扫描JPA entity类。

为了方便起见,我们使用内存数据库H2. 一旦H2在依赖包里面,Spring boot会自动检测到,并使用它。 我们需要配置一些H2的属性:

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=

和安全一样,存储也是一个非常重要和复杂的课题,我们也会在后面的文章中讨论。

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

更多教程请参考 flydean的博客

使用Spring Boot搭建你的第一个应用程序的更多相关文章

  1. 自我救赎 → 利用 IDEA 和 Spring Boot 搭建 SSM

    前言 开心一刻 儿子读高中放学回来了,一向不管他学习的我突然来了兴趣,想看看他的学习他的状况,抄起他的数学习题看了起来,当看到 1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x ...

  2. Set up HTTP/2 server with Spring Boot 【基于Spring boot搭建http2.0服务器】

    1. Server side With spring boot, we can set up a http server easily. Restcontroller make it easier t ...

  3. maven 聚合工程 用spring boot 搭建 spring cloud 微服务 模块式开发项目

    项目的简单介绍: 项目采用maven聚合工程 用spring boot 搭建 spring cloud的微服务 模块式开发 项目的截图: 搭建开始: 能上图 我少打字 1.首先搭建maven的聚合工程 ...

  4. 使用Spring Boot搭建应用开发框架(一) —— 基础架构

    Spring的简史 第一阶段:XML配置,在Spring1.x时代,使用Spring开发满眼都是xml配置的Bean,随着项目的扩大,我们需要把xml配置文件分放到不同的配置文件里,那时候需要频繁的在 ...

  5. 记录一次Spring boot 搭建框架连接Mysql数据库注解事务不回滚的故障

    搭建了一个新框架,使用了spring boot 替换以简化原来繁杂的spring配置,使用Spring注解管理事务,持久层使用mybatis. 连接mysql数据库完成项目的过程中发现不支持事务,因为 ...

  6. Spring Boot搭建Web项目常用功能

    搭建WEB项目过程中,哪些点需要注意: 1.技术选型: 前端:freemarker.vue 后端:spring boot.spring mvc 2.如何包装返回统一结构结果数据? 首先要弄清楚为什么要 ...

  7. 如何基于Spring Boot搭建一个完整的项目

    前言 使用Spring Boot做后台项目开发也快半年了,由于之前有过基于Spring开发的项目经验,相比之下觉得Spring Boot就是天堂,开箱即用来形容是绝不为过的.在没有接触Spring B ...

  8. Spring Boot 搭建TCP Server

    本示例首选介绍Java原生API实现BIO通信,然后进阶实现NIO通信,最后利用Netty实现NIO通信及Netty主要模块组件介绍. Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可 ...

  9. (子文章)Spring Boot搭建两个微服务模块

    目录 1. 创建工程和user-service模块 1.1 创建空工程 1.2 在空工程里新建Module 2. 配置文件 2.1 pom.xml 2.2 application.yml 3. 代码 ...

随机推荐

  1. Java第十三天,内部类

    内部类 一.①成员内部类.②局部内部类(包含③匿名内部类) 1.内部类用外部类属性和方法的时候,可以随意进行访问. 2.外部类用内部类属性和方法的时候,需要通过内部类对象访问. 3.在编译成class ...

  2. Python常见数据结构-List列表

    Python list基本特点 列表是一种有序集合,可以随时添加和删除元素. 序列中的每个元素都分配一个数字 - 它的位置. 列表的数据项不需要具有相同的类型. 创建一个列表,只要把逗号分隔的不同的数 ...

  3. pyecharts的使用及总结

    包的下载及配置 这个包的相应的配置较多,版本也不兼容,总结一下 预览:pyecharts画图 pip pyecharts pip 各级别地图(6.7个左右) pip jupyter环境 [为了生成pn ...

  4. golang开发:环境篇(七) Go mod 使用

    Glide用的好好的,为什么要使用Modules 在 Go 1.11 中 ,官方加入package management tool,称为Go Modules.Go mod 没有出现之前,用的最多的包管 ...

  5. 2020-3-15 20175110王礼博 Exp2后门原理与实践

    目录 1.使用netcat获取主机操作Shell,cron启动 2.使用socat获取主机操作Shell, 任务计划启动 3.使用MSF meterpreter(或其他软件)生成可执行文件,利用nca ...

  6. Python设计模式(9)-外观模式

    # /*外观模式:为外界调用提供一个统一的接口,把其他类中需要用到的方法提取# * 出来,由外观类进行调用.然后在调用段实例化外观类,以间接调用需要的# * 方法.这种方式和代理模式有异曲同工之妙.然 ...

  7. 《深入理解 Java 虚拟机》笔记整理

    正文 一.Java 内存区域与内存溢出异常 1.运行时数据区域 程序计数器:当前线程所执行的字节码的行号指示器.线程私有. Java 虚拟机栈:Java 方法执行的内存模型.线程私有. 本地方法栈:N ...

  8. 2019-08-02【机器学习】有监督学习之分类 SVC算法 实例(上证指数跌涨预测)

    样本: 代码:有几处与教程不同,自行修改 import pandas as pd import numpy as np from sklearn import svm from sklearn imp ...

  9. ORCAD常用元件库说明

    以下是ORCAD自带库文件的说明,路径:Cadence\Cadence_SPB_16.6\tools\capture\library 1' AMPLIFIER.OLB共182个零件,存放模拟放大器IC ...

  10. 挑战全网最幽默的Vuex系列教程:第二讲 Vuex旗下的State和Getter

    先说两句 上一讲 「Vuex 到底是个什么鬼」,已经完美诠释了 Vuex 的牛逼技能之所在(纯属自嗨).如果把 Vuex 比喻成农药里面的刘备,那就相当于你现在已经知道了刘备他是一个会打枪的力量型英雄 ...