SpringBoot--简单入门

简介

本质上说Spring是一个组件容器,它负责创建并管理容器中的组件(也被称为Bean),并管理组件之间的依赖关系。

为什么要用SpringBoot?

Spring缺点是配置过多,SpringBoot就是为了解决这个问题。

SpringBoot为大部分第三方框架整合提供了自动配置。SpringBoot使用约定优于配置(CoC Covention over Configuration)为理念,针对企业开发各种场景,提供对应的Starter。

总体来说SpringBoot具有以下特性:

  1. 内嵌Tomcat、jetty或Undertow服务器。
  2. SpringBoot应用可被做成独立的Java应用程序。
  3. 尽可能的自动配置Spring及第三方框架。
  4. 完全没有代码生成,不需要XML配置。
  5. 提供产品级监控功能,如运行状况检查和外部化配置等。

创建SpringBoot应用

开发环境准备

Java 、Maven(配置本地资源库和国内镜像)

创建SpringBoot项目

只需要创建一个Maven项目即可,单机IDEA主菜单"File"-->"New"-->"project"-->左边选"Maven"-->"Next"

项目结构如下:

使用SpringInitializr工具自动生成Maven项目

单机IDEA主菜单"File"-->"New"-->"project"

编写控制器

java 复制代码
@Controller
public class BookController {

    @GetMapping("/")
    public String index(Model model){
        model.addAttribute("tip", "Hello, World!");
        return "hello";
    }

    @GetMapping("/rest")
    // 指定该方法生成restful风格的响应
    // 在前后端分离的架构中,SpringBoot应用只需要对外提供restful响应,前端通过restful接口与后端通信
    @ResponseBody
    public ResponseEntity restIndex() {
        return new ResponseEntity<>(
            "Hello, World from REST!",null,
            HttpStatus.OK);
    }
}

运行应用

java 复制代码
package com.example.ali2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 被@SpringBootApplication修饰的类位于com.example.ali2包下,Spring Boot会自动扫描该包及其子包下的所有配置类(@Configuration修饰的类)
// 和组件类(@Component、@Controller、@Service、@Repository 等修饰的类),将它变成容器中的bean
@SpringBootApplication
public class Ali2Application {

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

}

创建jar包

java 复制代码
<!--    打jar包首先保证在pom.xml中添加了spring-boot-maven-plugin插件,这样可以通过mvn package命令打包成可执行的jar文件。-->
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
<!--   此外,如果在pom.xml中添加了<packaging>元素,请确保是<packaging>jar</packaging>,省略也是可以的-->
   

执行maven命令打成jar包

mvn clean

mvn package

复制代码
执行"mvn package"命令,会从默认生命周期的第一阶段一直执行到"package"阶段,最终生成一个可执行的jar文件。这个jar文件会被放在target目录下,文件名通常是<artifactId>-<version>.jar,例如ali2-0.0.1-SNAPSHOT.jar。
而maven的生命周期包含compile(编译)、test(单元测试)、package(打包)、install(安装到本地)、deploy(部署到远程)等阶段,执行"mvn package"命令会自动执行compile、test等阶段,直到package阶段。
如果前面的阶段失败,则打包失败。

开发业务组件

先引入数据库相关依赖

java 复制代码
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>bootstrap</artifactId>
			<version>5.3.5</version> <!-- 可根据需要选择版本 -->
		</dependency>
<!-- Spring Data JPA 依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
   <version>3.3.6</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.16</version>
   <scope>runtime</scope>
</dependency>

service

java 复制代码
public interface BookService {
    List<Book> getAllBooks();
    Integer addBook(Book book);
    void deleteBook(Integer id);
}

serviceImpl

java 复制代码
@Service
@Transactional(propagation= Propagation.REQUIRED,timeout = 5) // 事务注解,表示该类中的所有方法需要事务支持
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    @Override
    public List<Book> getAllBooks() {
        return (List<Book>)bookDao.findAll();
    }

    @Override
    public Integer addBook(Book book) {
        bookDao.save(book);
        return book.getId();
    }

    @Override
    public void deleteBook(Integer id) {
        bookDao.deleteById(id);
    }
}

controller

java 复制代码
@Controller
public class BookController {

    @GetMapping("/")
    public String index(Model model){
        model.addAttribute("tip", "Hello, World!");
        return "hello";
    }

    @GetMapping("/rest")
    // 指定该方法生成restful风格的响应
    // 在前后端分离的架构中,SpringBoot应用只需要对外提供restful响应,前端通过restful接口与后端通信
    @ResponseBody
    public ResponseEntity restIndex() {
        return new ResponseEntity<>(
            "Hello, World from REST!",null,
            HttpStatus.OK);
    }

    @Autowired
    private BookService bookService;

    @PostMapping("/addBook")
    public String addBook(Book book, Model model) {
        bookService.addBook(book);
        return "redirect:listBooks";
    }

    @PostMapping("/rest/books")
    @ResponseBody
    public ResponseEntity<Map<String,String>> restAddBook(@RequestBody Book book) {
        bookService.addBook(book);
        return new ResponseEntity<>(
            Map.of("tip", "Book added successfully!"),
            HttpStatus.OK
        );
    }

    @GetMapping("/listBooks")
    public String listBooks(Model model) {
        model.addAttribute("books", bookService.getAllBooks());
        return "list";
    }

    @GetMapping("/rest/books")
    @ResponseBody
    public ResponseEntity<List<Book>> restListBooks() {
        List<Book> books = bookService.getAllBooks();
        return new ResponseEntity<>(books, null,HttpStatus.OK);
    }

    @GetMapping("/deleteBook")
    public String deleteBook(Integer id) {
        bookService.deleteBook(id);
        return "redirect:listBooks";
    }

    @DeleteMapping("/rest/books/{id}")
    @ResponseBody
    public ResponseEntity<Map<String,String>> restDelete(@PathVariable Integer id) {
        bookService.deleteBook(id);
        return new ResponseEntity<>(
            Map.of("tip", "Book deleted successfully!"),null,
            HttpStatus.OK
        );
    }
}

hello.html

html 复制代码
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/webjars/bootstrap/5.3.5/css/bootstrap.min.css}"/>
    <script type="text/javascript" th:src="@{/webjars/jquery/3.6.4/jquery.min.js}"></script>
</head>
<body>
<div class="container">
    <div class="alert alert-primary" th:text="${tip}"></div>
    <h2>添加图书</h2>
    <form method="post" th:action="@{/addBook}">
        <div class="form-group row">
            <label for="title" class="col-sm-3 col-form-label">图书名:</label>
            <div class="col-sm-9">
                <input type="text" class="form-control" id="title" name="title" placeholder="请输入图书名" required>
            </div>
        </div>
        <div class="form-group row">
            <label for="author" class="col-sm-3 col-form-label">作者:</label>
            <div class="col-sm-9">
                <input type="text" class="form-control" id="author" name="author" placeholder="请输入作者" required>
            </div>
        </div>
        <div class="form-group row">
            <label for="price" class="col-sm-3 col-form-label">价格:</label>
            <div class="col-sm-9">
                <input type="number" step="0.1" class="form-control" id="price" name="price" placeholder="请输入价格" required>
            </div>
        </div>
        <div class="form-group row">
          <div class="col-sm-6 text-right">
              <button type="submit" class="btn btn-primary">添加</button>
          </div>
            <div class="col-sm-6">
                <button type="reset" class="btn btn-primary">重设</button>
            </div>
        </div>
    </form>
</div>
</body>
</html>

list.html

html 复制代码
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/webjars/bootstrap/5.3.5/css/bootstrap.min.css}"/>
    <script type="text/javascript" th:src="@{/webjars/jquery/3.6.4/jquery.min.js}"></script>
</head>
<body>
<div class="container">
    <h2>全部图书</h2>
    <table class="table table-hover">
        <thead>
        <tr>
            <th>图书名</th>
            <th>作者</th>
            <th>价格</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="book : ${books}">
            <td th:text="${book.title}">书名</td>
            <td th:text="${book.author}">作者</td>
            <td th:text="${book.price}">价格</td>
            <td>
                <a th:href="@{/deleteBook(id=${book.id})}" class="btn btn-danger">删除</a>
            </td>
        </tr>
        </tbody>
    </table>
    <div class="text-right" >
        <a th:href="@{/}" class="btn btn-primary">添加图书</a>
    </div>
</div>
</body>
</html>

application.properties

复制代码
spring.datasource.url=jdbc:mysql://localhost:3306/ali2?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
# default ddl auto
spring.jpa.generate-ddl=true

Book

java 复制代码
@Entity
@Table(name = "book_inf") // 指定数据库表名
public class Book {
    @Id // 主键
    @Column(name = "book_id") // 指定数据库列名
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增主键
    private Integer id;

    private String title;

    private String author;

    private Double price;

    public Book(){}
    public Book(String title, String author, Double price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }


    @Override
    public String toString()
    {
        return "Book{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }

    // Getters and Setters
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

BookDao

java 复制代码
// 继承CrudRepository接口,提供基本的CRUD操作
public interface BookDao extends CrudRepository<Book, Integer> {
}

编写单元测试

测试restful接口

引入单元测试依赖

java 复制代码
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>
java 复制代码
// @SpringBootTest修饰单元测试用例类
// webEnvironment属性指定了测试环境的web环境,这里使用随机端口。不需要知道具体端口号,Spring Boot会自动分配一个可用的端口
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RandomPortTest {
    @Autowired
    private TestRestTemplate restTemplate;

    public void testIndexRest() {
        // 使用TestRestTemplate发送请求,测试RESTful接口
        String response = restTemplate.getForObject("/rest", String.class);
        Assertions.assertEquals("Hello, World from REST!", response);
    }

    //这个注解告诉 JUnit 测试是参数化的,并且有测试值注入到其中。
    @ParameterizedTest
    @CsvSource({"西柚击,黧黑,129.0", "Java编程思想,布鲁斯·埃克尔,99.0"})
    public void testRestAddBook(String title,String author, Double price) {
        var book = new com.example.ali2.domain.Book(title, author, price);
        // 使用TestRestTemplate发送请求
        var response = restTemplate.postForObject("/rest/books",book, Map.class);
        Assertions.assertEquals(response.get("tip"), "Book added successfully!");
    }

    @Test
    public void testRestList() {
        // 使用TestRestTemplate发送请求,测试获取书籍列表
        var result = restTemplate.getForObject("/rest/books", List.class);
        result.forEach(System.out::println);
    }

    @ParameterizedTest
    @ValueSource(ints = {4,5})
    public void testRestDelete(Integer id) {
        // 使用TestRestTemplate发送请求,测试删除书籍
        restTemplate.delete("/rest/books/{0}",id);
    }
}

模拟web环境测试控制器

如果想测试普通的控制器处理方法,比如读取方法返回的ModelAndView,则可使用MockMVC。

java 复制代码
// WebEnvironment.MOCK意味着启动模拟的web环境,不会启动实际的服务器
// WebEnvironment的默认值是WebEnvironment.Mock,其实不写也行
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
// @AutoConfigureMockMvc注解会自动配置MockMvc对象
@AutoConfigureMockMvc
public class MockEnvTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testIndex() throws Exception {
        // 使用MockMvc发送请求,测试首页
       var result = mockMvc.perform(MockMvcRequestBuilders.get(new URI("/"))).andReturn().getModelAndView();
        Assertions.assertEquals(Map.of("tip", "Hello, World!"), result.getModel());
        Assertions.assertEquals("hello", result.getViewName());
    }

    @ParameterizedTest
    @CsvSource({"疯狂Java讲义, 李刚, 129.0", "疯狂Android讲义, 李刚, 128.0"})
    public void testAddBook(String title, String author, double price) throws Exception
    {
        // 测试addBook方法
        var result = mockMvc.perform(MockMvcRequestBuilders.post(new URI("/addBook"))
                        .param("title", title)
                        .param("author", author)
                        .param("price", price + ""))
                .andReturn().getModelAndView();
        Assertions.assertEquals("redirect:listBooks", result.getViewName());
    }

    @Test
    public void testList() throws Exception
    {
        // 测试list方法
        var result = mockMvc.perform(MockMvcRequestBuilders.get(new URI("/listBooks")))
                .andReturn().getModelAndView();
        Assertions.assertEquals("list", result.getViewName());
        List<Book> books = (List<Book>) result.getModel().get("books");
        books.forEach(System.out::println);
    }

    @ParameterizedTest
    @ValueSource(ints = {7, 8})
    public void testDelete(Integer id) throws Exception
    {
        // 测试delete方法
        var result = mockMvc.perform(MockMvcRequestBuilders.get("/deleteBook?id={0}", id))
                .andReturn().getModelAndView();
        Assertions.assertEquals("redirect:listBooks", result.getViewName());
    }
}

测试业务组件

如果只是测试service或者Dao组件,则不需要启动web服务器。webEnvironment = WebEnvironment.NONE表示不启动web服务器

java 复制代码
// WebEnvironment.NONE意味着不启动实际的web环境,适用于只测试服务层逻辑的情况
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class BookServiceTest {
    @Autowired
    private BookService bookService;

    @Test
    public void testGetAllBooks()
    {
        bookService.getAllBooks().forEach(System.out::println);
    }

    @ParameterizedTest
    @CsvSource({"疯狂Java讲义, 李刚, 129.0", "疯狂Android讲义, 李刚, 128.0"})
    public void testAddBook(String title, String author, double price)
    {
        var book = new Book(title, author, price);
        Integer result = bookService.addBook(book);
        System.out.println(result);
        Assertions.assertNotEquals(result, 0);
    }

    @ParameterizedTest
    @ValueSource(ints = {9, 10})
    public void testDeleteBook(Integer id)
    {
        bookService.deleteBook(id);
    }
}

使用模拟组件

实际应用中,组件可能需要依赖其他组件来访问数据库,或者调用第三方接口,为避免这些不稳定因素影响单元测试效果,使用mock组件来模拟这些不稳定的组件。

比如BookDao未开发出来,如果对BookService测试,需要使用mock来模拟BookDao。

java 复制代码
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MockTest {
    // 定义要测试的目标组件:BookService
    @Autowired
    private BookService bookService;

    // 使用@MockBean注解模拟BookDao组件
    @MockBean
    private BookDao bookDao;

    @Test
    public void testGetAllBooks()
    {
        // 模拟bookDao的findAll()方法的返回值
        BDDMockito.given(this.bookDao.findAll()).willReturn(
                List.of(new Book("测试1", "李刚", 89.9),
                        new Book("测试2", "yeeku", 99.9)));
        List<Book> result = bookService.getAllBooks();
        Assertions.assertEquals(result.get(0).getTitle(), "测试1");
        Assertions.assertEquals(result.get(0).getAuthor(), "李刚");
        Assertions.assertEquals(result.get(1).getTitle(), "测试2");
        Assertions.assertEquals(result.get(1).getAuthor(), "yeeku");
    }
}
相关推荐
程序猿小D1 小时前
[附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+jsp实现的个人财务管理系统,推荐!
java·数据库·mysql·spring·毕业论文·ssm框架·个人财务管理系统
转转技术团队2 小时前
二奢仓店的静默打印代理实现
java·后端
钢铁男儿2 小时前
C# 接口(什么是接口)
java·数据库·c#
丶小鱼丶2 小时前
排序算法之【归并排序】
java·排序算法
上上迁2 小时前
分布式生成 ID 策略的演进和最佳实践,含springBoot 实现(Java版本)
java·spring boot·分布式
永日456702 小时前
学习日记-spring-day42-7.7
java·学习·spring
龙谷情Sinoam3 小时前
扩展若依@Excel注解,使其对字段的控制是否导出更加便捷
java
二十雨辰3 小时前
[尚庭公寓]07-Knife快速入门
java·开发语言·spring
掉鱼的猫3 小时前
Java MCP 实战:构建跨进程与远程的工具服务
java·openai·mcp
我爱Jack3 小时前
时间与空间复杂度详解:算法效率的度量衡
java·开发语言·算法