Springboot

Springboot的概念

spring的优缺点

优点

IOC 和AOP

缺点:

  • 配置繁琐

    最初spring是以xml配置开始的,而且是很对的xml;spring2.5引入了注解开发,这消除了大量针对应用程序自身组件的显示xml配置;spring3.0引入了基于java的配置,这是一种类型安全的可重构的配置方式,可以代替xml。

  • 依赖管理费时费力

    在项目环境搭建的时候,需要引入一些坐标,而且需要分析导入的坐标与其他坐标之间的关系,一旦选错了版本,随之而来的就是不兼容问题。

Springboot解决上述spring产生的问题

起步依赖

起步依赖就是将具备某些功能的坐标打包在一起,并一共一些默认的功能。

自动配置

自动配置指的是会自动将一些配置类的bean注册进IOC容器,我们要用到的地方直接使用@Autowired和@Resource就行。

Springboot项目的扫描包会自动扫描与启动类同级及子包下的内容。

Springboot的热部署

  • 添加 spring-boot-devtools 热部署依赖启动器

     <!--热部署启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
    
  • IDEA工具热部署设置

选择IDEA工具setting->打开Complier面板设置页面。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U6So095h-1593006195492)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592471721490.png)]

在项目任意界面按下Ctrl+shift+Alt+/打开 Maintenance 选项框,选中并打开Registry页面。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DPjHF9z9-1593006195497)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592471858930.png)]

Springboot的配置全局配置文件

application.properties配置文件

需求:将application.properties中的自定义属性值注入到实体类中。

public class Pet {
 private String type;
 private String name;
//省略get set方法

}
@Component //将Person类注入到IOC容器中
@ConfigurationProperties(prefix = "person") //将配置文件中以person开头的属性注入到该类中
public class Person {
     private int id; 
    private String name; 
    private List hobby; 
    private String[] family; 
    private Map map;
    private Pet pet; 
}
#person.id=1
#person.name=tom
#person.hobby=打豆豆,看电视,睡觉
#person.family=father,mother
#person.map.k1=v1
#person.map.k2=v2
#person.pet.type=dog
#person.pet.name=旺财

@ConfigurationProperties(prefix = “person”)注解的作用是将配置文件中以person开头的属性通过set方法注入到Person类中。

@Component 注解是将Person类交给IOC容器,只有交给Spring容器才能被@ConfigurationProperties注入值。

在编写application.properties文件时,由于person对象属性是我们自定义的,所以不会有提示,如果需要有提示,必须在类上加@ConfigurationProperties注解,并导入坐标

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-configuration-processor</artifactId>
     <optional>true</optional>
</dependency>

​ 添加pom后,还需重启项目或者按住Ctrl+F9重构项目。

application.yml配置文件

YAML格式是Springboot支持的一种JSON超集文件格式,是一种相比于properties更为直观且容易被电脑识别的数据序列化格式。

  • YAML文件的扩展名为*.yml或者 *.yaml.
  • application.yml使用“key:(空格) value"格式配置属性,使用缩进控制层级关系。
#对实例类Person属性赋值
person:
 id: 1
 name: lucy
 hobby: [吃饭,睡觉]
 family: [father,mother]
 map: {k1: v1,k2: v2}
 pet: {type: dog,name: 旺财}

配置文件属性值注入

使用Springboot全局配置文件设置属性值:

如果配置属性是Springboot已有属性,比如服务端口server.port,那么Springboot内部会自动扫描并读取这些配置文件中的属性值并覆盖默认属性值。

如果配置的属性是用户自定义的,例如自定义的person实例属性,还必须在程序中注入这些配置属性才能生效。

Springboot支持@value和@Configurationproperties注入属性

1、使用@Configurationproperties注入属性

Spring Boot提供的@ConfigurationProperties注解用来快速、方便将配置文件中的自定义属性值批量注入到某个Bean对象中。

@Component
@ConfigurationProperties(prefix = "person")
public class Person {
 private int id;
 //属性的set方法
 public void setId(int id) {
 this.id = id;
 }
}

2、使用@value注入属性

@Value是spring框架中的注解,用来读取配置文件中的值并逐个注入到Bean对象中去。Springboot从spring中继承了该注解。使用@value注解还可以直接给属性赋值。

@Component
public class Student {
 @Value("${3}")
 private int id;
 @Value("${person.name}")
 private String name; 

 }

自定义配置

使用@PropertySource加载自定义配置文件

对于自定义配置文件的需求,可以使用@PropertySource和@Configuration注解来完成,@PropertySource用于加载自定义的配置文件,@Configuration用于表明当前类是一个配置类。

自定义配置文件test.properties

test.id=110
test.name=test

自定义配置类

@Configuration // 表明配置类
@PropertySource("classpath:test.properties") // 指定配置文件的名称和路径
@EnableConfigurationProperties(MyProperties.class) //开启对应配置类的 属性注入功能
@ConfigurationProperties(prefix = "test") // 指定配置文件属性前缀
public class MyProperties {
 private int id;
 private String name;
 //省略set get方法
 
 }

随机数设置及参数间引用

在springboot配置文件设置属性时,还可以设置随机数。

1、随机数值设置

my.secret=${random.value} // 配置随机数值
my.number=${random.int} // 配置随机int类型数
my.bignumber=${random.long} // 配置随机long泪细数
my.uuid=${random.uuid} // 配置随机UUID类型数
my.number.less.than.ten=${random.int(10)} // 小于10的随机数
my.number.in.range=${random.int[1024,65536]} //1024到65536之间的整数

2、参数引用

app.name=MyApp
app.description=${app.name} is a Spring Boot application

springboot源码分析

自动配置(启动流程)

概念:能够在添加jar包的时候,自动为我们配置一些相关的组件,我们无需配置就能运行项目。

注解@SpringbootApplication源码

@Target({ElementType.TYPE}) //作用于类上
@Retention(RetentionPolicy.RUNTIME) //运行时生效
@Documented //表示注解可以记录在Javadoc中
@Inherited //表示可以被子类继承
@SpringBootConfiguration //标明该类为配置类
@EnableAutoConfiguration // 开启自动配置功能
@ComponentScan( //包扫描器
 excludeFilters = {@Filter(
 type = FilterType.CUSTOM,
 classes = {TypeExcludeFilter.class}
), @Filter(
 type = FilterType.CUSTOM,
 classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}

从@SpringbootApplication源码可以看出,前面四个注解是元注解,后面3个注解才是Springboot的核心注解。

@SpringbootConfiguration注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //配置IOC容器类
public @interface SpringBootConfiguration {
}

从源码可以看出,@SpringbootConfiguration注解中有一个核心注解@Configuration,该注解是spring提供的,表示当前类为一个配置类。

@EnableAutoConfiguration注解

@EnableAutoConfiguration注解表示开启自动配置功能,该注解是Springboot框架的重要注解,也是实现自动化配置的注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage//
@Import({AutoConfigurationImportSelector.class})//自动配置类扫描导入
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@AutoConfigurationPackage注解

自动配置包 , 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) // 导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
}

@Import({Registrar.class}):默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中。

@Import(AutoConfigurationImportSelector.class)

将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector可以帮助Springboot应用将所有符合条件的@configuration配置加载到当前Springboot创建的IOC容器中。

AutoConfigurationImportSelector 类中通过selectImports方法导入组件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZlLjkULD-1593006195499)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592831539360.png)]

深入方法loadMetadata:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJ4qjQcM-1593006195501)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592833789828.png)]

深入getAutoConfigurationEntry方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	    // 1. 判断是否开启注解。如未开启,返回空串
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 2. 获得注解的属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

		// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
		// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
		// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
		// 将这些值作为自动配置类导入到容器中,自动配置类就生效了
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);


		// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
		configurations = removeDuplicates(configurations);
		// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
		// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
		//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
		checkExcludedClasses(configurations, exclusions);
		// 4.2 从 configurations 中,移除所有不希望自动配置的配置类
		configurations.removeAll(exclusions);

		// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类

		//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
		//@ConditionalOnMissingClass : classpath中不存在该类时起效
		//@ConditionalOnBean : DI容器中存在该类型Bean时起效
		//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
		//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
		//@ConditionalOnExpression : SpEL表达式结果为true时
		//@ConditionalOnProperty : 参数设置或者值一致时起效
		//@ConditionalOnResource : 指定的文件存在时起效
		//@ConditionalOnJndi : 指定的JNDI存在时起效
		//@ConditionalOnJava : 指定的Java版本存在时起效
		//@ConditionalOnWebApplication : Web应用环境下起效
		//@ConditionalOnNotWebApplication : 非Web应用环境下起效

		//总结一下判断是否要加载某个类的两种方式:
		//根据spring-autoconfigure-metadata.properties进行判断。
		//要判断@Conditional是否满足
		// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
		configurations = filter(configurations, autoConfigurationMetadata);


		// 6. 将自动配置导入事件通知监听器
		//当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
		// 并触发fireAutoConfigurationImportEvents事件。
		fireAutoConfigurationImportEvents(configurations, exclusions);
		// 7. 创建 AutoConfigurationEntry 对象
		return new AutoConfigurationEntry(configurations, exclusions);
	}

继续点开loadFactory方法:


@EnableAutoConfiguration 注解就是从classpath路径下,找到 META-INF/spring.factories 配置文件, org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射实例化为对应的标注了@Configuration的javaConfig 形式的配置类,并加载到IOC容器中。

总结:

Springboot底层实现自动配置的步骤是:

1、Springboot应用启动。

2、@SpringbootApplication起作用;

3、 @EnableAutoConfiguration;

4、 @AutoConfigurationPackage 组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class) 将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中。

5、 @Import(AutoConfigurationImportSelector.class) :它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类的作用是通过 selectImports 方法执行的过程中,会使用内部工具类 SpringFactoriesLoader 查找classpath下的所有jar包中的 META-INF/spring.factories进行加载,实现将配置类信息交给springFactory加载器进行一系列的容器创建过程。

自定义starter

自定义starter

1、新建maven工程,工程名为xxx-spring-boot-starter,导入以下依赖

<dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-autoconfigure</artifactId>
         <version>2.2.2.RELEASE</version>
     </dependency>
</dependencies>

2、编写javaBean

@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {

    private int id;
    private String name;

    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;
    }

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

3、编写配置类 MyAutoConfiguration

@Configuration
@ConditionalOnClass//条件值:表示当前类路径下有某个类才会进行自动配置
public class MyAutoConfiguration {
    static {
        System.out.println("MyAutoConfiguration init....");
    }

    @Bean//将SimpleBean实例化并添加到IOc容器中
    public SimpleBean simpleBean() {

        return new SimpleBean();
    }
}

4、resources下闯进META-INF/spring.factories

注意:META-INF是自己手动创建的目录,spring.factories也是自己手动创建的文件,在该文件中配置自己写的自动配置类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HGuvnix1-1593006195503)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592896043391.png)]

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lagou.config.MyAutoConfiguration

使用自定义starter

1、导入自定义starter

<dependency>
     <groupId>com.lagou</groupId>
     <artifactId>xxx-spring-boot-starter</artifactId>
     <version>1.0-SNAPSHOT</version>
</dependency>

2、在全局配置文件中配置simple人Bean的属性值

simplebean.id=1
simplebean.name=自定义starter

3、编写测试方法

//测试自定义starter
@Autowired
private SimpleBean simpleBean;

@Test
public void zdyStarterTest(){
     System.out.println(simpleBean);
}

Springboot项目执行原理

每个Springboot项目中都有一个主程序启动类,在主程序启动类中都有一个项目的main方法,在该方法中通过执行SpringApplication.run()方法来启动项目。

run方法源码

public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {
 	return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {
 	return (new SpringApplication(primarySources)).run(args);
}

从run方法源码可以看出,SpringApplication.run()内部执行了两个操作,初始化SpringApplication对象和执行初始化对象的run方法。

初始化SpringApplication对象

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

		this.sources = new LinkedHashSet();
    	//Banner 模式
		this.bannerMode = Mode.CONSOLE;
		this.logStartupInfo = true;
    	//是否添加 JVM 启动参数
		this.addCommandLineProperties = true;
    	//是否添加共享的 ConversionService
		this.addConversionService = true;
    	//是否 AWT headless
		this.headless = true;
    	//是否注册 ShutdownHook 钩子
		this.registerShutdownHook = true;
    	//附加的 profiles 的数组
		this.additionalProfiles = new HashSet();
		this.isCustomEnvironment = false;
    	//资源加载器
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");

		//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

		//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
		this.webApplicationType = WebApplicationType.deduceFromClasspath();

		// 设置初始化器(Initializer),最后会调用这些初始化器
		//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

		// 设置监听器(Listener)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

		// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

从源码看出,SpringApplication初始化主要包括四个部分:

1、this.webApplicationType = WebApplicationType.deduceFromClasspath()

用于判断当前webApplicationType应用的类型,deduceFromClasspath方法用于查看classPath路径下是否存在某个特征类,从而判断当前webApplicationType 是Servlet应用(Spring 5之前的传统MVC应用)还是reactive应用(Spring 5开始出现的WebFlux交互式应用)。

static WebApplicationType deduceFromClasspath() {
        // WebApplicationType.REACTIVE 类型  通过类加载器判断REACTIVE相关的Class是否存在
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) // 判断REACTIVE相关的Class是否存在
				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		// WebApplicationType.NONE 类型
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) { // 不存在 Servlet 的类
				return WebApplicationType.NONE;
			}
		}
		// WebApplicationType.SERVLET 类型。可以这样的判断的原因是,引入 Spring MVC 时,是内嵌的 Web 应用,会引入 Servlet 类。
		return WebApplicationType.SERVLET;
	}

2、setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))

用于springApplication应用的初始化器设置。在初始化过程中会使用spring类加载器 SpringFactoriesLoader 从 META-INF/spring.factories 类路径下的META-INF下的spring.factories文件中获取所有可用的初始化器类ApplicationContextInitializer。

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
        // 加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
		//org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
		// 根据names来进行实例化
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		// 对实例进行排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

3、setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))

用于SpringApplication应用的监听器设置。监听器设置与上一步初始化器设置基本一致,也是从META-INF/spring.factories 类路径下的META-INF下的spring.factories文件中获取所有可用的ApplicationListener类。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
        // 加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
		//org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
		// 根据names来进行实例化
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		// 对实例进行排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

4、this.mainApplicationClass = deduceMainApplicationClass()

用于设置项目main()方法启动的主程序启动类。

项目的初始化启动(run方法)

public ConfigurableApplicationContext run(String... args) {
	    // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		// 初始化应用上下文和异常报告集合
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 配置 headless 属性
		configureHeadlessProperty();


		//   (1)获取并启动监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
		    // 创建  ApplicationArguments 对象 初始化默认应用参数类
			// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

			//(2)项目运行环境Environment的预配置
			// 创建并配置当前SpringBoot应用将要使用的Environment
			// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

			configureIgnoreBeanInfo(environment);
			// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
			Banner printedBanner = printBanner(environment);

			// (3)创建Spring容器
			context = createApplicationContext();
			// 获得异常报告器 SpringBootExceptionReporter 数组
			//这一步的逻辑和实例化初始化器和监听器的一样,
			// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);


			// (4)Spring容器前置处理
			//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);

			// (5):刷新容器
			refreshContext(context);

			// (6):Spring容器后置处理
			//扩展接口,设计模式中的模板方法,默认为空实现。
			// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
			afterRefresh(context, applicationArguments);
			// 停止 StopWatch 统计时长
			stopWatch.stop();
			// 打印 Spring Boot 启动的时长日志。
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			// (7)发出结束执行的事件通知
			listeners.started(context);

			// (8):执行Runners
			//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
			//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
			//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
			callRunners(context, applicationArguments);
		} catch (Throwable ex) {
		    // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

        //   (9)发布应用上下文就绪事件
		//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
		// 这样整个Spring Boot项目就正式启动完成了。
		try {
			listeners.running(context);
		} catch (Throwable ex) {
            // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
            handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		 //返回容器
		return context;
	}

从源码可以看出,项目的启动主要包括以下几个部分:

1、获取并启动监听器

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		// 这里仍然利用了getSpringFactoriesInstances方法来获取实例,
		// 从META-INF/spring.factories中读取Key为org.springframework.boot.SpringApplicationRunListener的Values
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}

2、项目运行环境的配置

// 创建并配置当前SpringBoot应用将要使用的Environment
// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);

prepareEnvironment(listeners,applicationArguments)方法主要用于对项目环境的预设值,同时通过configureIgnoreBeanInfo(environment)方法排除一些不需要配置的环境。

/*
	加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、
	servletContextInitParams、systemProperties、sytemEnvironment、random、
	application.yml(.yaml/.xml/.properties)等;初始化日志系统。
	 */
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {

		//获取或创建环境(存在就直接返回,不存在创建一个再返回)
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//配置环境:配置PropertySources和active Profiles
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。
		listeners.environmentPrepared(environment);
		//将环境绑定到SpringApplication
		bindToSpringApplication(environment);
		//如果非web环境,将环境转换成StandardEnvironment
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		// 配置PropertySources对它自己的递归依赖
		// 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中。
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

3、创建Spring容器

// (3)创建Spring容器
			context = createApplicationContext();
			// 获得异常报告器 SpringBootExceptionReporter 数组
			//这一步的逻辑和实例化初始化器和监听器的一样,
			// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

根据 webApplicationType 判断当前应用类型,确定容器类型,如果该容器为servlet类型,会通过反射装载对应的字节码,也就是 AnnotationConfigServletWebServerApplicationContext ,接着使用之前初始化设置的context(上下文环境)、environment(项目运行环境)、listeners(运行监听器)、 applicationArguments(项目参数)、 printedBanner (项目图标)进行上下文的组装配置,并刷新配置。

protected ConfigurableApplicationContext createApplicationContext() {
	    // 根据 webApplicationType 类型,获得 ApplicationContext 类型
		// 这里创建容器的类型 还是根据webApplicationType进行判断的,
		// 该类型为SERVLET类型,所以会通过反射装载对应的字节码,
		// 也就是AnnotationConfigServletWebServerApplicationContext

		// 先判断有没有指定的实现类
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {

				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			} catch (ClassNotFoundException ex) {
				throw new IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex);
			}
		}
		// 创建 ApplicationContext 对象
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

4、spring容器的前置处理

这一步主要是在容器刷新之前的准备动作,设置容器环境,包括各种变量等等,其中一个非常重要的操作就是将启动类注入到容器中,为后续自动化配置奠定基础。

	// (4)Spring容器前置处理
			//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
prepareContext(context, environment, listeners, applicationArguments,printedBanner);

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		//设置容器环境,包括各种变量
	    context.setEnvironment(environment);

		//设置上下文的 bean 生成器和资源加载器
		postProcessApplicationContext(context);

		//执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
		applyInitializers(context);

		//触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
		listeners.contextPrepared(context);

		//记录启动日志
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		//注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		// Load the sources
		// 加载所有资源
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		//加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础
		load(context, sources.toArray(new Object[0]));

		//触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
        listeners.contextLoaded(context);

		//这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等
	}

5、刷新容器

开启刷新容器,通过refresh方法对整个IOC容器的初始化(包括bean资源定位,解析、注册等),同时向jvm运行时注册一个关机钩子方法,在JVM关机时会关闭整个上下文。

	// (5):刷新容器
refreshContext(context);
private void refreshContext(ConfigurableApplicationContext context) {
	    // 开启(刷新)Spring 容器,通过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等)
		refresh(context);
		// 注册 ShutdownHook 钩子
		if (this.registerShutdownHook) {
			try {
				//向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭
				context.registerShutdownHook();
			} catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

6、spring容器后置处理

扩展接口,设计模式中的模板方法默认为空实现。如果有自定义需求,可以重写该方法。

// (6):Spring容器后置处理
			//扩展接口,设计模式中的模板方法,默认为空实现。
			// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
			afterRefresh(context, applicationArguments);

7、发出结束执行的事件

获取 EventPublishingRunListener 监听器,并执行其starter方法,并且将创建的容器传递进去,创建一个 ApplicationStartedEvent 事件,并执行 ConfigurableApplicationContext 的 publishEvent 方法。

8、执行runners

用于调用项目中自定义的XXXRunner类,使得项目在启动后执行某些特定的程序。其中Springboot提供的执行器有 ApplicationRunner 和 CommandLineRunner 两种。在使用时只需自定义一个执行器类并实现其中的一个借口重写对应的run方法。然后Springboot项目启动后会执行这些特定程序。

	// (8):执行Runners
			//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
			//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
			//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
			callRunners(context, applicationArguments);
private void callRunners(ApplicationContext context, ApplicationArguments args) {
	    // 获得所有 Runner 们
		List<Object> runners = new ArrayList<>();
		// 获得所有 ApplicationRunner 实现类
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		// 获得所有 CommandLineRunner 实现类
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		// 排序 runners
		AnnotationAwareOrderComparator.sort(runners);
		// 遍历 Runner 数组,执行逻辑
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

Springboot项目执行流程图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZzQ9Ielt-1593006195505)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592904202977.png)]

Springboot数据访问

springdata是spring提供的一个用于简化数据库访问、支持云服务的开源框架,它包含了大量的关系型数据库和菲关系型数据库的数据访问解决方案。Springboot默认采用整合SpringData的方式统一处理数据访问层,通过添加大量自动配置,引入各种数据模板xxxTemplate以及统一的Repository接口,从而达到简化数据访问的操作。

spring Data提供了多种数据库支持。

名称描述
spring-boot-starter-data-jpa使用Spring data JPA和hibernate
spring-boot-starter-datamongodb使用MongoDB和Spring Data MOngoDB
spring-boot-starter-data-neo4j使用Neo4j图数据库和Spring Data Neo4j
spring-boot-starter-data-redis使用Redis键值数据存储与Spring Data Redis和Redis客户端

除此之外还有一些框架,spring没有做管理,但是这些框架开发团队多了响应的整合,如mybatis。

Springboot整合Redis

1、添加依赖

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

2、编写实体类

@RedisHash(value = "persons") //指定实体类对象在redis中的存储空间
public class Person {

    @Id // 用来标识实体类主键  字符串形式的hashkey标识唯一的实体类对象id
    private String id;
    @Indexed // 用来标识对应属性在redis中生成二级索引
    private String firstname;
    @Indexed
    private String lastname;
    private Address address;
    //省略set方法
    
}

public class Address {

    @Indexed
    private String city; //城市
    @Indexed
    private String country; //国家

	//省略set方法
}

讲解:

  • @RedisHash(value = “persons”):用于指定实体类在redis中的存储空间。
  • @Id:用于标识实体类主键,在redis中会默认生成字符串形式的HashKey标识唯一实体对象ID。
  • @Indexed :用于标识对应属性在redis中生成二级索引。

3、编写Repository接口,Springboot针对包括redis在内的一些常用的数据库提供了自动化配置,可以通过实现Repository接口简化对数据库中的增删改查操作。


public interface PersonRepository extends CrudRepository<Person,String> {
    // 根据城市信息查询对应的人
    List<Person> findByAddress_City(String name);
}

说明:

​ 在操作redis数据库时编写Repository接口时需要实现CrudRepository接口,而不是实现JPARepository接口。因为JPARepository是springboot整合JPA时特有的接口。

4、配置文件

#配置redis的连接配置

#redis服务器地址
spring.redis.host=127.0.0.1
#redis服务器连接端口
spring.redis.port=6379
#redis服务器连接密码
spring.redis.password=

SpringBoot模板技术

Thymeleaf

Thymeleaf 是一种 现代的基于服务器端的java模板技术,也是一个优秀的面向java的xml、XTML、HTML5页面模板,它具有丰富的标签、函数、和表达式。

Thymeleaf 语法

th:标签说明实例
th:id替换id<input th:id=" ${user.id}"/>
th:text文本替换<p th:text="${user.name}">username
th:utext支持html的文本替换<p th:utext="${htmlcontent}">content
th:value属性赋值<input th:value = “${user.name}” />
th:object替换对象<div th:object="${user}">
th:with变量赋值运算<div th:with=“isEvens = ${prodStat.count}%2 == 0”>
th:style 设置样式 <div th:style="‘display:’ + @{(${sitrue} ? ‘none’ : ‘inline- block’)} + ‘’"> th:onclick 点击事件 <td th:onclick = “‘login()’”> th:each 属性赋值 <tr th:each = “user,userStat:${users}”> th:if 判断条件 <a th:if = “${userId == collect.userId}”> th:unless 和th:if判断相反 <a th:href="@{/login} th:unless=${session.user != null}">Login th:href 链接地址 <a th:href="@{/login}" th:unless=${session.user != null}>Login th:switch 多路选择配合th:case使用 <div th:switch="${user.role}">

User is an administrator

th:fragment th:switch的一个分支 th:includ 布局标签,替换内容到引入的文件 <head th:include=“layout :: htmlhead” th:with=“title=‘xx’”> th:replace 布局标签,替换整个标签到引入的文件 <div th:replace=“fragments/header :: title”></div> th:selectd selected选择框选中 th:selected="(${xxx.id} == ${configObj.dd})" th:src 图片类地址引入 本地图片<img class=“img-responsive” alt=“App Logo” th:src="@{/img/logo.png}" />
获取参数<img th:src="@{${data.img}}" / th:inline 定义js脚本可以使用变量 <script type=“text/javascript” th:inline=“javascript”> th:action 表单提交的地址 <form action=“login.html” th:action="@{/login}"> th:remove 删除某个属性 <tr th:remove=“all”> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。 th:attr 设置标签属性,多个属性可以用逗号分隔 比如 th:attr=“src=@{/image/aa.jpg},title=#{logo}”,此标签不太优雅,一般用的比较少。

标准表达式

Thymeleaf 模板引入很多的标准表达式

说明表达式
变量表达式${…}
选择变量表达式*{…}
消息表达式#{…}
链接URl表达式@{…}
片段表达式~{…}

1、变量表达式${…}

变量表达式主要用于获取上下文中变量的值

<p th:text="${title}">这是标题</p>

2、选择变量表达式{…}*

选择变量表达式和变量表达式用法类似,一般用于选定对象而不是上下文中获取属性值,如果没有选定对象,则和变量表达式一样。

<div th:object="${book}">
 <p>titile: <span th:text="*{title}">标题</span>.</p>
</div>

*{title}表示获取当前指定对象book中的title值。

3、消息表达式#{…}

消息表达式#{…}主要用于 Thymeleaf 模板页面国际化内容的动态替换和展示,使用消息表达式进行页面国际化设置时,还需要配置一些国际化配置文件。

4、链接URL表达式@{…}

链接URL表达式@{…}一般用于页面跳转和资源的引入,在web开发中很重要。

<a th:href="@{http://localhost:8080/order/details(orderId=${o.id})}">view</a>
<a th:href="@{/order/details(orderId=${o.id})}">view</a>

再有的表达式中需要按照@{路径(参数名称=参数值,参数名称=参数值)}的形式编写,同时该参数值可以用变量表达式来传递动态的参数。

5、片段表达式~{…}

片段表达式~{…}**用来标记一个模板,并根据需要移动或传到模板。其中最常用的用法是使th:insert或th:repace属性插入片段:

<div th:insert="~{thymeleafDemo::title}"></div>

隔 | 比如 th:attr=“src=@{/image/aa.jpg},title=#{logo}”,此标签不太优雅,一般用的比较少。 |

标准表达式

Thymeleaf 模板引入很多的标准表达式

说明表达式
变量表达式${…}
选择变量表达式*{…}
消息表达式#{…}
链接URl表达式@{…}
片段表达式~{…}

1、变量表达式${…}

变量表达式主要用于获取上下文中变量的值

<p th:text="${title}">这是标题</p>

2、选择变量表达式{…}*

选择变量表达式和变量表达式用法类似,一般用于选定对象而不是上下文中获取属性值,如果没有选定对象,则和变量表达式一样。

<div th:object="${book}">
 <p>titile: <span th:text="*{title}">标题</span>.</p>
</div>

*{title}表示获取当前指定对象book中的title值。

3、消息表达式#{…}

消息表达式#{…}主要用于 Thymeleaf 模板页面国际化内容的动态替换和展示,使用消息表达式进行页面国际化设置时,还需要配置一些国际化配置文件。

4、链接URL表达式@{…}

链接URL表达式@{…}一般用于页面跳转和资源的引入,在web开发中很重要。

<a th:href="@{http://localhost:8080/order/details(orderId=${o.id})}">view</a>
<a th:href="@{/order/details(orderId=${o.id})}">view</a>

再有的表达式中需要按照@{路径(参数名称=参数值,参数名称=参数值)}的形式编写,同时该参数值可以用变量表达式来传递动态的参数。

5、片段表达式~{…}

片段表达式~{…}**用来标记一个模板,并根据需要移动或传到模板。其中最常用的用法是使th:insert或th:repace属性插入片段:

<div th:insert="~{thymeleafDemo::title}"></div>

上述代码中,使用th:insert属性将title片段模板引用到改标签中。 thymeleafDemo 为模板名称, Thymeleaf 会自动在 “/resources/templates/” 目录下查找 thymeleafDemo模板,title为片段名称。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值