SpringBoot学习文档

310 阅读49分钟

SpringBoot

 创建独立Spring应用
 内嵌web服务器
 自动starter依赖,简化构建配置
 自动配置Spring以及第三方功能
 提供生产级别的监控、健康检查及外部化配置
 无代码生成、无需编写XML

SpringBoot特点

依赖管理

依赖管理    
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
</parent>

他的父项目
 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.4.RELEASE</version>
  </parent>

几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制

开发导入starter场景启动器

1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>

无需关注版本号,自动版本仲裁

1、引入依赖默认都可以不写版本

2、引入非版本仲裁的jar,要写版本号。

自动配置

自动配好Tomcat

  • 引入Tomcat依赖。
  • 配置Tomcat
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>

自动配好SpringMVC

  • 引入SpringMVC全套组件
  • 自动配好SpringMVC常用组件(功能)

自动配好Web常见功能

如:字符编码问题

  • SpringBoot帮我们配置好了所有web开发的常见场景

默认的包结构

  • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
  • 无需以前的包扫描配置
  • 想要改变扫描路径,@SpringBootApplication(scanBasePackages="com.atguigu") 或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")

各种配置拥有默认值

  • 默认配置最终都是映射到某个类上,如:MultipartProperties
  • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

按需加载所有自动配置项

  • 非常多的starter
  • 引入了哪些场景这个场景的自动配置才会开启
  • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

主程序类@SpringBootApplication

主程序类 @SpringBootApplication:注明这是一个SpringBoot应用,默认扫描组件的范围是主程序所在的包下

想要改变扫描路径,@SpringBootApplication(scanBasePackages="com.atguigu") 或者@ComponentScan 指定扫描路径

@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan()使用该组合注解就可以通过@ComponentScan()的value的值来设置扫描的范围

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        //获取IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class);
  }
 }
/**
 * Created by KingsLanding on 2022/8/12 15:17
 *
 *  主程序类
 *  @SpringBootApplication:注明这是一个SpringBoot应用,默认扫描组件的范围是主程序所在的包下
 *
 *  @SpringBootConfiguration
 *  @EnableAutoConfiguration
 *  @ComponentScan()使用该组合注解就可以通过@ComponentScan()的value的值来设置扫描的范围
 */
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        //获取IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class);
        //插卡案容器组件
        String[] beanDefinitionNames = run.getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames){
            System.out.println(beanName);
        }

        //如果@Configuration(proxyBeanMethods = true)配置类不是一个普通的类,
        // 而是一个增强的代理类com.springboot.config.MyConfig$$EnhancerBySpringCGLIB$$1d514a48@5de6cf3a
        //如果@Configuration(proxyBeanMethods = false)
        // com.springboot.config.MyConfig@4b97c4ad
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        //测试是否是单例
        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中存在。
        // 保持组件单实例
        Object user01 = run.getBean("user01");
        Object user02 = run.getBean("user01");

        System.out.println(user01 == user02);//true //false

        //验证获取通过@Import创建的组件
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        for (String s : beanNamesForType) {
            System.out.println(s);
            //com.springboot.pojo.User
            //user01
        }
        System.out.println(run.getBean(DBHelper.class));
        //ch.qos.logback.core.db.DBHelper@32639b12

        System.out.println(run.containsBean("car"));
        System.out.println(run.containsBean("test"));

    }
}

配置类@Configuration

  • 1.配置类里面使用@Bean标注在方法上给容器注册组件,默认是单例的

  • 2.配置类本身也是组件

    • 如果@Configuration(proxyBeanMethods = true)配置类不是一个普通的类, 而是一个增强的代理类com.springboot.config.MyConfigEnhancerBySpringCGLIBEnhancerBySpringCGLIB1d514a48@5de6cf3a
      • 如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中存在。

    • 如果@Configuration(proxyBeanMethods = false)那么该配置类就是一个普通的类com.springboot.config.MyConfig@4b97c4ad
  • 3.proxyBeanMethods:代理bean的方法

    • Full(proxyBeanMethods = true) -- 保证每个@Bean方法被调用多少次返回的组件都是单例的
    • Lite(proxyBeanMethods = false) -- 每个@Bean方法被调用多少次返回的组件都是新创建的
    • 组件之间存在依赖关系的必须使用Full模式,其他默认为lite模式;因为true情况下每次都要查找ioc中是否有该实例,而false不需要,直接new一个新的实例就行

@Bean()

向ioc容器中添加组件,以方法名为组件的id,返回类型就是组件类型返回的值就是组件在容器中的实例,其中@Bean中的value值可设置组件id,默认id就是方法名

@Bean("user01") 
//向ioc容器中添加组件,以方法名为组件的id,返回类型就是组件类型返回的值就是组件在容器中的实例,其中@Bean中的value值可设置组件id,默认id就是方法名

public User user(){
    return new User("zmj",22);
}

@Bean(name = "car")
public Car car(){

    return new Car();
}

@Import

@Import({User.class, DBHelper.class}) 给容器中自动创建出这两个类的组件,默认组件的名字就是全类名

@Import(AutoConfigurationPackages.Registrar.class) 给容器中导入一个组件

public @interface AutoConfigurationPackage {}

利用Registrar给容器中导入一系列组件

将指定的一个包下的所有组件导入进来?MainApplication 所在包下。

@AutoConfigurationPackage

自动配置包?指定了默认的包规则

@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。

@EnableConfigurationProperties

1.开启配置绑定功能 2.value值设置组件并把这个组件自动注册到容器中

@ConfigurationProperties

设置组件与配置文件(properties or yml)的关联

@ConfigurationProperties(prefix = "person")
public class User {
    private String name;
    private Integer age;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

YAML类型的配置文件

YAML

YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义

yaml文件语法规则

user00:
  name: zmj
  age: 22

properties文件语法规则

person.age=22
person.name=zmj

yaml文件提示

如果要向在这种文件中编写自己创建的文件的代码时,要想有提示必须要有

依赖

<!--自定义的类和配置文件绑定提示-->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-configuration-processor</artifactId>
     <optional>true</optional>
</dependency>
<!--打包时忽略该依赖-->
<configuration>
    <excludes>
        <exclude>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </exclude>
    </excludes>
</configuration>

@ImportResource原生配置文件引入

======================testBeanXML.xml=========================
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.atguigu.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>

@ImportResource("classpath:testBeanXML.xml")

@ConditionalOnBean条件装配

条件装配:满足Conditional指定的条件,则进行组件注入

类似的条件装配还有很多

尤其在springboot自动装入容器的组件中有很多这种条件,在满足条件是才会装配

虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration

按照条件装配规则(@Conditional),最终会按需配置。

/**
 * Created by KingsLanding on 2022/8/12 21:17
 *
 * 1.配置类里面使用@Bean标注在方法上给容器注册组件,默认是单例的
 * 2.配置类本身也是组件
 * 3.proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true) -- 保证每个@Bean方法被调用多少次返回的组件都是单例的
 *      Full(proxyBeanMethods = false) -- 每个@Bean方法被调用多少次返回的组件都是新创建的
 *      组件有依赖的或必须使用Full模式,其他默认为lite模式;因为true情况下每次都要查找ioc中是否有该实例,
 *      而false不需要,直接new一个新的实例就行
 *
 *  4.@Import({User.class, DBHelper.class})
 *      给容器中自动创建出这两个类的组件,默认组件的名字就是全类名
 *
 *
 */
//@ImportResource("classpath:testBeanXML.xml")
//@ConditionalOnBean(name = "car")//条件装配:满足Conditional指定的条件,则进行组件注入
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false)//表明SpringBoot这是一个配置类 == 配置文件

@EnableConfigurationProperties(Car.class)//1.开启Car配置绑定功能
                                        //2.把这个Car组件自动注册到容器中
public class MyConfig {

//    @ConditionalOnBean(name = "car")
    @Bean("user01") //向ioc容器中添加组件,以方法名为组件的id,返回类型就是组件类型
            //返回的值就是组件在容器中的实例,其中@Bean中的value值可设置组件id,默认id就是方法名
    public User user(){
        return new User("zmj",22);
    }

    @Bean(name = "car")
    public Car car(){

        return new Car();
    }
}

主程序中验证

/**
 * Created by KingsLanding on 2022/8/12 15:17
 *
 *  主程序类
 *  @SpringBootApplication:注明这是一个SpringBoot应用,默认扫描组件的范围是主程序所在的包下
 *
 *  @SpringBootConfiguration
 *  @EnableAutoConfiguration
 *  @ComponentScan()使用该组合注解就可以通过@ComponentScan()的value的值来设置扫描的范围
 */
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        //获取IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class);
        //查看容器组件
        String[] beanDefinitionNames = run.getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames){
            System.out.println(beanName);
        }

        //如果@Configuration(proxyBeanMethods = true)配置类不是一个普通的类,
        // 而是一个增强的代理类com.springboot.config.MyConfig$$EnhancerBySpringCGLIB$$1d514a48@5de6cf3a
        //如果@Configuration(proxyBeanMethods = false)
        // com.springboot.config.MyConfig@4b97c4ad
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        //测试是否是单例
        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中存在。
        // 保持组件单实例
        Object user01 = run.getBean("user01");
        Object user02 = run.getBean("user01");

        System.out.println(user01 == user02);//true //false

        //验证获取通过@Import创建的组件
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        for (String s : beanNamesForType) {
            System.out.println(s);
            //com.springboot.pojo.User
            //user01
        }
        System.out.println(run.getBean(DBHelper.class));
        //ch.qos.logback.core.db.DBHelper@32639b12

        System.out.println(run.containsBean("car"));
        System.out.println(run.containsBean("test"));

    }
}

启动时导入的组件

1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件 2、调用1List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类 3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件 4、从META-INF/spring.factories位置来加载一个文件。 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件 spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。

  • xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

    • 定制化配置

    • 用户直接自己@Bean替换底层的组件

    • 用户去看这个组件是获取的配置文件什么值就去修改。

AutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties**

@SpringBootApplication

注明这是一个SpringBoot应用,默认扫描组件的范围是主程序所在的包下

相当于以下三个注解的连用效果

*  @SpringBootConfiguration
*  @EnableAutoConfiguration
*  @ComponentScan()使用该组合注解就可以通过@ComponentScan()的value的值来设置扫描的范围

1、@SpringBootConfiguration

@Configuration。代表当前是一个配置类

2、@ComponentScan

指定扫描哪些,Spring注解;

3、@EnableAutoConfiguration

包含

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

1、@AutoConfigurationPackage

自动配置包?指定了默认的包规则

@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。

2、@Import(AutoConfigurationImportSelector.class)

1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
	默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
    spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
    

Lombok插件

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

同时需要下载该插件

简化JavaBean开发但是一般公司不用,因为必须要求所有参与者都必须安装此插件,可以在自己学习和调试代码时使用

静态资源访问

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名

原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

静态资源访问前缀

默认无前缀

spring:
  mvc:
    static-path-pattern: /res/**

webjar

前端需要的js等文件

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.5.1</version>
        </dependency>

欢迎页

默认为静态资源路径下 index.html

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致welcome page功能失效

  resources:
  #切换欢迎页路径
    static-locations: [classpath:/haha/]

静态资源配置原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
  • 给容器中配了什么。
	@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
  • 配置文件的相关属性和xxx进行了绑定。WebMvcProperties==spring.mvc、ResourceProperties==spring.resources

1、配置类只有一个有参构造器

	//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
	public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
		}

2、资源处理的默认规则

@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
			CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
			//webjars的规则
            if (!registry.hasMappingForPattern("/webjars/**")) {
				customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
            
            //
			String staticPathPattern = this.mvcProperties.getStaticPathPattern();
			if (!registry.hasMappingForPattern(staticPathPattern)) {
				customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
						.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
		}
spring:
#  mvc:
#    static-path-pattern: /res/**

  resources:
    add-mappings: false   禁用所有静态资源规则
@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/" };

	/**
	 * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
	 * /resources/, /static/, /public/].
	 */
	private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

3、欢迎页的处理规则

	HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。	

	@Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
				FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
			WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
					new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
					this.mvcProperties.getStaticPathPattern());
			welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
			welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
			return welcomePageHandlerMapping;
		}

	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)) {
            // 调用Controller  /index
			logger.info("Adding welcome page template: index");
			setRootViewName("index");
		}
	}

请求参数处理

1、rest使用

  • @xxxMapping;

  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作

    • 以前:/getUser 获取用户 /deleteUser删除用户 /editUser 修改用户 /saveUser 保存用户

    • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户

    • 核心Filter;HiddenHttpMethodFilter

  • 用法: 表单method=post,隐藏域 _method=put

    • SpringBoot中手动开启
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true
  • 扩展:如何把_method 这个名字换成我们自己喜欢的。
//自动配置中的方法
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
	return new OrderedHiddenHttpMethodFilter();
}

显示定义之后将使用自定义配置类中的方法(组件)

//自定义filter
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }

方法测试

/**
 * Created by KingsLanding on 2022/8/14 16:33
 */
@RestController
public class RestTestController {

    @RequestMapping(
            value = "/rest",
            method = RequestMethod.POST
    )
    public String testPost(){

        return "POST-方式";
    }

    @RequestMapping(
            value = "/rest",
            method = RequestMethod.GET
    )
    public String testGet(){

        return "GET-方式";
    }

    @RequestMapping(
            value = "/rest",
            method = RequestMethod.PUT
    )
    public String testPut(){

        return "PUT-方式";
    }

    @RequestMapping(
            value = "/rest",
            method = RequestMethod.DELETE
    )
    public String testDelete(){

        return "DELETE-方式";
    }
}

2、Rest原理(表单提交要使用REST的时候)

  • 表单提交会带上_method=PUT

  • 请求过来被HiddenHttpMethodFilter拦截

    • 请求是否正常,并且是POST
      • 获取到_method的值。
      • 兼容以下请求;PUT.DELETE.PATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

注意

**Rest使用客户端工具,**如PostMan直接发送Put、delete等方式请求,无需Filter。

请求映射原理

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx

请求映射handlerMapping.png

RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则

请求映射处理器.png

所有的请求映射都在HandlerMapping中。

欢迎页的原理

●SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;

●SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

●请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

​ ○如果有就找到这个请求对应的handler

​ ○如果没有就是下一个 HandlerMapping

我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

矩阵变量@MatrixVariable

  • SpringBoot默认禁用了矩阵变量的功能

    • 手动开启:原理。对于路径的处理,UrlPathHelper进行解析

    • removeSemicolonContent(移除分号内容)支持矩阵变量

  /**
   * Created by KingsLanding on 2022/8/16 13:01
   */
  @SpringBootConfiguration
  public class MyConfig implements WebMvcConfigurer{
  
      @Bean
      public WebMvcConfigurer webMvcConfigurer(){
  
          WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
              @Override
              public void configurePathMatch(PathMatchConfigurer configurer) {
                  UrlPathHelper urlPathHelper = new UrlPathHelper();
                  //不移除分号;后面的内容,矩阵变量功能就可以生效
                  urlPathHelper.setRemoveSemicolonContent(false);
                  configurer.setUrlPathHelper(urlPathHelper);
              }
              @Override
              public void addFormatters(FormatterRegistry registry) {
                  registry.addConverter(new Converter<String, Pet>() {
                      @Override
                      public Pet convert(String source) {
                          if (StringUtils.hasText(source)){
                              Pet pet = new Pet();
                              String[] split = source.split(",");
                              pet.setName(split[0]);
                              pet.setAge(Integer.parseInt(split[1]));
                              return pet;
                          }
                          return null;
                      }
                  });
              }
          };
          return webMvcConfigurer;
      }
  
  //    @Override
  //    public void configurePathMatch(PathMatchConfigurer configurer) {
  //        UrlPathHelper urlPathHelper = new UrlPathHelper();
  //        //不移除分号;后面的内容,矩阵变量功能就可以生效
  //        urlPathHelper.setRemoveSemicolonContent(false);
  //        configurer.setUrlPathHelper(urlPathHelper);
  //    }
  }

前端数据格式

<a href="/test/sell;low=22;name=zmj,sbh,zqy">测试矩阵变量@MatrixVariable</a><br/>
///test/sell;low=22;name=zmj,sbh,zqy"
//SpringBoot默认禁用了矩阵变量的功能
//  手动开启:原理。对于路径的处理,UrlPathHelper进行解析,
// removeSemicolonContent(移除分号内容)支持矩阵变量
@RequestMapping("/test/{sell}")
public Map<String, Object> testMatrix(@MatrixVariable("low") Integer low,
                                      @MatrixVariable("name") String name,
                                      @PathVariable("sell") String sell){

    HashMap<String, Object> map = new HashMap<>();
    map.put("low",low);
    map.put("name",name);
    map.put("sell",sell);
    return map;
}

测试

<a href="/test/path01;low=22/path02;low=23">测试矩阵变量@MatrixVariable pathVar属性</a>
//"/test/path01/;low=22/path02;low=23"
@RequestMapping("/test/{path01}/{path02}")
public Map<String ,Object> testPathVar(@MatrixVariable(value = "low",pathVar = "path01") Integer path01,
                                       @MatrixVariable(value = "low",pathVar = "path02") Integer path02){
    HashMap<String, Object> map = new HashMap<>();
    map.put("path01",path01);
    map.put("path02",path02);
    return map;
}

参数处理原理(待了解)

数据响应与内容协商

当引入web场景时

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

其将会自动将json场景也引入

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <version>2.7.2</version>
  <scope>compile</scope>
</dependency>

返回值处理

  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType

  • 2、返回值处理器调用 handleReturnValue 进行处理

  • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。

      1. 利用 MessageConverters 进行处理 将数据写为json
      • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
      • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
        • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

SpringMVC到底支持哪些返回值

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;

内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

开启浏览器参数方式内容协商功能

为了方便内容协商,开启基于请求参数的内容协商功能。

spring:
	mvc:
    	contentnegotiation:
      		favor-parameter: true  #开启请求参数内容协商模式

Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

1、视图解析原理流程

1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址

2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer

3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。

4、processDispatchResult 处理派发结果(页面改如何响应)

  • 1、render(mv, request, response); 进行页面渲染逻辑

    • 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】

    • 1、所有的视图解析器尝试是否能根据当前返回值得到View对象

      • 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()

      • 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。

      • 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作

        • RedirectView 如何渲染【重定向到一个页面】
        • 1、获取目标url地址
        • 2、response.sendRedirect(encodedURL);

视图解析:

    • 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发****request.getRequestDispatcher(path).forward(request, response);
    • 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向
    • 返回值是普通字符串: new ThymeleafView()--->

拦截器

1、HandlerInterceptor 接口

/**
 * Created by KingsLanding on 2022/8/21 21:59
 *
 * 1.编写一个拦藏器实现HandlerInterceptor接口
 * 2.拦截器注册到容器中(实现WebMvcConfigurer 的addInterceptors )
 * 3.指定拦截规则[如果是拦截所有,静态资源也会被拦截
 */
@Slf4j
@Component
public class ChickFilter implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        Object name = session.getAttribute("name");
        if (name==null){

            StringBuffer requestURL = request.getRequestURL();
            log.info("拦截到的路径:"+requestURL);
            session.setAttribute("msg","请先登陆");
            request.getRequestDispatcher("/").forward(request,response);
            return false;
        }else {
            return true;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

2、拦截器注册

//拦截器的配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new ChickFilter())
            //拦截的路径
            .addPathPatterns("/**")
            //放行的路径
            .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");
}

3、拦截器原理

1、根据当前请求,找到**HandlerExecutionChain【**可以处理请求的handler以及handler的所有 拦截器】

2、先来顺序执行 所有拦截器的 preHandle方法

  • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
  • 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion

3、如果任何一个拦截器返回false。直接跳出不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

6、前面的步骤有任何异常都会直接倒序触发 afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

拦截器.png

## 文件上传

表单规则

必须设置enctype="multipart/form-data",浏览器才能将文件作为流传输到服务器

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>

配置文件设置文件相关规则

#设置单个文件上传的限制大小
spring.servlet.multipart.max-file-size=10MB
#设置整个表单上传的大小限制
spring.servlet.multipart.max-request-size=100MB

@RequestPart

承载了表单传过来的文件流数据

@RequestPart("header") MultipartFile header;参数header中即是流数据

同时也可以有@RequestParam的功能

/**
 * Created by KingsLanding on 2022/8/22 0:05
 */
@Slf4j
@Controller
public class FileUpAndLoadController {

    @GetMapping("/form_layouts")
    public String testFormLayouts(){
        return "file/form_layouts";
    }

    @PostMapping("/upload")
    public String testUpload(@RequestParam("email") String email,
                             @RequestParam("password") String password,
                             @RequestPart("header") MultipartFile header,//处理单文件上传
                             @RequestPart("photos") MultipartFile[] photos,//多文件上传
                             HttpSession session) throws IOException {
        //获取ServletContext对象
        ServletContext servletContext = session.getServletContext();
        //获取当前工程下photo目录的真实路径
        String photo = servletContext.getRealPath("/photo/");
        String photoPath=photo+header.getOriginalFilename();
        if (!(header.isEmpty())){
            header.transferTo(new File(photoPath));
        }
       log.info("上传的文件名:"+header.getOriginalFilename()+"存储位置:"+photoPath);
        return "main";
    }
}

Springboot文件上传遇到的问题

//获取ServletContext对象 ServletContext servletContext = session.getServletContext(); //获取当前工程下photo目录的真实路径 String photo = servletContext.getRealPath("/photo/");

这个语句如果不设置ieda,那么获取到的是一个临时文件路径,且路径随启动变化

springboot运行空间.png

设置该路径时,需要保证resource目录下存在static或public目录

且String photo = servletContext.getRealPath("/photo/");获取到的路径为

E:\IDEA 2018.2.4\demo01\demo02\src\main\resources\static\photo\

并且参数必须写成/photo/,否则路径拼接不成功

自动配置原理

**文件上传自动配置类-MultipartAutoConfiguration-**MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】

  • 原理步骤

    • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
    • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
    • **3、将request中文件信息封装为一个Map;**MultiValueMap<String, MultipartFile>

FileCopyUtils。实现文件流的拷贝

异常处理

默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

错误信息.png

templates/error/下的4xx,5xx页面会被自动解析

定制错误处理逻辑

  • 自定义错误页
    • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
/**
    * Created by KingsLanding on 2022/8/22 18:34
    * 处理整个web controller的异常
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})  //处理异常
    public String handleArithException(Exception e){

        log.error("异常是:{}",e);
        return "login"; //视图地址
    }
}
  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
//ResponseStatus的作用就是为了设置HTTP响应的状态码
@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {

    public  UserTooManyException(){

    }
    public  UserTooManyException(String message){
        super(message);
    }
}
  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());

Spring底层的异常.png

  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
/**
 * Created by KingsLanding on 2022/8/22 18:28
 */
@Order(1)
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler, Exception ex) {
        try {
            response.sendError(511,"自定义的错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}
  • ErrorViewResolver 实现自定义处理异常;

    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver

异常处理自动配置原理

  • ErrorMvcAutoConfiguration 自动配置异常处理规则

    • 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes

    • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver

    • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。

    • 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)

    • 处理默认 /error 路径的请求;页面响应 new ModelAndView("error", model);

    • 容器中有组件 View->id是error;(响应默认错误页)

    • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。

  • 容器中的组件类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver

  • 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面error/404、5xx.html

如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)

异常处理步骤流程

1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException

2、进入视图解析流程(页面渲染?)

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

  • 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器

  • 2、系统默认的 异常解析器;

  • 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;

  • 2、默认没有任何人能处理异常,所以异常会被抛出

    • 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
    • 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
    • 3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
    • 4、模板引擎最终响应这个页面 error/500.html

Web原生组件注入(Servlet、Filter、Listener)

1、使用Servlet API

@ServletComponentScan

@ServletComponentScan(basePackages = **"com.atguigu.admin"**) :指定原生Servlet组件都放在那里

@WebServlet

@WebServlet(urlPatterns = **"/my"**):效果:直接响应,没有经过Spring的拦截器?

@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {

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

@WebFilter

@WebFilter(urlPatterns={"/css/*","/images/*"})`

@Slf4j
@WebFilter(urlPatterns={"/css/*","/images/*"}) //my
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("MyFilter初始化完成");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("MyFilter工作");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("MyFilter销毁");
    }
}

@WebListener

@Slf4j
//@WebListener
public class MySwervletContextListener implements ServletContextListener {


    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MySwervletContextListener监听到项目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MySwervletContextListener监听到项目销毁");
    }
}

使用RegistrationBean

/**
 * 1、MyServlet --> /my
 * 2、DispatcherServlet --> /
 */
// (proxyBeanMethods = true):保证依赖的组件始终是单实例的
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {

    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();

        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


   // @Bean
    public FilterRegistrationBean myFilter(){

        //创建配置bean对象,并指定->过滤器
        MyFilter myFilter = new MyFilter();
//        return new FilterRegistrationBean(myFilter,myServlet());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}

嵌入式Servlet容器(尚硅谷)

1、切换嵌入式Servlet容器

  • 默认支持的webServer

    • Tomcat, Jetty, or Undertow
    • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
  • 切换服务器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  • 原理

    • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
    • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
    • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂---> Servlet 的web服务器)`
    • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
    • 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
    • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
    • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
    • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
    • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

定制Servlet容器

  • 实现 WebServerFactoryCustomizer

    • 把配置文件的值和**ServletWebServerFactory 进行绑定**
  • 修改配置文件 server.xxx

  • 直接自定义 ConfigurableServletWebServerFactory

xxxxxCustomizer:定制化器,可以改变xxxx的默认规则


@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}

定制化原理(尚硅谷)

1、定制化的常见方式

  • 修改配置文件;
  • xxxxxCustomizer;
  • 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
  • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
  • @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能

    • 原理
    • 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页.....
    • 2、一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class)
    • 3、DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用
      • 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
      • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
      • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
    • 4、WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    • 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
  • ... ...

2、原理分析套路

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties -- 绑定配置文件项

数据访问

数据源的自动配置-HikariDataSource

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

为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下要操作什么数据库。

数据库版本和驱动版本对应

默认版本:<mysql.version>8.0.22</mysql.version>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
<!--            <version>5.1.49</version>-->
        </dependency>
想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)
    <properties>
        <java.version>1.8</java.version>
        <mysql.version>5.1.49</mysql.version>
    </properties>

分析自动配置

  • DataSourceAutoConfiguration : 数据源的自动配置

    • 修改数据源相关的配置:spring.datasource
    • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
    • 底层配置好的连接池是:HikariDataSource
	@Configuration(proxyBeanMethods = false)
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
			DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
	protected static class PooledDataSourceConfiguration
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置

  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud

    • 可以修改这个配置项@ConfigurationProperties(prefix = "spring.jdbc") 来修改JdbcTemplate
    • @Bean@Primary JdbcTemplate;容器中有这个组件
  • JndiDataSourceAutoConfiguration: jndi的自动配置

  • XADataSourceAutoConfiguration: 分布式事务相关的

配置项设置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jdbctest?serverTimezone=UTC
    username: root
    password: zmj.666.999

测试连接

@Slf4j
@SpringBootTest
class Demo02ApplicationTests {

    @Resource
    private JdbcTemplate jdbcTemplate;
    @Resource
    private DataSource dataSource;
    @Test
    void contextLoads() {
        String sql="select count(*) from massage";
        Long aLong = jdbcTemplate.queryForObject(sql, Long.class);
        System.out.println(aLong);
        String sql02="select * from massage";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql02);
        System.out.println(maps);
        log.info("数据源类型:"+dataSource.getClass());

    }
}

Druid数据源

整合第三方技术的两种方式

自定义

  <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
		destroy-method="close">
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxActive" value="20" />
		<property name="initialSize" value="1" />
		<property name="maxWait" value="60000" />
		<property name="minIdle" value="1" />
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<property name="poolPreparedStatements" value="true" />
		<property name="maxOpenPreparedStatements" value="20" />

StatViewServlet

  • 提供监控信息展示的html页面
  • 提供监控信息的JSON API
<servlet>
	<servlet-name>DruidStatView</servlet-name>
	<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>DruidStatView</servlet-name>
	<url-pattern>/druid/*</url-pattern>
</servlet-mapping>

StatFilter

用于统计监控信息;如SQL监控、URI监控

需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如:

需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如:

<property name="filters" value="stat,slf4j" />

官方starter(推荐)

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.17</version>
    </dependency>

配置数据源DataSourceAutoConfiguration自动配置类中规定 @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) 就是默认自动配置时判断容器中没有才会默认使用springboot自动配置的数据源,一旦手动配置了, 那么默认数据源DataSourceConfiguration.Hikari.class就会失效,转而使用手动配置的数据源

    /*
    在DataSourceAutoConfiguration自动配置类中规定
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    就是默认自动配置时判断容器中没有才会默认使用springboot自动配置的数据源,一旦手动配置了,
    那么默认数据源DataSourceConfiguration.Hikari.class就会失效,转而使用手动配置的数据源
     */
    @ConfigurationProperties("spring.datasource")//绑定yml配置文件
    @Bean
    public DataSource DataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
//        druidDataSource.setFilters("start");
//        druidDataSource.setUrl("");
//        druidDataSource.setPassword("");
        return druidDataSource;
    }

分析自动配置

  • 扩展配置项 spring.datasource.druid
  • DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
  • DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
  • DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
  • DruidFilterConfiguration.class}) 所有Druid自己filter的配置
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";

配置durid监控(方式一)

相关配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jdbctest?serverTimezone=UTC
    username: root
    password: zmj.666.999
    #type: com.zaxxer.hikari.HikariDataSource
    druid:
      aop-patterns: com.springboot.*  #springbean监控
      filters: stat,wall,slf4j  #所有开启的功能

      stat-view-servlet:  #监控页配置
        enabled: true
        login-username: admin
        login-password: admin
        resetEnable: false

      web-stat-filter:  #web监控
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


      filter:
        stat: #sql监控
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall: #防火墙
          enabled: true
          config:
            drop-table-allow: false
  

配置durid监控(方式二)

 /**
  * 配置 druid的监控页功能
  * @return
  */
@Bean
 public ServletRegistrationBean statViewServlet(){
     StatViewServlet statViewServlet = new StatViewServlet();
     ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");

     registrationBean.addInitParameter("loginUsername","admin");
     registrationBean.addInitParameter("loginPassword","123456");


     return registrationBean;
 }

 /**
  * WebStatFilter 用于采集web-jdbc关联监控的数据。
  */
 @Bean
 public FilterRegistrationBean webStatFilter(){
     WebStatFilter webStatFilter = new WebStatFilter();

     FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
     filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
     filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

     return filterRegistrationBean;
 }

SpringBoot配置示例

github.com/alibaba/dru…

配置项列表github.com/alibaba/dru…

springboot整合MyBatis操作

官方starter

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>

配置模式

  • 全局配置文件
  • SqlSessionFactory: 自动配置好了
  • SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
  • @Import(AutoConfiguredMapperScannerRegistrar.class);
  • Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class) // MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}

@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties

配置文件(非xml)

可以不写全局;配置文件,所有全局配置文件的配置都放在configuration配置项中即可

#导入mybatis官方starter
  #编写mapper接口。标准@Mapper注解
  #编写sql映射文件并绑定mapper接口
  #在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息(建议; 配置在mybatis.configuration)
  #配置mybatis规则

mybatis:
  #核心配置文件位置
  config-location: classpath:mybatis/mybatis-config.xml
  #映射文件的位置
  mapper-locations: classpath:mybatis/mapper/*.xml
  #设置驼峰命名原则,但是在yml配置文件中设置了,就不能在配置文件中设置,
  #全局文件可以不写,建议全局文件的配置都在configuration项中配置
#  configuration:

核心配置文件xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--
        MyBatis核心配置文件中,标签的顺序:(不按标签顺序写会报错)
        properties?,settings?,typeAliases?,typeHandlers?,
        objectFactory?,objectWrapperFactory?,reflectorFactory?,
        plugins?,environments?,databaseIdProvider?,mappers?
     -->

    <!--引入properties文件,此后就可以在当前文件中使用${key}\的方式访问value-->
    <properties resource="druid.properties"/>

    <settings>
        <!--将下划线映射为驼峰原则;将字段名中的下划线自动转换为驼峰命名的属性名-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <!--设置类型别名-->
        <!--单独设置-->
        <!--<typeAlias type="" alias=""></typeAlias>-->
        <!--以包为单位设置-->
        <package name="com.springboot"></package><!--bean包下的所有类都设置了默认别名-->
    </typeAliases>

    <!--设置连接数据库的环境-->
    <environments default="development">

        <environment id="development">
            <transactionManager type="JDBC"/>

            <dataSource type="POOLED">
                <property name="driver" value="${driverClass}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>

        </environment>
    </environments>

    <!--引入映射文件-->
    <mappers>
        <!--单独引入映射文件-->
        <!--<mapper resource=""/>-->
        <!--以包为单位来引入-->
        <package name="mybatis.mapper"/>
    </mappers>

</configuration>

注解模式

  • 编写mapper接口。标注@Mapper注解

可以直接在接口中通过@Select("select * from massage where id=#{id}")类型的注解在接口方法上写sql语句,不用在mapper.xml映射文件中写sql语句

混合模式

/**
 * Created by KingsLanding on 2022/8/23 16:26
 */
@Component
@Mapper
public interface MassageMapper {

    public List queryMassageAll();

    /*
    @Mapper表明接口是mybatis,与映射文件同
    Mybatis的注解式编程,可以和xml方式混合使用
     */
    @Select("select * from massage where id=#{id}")
    public Massage getMassageById(Integer id);

}

映射文件

<?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)保持一致-->
<mapper namespace="com.springboot.mapper.MassageMapper">

    <!--List queryMassageAll();-->
    <select id="queryMassageAll" resultType="Massage">
        select * from massage
    </select>

</mapper>

方法测试

/**
 * Created by KingsLanding on 2022/8/23 16:29
 */
@SpringBootTest
public class testMybatis {

    @Resource
    private SqlSession sqlSession;

    @Test
    public void testQueryMassageAll(){
        MassageMapper mapper = sqlSession.getMapper(MassageMapper.class);
        List list = mapper.queryMassageAll();
        System.out.println(list);

        Massage massageById = mapper.getMassageById(2);
        System.out.println(massageById);
    }
}

最佳实战:**

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可
  • 编写Mapper接口并标注@Mapper注解
  • 简单方法直接注解方式
  • 复杂方法编写mapper.xml进行绑定映射
  • @MapperScan("com.atguigu.admin.mapper") 简化,其他的接口就可以不用标注@Mapper注解

整合 MyBatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

mybatis plus 官网

建议安装 MybatisX 插件

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>

自动配置

  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对****mybatis-plus的定制
  • SqlSessionFactory 自动配置好。底层是容器中默认的数据源
  • **mapperLocations 自动配置好的。有默认值。*classpath*:/mapper/*/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下
  • 容器中也自动配置好了 SqlSessionTemplate
  • @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan("com.atguigu.admin.mapper") 批量扫描就行

优点:

  • 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力
/**
 * Created by KingsLanding on 2022/8/23 16:26
 */
@Component
@Mapper
public interface MassageMapper extends BaseMapper<Massage> {

    public List queryMassageAll();

    /*
    @Mapper表明接口是mybatis,与映射文件同
    Mybatis的注解式编程,可以和xml方式混合使用
     */
    @Select("select * from massage where id=#{id}")
    public Massage getMassageById(Integer id);

}
  • 同时service接口的方法以及serviceImpl的方法也都由Mybatis-plus逆向创建好了,只需继承相应的接口和实现类,无需编写任何代码就可以使用Mybatis-plus创建的所有crud方法

Iservice

/**
 * Created by KingsLanding on 2022/8/24 1:10
 */
@Service
public interface MassageService extends IService<Massage> {

}

ServiceImpl

/**
 * Created by KingsLanding on 2022/8/24 1:10
 */
@Service
public class MassageServiceImpl extends ServiceImpl<MassageMapper, Massage> implements MassageService {
}

测试

@TableName

@TableField

package com.springboot.pojo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;

/**
 * Created by KingsLanding on 2022/7/12 23:50
 */
//如果表明和实体类名不同,可以使用@TableName注解标明表明
@TableName("massage")
public class Massage {
    private Integer id;
    private String name;
    private Integer age;
    private String phone;

    /*
    使用springboot自动装配的Mybatis-plus时要注意,bean对象的属性必须与数据表对应,
    否则SQLSyntaxErrorException,但是可以使用@TableField注解表示该属性不在数据表中
     */
    @TableField(exist = false)
    private String dataTest;

    public Massage() {
    }

    public Massage(Integer id, String name, Integer age, String phone) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.phone = phone;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Massage{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }
}

分别使用dao层、service层的方法来进行测试

/**
 * Created by KingsLanding on 2022/8/24 0:49
 */
@SpringBootTest
public class testMybatisPlus {

    @Resource
    private MassageMapper massageMapper;

    @Resource
    private MassageService massageService;

    @Test
    public void testPlus(){

        List<Massage> massages = massageMapper.selectList(null);
        System.out.println(massages);
    }

    @Test
    public void testIService(){
       	Massage byId = massageService.getById(10);

        List<Massage> list = massageService.list();
       
        System.out.println(list);

        System.out.println(byId);

    }
}

MybatisPlus自带的分页插件

MyBatis-Plus 3.4.0开始,不再使用旧版本的PaginationInterceptor ,而是使用MybatisPlusInterceptor

创建配置类配置MybatisPlusInterceptor,将分页功能添加进来

/**
 * Created by KingsLanding on 2022/8/24 14:03
 */
@Configuration
//@MapperScan("scan.your.mapper.package")
public class MybatisConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration
     * #useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }
}

测试分页功能的使用

    @Test
    public void testIService(){
        Page<Massage> page = new Page<>(2, 5);

        Page<Massage> massagePage = massageService.page(page, null);
        System.out.println(massagePage.getPages());

        System.out.println(page);


    }

详细介绍 blog.csdn.net/hbtj_1216/a…

NoSQL

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

stater

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

自动配置:

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • key:value
  • 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis

RedisTemplate与Lettuce

/**
 * Created by KingsLanding on 2022/8/24 17:32
 */
@SpringBootTest
public class testRedis {

    @Resource
    private RedisTemplate redisTemplate;

    @Test
    public void testRedis(){
        ValueOperations v = redisTemplate.opsForValue();
        v.set("k1","v1");

        System.out.println(v.get("k1"));
        System.out.println(v.getClass());
    }
}

切换至jedis

<!--        导入jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

配置文件

spring.redis.host=192.168.222.128
spring.redis.port=6379
spring.redis.client-type=jedis

RedisTemplate查询不到redis中的数据问题解决_Ich will mit dir S wim的博客-CSDN博客_redistemplate.keys获取不到值

JUnit5单元测试

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

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。

JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

注意:

SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)

JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage

以前:

@SpringBootTest + @RunWith(SpringTest.class)

SpringBoot整合Junit以后

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

常用注解

@Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试 @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍 @RepeatedTest :表示方法可重复执行,下方会有详细介绍 @DisplayName :为测试类或者测试方法设置展示名称 @BeforeEach:表示在每个单元测试之前执行 @AfterEach :表示在每个单元测试之后执行 @BeforeAll :表示在所有单元测试之前执行 @AfterAll :表示在所有单元测试之后执行 @Tag :表示单元测试类别,类似于JUnit4中的@Categories @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore @Timeout :表示测试方法运行如果超过了指定时间将会返回错误 @ExtendWith :为测试类或测试方法提供扩展类引用

/**
 * Created by KingsLanding on 2022/8/24 18:19
 */
@DisplayName("Junit5测试类")
public class TestJunit5 {

        /*
    @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外
测试
    @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
    @RepeatedTest :表示方法可重复执行,下方会有详细介绍
    @DisplayName :为测试类或者测试方法设置展示名称
    @BeforeEach :表示在每个单元测试之前执行
    @AfterEach :表示在每个单元测试之后执行
    @BeforeAll :表示在所有单元测试之前执行
    @AfterAll :表示在所有单元测试之后执行
    @Tag :表示单元测试类别,类似于JUnit4中的@Categories
    @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
    @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
    @ExtendWith :为测试类或测试方法提供扩展类引用
     */

    @DisplayName("测试DisplayName方法")
    @Test
    public void testDisplayName(){
        System.out.println(1);
    }

    @Test
    @BeforeEach
    public void testBeforeEach(){
        System.out.println("@BeforeEach注解测试");
    }

    @Test
    @AfterEach
    public void testAfterEach(){
        System.out.println("@AfterEach注解测试");
    }

    /**
     * 规定方法超时时间,超出时间测试异常
     * @throws InterruptedException
     */
    @Test
    @Timeout(value = 300,unit = TimeUnit.MILLISECONDS)
    public void testTimeout() throws InterruptedException {
        Thread.sleep(500);
    }

    @Test
    @RepeatedTest(2)
    public void testRepeatedTest(){
        System.out.println("@RepeatedTest 注解测试方法");
    }
}

断言(assertions)

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:

检查业务逻辑返回的数据是否合理。

所有的测试运行结束以后,会有一个详细的测试报告;

简单断言

@Test
public void testAssertions(){
    int sum = sum(3, 3);
    assertEquals(sum,5,"业务逻辑计算失败");

    /**
     * 断言:前面的断言失败,后面的代码都不会执行
     */
    Object o1 = new Object();
    Object o2 = new Object();
    assertSame(o1,o2,"不是同一个对象");
}

public int sum(int i,int j){
    int sum;
    sum=i+j;
    return sum;
}

数组断言

@Test
@DisplayName("数组断言")
public void testAssertArrayEquals(){
    assertArrayEquals(new int[]{1,2},new int[]{2,1},"数组内容不匹配");
}

异常断言

@Test
@DisplayName("异常断言")
public void testException(){
    /**
     * 无错为有错
     */
    assertThrows(ArithmeticException.class,()->{int i=10/0;},"业务逻辑没有报错,所以错误");
}

快速失败

@Test
@DisplayName("快速失败")
public void testFail(){
    /**
     * 判断正确即失败
     */
    if (2==2){
        fail("测试失败");
    }
}

组合断言

@Test
@DisplayName("组合断言")
public void testAssertAll(){

    /**
     * 只有当所有的断言都成功才能通过测试
     */
    assertAll("test",
            ()-> assertTrue(true&&true,"结果不为true"),
            ()-> assertEquals(1,2,"结果不同"));
}

超时断言

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    //如果测试方法时间超过1s将会异常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

前置条件(assumptions)

JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

    /*
    JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只
会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
     */

    @Test
    @DisplayName("假设")
    public void testAssumption(){
        Assumptions.assumeFalse(false,"条件不符");
        Assumptions.assumeTrue(true,"条件不符");
    }
}

assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

/**
 * Created by KingsLanding on 2022/8/24 21:36
 *
 * JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。
 * 在内部类中可以使用@BeforeEach@AfterEach 注解,而且嵌套的层次没有限制。
 */
@DisplayName("嵌套测试")
public class TestJunit5Nested {
    @DisplayName("A stack")
    class TestingAStackDemo {

        Stack<Object> stack;

        @Test
        @DisplayName("is instantiated with new Stack()")
        void isInstantiatedWithNew() {
            new Stack<>();
        }

        @Nested
        @DisplayName("when new")
        class WhenNew {

            @BeforeEach
            void createNewStack() {
                stack = new Stack<>();
            }

            @Test
            @DisplayName("is empty")
            void isEmpty() {
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("throws EmptyStackException when popped")
            void throwsExceptionWhenPopped() {
                assertThrows(EmptyStackException.class, stack::pop);
            }

            @Test
            @DisplayName("throws EmptyStackException when peeked")
            void throwsExceptionWhenPeeked() {
                assertThrows(EmptyStackException.class, stack::peek);
            }

            @Nested
            @DisplayName("after pushing an element")
            class AfterPushing {

                String anElement = "an element";

                @BeforeEach
                void pushAnElement() {
                    stack.push(anElement);
                }

                @Test
                @DisplayName("it is no longer empty")
                void isNotEmpty() {
                    assertFalse(stack.isEmpty());
                }

                @Test
                @DisplayName("returns the element when popped and is empty")
                void returnElementWhenPopped() {
                    assertEquals(anElement, stack.pop());
                    assertTrue(stack.isEmpty());
                }

                @Test
                @DisplayName("returns the element when peeked but remains not empty")
                void returnElementWhenPeeked() {
                    assertEquals(anElement, stack.peek());
                    assertFalse(stack.isEmpty());
                }
            }
        }
    }
}

参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource: 表示为参数化测试提供一个null的入参

@EnumSource: 表示为参数化测试提供一个枚举入参

@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参

/**
 * Created by KingsLanding on 2022/8/24 21:58
 */
@DisplayName("参数化测试")
public class TestJunit5Parameter {

    @ParameterizedTest
    @ValueSource(ints = {1,2,3,4})
    public void testParameterized(int i){
        System.out.println(i);
    }

    @ParameterizedTest
    @MethodSource("stringStream")
    public void testParameterized02(String str){
        System.out.println(str);
    }

    static Stream<String> stringStream(){
        return Stream.of("apple","banana","lizi");
    }
}

指标监控

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

场景依赖

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

使用方法

配置文件开启并设置相关功能

management是所有Actuator的配置

# management是所有Actuator的配置
# 但是一些敏感端点不想暴露出来所以最好设置为false,手动开启想要开启的端点
management.endpoints.enabled-by-default=true
#以web方式暴露所有端点
management.endpoints.web.exposure.include=*
  • 测试

http://localhost:8080/actuator/beans

http://localhost:8080/actuator/configprops

http://localhost:8080/actuator/metrics

http://localhost:8080/actuator/metrics/jvm.gc.pause

http://localhost:8080/actuator/

http://localhost:8080/actuator/metrics/endpointName/detailPath

访问localhost:8080/actuator/health输出的结果

{"status":"DOWN","components":{"db":{"status":"UP","details":{"database":"MySQL","validationQuery":"isValid()"}},"diskSpace":{"status":"UP","details":{"total":53687087104,"free":21882572800,"threshold":10485760,"exists":true}},"my":{"status":"UP","details":{"code":100,"ms":100,"count":1}},"ping":{"status":"UP"},"redis":{"status":"DOWN","details":{"error":"org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is org.springframework.data.redis.connection.PoolException: Could not get a resource from the pool; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379"}}}}

最常使用的端点

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录
ID描述
auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
beans显示应用程序中所有Spring Bean的完整列表。
caches暴露可用的缓存。
conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops显示所有@ConfigurationProperties
env暴露Spring的属性ConfigurableEnvironment
flyway显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
health显示应用程序运行状况信息。
httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
info显示应用程序信息。
integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
loggers显示和修改应用程序中日志的配置。
liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics显示当前应用程序的“指标”信息。
mappings显示所有@RequestMapping路径列表。
scheduledtasks显示应用程序中的计划任务。
sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown使应用程序正常关闭。默认禁用。
startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
threaddump执行线程转储。

如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

ID描述
heapdump返回hprof堆转储文件。
jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

同时也可以使用jdk自带的监控

控制台输入jconsole

可视化

github.com/codecentric…

Health Endpoint

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

重要的几点:

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
  • 可以很容易的添加自定义的健康检查机制

Metrics Endpoint

提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已有Metrics

管理Endpoints

开启与禁用Endpoints

  • 默认所有的Endpoint除shutdown都是开启的。
  • 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint.enabled = true

暴露Endpoints

  • HTTP:默认只暴露healthinfo Endpoint
  • JMX:默认暴露所有Endpoint
  • 除health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
# management是所有Actuator的配置
# 但是一些敏感端点不想暴露出来所以最好设置为false,手动开启想要开启的端点
management.endpoints.enabled-by-default=true
#以web方式暴露所有端点
management.endpoints.web.exposure.include=*
#开启health监控详细信息
management.endpoint.health.show-details=always
management.endpoint.health.enabled=true
#开启部分端点
management.endpoint.info.enabled=true
management.endpoint.beans.enabled=true
management.endpoint.metrics.enabled=true

定制 Endpoint

定制 Health 信息

/**
 * Created by KingsLanding on 2022/8/25 17:00
 */
@Component
public class MyHealthIndicator extends AbstractHealthIndicator {
    /*
    自定义的检查监控
     */
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        HashMap<String, Object> map = new HashMap<>();
        if(1==1){
            //健康
            builder.status(Status.UP);
            map.put("count",1);
            map.put("ms",100);
        }else {
            builder.status(Status.OUT_OF_SERVICE);
            map.put("err","连接超时");
            map.put("ms",300);
        }
        builder.withDetail("code",100).withDetails(map);
    }
}

定制info信息

方式一

/**
 * Created by KingsLanding on 2022/8/25 18:04
 */
@Component
public class MyInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("msg","hhhh");
        builder.withDetails(Collections.singletonMap("hello","world"));
    }
}

方式二

info:
  appName: boot-admin
  mavenProjectName: '@project.artifactId@'
  mavenProjectVersion: '@project.version@'

定制Metrics信息

class MyService{
    Counter counter;
    public MyService(MeterRegistry meterRegistry){
         counter = meterRegistry.counter("myservice.method.running.counter");
    }

    public void hello() {
        counter.increment();
    }
}


//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
    return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}

定制Endpoint

/**
 * Created by KingsLanding on 2022/8/25 18:09
 * 自定义监控端点
 */
@Component
@Endpoint(id = "container")
public class MyServiceEndpoint {

    @ReadOperation
    public Map getDockerInfo(){
        return Collections.singletonMap("dockerInfo","docker started...");
    }

    @WriteOperation
    public void stopDocker(){
        System.out.println("docker stopped...");
    }
}

Profile功能

为了方便多环境适配,springboot简化了profile功能。

简而言之就是不同的功能需求如果都配置在一个配置文件中(properties;yaml),代码可读性较差,将其分到不同的配置文件中,按照规则加载,可读性,可操作性大大提升

application-profile功能

  • 默认配置文件 application.yaml or application.properties;任何时候都会加载

  • 指定环境配置文件 application-{env}.yaml

  • 激活指定环境

    • 配置文件激活
    • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
      • 修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效

  • 同名配置项,profile配置优先

分组设置profile

注意,如果两个配置文件都设置了端口号,那么以最后一个加入数组的配置文件的设置为准

application.properties

#指定运行端口号
server.port=8080
#指定激活的环境,相当于两个或多个yml配置文件都会生效
spring.profiles.active=myprod

#分组,以便批量加载
spring.profiles.group.myprod[0]=ppd
#注意,如果两个配置文件都设置了端口号,那么以最后一个加入数组的配置文件的设置为准
spring.profiles.group.myprod[1]=prod

spring.profiles.group.mytest[0]=test

application-ppd.yml

server:
  port: 7000
person:
  age: 22

application-prod.yml

person:
  name: prod-张明均

#指定端口号
server:
  port: 8000

application-test.yml

person:
  name: test-张三

server:
  port: 6000

绑定实体类@Profile

/**
 * Created by KingsLanding on 2022/8/25 21:52
 */

@Profile(value = {"myprod","ppd"})
@ConfigurationProperties("person")
@Component
@Data
public class Person {
    private String name;
    private Integer age;
}

TestController测试

/**
 * Created by KingsLanding on 2022/8/25 21:09
 */

@RestController
public class TestController {

    @Value("${person.name:李四}")
    private String name;

    //获取环境变量中的值;就是高级系统设置里的环境变量(贼离谱)
    @Value("${MAVEN_HOME}")
    private String msg;

    @Autowired
    private Person person;

    @RequestMapping("/")
    public String testProfile(){

        return "测试结果"+name;//测试结果prod-张明均
    }

    @RequestMapping("/person")
    public Person testProfileFenzu(){

        return person;//{"name":"prod-张明均","age":22}
    }
}

外部化配置

官方文档

docs.spring.io/spring-boot…

外部配置源

常用:Java属性文件YAML文件环境变量命令行参数

配置文件查找位置

(1) classpath 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目

配置文件加载顺序:

生效优先级:从下到上,从外到内(甚至可以在jar包同文件下设置配置文件,而且能生效,且优先级最高,可用于老项目的配置修改

  • 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

Profile外部配置文件.png

  1.  当前jar包内部的application.properties和application.yml
  2.  当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3.  引用的外部jar包的application.properties和application.yml
  4.  引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

自定义starter

  • starter-pom引入 autoconfigurer 包

  • autoconfigure包中配置使用 META-INF/spring.factoriesEnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类

  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties

    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean
    • ......

引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

自定义starter

atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)

<groupId>com.springboot</groupId>
<artifactId>atguigu-hello-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>

HelloService

/**
 * Created by KingsLanding on 2022/8/26 2:26
 */
//默认不放在容器中
public class HelloService {
    @Autowired
    HelloProperties helloProperties;

    public String sayHello(String userName){

        return helloProperties.getPrefix()+":"+userName+"》"+helloProperties.getSuffix();

    }
}

#### 实体HelloProperties

/**
 * Created by KingsLanding on 2022/8/26 2:15
 */
//设置配置文件对应的标识
@ConfigurationProperties("startest.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;
    }
}

HelloServiceAutoConfiguration

/**
 * Created by KingsLanding on 2022/8/26 2:32
 */
@Configuration
@EnableConfigurationProperties(HelloProperties.class) //默认将HelloProperties加到容器中
public class HelloServiceAutoConfiguration {

    @ConditionalOnMissingBean(HelloService.class)//若容器中没有该组件才会自动装配
    @Bean
    public  HelloService helloService(){
        HelloService helloService = new HelloService();
        return helloService;
    }
}

spring.factories

resources\META-INF\spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.springboot.autoconfig.HelloServiceAutoConfiguration

1、将该项目打包保存到maven仓库

2、创建starter

atguigu-hello-spring-boot-starter(启动器)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>atguigu-hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.springboot</groupId>
            <artifactId>atguigu-hello-spring-boot-starter-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

3、在新建的项目中加入该starter

<dependency>
    <groupId>org.example</groupId>
    <artifactId>atguigu-hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

4、根据在atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)中定义的规则使用其中的方法

也可以通过@Configuration配置类改写其中的方法

myconfig\MyHelloService

/**
 * Created by KingsLanding on 2022/8/26 3:07
 */
@Configuration
public class MyHelloService {
    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService(){
            @Override
            public String sayHello(String userName) {
                return super.sayHello(userName);//没写
            }
        };
        return helloService;
    }
}

application.properties通过配置文件定义属性值

startest.hello.prefix=zzz
startest.hello.suffix=mmm

HelloController

/**
 * Created by KingsLanding on 2022/8/26 2:54
 */
@Controller
public class HelloController {

    @Autowired
    HelloService helloService;

    @RequestMapping("/")
    @ResponseBody
    public String sayHello(){
        String zmj = helloService.sayHello("zmj");
        return zmj;
    }
}

SpringBoot启动过程

  • 创建 SpringApplication

    • 保存一些信息。
    • 判定当前应用的类型。ClassUtils。Servlet
    • bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper
    • ApplicationContextInitializer;去spring.factoriesApplicationContextInitializer
      • List<ApplicationContextInitializer<?>> initializers
    • ApplicationListener ;应用监听器。spring.factories****找 ApplicationListener
      • List<ApplicationListener<?>> listeners
  • 运行 SpringApplication

    • StopWatch
    • 记录应用的启动时间
    • **创建引导上下文(Context环境)**createBootstrapContext()
      • 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
    • 让当前应用进入headless模式。java.awt.headless
    • 获取所有 RunListener**(运行监听器)【为了方便所有Listener进行事件感知】**
      • getSpringFactoriesInstances 去spring.factories****找 SpringApplicationRunListener.
    • 遍历 SpringApplicationRunListener 调用 starting 方法;
      • 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
    • 保存命令行参数;ApplicationArguments
    • 准备环境 prepareEnvironment();
      • 返回或者创建基础环境信息对象。StandardServletEnvironment
      • 配置环境信息对象。
        • 读取所有的配置源的配置属性值。
      • 绑定环境信息
      • 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
    • 创建IOC容器(createApplicationContext())
      • 根据项目类型(Servlet)创建容器,
      • 当前会创建 AnnotationConfigServletWebServerApplicationContext
    • 准备ApplicationContext IOC容器的基本信息 prepareContext()
      • 保存环境信息
      • IOC容器的后置处理流程。
      • 应用初始化器;applyInitializers;
        • 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
        • 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器****contextPrepared
      • 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
    • **刷新IOC容器。**refreshContext
      • 创建容器中的所有组件(Spring注解)
    • 容器刷新完成后工作?afterRefresh
    • 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
    • **调用所有runners;**callRunners()
      • 获取容器中的 ApplicationRunner
      • 获取容器中的 CommandLineRunner
      • 合并所有runner并且按照@Order进行排序
      • 遍历所有的runner。调用 run 方法
    • 如果以上有异常,
      • 调用Listener 的 failed
    • 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
    • running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed