Spring Boot 框架基础
基础案例
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
web环境依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
controller
@RestController
@RequestMapping("/demo")
public class DemoController {
@RequestMapping("/handle01")
public String handle01() {
return "你好 springboot";
}
}
测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
@Autowired
private DemoController demoController;
@Test
void contextLoads() {
System.out.println(demoController.handle01());
}
热部署
pom.xml
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
使用IDEA工具热部署支持:
- File => Settings => Build, Execution, Deployment => Compiler => 勾选 Build project automatically
- “Ctrl+Shift+Alt+/” 组合键打开 Registry 面板 => 勾选 compiler.automake.allow.when.app.running
热部署原理:
在编译器上启动项目,改动代码,编译器会自动触发编译,替换掉原来的 class 文件,项目检测到有文件变更后会重启项目。
当引入插件后,插件会监控 classpath 下文件的变化,当 classpath 有变化后,会触发重启。
快速重启的原因:对类采用了两种加载机制,对于第三方的 jar 包采用 base-classloader 来加载,对于开发人员自己开发的代码使用 restart-classLoader 来加载,这里比停掉服务重启快得多,因为使用插件只重启开发人员编写的代码部分。
@Component
public class Devtools implements InitializingBean {
@Override
public void afterPropertiesSet() {
// 在添加了 devtools 插件
// 第三方 jar 包 使用 base-classloader 来加载
// 自己开发的代码 使用 restart-classLoader 来加载
// org.springframework.boot.devtools.restart.classloader.RestartClassLoader@472075a1
System.out.println(Devtools.class.getClassLoader());
// jdk.internal.loader.ClassLoaders$AppClassLoader@e73f9ac
System.out.println(DispatcherServlet.class.getClassLoader());
}
}
配置文件中属性注入
@Value
@Component
@Data
public class JdbcConfiguration {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
}
配置文件
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/bank
jdbc.username=root
jdbc.password=123
测试
@Autowired
private JdbcConfiguration jdbcConfiguration;
@Test
void JdbcValueConfigTest() {
System.out.println(jdbcConfiguration);
}
结果
JdbcConfiguration(url=jdbc:mysql://127.0.0.1:3306/bank, driverClassName=com.mysql.jdbc.Driver, username=root, password=123)
@ConfigurationProperties
pom.xml
<!-- 添加配置文件提示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
定义需要被注入属性的实体类
@Component
@ConfigurationProperties(prefix = "person")
@Data
public class Person {
private int id;
private String name;
private List<String> hobbies1;
private List<String> hobbies2;
private String[] family;
private Map<String, Object> map1;
private Map<String, Object> map2;
private Pet pet;
}
@Component
@Data
public class Pet {
private String name;
private String type;
}
配置文件
application.properties
# 自定义属性注入到对应的实体中
person.id=1
person.name=应巅
person.hobbies1=吃饭,睡觉,打豆豆
person.hobbies2=睡觉,睡觉,睡觉
person.family=爸爸,妈妈,爷爷,奶奶
person.map1.extra1=1额外的信息1
person.map1.extra2=1额外的信息2
person.map2.extra1=2额外的信息1
person.map2.extra2=2额外的信息2
person.pet.type=猫
person.pet.name=旺财
application.yml
person:
id: 2
family: [爸爸,妈妈,爷爷,奶奶]
name: 应巅
hobbies1:
睡觉,
睡觉,
睡觉
hobbies2:
- 吃饭
- 睡觉
- 打豆豆
map1:
extra1: 1额外的信息1
extra2: 1额外的信息2
map2: {extra1: 2额外的信息2, extra2: 2额外的信息2}
pet:
type: 猫
name: 旺财
测试
@Autowired
private Person person;
@Test
void personConfigTest() {
System.out.println(person);
}
结果
Person(id=1, name=应巅, hobbies1=[吃饭, 睡觉, 打豆豆], hobbies2=[睡觉, 睡觉, 睡觉], family=[爸爸, 妈妈, 爷爷, 奶奶], map1={extra1=1额外的信息1, extra2=1额外的信息2}, map2={extra1=2额外的信息1, extra2=2额外的信息2}, pet=Pet(name=旺财, type=猫))
日志框架
日志抽象层 |
日志实现层 |
JCL、SLF4J、jboos-logging |
jul、log4j、log4j2、logback |
springboot 默认使用 SLF4J 为日志抽象层,logback 为日志实现层。
日志使用
private Logger logger = LoggerFactory.getLogger(SpringBootDemoApplicationTests.class);
@Test
void logTest() {
// 日志级别:trace < debug < info < warn < error
// 日志默认使用:SLF4j + LogBack
// spring boot 默认日志级别 为Info
logger.trace("Trace 日志...");
logger.debug("Debug 日志...");
logger.info("Info 日志...");
logger.warn("Warn 日志...");
logger.error("Error 日志...");
}
配置文件
# 调整某个包下面的日志级别
logging.level.com.demo=trace
# 日志的输出格式
# 控制台
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
# 文件
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
# 文件路径 默认的日志文件名 spring.log
#logging.file.path=springLog
# 写入具体的文件
logging.file.name=log/log.log
缓存
pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
application.properties
spring.datasource.url=jdbc:mysql:///bank?
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.show-sql=true
@Data
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@Column(name = "last_name")
private String lastName;
@Column(name = "email")
private String email;
@Column(name = "gender")
private Integer gender; //性别 1男 0女
@Column(name = "d_id")
private Integer dId;
}
// JPA实现简单CRUD
public interface EmployeeDao extends JpaRepository<Employee, Integer> {}
@SpringBootApplication
@EnableCaching //开启基于注解的缓存
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
添加缓存
@GetMapping("/emp/{id}")
// 添加缓存 cacheNames:缓存的名字 = value;
// key:默认参数的值 次数为id的值1;可以使用 SpEL 表达式
@Cacheable(cacheNames = {"emp"})
public Employee getEmp(@PathVariable("id") Integer id) {
return employeeDao.findById(id).get();
}
更新缓存
@GetMapping("/emp/update")
// 更新缓存
@CachePut(value = "emp", key = "#employee.id")
public Employee updateEmp(Employee employee) {
employeeDao.save(employee);
return employee;
}
删除缓存
@DeleteMapping("/emp/{id}")
// 删除缓存
// beforeInvocation:默认为 false,译为在方法调用之后删除缓存;设置为 true,译为在方法调用之前删除缓存
// 若方法出现异常,在方法调用之后清除缓存将不起作用。
@CacheEvict(value = "emp", key = "#id", beforeInvocation = true)
public void delEmp(@PathVariable("id") Integer id) {
employeeDao.deleteById(id);
}
SpEL 表达式
@GetMapping("/SpELDemo/{id}")
@Cacheable(cacheNames = {"emp"}, key = "#root.method")
public Employee SpELDemo(@PathVariable("id") Integer id) {
return employeeDao.findById(id).get();
}
SpEL 表达式 |
描述 |
值 |
#root.methodName |
方法的名称 |
SpELDemo |
#root.method |
方法 |
public com.demo.pojo.Employee com.demo.controller.DemoController.SpELDemo(java.lang.Integer) |
#root.args[0] |
当前被调用的方法的参数列表 |
{id}的值 |
#iban、#a0、#p0 |
方法参数的名字,可以直接 #参数名,也可以使用 #p0 或 #a0 的形式,0 代表参数的索引 |
{id}的值 |
#result |
方法执行后的返回值 |
|
基于redis 实现缓存
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.redis.database=1
spring.redis.host=127.0.0.1
需要实现 Serializable 接口
public class Employee implements Serializable {}
SpringBoot默认采用的是JDK的对象序列化方式。
自定义RedisCacheManager
使用 JSON 格式进行对象的序列化操作。
@Configuration
public class RedisConfig {
// 自定义一个RedisTemplate
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//设置redisTemplate模板API的序列化方式为json
template.setDefaultSerializer(getJackson2JsonRedisSerializer());
return template;
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
// 定制缓存数据序列化方式及时效
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(getJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
}
private Jackson2JsonRedisSerializer<Object> getJackson2JsonRedisSerializer() {
// 创建JSON格式序列化对象,对缓存数据的key和value进行转换
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
}
}
打包部署
pom.xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
运行:
java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar
多环境部署
两种方式:
1、@Profile 注解:可以使用在 @Component 或 @Configuration 注解的类上,指定一个字符串值用于约定生效的环境。
@Configuration
// 字符串需要和 激活的参数一致
@Profile("dev")
public class DataSourceConfig {
private String name="devName";
private String config="devConfig";
}
2、创建 application-dev.properties 配置文件。-dev 需要和 激活的参数一致
@Component
@Data
public class Sound {
@Value("${sound.name}")
String name;
@Value("${sound.location}")
String location;
}
# application-dev.properties
sound.name=devName
sound.location=devLocation
application.properties 中配置文件激活:spring.profiles.active=dev
如果不添加在打包的时候使用命令:
java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
测试:此时使用的配置文件是 application-dev.properties。注入的类是 @Profile(“dev”) 修饰过的。
@Autowired
private Sound sound;
@Test
void devConfig() {
System.out.println(sound);
}
@Autowired
private DataSourceConfig dataSourceConfig;
@Test
void devDataSourceConfig() {
System.out.println(dataSourceConfig);
}
项目监控
actuator
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml
management:
endpoints:
web:
exposure:
# 暴露所有的端点
include: '*'
# 默认端点的url /actuator;修改为 /monitor
base-path: '/monitor'
endpoint:
health:
# 是否展示细节
show-details: always
shutdown:
# 能否通过接口关闭 spring boot 服务
enabled: true
server:
# 监控的端口 通常不同于服务端口
port: 8082
health:
redis:
# actuator 会自动进行一些健康检查 此处可用于排除 redis 的健康检查
enabled: false
基本监控接口
- GET:http://127.0.0.1:8082/monitor/info
自定义的信息
@Component
public class InfoConfig implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
HashMap<String, String> map = new HashMap<>();
map.put("app", "001");
builder.withDetail("key", "value").withDetail("key2", map);
}
}
{
key: "value",
key2: {
app: "001"
}
}
- GET:http://127.0.0.1:8082/monitor/health
当前服务健康状态
{
status: "UP",
components: {
db: {
status: "UP",
details: {
database: "MySQL",
validationQuery: "isValid()"
}
},
diskSpace: {
status: "UP",
details: {
total: 431645257728,
free: 366851395584,
threshold: 10485760,
exists: true
}
},
ping: {
status: "UP"
}
}
}
- POST:http://127.0.0.1:8082/monitor/shutdown
关闭当前服务
spring boot admin
需要注意 admin 的版本 和 parent 的版本
服务端
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.6.1</version>
</dependency>
启动类上标注
@EnableAdminServer
客户端
pom.xml
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.6.1</version>
</dependency>
application.yml
spring:
boot:
admin:
client:
# admin 服务端的地址
url: http://localhost:8080
application:
name: spring-boot-demo
定时任务
@Scheduled
启动类上标注
@EnableScheduling
编写定时任务逻辑
@Component
@EnableAsync
public class ScheduledConfig {
@Scheduled(fixedRate = 10000)
public void fixedRate() {
System.out.println("fixedRate>>>" + new Date());
}
@Scheduled(cron = "*/10 * * * * MON-FRI")
public void doSomething() {
System.out.println("something that should run on weekdays only");
}
}
SchedulingConfigurer 接口
实现 SchedulingConfigurer 接口 可以动态执行定时任务
@Component
public class DynamicScheduleTask implements SchedulingConfigurer {
@Autowired
private CornDao cornDao;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(() -> {
// 定时任务逻辑
System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime());
}, triggerContext -> {
// 从数据库中获取 定时任务的执行时间
// String name = "*/10 * * * * MON-FRI";
CornMsg cornMsg = cornDao.findById(1).get();
String name = cornMsg.getName();
return new CronTrigger(name).nextExecutionTime(triggerContext);
});
}
}
参考git:https://gitee.com/zhangyizhou/learning-spring-boot-demo.git