3. 数据访问
说明:在SpringBoot中想要操作数据库完成增删改差,按照以往的经验:
-
原理:
- 首先 导入数据开发的场景starter(依赖)---->
- 这个场景会又会自动导入数据库相关的配置类---->
- 这个配置类又会导入相关的组件,如:数据源----》
- 数据源组件中又有相关的数据库配置项:用户名、密码等。
- 这写配置项又与yml/properties 配置文件的属性在一起。
-
总结:想要在在SpringBoot中想要操作数据库只需要2步:
- 引入场景依赖
- 在配置文件中完成数据库相关的配置即可。
3.1 SQL
3.1.1 数据库连接池的自动配置(Hikari连接池)
1) 导入JDBC场景(依赖)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
说明:导入jdbc场景会自动引入以下依赖:
- 数据源:HikariCP
- spring-jdbc:spring进行jdbc操作
- spring-tx:spring支持事务
问题:操作数据库还需要有数据库驱动,那为什么导入JDBC场景,官方不导入驱动数据库驱动呢???
- 答:官方不知道我们接下要操作什么数据库,也就是说你操作的数据库种类是不确定的也就没办法提前确定。
导入MySQL的数据库驱动:用谁的数据库就导入谁的驱动。
- 官方自动仲裁了MySql的驱动版本:
<mysql.version>8.0.22</mysql.version>
,所以在导入驱动时就不需要写版本号了。 - 当然也可以手动进行修改。
-
注意:数据库版本和驱动版本要对应,可以向下兼容(
即:数据库版本为8.0,那么数据库驱动可以使用8.0以及8.0以下的版本
)
想要修改版本:
方式一:直接依赖引入具体版本(maven的就近依赖原则)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- 修改版本,方式一: <version>5.1.49</version>-->
</dependency>
方式二:重新声明版本(maven的属性的就近优先原则)
<properties>
<java.version>1.8</java.version>
<!--修改版本,方式二:-->
<!--<mysql.version>5.1.49</mysql.version>-->
</properties>
2) 分析自动配置
2.1) 自动配置的类
- DataSourceAutoConfiguration : 数据源的自动配置
- 修改数据源相关的配置:spring.datasource
- 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
- 底层配置好的连接池是:
HikariDataSource
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration
- DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
-
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud(
因为此时没有整合MyBatis等第三方的持久层框架,这是spring官方提供的一个crud的小工具
)- 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
- @Bean@Primary JdbcTemplate;容器中有这个组件
- JndiDataSourceAutoConfiguration: jndi的自动配置(在web容器中配置数据源的方式)
- XADataSourceAutoConfiguration: 分布式事务相关的
3) 修改配置项(yml文件)
# 数据库连接地址、用户名、密码、数据库驱动的名字、数据库的类型不用指定默认就是HikariDataSource
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_db
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
# type: com.zaxxer.hikari.HikariDataSource
# 配置sprig下的jdbc下的template属性:查询的超时时间 单位:秒 表示3秒之内没有查询到数据则超时
jdbc:
template:
query-timeout: 3
4) 测试
package com.atguigu;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
//使用spring提供的工具jdbcTemplate来进行crud操作。
@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Autowired //这个组件spring容器注册好了,直接注入即可。
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
// jdbcTemplate.queryForObject("select * from account_tbl")
// jdbcTemplate.queryForList("select * from account_tbl",)
Long aLong = jdbcTemplate.queryForObject("select count(*) from t_account", Long.class);
log.info("记录总数:{}",aLong);
}
}
3.1.2 使用Druid数据库连接池(Druid连接池)
说明:这是阿里巴巴提供的数据源。
1) druid官方github地址
https://github.com/alibaba/druid
整合第三方数据库连接池技术的两种方式
- 自定义 (纯手工,手动的一个个的写。)
- 找官方starter(依赖)
2) 自定义方式(不推荐)
2.1) 创建数据源
添加依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
spring原生方式:使用xml文件注册组件并进行配置属性。现在使用配置类进行代替。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
配置文件:
# 数据库连接地址、用户名、密码、数据库驱动的名字、数据库的类型不用指定 默认就是HikariDataSource
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_db
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
# type: com.zaxxer.hikari.HikariDataSource
#配置sprig下的jdbc下的template属性:查询的超时时间 单位:秒 表示3秒之内没有查询到数据则超时
jdbc:
template:
query-timeout: 3
配置类:
@Configuration
public class MyDataSourceConfig {
// 默认的自动配置是判断容器中没有才会配@ConditionalOnMissingBean(DataSource.class)
@ConfigurationProperties("spring.datasource")//方式二:设置连接数据库的信息,读取配置文件设置的连接信息
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
//设置连接数据库的信息,方式一:这是写死的写法
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
return druidDataSource;
}
}
测试类:
package com.atguigu;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
//使用spring提供的工具jdbcTemplate来进行crud操作。
@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() {
log.info("数据源类型:{}",dataSource.getClass());
}
}
2.2) StatViewServlet
StatViewServlet的用途包括:
- 提供监控信息展示的html页面
- 提供监控信息的JSON API
- 根据配置中的url-pattern来访问内置监控页面,如果是上面的配置,内置监控页面的首页是/druid/index.html
web.xml原生方式:配置监控功能、
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
现在使用配置类:
package com.atguigu.admin.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Arrays;
@Configuration
public class MyDataSourceConfig {
// 默认的自动配置是判断容器中没有才会配@ConditionalOnMissingBean(DataSource.class)
@ConfigurationProperties("spring.datasource")//方式二:设置连接数据库的信息,读取配置文件设置的连接信息
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
//设置连接数据库的信息,方式一:这是写死的写法
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
// 加入监控功能
druidDataSource.setFilters("stat");
return druidDataSource;
}
/**
* 配置 druid的监控页功能
* @return
*/
@Bean
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
//配置拦截的路径
ServletRegistrationBean<StatViewServlet> registrationBean =
new ServletRegistrationBean<>(statViewServlet, "/druid/*");
//配置监控页的用户名和密码
registrationBean.addInitParameter("loginUsername","admin");
registrationBean.addInitParameter("loginPassword","123456");
return registrationBean;
}
}
控制层方法:
package com.atguigu.admin.controller;
import com.atguigu.admin.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
@Controller
public class IndexController {
@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/sql")
public String queryFromDb(){
Long aLong = jdbcTemplate.queryForObject("select count(*) from t_account", Long.class);
return aLong.toString();
}
}
启动主启动类:
访问监控页:http://localhost:8080/druid/index.html
2.3) StatFilter
用于统计监控信息;如SQL监控、URI监控
需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如:
<property name="filters" value="stat,slf4j" />
系统中所有filter:
慢SQL记录配置
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
<property name="slowSqlMillis" value="10000" />
<property name="logSlowSql" value="true" />
</bean>
使用 slowSqlMillis 定义慢SQL的时长
WebStatFilter用于采集web-jdbc关联监控的数据。
web.xml方式配置:
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<!--经常需要排除一些不必要的url,比如*.js,/jslib/*等等。配置在init-param中。-->
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<!--拦截所有路径-->
<url-pattern>/*</url-pattern>
</filter-mapping>
配置类方式:
/**
* WebStatFilter 用于采集web-jdbc关联监控的数据。
*/
@Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));//拦截所有路径
//排除不需要监控的资源
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
启动主启动类访问测试:
(2.2.4)防火墙
配置类:
3) 使用官方starter方式(推荐,过时注解)
说明:把方式一的自定义方式注释掉。
注释掉依赖:
配置类标识为过时,并注释掉。
3.1) 引入druid-starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
3.2) 分析自动配置
- 扩展配置项 spring.datasource.druid
- DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
- DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
- DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
- DruidFilterConfiguration.class}) 所有Druid自己filter的配置
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
3.3) 配置示例(yml文件)
spring:
datasource: #配置数据库连接池的基本信息
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid: #配置druid的监控页功能
aop-patterns: com.atguigu.admin.* # 监控SpringBean的范围
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true # 默认false,需要开启
login-username: admin # 配置监控页的用户名和密码
login-password: admin
resetEnable: false # 是否有重置按钮
web-stat-filter: # 监控web
enabled: true # 开启
urlPattern: /* # 需要匹配的
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' # 需要排除的
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000 # sql超过1000毫秒的查询都是慢查询
logSlowSql: true # 日志是否要记录慢查询的sql
enabled: true # 监控的功能要不要开启
wall:
enabled: true # 防火墙的功能要不要开启
config:
drop-table-allow: false # 所有的删除操作都会被防火墙拦截
SpringBoot配置示例
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
配置项列表
https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
3.1.3 整合MyBatis操作
MyBatis官方整合SpringBoot场景的github地址:https://github.com/mybatis
说明:不能导入原来的MyBatis依赖,现在使用的是SpringBoot整合MyBatis后的依赖。
- SpringBoot官方的Starter:
spring-boot-starter-*
- 第三方的Starter:
*-spring-boot-starter
<!--mybatis核心依赖,包含了jdbc依赖,所以这里不用在引入jdbc依赖了。-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--注释掉jdbc-->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>-->
说明:引入了mybatis-spring-boot-starter场景依赖,就会自动引入:
- spring-boot-starter-jdbc:数据库开发的jdbc场景
- mybatis-spring-boot-autoconfigure:SpringBoot的自动配置包
- Mybatis: MyBatis框架
- mybatis-spring:MyBatis和Spring的整合
1) 配置模式(@Mapper)
说明:以前的配置方式需要在配置文件中自己一个个的写,现在底层自动配置好了。
- 全局配置文件
- SqlSessionFactory: 自动配置好了
- SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
-
@Import(AutoConfiguredMapperScannerRegistrar.class)
; - Mapper: 只要我们写的操作MyBatis的接口标注了 @Mapper就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class) : MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}
@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties
可以修改配置文件中 mybatis 开头的所有:
1.1)方式一:用核心配置文件的方式配置
yml配置文件:配置核心配置文件和映射文件的路径。
# 配置mybatis规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
核心配置文件:
<?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>
<!--所有的东西都不用配置,底层都配置好了-->
<!--设置MyBatis的全局配置-->
<settings>
<!--将_自动映射为驼峰,emp_name:empName-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
控制层,service层略。
接口:注意加上@Mapper
注解
package com.atguigu.admin.mapper;
import com.atguigu.admin.bean.Account;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Mapper;
/**
* @Mapper 用于描述(做标记)数据层访问接口,用于告诉mybatis框架,
* 使用此注解描述的接口要由底层为创建实现类.在实现类中基于mybatis
* API实现与数据库的交互.这个类的对象最后会交给spring管理.
*/
@Mapper
public interface AccountMapper {
public Account getAcct(Long id);
}
映射文件:
Mapper接口--->绑定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.atguigu.admin.mapper.AccountMapper">
<!-- public Account getAcct(Long id); -->
<select id="getAcct" resultType="com.atguigu.admin.bean.Account">
select * from account_tbl where id=#{id}
</select>
</mapper>
1.2)方式二:使用配置文件(yml 推荐)
说明:
- 配置 private Configuration configuration; mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值
-
即:之前用的mybatis-config.xml核心配置文件,现在可以使用yml或者properties文件下的 mybatis.configuration项,来配置各种属性以代替核心配置文件里面的一些列配置。
注意:
- 方式一:使用需要手动指定核心配置文件和映射文件位置,之后在里面进行一些列配置
- 方式二:使用configuration配置项代替核心配置文件,所以方式一的自动核心文件路径需要注释掉。映射文件的路径位置要保留。
# 配置mybatis规则
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml #注释掉 方式一的全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
configuration: # 指定mybatis全局配置文件中的相关配置项
map-underscore-to-camel-case: true #开启驼峰规则映射,一旦使用这种方式,那么必须要注释掉xml核心配置文件的方式。
总结: 可以不写全局配置文件,所有全局配置文件的配置都放yml/properties中的configuration配置项中即可
2) 注解模式(注解方式写sql、自增主键)
说明:使用注解方式代替映射文件方式写sql,适合简单地sql语句
。
注解方式获取插入数据的自增主键值:
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public City getById(Long id);
@Insert("insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})")
@Options(useGeneratedKeys = true,keyProperty = "id")//注解方式获取插入数据的自增主键值。
public void insert(City city);
</*
useGeneratedKeys:设置当前标签中的sql使用了自增的主键
keyProperty:将自增的主键的值赋值给传输到映射文件中参数的某个属性
说明:增删改的返回值是固定的就是受影响的行数,不能把自动递增的主键作为方法的返回值进行获取,
只能把自动递增的id放在所传输过来的参数User对象中的某一个属性上,此时这个User对象id属性值
就可以获取到了。
*/>
}
3) 混合模式(注解+映射文件 写sql xml自增主键)
说明:注解方式和xml方式可以同时使用。
接口:
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public City getById(Long id);
public void insert(City city);
}
映射文件: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.atguigu.admin.mapper.CityMapper">
<!-- public void insert(City city);-->
<!-- xml方式获取插入数据的自增主键值-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})
</insert>
</mapper>
4) 总结开发步骤:(@MapperScan)
- 导入mybatis官方starter:mybatis-starter
- 在application.yml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议:配置在yml配置文件的mybatis.configuration项中)
- 编写Mapper接口并标注@Mapper注解
- 简单方法直接注解方式
- 复杂方法编写mapper.xml进行绑定映射
- 在主启动类上添加@MapperScan(“com.atguigu.admin.mapper”) 简化,其他的接口就可以不用标注@Mapper注解
3.1.4 整合 MyBatis-Plus 完成CRUD
详情查看:MyBatis-Plus的篇章。
1) 什么是MyBatis-Plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatis plus 官网:https://baomidou.com/
建议安装 MybatisX 插件 :点击接口方法上的小鸟图标,会直接跳到对应的xml文件,开发时来回跳转比较方便,还有逆向生成代码,代码提示等功能。
2) 整合MyBatis-Plus
<!--mybatis和mybatis-plus核心依赖,都包含了jdbc依赖,所以这里不用在引入jdbc依赖了。-->
<!--mybatis-plus:又包含了mybatis场景依赖,所以也不需要引入mybatis依赖了。-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--注释掉mybatis-->
<!-- <dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>-->
<!--注释掉jdbc-->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>-->
自动配置
- MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制
- SqlSessionFactory 自动配置好。底层是容器中默认的数据源(之前配置过德鲁伊)
-
mapperLocations 自动配置好的。有默认值。
classpath*:/mapper/**/*.xml
;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件
。 建议以后sql映射文件,放在 mapper下(当然也可以自定义修改) - 容器中也自动配置好了 SqlSessionTemplate
- @Mapper 标注的接口也会被自动扫描;建议直接使用@MapperScan(“com.atguigu.admin.mapper”) 批量扫描就行
优点:
- 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力
package com.atguigu.admin.mapper;
import com.atguigu.admin.bean.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 泛型:数据库对应的实体类类型。
*/
public interface UserMapper extends BaseMapper<User> {
}
3) CRUD功能
控制层:
@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
@RequestParam(value = "pn",defaultValue = "1")Integer pn,
RedirectAttributes ra){
userService.removeById(id);
ra.addAttribute("pn",pn);
return "redirect:/dynamic_table";
}
@GetMapping("/dynamic_table")
public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
//表格内容的遍历
// response.sendError
// List<User> users = Arrays.asList(new User("zhangsan", "123456"),
// new User("lisi", "123444"),
// new User("haha", "aaaaa"),
// new User("hehe ", "aaddd"));
// model.addAttribute("users",users);
//
// if(users.size()>3){
// throw new UserTooManyException();
// }
//从数据库中查出user表中的用户进行展示
//构造分页参数
Page<User> page = new Page<>(pn, 2);
//调用page进行分页
Page<User> userPage = userService.page(page, null);
// userPage.getRecords()
// userPage.getCurrent()
// userPage.getPages()
model.addAttribute("users",userPage);
return "table/dynamic_table";
}
业务层:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
public interface UserService extends IService<User> {
}
3.2 NoSQL(待定+++++++++++++)
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库
、缓存
和消息中间件
。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
3.2.1 Redis自动配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
自动配置:
- RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
- 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
- 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
- 自动注入了StringRedisTemplate;k:v都是String
- key:value
- 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis
redis环境搭建
1、阿里云按量付费redis。经典网络
2、申请redis的公网连接地址
3、修改白名单 允许0.0.0.0/0 访问
3.2.2 RedisTemplate与Lettuce
@Test
void testRedis(){
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello","world");
String hello = operations.get("hello");
System.out.println(hello);
}
3.2.3 切换至jedis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 导入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
spring:
redis:
host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
port: 6379
password: lfy:Lfy123456
client-type: jedis
jedis:
pool:
max-active: 10
4. 单元测试
4.1 JUnit5 的变化
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
- JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
-
JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。
步骤1:添加单元测试依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
会自动依赖一下场景
注意:
-
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
-
JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage
-
总结:
- 2.4 以上版本默认移除了Vintage 的依赖,即 不能在使用junit4的注解了,只能使junit5的注解。
- 想要兼容使用junit4的注解需要手动添加以下配置。
<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>
以前:使用单元测试需要添加很多注解
@SpringBootTest + @RunWith(SpringTest.class)
步骤2:
现在版本:
- 创建好项目后会自动配置好,自动提供一个单元测试类,直接在里面使用就行。
- 可以添加多个单元测试类,如果想要使用注入组件功能,需要类上添加@SpringBootTest注解标识。
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Test
void contextLoads() {
}
}
总结使用测试类的步骤:SpringBoot整合Junit以后。
- 创建完SpringBoot项目后会自动:添加单元测试依赖、创建好测试类。
- 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
- Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
4.2 JUnit5常用注解
JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
- @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest :表示方法可重复执行,下方会有详细介绍
- @DisplayName :为测试类或者测试方法设置展示名称
- @BeforeEach :表示在每个单元测试之前执行
- @AfterEach :表示在每个单元测试之后执行
- @BeforeAll :表示在所有单元测试之前执行
- @AfterAll :表示在所有单元测试之后执行
- @Tag :表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith :为测试类或测试方法提供扩展类引用
package com.atguigu.admin;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.concurrent.TimeUnit;
/**
* @SpringBootTest是复合注解:
* @BootstrapWith(SpringBootTestContextBootstrapper.class)
* @ExtendWith(SpringExtension.class)
*/
//@ExtendWith 为测试类或测试方法提供扩展类引用,@SpringBootTest本身就包含它。
@SpringBootTest //想要使用spring的容器注入功能就需要加上此注解,如果不需要使用注入功能可以不写。
@DisplayName("junit5功能测试类")
public class Junit5Test { //在类上运行单元测试,整个类上的测试方法都会执行一遍
@Autowired
JdbcTemplate jdbcTemplate;
//为测试类或者测试方法设置展示名称,运行的时候知道是那个测试方法在运行
@DisplayName("测试DisplayName注解")
@Test
void testDisplayName(){
System.out.println("111111");
System.out.println(jdbcTemplate);
}
@Disabled //表示测试类或测试方法不执行
@DisplayName("测试方法2")
@Test
void test2() {
System.out.println(2);
}
@RepeatedTest(5) //表示方法可重复执行,此测试方法可以执行5次
@Test
void test3() {
System.out.println(5);
}
/**
* 规定方法超时时间。超出时间测试出异常
* 可以指定单位:MILLISECONDS毫秒
* @throws InterruptedException
*/
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(600);
}
@BeforeEach //表示在每个单元测试之前执行
void testBeforeEach() {
System.out.println("测试就要开始了...");
}
@AfterEach //表示在每个单元测试之后执行
void testAfterEach() {
System.out.println("测试结束了...");
}
//必须是静态方法,或表示@TestInstance.因为这个方法只会被调用一次
@BeforeAll //表示在所有单元测试之前执行
static void testBeforeAll() {
System.out.println("所有测试就要开始了...");
}
//必须是静态方法,或表示@TestInstance。因为这个方法只会被调用一次
@AfterAll //表示在所有单元测试之后执行
static void testAfterAll() {
System.out.println("所有测试以及结束了...");
}
}
4.3 断言(assertions)
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告;
断言:即断定某件事情一定会发生,如果这件事情没有发生表示出现了问题。用来代替system输出方法,相比输出方法会有一个详细的测试报告。
4.3.1 简单断言
用来对单个值进行简单的验证。如:
package com.atguigu.admin;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class Juint5Test01 {
/**
* 断言:前面断言失败,后面的代码都不会执行
*/
@DisplayName("测试简单断言")
@Test
void testSimpleAssertions() {
int cal = cal(3, 2);
//判断:传入的期望值和真实值是否相等
//参数:期望值、传入的真实值、自定义断言失败的提示信息
assertEquals(5, cal, "业务逻辑计算失败");
Object obj1 = new Object();
Object obj2 = new Object();
//判断两个对象引用是否指向同一个对象,即地址值是否相等
assertSame(obj1, obj2, "两个对象不一样");
}
int cal(int i, int j) {
return i + j;
}
}
4.3.2 数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
@Test
@DisplayName("array assertion")
public void array() {
//比较的是2个数组里面的值
//前面用的永远是期望的值,后面传的才是真正的值
//每个断言都可以自定义异常信息
assertArrayEquals(new int[]{1, 1}, new int[] {1, 2},"数组内容不相等");
}
4.3.3 组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
@Test
@DisplayName("组合断言")
void all() {
/**
* 所有断言全部需要成功,才会向下执行。
*/
//参数1:给组合断言是哪一个组起一个名
//参数2:可变参数可以出传多个断言
assertAll("test",
() -> assertTrue(true && true, "结果不为true"),
() -> assertEquals(1, 1, "结果不是1"));
System.out.println("=====");
}
4.3.4 异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用 @Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。
@DisplayName("异常断言")
@Test
void testException() {
//断定业务逻辑一定出现异常,没有出现异常所说明这个业务逻辑是有问题的。
//参数:异常类型,可执行的方法,没有抛出异常的提示信息
assertThrows(ArithmeticException.class, () -> {
int i = 10 / 2;
}, "业务逻辑居然正常运行?");
}
4.3.5 超时断言
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
4.3.6 快速失败
通过 fail 方法直接使得测试失败
@DisplayName("快速失败")
@Test
void testFail(){
//xxxxx
//在前面xxxx中单元测试了一大堆,之后想要直接结束,不想要在测试了。
if(1 == 2){
fail("测试失败");
}
}
4.3.7 断言机制的好处
说明:在项目上线之前,跑一遍单元测试,会有一个完整的单元测试报告。它会执行每一个单元测试方法,那个方法执行成功,那个执行失败 失败的原因是什么,会有一个汇总报告,方便我们定位。
4.4 前置条件(assumptions)
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("simple")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assume then do")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
/**
* 测试前置条件
*/
@DisplayName("测试前置条件")
@Test
void testassumptions(){
//是true才会向下执行,如果不是true会有个提示信息。
Assumptions.assumeTrue(false,"结果不是true");
System.out.println("111111");
}
}
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
4.5 嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
package com.atguigu.admin;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.EmptyStackException;
import java.util.Stack;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("嵌套测试")
public class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
//嵌套测试情况下,外层的Test不能驱动内层的Before(After)Each/All之类的方法提前/之后运行
assertNull(stack);
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
/**
* 内层的Test可以驱动外层的Before(After)Each/All之类的方法提前/之后运行
*/
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
4.6 参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource
等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
@ParameterizedTest
@MethodSource("method") //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}
static Stream<String> method() {
return Stream.of("apple", "banana");
}
4.7 迁移指南
在进行迁移的时候需要注意如下的变化:
- 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
- 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
- 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
- 把@Ignore 替换成@Disabled。
- 把@Category 替换成@Tag。
- 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。
5. 指标监控
5.1 SpringBoot Actuator
5.1.1 简介
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
5.1.2 1.x与2.x的不同
5.1.3 如何使用
- 引入场景
- 访问 http://localhost:8080/actuator/**
- 暴露所有监控信息为HTTP
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
- 测试
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/endpointName/detailPath
。。。。。。
5.1.4 可视化
https://github.com/codecentric/spring-boot-admin
5.2 Actuator Endpoint
5.2.1 最常使用的端点
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:日志记录
5.2.2 Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
- 可以很容易的添加自定义的健康检查机制
5.2.3 Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;
- 通过Metrics对接多种监控系统
- 简化核心Metrics开发
- 添加自定义Metrics或者扩展已有Metrics
5.2.4 管理Endpoints
1) 开启与禁用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
2) 暴露Endpoints
支持的暴露方式
- HTTP:默认只暴露health和info Endpoint
- JMX:默认暴露所有Endpoint
- 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
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 |
5.3 定制 Endpoint
5.3.1 定制 Health 信息
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();
management:
health:
enabled: true
show-details: always #总是显示详细信息。可显示每个模块的状态信息
@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);
}
}
5.3.2 定制info信息
常用两种方式
1) 编写配置文件
info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@
2) 编写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信息
5.3.3 定制Metrics信息
1) SpringBoot支持自动适配的Metrics
- JVM metrics, report utilization of:
○ Various memory and buffer pools
○ Statistics related to garbage collection
○ Threads utilization
○ Number of classes loaded/unloaded - CPU metrics
- File descriptor metrics
- Kafka consumer and producer metrics
- Log4j2 metrics: record the number of events logged to Log4j2 at each level
- Logback metrics: record the number of events logged to Logback at each level
- Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
- Tomcat metrics (server.tomcat.mbeanregistry.enabled must be set to true for all Tomcat metrics to be registered)
- Spring Integration metrics
2) 增加定制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);
}
5.3.4 定制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来管理程序是否存活;
当然,这个也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes
6 原理解析
6.1 Profile功能
为了方便多环境适配,springboot简化了profile功能。
使用场景:开发一个微服务项目,在配置文件中配置了很多的信息。
- 当前在idea里面进行开发,使用配置文件连接的是本地测试数据库,一旦服务迁移到生产环境,可能配置文件的一系列信息都要变为生产环境下的这些线上的数据库。每次切换环境,都需要把这些配置手动修改掉在打包部署,太过麻烦,
- 解决:SpringBoot提供了Profile功能,可以快速的进行环境切换。
- 案例场景:在微服务项目中多创建几个配置文件,分别代表生成、测试。服务一旦上线只需要告诉SpringBoot切换到生产环境下即可。
6.1.1 application-profile功能
说明:指定使用的是哪个配置环境。
执行规则:
- 默认配置文件 application.yaml/properties,
任何时候
都会加载 - 指定环境配置文件 application-{env}.yaml
- 激活指定环境
- 配置文件激活
- 命令行激活:
java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
- 修改配置文件的任意值,命令行优先
- 默认配置与环境配置同时生效
- 同名配置项,profile指定环境的配置优先
1) 环境准备
创建springboot项目,打包方式为jar,添加web依赖。
如何修改文件名:
2) 测试:使用默认配置文件环境(@Value注入)
默认配置文件:即 不带任何标识的文件,application.properties/yml,默认配置文件永远都会加载。
application.properties:项目创建好默认生成的配置文件
person.name=张三
application-prod.yml:手动创建生产环境的配置文件
person:
name: prod-张三 # 这是生产环境下的配置
#如何告诉SpringBoot这是生产环境下配置文件呢??
#比如:在配置文件的名字加上环境标识(-xx),application-xxx.yml xxx环境标识名字随便写
application-test.yml:手动创建测试环境的配置文件
person:
name: test-张三 # 这是测试环境下的配置
#如何告诉SpringBoot这是测试环境下配置文件呢??
#比如:在配置文件的名字加上环境标识(-xx),application-xxx.yml xxx环境标识名字随便写
控制层方法:
package com.cn.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
//默认的properties直接就可以读取到了,如果是自定义的properties文件还需要指定路径
//@PropertySource("classpath:/application.properties")
public class HelloController {
//如果读取不到使用默认值小李 也可以使用绑定配置文件的方式@ConfigurationProperties
@Value("${person.name:小李}")
private String name; //读取配置文件中的值
@GetMapping("/")
public String name(){
return "Hello"+ name;
}
}
启动运行项目,在页面输入地址访问此方法,输出的结果为:Hello张三
说明:只设置了生产环境和测试环境的标识格式,但没有指定使用的是哪个环境的配置文件,项目运行时使用的是,默认的配置文件application.properties(项目创建好后自动生成的),yml也可以只要名字是application前缀就行。
3) 测试:指定使用配置文件环境
说明:想要指定使用哪个环境,需要在默认的配置文件中设置激活哪个环境。
#值为环境标识, 注意如果注释加在这一行的后面,会让此配置active不会生效
spring.profiles.active=prod
运行结果:输出的是生产环境下设置的名字
4) 测试:出现同名配置
说明:在指定环境标识的情况下,3个配置文件出现了同名的配置,运行时以指定的环境标识下的配置文件为准。
总结执行流程:项目启动永远是先加载默认的配置文件,如果在默认的配置文件中设置了要加载那个环境的配置,那么之后就加载有环境标识的文件。如果默认的配置文件和指定的标识环境文件中出现了相同的配置,则以指定环境标识的文件为准。
端口号:8080
端口号:8000
端口号:7000
运行:
5) 测试:打包后如何切换环境
说明:打包后因为配置文件里面配置的是生产环境prod,所以运行时使用的是生产环境prod,此时想要使用别的环境该如何办呢???
- 修改配置,再重新打包部署太过麻烦
- 可以使用命令,指定使用的环境,即使你在默认的配置文件中指定使用哪个环境,此时仍然使用的是通过命令指定的环境,甚至可以修改配置文件中的配置。
-
java -jar xxx.jar
:运行打包后的jar包项目 -
java -jar xxx.jar --spring.profiles.active=prod
:执行项目并且指定使用那个环境 -
java -jar xxx.jar --spring.profiles.active=test --person.name=haha
:执行项目,指定使用那个环境,并修改name属性值。
-
java -jar profile-test-0.0.1-SNAPSHOT.jar --spring.profiles.active=test --person.name=haha
6.1.2 @Profile条件装配功能(绑定方式注入)
说明:使用 @Profile注解可以按条件注入属性,即符合@Profile注解的值配置才生效。
业务:如果当前是生成环境就注入到Boss中,是测试环境就注入到Worker中
application.properties:
person.name=张三
#值为环境标识, 注意如果注释加在这一行的后面,会让此配置active不会生效
spring.profiles.active=prod
server.port=8080
application-prod.yml
person:
name: prod-张三 # 这是生产环境下的配置
age: 18 #如何告诉SpringBoot这是生产环境下配置文件呢??
#比如:在配置文件的名字加上环境标识(-xx),application-xxx.yml xxx环境标识名字随便写
server:
port: 8000
创建4个对象:
说明:
- Boss和Work分别实现Person接口,之后添加注解@Component这是2个对象。
- 定义一个Color类,创建配置类写2个@Bean的方法创建2个对象。
Person接口:
package com.cn.bean;
public interface Person {
String getName();//接口中的方法 连{}也没有,默认加上public abstract
Integer getAge();
}
Boss实现类:
package com.cn.bean;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
//业务:如果当前是生成环境就注入到Boss中,是测试环境就注入到Worker中
@Profile("prod") //指定prod环境才生效
@Component
@ConfigurationProperties("preperson")//绑定配置文件prod中的以person为前缀的值,因为指定了prod环境。交给Spring容器管理才生效
@Data
public class Boss implements Person {
private String name; //可以获取配置文件中的值
private Integer age;
}
Work实现类:
package com.cn.bean;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
//业务:如果当前是生成环境就注入到Boss中,是测试环境就注入到Worker中
@Profile("test") //指定test环境才生效
@Component
@ConfigurationProperties(prefix = "person")//绑定配置文件prod中的以person为前缀的值,因为指定了prod环境。 交给Spring容器管理才生效
@Data
public class Worker implements Person {
private String name;
private Integer age;
}
Color:
package com.cn.bean;
public class Color {
}
配置类:
package com.cn.config;
import com.cn.bean.Color;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class MyConfig {
@Profile("prod")//当环境是prod时才生效
@Bean
public Color red(){
return new Color();
}
@Profile("test") //当环境是test时才生效
@Bean
public Color green(){
return new Color();
}
}
控制层方法:
package com.cn.controller;
import com.cn.bean.Color;
import com.cn.bean.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
//默认的properties直接就可以读取到了,如果是自定义的properties文件还需要指定路径
//@PropertySource("classpath:/application.properties")
public class HelloController {
//如果读取不到使用默认值小李 也可以使用绑定配置文件的方式@ConfigurationProperties
@Value("${person.name:小李}")
private String name; //读取配置文件中的值
@Autowired
private Person person;
@Autowired
private Color color;
@GetMapping("/")
public String name(){
//反射获取 普通对象获取
return "第一个"+person.getClass().toString()+"第二个"+color.toString();
}
}
运行测试:
可以看到因为在配置文件激活的是prod环境,所以只有是满足@Profile(“prod”)注解标识的方法或类才能够生效。
6.1.3 profile分组
说明:使用profile分组,可以同时激活同一个组下的多个环境。
application.properties:
在默认的配置文件中设置分组,并设置需要激活的分组。
#值为环境标识, 注意如果注释加在这一行的后面,会让此配置active不会生效
# 此时激活的是这个分组下的所有环境:ppd prod
spring.profiles.active=myprod
#定义生产的分组
spring.profiles.group.myprod[0]=ppd
spring.profiles.group.myprod[1]=prod
# 定义测试的分组
spring.profiles.group.mytest[0]=test
application-ppd.yaml
person:
age: 20
application-prod.yaml
person:
name: prod-张三 # 这是生产环境下的配置
#如何告诉SpringBoot这是生产环境下配置文件呢??
#比如:在配置文件的名字加上环境标识(-xx),application-xxx.yml xxx环境标识名字随便写
server:
port: 8000
说明:在默认的配置文件中使用分组同时激活ppd prod,此时2个配置文件的值注入到了此类的name、age属性中(因为使用注解绑定了配置文件)。
测试类:
@GetMapping("/person")
public Person person(){
return person;
}
可以看到:来自同一分组下的 ppd环境中的age: 20 , prod环境中的name: prod-张三,都被输出了。
6.2 外部化配置(配置文件的位置,顺序)
说明:
- 概念:把常用的配置抽取为一个文件放到外面,就叫作外部化配置。
- eg:数据库的连接信息:
- 以前:把连接信息写死到java代码中,项目上线后想要修改每次都需要打包部署,非常麻烦。
- 现在:把连接信息写在配置文件中,之后只需要修改配置文件即可。
- eg:数据库的连接信息:
- 和profile的区别:
- profile:是来指定使用那个配置环境生效的,它解决的是切换环境时需要修改配置文件中的连接信息,之后项目需要重新打包部署的问题。(单环境下的问题)
- 外部化配置:是把常用的配置抽取到配置文件中,解决的是把配置信息写死到代码中,以后如果修改则需要项目重新打包部署问题。(多环境下的问题)
[https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config
](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config)
6.2.1 外部配置源(不只是使用配置文件配置)
说明:外部配置源可以来自不同的地方。
常用:Java属性文件、YAML文件、环境变量、命令行参数(Dos窗口);
测试:配置信息来自环境变量
控制层代码:
@RestController
public class HelloController {
@Value("${MAVEN_HOME}")//获取Maven的安装目录
private String msg;
@Value("${os.name}") //获取操作系统
private String osName;
@GetMapping("/msg")
public String getMsg(){
return msg+"==>"+osName;
}
}
访问:
主启动类输出系统的环境变量所有信息:启动项目后会自动输出
package com.atguigu.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.Map;
@SpringBootApplication
public class Boot09FeaturesProfileApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Boot09FeaturesProfileApplication.class, args);
//获取当前的环境
ConfigurableEnvironment environment = run.getEnvironment();
//系统的环境变量
Map<String, Object> systemEnvironment = environment.getSystemEnvironment();
//系统的属性
Map<String, Object> systemProperties = environment.getSystemProperties();
System.out.println(systemEnvironment);
System.out.println(systemProperties);
}
}
6.2.2 配置文件查找位置(类路径)
说明:配置文件的位置可以有以下5种种方式。仍然有优先级(先执行根路径下的–>在执行根路径下的config目录中的配置文件—>…),如果后执行的配置文件中有同名的配置信息,则后面的会覆盖前面的配置信息。
-
classpath 根路径(类路径下的根路径:即源码标识的路径)
-
classpath 根路径下config目录
-
jar包当前目录
即:项目打成jar包发布到服务器的某一个位置,那么这个jar包所在的目录里面的配置文件也能拿到。 -
jar包当前目录的config目录
即:在config目录下写配置文件 -
linux根目录下的/config子目录的直接子目录(针对于linux系统)
即:在v1目录下写配置文件,这里是在windows下测试用的,执行不成功。
6.2.3 配置文件加载顺序:
说明:这个指的是都使用配置文件的方式进行配置,多个配置文件的加载顺序。
先执行jar内部的默认配置文件—>jar包内部的指定的配置文件---->jar包外部的默认配置文件---->jar包外部的指定配置文件。
如果后面有同名的配置信息,后面会覆盖前面的,即后面的优先级高于前面的。(优先级指的是如果出现同名的配置,后面的会覆盖前面的,而不是文件的加载顺序。
)
- 当前jar包内部的application.properties和application.yml
- 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
- 引用的外部jar包的application.properties和application.yml
- 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
6.2.4 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
说明:这14种指的是不同种类的配置方式,比如使用配置文件配置端口号,使用命令行配置端口号,后面的命令行配置方式优先级高于配置文件的方式。
- Default properties (specified by setting SpringApplication.setDefaultProperties).(java代码主程序设置的值)
-
@PropertySource
annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins. - Config data (such as application.properties files)(application.properties问文件)
- A RandomValuePropertySource that has properties only in random.*.
- OS environment variables.(操作系统的环境变量)
- Java System properties (System.getProperties()).(java系统的属性)
- JNDI attributes from java:comp/env.
- ServletContext init parameters.
- ServletConfig init parameters.
- Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
- Command line arguments.(命令行参数)
- properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
- @TestPropertySource annotations on your tests.
- Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.
- 说明:springboot可以通过以上14种方式获取配置信息的值,优先级从低到高排序(即:从上到下优先级越来越高),如果出现了同名配置,优先级高的会覆盖优先级低的。
6.3 配置优先级
6.3.1 properties、yml、yaml的优先级
- SpringBoot中支持三种格式的配置文件: 如果3份配置文件中都配置了同一个属性,那么哪一份配置文件生效呢?
- 答:properties>yml>yaml
- 注意事项:虽然springboot支持多种格式配置文件,但是在项目开发时,推荐统一使用一种格式的配置(
yml是主流
)。
6.3.2 java系统属性&命令行参数(idea)
-
SpringBoot除了支持
配置文件属性配置
,还支持Java系统属性
和命令行参数
的方式进行属性配置。- java系统属性:
-Dkey.value=xxx
- 命令行参数:
--key.value=xxx
- java系统属性:
-
测试:java系统属性和命令函参数
-
打开配置
-
新版本idea的命令函参数默认隐藏,需要勾选显示
-
输入参数:
-
运行测试:说明命令行参数的方式优于java系统属性
-
6.3.3 java系统属性&命令行参数(打包后)
步骤:
- 执行maven打包指令package
- 执行java指令,运行jar包
- 注意事项:Springboot项目进行打包时,需要引入插件
spring-boot-maven-plugin
(基于官网骨架创建项目,会自动添加该插件)
测试:
-
打包项目
-
找到打包后的jar包
- 方式一:进入到jar包对应的磁盘目录,之后使用Dos窗口打开
- 方式二:直接复制打包后生成在target目录下的jar包到磁盘目录中,之后使用命令行窗口打开。
- 方式一:进入到jar包对应的磁盘目录,之后使用Dos窗口打开
-
在Dos窗口运行命令:
java -Dserver.port=9000 -jar springboot-web-config-0.0.1-SNAPSHOT.jar--server. port=10010
,说明命令行参数的方式优先级大于java系统属性
6.3.4 总结优先级
6.4 Bean管理
6.4.1 获取bean
说明:
- 默认情况下,Spring项目启动时,会把bean都创建好放在IOC容器中,如果想要主动获取这些bean,可以通过如下方式:
- 注意事项:上述所说的【Spring项目启动时,会把其中的bean都创建好】还会受到作用域及延迟初始化影响,这里主要针对于默认的单例非延迟加载的bean而言。
测试需求:
- 导入课前资料的基础工程,里面包含了部门管理的crud操作
- 以获取部门Controller中的bean对象为例
测试步骤:
-
部门Controller:
-
测试类
package com.itheima;
import com.itheima.controller.DeptController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {
/**
* 想要从ioc容器中获取bean对象,首先需要拿到ioc容器,在springboot环境中
* 可以直接将ioc容器对象注入进来就可以了。
*/
@Autowired
private ApplicationContext applicationContext; //IOC容器对象
//获取bean对象
@Test
public void testGetBean(){
//根据bean的名称获取, bean名称默认为类名首字母小写,获取的类型为object类型
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
System.out.println(bean1);
//根据bean的类型获取
DeptController bean2 = applicationContext.getBean(DeptController.class);
System.out.println(bean2);
//根据bean的名称和类型获取
DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
System.out.println(bean3);
}
}
- 运行:
6.4.2 bean作用域
- spring支持五种作用域,后三种在web环境才生效:
- 可以通过@Scope注解来进行配置作用域:
- 注意事项
测试:
- 修改控制层
- 测试方法
package com.itheima;
import com.itheima.controller.DeptController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {
/**
* 想要从ioc容器中获取bean对象,首先需要拿到ioc容器,在springboot环境中
* 可以直接将ioc容器对象注入进来就可以了。
*/
@Autowired
private ApplicationContext applicationContext; //IOC容器对象
//bean的作用域:获取10次bean对象
@Test
public void testScope(){
for (int i = 0; i < 10; i++) {
DeptController deptController = applicationContext.getBean(DeptController.class);
System.out.println(deptController);
}
}
}
- 运行
6.4.3 第三方bean
1)使用背景&建议
说明:
-
之前
:我们在项目中自己定义的类,可以使用@Controller 、@Service、@Component、@repository这几个注解来声明bean对象。 -
现在
:这个类不是我们自己定义的而是引入的第三方依赖中提供的,此时无法使用@component以及衍生注解声明bean,而是需要使用@Bean注解来声明第三方的bean -
建议
:若要管理的第三方bean对象,建议对这些bean进行集中分类配置,可以通过@Configuration注解声明一个配置类。- 方式一:可以直接在Springboot主启动类上进行定义这个第三方的bean,因为主启动类就是一个配置类(不推荐)
- 缺点:因为在项目开发中我们一般要保证主启动类的纯粹性,它只是用来启动整个springboot应用。
- 方式二:建议定义一个配置类,在配置类中对第三方bean进行集中地配置管理。
- 方式一:可以直接在Springboot主启动类上进行定义这个第三方的bean,因为主启动类就是一个配置类(不推荐)
-
使用场景
: -
流程:使用@Bean注解来定义这个第三方的bean,可以在Springboot主启动类中定义一个方法,指定方法的返回值为需要定义的bean对象,方法名字任意,之后在方法中构建这个对象,方法上加上@Bean注解,然后在使用到这个bean的地方直接注入即可。
2)案例测试(使用new)
- 在pom文件中引入解析xml的依赖
<!--Dom4j-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
- 需要解析的xml文件
<?xml version="1.0" encoding="UTF-8"?>
<emp>
<name>Tom</name>
<age>18</age>
</emp>
- 在测试类中的测试方法中编写解析xml的api
//第三方bean的管理
@Test
public void testThirdBean() throws Exception {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml"));
Element rootElement = document.getRootElement();
String name = rootElement.element("name").getText();
String age = rootElement.element("age").getText();
System.out.println(name + " : " + age);
}
- 运行测试:可以看到xml被正确解析
3)案例测试(使用@Bean–主启动类-不推荐)
说明:
- 解析xml文件,每次都需要new一个SAXReader对象是非常耗费资源的,在学习Spring框架之后都是把对象交给Spring容器去管理,用到的时候直接依赖注入,这种方式更加节省资源。
步骤:
- 在主启动类上定义第三方的bean
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
public SAXReader saxReader(){
return new SAXReader();
}
- 修改测试类中的SAXReader对象为注入方式:
- 运行测试:可以看到仍能解析成功。
4)案例测试(使用@Bean–配置类-推荐)
-
注释掉主启动类定义bean的方式
-
定义配置类
package com.itheima.config;
import org.dom4j.io.SAXReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //配置类
public class CommonConfig {
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
//通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
public SAXReader reader(){
return new SAXReader();
}
}
- 运行测试方法:仍然解析成功
声明第三方bean时想要进行依赖注入:
- 修改配置类
package com.itheima.config;
import com.itheima.service.DeptService;
import org.dom4j.io.SAXReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //配置类
public class CommonConfig {
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
//通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
//声明第三方bean时想要进行依赖注入,直接在这个定义bean的方法当中,来指定对应的方法形参即可,
// 那此时呢spring容器它会进行自动装配,根据这个类型去ioc容器当中找到这个bean对象,然后
// 完成注入操作。
public SAXReader reader(DeptService deptService){
/*
* 随便运行一个单元测试,单元测试在运行的时候它会加载整个spring运行环境,此时
* 它会自动调用这个方法并且完成注入操作。
* */
System.out.println(deptService);
return new SAXReader();
}
}
- 运行测试方法:发现在定义第三方bean的方法参数上,注入bean成功。
6.5 自定义starter(场景启动器)
说明:springboot为了简化开发,常用的场景都帮我们抽取出来了,即使这样也不能模拟所有的开发场景。比如我们现在有一个功能非常常用,我们把它抽取出来,其它的模块经常引用,而且引用过来一切场景都要配置好,此时我们就需要把这个功能抽取为自定义的starter。别人一引用这个自定义的stater,所有东西全配置好只需要改下配置文件就能用。
业务场景:
- 问题:starter指的就是Springboot当中的起步依赖,也叫作场景启动器,在Springboot当中已经给我们提供了很多的起步依赖了,那么我们为什么还需要自定义starter起步依赖呢???
- 原因:在实际的项目开发中我们可能会用到很多第三方的技术,并不是所有的第三方技术官方都给我们提供了,与Springboot整合的starter起步依赖,但是这些技术有非常的通用在很多项目组中都在使用。
- 例子:
- 之前我们案例使用到的阿里云oss对象存储服务,阿里云的官方是没有给我们提供对应的起步依赖,这个时候使用起来就比较繁琐。我们需要引入对应的依赖,需要在配置文件中进行配置,还需要基于官方sdk示例来改造对应的工具类,然后在项目中才可以使用。
- 缺点:在当前项目中使用阿里云oss对象存储服务需要进行这么多步操作,那么在别的项目中想要使用阿里云oss还带需要这么多步操作。
- 解决:在实际的开发中,经常会定义一些公共的组件,在这些公共的组件当中提前把需要配置的bean都配置好,将来在项目中使用到的时候直接把这个组件对应的坐标直接引入进来,就可以直接使用了。也可以把这个公共的组件提供给别的项目中进行使用,这样就可以大大的简化开发步骤。
- 在Springboot项目当中一般都会将这些公共组件封装为Springboot当中的starter,也就是我们所说的起步依赖,
起步依赖定义的规范:
-
SpringBoot官方提供的starter起步依赖:都是以spring-boot-starter为前缀
- 格式:spring-boot-starter-功能
- 格式:spring-boot-starter-功能
-
第三方技术整合的SpringBoot,提供的starter起步依赖:一般都是功能在前
- 格式:功能-spring-boot-starter
- 格式:功能-spring-boot-starter
6.5.1 starter启动原理
- starter-pom引入 autoconfigurer 包
- 编写一个stater,当前场景没有任何代码,只是为了告诉当前场景有哪些依赖
- stater引入了当前场景真正配置的包autoconfigure
- 自动配置包autoconfigure需要引入SpringBoot最底层的,每个模块都要用的spring-boot-starter
- autoconfigure包中配置使用
META-INF/spring.factories
中 EnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类 -
编写自动配置类 xxxAutoConfiguration -> xxxxProperties
○ @Configuration
○ @Conditional
○ @EnableConfigurationProperties
○ @Bean
○ …
引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项
6.5.2 自定义starter
atguigu-hello-spring-boot-starter(启动器)
atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)
1)环境准备
一、创建场景启动器项目:以maven为例(场景启动器不需要写代码只需要引入依赖即可,而使用Initailizr创建会帮你创建很多的东西)
二、创建自动配置包项目:
三、创建开发场景项目:
选中web开发场景:
项目结构:
2)编写业务
首先在场景启动器中引入自动配置的场景:
<!--在场景启动器中引入自动配置类,即把自动配置类的项目坐标放入即可-->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>atguigu-hello-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
可以看到添加依赖后,场景启动器会自动依赖自动配置类项目:
其次:在自动配置项目中编写功能:
业务说明:有一个组件的功能经常使用,所以把此功能抽取出来 ,可以实现这个功能场景能够自动配置。
HelloProperties:此类的作用是绑定配置文件读取里面的内容:
package com.atguigu.hello.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("atguigu.hello")//绑定配置文件以atguigu.hello为前缀的属性 加入到容器中才生效
//因为实体类可能用的是第三方包里面的,可能没有加@Component,你也不能擅自加上,此时就可以使用
// 在配置类中添加 @EnableConfigurationProperties注解:开启属性绑定功能,把组件放到容器中。
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;
}
@Override
public String toString() {
return "HelloProperties{" +
"prefix='" + prefix + '\'' +
", suffix='" + suffix + '\'' +
'}';
}
}
HelloService:编写业务功能,用户传入一个人名,会自动拼接上前缀后缀成一个礼貌用语,这个前缀后缀希望是动态的可配置的
package com.atguigu.hello.service;
import com.atguigu.hello.bean.HelloProperties;
import org.springframework.beans.factory.annotation.Autowired;
/**希望有一个功能场景能够自动配置
* 说明:这个组件的功能经常使用,所以把此功能抽取出来
* 这个组件HelloService默认不要在这个地方放在容器中,因为我们可能会进行配置。可以使用配置类的方式,在这里配置好后
* 使用配置类判断如果容器中没有此组件在进行配置。
*/
public class HelloService {
//业务流程:HelloProperties获取到配置文件的值,一旦在配置类中把HelloProperties获取到配置文件的值组件注册到容器中,此时
//就可以在这里注入了。
@Autowired
private HelloProperties helloProperties;
//业务需求:用户传入一个人名,会自动拼接上前缀后缀成一个礼貌用语,这个前缀后缀希望是动态的可配置的
public String sayHello(String userName){
return helloProperties.getPrefix() + ":"+userName+"》"+helloProperties.getSuffix();//前后缀希望是可变的
}
}
HelloServiceAutoConfiguration配置类:把HelloProperties、HelloService都注册到容器中。
package com.atguigu.hello.auto;
import com.atguigu.hello.bean.HelloProperties;
import com.atguigu.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
@EnableConfigurationProperties(HelloProperties.class)//开启属性绑定功能,并会把组件放到容器中
public class HelloServiceAutoConfiguration {
@ConditionalOnMissingBean(HelloService.class)//当容器中没有配HelloService时,此配置类才会生效
@Bean
public HelloService helloService(){
HelloService helloService =new HelloService();
return helloService;
}
}
3)测试
自动配置场景中的配置类不会自动生效,需要在META-INF/spring.factories中配置哪些类在项目启动时加载。
spring.factories文件:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.atguigu.hello.auto.HelloServiceAutoConfiguration
说明:开发场景依赖于----》场景启动器依赖于----》依赖自动配置包
所以首先需要打包自动依赖配置项目,在打包场景启动器项目,作用是把打包后的项目安装到本地仓库里面。之后开发场景在pom.xml文件中添加坐标后只需要更新依赖就可以使用了。
一般是先clean:删除本地仓库中之前可能打包过的同名项目,因为项目的代码可能修修改了所以你需要先删除在重写打包。之后在install打包安装到本地仓库。
在开发场景引入场景启动器,编写配置文件,控制层方法
<!--在开发场景引入场景启动器-->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>atguigu-hello-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
可以看到开发场景依赖了场景启动器,场景启动器又依赖了自动配置包。
application.properties:
atguigu.hello.prefix=nihao
atguigu.hello.suffix=6666
HelloController:
package com.atguigu.controller;
import com.atguigu.hello.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/")
public String sayHello(){
String say = helloService.sayHello("小明");
return say;
}
}
启动当前开发场景项目,在页面输入url进行访问:
总结执行流程:
- 在开发场景引入场景启动器依赖,在场景启动器中引入自动配置场景。
- 自动配置场景一旦启动,它会根据在META-INF/spring.factories的配置加载那个配置类
- 这个自动配置类会注册2个组件,一个是绑定配置文件的组件,一个是业务的组件。其中绑定配置文件的组件会注入到这个业务组件的属性中。
- 此时启动项目访问控制层方法,在方法注入容器中的业务组件调用它的方法,由于读取到的配置文件的值会动态的注入到这个业务的组件的方法中,现在通过业务组件调用方法就可以动态的输出配置文件的值。
- 最终实现:只需要修改配置文件的值,引入别人写好的场景依赖,你就可以使用他写的功能了。
4)自定义业务Bean
说明:可以在开发场景项目中自定义组件,此时用的就是自己的组件,而不是原先引入的自动配置包的HelloService组件了。
package com.atguigu.config;
import com.atguigu.hello.service.HelloService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HelloConfig {
@Bean
public HelloService helloService(){
HelloService helloService= new HelloService();
return helloService;
}
}
DeBug查看:可以看到用的是自己的组件。
6.6 SpringBoot原理
Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理
6.6.1 SpringBoot启动过程
public interface Bootstrapper {
/**
* Initialize the given {@link BootstrapRegistry} with any required registrations.
* @param registry the registry to initialize
*/
void intitialize(BootstrapRegistry registry);
}
文章来源:https://www.toymoban.com/news/detail-815689.html
@FunctionalInterface
public interface ApplicationRunner {
/**
* Callback used to run the bean.
* @param args incoming application arguments
* @throws Exception on error
*/
void run(ApplicationArguments args) throws Exception;
}
@FunctionalInterface
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
* @throws Exception on error
*/
void run(String... args) throws Exception;
}
6.6.2 Application Events and Listeners
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners
ApplicationContextInitializer
ApplicationListener
SpringApplicationRunListener文章来源地址https://www.toymoban.com/news/detail-815689.html
6.6.3 ApplicationRunner 与 CommandLineRunner
四、核心技术之- ->场景整合
到了这里,关于003 第一季SpringBoot2核心技术-核心功能2:数据访问、单元测试、指标监控、原理解析:@Value、命令行参数、手动获取bean、自定义starter的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!