springBoot 基础入门
阅读原文时间:2023年07月10日阅读:1

来处:是spring项目中的一个子项目

优点  (被称为搭建项目的脚手架)

减少一切xml配置,做到开箱即用,快速上手,专注于业务而非配置

  从创建项目上: -- 快速创建独立运行的spring项目以及与主流框架集成(云计算)

从运行项目上:--使用嵌入式的Servlet 容器,应用无需打成war包    直接打成jar包使用java -jar就可以启动

从项目jar包上 :-- starters 自动依赖与版本控制,大量的自动配置。

微服务:

一个应用就是一组小型的服务,可以通过http的方式进行互通

每一个功能元素最终都是一个可独立替换和独立升级的软件单元

查看maven版本  mvn -v

搭建一个springboot应用(由浅到深的了解便于记忆)

1. 1.1同以往搭建项目一样首先重视的就是环境配置

jar包会被场景启动器所替换首先会引入场景启动器


org.springframework.boot spring-boot-starter-parent 2.1.6.RELEASE

从官网上拷贝下来的依赖需要什么场景启动器就引入什么场景启动器就行


org.springframework.boot spring-boot-starter-web
org.springframework.boot spring-boot-starter-test test
com.jayway.jsonpath json-path test

   也可以加入日志文件

   <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-logging</artifactId>  
    </dependency>

1.8

spring的几个项目结构介绍

libraries 库(maven自定下载的jar包)

compiler.xml 编译器(主要用于maven初始化的编译)

modules.xml(模块的xml)

.mvn :主要用于版本切换   因为maven的版本和插件配合并不是那么完美,.mvn为解决这个问题有一个第三方的插件wrapper可以使用

properties文件中记录你要使用的maven版本当用户执行mvn的clear命令时发现当前用户使用的maven版本和用户期望的版本不一样那么就下载期望的maven版本。

.gitignore 的作用 忽略不需要上传的文件

demo.iml    idea工程的配置文件是当前project的一些配置信息

开启第一个springboot项目

MVC 中  创建的第一个Controller类

注解

@RestController /*ResponseBody+Controller 返回json类型的数据*/ https://www.cnblogs.com/zhulina-917/p/11555833.html RESTFUL风格基础概念

@RequestMapping("hello") 请求映射 可以配置全局的也可以配置局部的

@EnableAutoConfiguration /*启用自动配置如果不声明那么他就认为你不需要自动配置*/

实际上springboot的自动配置是根据你引入了什么依赖,如果你引入了他就会给你自动配置这些需要的config 或则javabean 或者class

后来我将springboot启动类换到了另外一个方法中

出现了一个异常   https://www.cnblogs.com/zhulina-917/p/11556862.html 解决办法

因为我换了类但是忘记了换类名所以才报错

** WARNING ** : Your ApplicationContext is unlikely to start due to a @ComponentScan of the default package.

这个是因为  ComponentScan注解的默认扫描范围是启动程序XxxApplication. java所在目录及其下的所有子包。

所以你不可以直接放在java目录下需要建一个父包 然后进行扫描,这时就可以了

对比以前的写法

Properties properties = new Properties(); properties.load(JDBCUtil.class.getResourceAsStream("/jdbc.properties")); 底层使用流来进行读取

springboot 现在的写法

1.在resources下创建jdbc.properties

@Configuration //声明一个类是java配置类相当于一个xml配置文件
@PropertySource("classpath:jdbc.properties") //指定外部属性文件 读取我们的资源文件
public class jdbcConfiguration {
//声明jdbc的配置文件
//使用属性注入的方式
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean //类似一个bean标签将方法的返回值加入bean容器
public DataSource dateSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(this.driverClassName);
dataSource.setUrl(this.url);
dataSource.setUsername(this.username);
dataSource.setPassword(this.password); 这里用上this主要是为了区分成员变量和局部变量
return null;

}  

}

升级写法

设置set/get方法

springboot   给我们提供了四种注入方式 第一种是set/get方式也就是上面那种方式

第二种是通过 构造方法

第三种:通过bean方法的形参也可以自动注入

第四种:是一种更加简单的方法

@ConfigurationProperties(prefix = "jdbc") 不仅可以用在类上也可以用在方法上。

底层使用Datasource 的构造方法 get/set方法

4.yml文件中的值不能以*开头,可以用 "*" 加个引号开头。

/*来标注主程序类,来说明这是一个spring boot应用
* */
@SpringBootApplication
public class SpringBoot01ConfigApplication {

public static void main(String\[\] args) {  
    /\*springboot应用启动起来\*/  
    SpringApplication.run(SpringBoot01ConfigApplication.class, args);  
}

package cn.edu.aynu.springboot01config;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {
@RequestMapping("/some")
public String doFirst(){
return "Helloworld";
}

}

将一下工程的监控器,监控工程的运行状态

Actuator是Spring Boot提供的一个可插拔模块,用于对工程进行监控。其通过不同的监控终端实现不同的监控功能。其功能与Dubbo的监控中心类似,不同的是,Dubbo的监控中心是需要专门部署的,而Spring Boot的Actuator是存在于每一个工程中的。官网https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#production-ready

导入依赖

org.springframework.boot spring-boot-starter-actuator

有一点要注意:Actuator 与 elasticsearch 是相互冲突的。如果导入了 Acturtor 就不要再使用 elasticserach 否则报错

server:
port: 8081
tomcat:
uri-encoding: UTF-8

management:
server:
port: 9999
servlet:
context-path: /xxx
endpoints:
web:
base-path: /base
endpoint:
health:
show-details: always

actuator配置

{
"status": "DOWN",
"details": {
"diskSpace": {
"status": "UP",
"details": {
"total": 15453913088,
"free": 7720787968,
"threshold": 10485760
}
},
"redis": {
"status": "DOWN",
"details": {
"error": "org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379"
}
}
}
}

postman响应结果

management.server.port=9999
management.server.servlet.context-path=/xxx
management.endpoints.web.base-path=/actuator

#自定义info 信息
info.company.name=abc
info.auth.name=lisi

自定义info信息

{
"company": {
"name": "abc"
},
"auth": {
"name": "lisi"
}
}

响应结果

开放所有的监控终端

management.endpoints.web.exposure.include=*

排除某些不想开放的终端

management.endpoints.web.exposure.exclude=env,beans

常用的监控终端

在配置文件中取出属性值

方法一:

使用主配置文件或者自定义的配置文件都可以
@RestController
@PropertySource(value = "classpath:student.properties",encoding = "utf-8")
/*@PropertySource(value = "application.properties")*/
public class controller {

@Value("${student.id}")
int id;

@Value("${student.name}")
String name;
@Value("${student.age}")
int age;

@RequestMapping("/hello")  
public String handler(){

    return id+","+name+","+age;  
}

Controller   在controller 中的注解式 @PropertySource

student.id = 111111111
student.name = zhu
student.age = 12

student.properties

但是建议一般不要写在主主配置文件中

方法二

将实体类声明为一个组件,并使用ConfigurationProperties注解将配置文件中的属性赋值上去

package cn.edu.aynu.bean;

import com.sun.tracing.dtrace.ArgsAttributes;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "student")
public class Student {

private int id;  
private String name;  
private int age;

public int getId() {  
    return id;  
}

public void setId(int id) {  
    this.id = id;  
}

public String getName() {  
    return name;  
}

public void setName(String name) {  
    this.name = name;  
}

public int getAge() {  
    return age;  
}

public void setAge(int age) {  
    this.age = age;  
}

@Override  
public String toString() {

    return "Student{" +  
            "id=" + id +  
            ", name='" + name + '\\'' +  
            ", age=" + age +  
            '}';  
}  

}

Student

student:
id: 12
name: lisi
age: 34

application.yml

package cn.edu.aynu.controller;

import cn.edu.aynu.bean.Student;
import cn.edu.aynu.service.ServiceHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.bind.annotation.*;

@RestController
public class controller {

@Autowired  
Student  student;  
@RequestMapping("/hello")  
public String handler(){

    System.out.println(student);  
    return "/hello";  
}

}

Controller

controller 中需要注意一点,当我们使用自动注入bean的时候,是可以得到值的,但是如果我们在重新定义一个剧不能得到值了,应为那就相当于是重写了这个方法。

需要注意一点,有时候我们不仅仅需要的属性只是int 或者是String 我们可能需要的是map 或者list

如果配置文件没有了提示就导入这个jar包


org.springframework.boot spring‐boot‐configuration‐processor true

processor

可以将程序打包成jar包

org.springframework.boot spring-boot-maven-plugin

怎么打成jar包

可以直接在命令行上进行启动执行

执行成功 这个路径是Controller里面的requestMapping

探究原理

自动导入的jar包

父项目

org.springframework.boot spring-boot-starter-parent 2.1.0.RELEASE

所以又叫他Springboot的仲裁中心

导入的依赖

org.springframework.boot spring-boot-starter-web

主程序类

package cn.edu.aynu.springboot01config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/*来标注主程序类,来说明这是一个spring boot应用
* */
@SpringBootApplication
public class SpringBoot01ConfigApplication {

public static void main(String\[\] args) {  
    /\*springboot应用启动起来\*/  
    SpringApplication.run(SpringBoot01ConfigApplication.class, args);  
}  

}

/*这个类的所有方法返回的数据写给浏览器,如果是对象转为json格式*/
/*@ResponseBody
@Controller*/
@RestController /*@RestController 直接代表了@RequestBody 和@Controller*/
public class MyController {

@RequestMapping("/some")  
public String doFirst(){  
    return "Helloworld";  
}

}

application.properties的配置文件

server.port=9090 # 服务端口号
server.tomcat.uri-encoding=UTF-8 #以Tomcat为web容器时的字符编码

spring.application.name=customer # 应用名称,一般就是项目名称,这个名称在SpringCloud中比较关键
spring.profiles.active=dev #指定当前的活动配置文件,主要用于多环境多配置文件的应用中
spring.http.encoding.charset=UTF-8 #http请求的字符编码
spring.http.multipart.max-file-size=10MB #设置文件上传时单个文件的大小限制
spring.http.multipart.max-request-size=100MB #设置文件上传时总文件大小限制

spring.thymeleaf.prefix=classpath:/templates/ #配置在使用Thymeleaf做页面模板时的前缀,即页面所在路径
spring.thymeleaf.suffix=.html #设置在使用Thymeleaf做页面模板时的后缀
spring.thymeleaf.cache=false #设置在使用Thymeleaf做页面模板时是否启用缓存

spring.mvc.static-path-pattern=/** #设置静态资源的请求路径
spring.resources.static-locations=classpath:/static/,classpath:/public/ #指定静态资源的路径

##以下是使用MySQL数据库的配置
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect #指定数据库方言
hibernate.show_sql=true #是否显示sql语句
hibernate.hbm2dll.auto=update #设置使用Hibernate的自动建表方式
entitymanager.packagesToScan=com.zslin #设置自动扫描的包前缀

spring.datasource.url=jdbc:mysql://localhost:3306/customer?\
useUnicode=true&characterEncoding=utf-8&useSSL=true&autoReconnect=true #数据库链接
spring.datasource.username=root #数据库用户名
spring.datasource.password=123 #数据库用户对应的密码
spring.datasource.driver-class-name=com.mysql.jdbc.Driver #数据库驱动名称

application.properties

@ConfigurationProperties主要作用:就是绑定application.properties中的属性

使用方法
@ConfigurationProperties(prefix = "spring.datasource")

application.properties 文件
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.url=jdbc:mysql:///jdbc
spring.datasource.driver-class-name=com.alibaba.druid.proxy.DruidDriver

springboot的包扫描问题

根据英文的提示是在配置中找不到一个指定自动注入类型的bean,SpringBoot项目的Bean装配默认规则是根据Application类所在的包位置从上往下扫描!Application类是指SpringBoot项目入口类。
这个类的位置很关键:如果mapper、service所在包不在其子包下,则不会被扫描。
即, 把Application类放到mapper、service所在包的上级,

两种解决办法: 
  1 .将接口与对应的实现类放在与application启动类的同一个目录或者他的子目录下,这样注解可以被扫描到,这是最省事的办法
  2 .在指定的application类上加上这么一行注解,手动指定application类要扫描哪些包下的注解(图3)

图3

@ComponentScan(basePackages = "cn.edu.aynu.*")

server:
port: 8087

xml

port:8087


org.springframework.boot spring-boot-configuration-processor

做一个小练习

定义两个类  在yaml文件中给属性赋值

people:
lastName: zs
age: 12
boss: ls
birth: 2017/12/14
maps: {k1: v1,k2: v2}
lists:
- lisi
- zhou
dog:
name: 小狗
age: 23

现在如何将两个属性与java bean类进行绑定

/*将配置文件中的每一个值映射到这个组件中
* @ConfigurationProperties 告诉springboot这个类中的属性和配置文件的属性进行绑定
* 加上前缀是因为配置文件中可以写入很多的属性值要找到相对应的那一个。进行一一映射
* 只有这个组件是容器中的组件才能使用容器中提供的功能,所以要将这个类声明为组件
* */
@Component
@ConfigurationProperties(prefix = "people")
public class People {
private String lastName;
private Integer age;
private Boolean boss;
private Date birth;
private Map maps;
private List lists;
private Dog dog;

public class Dog {
private String name;
private Integer age;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBoot01ConfigApplicationTests {
@Autowired
People people;
@Test
public void contextLoads() {
System.out.println(people);

}

执行成功

在properties配置文件中进行配置

people.last-name=zs
people.age=23
people.birth=2013/12/12
people.boss=false
people.maps.k1=v1
people.maps.k2=v2
people.lists=a,b,c
people.dog.name=li
people.dog.age=34

如果在peoperties文件中产生了乱码就在idea中改变一下,file-setting-输入file Enconding 调一下

@Component
//@ConfigurationProperties(prefix = "people")
public class People {
/*在spring中进行给变量赋值
* * * 那么注解也支持这几种方式
* 采用注解的方式
*/
@Value("${people.last-name}")
private String lastName;
@Value("#{11*2}")
private Integer age;
private Boolean boss;
private Date birth;
private Map maps;
private List lists;
private Dog dog;

@ConfigurationProperties(prefix = "people")代表从全局配置文件中获取值,自己也可以定义配置文件

@PropertySource(value = {"classpath:people.properties"})
@Component
@ConfigurationProperties(prefix = "people")
public class People {
/*在spring中进行给变量赋值
* * * 那么注解也支持这几种方式
* 采用注解的方式
*/
@Value("${people.last-name}")
private String lastName;
@Value("#{11*2}")
private Integer age;
private Boolean boss;
private Date birth;
private Map maps;
private List lists;
private Dog dog;

这里面需要补充一点,也是需要注意的地方

@PropertySources 和 ConfigurationProprtties 这两个注解式相辅相成的

如果只是加上 @COnfigurationSources这个注解那么,与bean绑定的属性值默认是从主配置文件中加载,

但是如果加上 PropertySources这个注解,则可以从自己自定义的 XXX.properties 中加载。

@PropertySource(value = {"classpath:person.properties"})  //加载指定的配置文件;

@ImportResource  //导入Spring的配置文件,让配置文件里面的内容生效;Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上

用法:

@ImportResource(locations = {"classpath:beans.xml"})
导入Spring的配置文件让其生效这个配置文件主要是让配置文件进行生效。


http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring‐beans.xsd">

beans.xml

@Bean

SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式1、配置类@Configuration------>Spring配置文件2、使用@Bean给容器中添加

举个例子

* 在配置文件中用标签添加组件
*
*/
@Configuration
public class MyAppConfig {
//将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
@Bean
public HelloService helloService02(){
System.out.println("配置类@Bean给容器中添加组件了…");
return new HelloService();
}
}

@ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;prefix = "person":配置文件中哪个下面的所有属性进行一一映射只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;@ConfigurationProperties(prefix = "person")默认从全局配置文件中获取值;


http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

@Test
public void testService(){
boolean helloService = ioc.containsBean("hello");
System.out.println(helloService);
}

@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
public class SpringBoot01ConfigApplication {

public static void main(String\[\] args) {  
    /\*springboot应用启动起来\*/  
    SpringApplication.run(SpringBoot01ConfigApplication.class, args);  
}  

}

/*@Configuration 指明当前类就是一个配置类,用来代替之前的spring配置文件
* 在标签中使用添加组件
*
* */

@Configuration
public class RegisterConfig {
/*将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名*/
@Bean
public helloService helloService02(){
return new helloService();
}
}

package cn.edu.aynu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Description: review-project
* Created by lenovo on 2019/4/26 16:33
*/
@Configuration //告诉springboot 这是一个配置类
public class MyConfig {
@Bean //将方法的返回值添加到容器中,容器中这个组件默认的id就是方法名
public HelloService configApp(){
System.out.println("@bean给容器中添加了组件,根据方法名这到这个类");
return new HelloService();
}

}

自定义配置类

2019-04-26 16:38:36.327 INFO 11556 --- [ main] cn.edu.aynu.ReviewApplication : Starting ReviewApplication on LAPTOP-4U4DHM78 with PID 11556 (E:\sturts2\review-project\target\classes started by lenovo in E:\sturts2\review-project)
2019-04-26 16:38:36.336 INFO 11556 --- [ main] cn.edu.aynu.ReviewApplication : No active profile set, falling back to default profiles: default
2019-04-26 16:38:38.298 INFO 11556 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2019-04-26 16:38:38.302 INFO 11556 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2019-04-26 16:38:38.414 INFO 11556 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 88ms. Found 0 repository interfaces.
2019-04-26 16:38:39.418 INFO 11556 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)
2019-04-26 16:38:39.449 INFO 11556 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-04-26 16:38:39.449 INFO 11556 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.17]
2019-04-26 16:38:39.653 INFO 11556 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-04-26 16:38:39.653 INFO 11556 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3207 ms
@bean给容器中添加了组件,根据方法名这到这个类

控制台打印的内容

通过这个控制台打印的信息可以看出  Root WebApplicationContext: initialization completed in 3207 ms

当WebApplicationContext 初始化完成后,就打印了自定义信息

看一下控制台的输出就可以知道程序执行的先后顺序,挑几个我认识的

1.先检查一个有没有多环境选择的profile
No active profile set, falling back to default profiles: default
2.是对 Spring Data repository 的扫描
Finished Spring Data repository scanning in 88ms. Found 0 repository interfaces.

Tomcat initialized with port(s): 8081 (http)
 Starting service [Tomcat]
 Starting Servlet engine: [Apache Tomcat/9.0.17]
 Initializing Spring embedded WebApplicationContext
 Root WebApplicationContext: initialization completed in 3207 ms

……………..

在配置文件中的代码,一般都是按照配置文件的先后循序执行。

解释一下spring Data (https://www.cnblogs.com/airycode/p/6535323.html可以参考这个博主)

Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法 public interface Repository { }

Spring Data可以让我们只定义接口,只要遵循 Spring Data的规范,就无需写实现类。
与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。如下两种方式是完全等价的

Repository 的子接口

基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:
1.Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类

2.CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法

3.PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法

4.JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法

5.自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。

6.JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法

https://www.cnblogs.com/airycode/p/6535323.html

springData

---代表一个文档

配置文件的加载顺序:

springboot启动会扫描以下位置的application.properties或者application.yml作为springboot的默认配置文件

-file:./config

-file:./

-classpath:/config/

-classpath:/

优先级从高到低,高优先级的配置会覆盖低优先级的配置。

最重要的是,springboot会从这四个位置全部加载配置文件,想成互补配置  

我们还可以通过改变

spring.config.location 来改变默认配置文件的位置,但是这个方式有一个前提就是需要在项目打包好以后,启动项目的时候指定配置文件的新位置,

所有的配置文件都会形成互补配置

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties

刚才讲的都是内部文件的加载顺序,现在讲一下外部文件的加载顺序

1.命令行参数,所有你命令都可以在命令行中进行指定

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc

特点:可以同时加载多个配置项,但是多个配置项之间需要使用空格隔开

2由jar包外向jar包内进行寻找

优先加载带profile的

jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
    jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件

再来加载不带profile的

jar包外部的application.properties或application.yml(不带spring.profile)配置文件
   jar包内部的application.properties或application.yml(不带spring.profile)配置文件

配置文件上进行指定

@Configuration注解类上的@PropertySource

通过SpringApplication.setDefaultProperties指定的默认属性

8.自动配置原理

springboot启动的时候加载了主启动类开启了自动配置功能

@EnableAutoConfiguration的作用是:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

**@EnableAutoConfiguration 里面封装了
**

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {

利用EnableAutoConfigurationImportSelector,先判断是属于什么注解,然后再进行下一步操作

类的全名就作为组件的id.

List configurations = getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置,也就是将候选的组件组件获取内容保存起来。

接着,有一个类SpringFactoriesLoader.loadFactoryNames()

扫描所有jar包类路径下 META‐INF/spring.factories把扫描到的这些文件的内容包装成properties对象从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中

将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;

这时,自定义配置才算真正的加载到容器中

一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取
的,这些类里面的每一个属性又是和配置文件绑定的;

所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功
能对应的这个属性类精髓:

1)、SpringBoot启动会加载大量的自动配置类

2)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类; 3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了) 4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这 些属性的值;

xxxxAutoConfigurartion:自动配置类;
给容器中添加组件

xxxxProperties:封装配置文件中相关属性

细节

1、@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

自动配置类在一定条件下才能生效  我们怎么知道哪些配置类生效

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置
类生效;

0.日志门面: SLF4J;
日志实现:Logback;
SpringBoot:底层是Spring框架,Spring框架默认是用JCL;
SpringBoot选用 SLF4j和logbac

SpringBoot底层也是使用slf4j+logback的方式进行日志记录
     SpringBoot也把其他的日志都替换成了slf4j;
     中间替换包

SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要
     把这个框架依赖的日志框架排除掉。


org.springframework
spring‐core
commons‐logging commons‐logging
</dependency

1.springboot 如何切换日志框架,其实很简单,将原先有的框架的依赖删去,导入新的依赖即可

不过你一定很好奇。怎么知道自己是什么依赖吧,这个不难,两种方法
(1),就是查看你的pom的父项目文件的版本仲裁中心,查看。
(2)在你的idea 项目中可以查看你的依赖树。
日志级别 trace

2、指定配置
给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了
logback.xml:直接就被日志框架识别了;
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能
可以指定某段配置只在某个环境下生效

如:
%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n %d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n
如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误
no applicable action for [springProfile

日志文件产生的位置

1.在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件

logging:
path: /spring/log

2.在控制台输出的日志的格式

logging:
path: /spring/log
pattern:
console: %d{yyyy‐MM‐dd} [%thread] %‐5level %logger{50} ‐ %msg%n

3.指定文件中日志输出的格式

logging:
path: /spring/log
pattern:
console: %d{yyyy‐MM‐dd} [%thread] %‐5level %logger{50} ‐ %msg%n
file: %d{yyyy‐MM‐dd} === [%thread] === %‐5level === %logger{50} ==== %msg%n

下面进行web开发

我们一定要养成一个好习惯就是,遇到底层代码

我们要知道底层配置中,以springboot为例:

1.springbooot帮我们配置了什么

2.能不能修改,能修改哪些配置

3.能不能扩展

xxxxAutoConfiguration:帮我们给容器中自动配置。
xxxxProperties:配置类来封装配置文件的内。

Springboot 对静态资源的映射规则

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

private static final String\[\] CLASSPATH\_RESOURCE\_LOCATIONS = {  
        **"classpath:/META-INF/resources/", "classpath:/resources/",  
        "classpath:/static/", "classpath:/public/" };  

这就是为什么能够自动识别static,public,redources 文件夹了,在这里都已经定义了。**

这个类就厉害了,几乎包含了我们入门水平对配置类怎么来的的所需要的所有方法了。

WebMvcAuotConfiguration:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
if (!registry.hasMappingForPattern("/webjars/**")) {
ResourceHandlerRegistration registration = registry
** .addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/");**
configureResourceCaching(registration);
customizeResourceHandlerRegistration(registration);
}

        String staticPathPattern = this.webFluxProperties.getStaticPathPattern();  
      **静态资源映射文件夹 执行了上面的addResourceHandler()方法。** if (!registry.hasMappingForPattern(staticPathPattern)) {  
            ResourceHandlerRegistration registration = registry  
                    .addResourceHandler(staticPathPattern).addResourceLocations(  
                            this.resourceProperties.getStaticLocations());  
            configureResourceCaching(registration);  
            customizeResourceHandlerRegistration(registration);  
        }  
    }

    private void **configureResourceCaching**(ResourceHandlerRegistration registration) {  
        Duration cachePeriod = this.resourceProperties.getCache().getPeriod();  
        ResourceProperties.Cache.Cachecontrol cacheControl = this.resourceProperties  
                .getCache().getCachecontrol();  
        if (cachePeriod != null && cacheControl.getMaxAge() == null) {  
            cacheControl.setMaxAge(cachePeriod);  
        }  
        registration.setCacheControl(cacheControl.toHttpCacheControl());  
    }

    @Override  
    public void **configureViewResolvers**(ViewResolverRegistry registry) {  
        this.viewResolvers.orderedStream().forEach(registry::viewResolver);  
    }

    @Override  
    public void **addFormatters**(FormatterRegistry registry) {  
        for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {  
            registry.addConverter(converter);  
        }  
        for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {  
            registry.addConverter(converter);  
        }  
        for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {  
            registry.addFormatter(formatter);  
        }  
    }

    private <T> Collection<T> **getBeansOfType**(Class<T> type) {  
        return this.beanFactory.getBeansOfType(type).values();  
    }

    private void customizeResourceHandlerRegistration(  
            ResourceHandlerRegistration registration) {  
        if (this.resourceHandlerRegistrationCustomizer != null) {  
            this.resourceHandlerRegistrationCustomizer.customize(registration);  
        }

    }

下面这个是对欢迎页面的 底层注解

final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {

private static final Log logger = LogFactory.getLog(WelcomePageHandlerMapping.class);

private static final List<MediaType> MEDIA\_TYPES\_ALL = Collections  
        .singletonList(MediaType.ALL);

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,  
        ApplicationContext applicationContext, Optional<Resource> welcomePage,  
        String staticPathPattern) {  
    if (welcomePage.isPresent() && "/\*\*".equals(staticPathPattern)) {  
        logger.info("Adding welcome page: " + welcomePage.get());  
        setRootViewName("forward:index.html");  
    }  
    else if (welcomeTemplateExists(templateAvailabilityProviders,  
            applicationContext)) {  
        logger.info("Adding welcome page template: index");  
        setRootViewName("index");  
    }  
}

private boolean **welcomeTemplateExists**(  
        TemplateAvailabilityProviders templateAvailabilityProviders,  
        ApplicationContext applicationContext) {  
    return templateAvailabilityProviders.getProvider("index",  
            applicationContext) != null;  
}

private void **setRootViewName**(String viewName) {  
    ParameterizableViewController controller = new ParameterizableViewController();  
    controller.setViewName(viewName);  
    setRootHandler(controller);  
    setOrder(2);  
}

@Override  
public Object **getHandlerInternal**(HttpServletRequest request) throws Exception {  
    for (MediaType mediaType : getAcceptedMediaTypes(request)) {  
        if (mediaType.includes(MediaType.TEXT\_HTML)) {  
            return super.getHandlerInternal(request);  
        }  
    }  
    return null;  
}

private List<MediaType> **getAcceptedMediaTypes**(HttpServletRequest request) {  
    String acceptHeader = request.getHeader(HttpHeaders.ACCEPT);  
    if (StringUtils.hasText(acceptHeader)) {  
        return MediaType.parseMediaTypes(acceptHeader);  
    }  
    return MEDIA\_TYPES\_ALL;  
}

下面这个就是我们需要的图标了

public static class **FaviconConfiguration** implements ResourceLoaderAware {

        private final ResourceProperties resourceProperties;

        private ResourceLoader resourceLoader;

        public FaviconConfiguration(ResourceProperties resourceProperties) {  
            this.resourceProperties = resourceProperties;  
        }

        @Override  
        public void setResourceLoader(ResourceLoader resourceLoader) {  
            this.resourceLoader = resourceLoader;  
        }

        @Bean  
        public SimpleUrlHandlerMapping faviconHandlerMapping() {  
            SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();  
            mapping.setOrder(Ordered.HIGHEST\_PRECEDENCE + 1);  
           ** mapping.setUrlMap(Collections.singletonMap(****"\*\*/favicon.ico"****,**  
                    faviconRequestHandler()));  
            return mapping;  
        }

总结:1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;

webjars:以jar包的方式引入静态资源;

在访问的时候只需要写webjars下面资源的名称即可
org.webjars jquery 3.3.1

(2)"/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射

"classpath:/META‐INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径

3)、欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;

4)、所有的 **/favicon.ico 都是在静态资源文件下找;

模板引擎

SpringBoot推荐的Thymeleaf;
语法更简单,功能更强大;

先看一下,Thyemleaf的底层配置文件

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

private static final Charset DEFAULT\_ENCODING = StandardCharsets.UTF\_8;

public static final String DEFAULT\_PREFIX = **"classpath:/templates/";** public static final String DEFAULT\_SUFFIX = **".html";**

可以看到,它的底层其实已经配置好了,初始就是在classpath下,以.html结尾,所以他是不能解析jsp等文件的。

如果我们要使用Thyemleaf 就要使用它的名称空间

Thyemleaf常用的四个标志就是

*{}

#{…}:获取国际化内容

${}

@{…}:定义URL;

:*{…}:选择表达式:和${}在功能上是一样

~{…}:片段引用表达式

下面就来真正的表现吧。

springmvc的自动配置

讲一下简单的吧,

扩展springmvc

编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc;

既保留了所有的自动配置,也能用我们扩展的配置;

这里我们想要重写 webMvcApplication中的方法有下面几种方式

1.继承WebMvcConfigurationSupport

public class MyConfig extends WebMvcConfigurationSupport {
@Bean //将方法的返回值添加到容器中,容器中这个组件默认的id就是方法名
public HelloService configApp(){
System.out.println("@bean给容器中添加了组件,根据方法名这到这个类");
return new HelloService();
}

@Override protected void addViewControllers(ViewControllerRegistry registry) { super.addViewControllers(registry); registry.addViewController("/zhulina").setViewName("index.html"); } }

2.implements WebMvcConfigure

public class MyConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/zhulina").setViewName("index.html");
}
}

那么这俩个类是什么关系呢

WebMvcConfigurationSupport与WebMvcConfigurerAdapter、接口WebMvcConfigurer处于同一个目录下,并且拥有更多的方法与属性(WebMvcConfigurer中有的方法,此类中全都存在)

WebMvcConfigurationSupport应该是新版本中对WebMvcConfigurerAdapter的替换和扩展,如果新版本中应该是推荐使用WebMvcConfigurationSupport类

原理:
1)、WebMvcAutoConfiguration是SpringMVC的自动配置类
2)、在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)

@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//从容器中获取所有的WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
@Override
// public void addViewControllers(ViewControllerRegistry registry) {
// for (WebMvcConfigurer delegate : this.delegates) {
/ delegate.addViewControllers(registry);
// }
}
}
}

容器中所有的WebMvcConfigurer都会一起起作用;
我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;

分析源码可以看出

EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration

DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport

如果说,有一天你觉得自己水平特别高,或者想尝试一下自己配置所有的springmvc的底层代码

可以使用注解@EnableWebMvc  在你的自动配置类中声明

//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/atguigu").setViewName("success");
}
}

原理:为什么配置了@EnableWebMvc就失效了呢

查看@EnableWebMvc的注解就会发现

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc {
}

点开查看就知道

*/
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

@Autowired(required = false)  
public void setConfigurers(List<WebMvcConfigurer> configurers) {  
    if (!CollectionUtils.isEmpty(configurers)) {  
        this.configurers.addWebMvcConfigurers(configurers);  
    }  
}

再查看EnableWebMvcConfig

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)//这个注解就是检查bean是不是存在,只有当容器中没有这个组件的时候这个自动配置类才会生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

而@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;这个时候容器中存在了组件那么,自动配置类就不会再起作用了。

导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;所以,很多功能都要自己写了。

下面一个问题如何修改springboot的默认配置

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如
果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默
认的组合起来;

在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置

在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置

国际化

首先建立一个它就知道了

SpringBoot自动配置好了管理国际化资源文件的组件;

根据浏览器语言设置切换国际化

原理:

国际化(Local)区域信息对象  LocalResolver  获取区域信息对象

@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {
/**
* Comma‐separated list of basenames (essentially a fully‐qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties;
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(this.basename)));
}
if (this.encoding != null) {
messageSource.setDefaultEncoding(this.encoding.name());
}
messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
messageSource.setCacheSeconds(this.cacheSeconds);
messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
return messageSource;
}

源码

@Bean@ConditionalOnMissingBean@ConditionalOnProperty(prefix = "spring.mvc", name = "locale"public LocaleResolver localeResolver() {if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {return new FixedLocaleResolver(this.mvcProperties.getLocale());}AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();localeResolver.setDefaultLocale(this.mvcProperties.getLocale());return localeResolver;}默认的就是根据请求头带来的区域信息获取Locale进行国

源码

进行了一个小例子

第一次遇到的问题是:我使用模板引擎但是html页面中的css js都不能访问,于是我花了很长时间将网上的解决方法都试了一下,但是很可惜全部没有成功

真是替自己感觉到悲哀,浪费了那么长的时间,无奈之下,我就有新建了一个工程将页面copy进去这时就全部能正常执行。

我紧接着就遇到了第二个问题,可能是由于第一个问题太过于烧脑,我第二个工程就忘记了配置文件中加入

# 国际化配置文件(包名.基础名)
spring.messages.basename=i18N.login

fuck ,真是气死我了又浪费了很长时间。

然后就可以正常访问了。

而且更搞笑的是,当我将服务器切换成8081端口就产生乱码,样式也全部乱了,使用默认端口8080 就可以。

我又遇到了问题三

package cn.edu.aynu.springboot.zhaoyujing.Component;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/*解决区域信息*/
public class MyLocalResolver implements LocaleResolver {

@Override  
public Locale resolveLocale(HttpServletRequest request) {  
    String l = request.getParameter("l");  
    Locale locale = Locale.getDefault();  
    if (!StringUtils.isEmpty(l)){  
        String\[\] s = l.split("\_");  
        locale = new Locale(s\[0\], s\[1\]);

    }

    return locale;  
}

@Override  
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

}  

}

自定义的MyLocalResolver

package cn.edu.aynu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/*@EnableWebMvc*/
@Configuration //告诉springboot 这是一个配置类
public class MyConfig extends WebMvcConfigurationSupport {

/*
@Bean //将方法的返回值添加到容器中,容器中这个组件默认的id就是方法名
public HelloService configApp(){
System.out.println("@bean给容器中添加了组件,根据方法名这到这个类");
return new HelloService();
}*/

@Override  
protected void addViewControllers(ViewControllerRegistry registry) {  
    super.addViewControllers(registry);  
    registry.addViewController("/zhulina").setViewName("index.html");  
    registry.addViewController("/").setViewName("index.html");  
}

/* @Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
*//*引入静态资源css js无效时加上*//*
registry.addResourceHandler("/static").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX+"/static/**");

}\*/  

@Bean
public MyLocalResolverre myLocalResolver(){
System.out.println("执行了");
return new MyLocalResolverre();
}

}

MyMvcConfig

但是它居然不执行,重写的resolveLocal 这个方法

开始写拦截器

国际化都不执行了,那么我这个拦截器肯定也不执行了。

待续………………………….

直接下一步了

引入片段的时候

spring.mvc.date-format=yyyy-MM-dd

有时候我们的修改页面和添加页面是相同的,这个时候我们就可以将他俩合二为一




    <div class="container-fluid">  
        <div class="row">  
            <!--引入侧边栏-->  
            <div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>

            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">  
                <!--需要区分是员工修改还是添加;-->  
                <form th:action="@{/emp}" method="post">  
                    <!--发送put请求修改员工数据-->  
                    <!--  
                    1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)  
                    2、页面创建一个post表单  
                    3、创建一个input项,name="\_method";值就是我们指定的请求方式  
                    -->  
                    <input type="hidden" name="\_method" value="put" th:if="${emp!=null}"/>  
                    <input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">  
                    <div class="form-group">  
                        <label>LastName</label>  
                        <input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">  
                    </div>  
                    <div class="form-group">  
                        <label>Email</label>  
                        <input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">  
                    </div>  
                    <div class="form-group">  
                        <label>Gender</label><br/>  
                        <div class="form-check form-check-inline">  
                            <input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">  
                            <label class="form-check-label">男</label>  
                        </div>  
                        <div class="form-check form-check-inline">  
                            <input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">  
                            <label class="form-check-label">女</label>  
                        </div>  
                    </div>  
                    <div class="form-group">  
                        <label>department</label>  
                        <!--提交的是部门的id-->  
                        <select class="form-control" name="department.id">  
                            <option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>  
                        </select>  
                    </div>  
                    <div class="form-group">  
                        <label>Birth</label>  
                        <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">  
                    </div>  
                  **  <button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>**  
                </form>  
            </main>  
        </div>  
    </div>

如果是修改,那么从后台取出的肯定是当前修改的信息然后保存在一个变量中,如果是添加那么就即将这个变量职位空,然后在前台页面中进行判断

变量是否为空,为空就是添加,否则就是修改。

员工删除

                     <tr th:each="emp:${emps}">  
                                <td th:text="${emp.id}"></td>  
                                <td>\[\[${emp.lastName}\]\]</td>  
                                <td th:text="${emp.email}"></td>  
                                <td th:text="${emp.gender}==0?'女':'男'"></td>  
                                <td th:text="${emp.department.departmentName}"></td>  
                                <td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td>  
                                <td>  
                                    <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>  
                                    <button th:attr="del\_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>  
                                </td>  
                            </tr>

springboot默认的错误处理机制

浏览器返回一个默认错误处理的页面

如果我们想自己修改错误页面就需要参考

ErrorMvcAutoConfiguration;错误处理的自动配置;

我们自定义的错误类是如何进行工作的

首先我们可以找到这个方法

BasicErrorController

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

//产生html类型的数据;浏览器发送的请求来到这个方法处理
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪个页面作为错误页面;包含页面地址和页面内容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}

@RequestMapping  
@ResponseBody             **//产生json数据,其他客户端来到这个方法处理** public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {  
    Map<String, Object> body = getErrorAttributes(request,  
            isIncludeStackTrace(request, MediaType.ALL));  
    HttpStatus status = getStatus(request);  
    return new ResponseEntity<Map<String, Object>>(body, status);  
}  
}

ErrorPageCustomizer

@Value("${error.path:/error}")
private String path = "/error"; 系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则

private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {

    private final ServerProperties properties;

    protected ErrorPageCustomizer(ServerProperties properties) {  
        this.properties = properties;  
    }

   **@Override** **public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {  
        ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()  
                + this.properties.getError().getPath());  
        errorPageRegistry.addErrorPages(errorPage);  
    }**

DefaultErrorViewResolver

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map model) {
ModelAndView modelAndView = resolve(String.valueOf(status), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}

private ModelAndView resolve(String viewName, Map<String, Object> model) {  
  **String errorViewName** **\= "error/" +** **viewName;  //默认SpringBoot可以去找到一个页面? error/404  

    TemplateAvailabilityProvider provider** **\= this****.templateAvailabilityProviders  //模板引擎可以解析这个页面地址就用模板引擎解析**  
            .getProvider(errorViewName, this.applicationContext);  
    if (provider != null) {  
        **return new** **ModelAndView(errorViewName, model);  //模板引擎可用的情况下返回到errorViewName指定的视图**  
    }  
    **return** **resolveResource(errorViewName, model);   //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html**  
}

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {  
    for (String location : this.resourceProperties.getStaticLocations()) {  
        try {  
            Resource resource = this.applicationContext.getResource(location);  
            resource = resource.createRelative(viewName + ".html");  
            if (resource.exists()) {  
                return new ModelAndView(new HtmlResourceView(resource), model);  
            }  
        }  
        catch (Exception ex) {  
        }  
    }  
    return null;  
}

步骤:
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error
请求;就会被BasicErrorController处理;

响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;

如何定制错误页面

1)有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹下】,发生此状态码的错误就会来到 对应的页面;

我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);_

页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;_

自定义异常处理返回json数据

当出现错误以后,会来到/Error请求  会被BasicErrorController 这个类进行处理后 响应页面去哪个页面是由DefaultErrorViewResolver解析得到的

将响应的值保存起来然后可以通过调用getErrorAttributes得到的

getErrorAttributes 这个方法是 AbstractErrorController(ErrorController) 这个类规定的方法

public class BasicErrorController extends AbstractErrorController

public abstract class AbstractErrorController implements ErrorController

所以,如果想要自定义错误就可以这样做

1.写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;

2.页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes()默认进行数据处理的

配置嵌入式Servlet容器

package com.atguigu.springboot.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServlet extends HttpServlet {

//处理get请求  
@Override  
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
    doPost(req,resp);  
}

@Override  
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
    resp.getWriter().write("Hello MyServlet");  
}  

}

new MyServlet

package com.atguigu.springboot.filter;

import javax.servlet.*;
import java.io.IOException;

public class MyFilter implements Filter {

@Override  
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override  
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
    System.out.println("MyFilter process...");  
    chain.doFilter(request,response);

}

@Override  
public void destroy() {

}  

}

MuFilter

new MyListener

综合

package com.atguigu.springboot.config;

import com.atguigu.springboot.filter.MyFilter;
import com.atguigu.springboot.listener.MyListener;
import com.atguigu.springboot.servlet.MyServlet;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

@Configuration
public class MyServerConfig {

//注册三大组件  
@Bean  
public ServletRegistrationBean myServlet(){  
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");  
    registrationBean.setLoadOnStartup(1);  
    return registrationBean;  
}

@Bean  
public FilterRegistrationBean myFilter(){  
    FilterRegistrationBean registrationBean = new FilterRegistrationBean();  
    registrationBean.setFilter(new MyFilter());  
    registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));  
    return registrationBean;  
}

@Bean  
public ServletListenerRegistrationBean myListener(){  
    ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());  
    return registrationBean;  
}

//配置嵌入式的Servlet容器  
@Bean  
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){  
    return new EmbeddedServletContainerCustomizer() {

        //定制嵌入式的Servlet容器相关的规则  
        @Override  
        public void customize(ConfigurableEmbeddedServletContainer container) {  
            container.setPort(8083);  
        }  
    };  
}

}

原理:

SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
DispatcherServletAutoConfiguration中:

spring也支持配置其他Servlet容器

默认支持  默认使用(Tomcat)

org.springframework.boot spring‐boot‐starter‐web 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;

jetty(Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的,

它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和web连接。)

(1)Jetty更轻量级。这是相对Tomcat而言的

(2)Jetty更灵活,体现在其可插拔性和可扩展性,更易于开发者对Jetty本身进行二次开发,定制一个适合自身需求的Web Server。

然而,当支持大规模企业级应用时,Jetty也许便需要扩展,在这场景下Tomcat便是更优的。

总结:Jetty更满足公有云的分布式环境的需求,而Tomcat更符合企业级环境。


org.springframework.boot spring‐boot‐starter‐web spring‐boot‐starter‐tomcat org.springframework.boot

spring‐boot‐starter‐jetty org.springframework.boot

Undertow

  • Undertow 是红帽公司开发的一款基于 NIO 的高性能 Web 嵌入式服务器
  • 轻量级:它是一个 Web 服务器,但不像传统的 Web 服务器有容器概念,它由两个核心 Jar 包组成,加载一个 Web 应用可以小于 10MB 内存
  • Servlet3.1 支持:它提供了对 Servlet3.1 的支持
  • * WebSocket 支持:对 Web Socket 完全支持,用以满足 Web 应用巨大数量的客户端
    • 嵌套性:它不需要容器,只需通过 API 即可快速搭建 Web 服务器
    • 默认情况下 Spring Cloud 使用 Tomcat 作为内嵌 Servlet 容器,可启动一个 Tomcat 的 Spring Boot 程序与一个 Undertow 的 Spring Boot 程序,通过 VisualVM 工具进行比较,可看到 Undertow 性能优于 Tomcat


org.springframework.boot spring‐boot‐starter‐web spring‐boot‐starter‐tomcat org.springframework.boot


     spring‐boot‐starter‐undertow      org.springframework.boot

嵌入式Servlet容器自动配置原理

EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置

嵌入式Servlet 容器启动原理

1.springboot根据依赖导入的情况给容器中添加相应的EmbeddedServletContainerFactory
也即是 TomcatEmbeddedServletContineryFactory.

2.容器中某个组件要创建对象就会惊动后置处理器
EmbeddedServletContainerCustomizerBeanPostProcessor;

只要是嵌入式的Servlet容器工厂后置处理器就工作

3.后置处理器从容器中获取所有的EmbeddedServletContainerCustomizer 调用定制器的定制方法
嵌入式Servlet容器启动原理

首先获取嵌入式的Servlet容器工厂

springboot启动后运行 run方法,调用 refreshContext(context) 方法 springboot 刷新IOC容器 [ 创建Ioc容器对象,并初始化容器,创建容器中的每一个组件];如果是web应用创建

AnnotationConfigEmbeddedWebApplicationContext  .  如果不是web应用创建 AnnoctionConfigApplicationContext .

refreshContext(context)  这个方法的作用就是刷新Ioc刚才创建好的IOC容器

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post‐processing of the bean factory in context subclasses.
onRefresh(); web的ioc容器重写了onRefresh方法
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non‐lazy‐init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization ‐ " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore…
resetCommonCaches();
}
}
}

onRefresh(); web的ioc容器重写了onRefresh方法

web   ioc 容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();

然后通过下面的方法获取嵌入式的容器工厂

EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建
对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;

使用容器工厂获取嵌入式的Servlet容器

this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
嵌入式的Servlet容器创建对象并启动Servlet容器

先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来

IOC容器启动创建嵌入式的Servlet容器

使用外置的Servlet容器

嵌入式的Servlet容器打包成可执行的jar包  优点:简单,便捷

缺点:默认不支持jsp     优化定制比较复杂 (使用定制器ServerProperties)

自定义EmbeddedServletContionerCustomizer,  自己编写嵌入式Servlet容器的创建工厂

EmbeddedServletContioneryFactory

外置的Servlet容器:外面安装Tomcat--应用war包的方式打包

步骤:

1.必须创建好一个war项目(利用idea创建好目录结构 2)将嵌入式的Tomcat指定为Provider

对于scope=compile的情况(默认scope),也就是说这个项目在编译,测试,运行阶段都需要这个artifact对应的jar包在classpath中。而对于scope=provided的情况,

则可以认为这个provided是目标容器已经provide这个artifact。

这么说可能不是很好理解:换个简单的,就是你在把这个项目打成war包时,scope=provided的jar包,不会出现在WEB-INFO/lib目录下,而scope=compile的jar包,会放到WEB-INFO/lib目录

这样可以避免像servlet-api.jar这样的jar包冲突,但是如果遇到idea下默认不提供servlet-api.jar时,使用scope=provided就会缺少对应的jar包.

org.springframework.boot spring‐boot‐starter‐tomcat provided

必须编写一个SpringBootServletInitializer的子类,并调用configure方法

package cn.edu.aynu.defineconfig;

import cn.edu.aynu.WebProjectApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

/**
* Description: 02-web-project
* Created by lenovo on 2019/5/1 18:30
*/
public class servletInitializer extends SpringBootServletInitializer {

@Override  
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {

    /\*传入Springboot应用的主程序\*/  
    return builder.sources(WebProjectApplication.class);

}  

}

MyConfig

启动服务器即可
原理:

jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容

war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;

规则:

1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:

2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为

javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名

3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

流程:

1)、启动Tomcat

2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\METAINF\services\javax.servlet.ServletContainerInitializer:

Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer

3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型

的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例;

SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}

    builder.initializers(new ApplicationContextInitializer\[\]{new ServletContextApplicationContextInitializer(servletContext)});  
    builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);  
    builder = this.configure(builder);  
    builder.listeners(new ApplicationListener\[\]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});  
    SpringApplication application = builder.build();  
    if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {  
        application.addPrimarySources(Collections.singleton(this.getClass()));  
    }

    Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");  
    if (this.registerErrorPageFilter) {  
        application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));  
    }

    return this.run(application);  
}

createRootApplicationContext

//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来

//使用builder创建一个Srin

//启动Sring

public ConfigurableApplicationContext run(String… args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();

    Collection exceptionReporters;  
    try {  
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);  
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);  
        this.configureIgnoreBeanInfo(environment);  
        Banner printedBanner = this.printBanner(environment);  
        context = this.createApplicationContext();  
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class\[\]{ConfigurableApplicationContext.class}, context);  
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);  
        this.refreshContext(context);  
        this.afterRefresh(context, applicationArguments);  
        stopWatch.stop();  
        if (this.logStartupInfo) {  
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);  
        }

        listeners.started(context);  
        this.callRunners(context, applicationArguments);  
    } catch (Throwable var10) {  
        this.handleRunFailure(context, var10, exceptionReporters, listeners);  
        throw new IllegalStateException(var10);  
    }

    try {  
        listeners.running(context);  
        return context;  
    } catch (Throwable var9) {  
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);  
        throw new IllegalStateException(var9);  
    }  
}

run

启动Servlet容器,再启动SpringBoot

------------------------------------------------------------------

自定义启动器

@Configuration //指定这个类是一个配置类
@ConditionalOnXXX //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter//指定自动配置类的顺序
@Bean //给容器中添加组件
@ConfigurationPropertie //结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties//让xxxProperties生效加入到容器中

先放上我的代码吧  自定义的场景启动类

package cn.edu.aynu.config;

import cn.edu.aynu.properties.SomeServiveProperties;
import cn.edu.aynu.service.SomeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Description: 01-project-myweb
* Created by lenovo on 2019/5/1 9:00
*/
@Configuration //指定这个类是配置类
@ConditionalOnClass(SomeService.class) //判断这个类是不是存在,如果存在的情况下,自动配置类生效
@EnableConfigurationProperties(SomeServiveProperties.class)//让SomeServiveProperties生效加入到容器中,这个时候它里面已经保存了从配置文件读取到的值。
//通过自动注入就能取到这个对象里面的值

public class SomeServiceConfig {
@Autowired
private SomeServiveProperties sp;

/\*让我们的自定义方法生效将这个方法加入到容器中\*/  
@Bean  
public SomeService someService(){

    return new SomeService(sp.getBefore(),sp.getAfter());  
}

}

SomeServiceConfig

package cn.edu.aynu.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* Description: 01-project-myweb
* Created by lenovo on 2019/5/1 8:32
*/
@Data
@ConfigurationProperties("some.service")
public class SomeServiveProperties {

private String before;  
private String after;

}

SomeServiveProperties

package cn.edu.aynu.service;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
* Description: 01-project-myweb
* Created by lenovo on 2019/5/1 8:21
*/
@Data
@NoArgsConstructor
@ToString
public class SomeService {
private String before;
private String after;

public SomeService(String before, String after) {  
    this.before = before;  
    this.after = after;  
}

/\*核心方法,会调用这个方法\*/  
public String wrap(String word){

    return before+word+after;  
}  

}

SomeService

org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.edu.aynu.config.SomeServiceConfig

spring.factories

然后再自定义一个工程用于检测我们的场景启动器是否成功的建立

wrapproject wrap-spring-boot-starter 0.0.1-SNAPSHOT

pom

package cn.edu.aynu.controller;

import cn.edu.aynu.service.SomeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* Description: 02-web-project
* Created by lenovo on 2019/5/1 16:04
*/
@RestController
public class Handler {

@Autowired  
SomeService someService;

@RequestMapping("/myquest")  
public String handlerQequest(){

 return someService.wrap("hello");

}

}

controller

some.service.before=hello
some.service.after=bye

pom

遇到的问题

1.把以war 打包的方式改成以jar的方式打包

如果是maven文件只需要改pom文件就可以了

pom文件上面也是需要改的

wrap-project
wrap-spring-boot-stater
1.0-SNAPSHOT
jar

maven-jar-plugin 3.1.0

如果还不行那你试试

把它改了,应该就行了吧,我的是可以的不同的电脑同样的错误改法可能不一样啊。

当我打包测试类的时候出现错误

-------------------------------------------------------------------------------

Test set: cn.edu.aynu.WebprojectApplicationTests

Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.806 s <<< FAILURE! - in cn.edu.aynu.WebprojectApplicationTests
initializationError(cn.edu.aynu.WebprojectApplicationTests) Time elapsed: 0.01 s <<< ERROR!
java.lang.IllegalStateException: Found multiple @SpringBootConfiguration annotated classes [Generic bean: class [cn.edu.aynu.WebprojectApplication]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [C:\Users\lenovo\Desktop\190429\01-project-myweb\web-project\target\classes\cn\edu\aynu\WebprojectApplication.class], Generic bean: class [cn.edu.aynu.WrapSpringBootStaterApplication]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in URL [jar:file:/D:/Maven/Repositories/wrap-project/wrap-spring-boot-stater/1.0-SNAPSHOT/wrap-spring-boot-stater-1.0-SNAPSHOT.jar!/cn/edu/aynu/WrapSpringBootStaterApplication.class]]

这个问题的解决方案

org.springframework.boot spring-boot-configuration-processor true

因为我的启动类不是springboot快速创建,而是一个一般的maven工程,所以出现了这个错误。然后我的最简单的办法就是创建两个快速创建的springboot项目

然后要勾选上

当代码写完了之后要进行 install 安装到仓库中。到此,其他用户就可以通过引入该Starter依赖,

然后从容器中获取我们自定义组件实现该业务。

然后呢我们需要的业务可不是就这么简单

@ConditionalOnProperty来控制Configuration是否生效

package cn.edu.aynu.config;

import cn.edu.aynu.properties.SomeServiveProperties;
import cn.edu.aynu.service.SomeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Description: 01-project-myweb
* Created by lenovo on 2019/5/1 9:00
*/
@Configuration //指定这个类是配置类
@ConditionalOnClass(SomeService.class) //判断这个类是不是存在,如果存在的情况下,自动配置类生效
@EnableConfigurationProperties(SomeServiveProperties.class)//让SomeServiveProperties生效加入到容器中,这个时候它里面已经保存了从配置文件读取到的值。
//通过自动注入就能取到这个对象里面的值
//@ConditionalOnProperty(prefix = "some.service",name = "enabled",havingValue = "true")
public class SomeServiceConfig {
@Autowired
private SomeServiveProperties sp;

/\*让我们的自定义方法生效将这个方法加入到容器中\*/  
@Bean  
@ConditionalOnMissingBean  
@ConditionalOnProperty(prefix = "some.service",name = "enabled",havingValue = "true")  
public SomeService someService(){

    return new SomeService(sp.getBefore(),sp.getAfter());  
}  
@Bean  
@ConditionalOnMissingBean  
@ConditionalOnProperty(prefix = "some.service",name = "enabled",havingValue = "false")  
public SomeService someService1(){

    return new SomeService(sp.getBefore(),sp.getAfter());  
}

}

SomeServiceConfig

查看源码可以发现

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

String\[\] value() default {}; //数组,获取对应property名称的值,与name不可同时使用  

String prefix() default "";//property名称的前缀,可有可无  

String\[\] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用  

String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置  

boolean matchIfMissing() default false;//缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错  

boolean relaxedNames() default true;//是否可以松散匹配,至今不知道怎么使用的  

}
}

通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。
如果该值为空,则返回false;
如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
如果返回值为false,则该configuration不生效;为true则生效。

该注解还有个属性: matchIfMissing=true (默认为false)  意思就是说当没配置framework.web.filter.enabled也会使其生效

prefix是   application.properties配置的前缀

name 属性是从application.properties配置文件中读取属性值

matchIfMissing = true表示如果没有在application.properties设置该属性,则默认为条件符合

解释几个注解

@Bean默认情况下bean的名称和方法名称相同,首字母小写,你也可以使用name属性来指定

@Configuration
public class AppConfig {

@Bean  
public TransferService transferService() {  
    return new TransferServiceImpl();  
}

}

bean的基本定义

这个配置就等同于之前在xml里的配置

xml

@bean 也可以依赖其他任意数量的bean,如果TransferService 依赖 AccountRepository,我们可以通过方法参数实现这个依赖

@Configuration
public class AppConfig {

@Bean  
public TransferService transferService(AccountRepository accountRepository) {  
    return new TransferServiceImpl(accountRepository);  
}

}

bean依赖

任何使用@Bean定义的bean,也可以执行生命周期的回调函数,类似@PostConstruct and @PreDestroy的方法。用法如下

public class Foo {
public void init() {
// initialization logic
}
}

public class Bar {
public void cleanup() {
// destruction logic
}
}

@Configuration
public class AppConfig {

@Bean(initMethod = "init")  
public Foo foo() {  
    return new Foo();  
}

@Bean(destroyMethod = "cleanup")  
public Bar bar() {  
    return new Bar();  
}

}

bean 回调

默认使用javaConfig配置的bean,如果存在close或者shutdown方法,则在bean销毁时会自动执行该方法,如果你不想执行该方法,则添加@Bean(destroyMethod="")来防止出发销毁方法

@Bean 还支持注解

@Configuration
public class AppConfig {

@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })  
public DataSource dataSource() {  
    // instantiate, configure and return DataSource bean...  
}

}

bean别名

使用@Scope注解

你能够使用@Scope注解来指定使用@Bean定义的bean

@Configuration
public class MyConfiguration {

@Bean  
@Scope("prototype")  
public Encryptor encryptor() {  
    // ...  
}

}

bean 的scope

有时候提供bean的详细信息也是很有用的,bean的描述可以使用 @Description来提供

@Configuration
public class AppConfig {

@Bean  
@Description("Provides a basic example of a bean")  
public Foo foo() {  
    return new Foo();  
}

}

bean的描述

注解二

**(https://blog.csdn.net/qq_36567005/article/details/80611139)**

@Qualifier

在controller中需要注入service 那么我的这个service 有两个实现类如何区分

package cn.edu.aynu.defineconfig;

import cn.edu.aynu.WebProjectApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

/**
* Description: 02-web-project
* Created by lenovo on 2019/5/1 18:30
*/
public class servletInitializer extends SpringBootServletInitializer {

@Override  

protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {

    /\*传入Springboot应用的主程序\*/  

return builder.sources(WebProjectApplication.class);

}
}

Docker简介

简介:Docker是一个开源的应用容器引擎;是一个轻量级容器技术;

Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使
用这个镜像;
运行中的这个镜像称为容器,容器启动是非常快速的。

核心概念

docker主机(Host):安装了Docker程序的机器(Docker直接安装在操作系统之上);
docker客户端(Client):连接docker主机进行操作;
docker仓库(Registry):用来保存各种打包好的软件镜像;
docker镜像(Images):软件打包好的镜像;放在docker仓库中;
docker容器(Container):镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用

使用Docker的步骤:
1)、安装Docker
2)、去Docker仓库找到这个软件对应的镜像;
3)、使用Docker运行这个镜像,这个镜像就会生成一个Docker容器;
4)、对容器的启动停止就是对软件的启动停止;

1)、安装linux虚拟机1)、VMWare、VirtualBox(安装);

2)、导入虚拟机文件centos7-atguigu.ova

3)、双击启动linux虚拟机;使用 root/ 123456登陆

4)、使用客户端连接linux服务器进行命令操作;

5)、设置虚拟机网络;桥接网络=选好网卡==接入网线;

6)、设置好网络以后使用命令重启虚拟机的

service network restart

查看linux的ip地址

ip addr

使用客户端连接linux;

在linux虚拟机上安装docker

1、检查内核版本,必须是3.10及以上
uname ‐r
2、安装docker
yum install docker
3、输入y确认安装
4、启动docker
[root@localhost ~]# systemctl start docker
[root@localhost ~]# docker ‐v
Docker version 1.12.6, build 3e8e77d/1.12.6
5、开机启动docker
[root@localhost ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi‐user.target.wants/docker.service to
/usr/lib/systemd/system/docker.service.
6、停止docker
systemctl stop docker

Docker常用命令&操作

https://hub.docker.co

软件镜像(QQ安装程序)----运行镜像----产生一个容器(正在运行的软件,运行的QQ);

1、搜索镜像
[root@localhost ~]# docker search tomcat
2、拉取镜像
[root@localhost ~]# docker pull tomcat
3、根据镜像启动容器
docker run ‐‐name mytomcat ‐d tomcat:latest
4、docker ps
查看运行中的容器
5、 停止运行中的容器
docker stop 容器的id
6、查看所有的容器
docker ps ‐a
7、启动容器
docker start 容器id
8、删除一个容器
docker rm 容器id
9、启动一个做了端口映射的tomcat
[root@localhost ~]# docker run ‐d ‐p 8888:8080 tomcat
‐d:后台运行
‐p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口
10、为了演示简单关闭了linux的防火墙
service firewalld status ;查看防火墙状态
service firewalld stop:关闭防火墙
11、查看容器的日志
docker logs container‐name/container‐id
更多命令参看
https://docs.docker.com/engine/reference/commandline/docker/
可以参考每一个镜像的文档

安装MySQL示例

docker pull mysql

SpringBoot与数据访问

JDBC

org.springframework.boot spring‐boot‐starter‐jdbc
mysql mysql‐connector‐java runtime

spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.15.22:3306/jdbc
driver‐class‐name: com.mysql.jdbc.Driver

效果:
默认是用org.apache.tomcat.jdbc.pool.DataSource作为数据源;
数据源的相关配置都在DataSourceProperties里面;

public interface DataSource extends CommonDataSource, Wrapper {

Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;

abstract class DataSourceConfiguration {

@SuppressWarnings("unchecked")  
protected static <T> T createDataSource(DataSourceProperties properties,  
        Class<? extends DataSource> type) {  
    return (T) properties.initializeDataSourceBuilder().type(type).build();  
}

/\*\*  
 \* Tomcat Pool DataSource configuration.  
 \*/  
@Configuration  
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)  
@ConditionalOnMissingBean(DataSource.class)  
@ConditionalOnProperty(name = "spring.datasource.type",  
        havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true)  
static class Tomcat {

    @Bean  
    @ConfigurationProperties(prefix = "spring.datasource.tomcat")  
    public org.apache.tomcat.jdbc.pool.DataSource dataSource(  
            DataSourceProperties properties) {  
        org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(  
                properties, org.apache.tomcat.jdbc.pool.DataSource.class);  
        DatabaseDriver databaseDriver = DatabaseDriver  
                .fromJdbcUrl(properties.determineUrl());  
        String validationQuery = databaseDriver.getValidationQuery();  
        if (validationQuery != null) {  
            dataSource.setTestOnBorrow(true);  
            dataSource.setValidationQuery(validationQuery);  
        }  
        return dataSource;  
    }

}

/\*\*  
 \* Hikari DataSource configuration.  
 \*/  
@Configuration  
@ConditionalOnClass(HikariDataSource.class)  
@ConditionalOnMissingBean(DataSource.class)  
@ConditionalOnProperty(name = "spring.datasource.type",  
        havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)  
static class Hikari {

    @Bean  
    @ConfigurationProperties(prefix = "spring.datasource.hikari")  
    public HikariDataSource dataSource(DataSourceProperties properties) {  
        HikariDataSource dataSource = createDataSource(properties,  
                HikariDataSource.class);  
        if (StringUtils.hasText(properties.getName())) {  
            dataSource.setPoolName(properties.getName());  
        }  
        return dataSource;  
    }

}

/\*\*  
 \* DBCP DataSource configuration.  
 \*/  
@Configuration  
@ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)  
@ConditionalOnMissingBean(DataSource.class)  
@ConditionalOnProperty(name = "spring.datasource.type",  
        havingValue = "org.apache.commons.dbcp2.BasicDataSource",  
        matchIfMissing = true)  
static class Dbcp2 {

    @Bean  
    @ConfigurationProperties(prefix = "spring.datasource.dbcp2")  
    public org.apache.commons.dbcp2.BasicDataSource dataSource(  
            DataSourceProperties properties) {  
        return createDataSource(properties,  
                org.apache.commons.dbcp2.BasicDataSource.class);  
    }

}

/\*\*  
 \* Generic DataSource configuration.  
 \*/  
@Configuration  
@ConditionalOnMissingBean(DataSource.class)  
@ConditionalOnProperty(name = "spring.datasource.type")  
static class Generic {

    @Bean  
    public DataSource dataSource(DataSourceProperties properties) {  
        return properties.initializeDataSourceBuilder().build();  
    }

}

自动配置原理:
org.springframework.boot.autoconfigure.jdbc:
1、参考DataSourceConfiguration,根据配置创建数据源,默认使用Tomcat连接池;可以使用
spring.datasource.type指定自定义的数据源类型;

2、SpringBoot默认可以支持;

org.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource、

自定义数据源类型

/**
* Generic DataSource configuration.
*/
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
@Bean
public DataSource dataSource(DataSourceProperties properties) {
//使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性
return properties.initializeDataSourceBuilder().build();
}
}

DataSourceInitializer:ApplicationListener;

作用:
1)、runSchemaScripts();运行建表语句;
2)、runDataScripts();运行插入数据的sql语句;

默认只需要将文件命名为:

schema‐*.sql、data‐*.sql
默认规则:schema.sql,schema‐all.sql;
可以使用
schema:
‐ classpath:department.sql
指定位置

5、操作数据库:自动配置了JdbcTemplate操作数据库

spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
schema:
- classpath:sql/department.sql
- classpath:sql/employee.sql

pom

根据上面的配置,可以知道扫描 resources下面的.sql文件,然后加载这个sql文件,在数据库中进行创建

如何整合Druid数据源

首先肯定是一个配置文件,当配置好了druid数据源之后,根据.sql文件也会在数据中创建表,

导入druid数据源
@Configuration
public class DruidConfig {

@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid(){

return new DruidDataSource();
}
//配置Druid的监控
//1、配置一个管理后台的Servlet
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
Map initParams = new HashMap<>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow","");//默认就是允许所有访问
initParams.put("deny","192.168.15.21");
bean.setInitParameters(initParams);

return bean;
}
//2、配置一个web监控的filter,这个在配置类中都可以进行配置,只需要把他加载进容器中。
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}

整合Mybaties

org.mybatis.spring.boot mybatis‐spring‐boot‐starter 1.3.1

注解版

//指定这是一个操作数据库的mapper
@Mapperpublic interface DepartmentMapper {
@Select("select * from department where id=#{id}")
public Department getDeptById(Integer id);
@Delete("delete from department where id=#{id}")
public int deleteDeptById(Integer id);
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into department(departmentName) values(#{departmentName})")
public int insertDept(Department department);
@Update("update department set departmentName=#{departmentName} where id=#{id}")
public int updateDept(Department department);
}

问题:
自定义MyBatis的配置规则;给容器中添加一个ConfigurationCustomizer;

@org.springframework.context.annotation.Configuration
public class MyBatisConfig {
@Bean
public ConfigurationCustomizer configurationCustomizer(){
return new ConfigurationCustomizer(){
@Override
public void customize(Configuration configuration) {
//设置驼峰命名法
configuration.setMapUnderscoreToCamelCase(true);
}
};
}
}

使用MapperScan批量扫描所有的Mapper接口;
@MapperScan(value = "com.atguigu.springboot.mapper")
@SpringBootApplication
public class SpringBoot06DataMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBoot06DataMybatisApplication.class, args);
}
}

下面一个就是配置文件版

像spring 写sql文件一样声明配置文件,还有映射文件、




<insert id="insertEmp">  
    INSERT INTO employee(lastName,email,gender,d\_id) VALUES (#{lastName},#{email},#{gender},#{dId})  
</insert>

<select id="getAll" resultType="cn.edu.aynu.springboot.zhaoyujing.entities.Employee">  
     SELECT \* FROM employee  
</select>  
<delete id="delete">  
    delete from employee WHERE id=#{id}  
</delete>  

EmployeeMapper.xml





Mybatis-config.xml

整合srpingData    JPA

1.写一个实体类

//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是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;

2)、编写一个Dao接口来操作实体类对应的数据表(Repository)

_也就是你可以自定义sql语句然后直接就可以被使用了,但是定义的方法名要符合一些规则
具体信息在下面这个网址中(https://www.cnblogs.com/zhulina-917/p/10504377.html)_

//继承JpaRepository来完成对数据库的操作
public interface UserRepository extends JpaRepository {
}

基本的配置JpaProperties

spring:
jpa:
hibernate:

更新或者创建数据表结构

ddl‐auto: update

控制台显示SQL

show‐sql: true

讲解springboot的启动配置原理

几个重要的事件回调机制

配置在META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener

只需要放在ioc容器中
ApplicationRunner
CommandLineRunner

启动流程

initialize(sources);
private void initialize(Object[] sources) {
//保存主配置类
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//判断当前是否一个web应用
this.webEnvironment = deduceWebEnvironment();
//从类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然后保存来
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从类路径下找到ETA‐INF/spring.factories配置的所有ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有main方法的主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}

运行run方法

public ConfigurableApplicationContext run(String… args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null;

__configureHeadlessProperty();
    //获取SpringApplicationRunListeners;从类路径下META‐INF/spring.factories
      SpringApplicationRunListeners listeners = getRunListeners(args);
     //回调所有的获取SpringApplicationRunListener.starting()方法
      listeners.starting();
       try {
       //封装命令行参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

      //准备环境
       ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

     //创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成

      Banner printedBanner = printBanner(environment);
      //创建ApplicationContext;决定创建web的ioc还是普通的ioc
        context = createApplicationContext();
       analyzers = new FailureAnalyzers(context);
       //准备上下文环境;将environment保存到ioc中;而且applyInitializers();
        //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
       //回调所有的SpringApplicationRunListener的contextPrepared();

        prepareContext(context, environment, listeners, applicationArguments,printedBanner);

           //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
         //s刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版
        //扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
       refreshContext(context);
      / /从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
      //ApplicationRunner先回调,CommandLineRunner再回调
       afterRefresh(context, applicationArguments);
       //所有的SpringApplicationRunListener回调finished方法
       listeners.finished(context, null);
      stopWatch.stop();
       if (this.logStartupInfo) {
       new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);

}
       / /整个SpringBoot应用启动完成以后返回启动的ioc容器;
         return context;
}
      catch (Throwable ex) {
       handleRunFailure(context, listeners, analyzers, ex);
       throw new IllegalStateException(ex);
}
}__

事件的监听机制

配置在META-INF/spring.factories
ApplicationContextInitializer

public class HelloApplicationContextInitializer implements
ApplicationContextInitializer {

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer…initialize…"+applicationContext);
}
}

SpringApplicationRunListener

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) {
Object o = environment.getSystemProperties().get("os.name");
System.out.println("SpringApplicationRunListener…environmentPrepared.."+o);
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener…contextPrepared…");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener…contextLoaded…");
}
@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("SpringApplicationRunListener…finished…");
}
}

配置(META-INF/spring.factories)

org.springframework.context.ApplicationContextInitializer=\
com.atguigu.springboot.listener.HelloApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\
com.atguigu.springboot.listener.HelloSpringApplicationRunListener