springboot速通
SpringBoot低级
SpringBoot概述
SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置,可以让开发人员不必配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
Spring缺点:
- 配置繁琐
- 依赖繁琐
SpringBoot功能:
- 自动配置
- springboot的自动配置是一个运行时的过程,考虑了众多因素才决定Spring配置应该用哪个,不该用哪个。该过程是SpringBoot自动完成的
- 起步依赖
- 就是Maven将具备某种过功能的某些包打包到一起,并提供了一些默认的功能,然后传给项目
- 辅助功能
- 提供了一些大型项目的非功能性特性,如嵌入式服务器、安全、指标、健康检测等
SpringBoot并不是对Spring功能的增强,而是提供了一种快速使用Spring的方式
SpringBoot快速入门
引导类Demo1Application
package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Demo1Application {public static void main(String[] args) {SpringApplication.run(Demo1Application.class, args);}}
HelloApplication
package com.example.demo.demos.web;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloSpringBoot {@RequestMapping("/hello")public String hello(){return "hello Spring Boot 嘤嘤嘤";}
}
application.yml
server:port: 8080
index.html
<html>
<body>
<h1>hello word!!!</h1>
<p>this is a html page</p>
</body>
</html>
运行项目后,我们可以在浏览器网页栏搜索localhost:8080就会打开对应的web程序
小结
- SpringBoot在创建项目时,使用jar的打包方式
- SpringBoot的引导类,是项目入口,运行main方法就可以完全启动项目
- 使用SpringBoot和Spring构建的项目,业务代码编写方式完全一样
SpringBoot起步依赖原理分析
SpringBoo;t提供的一种依赖管理机制,极大地简化了Spring应用程序的开发过程
-
起步依赖是一种特殊的依赖,它将一组相关的依赖项打包在一起,方便开发者快速引入。例如,
spring-boot-starter-web
包含了开发 Web 应用所需的所有依赖,如 Spring MVC、Tomcat 等。 -
作用:
- 简化依赖管理:无需依次添加每个组件,秩序引入一个起步依赖即可
- 版本一致性:依赖版本由Spring Boot进行维护,确保版本兼容
- 自动配置:引入岂不依赖后,SpringBoot会自动进行相关配置,减少手动配置的工作量
-
原理:
- Maven依赖传递机制:当引入一个起步依赖时,Maven 会自动解析并引入该依赖中声明的所有传递依赖。
- 自动配置机制:Spring Boot 通过
@EnableAutoConfiguration
注解(通常由@SpringBootApplication
指定)来启用自动配置。
-
小结:
- 在spring-boor-start-parent中定义了各种技术的版本信息,组合了一套最优搭配的技术版本
- 在各种starter中,定义了完成该功能需要的坐标集合,其中大部分版本信息来自于父工程
- 我们的工程继承parent,引入starter后,通过依赖传递,就可以简单方便的获取jar包,并且不会存在版本冲突等问题
SpringBoot配置
配置文件分类
SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties
或者application.yml(application.yaml)
进行配置
- 特点:
- 全局作用域:配置项对整个应用程序有效
- 自动加载:SpringBoot会自动加载
application.properties
和application.yml
中的配置 - 优先级:同一级目录下,properties>yml>yaml
- 默认配置文件名称:application
yaml
- 是一种能够直观的被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互,可以被支持YAML库的不同变成语言导入
- YAML文件是以数据为核心的,比传统的xml方式更加简洁
- YAML文件的拓展名可以使用
.yml
或者.yaml
比较各种配置文件
-
properties:
-
server.port=8080 servet.address=127.0.0.1
-
xml:
-
<server><port>8080</port><address>127.0.0.1</address> </server>
-
yml: (简洁,直观,以数据为核心)
-
server:port: 8080address: 127.0.0.1
YAML:基本语法
- 大小写敏感
- 数据值前边必须有空格作为分隔符
- 使用缩进表示层级关系
- 缩进下空格数目不重要,但必须有,并且相同层级的元素左侧对齐即可
- ‘# ’表示注释,从这个字符一直到行尾,都会被解析器忽略
YAML:数据格式
- 对象(map): 键值对的集合
- 数组(address): 一组按次序排序的值
- 纯量: 单个的、不可再分的值
server:port: 8080person:name: zhangsan# map的行内写法
person2: {name: zhangsan}address:- beijing- shanghai# 数组的行内写法
address2: [beijing,shanghai]mag1: 'hello \n world' # 单引号忽略转义字符
map2: "hello \n world" # 双引号识别转义字符# 参数引用
name = lisi
person:name: ${name}
读取配置文件内容
HelloController类
package com.example.demo.demos.web;import com.example.demo.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloController {@Value("${name}")private String name;@Value("${person.name}")private String name2;@Value("${address[0]}")private String address;@Value("${mag1}")private String mg1;@Value("${mag2}")private String mg2;@Autowiredprivate Environment evn;@Autowiredprivate Person person;// 方式1: 以@Value注解方式进行@RequestMapping("/hello3")public String hello3() {System.out.println(name);System.out.println(name2);System.out.println(address);System.out.println(mg1);System.out.println(mg2);System.out.println(person);return "hello3";}// 方式2: 以Environment类进行@RequestMapping("/hello2")public String hello2() {System.out.println(evn.getProperty("name"));System.out.println(evn.getProperty("address[0]"));System.out.println(evn.getProperty("mag1"));System.out.println(evn.getProperty("mag2"));System.out.println(evn.getProperty("person.name"));return evn.getProperty("person.name");}}
application.yml
server:port: 8080name: abcperson:name: zhangsamage: 200# map的行内写法
person2: {name: zhangsan}address:- beijing- shanghai# 数组的行内写法
address2: [beijing,shanghai]mag1: 'hello \n world' # 单引号忽略转义字符
mag2: "hello \n world" # 双引号识别转义字符
Person类
package com.example.demo;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@ConfigurationProperties(prefix = "person")
public class Person {// 这些属性名要和对应的yaml里面的一一对应private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}
小结:三种方式获取数据
- 用@Value注解修饰属性
- 使用@Autowired注入一个Environment对象,然后通过该对象的getProperty属性获取
- 定义一个新Bean容器管理的类,在类上添加注解@Configuration,默认识别第一级的,可以通过修改参数prefix定义前缀
profile
我们在开发Spring Boot应用时,通常同一套程序会被安装到不同环境,比如:开发、测试、生产等。其中数据库地址、服务器端口等等配置都不同,如果每次打包都要修改配置文件,那么非常麻烦,profile功能就是来进行动态配置切换的
- profile配置方式
- 多profile文件方式
- yml多文档方式
- prifile激活方式
- 配置文件
- 虚拟机参数
- 命令行参数
多profile文件方式
- application.properties
name = abc
spring.profiles.active==dev
- application-dev.properties
spring.profiles.active==dev
- application-pro.properties
spring.profiles.active==pro
- application-test.properties
spring.profiles.active==test
yml多文档方式
# 通过激活不同的 Profile,Spring Boot 会加载与该 Profile 对应的配置文件
---
server:port: 8081
spring:config:activate:on-profile: pro
---
server:port: 8082
spring:config:activate:on-profile: dev
---
server:port: 8083
spring:config:activate:on-profile: test
---spring:profiles:active: dev
Profile-小结
- profile 是用来完成不同环境下,配置动态切换功能的。
- profile 配置方式
- 多profile文件方式:提供多个配置文件,每个代表一种环境。
application-dev.properties/yml
开发环境application-test.properties/yml
测试环境application-pro.properties/yml
生产环境
- yml多文档方式:
- 在yml中使用
---
分隔不同配置
- 在yml中使用
- 多profile文件方式:提供多个配置文件,每个代表一种环境。
- profile 激活方式
- 配置文件:再配置文件中配置:
spring.profiles.active=dev
- 虚拟机参数:在VM options 指定:
-Dspring.profiles.active=dev
- 命令行参数:
java -jar xxx.jar --spring.profiles.active=dev
- 配置文件:再配置文件中配置:
内部配置加载顺序
-
我们之前的配置文件都是放在配置文件,都是放在resources文件夹,根据当前目录下,优先级的高低判断谁先被加载,比如properties > yml > yaml
-
实际开发这种,我们写的配置文件并不是都放在一起的,甚至“东一个,西一个”,这时候就需要判断谁先被加载
SpringBoot程序启动时,会从以下位置加载配置文件
- file:/config/: 当前项目下的/config目录下
- file:/ 当前项目的根目录
- classpath:/config: classpath的/config目录下
- classpath: classpath的根目录下
加载顺序为上文的排序顺序,高优先级配置的属性会生效
小结
- 内部配置,是寄托于IDEA编辑器实现的。对于编写配置(命令,实现功能),我们不仅可以在IDEA中实现,还可以打开黑窗口,输入命令,在外部实现。例如把spring项目打成jar包,在“黑窗口”运行,大家可以看我SpringBoot配置–Profile这篇博客的命令行参数内容。
对于内部配置加载顺序,记住三点
-
靠近项目,优先被加载
-
具有config下的文件,在同等情况下,优先被加载
-
同一级目录下,优先级:properties >yml >yaml
-
上面配置的第一种和第二种是不会被打包到对应的jar包里面的,因此如果我们使用package的jar包运行只会识别classpath路径下的配置文件
外部配置加载顺序
外部配置一般都是通过命令行参数实现,我们可以运行
java -jar yourapp.jar --serve.port=8090
后面那部分就是修改我们的端口号,多个参数可以之间空格隔开
java -jar yourapp.jar --serve.port=9090 --serve.servelet.context-path=/hehe
甚至我们可以直接加载外部配置文件
java -jar yourapp.jar --spring.config.location="路径"
当然,我们也可以直接将配置属性复制到对应的jar包的同一目录下,jar包运行时也会自动加载
SpringBoot官网外部配置加载顺序
- 默认属性(通过设置SpringApplication.setDefaultProperties指定)。
- 在你的@Configuration类上的@PropertySource注释。请注意,这些属性源在应用上下文刷新之前不会添加到Environment中。因此,这对于配置某些属性(如logging.和spring.main.)来说为时已晚,因为这些属性在应用上下文刷新之前被读取。
- 配置数据(例如application.properties文件)。
- 随机值属性源(RandomValuePropertySource),其属性仅在random.*。
- 操作系统环境变量。
- Java系统属性(System.getProperties())。
- 来自java:comp/env的JNDI属性。
- ServletContext init参数。
- 来自SPRING_APPLICATION_JSON的属性(内联JSON嵌入在环境变量或系统属性中)。
- 命令行参数。
- 测试中的属性属性。在@SpringBootTest和用于测试应用特定切片的测试注释中可用。
- 测试中的@DynamicPropertySource注释。
- 测试中的@TestPropertySource注释。
- 当devtools激活时,在$HOME/.config/spring-boot目录中的Devtools全局设置属性。
越往下优先级越高
SpringBoot整合其他框架
Junit
实现步骤
- 搭建SpringBoot工程
- 引入starter-test依赖
- 编写测试类
- 添加测试相关注解
- RunWith(SpringRunner.class)
- SpringBootTest(classer = 启动类.class)
- 编写测试方法
如果我们是在源根目录下写的test测试类,那么测试类中的注解@SpringBootTest的括号中不需要添加任何信息
UserService类
package com.lele.springboottest;import org.springframework.stereotype.Service;@Service
public class UserService {public void add(){System.out.println("add...");}
}
测试类UserServiceTest
package com.lele.springboottest;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;/*** UserService的测试类*/@SpringBootTest(classes = SpringbootTestApplication.class)
public class UserServiceTest {@Autowiredprivate UserService userService;@Testpublic void testadd(){userService.add();System.out.println("hello ");}
}
mybatis
实现步骤
- 搭建SpringBoot工程
- 引入mybatis起步依赖,添加mysql驱动
- 编写DataSource 和MyBatis相关配置
- 定义表和实体类
- 编写dao和mapper文件/纯注解开发
- 测试
首先创建数据库并添加两条记录
create database springboot;
use springboot;
create table t_user(id int(11) NOT NULL primary key auto_increment,username varchar(32) default null,password varchar(32) default null
);insert into t_user (id,username,password) values(1,'zhangsan',123),(2,'lisi',234);
我们在下面实现了两种方式的mybatis的注入:注解和xml文件
User类
package com.lele.springbotmybatis.domain;import org.springframework.stereotype.Component;public class User {int id;String name;int age;public User(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
UserMapper接口
package com.lele.springbotmybatis.mapper;import com.lele.springbotmybatis.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;import java.util.List;@Mapper
@Repository
public interface UserMapper {@Select("select * from t_user")public List<User> findAll();
}
UserXmlMapper接口
package com.lele.springbotmybatis.mapper;import com.lele.springbotmybatis.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;import java.util.List;@Mapper
@Repository
public interface UserMapper {@Select("select * from t_user")public List<User> findAll();
}
启动类省略,都一样没任何变化
UserXmlMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mbatis-3-mapper.dtd">
<mapper namespace="com.lele.springbotmybatis.mapper.UserXmlMapper"><select id="findAll" resultType="user">select * from t_user</select>
</mapper>
appliaction.yml
# dataSource
spring:datasource:url: jdbc:mysql://localhost:3306/springbootusername: rootpassword: 159357258zx.driver-class-name: com.mysql.cj.jdbc.Drivermybatis:mapper-locations: classpath:mapper/*Mapper.xmltype-aliases-package: com.lele.springbotmybatis.domain.User
# config-location: # 指定mybatis核心配置文件
测试类
package com.lele.springbotmybatis;import com.lele.springbotmybatis.domain.User;
import com.lele.springbotmybatis.mapper.UserMapper;
import com.lele.springbotmybatis.mapper.UserXmlMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest
class SpringbotMybatisApplicationTests{@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserXmlMapper userXmlMapper;@Testpublic void testfindALl() {List<User> all = userMapper.findAll();for (User user : all) {System.out.println(user);}}@Testpublic void testfindALl2() {List<User> all = userXmlMapper.findAll2();for (User user : all) {System.out.println(user);}}}
SpringBoot高级(原理分析)
SpringBoot其实是对Spring的高度封装,虽然用起来很方便,但是我们不知道它为什么这么方便、或者它在哪方面还能进行拓展。通过学习SpringBoot的原理分析,我们能够更好的使用SpringBoot,同时也能学到很多好的设计思想
自动配置
Condition
- Condition:在Spring4.0增加的条件判断功能,可以实现选择性的创建Bean操作
思考:SpringBoot是如何知道要创建哪个依赖的?
可以看到,当我没有导入redis依赖而直接获取Bean时,运行出错
package com.lele.springbootcondition;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {// 启动Springboot项目,并返回IOC容器ConfigurableApplicationContext context =SpringApplication.run(SpringbootConditionApplication.class, args);// 获取Bean,redisTemplateObject redisTemplate = context.getBean("redisTemplate");System.out.println(redisTemplate);context.close();}
}
错误信息:No bean named ‘redisTemplate’ available
但是当我添加依赖后,成功输出
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
- 因此我们思考,Condition可能与依赖有关,但它怎么知道我导入了相应的坐标呢
案例:在Spring的IOC容器中有一个User的Bean,现要求导入Jedis坐标后加载Bean,没导入则不加载
核心配置类UserConfig
package com.lele.springbootcondition.config;import com.lele.springbootcondition.Condition.ClassCondition;
import com.lele.springbootcondition.Condition.ConditionOnClass;
import com.lele.springbootcondition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import com.alibaba.fastjson.JSON;@Configuration
public class UserConfig {// 下面可以添加一个注解@Conditional,它需要传入一个Condition接口的实现类// 接口Condtion里面定义了一个方法,我的实现类里面需要重写该方法// 该方法返回一个boolean类型,true表示加载Bean,否则不加载@Bean//@Conditional(ClassCondition.class)@ConditionOnClass("com.alibaba.fastjson.JSON")public User user(){return new User();}
}
第一种方式 是直接使用直接使用自带的@Conditional
-
//@Conditional(ClassCondition.class)
然后定义一个实现Condition接口的方法
ClassCondition类
package com.lele.springbootcondition.Condition;import com.alibaba.fastjson.JSON;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;import java.util.Map;public class ClassCondition implements Condition {/**** @param context 上下文对象,用于获取环境、IOC容器、ClassLoader对象* @param metadata 注解元对象,用于获取注解定义的属性值* @return*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 1. 需求:导入Jedis类则加载Bean,否则不加载/*boolean flag;try {Class<?> cls = Class.forName("com.alibaba.fastjson.JSON");flag = true;} catch (ClassNotFoundException e) {flag = false;}return flag;*///2. 需求: 导入通过注解属性值value指定的坐标后创建Bean// 获取注解属性值 valueMap<String, Object> map =metadata.getAnnotationAttributes(ConditionOnClass.class.getName());// System.out.println(map);String[] value = (String[])map.get("value");boolean flag = false;try {for (String className : value) {Class<?> cls = Class.forName(className);}flag = true;} catch (ClassNotFoundException e) {flag = false;}return flag;}
}
第二种方式是我们自己定义一个注解,但还是要用系统的注解@Conditino覆盖我们自己的
ConditionOnClass注解
package com.lele.springbootcondition.Condition;import org.springframework.context.annotation.Conditional;import java.lang.annotation.*;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {String[] value();
}
启动类SpringbootConditionApplication
package com.lele.springbootcondition;import com.lele.springbootcondition.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {// 启动Springboot项目,并返回IOC容器ConfigurableApplicationContext context =SpringApplication.run(SpringbootConditionApplication.class, args);// // 获取Bean,redisTemplate
// Object redisTemplate = context.getBean("redisTemplate");
// System.out.println(redisTemplate);Object bean = context.getBean("user");System.out.println(bean);context.close();}
}
- 除了Conditional注解外,我们能还有其他注解就不一一演示了,都放在小结里面
切换内置Web服务器
SpringBoot的web环境中默认使用tomcat作为内置服务器,其中SpringBoot提供了4种内置服务器供我们选择,我们可以很方便的进行切换
我们可以在外部库/Maven:org:springframework.boot/spring-boot-autoconfigure-2.6.13,jar
/org/springframework/boot/autofigure/web/embedded里面
- Jetty
- Metty
- Tomcat
- Undertow
我们默认都是选择tomcat进行切换的,但是如果想要切换,可以修改配置文件pom.xml,在web的大包里面排除Tomcat的,然后外面再导入对应其他web内置服务器即可
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId></dependency
@Enable*注解
- SpringBoot种提供了很多Enable开头的注解,这些注解都是用于东岸提开启某些功能的。而其底层原理就是使用@Import注解导入一些配置类,实现Bean的动态加载
思考
SpringBoot工程是否可以直接获取Jar包种定义的Bean?
答案肯定是不行的,SpringBoot无法直接引用别人jar包里的Bean
那么为什么我们之前引入一个Redis
的起步依赖就可以直接获取到RedisTemplate
呢?
演示
接下来我们引入了两个模块工程springboot-enable和springboot-enable-other
springboot-enable-other的作用单纯就是提供bean类
- User类
package com.xh.config;public class UserConfig {
}
- UserConfig配置类
package com.xh.config;import com.xh.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/** 表示配置类 */
@Configuration
public class UserConfig {/** 注入 */@Beanpublic User user(){return new User();}
}
然后就是在springboot-enable工程中pom.xml中添加新的依赖
<dependency><groupId>com.xh</groupId><artifactId>springboot-enable-other</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
然后修改启动类中添加获取User类 的代码
public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
运行项目,然后就报错了,提示错误信息是No qualfied bean named ‘user’ available
-
通过演示我们可以看到SpringBoot不能直接获取我们在其他工程中定义的Bean
-
原因:在启动类中@SpringBootApplication注解中有一个@ComponentScan注解,这个注解扫描的范围是当前引导类所在包及其子包
-
我们项目的引导类包路径为:om.xh.springbootenable
而 UserConfig 所在的包路径为:com.xh.config -
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class} ), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class} )} )
方案:
- 使用@ComponentScan:我们可以在引导类上使用@ComponentScan注解扫描配置类所在的包
@SpringBootApplication
@ComponentScan("com.xh.config")
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}
这样的方案虽然可以解决这个问题,但是看起来不太行,如果获取的包的特别多的话,那么可能太多了,所以这种方案不推荐
- 使用@Import注解:被@Import注解所导入的类,都会被Spring创建,并放入IOC容器中,如图可以看到@Import注解的value值是一个数组,可以传多个值
// @Import的定义
package org.springframework.context.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {Class<?>[] value();
}
修改引导类
@SpringBootApplication
//@ComponentScan("com.xh.config")
@Import(UserConfig.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}
启动类后可以正常运行,方案稍微比方案一好一些,需要记住很多类的名字,所以仍然不是很方便
- 对@Import注解进行封装
对springboot-enable-other工程中编写注解@EnableUser,在注解中使用@Import注解导入userConfig,并且添加@Import的元注解
package com.xh.config;import org.springframework.context.annotation.Import;
import java.lang.annotation.*;@Import(UserConfig.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableUser {
}
修改springboot-enable工程的引导类,现在可以使用我们自定义的注解
@SpringBootApplication
//@ComponentScan("com.xh.config")
//@Import(UserConfig.class)
@EnableUser
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}
这种自定义注解的方式和方案二是一个原理,只不过是在使用上简化了一下。
@Import注解
@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中,而@Import提供4种用法。SpringbootAppliation中选择用第三种实现方式
- 导入Bean
- 导入配置类
- 导入ImportSelector实现类,一般用于加载配置文件中的类
- 导入ImportBeanDefinitionRegistrat实现类
导入Bean
/*** Import 4 种用法* 1. 导入Bean* 2. 导入配置类* 3. 导入 ImportSelector 实现类。一般用于加载配置文件中的类* 4. 导入 ImportBeanDefinitionRegistrar 实现类。*/
@SpringBootApplication
//@ComponentScan("com.xh.config")
//@Import(UserConfig.class)
//@EnableUser
@Import(User.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
// Object user = context.getBean("user");
// System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);// 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值Map<String, User> map = context.getBeansOfType(User.class);System.out.println("map:" + map);}
}
导入配置类
这里我们创建了一个新的对象Role,看看一个配置类是否可以加载两个对象
Role
package com.xh.domain;public class Role {
}
在UserConfig里面添加对应方法
@Beanpublic Role role(){return new Role();}
然后在启动类上修改为对应的注解
@SpringBootApplication
//@ComponentScan("com.xh.config")
//@EnableUser
//@Import(User.class)
@Import(UserConfig.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
// Object user = context.getBean("user");
// System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);Role role = context.getBean(Role.class);System.out.println("role:" + role);// 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值
// Map<String, User> map = context.getBeansOfType(User.class);
// System.out.println("map:" + map);}
}
导入ImportSelector实现类
首先我们先来看看ImportSelector接口,里面有一个方法selectImports,参数是一个注解元对象,可以用来获取一些注解的属性等,然后返回String类型数组,一些类的全限定名
package org.springframework.context.annotation;import java.util.function.Predicate;
import org.springframework.core.type.AnnotationMetadata;public interface ImportSelector {String[] selectImports(AnnotationMetadata importingClassMetadata);
}
然后我们就来实现该接口
package com.xh.config;import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 放入 user、role 的全限定名之后,就会自动去加载 user 和对应的 role 了// return new String[]{"com.xh.domain.User", "com.xh.domain.Role"};// 或者可以使用方法获取// 或者我们可以直接导入配置文件,然后从配置文件上动态获取return new String[]{User.class.getName(), Role.class.getName()};}
}
最后修改为对应注解
@SpringBootApplication
//@ComponentScan("com.xh.config")
//@EnableUser
//@Import(User.class)
//@Import(UserConfig.class)
@Import(MyImportSelector.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
// Object user = context.getBean("user");
// System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);Role role = context.getBean(Role.class);System.out.println("role:" + role);
// // 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值
// Map<String, User> map = context.getBeansOfType(User.class);
// System.out.println("map:" + map);}
}
导入ImportBeanDefinitionRegistrar实现类
package com.example.springbootenableother.config;import com.example.springbootenableother.domain.User;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;/*** @author XH* @create 2021/12/13* @since 1.0.0*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//在 IOC 容器中注册 Bean,Bean 名称为 user,类型为 User.class// 获取 beanDefinitionAbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();// 注入 userregistry.registerBeanDefinition("user", beanDefinition);}
}
@SpringBootApplication
//@EnableUser
//@Import(MyImportSelector.class)
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 根据类型获取Object user = context.getBean(User.class);System.out.println(user);// 根据 Bean 名称获取Object user1 = context.getBean("user");System.out.println(user1);
// Object role = context.getBean(Role.class);
// System.out.println(role);}
}
@EnableAutoConfiguration注解
- @EnableAutoConfiguration注解内部使用@Import(AutoConfigurationImportSelector.class)来加载配置类
- 配置文案金位置:META/spring.factories,该配置文件中定义了大量的配置类,当SpringBoot应用启动时,会自动加载这些配置类,初始化Bean
- 并不是所有的Bean都会被初始化,在配置类中使用了Condition注解来加载满足条件的Bean
小结
- 自定义条件
- 定义条件类:自定义类实现接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法两个参数
- context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等
- metadata:元数据对象,用于获取注解属性
- 判断条件:在初始化Bean时,使用@Conditional(条件类.class)注解
- 定义条件类:自定义类实现接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法两个参数
- SpringBoot提供的其他常用条件注解:
- ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
- ConditionalOnClass: 判断环境中是否有对应字节码文件才初始化Bean
- ConditionalOnMissingBean: 判断环境中没有对应Bean才初始化
- 切换web内置服务器:Jetty、Metty、Tomcat、UnderTow
- SpringBoot 底层是使用 @Import 注解导入一些配置类,实现 Bean 的动态加载。
例如 @SpringBootApplication 其中的 @EnableAutoConfiguration 就使用了 @Import 来实现导入其他的类 - @EnableAutoConfiguration注解通过注解的方式启动自动配置类
SpringBoot 监听机制
SpringBoot的监听机制,其实就是对java提供的事件监听机制的封装
java监听机制
java中的事件监听机制定义了以下几个角色
- 事件:Event,继承java.util.EventObject类的对象
- 事件源:Source,任意对象Object
- 监听器:Listener,实现java.util.EventListener接口的对象
SpringBoot监听机制
SpringBoot在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作
ApplicationContextInitializer、SpringApplicationRunListener、CommandLineRunner、AapplicationRunner
基于上面四个接口我们分别实现了四个类
- MyApplicationContextInitializer
package com.lele.springbootlistener.listener;import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;@Component
public class MyApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("ApplicationContextInitializer... + initializing...");}
}
- MyApplicationRunner
package com.lele.springbootlistener.listener;import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;@Component
public class MyApplicationRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("MyApplicationRunner run");}
}
- CommandLineRunner
package com.lele.springbootlistener.listener;import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
public class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("MyCommandLineRunner.run(String[])");}
}
- MySpringApplicationRunListener
package com.lele.springbootlistener.listener;import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;import java.time.Duration;
// 这里一开始和前面一样可以使用@Component注解并且去掉构造函数
// 但是我们看springboot自己定义的发现需要两个参数和构造但是又不能自动注入,所以要驱动@Component注解
public class MySpringApplicationRunListener implements SpringApplicationRunListener {public MySpringApplicationRunListener(SpringApplication application, String[] args) {}@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {System.out.println("MySpringApplicationRunListener starting");}@Overridepublic void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {System.out.println("MySpringApplicationRunListener environmentPrepared");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("MySpringApplicationRunListener contextLoaded");}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("MySpringApplicationRunListener contextPrepared");}@Overridepublic void started(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("MySpringApplicationRunListener started");}@Overridepublic void ready(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("MySpringApplicationRunListener ready");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("MySpringApplicationRunListener failed");}
}
定义好后我们可以启动项目,发现只有MyApplicationRunner和MySpringApplicationRunListener成功运行了,这表明系统会自动运行,但是如果我们想要运行另外两个该如何实现呢?
-
在classpath:(即resources目录下)新建一个META-INF/spring.factories,系统会自动调用里面的配置文件
-
配置文件里面格式context.listener.classes = [ 监听器全类名 ],如:
-
org.springframework.context.ApplicationContextInitializer=\com.lele.springbootlistener.listener.MyApplicationContextInitializer org.springframework.boot.SpringApplicationRunListener=\com.lele.springbootlistener.listener.MySpringApplicationRunListener
SpringBoot启动流程分析
┌───────────────────────────────────────────┐
│ 1. main() 调用入口 │
│ │
│ public static void main(String[] args) { │
│ SpringApplication.run(App.class, args);│
│ } │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 2. 创建 SpringApplication 实例 │
│ - 绑定 sources、listeners、initializers │
│ - 推断应用类型(SERVLET/REACTIVE/NONE) │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 3. 预备阶段(prepareEnvironment) │
│ - 创建并配置 Environment(PropertySources)│
│ - 应用 `EnvironmentPostProcessor` │
│ - 触发 `ApplicationEnvironmentPreparedEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 4. 创建 ApplicationContext │
│ - 根据应用类型选择: │
│ • AnnotationConfigServletWebServerApplicationContext │
│ • AnnotationConfigReactiveWebServerApplicationContext│
│ • AnnotationConfigApplicationContext │
│ - 触发 `ApplicationContextInitializedEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 5. 上下文预处理(prepareContext) │
│ - 注册 `BeanDefinition`(扫描 @SpringBootConfiguration 来源)│
│ - 执行 `ApplicationContextInitializer` │
│ - 触发 `ContextRefreshedEvent` 前的事件│
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 6. 刷新上下文(refresh) │
│ • BeanFactory 后处理: │
│ – `BeanDefinitionRegistryPostProcessor`│
│ – `BeanFactoryPostProcessor` │
│ • 注册 BeanPostProcessor │
│ • 初始化单例 Bean(依赖注入、生命周期回调) │
│ • 触发 `ContextRefreshedEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 7. 启动 Web 容器(仅 Servlet/Reactive) │
│ - 嵌入式 Tomcat/Jetty/Netty 启动 │
│ - 触发 `ServletWebServerInitializedEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 8. 执行 Runner(CommandLineRunner、ApplicationRunner)│
│ - 按 `@Order` 顺序执行 │
│ - 触发 `ApplicationStartedEvent` & `ApplicationReadyEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 9. 应用启动完成,进入正常运行态 │
└───────────────────────────────────────────┘
SpringBoot监控概述
SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、配置属性、日志信息等。
使用步骤
- 导入依赖坐标
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 访问http://localhost:8080/acruator
可以看到所有支持的连接,默认只有:
/actuator
/actuator/health
/actuator/health/{component}
/actuator/health/{component}/{instance}
/actuator/info
具体的使用方法
-
引入上述的依赖
-
通过下面的配置启用所有的监控端点,这些端点默认是禁用的
-
management:endpoints:web:exposure:include: "*"
-
或者启用下面配置开启部分监控端点
-
management:endpoints:web:exposure:exclude: beans,trace
-
“*”号代表启用所有的监控端点,可以单独启用,例如,
health
,info
,metrics
等 -
Actuator 默认所有的监控点路径都在
/actuator/*
,当然如果有需要这个路径也支持定制。 -
management:endpoints:web:base-path: /manage #记得要加上/
-
设置完重启后,再次访问地址就会变成
/manage/*
Autuator的REST接口
Actuator监控分为两类:原生端点和用户自定义端点
- 自定义端点:主要指拓展性,根据自己的实际应用,比如Redis、MyBatis等
- 原生端点:在应用程序里提供众多Web接口,通过它们了解应用程序运行时的内部状态
- 应用配置类:可以查看应用在运行期的静态信息:例如自动配置信息、加载的springbean信息、yml文件配置信息、环境信息等
- 度量指标类:主要是运行期的动态信息,例如堆栈、请求连等
- 操作控制类:主要是指shurdown,用户可以发送一个请求将应用的控制功能关闭
GET | /auditevents | 显示应用暴露的审计事件 (比如认证进入、订单失败) |
---|---|---|
GET | /beans | 展示了所有 bean 的别名、类型、是否单例、类的地址、依赖等信息 |
GET | /conditions | 使用 conditions 可以在应用运行时查看代码了某个配置在什么条件下生效,或者某个自动配置为什么没有生效。 |
GET | /configprops | 描述配置属性(包含默认值)如何注入Bean |
GET | /env | 获取全部环境属性 |
GET | /env/{name} | 根据名称获取特定的环境属性值 |
GET | /flyway | 提供一份 Flyway 数据库迁移信息 |
GET | /liquidbase | 显示Liquibase 数据库迁移的纤细信息 |
GET | /health | 报告应用程序的健康指标,这些值由 HealthIndicator 的实现类提供,用来检查应用的运行状态,“UP”表示健康,"down"不健康 |
GET | /heapdump | 返回一个 GZip 压缩的 JVM 堆 dump |
GET | /httptrace | 显示HTTP足迹,最近100个HTTP request/repsponse |
GET | /info | 获取我们自己配置在配置文件中以 info 开头的配置信息 |
GET | /logfile | 返回log file中的内容(如果 logging.file 或者 logging.path 被设置) |
GET | /loggers | 可以查看当前应用的日志级别等信息 |
GET | /metrics | 是一个非常重要的监控端点,其监控内容覆盖了 JVM 内存、堆、类加载、处理器和 tomcat 容器等一些重要指标 |
GET | /metrics/{name} | 报告指定名称的应用程序度量值 |
GET | /scheduledtasks | 展示应用中的定时任务信息 |
GET | /sessions | 如果我们使用了 Spring Session 展示应用中的 HTTP sessions 信息 |
POST | /shutdown | 关闭应用程序,要求endpoints.shutdown.enabled设置为true |
GET | /mappings | 描述全部的 URI路径,以及它们和控制器(包含Actuator端点)的映射关系 |
GET | /threaddump | 生成当前线程活动的快照,主要展示了线程名、线程ID、线程的状态、是否等待锁资源等信息。 |
SpringBoot监控-图形化界面使用
这个要针对两个模块springboot-admin-server和springboot-admin-clients进行
对于springboot-admin-server
-
首先我们要导入依赖坐标admin-starter-server
-
<dependency><groupId>de.codecentric</groupId><artifactId>spring-boot-admin-starter-server</artifactId></dependency>
-
然后在引导类上启用监控功能@EnableAdminServer即可
-
package com.lele.springbootadminserver;import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;@EnableAdminServer @SpringBootApplication public class SpringbootAdminServerApplication {public static void main(String[] args) {SpringApplication.run(SpringbootAdminServerApplication.class, args);}}
对于springboot-admin-clients
-
导入依赖坐标admin-starter-client
-
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>de.codecentric</groupId><artifactId>spring-boot-admin-starter-client</artifactId></dependency>
-
配置相关信息:server地址、显示health详细信息、开启所有监控
-
# 执行admin.server地址 spring.boot.admin.client.url=http://localhost:9000# 这一句是表示在health那里显示详细信息 management.endpoint.health.show-details=always# 开启所有监控 management.endpoints.web.exposure.include=*server.port=8080
SpringBoot项目部署
SpringBoot项目开发支持两种方式部署到服务器
- jar包
- war包
注意:为了防止打包出来的名字过于繁琐,我们可以先定义一个
finalName标签
进行改名<build><finalName>springboot-package</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins> </build>
-
jar包
-
利用的是本地的Tomcat进行打包,使用命令行
mvn clean package
进行打包或者到右边Maven的生存期package -
然后就可以运行jar包
java -jar finalName.jar
-
-
war包
-
利用外部的Tomcat进行打包部署到其他Tomcat服务器上
-
首先要修改配置类
-
@SpringBootApplication() public class SpringbootstudyApplication extends SpringBootServletInitializer {//继承SpringBootServletInitializer 重写 configure@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder builder){return builder.sources(SpringbootstudyApplication.class);}public static void main(String[] args) {SpringApplication.run(SpringbootstudyApplication.class, args);} }
-
然后还要在pom.xml里面修改打包方式
<package>war</package>
就可以了
-