文章目录
五、Docker
Docker可以看我之前跟周阳老师视频学习时记的笔记:https://blog.csdn.net/qq_36903261/article/details/105870268(打开网站Crtl+F直接查询命令)
Docker官网:https://www.docker.com/
DockerHub官网:https://hub.docker.com/
这里需要先安装4个容器,为后续教学做准备:
-
安装mysql
dockerhub的mysql:https://hub.docker.com/_/mysql#拉取最新镜像(可以在mysql后面加上冒号加版本号指定下载(如:mysql:5.6 mysql:8.0.18)) docker pull mysql #启动mysql #--name some-mysql:为启动的mysql容器起名(some-mysql) #-v /my/custom:/etc/mysql/conf.d:设置容器中目录和容器外目录进行共享连接(冒号前为linux路径,冒号后为容器路径,linux下的/my/custom目录和mysql容器的/etc/mysql/conf.d进行共享) #-p 3306:3306:容器对外暴露的端口(冒号前为对外暴露的端口号,冒号后为容器要暴露的端口号) #-e MYSQL_ROOT_PASSWORD=123456:设置root密码(123456) #-d:后台运行 #mysql:运行mysql(如果不是最新版需要在mysql加上冒号和版本号) docker run --name myMysql -v /my/custom:/etc/mysql/conf.d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
-
安装redis(缓存数据库)
dockerhub的redis:https://hub.docker.com/_/redis#拉取最新镜像 docker pull redis #运行redis(redis-server启动路径可以会有偏差,具体参照dockerhub官网上关于redis的具体操作) docker run -p 6379:6379 --name myRedis -v /angenin/myredis/data:/data -v /angenin/myredis/conf:/usr/local/redis/conf -d redis:5.0.5 redis-server /usr/local/redis/conf/redis.conf --appendonly yes
-
安装rabbitmq(消息中间件)
dockerhub的rabbitmq:https://hub.docker.com/_/rabbitmq#拉取镜像(management是有带web的管理界面) docker pull rabbitmq:3.8.3-management
-
安装elasticsearch(全文检索)
dockerhub的elasticsearch:https://hub.docker.com/_/elasticsearch#拉取最新镜像 docker pull elasticsearch
六、SpringBoot与数据访问
1. JDBC
新建项目
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
启动linux中docker的mysql容器(我使用的是8.0.18版本)。
docker run -p 3306:3306 --name myMysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0.18
使用Navicat Premium连接上mysql(使用其他数据库工具都可以)。
并创建一个名为jdbc的数据库。
在resources目录下新建application.yml文件,配置数据库信息
#url使用linux的ip地址
#如果数据库使用的是5.x版本的driver-class-name为com.mysql.jdbc.Driver
#?serverTimezone=GMT是设置时区,8以上版本不设置可能会报错,5版本的可以不加
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://10.211.55.17:3306/jdbc?serverTimezone=GMT
driver-class-name: com.mysql.cj.jdbc.Driver
在test目录下的测试类SpringBoot06DataJdbcApplicationTests中测试:
@SpringBootTest
class SpringBoot06DataJdbcApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads()throws Exception {
//class com.zaxxer.hikari.HikariDataSource
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
//HikariProxyConnection@1905420854 wrapping com.mysql.cj.jdbc.ConnectionImpl@216e9ca3
System.out.println(connection);
connection.close();
}
}
默认使用com.zaxxer.hikari.HikariDataSource数据源,数据源的相关配置都在DataSourceProperties里面。
自动配置原理
org.springframework.boot.autoconfigure.jdbc:
- 参考DataSourceConfiguration,根据配置创建数据源,默认使用hikari连接池,可以使用spring.datasource.type指定自定义的数据源类型。
- SpringBoot默认支持:jdbc、hikari、dbcp2
- 自定义数据源
/** * Generic DataSource configuration. */ @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = "spring.datasource.type") static class Generic { @Bean DataSource dataSource(DataSourceProperties properties) { //使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性 return properties.initializeDataSourceBuilder().build(); } }
使用sql文件自动建表
在resources目录下新建schema-all.sql文件
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`departmentName` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
在application.yml中加入initialization-mode: always
(和username同级)始终初始化数据源。
然后运行SpringBoot项目后,数据库里多了张表
默认只识别schema.sql或schema-all.sql文件,如果想自定义sql文件名(department.sql),可以使用下面这种方法:
在application.yml文件中加入
#schema接受集合,所以使用 - 代表一个集合元素,一个 - 代表一个元素
schema:
- classpath:department.sql
# - classpath:xxx.sql
# - ...
测试JdbcTemplate
操作数据库:SpringBoot自动配置了JdbcTemplate和增强版NamedParameterJdbcTemplate来操作数据库。
- 在数据库表中插入一条数据
- 在springboot目录下新建controller/Hellocontroller
@Controller
public class HelloController {
@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/query")
public Map<String, Object> map() {
List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from department");
return list.get(0);
}
}
注意:每次启动项目时,SpringBoot都会自动建表(如果有sql文件的话,重新建表会覆盖原来的数据),所以如果配置文件中加了schema: - classpath:department.sql,需要注释掉(如果没加但是sql文件名是schema.sql或schema-all.sql,需要将文件改名或移除)
启动项目,在浏览器中输入http://localhost:8080/query
2. Druid
pom.xml中导入druid的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
然后在application.yml中加入type: com.alibaba.druid.pool.DruidDataSource
(和username同级别)
运行测试类,打印的结果为:class com.alibaba.druid.pool.DruidDataSource(切换成功)
在application.yml设置数据源的其他配置:
#url使用linux的ip地址
#如果数据库使用的是5.x版本的driver-class-name为com.mysql.jdbc.Driver
spring:
datasource:
#基本配置
username: root
password: 123456
url: jdbc:mysql://10.211.55.17:3306/jdbc?serverTimezone=GMT
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
type: com.alibaba.druid.pool.DruidDataSource
# schema:
# - classpath:department.sql
#其他配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j2
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
这里设置的其他配置是黄颜色的,这是因为上面的基本属性和DataSourceProperties类里的属性相对应,而其他配置DataSourceProperties类里没有相对应的属性,所以是不生效的。
debug测试类发现配置文件中设置的属性没生效
所以我们要自己写一个配置类进行配置,在springboot目录下新建config/DruidConfig
@Configuration
public class DruidConfig {
//自己创建一个数据源
@ConfigurationProperties(prefix = "spring.datasource") //引入配置
@Bean //添加到容器中
public DataSource druid(){
return new DruidDataSource();
}
}
重新debug测试类,设置的属性生效了
配置druid的监控
在DruidConfig配置类里添加:
//配置druid的监控
//1. 配置一个管理后台的servlet ServletRegistrationBean为注册servlet
@Bean
public ServletRegistrationBean statViewServlet(){
//第一个参数是StatViewServlet为管理后台的servlet,第二个参数为管理的路径
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String, String> initParams = new HashMap<>();
//登录后台时用的用户名
initParams.put("loginUsername", "admin");
//登录使用的密码
initParams.put("loginPassword", "123456");
//允许什么登录(访问)
initParams.put("allow", ""); //默认允许使用
//拒绝指定访问
initParams.put("deny", "192.168.0.107"); //192.168.0.107是我电脑的ip
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
//2. 配置一个监控的filter FilterRegistrationBean为注册filter
@Bean
public FilterRegistrationBean webStatFilter(){
//WebStatFilter为监控的filter
FilterRegistrationBean bean = new FilterRegistrationBean(new WebStatFilter());
Map<String, String> initParams = new HashMap<>();
//设置不拦截的资源
initParams.put("exclusions", "*.js,*.css,/druid/*");
//设置初始化参数
bean.setInitParameters(initParams);
//设置拦截请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
如果启动项目,在浏览器输入http://localhost:8080/druid
输入我们刚才在servlet中设置的用户名和密码,进入后台:
3. MyBatis
新建项目
mybatis的依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
准备工作
- 引入druid
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency>
- 新建一个名为mybatis的数据库
- 在resources目录下新建application.yml文件
spring: datasource: # 数据源基本配置 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://10.211.55.17:3306/mybatis?serverTimezone=GMT type: com.alibaba.druid.pool.DruidDataSource # 数据源其他配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 filters: stat,wall,log4j2 maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
- 在springboot目录下新建config/DruidConfig配置类
package com.angenin.springboot.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.util.Arrays; import java.util.HashMap; import java.util.Map; @Configuration public class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druid(){ return new DruidDataSource(); } @Bean public ServletRegistrationBean statViewServlet(){ ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); initParams.put("loginPassword", "123456"); initParams.put("allow", ""); //默认允许使用 initParams.put("deny", "192.168.0.107"); bean.setInitParameters(initParams); return bean; } @Bean public FilterRegistrationBean webStatFilter(){ FilterRegistrationBean bean = new FilterRegistrationBean(new WebStatFilter()); Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*"); bean.setInitParameters(initParams); bean.setUrlPatterns(Arrays.asList("/*")); return bean; } }
- 启动项目,在浏览器输入
http://localhost:8080/druid/
,测试登录
- 停止项目,在resources目录下新建sql目录,并在里面创建department.sql和employee.sql文件
department.sql
employee.sqlSET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS `department`; CREATE TABLE `department` ( `id` int(11) NOT NULL AUTO_INCREMENT, `departmentName` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS `employee`; CREATE TABLE `employee` ( `id` int(11) NOT NULL AUTO_INCREMENT, `lastName` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `gender` int(2) DEFAULT NULL, `d_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 在application.yml配置文件里添加
#与connectionProperties同级 initialization-mode: always schema: - classpath:sql/department.sql - classpath:sql/employee.sql
- 重新启动项目,创建数据表
- 停止项目,注释掉刚刚在application.yml添加的配置(不注释的话,每次启动项目都会运行指定文件在sql文件,覆盖原来的数据)
- 创建两个数据表对应的bean,在建bean之前,推荐一下lombok(它可以让我们的bean变得看起来不臃肿,虽然它的功能不仅仅只有如此)
引入相关的依赖
添加IDE工具对Lombok的支持:<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <!--只在编译阶段生效--> <scope>provided</scope> </dependency>
安装Lombok插件:
- 设置完后在springboot目录下新建bean包,在包下新建Employee和Department两个实体类
Employee
Department@Data //set/get方法 @AllArgsConstructor //有参构造器 @NoArgsConstructor //无参构造器 public class Employee { private Integer id; private String lastName; private Integer gender; private String email; private Integer dId; }
这里只用了lombok的一个注解,想使用@Data //set/get方法 @AllArgsConstructor //有参构造器 @NoArgsConstructor //无参构造器 public class Department { private Integer id; private String departmentName; }
的其他注解可以自行百度学习,当然,如果不喜欢lombok也可以创建set和get方法,这里只是安利一个好用的插件。
注解
- 在springboot目录下新建mapper.DepartmentMapper接口(这里注解和xml都会使用,这里先用注解来实现,然后再用xml来实现)
@Mapper //指定这是一个操作数据库的mapper public interface DepartmentMapper { @Select("select * from department where id=#{id}") Department getDeptById(Integer id); @Delete("delete from department where id=#{id}") int deleteDeptById(Integer id); //插入后主键会重新封装进Department中 //useGeneratedKeys = true表示使用自动生成的主键,keyProperty = "id"代表department里的id属性是主键 @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("insert into department(departmentName)values(#{departmentName})") int insertDept(); @Update("update department set departmentName=#{departmentName} where id=#{id}") int updateDept(Department department); }
- 在springboot目录下新建controller/DeptController类
@Controller public class DeptController { @Autowired DepartmentMapper departmentMapper; @GetMapping("/dept/{id}") public Department getDepartment(@PathVariable("id") Integer id){ //返回查询到的数据 return departmentMapper.getDeptById(id); } @GetMapping("/dept") public Department insertDept(Department department){ departmentMapper.insertDept(department); //返回我们插入的数据 return department; } }
- 启动项目,在浏览器输入
http://localhost:8080/dept?departmentName=AA
插入一条数据,http://localhost:8080/dept/1
然后查询。
- 问题一:如果插入的时间表里属性名和对应的bean属性名不一致时
- 在写sql语句为数据表里的属性起别名。
- 这里我把departmentName改为department_name,查询结果:
自定义Mybatis的配置规则,开启驼峰命名法映射规则,在config包里新建MyBatisConfig配置类
重新查询的结果:import org.apache.ibatis.session.Configuration; @org.springframework.context.annotation.Configuration public class MyBatisConfig { @Bean public ConfigurationCustomizer configurationCustomizer(){ //setMapUnderscoreToCamelCase(true)开启驼峰命名法映射规则 //Lambda表达式写法 return (Configuration configuration) -> configuration.setMapUnderscoreToCamelCase(true); //普通写法 // return new ConfigurationCustomizer(){ // @Override // public void customize(Configuration configuration) { // configuration.setMapUnderscoreToCamelCase(true); // } // }; } }
- 问题二:如果有多有很多mapper,每个都加@Mapper注解太麻烦了,我们可以在MyBatisConfig配置类(或者SpringBoot06DataMybatisApplication主配置类)上加上@MapperScan(value = “com.angenin.springboot.mapper”)注解,把mapper包下的所有接口作为mapper,批量扫描所有的mapper接口。
xml
- 在springboot/mapper下新建EmployeeMapper
//已经在MyBatisConfig配置类里加了@MapperScan注解,所以不需要加@Mapper public interface EmployeeMapper { Employee getEmpById(Integer id); void insertEmp(Employee employee); }
- 在resources目录下新建mybatis目录,在里面新建mybatis-config.xml全局配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 开启驼峰命名法映射规则 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>
- 在resources/mybatis目录下mapper目录,在里面新建EmployeeMapper.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.angenin.springboot.mapper.EmployeeMapper"> <select id="getEmpById" resultType="com.angenin.springboot.bean.Employee"> select * from employee where id=#{id} </select> <insert id="insertEmp"> insert into employee(lastName,email,gender,d_id)values(#{lastName},#{email},#{gender},#{dId}) </insert> </mapper>
- 在application.yml中添加
mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml
- 在DeptController中添加:
@Autowired EmployeeMapper employeeMapper;
- 往employee数据表中添加数据:
insert into employee(lastName,email,gender,d_id)values('zhangsan','zhangsan@qq.com',1,1); insert into employee(lastName,email,gender,d_id)values('lisi','lisi@qq.com',0,2);
- 启动项目,浏览器输入
http://localhost:8080/emp/1
4. JPA
JPA:基于ORM(Object Relational Mapping)思想,所以需要编写实体类(bean)和数据表进行映射,并且配置好映射关系。
新建项目
-
新建数据库
-
在resources目录下新建application.yml,配置数据源
spring: datasource: username: root password: 123456 url: jdbc:mysql://10.211.55.17:3306/jpa?serverTimezone=GMT driver-class-name: com.mysql.cj.jdbc.Driver
-
在springboot目录下entity/User实体类(这里不使用lombok,常用普通写法,和lombok做对比)
//使用JPA注解配置映射关系 @Entity //告诉JPA这是一个实体类(和数据表映射的类) @Table(name = "tbl_user") //指定和哪个数据表对应,如果省略name不写,默认是对应的数据表名是类名首字母小写即user public class User { @Id //这是一个主键 @GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键自增 private Integer id; @Column(name = "last_name", length = 50) //设置和数据表对应的列名 private String lastName; @Column //省略默认属性名就是列名 private String email; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
-
在springboot下新建repository/UserRepository,来操作实体类对应的数据表(即dao层)
@Repository //继承JpaRepository即可操作数据库(第一个参数为要操作的类,第二个参数为主键的类型) public interface UserRepository extends JpaRepository<User, Integer> { }
-
对应的数据表可以自己创建,也可以配置后自动创建,在application.yml中添加配置,具体可查看JpaProperties(jpa与datasource同级别)
jpa: hibernate: ddl-auto: update #更新或创建数据表结构 show-sql: true #控制台显示sql
-
启动项目后查看数据库
-
对数据的增删查改,在springboot包里新建controller/UserController
@RestController public class UserController { @Autowired UserRepository userRepository; @GetMapping("/user/{id}") public User getUser(@PathVariable("id") Integer id){ return userRepository.findById(id).get(); } @GetMapping("/user") public User insertUser(User user){ return userRepository.save(user); //save方法返回的user对象有自增的主键 } }
-
启动项目,浏览器输入
http://localhost:8080/user?lastName=zhangsan&email=zhangsan@qq.com
插入一条数据,然后输入http://localhost:8080/user/2
(因为我先插了一条,忘截图了,所以这里id是2)
七、SpringBoot启动配置原理
启动流程
-
创建SpringApplication对象
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; //判断primarySources是否为空,如果为空就抛出异常throw new IllegalArgumentException(msg),代码如果不捕捉处理这个异常,代码不往下执行,不为空代码继续向下执行。 Assert.notNull(primarySources, "PrimarySources must not be null"); //保存主配置类 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //保存当前应用的类型SERVLET(有三种类型:REACTIVE、SERVLET、NONE) this.webApplicationType = WebApplicationType.deduceFromClasspath(); //从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然后保存起来(详情看下图) setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //从类路径下找到META-INF/spring.factories配置的所有ApplicationListener,然后保存起来(详情看下图) setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //从多个配置类中找到有main方法的主配置类 this.mainApplicationClass = deduceMainApplicationClass(); }
保存起来的META-INF/spring.factories配置的所有ApplicationContextInitializer,共7个。
保存起来的META-INF/spring.factories配置的所有ApplicationListener,共11个。
-
运行run方法
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); //开启停止监听 stopWatch.start(); //声明一个IOC容器 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //与awt相关(图形界面) configureHeadlessProperty(); //获取SpringApplicationRunListeners(点进去发现也是从类路径META-INF/spring.factories中获取(详情看下图)) SpringApplicationRunListeners listeners = getRunListeners(args); //回调(运行)获取到的所有SpringApplicationRunListeners的starting()方法(因为只有一个,所以在里面只循环了一次) listeners.starting(); try { //封装命令行参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //准备环境 // 1. 创建并配置环境 // 2. 回调(运行)获取到的所有SpringApplicationRunListeners的environmentPrepared方法(表示环境准备完成) ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //得到系统属性spring.beaninfo.ignore,如果为空设置为true configureIgnoreBeanInfo(environment); //打印Banner图标(详情看下图) Banner printedBanner = printBanner(environment); //创建ApplicationContext(根据应用的类型创建IOC容器(当前为应用类型为servlet)) context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //准备上下文环境 // 1. 将environment保存到ioc容器中 // 2. applyInitializers(context):回调(运行)之前获取到的所有ApplicationContextInitializer的initialize方法 // 3. listeners.contextPrepared(context):回调(运行)之前获取到的所有ApplicationListener的contextPrepared方法 // ... // 4. 最后listeners.contextLoaded(context):回调(运行)之前获取到的所有ApplicationListener的contextLoaded方法 prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新容器(IOC容器初始化完成,此时所有的组件bean都创建了) //扫描、创建、加载所有组件的地方() refreshContext(context); //点进去发现是个空方法 afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } //回调(运行)之前获取到的所有ApplicationListener的started方法 listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } //整个springboot应用启动完成以后返回启动的IOC容器 return context; }
从类路径META-INF/spring.factories中获取的SpringApplicationRunListeners
事件监听机制
几个主要的事件回调机制:
配置在META-INF/spring.factories中
- ApplicationContextInitializer
- SpringApplicationRunListener
只需要放在IOC容器中
- ApplicationRunner
- CommandLineRunner
创建实现了上面4个接口的类
ApplicationContextInitializer
package com.angenin.springboot07.listener;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
//泛型是监听的对象(ConfigurableApplicationContext是IOC容器对象)
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("HelloApplicationContextInitializer...initialize..." + configurableApplicationContext);
}
}
SpringApplicationRunListener
package com.angenin.springboot07.listener;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
//需要配置有参构造器,不然会报错
public HelloSpringApplicationRunListener(SpringApplication application, String[] args) {
}
@Override
public void starting() {
System.out.println("SpringApplicationRunListener...starting...监听容器的开始");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
//environment为环境信息
//获取系统的属性,os.name是系统的名字
Object o = environment.getSystemEnvironment().get("os.name");
System.out.println("SpringApplicationRunListener...environmentPrepared...环境准备好了 " + o);
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
//context为IOC容器
System.out.println("SpringApplicationRunListener...contextPrepared...IOC容器准备好了");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextLoaded...服务器环境加载完成");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...started...");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...running...");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("SpringApplicationRunListener...failed...");
}
}
ApplicationRunner
package com.angenin.springboot07.listener;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
@Component
public class HelloApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...run...");
}
}
CommandLineRunner
package com.angenin.springboot07.listener;
import org.springframework.boot.CommandLineRunner;
import java.util.Arrays;
@Component
public class HelloCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...run..." + Arrays.asList(args));
}
}
ApplicationRunner和CommandLineRunner只需要加上@Component添加到容器中即可使用。
ApplicationContextInitializer和SpringApplicationRunListener需要在resources目录下新建META-INF目录,并在里面新建一个名为spring.factories的文件。
org.springframework.context.ApplicationContextInitializer=\
com.angenin.springboot07.listener.HelloApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\
com.angenin.springboot07.listener.HelloSpringApplicationRunListener
启动后控制台输出的信息
SpringApplicationRunListener...starting...监听容器的开始
SpringApplicationRunListener...environmentPrepared...环境准备好了 null
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.0.RELEASE)
HelloApplicationContextInitializer...initialize...org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@23f7d05d, started on Thu Jan 01 08:00:00 CST 1970
SpringApplicationRunListener...contextPrepared...IOC容器准备好了
2020-05-24 21:53:25.458 INFO 41926 --- [ main] c.a.s.SpringBoot07Application : Starting SpringBoot07Application on prodeMacBook-Pro.local with PID 41926 (/Users/pro/IdeaProjects/spring-boot-07/target/classes started by pro in /Users/pro/IdeaProjects/spring-boot-07)
2020-05-24 21:53:25.465 INFO 41926 --- [ main] c.a.s.SpringBoot07Application : No active profile set, falling back to default profiles: default
SpringApplicationRunListener...contextLoaded...服务器环境加载完成
2020-05-24 21:53:27.954 INFO 41926 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-05-24 21:53:27.972 INFO 41926 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-05-24 21:53:27.973 INFO 41926 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.35]
2020-05-24 21:53:28.099 INFO 41926 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-05-24 21:53:28.099 INFO 41926 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2548 ms
2020-05-24 21:53:28.467 INFO 41926 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-05-24 21:53:28.782 INFO 41926 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-24 21:53:28.794 INFO 41926 --- [ main] c.a.s.SpringBoot07Application : Started SpringBoot07Application in 3.978 seconds (JVM running for 5.535)
SpringApplicationRunListener...started...
ApplicationRunner...run...
CommandLineRunner...run...[]
SpringApplicationRunListener...running...
如果需要在项目特定位置运行相应的代码,可以使用上面的实现类来完成。
八、SpringBoot自定义starters
starter:场景启动器
- 这个场景需要使用到什么依赖?
- 如何编写自动配置
@Configuration //指定这个类是配置类 @ConditionalOnXXX //在指定条件成立的情况下自动配置类生效 @AutoConfigureAfter //指定自动配置类的顺序 @Bean //给容器中添加组件 @ConfigurationPropertie //结合相关xxxProperties类来绑定相关的配置 @EnableConfigurationProperties //让xxxProperties加入到容器中生效 //让自动配置类能加载:需要将需要启动就加载的自动配置类,配置到META-INF/spring.factories。 //例如: //org.springframework.context.ApplicationContextInitializer=\ //org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ //org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
- 模式:
启动器只用来做依赖导入,专门写一个自动配置模块,然后启动器依赖自动配置模块,而别人只需要引入启动器(starter),自动配置也会被引入。
官方命名为:spring-boot-starter-模块名
我们自定义命名:模块名-spring-boot-starter
创建自定义驱动器
添加模块:
选择Maven
在创建一个模块,选择Spring Initializr
不引入模块
Maven创建的用来做启动器,Spring Initializr创建的用来做自动配置。
复制自定义的自动配置模块的坐标
在启动器的pom.xml中引入
删除自动配置模块的主程序和主配置。
删除自动配置pom.xml中的test依赖,和插件(spring-boot-starter是所有starter的基本配置,所以需要留着)
并且把根目录下的test目录也删掉
写业务
HelloProperties
package com.angenin.starter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "angenin.hello")
public class HelloProperties {
//前缀
private String prefix;
//后缀
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
HelloService
package com.angenin.starter;
public class HelloService {
HelloProperties helloProperties;
public HelloProperties getHelloProperties() {
return helloProperties;
}
public void setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
public String sayHello(String name){
return helloProperties.getPrefix() + name + helloProperties.getSuffix();
}
}
HelloServiceAutoConfiguration
package com.angenin.starter;
public class HelloService {
HelloProperties helloProperties;
public HelloProperties getHelloProperties() {
return helloProperties;
}
public void setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
public String sayHello(String name){
return helloProperties.getPrefix() + name + helloProperties.getSuffix();
}
}
而要让这个自动配置类能生效,需要在resources目录下新建META-INF目录,在里面新建一个名为spring.factories的文件。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.angenin.starter.HelloServiceAutoConfiguration
把设置好的自动配置模块安装到maven仓库中
然后再把启动器也安装到maven仓库中
新建项目,用于测试自定义的starter
复制自定义starter的坐标
在新建的项目里引入
在新建的项目springboot目录下新建controller/Hellocontroller,自动注入自定义的starter中的HelloService
@RestController
public class Hellocontroller {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello(){
return helloService.sayHello("haha");
}
}
自动配置HelloProperties的前后缀可以在application.properties配置文件设置
angenin.hello.prefix=ANGENIN
angenin.hello.suffix=HELLOWORLD
启动项目,在浏览器中输入http://localhost:8080/hello
下一篇笔记:SpringBoot高级篇学习笔记(四、缓存与消息)
学习视频(p53-p72):https://www.bilibili.com/video/BV1gW411W76m?p=53