你的第一个Spring Boot应用

Posted 疯狂Java图书

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你的第一个Spring Boot应用相关的知识,希望对你有一定的参考价值。

创建可执行的JAR

由于Spring Boot应用内嵌了Web服务器(TomcatJettyUndertow),所以无须将SpringBoot应用部署到其他Web服务器中,Spring Boot应用完全可以独立运行。
在发布Spring Boot应用时,只需要将该应用打包成一个可执行的JAR包,以后就可直接通过该JAR包来运行Spring Boot应用了。
为了将Spring Boot应用打包成JAR包,首先需要保证在pom.xml文件中添加了SpringBoot Maven插件,也就是其中包含如下配置:
<build> <plugins> <!--定义Spring BootMaven插件,可用于运行SpringBoot应用 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>
此外,如果在pom.xml文件中定义了<packaging.../>元素,请确保该元素的值是jar,即该元素的内容如下:  
<packaging>jar</packaging> 
省略 <packaging.../> 元素是可以的,当在 pom.xml 文件中添加了 Spring Boot Maven 插件后, <packaging.../> 元素的内容默认就是 jar ,因此可以省略该元素。
接下来只要执行如下两条Maven命令即可生成可执行的JAR包:
mvncleanmvn package 
上面第一条命令指定执行到 Maven 生命周期的 clean 阶段,用于清除所有在构建过程中生成的文件。
上面第二条命令指定执行到Maven生命周期的package阶段,用于生成可执行的JAR包。
至于如何执行“mvn1 clean”和“mvn package”命令,同样可通过上面介绍过的任意一种方法:
  • 使用命令行窗口。

  • 使用IntelliJ IDEATerminal窗口。

  • 使用Run Anything

  • 使用Maven面板。

  • 使用运行配置。


通过Maven面板的Lifecycle节点可以查看该项目构建的生命周期,该生命周期就包含了cleanpackage等阶段,双击该阶段即可执行到Maven生命周期的对应阶段。
如果成功执行完构建生命周期的package阶段,则可看到如下输出:
[INFO] ---maven-jar-plugin:3.2.0:jar (default-jar) @ firstboot ---[INFO] Buildingjar: G:\publish\codes\01\1.2\firstboot\target\firstboot-0.0.1-SNAPSHOT.jar[INFO][INFO] ---spring-boot-maven-plugin:2.4.2:repackage(repackage) @ firstboot ---[INFO] Replacing main artifact with repackaged archive[INFO]------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------
上面的输出表明:Spring Boot应用的可执行的JAR包构建完成,该JAR包的文件名为firstboot-0.0.1-SNAPSHOT.jar,该文件名中的firstbootartifactId0.0.1-SNAPSHOTversion,都是由pom.xml文件指定的。
打包完成后,即可在项目的target目录下看到一个firstboot-0.0.1-SNAPSHOT.jar包,这就是可执行的JAR包。接下来只要执行如下Java命令即可启动Spring Boot应用:  
java -jar firstboot-0.0.1-SNAPSHOT.jar 
在打包过程中很容易出现如下错误(尤其是在 Windows 平台开发 Spring Boot 应用时):
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources) on project firstboot:Input length = 1 -> [Help 1][ERROR][ERROR] To see the full stack trace of the errors,re-run Maven with the -e switch.[ERROR] Re-run Maven using the -X switch to enablefull debug logging.
导致出现该错误的原因是: Spring Boot 应用需要在 src/main/resources/ 目录下添加配置文件 application.properties (或 application. yaml ), Windows 平台默认以 GBK 字符集保存这两个文件,但 Maven 打包时默认以 UTF-8 字符集读取配置文件。解决该错误非常简单,只要将 application.properties (或 application. yaml )以 UTF-8 字符集重新保存一次即可。
提示:
使用Notepad++打开需要转码的文件,然后通过Notepad++的“Encoding”主菜单即可完成转码。
由于Maven生命周期的缘故,执行“mvn package”命令会从默认生命周期的第一阶段一直执行到package阶段,而Maven的默认生命周期包含compile(编译项目)→test(单元测试)→ package(项目打包)→ install(安装到本地仓库)→ deploy(部署到远程)这几个核心阶段,这就意味着Maven在打包之前会先执行compiletest两个阶段。
如果Spring Boot应用包含了单元测试的测试用例(1.3节会介绍单元测试),那么“mvn package”命令会先执行compile阶段(编译项目),再执行test阶段(单元测试),最后才执行package(项目打包)阶段。如果在单元测试阶段某个测试失败,则会显示类似于如下的测试失败信息:  
[ERROR] Tests run: N, Failures: M, Errors: L,Skipped: 0  
上面信息显示运行了N个测试方法,其中失败了M个,有L个出现错误。一旦在构建过程中出现上述错误,就意味着Maven无法通过test阶段,从而根本无法执行到package阶段,这样打包也会失败。解决这种错误的方法也很简单:检查并修改测试出错的单元测试,使之能通过单元测试。
如果时间仓促,来不及修改所有的单元测试,可先将那些测试出错的单元测试注释掉,这样能暂时让项目通过单元测试,打包成功。但这只是权宜之计,在项目最终发布之前,还是要保证项目的所有单元测试都能测试通过。

开发业务组件

前面控制器的处理方法直接返回了字符串作为响应,这在实际项目中肯定是不行的,实际项目中的控制器要调用业务组件来处理用户响应,因此本例会开发一个业务组件来处理用户请求。
为了和前一个简单的示例区分开,此处将前一个项目复制一份,重命名为“firstboot2”。
本例的业务组件要能实现添加图书、删除图书、列出全部图书这三个功能。下面是本例业务组件的接口代码。
程序清单:
public interface BookService{ List<Book>getAllBooks(); Integer addBook(Book book); void deleteBook(Integer id);}
Service 组件的实现类则调用 DAO 组件的方法来实现上述方法。下面是 BookService 组件的实现类代码。
程序清单:
@Service@Transactional(propagation =Propagation.REQUIRED, timeout = 5)public class BookServiceImplimplements BookService{ // 依赖注入容器中的BookDao组件 @Autowired private BookDao bookDao; @Override publicList<Book> getAllBooks(){ return(List<Book>) bookDao.findAll(); } @Override publicInteger addBook(Book book){ bookDao.save(book); returnbook.getId(); } @Override publicvoid deleteBook(Integer id){ bookDao.deleteById(id); }}
上面第1行代码使用了@Service注解修饰该实现类,且该实现类位于org.crazyit. firstboot.service.impl包下,也就是位于FirstbootApplication类所在包的子包下,因此Spring Boot会自动扫描该实现类,并将它配置成容器中的Bean
上面第2行代码使用了@Transactional注解修饰该Service组件,该注解指定事务传播规则为“REQUIRED”,事务超时时长为5秒,Spring将会为该Service组件生成事务代理,从而为该Service组件中的每个方法都添加事务。为目标组件生成事务代理是Spring AOP的功能,但生成事务代码所需要的事务管理器,同样由Spring Boot的自动配置提供。
上面第3行代码使用了@Autowired注解修饰BookDao实例变量,这也是Spring的基本用法,Spring将会把容器中唯一的、类型为BookDaoBean注入该实例变量
接下来要对前面的控制器类进行一些修改,让SpringBookService组件注入控制器,控制器调用BookService的方法来处理用户请求。
下面是修改后的BookController类的代码。
程序清单:
@Controllerpublic class BookController{ @GetMapping("/") publicString index(Model model) { model.addAttribute("tip", "欢迎访问第一个Spring Boot应用"); return"hello"; } @GetMapping("/rest") @ResponseBody public ResponseEntity restIndex() { returnnew ResponseEntity<>("欢迎访问第一个Spring Boot应用", null, HttpStatus.OK); } @Autowired privateBookService bookService;  @PostMapping("/addBook") publicString addBook(Book book, Model model) { bookService.addBook(book); return"redirect:listBooks"; } @PostMapping("/rest/books") @ResponseBody public ResponseEntity<Map<String, String>> restAddBook(@RequestBodyBook book) { bookService.addBook(book); returnnew ResponseEntity<>(Map.of("tip","添加成功"), null, HttpStatus.OK); }  @GetMapping("/listBooks") publicString list(Model model) { model.addAttribute("books", bookService.getAllBooks()); return"list"; } @GetMapping("/rest/books") @ResponseBody public ResponseEntity<List<Book>> restList() { returnnew ResponseEntity<>(bookService.getAllBooks(), null, HttpStatus.OK); }  @GetMapping("/deleteBook") publicString delete(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); returnnew ResponseEntity<>(Map.of("tip","删除成功"), null, HttpStatus.OK); }}
上面的BookController类增加了一个BookService实例变量,且使用了@Autowired注解修饰,因此Spring就会将容器中唯一的、类型为BookServiceBean注入该实例变量。
接下来该BookController定义了6个处理方法,这些处理方法使用了@GetMapping@PostMapping@DeleteMapping等注解修饰,映射这些处理方法能处理来自不同URL地址的请求。这些注解都是@RequestMapping的简化版,用于指定被修饰的方法仅能处理GETPOSTDELETE请求。这些注解都属于Spring MVC的基本注解,并不属于Spring Boot
上面这些方法定义了两个版本:带界面响应的版本和RESTful版本。使用@ResponseBody修饰的方法就是用于生成RESTful响应的方法。
对于RESTful响应的处理方法,SpringBoot应用无须提供视图页面,在前后端分离的架构中,前端应用会负责提供用户界面、处理用户交互。Spring Boot应用只要暴露RESTful接口即可。
对于要生成界面的处理方法,程序还需要为它们提供视图页面。首先在前面的hello.html页面中增加一个表单,该表单供用户填写图书信息。修改后的hello.html页面代码如下。
程序清单
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <metacharset="UTF-8"/> <title>第一个Spring Boot应用</title> <!-- 引用WarJar中的静态资源--> <link rel="stylesheet"th:href="@{/webjars/bootstrap/4.5.3/css/ bootstrap.min.css}"/> <scripttype="text/javascript" th:src="@{/webjars/jquery/3.5.1/jquery.js}"> </script></head><body><div> <!-- 使用th:text将表达式的值绑定到标准HTML元素 --> <divclass="alert alert-primary"th:text="${tip}"></div> <h2>添加图书</h2> <formmethod="post" th:action="@{/addBook}"> <divclass="form-group row"> <labelfor="title" class="col-sm-3 col-form-label">图书名:</label> <divclass="col-sm-9"> <input type="text" id="title"name="title" class="form-control"placeholder="输入图书名"> </div> </div> <divclass="form-group row"> <labelfor="author" class="col-sm-3 col-form-label">作者:</label> <divclass="col-sm-9"> <input type="text" id="author"name="author" class="form-control"placeholder="输入作者"> </div> </div> <divclass="form-group row"> <labelfor="price" class="col-sm-3 col-form-label">价格:</label> <div> <input type="number" step="0.1"id="price" name="price" class="form-control"placeholder="输入价格"> </div> </div> <divclass="form-group row"> <divclass="col-sm-6 text-right"> <button type="submit" class="btnbtn-primary">添加</button> </div> <divclass="col-sm-6"> <button type="reset" class="btnbtn-danger">重设</button> </div> </div> </form></div></body></html> 
上面页面中添加了一个Bootstrap样式的表单,该表单的界面看起来会比较美观,该表单的提交地址是addBook,与前面BookController中处理方法定义的处理地址对应。
还需要一个list.html页面用于显示所有图书,该页面代码如下。
程序清单:
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <metacharset="UTF-8"/> <title>所有图书</title> <!-- 引用WarJar中的静态资源--> <link rel="stylesheet"th:href="@{/webjars/bootstrap/4.5.3/css/bootstrap.min.css}"/> <scripttype="text/javascript" th:src="@{/webjars/jquery/3.5.1/jquery.js}"> </script></head><body><div> <h2>全部图书</h2> <tableclass="table table-hover"> <tr> <th>书名</th> <th>作者</th> <th>价格</th> <th>操作</th> </tr> <trth:each="book : ${books}"> <tdth:text="${book.title}">书名</td> <tdth:text="${book.author}">作者</td> <tdth:text="${book.price}">0</td> <td><ath:href="@{/deleteBook?id=}+ ${book.id}">删除</a></td> </tr> </table> <divclass="text-right"><a class="btnbtn-primary" th:href="@{/}">添加图书</a></div></div></body></html>
该页面代码中的第21行代码使用th:each标签对指定集合进行迭代,这也是Thymeleaf的功能。Thymeleaf与传统视图技术的最大区别在于:传统视图技术总是使用额外的标签来控制页面数据的显示;而Thymeleaf则使用额外的th:*属性来控制页面数据的显示。这样做的好处在于:浏览器能自动忽略HTML标签中不认识的属性(th:*属性),因此,即使在不执行动态解析的情况下,也可直接使用浏览器查看Thymeleaf页面效果。

开发DAO组件

前面BookService中用到了BookDao组件和Book类,这些都是与持久化相关的类,本例直接使用SpringBoot Data JPA来访问数据库,为此首先要为项目添加如下依赖:
  • Spring Boot Data JPA依赖。

  • mysql数据库驱动依赖。

pom.xml文件的<dependencies.../>元素中添加如下两个子元素来添加依赖。
程序清单:
<!-- Spring Boot Data JPA依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- MySQL数据库驱动依赖 --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency> 
添加了上面的依赖之后,重新加载项目的依赖库,然后在项目的src\main\application\目录下添加一个application.properties文件—这个文件是Spring Boot项目的配置文件,当整合不同的项目时,该配置文件支持大量不同的属性,不同的属性也由不同的处理类负责读取(后面深入介绍Spring Boot整合时,会详细介绍该文件中不同属性的作用)。
此处只使用application.properties文件配置数据库的连接信息,该文件的内容如下。
程序清单
# 数据库URL地址spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC# 连接数据库的用户名spring.datasource.username=root# 连接数据库的密码spring.datasource.password=32147# 指定显示SQL语句spring.jpa.show-sql=true# 指定根据实体自动建表spring.jpa.generate-ddl=true
上面配置文件指定了连接数据库的基本信息:URL地址、用户名和密码,并指定了JPA能根据实体类自动建表,还会显示它所执行的SQL语句。
上面的数据库连接信息指定连接MySQLspringboot数据库,因此需要创建一个springboot数据库,仅创建数据库即可,应用启动时会自动建表。
为项目创建一个Book实体类,该实体类的代码如下。
程序清单
@Entity@Table(name = "book_inf")public class Book{ @Id @Column(name= "book_id") @GeneratedValue(strategy = GenerationType.IDENTITY) privateInteger id; privateString title; privateString author; privatedouble price; // 省略getter、setter方法 ...}
此处再次体现了Spring Boot的自动配置。上面的配置文件仅仅指定了连接数据库的基本信息,Spring Boot将会自动在容器中配置一个DataSourceBean;上面的配置文件仅仅指定了两个JPA属性,Spring Boot将会自动在容器中配置一个EntityManagerFactoryBean。这一切都是“静悄悄”地自动发生的,当然,这正是SpringBoot的职责所在。
为项目创建DAO组件:BookDao,该DAO组件的接口代码如下。
程序清单
public interface BookDaoextends CrudRepository<Book, Integer>{}
BookDao接口完全是一个空接口,它仅仅继承了CrudRepository,但它实际上已经拥有了大量方法。
这得益于Spring Data的优秀设计,继承了CrudRepository接口的BookDao不需要提供实现类,Spring Data会自动为它动态生成实现类,并将该实现类的实例部署在Spring容器中。不仅如此,Spring Data还可为BookDao动态增加很多查询方法,本书第5章会深入介绍Spring Boot整合Spring Data后的强大功能,此处暂不深入。
Spring Boot应用的主程序无须任何变化,依然只需要调用SpringApplicationrun()方法即可。该应用既提供了用户界面供浏览器访问,又提供了RESTful接口供前端应用或移动APP调用。
启动该Spring Boot应用,使用浏览器访问该应用,可以看到如图21所示的页面。

21   表单页面
在图21所示的页面中填写图书信息,然后单击“添加”按钮,即可看到如图22所示的图书列表页面。

你的第一个Spring Boot应用(2)

22   列出全部图书
单击图22所示页面中的“删除”链接,即可删除该图书。
正如在前面所看到的,该应用还提供了RESTful接口,可使用Postman来测试RESTful接口。
使用Postman向“http://localhost:8080/rest/books”发送GET请求,可以看到如图23所示的结果。

你的第一个Spring Boot应用(2)

23   发送 GET 请求测试 RESTful 接口
使用Postman向“http://localhost:8080/rest/books”发送POST请求,可以看到如图24所示的结果。

你的第一个Spring Boot应用(2)

24   发送 POST 请求测试 RESTful 接口
使用Postman向“http://localhost:8080/rest/books/1”发送DELETE请求可删除ID1的图书,此时可以看到如图25所示的结果。

你的第一个Spring Boot应用(2)

25   发送 DELETE 请求测试 RESTful 接口
以上内容摘自李刚老师的作品《 疯狂Spring Boot终极讲义 》,电商平台预订中,即刻扫码,了解图书详情,6折优惠,先领劵再结账哦


李刚老师亲自录制的 Spring Boot技术视频 ,预售秒杀中,敬请关注!
关注“ 疯狂Java图书 ”公众号,获得更多你想要的Java开发技术干货

 
   
   
 

如果希望阅读Spring Boot的后续干货好文
欢迎 在看留言分享至朋友圈 三连
点击“ 阅读原文”了解图书详情

以上是关于你的第一个Spring Boot应用的主要内容,如果未能解决你的问题,请参考以下文章