写点什么

Spring 源码探索 - 核心原理下 (AOP、MVC)

作者:Java你猿哥
  • 2023-04-13
    湖南
  • 本文字数:9674 字

    阅读完需:约 32 分钟

Spring源码探索-核心原理下(AOP、MVC)

AOP

AOP 是在 Bean 的后置处理器中设置的也就是在初始化 Bean 的时候(initializeBeanAOP 源码的入口函数)。这里重点主要分为两个部分一个是代码织入的部分也就是编译阶段,还有一个就是代码运行阶段。

时序图


核心代码

initializeBean


applyBeanPostProcessorsAfterInitialization

这个就是遍历所有的后置处理器,进行一些代码的织入,比如 AOP 的代码织入

@Overridepublic Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)      throws BeansException {
Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result;}
复制代码


在 Spring 中,BeanPostProcessor 的实现子类非常的多,分别完成不同的操作,如:AOP 面向切 面编程的注册通知适配器、Bean 对象的数据校验、Bean 继承属性、方法的合并等等,我们以最简单的 AOP 切面织入来简单了解其主要的功能。下面我们来分析其中一个创建 AOP 代理对象的子类 AbstractAutoProxyCreator 类。该类重写了 postProcessAfterlnitialization()方法。

wrapIfNecessary

这个方法主要是做一些判断,判断这个类是否需要创建代理类

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {        return bean;    } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {        return bean;    } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {        Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);        if (specificInterceptors != DO_NOT_PROXY) {            this.advisedBeans.put(cacheKey, Boolean.TRUE);            Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));            this.proxyTypes.put(cacheKey, proxy.getClass());            return proxy;        } else {            this.advisedBeans.put(cacheKey, Boolean.FALSE);            return bean;        }    } else {        this.advisedBeans.put(cacheKey, Boolean.FALSE);        return bean;    }}
复制代码

createProxy

这就是个比较核心的方法了在这里进行代理类的创建以及代码的织入

private Object buildProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {    BeanFactory var7 = this.beanFactory;    if (var7 instanceof ConfigurableListableBeanFactory clbf) {        AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);    }
ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); if (proxyFactory.isProxyTargetClass()) { if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) { Class[] var12 = beanClass.getInterfaces(); int var8 = var12.length;
for(int var9 = 0; var9 < var8; ++var9) { Class<?> ifc = var12[var9]; proxyFactory.addInterface(ifc); } } } else if (this.shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { this.evaluateProxyInterfaces(beanClass, proxyFactory); }
Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors); proxyFactory.addAdvisors(advisors); proxyFactory.setTargetSource(targetSource); this.customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (this.advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); }
ClassLoader classLoader = this.getProxyClassLoader(); if (classLoader instanceof SmartClassLoader smartClassLoader) { if (classLoader != beanClass.getClassLoader()) { classLoader = smartClassLoader.getOriginalClassLoader(); } }
return classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader);}
复制代码

可以看到最后是通过一个代理工厂生成代理类,这里用了一个策略模式和简单工厂模式结合的设计模式,具体策略其实就是 JDK 方式生成代理类,还一种策略是 CgLib 方式生成代理类


这里我们就拿 JDK 创建代理类的方式进行接下来的源码解读


至此代理类的创建也就算完成了。代理类完成了创建接下来就需要看 Spring 如何将对应的代码织入进来拿 JDK 距距离我们可以找 JdkDynamicAopProxy 这个类的 invoke 方法

JdkDynamicAopProxy-invoke 方法

在这里进行了代码的织入

@Nullablepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    Object oldProxy = null;    boolean setProxyContext = false;    TargetSource targetSource = this.advised.targetSource;    Object target = null;
Object var12; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { Boolean var18 = this.equals(args[0]); return var18; }
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { Integer var17 = this.hashCode(); return var17; }
if (method.getDeclaringClass() == DecoratingProxy.class) { Class var16 = AopProxyUtils.ultimateTargetClass(this.advised); return var16; }
Object retVal; if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); return retVal; }
if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; }
target = targetSource.getTarget(); Class<?> targetClass = target != null ? target.getClass() : null; List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); }
Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); }
var12 = retVal; } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); }
if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); }
}
return var12;}
复制代码

这里有个很重要的方法就是 getInterceptorsAndDynamicInterceptionAdvice 这个方法返回了所有 Interceptor。具体怎么找到的其实也比较简单:


MethodCacheKey 你可以简单理解为方法作为 key(主要作用就是获取到结果之后缓存下来)


也就是从提供的配置实例 config 中获取 advisor 列表,遍历处理这些 advisor. 如果是 IntroductionAdvisor,则判断此 Advisor 能否应用到目标类 targetClass 上.如果是 PointcutAdvisor,则判断此 Advisor 能否应用到目标方法 Method 上.将满足条件的 Advisor 通过 AdvisorAdaptor 转化成 Interceptor 列表返回拿到这个列表之后就开始对代码进行织入:

ReflectiveMethodInvocation-proceed 方法


到这里代码的织入就完成了,最后就是看一下最后的运行是如何实现的,这里我们可以找一下 MethodInterceptor 的实现类,我们可以哪几个典型的 Interceptor 拿出来说明,也就是方法执行前的执行后的抛异常的


  • 方法执行前的 Interceptor-MethodBeforeAdviceInterceptor

  • 方法执行后的 Interceptor-AspectJAfterAdvice

  • 方法异常的 Interceptor-AspectJAfterThrowingAdvice

核心类

  • ProxyFactory:代理生成工厂根据不同的策略生成对应的代理类

  • MethodInterceptor:是 AOP 项目中的拦截器(注:不是动态代理拦截器),区别于 HandlerInterceptor 拦截目标时请求,它拦截的目标是方法。

  • Advice:’切面〞对于某个“连接点〞所产生的动作。其中,一个“切面”可以包含多个 “Advice”

  • Joinpoint:是 AOP 的连接点。一个连接点代表一个被代理的方法。我们从源码角度看连接点有哪些属性和功能。

MVC

容器初始化时会建立所有 url 和 Controller 中的 Method 的对应关系,保存到 HandlerMapping 中,用户请求是根据 Request 请求的 url 快速定位到 Controller 中的某个方法。

在 Spring 中先将 url 和 Controller 的对应关包系,保存到 Map<url,Controller>中。Web 容器启动时会通知 Spring 初始化容器(加载 Bean 的定义信息和初始化所有单例 Bean),然后 SpringMVC 会遍历容器中的 Bean,获 取每一个 Controller 中的所有方法访问的 url 然后将 url 和 Controller 保存到一个 Map 中;这样就可以根据 Request 快速定位到 Controller,因为最终处理 Request 的是 Controller 中的方法,Map 中只保留了 url 和 Controller 中的对应关系,所以要根据 Request 的 url 进一步确认 Controller 中的 Method,这一步工作的原理就是拼接 Controller 的 url(Controller 上 @RequestMapping 的值)和方法的 url(Method 上 @RequestMapping 的值),与 request 的 url 进行匹配,找到匹配的那个方法;

确定处 理请求的 Method 后,接下来的任务就是参数绑定,把 Request 中参数绑定到方法的形 式参数上,这一步是整个请求处理过程中最复杂的一个步骤。

最后就是调用返回

核心类

DispatcherServlet

DispatcherServlet 是 SpringMVC 中的前端控制器(Front Controller), 负责接收 Request 并将 Request 转发给对应的处理组件。

HanlerMapping

HanlerMapping 是 SpringMVC 中完成 url 到 Controller 映射的组件。DispatcherServlet 接收 Request,然后 从 HandlerMapping 查找处理 Request 的 Controller。

HandlerAdapters

因为 Spring MVC 中 Handler 可以是任意形式的,只要 能够处理请求便行,但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是如 doService(HttpServletRequest req, HttpServletResponse resp)这样的形式,让固定的 Servlet 处理方法调用 Handler 来进行处理,这一步工作便是 HandlerAdapter 要做 的事。对应的有 HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter 和 AnnotationMethodHandlerAdapter

  1. AnnotationMethodHandlerAdapter 主要是适配注解类处理器,注解类处理器就是我们经常使用的 @Controller 的这类处理器

  2. HttpRequestHandlerAdapter 主要是适配静态资源处理器,静态资源处理器就是实现了 HttpRequestHandler 接口的处理器,这类处理器的作用是处理通过 SpringMVC 来访问的静态资源的请求

  3. SimpleControllerHandlerAdapter 是 Controller 处理适配器,适配实现了 Controller 接口或 Controller 接口子类的处理器,比如我们经常自己写的 Controller 来继承 MultiActionController.

  4. SimpleServletHandlerAdapter 是 Servlet 处理适配器, 适配实现了 Servlet 接口或 Servlet 的子类的处理器,我们不仅可以在 web.xml 里面配置 Servlet,其实也可以用 SpringMVC 来配置 Servlet,不过这个适配器很少用到,而且 SpringMVC 默认的适配器没有他,默认的是前面的三种。

ModelAndView

Web MVC 框架中 Model 和 View 的持有人。 这两类是截然不同的。 ModelAndView 仅保留两者,以使控制器有可能在单个返回值中返回模型和视图。 该视图由 ViewResolver 对象解析; 该模型是存储在 Map 中的数据。

ViewResolvers

视图解析器。这个组件的主要作用,便是将 String 类型的视图名和 Locale 解析为 View 类型的视图。这个接口只有一个 resolveViewName 方法。从方法的定义就可以看出,Controller 层返回的 String 类型的视图名 viewName 最终会在这里被解析成为 View. View 是用来渲染页面的,也就是说,它会将程序返回的 参数和数据填入模板中,最终生成 html 文件。ViewResolver 在这个过程中,主要做两 件大事,即,ViewResolver 会找到渲染所用的模板(使用什么模板来渲染?)和所用的 技术(其实也就是视图的类型,如 JSP 啊还是其他什么 Blabla 的)填入参数。默认情况 下,Spring MVC 会为我们自动配置一个 InternalResourceViewResolver,这个是针对 JSP 类型视图的。

时序图


核心代码

initServletBean

protected final void initServletBean() throws ServletException {    ServletContext var10000 = this.getServletContext();    String var10001 = this.getClass().getSimpleName();    var10000.log("Initializing Spring " + var10001 + " '" + this.getServletName() + "'");    if (this.logger.isInfoEnabled()) {        this.logger.info("Initializing Servlet '" + this.getServletName() + "'");    }
long startTime = System.currentTimeMillis();
try { this.webApplicationContext = this.initWebApplicationContext(); this.initFrameworkServlet(); } catch (RuntimeException | ServletException var4) { this.logger.error("Context initialization failed", var4); throw var4; }
if (this.logger.isDebugEnabled()) { String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data"; this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value); }
if (this.logger.isInfoEnabled()) { this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); }
}
复制代码

这段代码中最主要的逻辑就是初始化 IOC 容器,最终会调用 refresh 方法,IoC 容器初始化之后,最后有调用了 onRefresh 方法。这个方法最终是在 DisptcherServlet 中实现


在这里就开始初始化九大组件了

doDispatch

这个方法就到了运行阶段了

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {    HttpServletRequest processedRequest = request;    HandlerExecutionChain mappedHandler = null;    boolean multipartRequestParsed = false;    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try { try { ModelAndView mv = null; Exception dispatchException = null;
try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; //取得处理当前请求的 Controller,这里也称为 hanlder,处理器, //这里并不是直接返回 Controller,而是返回的 HandlerExecutionChain //请求处理器链对象,该对象封装了 handler 和 interceptors. mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } //获取处理 request 的处理器适配器 handler adapter HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); 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; } //结果视图对象的处理 this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new ServletException("Handler dispatch failed: " + var21, var21); }
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new ServletException("Handler processing failed: " + var23, var23)); }
} finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); }
}}
复制代码

getHandler(processedRequest)方法实际上就是从 HandlerMapping 中找到 url 和 Controller 的对应关系。也就是 Map<url,Controller>。我们知道,最终处理 Request 的是 Controller 中的方法,我们现在只是知道了 Controller,接下来就需要确定具体是哪个方法处理来处理 Request,这个是在获取 HandlerExecutionChain 的时候获取的具体源码如下:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {   String lookupPath = initLookupPath(request);   this.mappingRegistry.acquireReadLock();   try {   //遍历 Controller 上的所有方法,获取 url 匹配的方法      HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);      return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);   }   finally {      this.mappingRegistry.releaseReadLock();   }}
复制代码

整个处理过程中最核心的逻辑其实就是拼接 Controller 的 url 和方法的 url 与 Request 的 url 进行匹配,找到匹配的方法。

getMethodArgumentValues

获取方法参数的方法

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {    MethodParameter[] parameters = this.getMethodParameters();    if (ObjectUtils.isEmpty(parameters)) {        return EMPTY_ARGS;    } else {        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) { if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); }
try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception var10) { if (logger.isDebugEnabled()) { String exMsg = var10.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } }
throw var10; } } }
return args; }}
复制代码

上文中 invocableMethod.invokeAndHandle(最终要实现的目的就是:完成 Request 中的参数和方法参数上数据的绑定。Spring MVC 中提供两种 Request 参数到方法中参数的绑定方式:

  1. 通过注解进行绑定,@RequestParam。

  2. 通过参数名称进行绑定。


    使用注解进行绑定,我们只要在方法参数前面声明 @RequestParam("name"),就可以 将 request 中参数 name 的值绑定到方法的该参数上。使用参数名称进行绑定的前提是 必须要获取方法中参数的名称,Java 反射只提供了获取方法的参数的类型,并没有提供 获取参数名称的方法。SpringMMC 解决这个问题的方法是用 asm 框架读取字节码文件, 来获取方法的参数名称。asm 框架是一个字节码操作框架。所以建议,使用注解来完成参数绑定,这样就可以省去 asm 框架的读取字节码

用户头像

Java你猿哥

关注

一只在编程路上渐行渐远的程序猿 2023-03-09 加入

关注我,了解更多Java、架构、Spring等知识

评论

发布
暂无评论
Spring源码探索-核心原理下(AOP、MVC)_spring_Java你猿哥_InfoQ写作社区