spring boot 原理和代码分析

spring 能干什么

​ 来spring官网看看,我发现我所学习的只是它的冰山一角。

微服务

​ 支持模块化开发,把应用拆分成一个一个模块,好处其实有很多,我列举一些我自己能想到吧。

​ 首先是解耦合,不至于每个模块的耦合很大,而且很方便程序员去设计数据库,因为需要考虑的东西确实是少了。

​ 单从解耦合这个角度来看,我的各个模块可以独立部署,互不干扰,我测试或者更新其中一部分,对其他功能是没有任何影响的,这是单体应用无论如何都不可能做到的。

​ 这个不得不说DDD,我觉得它里面有一个很好的思想,就是领域划分,如果我进行了领域划分,那么我在微服务拆分的时候,就会很方便去拆分。

​ 在微服务场景下,肯定会产生很多问题,这个就需要我们有一种好的架构,能让它在单体成长的过程中,就有了微服务拆分的意识,当单体成长到一定的水平,就能很容易的划分开。所以,我得先学习微服务,然后在使用DDD的领域划分,我得在单体中去思考,拆分之后,这个要如何编写代码。

响应式

​ 可以用更少的线程,更少的资源来实现跟高的并发,我以前只是简单的了解过,没想到它这里有这样的好处,打算尝试一下。

Serverless

​ 没想到spring家族竟然支持这个,我一直以为这个是一个很新的技术,值得去了解一下,毕竟很多小功能函数没有必要设计一个web应用来使用

spring boot 2.x

​ 基于java8 提供了接口的默认实现,在以前,比如我一个借口有5个函数,A类需要实现1,2函数,B类需要实现4,5函数,但是我在实现接口的时候,不得不实现其他必要的空接口,所以用一个java类来做适配,来实现空方法,但是现在不需要中间这层类了,但其实我觉得加一个类也挺好,毕竟抽象类的功能比接口要强大,但可惜java不支持多继承。

优点

  • 内嵌web服务器
    • 这个比tomcat放war包好多了,通过jar独立启动,让端口号各不相同,而且自定义jvm配置很方便,而且还能和pm2这种线程管理工具结合,确实太方便了
  • 自动starter依赖,简化构建配置,自动配置Spring以及第三方功能
    • 最喜欢的功能,没有之一,因为我个人觉得,配置这种东西本身就是让用户来定制需求的,也就是说,我们的应用要有能力,自动判断出用户的需求,约定大于配置,自动判断是我的设计理念
  • 提供生产级别的监控、健康检查及外部化配置
    • 这个我感觉很有用,我在项目上线的时候,才意识到,这个真的很重要,目前没有用过
  • 无代码生成、无需编写XML
    • 这个是简单配置最适合的地方了,我以前也分析过,面对不同的配置需求,不同的配置方法各有各自的优点,但是我也可以结合起来使用,使收益最大化,这个时间本身就不是什么非此即彼的世界

官方文档

​ 看了一下,还是有一些东西的,打算学完后,梳理一下官方文档

​ 还有看更新,没有什么比看官方文档更能快速并且准确的了解新的特性了,不过我一般是先看博客,因为他们会把重要的更新点摘出来,然后再看看官方,最后可以去看源码,没有什么比源码更加详细了,我指的是源码的文档。

yaml

​ 字符串不需要加’’,“”,加的话是为了防止转义,比如on,off会变成ture或者false

使用细节

maven

​ 版本号放在属性里面管理,说一下这样做的原因,我经常改的是依赖的版本,而依赖一般是不会切换的,因为切换依赖会涉及到大量代码的改动,然后是有的依赖需要版本的统一,它们共用一个版本

​ maven爆红的问题,这个一般是网络的问题,如果maven下载失败的话。

1. 用镜像
2. 删除下载到一半到失败文件
3. spring boot parent 爆红,可以重新创建一个spring boot版本最好和这个一样,然后尽量勾选有的依赖,然后你会发现这些都不爆红了,如果没有对应的版本,就选一个差不多的,比较个人使用其实区别并不大
4. 依赖项目爆红,如果有的不爆红,有的爆红,而且爆红的还是那种没有版本号的依赖,很简单,直接设置版本号,让maven根据这个下载就行

运行时参数

​ 这个实现原理其实简单,就是我们平时项目启动的时候,传递到main方法里面的args参数。

组件管理

ConfigurableApplicationContext run = SpringApplication.run(HearuApplication.class, args);
String[] beanDefinitionNames = run.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    log.info(beanDefinitionName);
}

​ 这个就是我们平时最容易忽视的spring boot启动类,它是有返回值的,通过这个返回值,我们可以获取到所有的bean。run.getBean(DispatcherServlet.class);可以获取到bean里面的实例。

​ run里面的方法是很丰富的,肯定能支持你的所有对组件的要求,它不止是接口定义的那些,因为接口继承了好多,所以是好多接口方法的集合

创建新的类(包括它的包)

​ 我们用idea创建一个类的时候,如果输入的是utils.WebUtil,那idea会自动帮助我们生成utils包,所以idea还是极其强大的,我们对它的功能了解的越多,意味着我们越能发挥出更大的能力

attribution

​ 原来spring自己用attrbution的时候,用的都是以类名为key,这样就可以保证很用户自己定义的类不冲突

自动配置

依赖管理

​ 原理是maven的spring-boot-starter-parent,这个里面主要负责了build构建相关的,然后是spring-boot-dependencies,这个里面维护了大量的依赖和版本管理,到此版本管理解决了。那复杂的依赖关系怎么做?

​ 你随便点进一个start,你就会发现这个里面维护了相关的依赖,而且还有版本,而且都以spring-boot-starter这个依赖为核心

自动装配

@SpringBootApplication

​ 效果和下面三个结合起来是一样的,我突然就想到spring那个工具类了,因为我在想,如果我把注解标注在注解上,是扫码不到的,为什么spring中有这么多标注给注解的注解呢,它什么是怎么起作用的呢,原理仅仅是一个工具类,这个工具类在获取注解的时候,可以获取注解的注解。而且如果你看了AnnotationUtils.findAnnotation的源码,就会值得,它不止会扫描注解的注解还会扫描接口和超类的注解,当然你不用担心效率,比较这个注解是我们自己标注的。

​ spring的工具类还是非常有用的,可以多看看源码学习一下,这样可以让自己少创造很多轮子,而且你能感受到他们对反射的加强,而且能够增强你代码的严谨性,毕竟我从来不会怀疑是spring的错,就像不会怀疑java一样

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

@Configuration

​ 如果我们想配置bean,比如我们配置redis的bean,让它有string,object和各种序列化的能力,我们就可以重新配置一个。

​ 或者,我们像mybatis plus的分页插件,需要额外配置,因为有的时候,可能并不需要开启这个功能,但是我个人感觉,这个应该作为默认实现。倒是像字段填充那种,才真正的需要额外的配置bean,而且这样比配置一个类方便的多了,但是mybatis plus采取了一种更合适的方法,就是利用接口。

​ 还有一种很方便的就是组件的覆盖,比如objectmapper,我们加一个@Primary注解,来确保代替原来的组件,这个定制化增强它的功能,也就是说。

​ 也就是说,如果你想增强一个组件或者定制化一个组件,就用@bean的方法,定制化的意思就是,这个对象比较常用,而且创建比较复杂,创建对象本身就是ioc的意义所在

​ 但是你要注意几点,就是标注之后会被spring代理,成为一个代理对象,当你在执行对象方法的时候,已经不是原来对象的方法了,而是已经被增强过的方法。比如你通过这个bean获取的对象总是同一个,当然你可以通过修改注解的属性让spring不代理它。

​ @bean注解的方法是组件的id,然后返回的类型就是我们注入的类型,当然可以通过bean的属性来定义我们的id

Full模式与Lite模式

​ 配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
​ 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式

​ 依赖关系指的是组件之间会产生关系,一般不需要改变配置,因为产生依赖关系太正常了

@Import

​ 默认以全类名为id,然后把这个类注册到组件里面

@Conditional

​ 条件装配,按条件决定是否装配指定的组件

@ImportResoure

​ 支持导入xml等其他资源文件,这里区别于配置

@ConfigurationProperties

​ 这个必须配合@Component使用!而且我发现你得运行一下spring boot才会有提示,这下我最觉得不方便的问题就解决了,而且我现在学会了注入静态属性,这样可以定义很多东西了。也许你会问,为什么需要@Componet呢,因为有的时候,你并不一定需要把这些配置注入进去,所以还有另外一种方法开启这个注解的功能@EnableConfigurationProperties,就像是@EnableXXX一样

​ 而你这个类名最好是XxxProperties,和注册bean的XxxConfiguration区别一下

源码分析

@EnableAutoConfiguration

​ 自动配置的核心

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})

@AutoConfigurationPackage

@Import(AutoConfigurationPackages.Registrar.class)

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            //AnnotationMetadata是注解的元信息,我们可以看到这个注解标注在了哪个类上面
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
            //new PackageImports(metadata).getPackageNames()获取到了我们的包名,说明它是为了注册我们在包内定义的所有注解,所以说,不要把java类写在启动类的包外边
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

利用上面这个类,注册我们自己定义的一系列组件

AutoConfigurationImportSelector

	@Override
	//选择要导入哪些组件
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);//明显这个是核心方法
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

​ 怎么学习这个源码呢,最方便的方法就是,自己打断点看,但前提你得了解足够多的外部使用,当然,你连使用都不了解的话,学习源码就是一个笑话。

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//明显关键代码是这个
		configurations = removeDuplicates(configurations);//移除重复
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);//移除排除
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {//获取所有的可能的bean,然后再进行排除
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

​ 我们直接来到核心方法,真的,看哪个方法是核心很容易,一般特点是获取这个结果后直接返回,或者对这个结果做一下处理后返回,说实在的,一个方法的目的不就是为了得到返回的结果吗,那我关注的肯定是这个结果。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
    	//从缓存里面得到,你会发现是一个List<String>明显是为了支持不止一个bean,而且也能说明,有很多核心的类比如spring自己的组件,已经被提前加载了
		if (result != null) {
			return result;
		}
		//下面就是没有缓存初次加载的情况
		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);//"META-INF/spring.factories"
            //这个常量就是保存我们在这个文件下配置的所有类,这样就可以在spring boot外部启动,这个就是原理,你知道使用方法,一看原理的时候,很容易就懂了
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();//jar:file:/D:/java/maven/repository/org/springframework/spring-beans/5.3.15/spring-beans-5.3.15.jar!/META-INF/spring.factories
                //不理解源码,还不理解例子吗,这就是debug的好处,能加强和方便我们看源码的能力,而且可以随处计算内存中的值,实在是太方便了,我以后尽量用debug看源码
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

​ 主要是为了扫描spring-boot-autoconfigure-2.6.3.jar这个包下的spring.factories,它会扫描出所有的配置类,然后按需加载组件,所以,启动时间才会那么漫长

​ 除此之外,你可以多学习学习org.springframework.boot.autoconfigure包下的自动配置是怎么写的,比如我看到了@Conditional可以标注一个类,然后类继承SpringBootCondition,进行复杂的代码级别的判断

​ 那最常见的一个功能,就是如果你用户配置了,我就不配置,如果你用户配置了,我就使用你配置的,这种是怎么实现的呢?@ConditionalOnMissingBean。而且bean是可以传递参数的,参数自动从组件里面获取,然后赋值,就像@Autowire注入静态变量一样

​ 小技巧,yaml开启debug=true可以查看自动配置的情况,甚至告诉你为什么这个配置没有生效

静态资源配置

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

@ConditionalOnProperty

​ prefix 前缀 用xx.xxx.xx分割指定

​ name 这个才是真正的配置属性

​ matchIfMissing = true 如果没有在yaml中配置,就按你配置了算

WebMvcAutoConfigurationAdapter

​ 如果一个类上面有@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }),明显是在说明,这个两个配置文件的信息就是为了这个配置类准备的

​ 而且这个配置类只有一个有参构造器,说明所有的参数都从容器或获取,如果我们在spring中看到其他这样的写法,也说明是从容器中获取的。

缓存时间

​ spring boot 的静态资源也是支持缓存的,原理是通过配置请求头来实现的。但其实不推荐这样做,用nginx也可以实现。而且看官方的配置文档,或者使用demo也可以获取很多使用的方法。

其他

​ 我们写的代码,可以进行配置的,要放到配置类里面,宁可多加一些判断,也不能写死,写死是最不好的方法了

Restful

​ 我个人觉得不好,这种只是适合简单的后台操作,毕竟我们对一个资源怎么可能才只有四种简单的操作呢,spring boot默认是没有开启的,需要我们在yaml配置。前端传递参数用post,然后提供参数_method=PUT/Delete等,当然这种是表单传递参数,如果通过ajax工具,我感觉是不需要开启这个的,毕竟从源码层面看确实是这样的。

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

请求处理

​ 这部分内容是对以前做的回顾和补充,不会太全面

try {
				processedRequest = checkMultipart(request);//检查是否为文件上传
				multipartRequestParsed = (processedRequest != request);

				// 获取处理器,一般来说,就通过请求路径决定用哪个controller的方法
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 获取一个要执行方法的类,因为执行方法比较复杂,要解析注解标注的参数
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// get和head结合浏览器缓存处理
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;//你看这个就直接返回了,不处理
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 实际执行方法
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

RequestMappingHandlerMapping

​ 本质上就是先决定用哪个controller去处理请求,设计思路是有多个Handlermapping,然后其中有一个是RequestMappingHandlerMapping,如果我们在Controller上面标注了它,我相信,它在扫描的时候,就会扫码到我们的controller注解,然后解析里面的方法,把他们都加到一个mappingRegistry里面,然后先根据路径筛选,然后是通过请求方法筛选,如果发现有多个,就会报错,这我也经历过。所以,我发现spring匹配的大部分思路都是,先获取全部,然后筛选有可能的,最后才是精确的筛选,这样的编码实现明显会简单很多,而且很多都是交给不同的类去处理的,这样就更简单了,而且简单有一个最大的好处就是,不容易出错。看起来代码多,其实这样代码都是化简使用的。

​ 而且这个接口也设计的特别好,能够让我们自定义,比如接口有常见的V1,V2这种,如果我们一个controller里面写那么多v1,v2难免会很麻烦,不如自己自定义到不同的类执行

​ 为什么需要Handler?

​ 因为我们处理的不止有一种请求,还要处理静态资源请求,这个思想在tomcat中就体现过。

RequestMappingHandlerAdapter

invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod)

​ 这个方法的定义很明显,是想把request里面的参数绑定在方法里面,而且spring对原始的method做了加强,这样的话,可以做很多自己想做的事情,真的是对java反射的史诗级加强啊。

			if (this.argumentResolvers != null) {//参数解析
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {//返回值处理
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}

参数解析

public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter parameter);//是否支持解析这种参数

    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;//具体解析规则
}

InvocableHandlerMethod

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
    	//获取方法的参数,这个是spring包装的一个类,这个类里面保存了方法的所有参数
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
            //能看到参数的所有信息,包括参数的名称,参数的注解信息
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
            //判断是否能找到可以处理它的解析器
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                //这个的实现是遍历判断,如果成功了就加入缓存,所以spring mvc执行的会越来越快,而且spring里面很多都是有缓存的,缓存其实一点也不能,难的是并发场景下的缓存
			}
			try {
                //真正开始解析参数
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}

​ 具体的解析过程就是解析器的事情了,其实原理也不难,就是request里面获取各种各样的值,然后保存到request的attr里面,然后获取这个值,获取后放到arg数组里面,然后给传递给方法执行就可以了。那么我看几个关键注解的处理方法!

AbstractNamedValueMethodArgumentResolver

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
		MethodParameter nestedParameter = parameter.nestedIfOptional();

		Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
		if (resolvedName == null) {
			throw new IllegalArgumentException(
					"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
		}

		Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
		if (arg == null) {//如果解析完,发现是空的
			if (namedValueInfo.defaultValue != null) {//如果defaultValue不是空的
				arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
                //设置默认值
                //这种情况就是我们不传递参数
			}//当默认值就是空,而且还需要这个参数,而且参数还不是Optional类型
			else if (namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);//肯定是抛出异常
			}
			arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());//这个处理空值处理处理boolen值以外,其他都会给你抛出异常
		}
    	//所以给一个结论,defaultValue的意义就是,前端没有传递,我把这个值设置为defalutValue,和required基本没有什么关系,然后就是required了,true的话,你没有传递,而且没有默认值会报错,false的话,就不管了,但你要在代码层面判断一下它为null,我觉得这个方法比我设置一个特殊的defalutValue好,这就是看源码的收获!!!
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            //这种情况就是告诉你,如果参数是空字符串,而且配置了默认值,我就会给你设置默认值,这个和我之前做的实验是一样的,不过看源码一下就能看出来,不然还要尝试特别多的组合
			arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
		}

		if (binderFactory != null) {//这个是对参数的特殊处理
			WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
			try {
				arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);//如果我们要对参数做特殊处理的话,就是比如日期类型的转换,我发现LocalDate用这个更好些,这个就是学习源码的意义
			}
			catch (ConversionNotSupportedException ex) {
				throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			catch (TypeMismatchException ex) {
				throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			// Check for null value after conversion of incoming argument value
			if (arg == null && namedValueInfo.defaultValue == null &&
					namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
			}
		}

		handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

		return arg;
	}

BeanUtils

​ 无意间发现了spring boot的一个工具类,这个工具类竟然是一个抽象类,我一想,这样也可以啊,毕竟都是静态方法,而且不能直接创建对象,比用一个private修饰的构造器好多了,我以后也用这种方法弄工具类

HttpMessageConverter

​ 这个也是spring boot用来转换的接口,被我在看requestBody源码的时候发现了

Model

​ 虽然不是很常用,但是这些原理都是在BindingAwareModelMap一个单例中获取,而且数据传递的原理就是放在了request里面的attribution里面,比如转发请求就可以用这个来传递数据,不过在微服务场景下,消息队列明显更合适一些。

ModelAttributeMethodProcessor

	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		String name = ModelFactory.getNameForParameter(parameter);
		//删掉的这部分是ModelAttribute注解,因为不常用
		else {
			try {
                //这个就是创建出了一个空的对象,准备给它赋值
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				else {
					attribute = ex.getTarget();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {//自定义的属性,没有标注注解,准备解析它
			// Bean property binding and validation;绑定和校验
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
                    //核心绑定方法
					bindRequestParameters(binder, webRequest);//这个就是利用我刚刚发现的转换器进行转换
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

自定义converter

@Component
public class StringToPOJOConverter implements Converter<String, POJO> {
 
    @Override
    public POJO convert(String source) {
        POJO pojo = new POJO();
        if(StringUtils.hasText(source)){
            String[] split = source.split(",");
            if(split.length!=2){
                throw new IllegalArgumentException("格式为name,age");
            }
            pojo.setName(split[0]);
            try {
                pojo.setAge(Integer.valueOf(split[1]));
            }catch (Exception e){
                throw new IllegalArgumentException("年龄必须是整数");
            }
            return pojo;
        }
        throw new IllegalArgumentException("字符串不能为空");
    }
}

响应处理

基本原理

​ 和请求处理的类似,在处理方法之前,和确定参数赋值器的同一个函数,就确定了用哪个返回值处理器。利用的接口是HandlerMethodReturnValueHandler

RequestResponseBodyMethodProcessor

​ 这个就是我们最常用的返回处理器,我在想,是不是自己写一个接口,能把这个变成RPC接口呢?

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {//如果返回的是字符串类型
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {//一般都是我们的包装类型
			body = value;
			valueType = getReturnValueType(body, returnType);
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		if (isResourceType(value, returnType)) {//判断是否为流数据
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
					outputMessage.getServletResponse().getStatus() == 200) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					body = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = body.getClass();
					targetType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}

    	//下面是用来确定返回类型的,有的时候,浏览器想要json,有的时候想要xml,我们都需要确定这些内容
		MediaType selectedMediaType = null;
		MediaType contentType = outputMessage.getHeaders().getContentType();
		boolean isContentTypePreset = contentType != null && contentType.isConcrete();
		if (isContentTypePreset) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
			selectedMediaType = contentType;
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> acceptableTypes;
			try {
				acceptableTypes = getAcceptableMediaTypes(request);
			}
			catch (HttpMediaTypeNotAcceptableException ex) {
				int series = outputMessage.getServletResponse().getStatus() / 100;
				if (body == null || series == 4 || series == 5) {
					if (logger.isDebugEnabled()) {
						logger.debug("Ignoring error response content (if any). " + ex);
					}
					return;
				}
				throw ex;
			}
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			List<MediaType> mediaTypesToUse = new ArrayList<>();
            //下面就是遍历自己能转换的和浏览器想要的,找一个最合适的
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (body != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				if (logger.isDebugEnabled()) {
					logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
				}
				return;
			}

			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);//看名字就知道,是在决定权重

			for (MediaType mediaType : mediaTypesToUse) {
				if (mediaType.isConcrete()) {
					selectedMediaType = mediaType;
					break;
				}
				else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
					selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
					break;
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Using '" + selectedMediaType + "', given " +
						acceptableTypes + " and supported " + producibleTypes);
			}
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
            //看看我们的消息转化器,按照优先级排序,能处理了哪一个
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					else {
						if (logger.isDebugEnabled()) {
							logger.debug("Nothing to write: null body");
						}
					}
					return;
				}
			}
		}

		if (body != null) {
			Set<MediaType> producibleMediaTypes =
					(Set<MediaType>) inputMessage.getServletRequest()
							.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

			if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
				throw new HttpMessageNotWritableException(
						"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
			}
			throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
		}
	}

HttpMessageConverter

public interface HttpMessageConverter<T> {

	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();

	default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
		return (canRead(clazz, null) || canWrite(clazz, null) ?
				getSupportedMediaTypes() : Collections.emptyList());
	}

	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

​ 读写兼备,然其实就把对象转换成json,并且对response做了对应的处理

自定义

​ 这个不错,可以弄一个返回excel的协议。

public class MyMessageConverter implements HttpMessageConverter<ExcelFile> {
 
    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }
    
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(ExcelFile.class);
    }
   
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/excel");
    }
 
    @Override
    public List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
        return HttpMessageConverter.super.getSupportedMediaTypes(clazz);
    }
 
    @Override
    public ExcelFile read(Class<? extends ExcelFile> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }
 
    @Override
    public void write(ExcelFile file, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        OutputStream body = outputMessage.getBody();
        body.write(file.getBytes());
    }
}
@Configuration
public class MyMessageConverterConfig {
 
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new MyMessageConverter());
            }
        };
 
    }
}

JUnit5 单元测试

​ spring boot 2.4以后,junit以前的一些测试不能使用了,当然你可以自己导入依赖配置上,这个就是为什么最新版的spring boot使用单元测试会出现错误

官网的示例也是非常多,而且通俗易懂的,可以去看看

注解

@Transactional

​ 标注这个会自动回滚事务,非常适合我们做测试,这个真的很重要!

@DisplayName

​ 可以给我们的测试方法和测试类起一个名称,方便我们去看测试的结果

@beforeEach 和 @afterEach

​ 和aop是一样的,在每个测试方法的前后执行,会执行多次,有多少个测试执行多少次

@beforeAll 和 @afterAll

​ 在所有测试方法的前后执行,只会执行一次,所以要设置成静态方法static

@Timeout

​ 可以进行超时时间的测试,规定这个方法必须在多少单位时间内执行完成

@Disabled

​ 禁用这个测试

@RepeatedTest

​ 重复测试,设置重复次数,然后测试,比如我们要随机测试数据,当然希望测试的次数多一些,这样随机出来的值也多

@ParameterizedTest

​ 可以通过ValueSource来为方法注入参数

断言

导包

​ import static org.junit.jupiter.api.Assertions.*;

​ 因为这个静态方法都非常的常用,同时你要注意前面加了static这个标志。

普通断言

​ assertTrue(“a”.equals(“b”)); 可以额外加一个参数表示断言失败会显示什么信息

​ 而且断言失败后,后面的代码是不会执行的

组合断言

​ 很多时候,我们最终结果不是一个断言就能判断出来的

assertAll("person",
            () -> assertEquals("Jane", person.getFirstName()),
            () -> assertEquals("Doe", person.getLastName())
        );

快速失败

​ fail(“测试失败”);

系统监控

yaml

​ yaml里面的@@语法可以获取到maven里面的配置,而且还有自动提示

使用

maven

        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
            <version>${spring-boot-admin.version}</version>
        </dependency>

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

yaml

spring:
  boot:
    admin:
      client:
        url: http://ip:port/
  application:
    name: 项目名称
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: ALWAYS

多环境

注意spring boot 2.4发送了较多的变化!

规则

  • 不配置环境,就是默认环境,就是什么都不加的原始配置文件
    • 默认配置文件永远都会加载
  • 配置了相同的属性,选择了某个开发环境,其他环境的该属性不会生效

@Profile

​ 根据环境决定装配规则

分组配置

spring:
  profiles:
    active: "dev"
    group:
      "dev": "dev-db,dev-mq"
      "prod": "prod-db,prod-mq"

环境选择

默认环境的配置

spring:
  profiles:
    active: "dev"

运行时指定的参数

java -jar hearu-2.6.3.jar --spring.profiles.active=prod

​ 以上参数都是经过测试,确保正确的!!!

自定义Starter

步骤

  1. 创建一个空项目
  2. 创建一个maven模块,场景启动器模块xxx-spring-boot-starter
  3. 利用springboot的场景启动器,初始化一个xxx-spring-boot-autoconfigure
  4. 在starter的maven里面依赖我们的自动配置包
  5. 删除autoconfigure的打包和测试依赖,yaml配置文件和主程序也删掉,本质就是创建一个含有spring-boot父工程的maven项目
  6. 然后你就可以在autoconfigure里面写代码了

注意

​ 不要在组件上标注@compent之类的注解,因为我们的组件在主项目外,是扫描不到的,所以要通过自动配置类来帮助我们扫描,一般是条件装配和import

​ 条件装配还有一个问题就是你要标注@configuration注解,然在里面根据条件注入bean

​ 因为spring不知道哪个是你的自动装配类,所以你需要在类路径下创建一个META-INF/spring.factories配置你的自动装配类

拦截器

mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
   noHandlerFound(processedRequest, response);
   return;
}

​ 再获取处理器的过程中,就已经获取到拦截器链了,因为我们在配置拦截器的时候,配置了拦截器的路径,而我们在获取处理器的时候,又恰好知道这个请求路径,反过来想,路径不同的话,处理的拦截器链肯定是不同的。

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
   return;
}

​ 上面的代码很明显的说明了preHandle执行返回true和false的不同结果,理论上,一旦返回false,后面的是不可能执行的,但是实际情况多少还是有点不一样的,当然我说的是出现异常的时候

preHandler

	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		for (int i = 0; i < this.interceptorList.size(); i++) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
            //按顺序执行preHandler
			if (!interceptor.preHandle(request, response, this.handler)) {
               	//这是返回false的情况
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;//每次循环都标记了拦截器执行到的位置,所以下面才能出现逆序从哪个地方开始逆,这个时候我们不妨给出一个结论。就是如果我们拦截器走到某一步停止了以后,后面的拦截器的pre和after都是不会执行的
		}
		return true;
	}

afterHandler

for (int i = this.interceptorIndex; i >= 0; i--) {//逆序执行
    //现在我就清楚了,责任链是如何实现的了,其实就是一个顺序的问题,不一定是通过堆栈去模拟的,我们 的正逆循环也完全没有问题
   HandlerInterceptor interceptor = this.interceptorList.get(i);
   try {
       //执行的是这个after
      interceptor.afterCompletion(request, response, this.handler, ex);
   }
   catch (Throwable ex2) {
      logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
   }
}
catch (Exception ex) {
	//当我们在任意一个环节出现异常的时候,after也是会被执行的,其实看源码好多现象都不用去做实验
   triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
   triggerAfterCompletion(processedRequest, response, mappedHandler,
         new NestedServletException("Handler processing failed", err));
}

​ 这个有一个特点,就是它是永远都会触发的,而且只会除非已经执行过的拦截器的逆序。永远会触发是因为我有异常捕获,已经执行过的,是因为我有一个指针,逆序就是i–

postHandler

applyDefaultViewName(processedRequest, mv);//明显在post执行前,视图有了一定程度的渲染,而且我们在post中,是可以看到视图参数mv被传递进去的
mappedHandler.applyPostHandle(processedRequest, response, mv);
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
      throws Exception {
	//下面是倒序执行所有的,因为你能走到这一步,说明所有的pre都已经放行了,而且这里也说明了,只有所有的pre放行,它才有可能执行一个,而且肯定是逆序的全部执行
   for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
      HandlerInterceptor interceptor = this.interceptorList.get(i);
      interceptor.postHandle(request, response, this.handler, mv);
   }
}

​ 这样我们对源码简单了解一下,对执行顺序的把握肯定要比看那些文档或者说明准确的多

异常处理机制

​ 在我们的适配器执行方法的时候,无论如何都会保存一个webRequest.requestCompleted();来标识这个方法已经被执行完了,如果我们遇到重复执行的问题,可以考虑一下这个,它在类RequestMappingHandlerAdapter中。

​ 我来说一下大致的思路,就是把你抛出的异常捕获了一下,然后保存在一个变量里面,这样spring就获取了这个异常的相关信息。

if (exception != null) {
   if (exception instanceof ModelAndViewDefiningException) {
      logger.debug("ModelAndViewDefiningException encountered", exception);
      mv = ((ModelAndViewDefiningException) exception).getModelAndView();
   }
   else {
      //我们一般执行的是下面这个异常
      Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
      mv = processHandlerException(request, response, handler, exception);
      errorView = (mv != null);
   }
}

​ 如果你没有为spring配置异常处理,或者没有能配置相关异常处理的内容,spring就会把你这个异常交给tomcat处理,而且交给tomcat的方式是请求/error页面,不是很推荐这样做,不可控性瞬间就变大了,所以,spring为我们做了一个controller

BasicErrorController

​ spring会把/error交给它自己写的controller去处理,所以我们在swagger里面就会扫描到这个controller,又联系起来一个

ResponseStatus

​ 可以直接标注在异常上面!这样我们就不需要在全局异常处理器中处理这个异常了。这个原理其实使用了一个和全局异常处理同一接口的,然后去遍历的,不过呢,这个在全局异常处理器之后,如果全局异常处理器无法处理的话,才轮的到它。而且除了这个异常解析器,spring还为自己会产生的异常准备了一个默认的异常解析器,如果还是解析不了的话,你就真的可以看到tomcat的错误处理页面了

HandlerExceptionResolver

​ 自定义异常处理器

Servlet原生

快速开始

​ @ServletComponentScan指定包的位置,然后标注@WebXXX指定url路径,注意一下这个是在拦截器之前的,spring的安全框架用的就是这个原理,但是你多多少少会感觉到不方便,因为这个是servlet的原生注释。

​ ServletRegistrationBean,这个使用方法就是new的时候设置一些信息,注册到容器中,其实也不怎么方便,尤其是我想要多个servlet的时候。不过我们的mvc核心类DispatcherServlet就是通过这个方法被自动配置进来的

boot 原理

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

​ 明显是先创建spring boot应用,然后依据args参数运行

创建

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//这个就是你的主启动类
   this.webApplicationType = WebApplicationType.deduceFromClasspath();//判断是不是webflux环境,默认都是返回一个普通web环境,然后你观察枚举类WebApplicationType有非web环境,我们不做考虑,不过利用spring的种种机制,设计一个软件也是很方便的
   this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();//SpringFactoriesLoader.loadFactoryNames从spring.factory中寻找相关的配置
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();//主程序创建,这个是主动抛出异常利用函数的调用堆栈,这个从思想上看确定有点意想不到哈
}

运行

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();//开始记录时间
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   ConfigurableApplicationContext context = null;
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);//获取监听器
   listeners.starting(bootstrapContext, this.mainApplicationClass);//执行监听器事件
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//处理参数
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);//准备环境
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();//这个是比较重要的,其他其实看方法名也行
      context.setApplicationStartup(this.applicationStartup);
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();//启动成功,统计时间
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);//监听器被调用函数
      callRunners(context, applicationArguments);//runner
   }
   catch (Throwable ex) {
       //启动时的异常处理器
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

容器创建

try {
   switch (webApplicationType) {//很明显,普通web和reactweb的容器都不一样
   case SERVLET:
      return new AnnotationConfigServletWebServerApplicationContext();
   case REACTIVE:
      return new AnnotationConfigReactiveWebServerApplicationContext();
   default:
      return new AnnotationConfigApplicationContext();
   }
}

​ 然后就是适当的时候,调用我们初始化器,调用监听器。

​ 这个就是生命周期函数的调用,至于我们怎么使用它,这里需要注意的一点就是需要在spring.factory配置他们,因为他们有的是在ioc容器创建之前就要执行的。

​ Bootstrapper,ApplicationRunner,CommandLineRunner,ApplicationContextInitializer,ApplicationListener,SpringApplicationRunListener

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值