SpringBoot
一、快速入门
1.1 创建项目
1.左上文件==新建==项目==左侧选择Maven Archetype==输入项目名HelloWorld,位置~\IdeaProjects
2.选择maven-archetype-webapp==点开高级设置==鼠标往下拉==组ID输入com.tys和版本号1.0==点击创建
3.对着src文件==右键新建==目录==全选==回车
1.2 引入依赖
//pom.xml 复制进去,刷新Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tys</groupId>
<artifactId>HelloWorld</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
1.3 编写业务
//对着src/main/java==右键新建==软件包==com.tys.boot.controller
//对着com.tys.boot.controller==右键新建==Java类==HelloController
package com.tys.boot.controller;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@Controller
@ResponseBody
public class HelloController {
@RequestMapping("/hello")
public String handle01(){
return "Hello, Spring Boot 2!";
}
}
1.4 创建主程序
//对着src/main/java==右键新建==Java类==com.tys.boot.MainApplication
package com.tys.boot;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
//点击第7行的小箭头,控制台出现多少秒,打开浏览器
//访问:localhost:8080/hello 看见网页出现 Hello, Spring Boot 2! 代表成功
二、底层注解
2.1 @Configuration
1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
2、配置类本身也是组件
3、proxyBeanMethods:代理bean的方法,有两种
Full模式(proxyBeanMethods = true)(默认值,每个@Bean方法被调用多少次返回的组件都是单实例的)
Lite模式(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)
//告诉SpringBoot这是一个配置类 == 配置文件
@Configuration(proxyBeanMethods = false)
public class MyConfig {
//给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型
@Bean
public User user01(){
User zhangsan = new User("zhangsan", 18);
zhangsan.setPet(tomcatPet()); //user组件依赖了Pet组件
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
2.2 @Import
//给容器中自动创建出这两个类型的组件,默认组件的名字就是全限定名
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false)
public class MyConfig {
}
2.3 @Conditional
//@Conditional是条件装配:满足指定的条件,则进行组件注入
//以@ConditionalOnMissingBean进行举例说明
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "tom")//没有tom名字的Bean时,MyConfig类的Bean才能生效。
public class MyConfig {
@Bean
public User user01(){
User zhangsan = new User("zhangsan", 18);
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom22")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
2.4 @ImportResource
//beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans ...">
<bean id="haha" class="com.lun.boot.bean.User">
<property name="name" value="zhangsan"></property>
<property name="age" value="18"></property>
</bean>
<bean id="hehe" class="com.lun.boot.bean.Pet">
<property name="name" value="tomcat"></property>
</bean>
</beans>
======================================================================================= //比如公司使用bean.xml文件生成配置bean,而非注解@bean
//使用@ImportResource导入配置文件
@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}
2.5 @ConfigurationProperties
//配置文件application.properties
mycar.brand=BYD
mycar.price=100000
======================================================================================= //配置绑定 如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中
//只有在容器中的组件,才会拥有SpringBoot提供的强大功能,所以需要@Component
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
Car的实体类
}
三、web开发
3.1 静态资源
# 对着/src/main/resources==右键新建==目录==static 在这放一张图片01.png
# 对着/src/main/resources==右键新建==文件==application.yaml
# /*表示拦截当前目录,/**表示拦截所有文件包括子文件夹里的
# 前两行表示访问路径是以static开头,localhost:8080/static/xx.png
# 后两行表示告诉spring,静态资源在项目的static文件夹下
spring:
mvc:
static-path-pattern: /static/**
resources:
static-locations: [classpath:/static/]
# 浏览器访问:localhost:8080/static/01.png
=======================================================================================
# 欢迎页
# 在/src/main/resources/static路径下,新建index.html,也可以在controller层指定
spring:
# mvc:
# static-path-pattern: /static/** 有了url前缀,会导致欢迎页功能失效
resources:
static-locations: [classpath:/static/]
#访问:localhost:8080 会自动打开index.html
=======================================================================================
# Favicon图标
# 在/src/main/resources/static路径下,放入favicon.ico
spring:
# mvc:
# static-path-pattern: /static/** 有了url前缀,会导致小图标功能失效
resources:
static-locations: [classpath:/static/]
#访问:localhost:8080
3.2 Restful风格
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启网页表单的Restful功能
<form action="/user" method="get">
<input value="GET提交" type="submit" />
</form>
<form action="/user" method="post">
<input value="POST提交" type="submit" />
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="DELETE"/>
<input value="DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT" />
<input value="PUT提交"type="submit" />
<form>
//springboot出了新注解,其实两种注解方式功能实现一样的
@GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
3.3 获取request域中的值
//此注解@RequestAttribute,获取request域中的值
@Controller
public class RequestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","成功了...");
request.setAttribute("code",200);
return "forward:/success"; //转发到success页面
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(value = "msg",required = false) String msg,
@RequestAttribute(value = "code",required = false) Integer code){
Map<String,Object> map = new HashMap<String,Object>();
map.put("msg",msg);
map.put("code",code);
return map;
}
}
3.4 自定义类型转换器
//Pet实体类,以前传name="啊猫" age=3
//现在直接逗号分割,直接传"啊猫,3"
@Bean
public WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
3.5 拦截器
//编写一个拦截器实现登录检查,没有账号登录,直接输入网址,进不了后台管理页面
//对着src/main/java==右键新建==软件包==com.tys.boot.interceptor
//对着com.tys.boot.interceptor==右键新建==Java类==LoginInterceptor
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
return true;
}
request.setAttribute("msg","请先登录");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}",modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}",ex);
}
}
//拦截器注册到容器中并且指定拦截规则
//对着src/main/java==右键新建==软件包==com.tys.boot.config
//对着com.tys.boot.config==右键新建==Java类==AdminWebConfig
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())//拦截器注册到容器中
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
"/js/**","/aa/**"); //放行的请求
3.6 文件上传
//页面代码/static/form/form_layouts.html
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<input type="file" name="photos" multiple>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
//对着com.tys.boot.controller==右键新建==Java类==FormTestController
@Controller
public class FormTestController {
@GetMapping("/form_layouts")
public String form_layouts(){
return "form/form_layouts";
}
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
if(!headerImg.isEmpty()){
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("D:\\Test\\"+originalFilename));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("D:\\Test\\"+originalFilename));
}
}
}
return "main";
}
}
//在application.properties修改文件上传大小,单文件上传最大10MB,全部文件上传最大100MB
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
3.7 异常处理
1.默认情况下,SpringBoot提供处理所有错误的映射
2.机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息,如下图
3.浏览器客户端,响应一个“whitelabel”错误视图,以HTML格式呈现相同的数据
4.自定义异常处理页:在/templates/error/文件夹下新建404.html 5xx.html即可
5.出现异常,springboot会自动跳转到该页面,404跳转到404.html,5开头的错误跳转到5xx.html
{
"timestamp": "2020-11-22T05:53:28.416+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/asadada"
}
3.8 Servlet相关API
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {
@Bean
//Servlet
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet,"/my");
}
@Bean
//Filter 拦截器
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter();
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
return filterRegistrationBean;
}
@Bean
//Listener 监听器
public ServletListenerRegistrationBean myListener(){
MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
return new ServletListenerRegistrationBean(mySwervletContextListener);
}
}
3.9 矩阵变量
//SpringBoot默认是禁用了矩阵变量的功能
//通过实现WebMvcConfigurer接口的方式,手动开启矩阵变量
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
========================================================================================
//编写业务代码,矩阵变量主要用于禁用cookie,实现url重写
@RestController
public class ParameterTestController {
//cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<String,Object>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
}
3.10 开启内容协商
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
http://localhost:8080/test/person?format=json
或
http://localhost:8080/test/person?format=xml
这样,后端会根据参数format的值,返回对应json或xml格式的数据。
实现多协议数据兼容。json、xml、x-guigu(这个是自创的)
1. @ResponseBody响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
2. Processor 处理方法返回值。通过 MessageConverter处理
3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
4. 内容协商找到最终的messageConverter
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));
}
}
}
}
四、单元测试
4.1 引入依赖
//pom.xml 复制进去,刷新Maven
//SpringBoot2.4以上版本移除了对 Vintage的依赖,如果需要继续兼容Junit4需要自行引入Vintage依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
4.2 常用注解
//对着src/test/java==右键新建==软件包==com.tys.boot
//对着com.tys.boot==右键新建==Java类==Junit5Test
package com.tys.boot;
import org.junit.jupiter.api.*;
import java.util.*;
import java.time.*;
import java.util.concurrent.*;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("junit5功能测试类")
public class Junit5Test {
@DisplayName("测试displayname注解") //为测试类或者测试方法起名字
@Test //表示该方法是测试方法
void testDisplayName() {
System.out.println(1);
}
@Disabled //禁用测试,单元测试时不执行该方法
@DisplayName("禁用测试")
@Test
void test2() {
System.out.println(2);
}
@RepeatedTest(5) //表示该测试方法重复执行5次
@DisplayName("重复执行5次")
@Test
void test3() {
System.out.println(5);
}
//表示测试方法运行如果超过了500毫秒,这儿设置了600毫秒,会抛出InterruptedException异常
@DisplayName("超时测试")
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(600);
}
@BeforeEach //@BeforeEach注解:表示在每个单元测试之前执行
void testBeforeEach() {
System.out.println("单元测试就要开始了...");
}
@AfterEach //@AfterEach注解:表示在每个单元测试之后执行
void testAfterEach() {
System.out.println("单元测试结束了...");
}
@BeforeAll //@BeforeAll注解:表示在所有单元测试之前执行,需要static
static void testBeforeAll() {
System.out.println("所有测试就要开始了...");
}
@AfterAll //@AfterAll注解:表示在所有单元测试之后执行,需要static
static void testAfterAll() {
System.out.println("所有测试已经结束了...");
}
}
//点击第10行,绿色箭头,观察左侧
//绿色√代表单元测试成功,黄色×代表单元测试失败,灰色圆圈一杠代表单元测试跳过该方法,红色感叹号代表抛出异常
4.3 断言机制
//1.断言,第三个位置可以写提示信息
//2.如果中间有断言执行失败了,则后续的代码不会执行
//3.把上一个Junit5Test大括号里的内容清空,复制以下代码测试
@Test
@DisplayName("简单断言")
void simple() {
assertEquals(3, 1 + 2, "计算结果相等"); //判断两个对象是否相等
assertNotEquals(3, 1 + 1, "计算结果不等");//判断两个对象是否不等
Object obj1 = new Object();
Object obj2 = new Object();
assertSame(obj1, obj2); //判断两个对象引用是否指向同一个对象
assertNotSame(obj1, obj2); //判断两个对象引用是否指向不同的对象
assertFalse(1 > 2); //判断结果是否为 false
assertTrue(1 < 2); //判断结果是否为 true
assertNull(null); //判断结果是否为 null
assertNotNull(new Object()); //判断结果是否不为 null
}
@Test
@DisplayName("数组断言")
void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
//判断两个数组是否相等。
}
@Test
@DisplayName("组合断言")
void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
//改成1/0单元测试通过,抛出异常,1/1单元测试会报错
@Test
@DisplayName("异常断言")
void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
ArithmeticException.class, () -> System.out.println(1 / 1),"居然正常运行了?");
}
//如果测试方法时间超过1000毫秒将会异常,这儿设置了500毫秒,所以测试通过
@Test
@DisplayName("超时断言")
void timeoutTest() {
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
//通过 fail 方法直接使得测试失败
@Test
@DisplayName("快速失败")
void shouldFail() {
fail("测试失败");
}
4.4 前置条件
//前置条件可以看成是测试方法的假设,当假设不成立时,就没有继续执行后续代码的必要
//assumeTrue结果给他false,所以假设不成立,所以输出11111111不执行,代码会被跳过
@Test
@DisplayName("前置条件")
public void testassumptions() {
Assumptions.assumeTrue(false,"结果不是true");
System.out.println("11111111");
}
4.5 嵌套测试
//@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起
package com.tys.boot;
import org.junit.jupiter.api.*;
import java.util.*;
import java.time.*;
import java.util.concurrent.*;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("嵌套测试")
public class Junit5Test {
Stack<Object> stack;
@Test
@DisplayName("创建一个栈")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("当创建一个栈时,此时栈没有元素")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("判断此时栈是否为空")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("出栈一个元素,此时栈为空,抛出异常")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("查看栈的第一个元素,此时栈为空,抛出异常")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("在放入一个元素后")
class AfterPushing {
String anElement = "aaa";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("此时栈不为空")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("在弹出一个元素后栈是否为空")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
}
}
}
4.6 参数化测试
//都需要加上@ParameterizedTest注解,下面五个选一个加上
//@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
//@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
//@NullSource: 表示为参数化测试提供一个null的入参
//@EnumSource: 表示为参数化测试提供一个枚举入参
//@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
package com.tys.boot;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
import java.util.stream.*;
@DisplayName("参数化测试")
public class Junit5Test {
//参数化测试 常见用法
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("常见用法")
void testdemo1(String str) {
System.out.println(str);
}
//参数化测试 指定方法名
@ParameterizedTest
@MethodSource("fruits")
@DisplayName("指定方法名")
void testdemo2(String name) {
System.out.println(name);
}
static Stream<String> fruits() {
return Stream.of("apple", "banana");
}
}
五、数据访问-环境整合
5.1 整合jdbcTemplate
//pom.xml 复制进去,刷新Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
//application.yaml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/companydb
username: root
password: 123456
//对着/src/test/java==右键新建==软件包==com.tys.boot
//对着com.tys.boot==右键新建==Java类==DAOTest
package com.tys.boot;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.context.*;
import org.springframework.jdbc.core.*;
@SpringBootTest
public class DAOTest {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
Long aLong = jdbcTemplate.queryForObject("select count(*) from t_jobs", Long.class);
System.out.println("记录总数:"+aLong);
}
}
5.2 整合MyBatis
USE companydb;
CREATE TABLE account_tb (
`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` VARCHAR(255),
`money` INT(11)
)CHARSET=utf8;
INSERT INTO account_tb(user_id,money) VALUES ('1001',5000);
INSERT INTO account_tb(user_id,money) VALUES ('1002',8000);
SELECT * FROM account_tb;
DROP TABLE account_tb;
//pom.xml 复制进去,刷新Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
//在/src/main/resources==右键新建==文件==application.yaml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/companydb
username: root
password: 123456
mybatis:
config-location: classpath:mybatis/sqlMapConfig.xml
mapper-locations: classpath:mybatis/mapper/*.xml
//对着src/main/resources==右键新建==目录==mybatis
//对着mybatis==右键新建==文件==sqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
//对着mybatis==右键新建==目录==mapper
//对着mapper==右键新建==文件==AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tys.boot.dao.AccountMapper">
<select id="getAccount" resultType="com.tys.boot.pojo.Account" parameterType="java.lang.Integer" >
select * from account_tb where id=#{id}
</select>
</mapper>
//对着src/main/java==右键新建==软件包==com.tys.boot.pojo
//对着com.tys.boot.pojo==右键新建==Java类==Account
package com.tys.boot.pojo;
public class Account {
private Integer id;
private String userId;
private Integer money;
public Account() {
}
public Account(Integer id, String userId, Integer money) {
this.id = id;
this.userId = userId;
this.money = money;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", userId='" + userId + '\'' +
", money=" + money +
'}';
}
}
//对着src/main/java==右键新建==软件包==com.tys.boot.dao
//对着com.tys.boot.dao==右键新建==Java类==AccountMapper
package com.tys.boot.dao;
import com.tys.boot.pojo.*;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AccountMapper {
Account getAccount(Integer id);
}
//对着src/main/java==右键新建==软件包==com.tys.boot.service
//对着com.tys.boot.service==右键新建==Java类==AccountService
package com.tys.boot.service;
import com.tys.boot.dao.*;
import com.tys.boot.pojo.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
public Account getAccountByid(Integer id){
return accountMapper.getAccount(id);
}
}
//对着src/main/java==右键新建==软件包==com.tys.boot.controller
//对着com.tys.boot.controller==右键新建==Java类==AccountController
package com.tys.boot.controller;
import com.tys.boot.pojo.*;
import com.tys.boot.service.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@Controller
public class AccountController {
@Autowired
private AccountService accountService;
@ResponseBody
@GetMapping("/account/{id}")
public Account getById(@PathVariable("id") Integer id){
return accountService.getAccountByid(id);
}
}
//运行主程序 访问 localhost:8080/account/1 看见json格式就对了
5.3 整合Redis
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--导入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
RedisAutoConfiguration自动配置类,RedisProperties 属性类 --> spring.redis.xxx是对redis的配置。- 连接工厂
LettuceConnectionConfiguration、JedisConnectionConfiguration是准备好的。 - 自动注入了
RedisTemplate<Object, Object>,xxxTemplate。 - 自动注入了
StringRedisTemplate,key,value都是String - 底层只要我们使用
StringRedisTemplate、RedisTemplate就可以操作Redis。
外网Redis环境搭建:
- 阿里云按量付费Redis,其中选择经典网络。
- 申请Redis的公网连接地址。
- 修改白名单,允许
0.0.0.0/0访问。
数据访问-Redis操作与统计小实验
相关Redis配置:
spring:
redis:
# url: redis://lfy:Lfy123456@r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com:6379
host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
port: 6379
password: lfy:Lfy123456
client-type: jedis
jedis:
pool:
max-active: 10
# lettuce:# 另一个用来连接redis的java框架
# pool:
# max-active: 10
# min-idle: 5
测试Redis连接:
@SpringBootTest
public class Boot05WebAdminApplicationTests {
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Test
void testRedis(){
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello","world");
String hello = operations.get("hello");
System.out.println(hello);
System.out.println(redisConnectionFactory.getClass());
}
}
Redis Desktop Manager:可视化Redis管理软件。
URL统计拦截器:
@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
//默认每次访问当前uri就会计数+1
redisTemplate.opsForValue().increment(uri);
return true;
}
}
注册URL统计拦截器:
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Autowired
RedisUrlCountInterceptor redisUrlCountInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(redisUrlCountInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
"/js/**","/aa/**");
}
}
Filter、Interceptor 几乎拥有相同的功能?
- Filter是Servlet定义的原生组件,它的好处是脱离Spring应用也能使用。
- Interceptor是Spring定义的接口,可以使用Spring的自动装配等功能。
调用Redis内的统计数据:
@Slf4j
@Controller
public class IndexController {
@Autowired
StringRedisTemplate redisTemplate;
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
log.info("当前方法是:{}","mainPage");
ValueOperations<String, String> opsForValue =
redisTemplate.opsForValue();
String s = opsForValue.get("/main.html");
String s1 = opsForValue.get("/sql");
model.addAttribute("mainCount",s);
model.addAttribute("sqlCount",s1);
return "main";
}
}
六、Thymeleaf
6.1 快速入门
//pom.xml 复制进去,刷新Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
//对着com.tys.boot.controller==右键新建==Java类==ViewTestController
//model和Map中的数据会被放在请求域中 request.setAttribute("a",aa)
package com.tys.boot.controller;
import org.springframework.stereotype.*;
import org.springframework.ui.*;
import org.springframework.web.bind.annotation.*;
@Controller
public class ViewTestController {
@GetMapping("/hello")
public String hello(Model model){
model.addAttribute("msg","一定要大力发展工业文化");
model.addAttribute("link","http://www.baidu.com");
return "success";
}
}
//对着src/main/resources==右键新建==目录==templates
//对着templates==右键新建==HTML文件==success
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试Thymeleaf</title>
</head>
<body>
<h1 th:text="${msg}">nice</h1>
<h2>
<a href="#" th:href="${link}">去百度$</a> <br/>
<a href="#" th:href="@{link}">去百度@</a>
</h2>
</body>
</html>
//启动Tomcat,访问 localhost:8080/hello
6.2 基本语法
1.文本值: 'one text'
2.数字: 0 34 3.0
3.布尔值: true false
4.空值: null
5.变量: one two
6.字符串拼接: +
7.变量替换: |The name is ${name}|
8.数学运算符: + - / %
9.布尔运算符: and or ! not
10.比较运算符: > < >= <= gt lt ge le == != eq ne
11.条件运算:
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
12.无操作: _
13.设置属性值-th:attr
//设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>
//设置多个值
<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
迭代
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
条件运算
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
| 表达式名字 | 语法 | 用途 |
|---|---|---|
| 变量取值 | ${…} | 获取请求域、session域、对象等值 |
| 选择变量 | *{…} | 获取上下文对象值 |
| 消息 | #{…} | 获取国际化等值 |
| 链接 | @{…} | 生成链接 |
| 片段表达式 | ~{…} | jsp:include 作用,引入公共页面片段 |
七、yaml
7.1 基本语法
1.key: value 冒号后要加空格
2.大小写敏感
3.使用缩进表示层级关系
4.缩进不允许使用tab,只允许空格,但idea会自动把tab转换为空格
5.缩进的空格数不重要,只要相同层级的元素左对齐即可
6.#表示注释
7.字符串无需加引号,如果要加,则单引号表示字符串会被转义,双引号表示不转义
7.2 数据类型
# 单个的不可再分的值。String Boolean Date Integer
k: v
========================================================================================
# 对象:键值对的集合。 Map 例如User对象
#行内写法:
k: {k1: v1,k2: v2,k3: v3}
#或
k:
k1: v1
k2: v2
k3: v3
=======================================================================================
# 数组:一组按次序排列的值。 array list Set
#行内写法:
k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
7.3 主程序与controller层
//对着src/main/java==右键新建==软件包==com.tys.boot.controller
//对着com.tys.boot.controller==右键新建==Java类==TestController
package com.tys.boot.controller;
import com.tys.boot.pojo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@Controller
@ResponseBody
public class TestController {
@Autowired
Person person;
@RequestMapping("/person")
public Person person(){
return person;
}
}
=======================================================================================
//对着src/main/java==右键新建==Java类==MainApplication
package com.tys.boot;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
7.4 实体类
//对着src/main/java==右键新建==软件包==com.tys.boot.pojo
//对着com.tys.boot.pojo==右键新建==Java类==Person
//添加无参有参,get/set方法,tostring方法
package com.tys.boot.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.*;
import java.util.*;
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private String[] interests;
private List<String> animal;
private Set<Double> salary;
private Map<String, Object> score;
private Pet pet;
private Map<String, List<Pet>> allPets;
}
=========================================================================================
//对着com.tys.boot.pojo==右键新建==Java类==Pet
//添加无参有参,get/set方法,tostring方法
package com.tys.boot.pojo;
public class Pet {
private String name;
private Double weight;
}
7.5 举例
//用yaml表示以上对象,/src/main/resources==右键新建==文件==application.yaml
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
interests: [篮球,游泳]
animal: [jerry,mario]
salary: [3000.0,4000.0]
score: {english: 80,math: 90}
pet:
name: tomcat
weight: 30.0
allPets:
slick:
- {name: 阿毛,weight: 30}
- {name: 阿狗,weight: 40}
health:
- {name: 阿花,weight: 50}
=======================================================================================
//MainApplication,运行主程序,点击第7行的小箭头,控制台出现多少秒,打开浏览器
//访问:localhost:8080/person 看见json字符串 代表成功
八、指标监控
8.1 快速入门
//pom.xml 复制进去,刷新Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
//在/src/main/resources==右键新建==文件==application.yaml
management:
endpoints:
enabled-by-default: true #开启所有监控端点信息
web:
exposure:
include: '*' #以web方式暴露所有监控端点
//运行主程序,浏览器访问以下url
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/metrics/endpointName/detailPath
8.2 常使用的端点
| ID | 描述 |
|---|---|
auditevents | 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件。 |
beans | 显示应用程序中所有Spring Bean的完整列表。 |
caches | 暴露可用的缓存。 |
conditions | 显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops | 显示所有@ConfigurationProperties。 |
env | 暴露Spring的属性ConfigurableEnvironment |
flyway | 显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。 |
health | 显示应用程序运行状况信息。 |
httptrace | 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。 |
info | 显示应用程序信息。 |
integrationgraph | 显示Spring integrationgraph 。需要依赖spring-integration-core。 |
loggers | 显示和修改应用程序中日志的配置。 |
liquibase | 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。 |
metrics | 显示当前应用程序的“指标”信息。 |
mappings | 显示所有@RequestMapping路径列表。 |
scheduledtasks | 显示应用程序中的计划任务。 |
sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown | 使应用程序正常关闭。默认禁用。 |
startup | 显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup。 |
threaddump | 执行线程转储。 |
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
| ID | 描述 |
|---|---|
heapdump | 返回hprof堆转储文件。 |
jolokia | 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core。 |
logfile | 返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。 |
prometheus | 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus。 |
其中最常用的Endpoint:
- Health:监控状况
- Metrics:运行时指标
- Loggers:日志记录
Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告。
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等。
- 可以很容易的添加自定义的健康检查机制。
Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到:
- 通过Metrics对接多种监控系统。
- 简化核心Metrics开发。
- 添加自定义Metrics或者扩展已有Metrics。
8.3 开启与禁用Endpoints
- 默认所有的Endpoint除过shutdown都是开启的。
- 需要开启或者禁用某个Endpoint。配置模式为
management.endpoint.<endpointName>.enabled = true
management:
endpoint:
beans:
enabled: true
- 或者禁用所有的Endpoint然后手动开启指定的Endpoint。
management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
8.4 暴露Endpoints
支持的暴露方式
- HTTP:默认只暴露health和info。
- JMX:默认暴露所有Endpoint。
- 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入Spring Security,则会默认配置安全访问规则。
| ID | JMX | Web |
|---|---|---|
auditevents | Yes | No |
beans | Yes | No |
caches | Yes | No |
conditions | Yes | No |
configprops | Yes | No |
env | Yes | No |
flyway | Yes | No |
health | Yes | Yes |
heapdump | N/A | No |
httptrace | Yes | No |
info | Yes | Yes |
integrationgraph | Yes | No |
jolokia | N/A | No |
logfile | N/A | No |
loggers | Yes | No |
liquibase | Yes | No |
metrics | Yes | No |
mappings | Yes | No |
prometheus | N/A | No |
scheduledtasks | Yes | No |
sessions | Yes | No |
shutdown | Yes | No |
startup | Yes | No |
threaddump | Yes | No |
若要更改公开的Endpoint,请配置以下的包含和排除属性:
| Property | Default |
|---|---|
management.endpoints.jmx.exposure.exclude | |
management.endpoints.jmx.exposure.include | * |
management.endpoints.web.exposure.exclude | |
management.endpoints.web.exposure.include | info, health |
8.5 指标监控-定制Endpoint
定制 Health 信息
management:
health:
enabled: true
show-details: always #总是显示详细信息。可显示每个模块的状态信息
通过实现HealthIndicator接口,或继承MyComHealthIndicator类。
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
/*
构建Health
Health build = Health.down()
.withDetail("msg", "error service")
.withDetail("code", "500")
.withException(new RuntimeException())
.build();
*/
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {
/**
* 真实的检查方法
* @param builder
* @throws Exception
*/
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//mongodb。 获取连接进行测试
Map<String,Object> map = new HashMap<>();
// 检查完成
if(1 == 2){
// builder.up(); //健康
builder.status(Status.UP);
map.put("count",1);
map.put("ms",100);
}else {
// builder.down();
builder.status(Status.OUT_OF_SERVICE);
map.put("err","连接超时");
map.put("ms",3000);
}
builder.withDetail("code",100)
.withDetails(map);
}
}
定制info信息
常用两种方式:
- 编写配置文件
info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@
- 编写InfoContributor
import java.util.Collections;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息
定制Metrics信息
增加定制Metrics:
class MyService{
Counter counter;
public MyService(MeterRegistry meterRegistry){
counter = meterRegistry.counter("myservice.method.running.counter");
}
public void hello() {
counter.increment();
}
}
//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
定制Endpoint
@Component
@Endpoint(id = "container")
public class DockerEndpoint {
@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("info","docker started...");
}
@WriteOperation
private void restartDocker(){
System.out.println("docker restarted....");
}
}
场景:
- 开发ReadinessEndpoint来管理程序是否就绪。
- 开发LivenessEndpoint来管理程序是否存活。
8.6 可视化指标监控
可视化指标监控
What is Spring Boot Admin?
codecentric’s Spring Boot Admin is a community project to manage and monitor your Spring Boot ® applications. The applications register with our Spring Boot Admin Client (via HTTP) or are discovered using Spring Cloud ® (e.g. Eureka, Consul). The UI is just a Vue.js application on top of the Spring Boot Actuator endpoints.
九、高级特性
9.1 Profile环境切换
为了方便多环境适配,Spring Boot简化了profile功能。
- 默认配置文件
application.yaml任何时候都会加载。 - 指定环境配置文件
application-{env}.yaml,env通常替代为test, - 激活指定环境
- 配置文件激活:
spring.profiles.active=prod - 命令行激活:
java -jar xxx.jar --spring.profiles.active=prod --person.name=haha(修改配置文件的任意值,命令行优先)
- 配置文件激活:
- 默认配置与环境配置同时生效
- 同名配置项,profile配置优先
@Profile条件装配功能
@Data
@Component
@ConfigurationProperties("person")//在配置文件中配置
public class Person{
private String name;
private Integer age;
}
application.properties
person:
name: lun
age: 8
public interface Person {
String getName();
Integer getAge();
}
@Profile("test")//加载application-test.yaml里的
@Component
@ConfigurationProperties("person")
@Data
public class Worker implements Person {
private String name;
private Integer age;
}
@Profile(value = {"prod","default"})//加载application-prod.yaml里的
@Component
@ConfigurationProperties("person")
@Data
public class Boss implements Person {
private String name;
private Integer age;
}
application-test.yaml
person:
name: test-张三
server:
port: 7000
application-prod.yaml
person:
name: prod-张三
server:
port: 8000
application.properties
# 激活prod配置文件
spring.profiles.active=prod
@Autowired
private Person person;
@GetMapping("/")
public String hello(){
//激活了prod,则返回Boss;激活了test,则返回Worker
return person.getClass().toString();
}
@Profile还可以修饰在方法上:
class Color {
}
@Configuration
public class MyConfig {
@Profile("prod")
@Bean
public Color red(){
return new Color();
}
@Profile("test")
@Bean
public Color green(){
return new Color();
}
}
可以激活一组:
spring.profiles.active=production
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
9.2 配置加载优先级
外部化配置
官方文档 - Externalized Configuration
Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order (with values from lower items overriding earlier ones)(1优先级最低,14优先级最高):
- Default properties (specified by setting
SpringApplication.setDefaultProperties). @PropertySourceannotations on your@Configurationclasses. Please note that such property sources are not added to theEnvironmentuntil the application context is being refreshed. This is too late to configure certain properties such aslogging.*andspring.main.*which are read before refresh begins.- Config data (such as
application.propertiesfiles) - A
RandomValuePropertySourcethat has properties only inrandom.*. - OS environment variables.
- Java System properties (
System.getProperties()). - JNDI attributes from
java:comp/env. ServletContextinit parameters.ServletConfiginit parameters.- Properties from
SPRING_APPLICATION_JSON(inline JSON embedded in an environment variable or system property). - Command line arguments.
propertiesattribute on your tests. Available on@SpringBootTestand the test annotations for testing a particular slice of your application.@TestPropertySourceannotations on your tests.- Devtools global settings properties in the
$HOME/.config/spring-bootdirectory when devtools is active.
import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.*;
@Component
public class MyBean {
@Value("${name}")//以这种方式可以获得配置值
private String name;
// ...
}
- 外部配置源
- Java属性文件。
- YAML文件。
- 环境变量。
- 命令行参数。
- 配置文件查找位置
- classpath 根路径。
- classpath 根路径下config目录。
- jar包当前目录。
- jar包当前目录的config目录。
- /config子目录的直接子目录。
- 配置文件加载顺序:
- 当前jar包内部的
application.properties和application.yml。 - 当前jar包内部的
application-{profile}.properties和application-{profile}.yml。 - 引用的外部jar包的
application.properties和application.yml。 - 引用的外部jar包的
application-{profile}.properties和application-{profile}.yml。
- 当前jar包内部的
- 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项。
9.3 自定义starter细节
starter启动原理
- starter的pom.xml引入autoconfigure依赖
autoconfigure包中配置使用
META-INF/spring.factories中EnableAutoConfiguration的值,使得项目启动加载指定的自动配置类编写自动配置类
xxxAutoConfiguration->xxxxProperties@Configuration@Conditional@EnableConfigurationProperties@Bean- …
引入starter —
xxxAutoConfiguration— 容器中放入组件 ----绑定xxxProperties---- 配置项
自定义starter
目标:创建
HelloService的自定义starter。创建两个工程,分别命名为
hello-spring-boot-starter(普通Maven工程),hello-spring-boot-starter-autoconfigure(需用用到Spring Initializr创建的Maven工程)。hello-spring-boot-starter无需编写什么代码,只需让该工程引入hello-spring-boot-starter-autoconfigure依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lun</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.lun</groupId>
<artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
hello-spring-boot-starter-autoconfigure的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lun</groupId>
<artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>hello-spring-boot-starter-autoconfigure</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
- 创建4个文件:
com/lun/hello/auto/HelloServiceAutoConfigurationcom/lun/hello/bean/HelloPropertiescom/lun/hello/service/HelloServicesrc/main/resources/META-INF/spring.factories
import com.lun.hello.bean.HelloProperties;
import com.lun.hello.service.HelloService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnMissingBean(HelloService.class)
@EnableConfigurationProperties(HelloProperties.class)//默认HelloProperties放在容器中
public class HelloServiceAutoConfiguration {
@Bean
public HelloService helloService(){
return new HelloService();
}
}
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("hello")
public class HelloProperties {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
import com.lun.hello.bean.HelloProperties;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 默认不要放在容器中
*/
public class HelloService {
@Autowired
private HelloProperties helloProperties;
public String sayHello(String userName){
return helloProperties.getPrefix() + ": " + userName + " > " + helloProperties.getSuffix();
}
}
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lun.hello.auto.HelloServiceAutoConfiguration
用maven插件,将两工程install到本地。
接下来,测试使用自定义starter,用Spring Initializr创建名为
hello-spring-boot-starter-test工程,引入hello-spring-boot-starter依赖,其pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lun</groupId>
<artifactId>hello-spring-boot-starter-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>hello-spring-boot-starter-test</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入`hello-spring-boot-starter`依赖 -->
<dependency>
<groupId>com.lun</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 添加配置文件
application.properties:
hello.prefix=hello
hello.suffix=666
- 添加单元测试类:
import com.lun.hello.service.HelloService;//来自自定义starter
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class HelloSpringBootStarterTestApplicationTests {
@Autowired
private HelloService helloService;
@Test
void contextLoads() {
// System.out.println(helloService.sayHello("lun"));
Assertions.assertEquals("hello: lun > 666", helloService.sayHello("lun"));
}
}