声明
这个系列文章是翻译自https://www.baeldung.com/的系列博客,个人感觉是一个非常不错的Spring Boot 教程。原网站属于一个公司,主要开展有偿培训业务,但提供相关文字教程的免费阅读和下载。因为我并没有在网页找到相关版权描述信息,所以并不能确定是否可以自由翻译和转载,如果有版权问题,请联系我,我会撤下这个系列文章。
原文地址:
概述
本教程是Spring Boot的起点,换句话说,是一种以简单的方式开始使用基本Web应用程序的方法。
我们将介绍一些核心配置、前端、快速数据操作和异常处理。
设置
首先,让我们使用Spring Initializr为我们的项目生成基础。
生成的项目依赖于 Boot 父项:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<relativePath />
</parent>
初始依赖关系将非常简单:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
应用程序配置
接下来,我们将为我们的应用程序配置一个简单的主类:
public class Ch1Application {
public static void main(String[] args) {
SpringApplication.run(Ch1Application.class, args);
}
}
注意我们是如何使用@SpringBootApplication
作为主要的应用程序配置类的。在幕后,这相当于 @Configuration
, @EnableAutoConfiguration
和 @ComponentScan
在一起。
最后,我们将定义一个简单的application.properties文件,它现在只有一个属性:
server.port=8081
port将服务器端口从默认的8080更改为8081;当然还有可用。
简单MVC视图
现在让我们使用Thymeleaf添加一个简单的前端。
首先,我们需要将spring-boot-starter-thymeleaf依赖项添加到我们的pom.xml中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
默认情况下启用Thymeleaf。无需额外配置。
我们现在可以在我们的www.example.com中配置它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
接下来,我们将定义一个简单的和一个带有欢迎消息的基本主页:
public class SimpleController {
"${spring.application.name}")
( String appName;
"/")
( public String homePage(Model model) {
model.addAttribute("appName", appName);
return "home";
}
}
最后,这里是我们的home.html:
<html>
<head><title>Home Page</title></head>
<body>
<h1>Hello !</h1>
<p>Welcome to <span th:text="${appName}">Our App</span></p>
</body>
</html>
注意我们是如何使用我们在属性中定义的属性,然后注入它,以便我们可以在主页上显示它的。
安全
接下来,让我们通过首先包含安全启动器来为应用程序添加安全性:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
现在,我们可以注意到一个模式:大多数Spring库都可以使用简单的Boot starter轻松导入到我们的项目中。
一旦spring-boot-starter-security
依赖项位于应用程序的classpath上,所有端点都将默认受到保护,使用基于Spring Security的内容协商策略的httpBasic或formLogin。
这就是为什么,如果我们在类路径上有这个启动器,我们通常应该定义自己的自定义安全配置:
public class SecurityConfig {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.csrf()
.disable();
return http.build();
}
}
在我们的示例中,我们允许不受限制地访问所有端点。
当然,SpringSecurity是一个广泛的主题,不容易在几行配置中涵盖。因此,我们绝对鼓励。
简单持久性
让我们从定义我们的数据模型开始,一个简单的Book实体(entity):
public class Book {
strategy = GenerationType.AUTO)
( private long id;
nullable = false, unique = true)
( private String title;
nullable = false)
( private String author;
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((author == null) ? 0 : author.hashCode());
result = prime * result + (int) (id ^ (id >>> 32));
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Book other = (Book) obj;
if (author == null) {
if (other.author != null)
return false;
} else if (!author.equals(other.author))
return false;
if (id != other.id)
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
public String toString() {
return "Book [id=" + id + ", title=" + title + ", author=" + author + "]";
}
}
及其存储库,在这里充分利用Spring Data:
public interface BookRepository extends CrudRepository<Book, Long> {
List<Book> findByTitle(String title);
}
最后,我们当然需要配置新的持久层:
"com.example.ch1.repository")
("com.example.ch1.entity")
(
public class Application {
...
}
请注意,我们正在使用以下内容:
-
@EnableJpaRepositories
扫描指定的包以查找存储库 -
@EntityScan
获取我们的JPA实体
为了简单起见,我们在这里使用H2内存数据库。这样我们在运行项目时就不会有任何外部依赖。
一旦我们包含H2依赖项,Spring靴子就会自动检测它并设置我们的持久性,除了数据源属性之外,不需要额外的配置:
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和控制器
接下来,让我们看一下Web层。我们将从设置一个简单的控制器BookControlle
r开始。
我们将实现基本的CRUD操作,通过一些简单的验证来暴露Book资源:
"/api/books")
(public class BookController {
private BookRepository bookRepository;
public Iterable findAll() {
return bookRepository.findAll();
}
"/title/{bookTitle}")
( public List findByTitle( String bookTitle) {
return bookRepository.findByTitle(bookTitle);
}
"/{id}")
( public Book findOne( Long id) {
return bookRepository.findById(id)
.orElseThrow(BookNotFoundException::new);
}
HttpStatus.CREATED)
( public Book create( Book book) {
return bookRepository.save(book);
}
"/{id}")
( public void delete( Long id) {
bookRepository.findById(id)
.orElseThrow(BookNotFoundException::new);
bookRepository.deleteById(id);
}
"/{id}")
( public Book updateBook( Book book, Long id) {
if (book.getId() != id) {
throw new BookIdMismatchException();
}
bookRepository.findById(id)
.orElseThrow(BookNotFoundException::new);
return bookRepository.save(book);
}
}
考虑到应用程序的这一方面是一个API,我们在这里使用了@RestController
注释-它相当于 @Controller
与@ResponseBody
一起(使用)—— 以便每个方法将返回的资源封送到HTTP响应。
请注意,我们在这里将Book实体公开为外部资源。对于这个简单的应用程序来说,这很好,但是在实际的应用程序中,我们可能希望。
错误处理
现在核心应用程序已经准备就绪,让我们关注一个简单的集中式错误处理机制,使用 @ControllerAdvice
:
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
BookNotFoundException.class })
({ protected ResponseEntity<Object> handleNotFound(
Exception ex, WebRequest request) {
return handleExceptionInternal(ex, "Book not found",
new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}
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);
}
}
除了我们在这里处理的标准异常之外,我们还使用了一个自定义异常BookNotFoundException
:
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(String message, Throwable cause) {
super(message, cause);
}
// ...
}
这让我们了解了这种全局异常处理机制的可能性。要查看完整的实现,请查看。
请注意,Spring Boot默认情况下还提供了/error
映射。我们可以通过创建一个简单的error.html
来定制它的视图:
<html lang="en">
<head><title>Error Occurred</title></head>
<body>
<h1>Error Occurred!</h1>
<b>[<span th:text="${status}">status</span>]
<span th:text="${error}">error</span>
</b>
<p th:text="${message}">message</p>
</body>
</html>
像Boot中的大多数其他方面一样,我们可以通过一个简单的属性来控制它:
server.error.path=/error2
测试
最后,让我们测试一下新的Books API。
我们可以使用 来加载应用程序上下文,并验证在运行应用程序时没有错误:
SpringRunner.class)
(
public class SpringContextTest {
public void contextLoads() {
}
}
接下来,让我们添加一个JUnit测试,使用REST Assured验证对我们编写的API的调用。
首先,我们将添加rest-assured依赖项:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
现在我们可以添加测试:
public class SpringBootBootstrapLiveTest {
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");
}
}
首先,我们可以尝试使用不同的方法来查找书籍:
public void whenGetAllBooks_thenOK() {
Response response = RestAssured.get(API_ROOT);
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
}
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);
}
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"));
}
public void whenGetNotExistBookById_thenNotFound() {
Response response = RestAssured.get(API_ROOT + "/" + randomNumeric(4));
assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
}
接下来,我们将测试创建一本新书:
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());
}
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());
}
然后我们将更新现有的书:
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"));
}
我们可以删除一本书:
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的快速而全面的介绍。
当然,我们只触及了表面。关于这个框架,我们在一篇介绍性文章中无法涵盖更多内容。
这就是为什么。
与往常一样,我们这里的示例的完整源代码。
原文作者的示例包含一些其他依赖,在构建时候会有一些麻烦,我参考原作者的代码构建了一个更干净的版本,可以通过
文章评论