1.@EnableAutoConfigurationEnableAutoConfiguration除了元注解之外,还有两个重要的部分:1)@AutoConfigurationPackage注解该注解只引入了一个内部类:AutoConfigurationPackages.Registrar.class类从名字上看,有两种方法。registerBeanDefinitions方法注册一些已定义的bean,determineImports方法决定是否导入它们。registerBeanDefinitions调用register方法,并传入注册表参数和元数据名称数组。registry参数是一个接口,必须是实际场景中使用的实现类。可以在这个方法的断点调试,发现实现类其实是一个DefaultListableBeanFactory,可以清楚的看到这个类的一些基本属性。从该值可以看出,注册表中存储了项目程序的一些基本配置和bean实例名称。例如,一些网页支持组件和springContext,一些加载器和用户自定义类在这些类组件中存储bean名称、作用域、延迟加载等。再看另一个参数:newPackageImports(metadata).getPackageNames().toArray(newString[0])好像是把元数据中的包名提取到一个数组中。让我们打开它看看这个类在初始化过程中做了什么。我们可以看到在初始化时实际上调用了一个ClassUtils。.getPackageName,传入的是什么,metadata.getclassname,是什么,划重点!,其实就是启动类的全名,com.***.application,而这个方法提取类名的前缀,也就是提取我们的包名com.&&&也就是这个AutoConfigurationPackages.register方法传给us很多初始的bean名,配置,总包名com.**,我们先研究他做了什么,判断定义的bean里面有没有bean,这个bean是类的一个属性,存储全称:org.springframework.boot.autoconfigure.AutoConfigurationPackages大概是想处理用户自定义了AutoConfigurationPackages类的情况。目前还没有定义,直接去else逻辑新建一个GenericBeanDefinition,姑且称之为通用bean定义工具吧,设置了很多东西。之后调用注册方法。把工具丢进去。在这个注册方法中,AutoConfigurationPackages类大概也是注册到定义bean的map中,然后把map中的值全部添加到默认的bean工厂中,然后class就完成了。所以这个注解的作用就是在bean工厂中注册自定义的bean,一些基本类型,原始组件2)AutoConfigurationImportSelector类在这个类中进入到视图的selectImports方法就是selectImports方法。听名字好像是selectimport,即方法决定加载哪些依赖组件,方法调用,只调用getAutoConfigurationEntry方法获取组件加载。让我们来看看这个方法。首先,我们调用getAttributes来获取一些东西。断点很熟悉。就是EnableAutoConfiguration注解的两个属性值,虽然默认值为null。点进方法体,发现确实是这样。getCandidateConfigurations(annotationMetadata,attributes)方法调用了很多,最后调用了这个方法loadSpringFactories。该方法的大致内容是从当前所有的依赖包中,从META-INF/目录下加载的spring.factories文件中寻找一些组件。可以看到他先尝试从缓存中获取,如果为空则从依赖包中META-INF/目录下的spring.factories中加载。拿到组件列表后,需要一层过滤,提取出包含factoryTypeName的组件列表。这个名字就是org.springframework.boot.autoconfigure.EnableAutoConfiguration自动配置注解然后返回getEntry方法,然后对获取到的组件列表进行去重,然后尝试去除列表中的排除项,这是获取到attributes中的内容EnableAutoConfiguration注解的两个属性值。很容易理解,这里会排除属性值中配置的要排除的内容。然后调用filter方法对列表进行过滤,过滤使用autoConfigurationMetadata类。可以看出,这个类是一种过滤规则,里面存储了501个属性,所以过滤规则可以一个一个比对,过滤掉中的Metadata组件。过滤后的结果就是最终要加载的组件bean。也就是说@EnableAutoConfiguration的两个重要成员,一个决定加载哪些默认组件(自定义bean、值包装类、字符串类等)和配置,另一个决定加载哪些外部依赖类,即是,通过starter等通过pom引入的组件。2.请求处理如何知道spring如何以及在何处处理请求?有一个很简单的方法可以在properties中设置日志级别为debug,就是dubug=true。然后运行程序,发送任何请求,你会发现spring会打印出一些处理请求的细节。例如,处理请求从初始化DispatcherServlet开始。可见,请求处理中最重要的是DispatcherServlet。然后AbstractHandlerMapping识别处理请求的具体方法。让我们去DispatcherServlet看看他的方法。学过mvc原生web开发的都知道servlet有两个重要的方法doGet和doPost。而DispatcherServlet继承FrameworkServlet继承HttpServletBean,HttpServletBean继承HttpServlet,可见DispatcherServlet也是一个httpServlet。那我们就有想法了,从他的do**方法开始探索。1)doDispatch从名字上看,这个方法好像是做转发的。并且其参数与doGet方法几乎相同。一开始初始化了很多东西,包括处理异步请求的异步管理器,检查是否是文件上传的请求。然后getHandler方法直接获取到这个请求的处理。具体方法。它是如何处理的?它遍历了一个handlerMappings的集合,里面存放了一些专门做映射的spring类,比如欢迎页面的映射,还有我们用requestMapping标记的url。这样他就会在requestMappingHandlerMapping中找到/hello请求,找到他映射的方法。专门找到调用HandlerMapping的gethandler,所以和log中的是匹配的。gethandler方法就是根据url找到mapping中的方法,先忽略这个细节。拿到这个processor(方法)之后,丢给HandlerAdapter请求适配器,这个是mvc架构中比较熟悉的人物。之后并不会立即执行该方法,而是先判断该方法的类型。如果是get或head方法,则执行逻辑。此逻辑将返回-1,这是硬编码的。我不明白为什么会这样。在网上搜索了Last-Modified,发现这是一种缓存机制。这也就理解了为什么一定要是get方法,他需要实现LastModified接口。这个我没有实现,所以spring的判断逻辑会是false。之后就是applyPreHandle方法。当你点击它的时候,你会发现使用当前的spring拦截器组件来拦截请求。可以发现有一些请求方法拦截器,token拦截器,资源拦截器等等,也就是一些坏请求被拦截到这一行之后,请求适配器执行自己的处理逻辑。可以看到他返回的是一个modelAndView类型的实例,说明这个时候方法已经执行完了。但实际上并没有使用modelAndView作为返回值,而是直接返回了字符串。所以mv是一个null值,但是在responsebody中可以观察到,responsebody中已经写入了19个字节,证明该方法已经执行完毕。接下来判断请求是否为异步请求。如果是异步请求,可能会做Firstresponse等处理。再往下是applyingthedefaultviewname的方法,将requestbody或defaultviewname添加到model中。因为如果应用了modelandview,此时只是一个model,必须添加对应的view。你可以简单地测试一下。在处理方法中,我们新建一个modelandview对象,设置一个model值并返回。可以看出,在这个方法之后,我们并没有设置视图值,系统默认将hello作为视图添加到模型中。这个默认值是没有前面/的请求路径。在这个方法之后还有一个拦截器,类似于在方法执行前后进行一次拦截。拦截器执行完毕后,方法结束。后者是一些自下而上的处理。例如,如果是文件相关的请求,则必须关闭相应的流。如果是异步请求,执行什么逻辑。2)handle方法我们知道,方法逻辑是在适配器的handle方法中执行的,具体是如何执行的。其实就是调用了super的AbstractHandlerMethodAdapter的handle方法。这个handle方法会调用自己的handleInternal方法(RequestMappingHandlerAdapter)。这个方法对session做了一些处理,然后调用了invokeHandlerMethod方法,这个方法做了很多处理。例如,WebDataBinderFactorybinderFactory对象在参数间进行数据格式转换,并在其中初始化了128个对象转换方法;或者初始化一些参数解析器和返回值处理器:它处理各种参数和返回值做相应的解析和处理,简单来说就是把这些丢到一个mavContainer容器中。然后调用invocableMethod.invokeAndHandle(webRequest,mavContainer,newObject[0])方法,同时传入webRequest,这是将请求体和响应体封装在一起。该方法中第一行直接执行该方法,可以看到此时已经有返回值了。这个方法比较简单:获取参数,执行方法①getMethodArgumentValues这个方法一开始是获取映射方法的参数类型和参数名。你可以猜到后面做了什么,就是从请求中找到这些参数名对应的参数,并转换成对应的类型。你可以看看具体情况。首先定义一个接收数组来存放获取到的参数,然后尝试从providedArgs中获取相应的参数,但实际上传递的参数是null值。于是执行下面的逻辑,从request中取出来,同时传入mav容器中,这个容器中有格式转换器和返回值处理器。该方法中有两个重要的组成部分,getArgumentResolver获取参数解析器和resolveArgument解析参数。在获取parameterparser的方法中,loop已经存放在ioc容器的parser组件中,将参数逐一比较,找到能够解析对应参数的parser。在解析参数方法中,就是获取参数名容器→获取方法参数容器→获取参数名→解析参数。之后还会做很多后续的处理,比如格式转换之类的。②doInvoke方法比较简单。它使用了之前存储在InvocableHandlerMethod中的反射方法publicjava.lang.Stringcom.glodon.controller.HelloController.home(java.lang.String)。事实上,这个对象实例也是由相关组件的特殊存储方法处理的。获取方法后,使用spring反射机制执行方法。3)requestresponse中方法执行完后,封装modelAndView,执行后处理拦截器后,必须执行一个bottom-up方法处理结果。从名字看,就是用来处理结果转发的。开始是判断方法执行过程中是否有异常,如果有则走异常处理逻辑。然后如果modelandview不为空,执行一个render方法render(mv,request,response)render方法先从request中获取语言标识添加到responsebody中,然后取出modelandview中的view名称,即、重定向或其他视图。然后调用resolveViewName方法对视图名进行处理,遍历当前容器中所有的解析器,能解析出视图名的,直接返回解析结果。resolver的解析过程,以ContentNegotiatingViewResolver为例。此解析器获取请求的属性,然后将其传递给getMediaTypes方法并调用它以获取返回数据类型。getMediaTypes其实是双循环匹配,格式化网页请求→获取浏览器请求头中可接受的媒体类型→获取系统可以生产的媒体类型→初始化匹配的媒体类型。然后进行双循环,如果匹配成功,则将对应的媒体类型添加到compatibleMediaTypes中。排序完成后是一个集合→List,然后进行一次排序,排序的依据是媒体类型的权重。浏览器发送请求的时候,会给服务器一个accept,里面明确的指明了浏览器可以接收的返回类型和它的权重,这里的排序就是根据这个权重进行的。方法返回后,查看解析方法,然后调用getCandidateViews获取候选视图。getCandidateViews方法也是一个双循环。外层是除当前解析器之外的其他三个视图解析器,内层是匹配的媒体类型。通过调试,发现被InternalResourceViewResolver处理成功。我们只看他的细节。它也是resolveViewname方法。该方法首先尝试从缓存中获取,获取不到则执行createView方法创建视图。而且这个方法非常清晰明了判断是重定向还是转发生成对应的视图。最后返回适当的视图,添加缓存等等等等。我们测试的恰好是一个重定向视图,所以返回的结果是一个bean为redirect,url为/helloWorld的视图。
