基于Spring 5和 Spring Boot 2
开发环境 Spring Tool Suite
文章目录
Spring概览
- Spring的核心是提供了一个容器container,通常称为Spring应用上下文(Spring application context)。用来创建和管理应用组件。
- 这些组件称为bean,在Spring的应用上下文中装配在一起,形成完整的应用。
- 通过一种基于依赖注入(dependency injection, DI)的模式将bean装配在一起。
过去Spring应用上下文使用一个或多个XML文件将bean装配在一起。
现在基于java的配置更常见,这样的基于java的配置类是和XML配置等价的。
@configuration
public class ServiceConfiguration{
@Bean
public InventoryService inventoryService(){
return new InventoryService();
}
@Bean
public ProductService productService(){
return new ProductService(inventoryService());
}
}
@configuration
注解会告诉Spring这是一个配置类,给Spring应用上下文提供bean。
Spring Initializr
Spring Initializr是一个基于浏览器的Web应用,也是一个REST API,能够生成一个Spring项目结构的骨架。
使用Spring Initializr的方式:
- 通过地址为https://start.spring.io/的web应用
- 在命令行中使用curl命令
- 在命令行中使用Spring boot命令行接口
- 在Spring Tool Suite中创建新项目
- 在IntelliJ IDEA中创建新项目
- 在NetBeans中创建新项目
Spring项目结构
这是一个典型的Maven或Gradle项目结构。
应用的源代码放到了src/main/java中,测试代码在test中,非java的资源放到了src/main/resources。
另外:
- mvnw和mvnm.cmd: 这是Maven包装器(Wrapper)脚本。
- pom.xml: 这是Maven构建规范
- TacoCloudApplication.java是Spring Boot主类,它会启动该项目
- application.properties: 这个文件提供了指定配置属性的地方
- static: 存放为浏览器提供服务的静态内容(图片、样式表、JavaScript等)
- templates: 存放用来渲染内容到浏览器的模板文件。
- ……test.java 就是测试类
构建规范
这个项目使用Maven来构建,pom.xml就是Maven的构建规范。
<artifactId>taco-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>taco-cloud</name>
- < packaging >的地方选择将应用构建成一个可执行的JAR文件,而不是WAR文件。
传统的Java Web应用都是打包成WAR文件,打包JAR是基于云思维,对于云平台JAR更友好。
<version>2.3.2.RELEASE</version>
- 表明要使用Spring Boot 2.3.2
引导应用
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication //表明这是一个Spring Boot应用
public class TacoCloudApplication {
public static void main(String[] args) {
SpringApplication.run(TacoCloudApplication.class, args);
}
}
@SpringBootApplication 注解表明这是一个Spring Boot应用。
@SpringBootApplication 是一个组合注解,组合了3个其他的注解
- @SpringBootConfiguration:将该类声明为配置类
- @EnableAutoConfiguration:启用Spring Boot的自动配置
- @ComponentScan:启用组件扫描
SpringApplication.run(TacoCloudApplication.class, args)
SpringApplication里的run()方法,执行应用的引导过程,创建Spring的应用上下文。
两个参数:配置类、命令行参数
(传递给run()的配置类不一定要和引导类相同)
测试应用
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
//使用spring的运行器
@SpringBootTest
//spring boot测试
class TacoCloudApplicationTests {
@Test
void contextLoads() {
}
}
@RunWith(SpringRunner.class)
@RunWith是JUnit的注解,它会提供一个测试运行器runner来指导JUnit如何运行测试。
可以想象给JUnit一个插件提供自定义的测试行为,整理提供的是SpringRunner,这是一个Spring提供的测试运行器,它会创建测试运行所需的Spring应用上下文。
@SpringBootTest
@SpringBootTest告诉JUnit在启动测试的时候要加上Spring boot的功能。
应用
创建两个代码构件
一个控制器类、一个视图模板
处理Web请求
Spring MVC
Spring自带了一个强大的Web框架:Spring MVC。
Spring MVC的核心是控制器(controller)的理念。
编写一个简单的控制器类:
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller //控制器
public class HomeController {
@GetMapping("/") //处理对根路径“/”的请求
public String home() {
return "home"; //返回视图名
}
}
这个类带有@controller
,让组件扫描这个类识别为一个组件。
所以Spring的组件扫描功能会自动发现它,并创建一个HomeController实例作为Spring应用上下文中的bean。
@GetMapping("/") //处理对根路径“/”的请求
public String home() {
return "home"; //返回视图名
}
home()是一个简单的控制器方法。
@GetMapping注解表明如果针对“/”发送HTTP GET请求,这个方法会处理请求。
Thymeleaf
使用Thymeleaf作为模板引擎,没有选择JSP和FreeMarker。
这些都是Java Web页面技术。
详情见补充说明。
模板名称是由逻辑视图名派生而来的,再加上“/templates/”前缀和“.html”后缀,
最终模板路径是"templates/home.html"。
定义视图
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
</head>
<body>
<h1>Welcome to ...</h1>
<img th:src="@{/images/Taco.png}" height="100" width="100"/>
</body>
</html>
< img >标签那里,使用了Thymeleaf的th:src属性和@{…}表达式,以便于引用相对于上下文路径的图片。
测试控制器
测试需要针对根路径"/"发送一个HTTP GET请求并期望得到成功结果。
package com.example.demo;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class) //针对HomeController的测试
public class HomeControllerTest {
@Autowired
private MockMvc mockMvc; //注入MockMvc
@Test
public void testHomePage()throws Exception{
mockMvc.perform(get("/")) //发起对/的GET
.andExpect(status().isOk()) //期望得到HTTP 200
.andExpect(view().name("home")) //期望得到home视图
.andExpect(content()
.string(containsString("Welcome to ..."))); //期望包含Welcome巴拉巴拉
}
}
HomeControllerTest没有使用@SpringBootTest
标记,而是添加了@WebMvcTest
注解。这是Spring Boot所提供的一个特殊测试注解,它会让整个测试在Spring MVC应用上下文中执行。
它会将HomeController注册到Spring MVC中,这样就可以发送请求了。
@WebMvcTest
同样为测试Spring MVC应用提供Spring环境的支持。
测试类被注入了一个MockMvc,能够让测试实现mockup。(模型、视觉稿?)
通过testHomePage()方法,定义了针对主页想要执行的测试。首先使用MockMvc对象对"/"(根路径)发起HTTP GET请求。
- 响应应该具备HTTP 200(OK)状态
- 视图的逻辑名应该是home
- 渲染后的视图应该包含文本Welcome to ……
Spring Boot DevTools
DevTools在初始化项目的时候作为一个依赖加入进来了,在pom.xml文件里有一个对应的依赖项。
DevTools提供了一些便利的开发期工具
- 代码变更后应用会自动重启
- 当面向浏览器的资源,如模板、JavaScript、样式表等发生变化时,会自动刷新浏览器
- 自动禁用模板缓存
- 如果使用H2数据库,内置H2控制台
DevTools并不是IDE插件,并且仅仅用于开发,能够很智能地在生产环境中把自己禁用掉。
应用自动重启
如果将DevTools作为项目的一部分,对项目中的Java代码和属性文件做出修改后,这些变更稍后就能发挥作用。DevTools会监控变更。
DevTools运行的时候,应用程序会被加载到Java虚拟机两个独立的类加载器中。
其中一个类加载器会加载你的Java代码、属性文件以及项目中"src/main"路径下几乎所有的内容。这些条目很可能会经常发生变化,另外一个类加载器会加载依赖的库,这些库不太可能经常发生变化。
当检测到变更的时候,DevTools只会重新加载包含项目代码的类加载器,并重启Spring应用上下文,在这个过程中另外一个类加载器和JVM会原封不动。这个策略非常精细,但是它能减少应用启动时间。
不足之处是自动重启无法翻译依赖项的变化。变更依赖需要重新启动应用。
浏览器自动刷新和禁用模板缓存
默认情况,Thymeleaf模板方案在配置时会缓存解析的结果,在位每个请求提供服务的时候就不用重新解析。在生产环境中可以带来一定的性能收益。
但是在开发期,缓存模板会使我们看不到模板变更的效果。
所以DevTools通过禁用所有模板缓存解决了这个问题。
Web开发
展示信息
构建领域类
package com.example.demo;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
public class Ingredient {
private final String id;
private final String name;
private final Type type;
//包装、蛋白质、蔬菜、奶酪、沙司
public static enum Type{
WRAP,PROTEIN,VEGGIES,CHEESE,SAUCE
}
}
这个Ingredient类没有getter和setter方法,以及equals()、hashCode()、toString()等方法。
这里使用了Lombok库,@Data
注解是由Lombok提供的,它会告诉Lombok生成所有缺失的方法,同时还会生成所有以final属性作为参数的构造器。
不过Lombok不是Spring库,需要自己添加依赖。
并且将Lombok作为扩展添加到IDE上。
总之有点点麻烦吧(以前觉得),,详情可以看本人以前写的博客:STS导入Lombok相关
创建控制器类
在Spring MVC框架中,控制器是最重要的参与者,主要职责是处理HTTP请求,要么将请求传递给视图以便于渲染HTML,要么直接将数据写入响应体RESTful。
package com.example.demo;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import com.example.demo.Ingredient.Type;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.*;
//SLF4J只是各种日志实现的一个接口抽象(facade)
@Slf4j
@Controller
@RequestMapping("/design")
public class DesignTacoController {
/**
* 处理GET请求视图渲染
*/
@GetMapping
public String showDesignForm(Model model) {
//Ingredient:(烹饪)原料
//构建一个列表
List<Ingredient> ingredients = Arrays.asList(
new Ingredient("FLTO","Flour Tortilla",Type.WRAP), //面粉薄饼
new Ingredient("COTO","Corn Tortilla",Type.WRAP), //玉米饼
new Ingredient("GRBF","Ground Beef",Type.PROTEIN), //碎牛肉
new Ingredient("CARN","Carnitas",Type.PROTEIN), //卡尼塔斯
new Ingredient("TMTO","Diced Tomatoes",Type.VEGGIES), //西红柿丁
new Ingredient("LETC","Lettuce",Type.VEGGIES), //生菜
new Ingredient("CHED","Cheddar",Type.CHEESE), //切达干酪
new Ingredient("JACK","Monterrey Jack",Type.CHEESE), //蒙特雷杰克
new Ingredient("SLSA","Salsa",Type.SAUCE), //萨尔萨
new Ingredient("SRCR","Sour Cream",Type.SAUCE) //酸奶油
);
Type[] types = Ingredient.Type.values();
for(Type type: types) {
//向前台传送数据
model.addAttribute(type.toString().toLowerCase(),filterByType(ingredients,type));
}
model.addAttribute("design",new Taco());
//视图的逻辑名称
return "design";
}
/**
* 处理表单提交
* 当表单提交的时候,表单的输入域会绑定到Taco对象的属性中
* 该对象会以参数的形式传递给该方法
*/
//指定方法要处理针对/design的POST请求
@PostMapping
public String processDesign(@Valid Taco design, Errors errors) {
// save the taco design
// do it in future
if(errors.hasErrors()) {
return "design";
}
log.info("Processing Design:" + design);
//返回值还是代表一个要展示给用户的视图
//redirect:表明这是一个重定向视图,完成之后用户的浏览器会重定向到/orders/current
/**
* 这里的想法是创建好Taco,用户将被重定向到一个订单页面
* 在这里用户可以创建一个订单
* 目前还没有处理这个请求的控制器
*/
return "redirect:/orders/current";
}
/**
* 过滤List对象
*/
private List<Ingredient> filterByType (List<Ingredient> ingredients, Type type){
//.stream().filter() 过滤List对象
//x -> x.getType().equals(type) 满足这个条件的
//Collectors.toList()用来结束Stream流
return ingredients.stream().filter(x -> x.getType().equals(type)).collect(Collectors.toList());
}
}
@Slf4j
是Lombok所提供的注解,在运行时,它会在这个类中自动生成一个SLF4J(Simple Logging Facade for Java)Logger。这个简单的注解也可以显式声明:
private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);
设计视图
Spring提供了多种定义视图的方法,包括JavaServer Pages(JSP),Thymeleaf,FreeMarker,Mustache和基于Groovy的模板。
这里使用Thymeleaf,需要添加依赖到pom.xml文件中。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在运行时,Spring Boot的自动配置功能会发现Thymeleaf在类路径中,因此会为Spring MVC创建支撑Thymeleaf视图的bean。
像Thymeleaf这样的视图库在设计的时候是与特定的Web框架解耦的。
这样的话,它们无法感知Spring的模型抽象,因此无法与控制器放到Model中的数据协同工作。
但是它们可以与Servlet的request属性协作。所以,在Spring将请求转移到视图之前,会把模型数据复制到request属性中,这样Thymeleaf和其他的视图模板方案就能访问到。
Thymeleaf模板就是增加一些额外元素属性的HTML,这些属性能够指导模板如何渲染request数据。
这个建议去翻专门讲Thymeleaf的手册……
<!DOCTYPE html>
<!-- xmlns 属性可以在文档中定义一个或多个可供选择的命名空间 -->
<!-- xmlns:th="http://www.thymeleaf.org" 在文档的开头处定义具有前缀的命名空间,后面可以直接用-->
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
<!-- 链接一个外部样式表 -->
<link rel="stylesheet" th:href="@{/styles.css}"/>
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="@{/images/TacoYummy.jpg}"/>
<form method="POST" th:object="${design}" th:modelAttribute="design">
<div class="grid">
<div class="ingredient-group" id="wraps">
<h3>Designate your wrap:</h3>
<!-- 在每次迭代时,配料元素都会绑定到ingredient变量上 -->
<div th:each="ingredient : ${wrap}">
<!-- 复选框元素 -->
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<!-- th:text是Thymeleaf命名空间中的属性 -->
<!-- ${}会告诉它要使用某个请求属性 也就是ingredient.name的值 -->
<!-- 为复选框提供标签 -->
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
……
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}"/>
<br/><br/>
<button>Submit your taco</button>
</div>
</form>
</body>
</html>
视图中的<form>标签,method属性被设置成了POST,form并没有声明action属性,当表单提交的时候,浏览器会收集表单中的的所有数据。并以HTTP POST请求的形式将其发送至服务器端。
在POST请求的接收端,需要有一个控制器方法处理针对/design的POST请求。
package com.example.demo;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
//使用Lombok的slf4j注释在允许期创建一个SLF4J Logger对象
//我们将会使用这个Logger记录所提交订单的详细信息
@Slf4j
@Controller
@RequestMapping("/orders")
public class OrderController {
//类级别RequestMapping和方法级别的GetMapping结合使用
//指定方法处理针对"/orders/current"的HTTP GET请求
@GetMapping("/current")
public String orderForm(Model model) {
model.addAttribute("order", new Order());
return "orderForm";
}
@PostMapping
public String processOrder(@Valid Order order,Errors errors) {
if(errors.hasErrors()) {
return "orderForm";
}
log.info("Order submitted:"+order);
return "redirect:/";
}
}
校验表单输入
Spring支持Java的Bean校验API(Bean Validation API, 也被称为JSR-303)。
package com.example.demo;
import java.util.List;
//SpringBoot 新版本没有自动导入validation 程序包 需要加依赖
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
@Data
public class Taco {
//design.html 中的checkbox元素 名字都是ingredients,还有一个name的文本输入元素
//表单中这些输入域直接对应Taco类的属性
@NotNull
@Size(min=5, message="Name must be at least 5 characters long")
private String name;
@Size(min=1, message="You must choose at least 1 ingredient")
private List<String> ingredients;
//将对象持久化到数据库的时候,通常最好有一个字段作为对象的唯一标识
private long id;
}
package com.example.demo;
import javax.validation.Valid;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.CreditCardNumber;
import lombok.Data;
@Valid
@Data
public class Order {
@NotBlank(message="Name is required")
private String name;
@NotBlank(message="Street is required")
private String street;
@NotBlank(message="City is required")
private String city;
@NotBlank(message="State is required")
private String state;
@NotBlank(message="Zip is required")
private String zip;
@CreditCardNumber(message="Not a valid credit card number")
private String ccNumber;
@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$",
message="Must be formatted MM/YY")
private String ccExpiration;
@Digits(integer=3, fraction=0, message="Invalid CVV")
private String ccCVV;
private long id;
}
除了上面的,还要给控制器方法加@Valid
注解
此外Thymeleaf提供了便捷访问Error对象的方法,借助fields及其th:errors属性。举例来说,为了展现信用卡字段的校验错误,可以添加一个<span>元素,该元素会将对错误的引用用到订单表单模板上,详情见上面的html代码。
<label for="ccNumber">Credit Card#:</label>
<input type="text" th:field="*{ccNumber}"/>
<span class="validationError"
th:if="${#fields.hasError('ccNumber')}"
th:errors="*{ccNumber}">CC num Error</span>
<span>元素使用class属性来为错误添加样式,使用th:if属性来决定是否要显示该元素。
fields属性的hasError()方法会检查ccNumber域是否存在错误,如果存在,将会渲染<span>
th:errors属性引用了ccNumber输入域,如果该输入域存在错误,那么它会将<span>元素的占位符内容替换为校验信息。
视图控制器
如果一个控制器非常简单,不需要填充模型或者处理输入,还有一种方式可以定义控制器
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Override
//registry:登记
/**用这个注册一个或多个视图控制器*/
public void addViewControllers(ViewControllerRegistry registry) {
//将"/"传递进去,视图控制器将会针对该路径执行GET请求
//这个方法会返回ViewControllerRegistration对象
//基于这个对象调用setViewName方法
//用它指明当请求“/”时要转发到home视图上
registry.addViewController("/").setViewName("home");
}
}
WebMvcConfigurer
接口定义了多个方法来配置Spring MVC。尽管只是一个接口,但是提供了所有方法的默认实现,只需要覆盖所需的方法即可。
补充说明
REST API
REST指一组架构约束条件和原则,满足约束条件和原则的应用程序设计。
架构,软件体系结构分为三部分:构建,用于描述计算机;连接器,用于描述构建的链接部分;配置将构建和连接器组成有机整体。
web基本技术:
URI(统一资源标示符)HTTP(超文本传输协议)(post、get、put、delete)
Hypertext。
1、每个资源都应该有唯一的一个标识
2、使用标准的方法更改资源的状态
3、request和response的自描述
4、资源多重表述
5、无状态服务
关于Maven
-
Maven于java有点像pip于python。
-
Maven是Apache下的一个开源项目,用于项目的构建。使用Maven可以对项目的依赖包进行管理,它也支持构建脚本的继承。
-
Maven本身的插件机制很灵活,可以配置各种Maven插件来完成各种事情。如果觉得插件不够用,还可以编写自己的Maven插件。Maven为使用者提供了一个统一的依赖仓库,各种开源项目的发布包都可以在上面找到。
-
在Maven的众多特性中,最重要的是对依赖包的管理。Maven将项目所使用的依赖包的信息放到pom.xml的dependencies节点。只需在pom.xml中配置该模块的依赖信息,Maven会自动将模块引入到环境变量中。
JUnit
JUnit是一个java测试框架,允许定义单元测试来验证方法和类的行为。
Thymeleaf
Thymeleaf是一个现代化的服务器端Java模板引擎,适用于Web和独立环境,能够处理HTML、XML、JavaScript、CSS甚至纯文本。
Thymeleaf的主要目标是提供一种优雅和高度可维护的创建模板的方法。
它建立在自然模板的概念上,将其逻辑注入到模板文件中,其方式不会影响模板被用作设计原型。
测试的时候端口被占用
Servlet
处理请求和发送响应的过程是由一种叫做Servlet的程序来完成的,Servlet是为了解决实现动态页面而衍生的东西
Tomcat是Web应用服务器,是一个Servlet/JSP容器。
Tomcat作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。
Servlet是一种运行在支持Java语言的服务器上的组件。
Servlet最常见的用途是扩展Java Web服务器功能,提供非常安全的,可移植的,易于使用的CGI替代品。