1.Spring核心
今天给大家讲讲Spring吧,谈到Spring,学过的人估计第一反应就是IOC和AOP,的确,这两个就是Spring的核心知识,也可以说是一种思想理念。下面我们就来具体讲讲这两个东西到底是什么吧~
1.IOC(控制反转):顾名思义,意思就是将我们控制Bean对象的权利反转交给Spring来控制和管理。那么Spring是怎么控制Bean的呢?众所周知,通常我们创建一个对象一般都是要用的时候new一个出来,在还没有出现IOC之前,这种方式习以为常,但是当IOC出现之后,再去使用new这种方式写代码,大家肯定有一种非常难受的感觉。Spring用一个注解能解决的事情使得大家就不用再去大费周章new一个对象了。
如何使用IOC?
这个问题其实就是再问我们如何去注册那些我们所需要的Bean对象,Spring 提供了四种方式供大家使用,分别是XML/注解/Java Config/Groovy DSL;前三种是我们经常使用的方法,我就不过多解释了。后面那种是基于DSL语言的,可以把它通俗理解为一个伪class文件,如果有接触过protocol buff格式数据或者是Grpc框架的同学应该知道,我们在进行java与python系统交互的时候,会将java对象转换生成一个proto文件,Groovy DSL方式大概就是这个意思了——自定义一个对象文件。
优势:
1.提供了一套管理bean的生命周期模式
2.单例模式
3.方便测试
4.屏蔽对象创建的细节
2.AOP(面向切口编程):在Spring中,我们还是会经常看到AOP的身影的,比如在我们实现一些非业务代码时——日志记录、接口记录、接口使用情况等。它的底层原理是动态代理,在spring中依赖的是beanPostProcessor。一般都是配合注解使用。在我们的项目中,为了响应网监的监管,我就是使用AOP+自定义注解的方式实现了一套用户ip地址记录和详细用户数据日志记录。
2.Bean
下面就来详细介绍一下在Spring中Bean对象创建到消亡的整个过程,话不多少,直接上图!
bean生命周期.png具体流程:spring在启动的时候,首先加载配置文件、注解、config中需要创建的bean,把这些元数据和相关信息封装成DefineBean对象,然后放入一个DefineBeanMap中,紧接着遍历这个DefineBeanMap,根据工厂模式思想,执行BeanFactoryPostProcesor前置处理器的方法,创建相关bean对象的工厂类,这里可以对对象的信息做一些修改操作。然后工厂类开始创建bean对象,下一步属性注入,当bean对象创建完成之后,接下来就要考虑实例化了;这时,首先检查该对象是否实现了Aware相关的接口,如果实现了,那么就要去填充相关资源。其次,再考虑执行BeanPostProcessor后置处理器里面的扩展方法,这里我们可以实现自己的方法,具体看业务逻辑是否需要!最后就是执行初始化相关的方法了,init相关方法——如PostConstrut、实现了 InitializingBean接口的类、init-method方法等。创建完成!(顺便提一下资源的销毁,实现disposable的desory()方法,或者自定义销毁方法)
这里需要注意一点,在做依赖注入时,可能会遇上循环依赖的问题,Spring又是如何解决循环依赖问题的呢?(补充:循环依赖可以理解为创建对象A需要依赖对象B,而对象B又需要依赖对象A)
答案是三级缓存!看图
从上面的流程可以知道,Spring在创建对象的时候,是和属性注入分开的,而且在创建之前是先创建该对象对应的工厂类,所以,我们从这里就可以将bean创建分为三个缓存阶段,也就是三个Map,在对象A创建的时候,首先在三级缓存中生成一个对象map,key为对象名,值为对象。发现需要依赖对象B,那么这时会先去创建对象B,在对象B创建的时候,会去get对象A,A已经存在,这时我们可以把对象B从三级缓存中移除,放入二级缓存,而对象A自然而然就可以生成了,再去做属性注入,放入一级缓存。
提一下,这里为什么要考虑三级缓存,而不是二级缓存!
从三级缓存中,我们能够拿到的代理对象,这样方便管理对象
从二级缓存中,我们不用每次获取对象都去工厂类中生成,提高性能
3.Spring MVC
如果说spring是让我们和系统对象打交道,那么MVC就是在和用户打交道!我们可以理解为一个用户发起一个请求,MVC封装了中间所有的通讯细节,找到这个请求想要的数据,并封装成一个ModelAndView对象返回给用户。
1.交互流程
总结一下:
①用户发起url请求
②DispatcherServlet处理并分发请求到handleMapping
②handleMapping根据url找到相应的handle
③handle查找适配器
④适配器调用对应的handle方法处理请求
⑤将响应结果封装成ModleAndViewdu对象返回
⑥通过dispatcherservlet将处理好的视图对象返回给用户
2.核心源码解析
根据上面讲解的运行流程,大家带着这个思路逻辑看一下核心代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 定义一个已处理请求,指向参数的request
HttpServletRequest processedRequest = request;
// 定义处理器执行连,内部封装拦截器列表和处理器
HandlerExecutionChain mappedHandler = null;
// 是否有文件上传的请求标志
boolean multipartRequestParsed = false;
// 获取异步管理器,执行异步操作
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
// 保存处理器执行的返回结果
ModelAndView mv = null;
// 保存处理过程中的异常
Exception dispatchException = null;
try {
// 判断当前请求是否有上传需求,并返回保存到processedRequest中
processedRequest = checkMultipart(request);
// 判断当前请求是否是文件上传的请求,如果是则说明是上传请求已经处理
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 获取可处理当前请求的请求处理器,通过HandlerMapping进行查找
mappedHandler = getHandler(processedRequest);
// 如果没有,就执行没有处理器的逻辑
if (mappedHandler == null) {
// 在内部处理中抛出异常或者返回404
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 根据当前请求的处理器获取支持该处理器的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 处理last-modified请求头,用于判断请求内容是否发生修改
String method = request.getMethod();
boolean isGet = "GET".equals(method);
// 只有get请求和head请求执行此判断
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 通过mappedHandler这个HandlerExecutionChain执行链的封装,链式执行所有连接器的前置拦截方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
// 任意一个拦截器的前置拦截方法返回false,提前结束请求的处理
return;
}
// Actually invoke the handler.
// 执行处理适配器的处理方法,传入请求,对请求进行处理,此方法的返回值是ModelAndView对象,封装了模型和视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果是异步处理,则直接返回,后续处理通过异步执行
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 返回的mv对象中如果没有视图名称,则根据请求设置默认视图名
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.
// 4.3版本之后提供了error类型异常的处理
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 对下执行结果进行处理,包括视图的处理和异常的处理
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 链式执行拦截器链的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 拦截error类型异常,拦截后链式执行拦截器链的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
// 做资源清理
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
看完之后,是不是或多或少都有一些想法了呢?不着急,我们先把一些前置知识给大家梳理一下。
在Spring运行开始,首先会初始化一些handleMapping,HandlerAdapter等核心组件,这些组件的作用,说白了就是一点——将用户发起的URL请求映射到我们写好的controller方法,执行业务逻辑;mvc将其封装好供我们直接使用;
总结:通过handleMapping,我们可以顺利找到相关的适配器,再通过适配器调用handle()方法(我们的业务逻辑),得到result之后封装成ModelAndView对象返回,从代码中我们可以看到,getHandler()方法会返回一个HandlerExecutionChain对象,(这个执行链对象包含两个拦截器和一个处理器,拦截器分别做前置和后置处理)所以在代码中有mappedHandler.getHandler()方法先拿到这个处理器,然后再通过getHandlerAdapter()获得对应的适配器紧接着执行handle()方法处理业务逻辑,最后封装ModelAndView对象返回。
大致步骤是说完了,其中还是有一些细节我们没有说的到
比如说初始化时是如何加载这些核心组件的,(可以思考一下url与处理逻辑如何映射?使用什么数据结构?)
或者是为什么采用执行链和适配器这种模式?
再或者说ModelAndView对象是如何封装的,以及mvc是如何处理异常的?
这些问题就留给读者在源码中寻找答案了!此时只提供一个总体的思路框架......
面试总结系列第三面——欢迎留言讨论,共同进步!
网友评论