【Spring源码分析】AOP源码解析(上篇)
前言
前面写了六篇文章详细地分析了Spring Bean加载流程,这部分完了之后就要进入一个比较困难的部分了,就是AOP的实现原理分析。为了探究AOP实现原理,首先定义几个类,一个Dao接口:
 public interface Dao {
     public void select();
     public void insert();
 }
Dao接口的实现类DaoImpl:
 public class DaoImpl implements Dao {
     @Override
     public void select() {
         System.out.println("Enter DaoImpl.select()");
     }
     @Override
     public void insert() {
         System.out.println("Enter DaoImpl.insert()");
     }
 }
定义一个TimeHandler,用于方法调用前后打印时间,在AOP中,这扮演的是横切关注点的角色:
 public class TimeHandler {
     public void printTime() {
         System.out.println("CurrentTime:" + System.currentTimeMillis());
     }
 }
定义一个XML文件aop.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="daoImpl" class="org.xrq.action.aop.DaoImpl" />
<bean id="timeHandler" class="org.xrq.action.aop.TimeHandler" /> <aop:config proxy-target-class="true">
<aop:aspect id="time" ref="timeHandler">
<aop:pointcut id="addAllMethod" expression="execution(* org.xrq.action.aop.Dao.*(..))" />
<aop:before method="printTime" pointcut-ref="addAllMethod" />
<aop:after method="printTime" pointcut-ref="addAllMethod" />
</aop:aspect>
</aop:config> </beans>
写一段测试代码TestAop.java:
 public class TestAop {
     @Test
     public void testAop() {
         ApplicationContext ac = new ClassPathXmlApplicationContext("spring/aop.xml");
         Dao dao = (Dao)ac.getBean("daoImpl");
         dao.select();
     }
 }
代码运行结果就不看了,有了以上的内容,我们就可以根据这些跟一下代码,看看Spring到底是如何实现AOP的。
AOP实现原理----找到Spring处理AOP的源头
有很多朋友不愿意去看AOP源码的一个很大原因是因为找不到AOP源码实现的入口在哪里,这个确实是。不过我们可以看一下上面的测试代码,就普通Bean也好、AOP也好,最终都是通过getBean方法获取到Bean并调用方法的,getBean之后的对象已经前后都打印了TimeHandler类printTime()方法里面的内容,可以想见它们已经是被Spring容器处理过了。
既然如此,那无非就两个地方处理:
- 加载Bean定义的时候应该有过特殊的处理
- getBean的时候应该有过特殊的处理
因此,本文围绕【1.加载Bean定义的时候应该有过特殊的处理】展开,先找一下到底是哪里Spring对AOP做了特殊的处理。代码直接定位到DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法:
 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
     if (delegate.isDefaultNamespace(root)) {
         NodeList nl = root.getChildNodes();
         for (int i = 0; i < nl.getLength(); i++) {
             Node node = nl.item(i);
             if (node instanceof Element) {
                 Element ele = (Element) node;
                 if (delegate.isDefaultNamespace(ele)) {
                     parseDefaultElement(ele, delegate);
                 }
                 else {
                     delegate.parseCustomElement(ele);
                 }
             }
         }
     }
     else {
         delegate.parseCustomElement(root);
     }
 }
正常来说,遇到<bean id="daoImpl"...>、<bean id="timeHandler"...>这两个标签的时候,都会执行第9行的代码,因为<bean>标签是默认的Namespace。但是在遇到后面的<aop:config>标签的时候就不一样了,<aop:config>并不是默认的Namespace,因此会执行第12行的代码,看一下:
 public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
     String namespaceUri = getNamespaceURI(ele);
     NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
     if (handler == null) {
         error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
         return null;
     }
     return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
 }
因为之前把整个XML解析为了org.w3c.dom.Document,org.w3c.dom.Document以树的形式表示整个XML,具体到每一个节点就是一个Node。
首先第2行从<aop:config>这个Node(参数Element是Node接口的子接口)中拿到Namespace="http://www.springframework.org/schema/aop",第3行的代码根据这个Namespace获取对应的NamespaceHandler即Namespace处理器,具体到aop这个Namespace的NamespaceHandler是org.springframework.aop.config.AopNamespaceHandler类,也就是第3行代码获取到的结果。具体到AopNamespaceHandler里面,有几个Parser,是用于具体标签转换的,分别为:
- config-->ConfigBeanDefinitionParser
- aspectj-autoproxy-->AspectJAutoProxyBeanDefinitionParser
- scoped-proxy-->ScopedProxyBeanDefinitionDecorator
- spring-configured-->SpringConfiguredBeanDefinitionParser
接着,就是第8行的代码,利用AopNamespaceHandler的parse方法,解析<aop:config>下的内容了。
AOP Bean定义加载----根据织入方式将<aop:before>、<aop:after>转换成名为adviceDef的RootBeanDefinition
上面经过分析,已经找到了Spring是通过AopNamespaceHandler处理的AOP,那么接着进入AopNamespaceHandler的parse方法源代码:
 public BeanDefinition parse(Element element, ParserContext parserContext) {
     return findParserForElement(element, parserContext).parse(element, parserContext);
 }
首先获取具体的Parser,因为当前节点是<aop:config>,上一部分最后有列,config是通过ConfigBeanDefinitionParser来处理的,因此findParserForElement(element, parserContext)这一部分代码获取到的是ConfigBeanDefinitionParser,接着看ConfigBeanDefinitionParser的parse方法:
 public BeanDefinition parse(Element element, ParserContext parserContext) {
     CompositeComponentDefinition compositeDef =
             new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
     parserContext.pushContainingComponent(compositeDef);
     configureAutoProxyCreator(parserContext, element);
     List<Element> childElts = DomUtils.getChildElements(element);
     for (Element elt: childElts) {
         String localName = parserContext.getDelegate().getLocalName(elt);
         if (POINTCUT.equals(localName)) {
             parsePointcut(elt, parserContext);
         }
         else if (ADVISOR.equals(localName)) {
             parseAdvisor(elt, parserContext);
         }
         else if (ASPECT.equals(localName)) {
             parseAspect(elt, parserContext);
         }
     }
     parserContext.popAndRegisterContainingComponent();
     return null;
 }
重点先提一下第6行的代码,该行代码的具体实现不跟了但它非常重要,configureAutoProxyCreator方法的作用我用几句话说一下:
- 向Spring容器注册了一个BeanName为org.springframework.aop.config.internalAutoProxyCreator的Bean定义,可以自定义也可以使用Spring提供的(根据优先级来)
- Spring默认提供的是org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator,这个类是AOP的核心类,留在下篇讲解
- 在这个方法里面也会根据配置proxy-target-class和expose-proxy,设置是否使用CGLIB进行代理以及是否暴露最终的代理。
<aop:config>下的节点为<aop:aspect>,想见必然是执行第18行的代码parseAspect,跟进去:
 private void parseAspect(Element aspectElement, ParserContext parserContext) {
     String aspectId = aspectElement.getAttribute(ID);
     String aspectName = aspectElement.getAttribute(REF);
     try {
         this.parseState.push(new AspectEntry(aspectId, aspectName));
         List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
         List<BeanReference> beanReferences = new ArrayList<BeanReference>();
         List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
         for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
             Element declareParentsElement = declareParents.get(i);
             beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
         }
         // We have to parse "advice" and all the advice kinds in one loop, to get the
         // ordering semantics right.
         NodeList nodeList = aspectElement.getChildNodes();
         boolean adviceFoundAlready = false;
         for (int i = 0; i < nodeList.getLength(); i++) {
             Node node = nodeList.item(i);
             if (isAdviceNode(node, parserContext)) {
                 if (!adviceFoundAlready) {
                     adviceFoundAlready = true;
                     if (!StringUtils.hasText(aspectName)) {
                         parserContext.getReaderContext().error(
                                 "<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
                                 aspectElement, this.parseState.snapshot());
                         return;
                     }
                     beanReferences.add(new RuntimeBeanReference(aspectName));
                 }
                 AbstractBeanDefinition advisorDefinition = parseAdvice(
                         aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
                 beanDefinitions.add(advisorDefinition);
             }
         }
         AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
                 aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
         parserContext.pushContainingComponent(aspectComponentDefinition);
         List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
         for (Element pointcutElement : pointcuts) {
             parsePointcut(pointcutElement, parserContext);
         }
         parserContext.popAndRegisterContainingComponent();
     }
     finally {
         this.parseState.pop();
     }
 }
从第20行~第37行的循环开始关注这个方法。这个for循环有一个关键的判断就是第22行的ifAdviceNode判断,看下ifAdviceNode方法做了什么:
 private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
     if (!(aNode instanceof Element)) {
         return false;
     }
     else {
         String name = parserContext.getDelegate().getLocalName(aNode);
         return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||
                 AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
     }
 }
即这个for循环只用来处理<aop:aspect>标签下的<aop:before>、<aop:after>、<aop:after-returning>、<aop:after-throwing method="">、<aop:around method="">这五个标签的。
接着,如果是上述五种标签之一,那么进入第33行~第34行的parseAdvice方法:
private AbstractBeanDefinition parseAdvice(
String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) { try {
this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement))); // create the method factory bean
RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
methodDefinition.setSynthetic(true); // create instance factory definition
RootBeanDefinition aspectFactoryDef =
new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
aspectFactoryDef.setSynthetic(true); // register the pointcut
AbstractBeanDefinition adviceDef = createAdviceDefinition(
adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
beanDefinitions, beanReferences); // configure the advisor
RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
advisorDefinition.getPropertyValues().add(
ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
} // register the final advisor
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition); return advisorDefinition;
}
finally {
this.parseState.pop();
}
}
方法主要做了三件事:
- 根据织入方式(before、after这些)创建RootBeanDefinition,名为adviceDef即advice定义
- 将上一步创建的RootBeanDefinition写入一个新的RootBeanDefinition,构造一个新的对象,名为advisorDefinition,即advisor定义
- 将advisorDefinition注册到DefaultListableBeanFactory中
下面来看做的第一件事createAdviceDefinition方法定义:
private AbstractBeanDefinition createAdviceDefinition(
Element adviceElement, ParserContext parserContext, String aspectName, int order,
RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) { RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
adviceDefinition.setSource(parserContext.extractSource(adviceElement));
adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order); if (adviceElement.hasAttribute(RETURNING)) {
adviceDefinition.getPropertyValues().add(
RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));
}
if (adviceElement.hasAttribute(THROWING)) {
adviceDefinition.getPropertyValues().add(
THROWING_PROPERTY, adviceElement.getAttribute(THROWING));
}
if (adviceElement.hasAttribute(ARG_NAMES)) {
adviceDefinition.getPropertyValues().add(
ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));
} ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
cav.addIndexedArgumentValue(METHOD_INDEX, methodDef); Object pointcut = parsePointcutProperty(adviceElement, parserContext);
if (pointcut instanceof BeanDefinition) {
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
beanDefinitions.add((BeanDefinition) pointcut);
}
else if (pointcut instanceof String) {
RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
beanReferences.add(pointcutRef);
} cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef); return adviceDefinition;
}
首先可以看到,创建的AbstractBeanDefinition实例是RootBeanDefinition,这和普通Bean创建的实例为GenericBeanDefinition不同。然后进入第6行的getAdviceClass方法看一下:
 private Class getAdviceClass(Element adviceElement, ParserContext parserContext) {
     String elementName = parserContext.getDelegate().getLocalName(adviceElement);
     if (BEFORE.equals(elementName)) {
         return AspectJMethodBeforeAdvice.class;
     }
     else if (AFTER.equals(elementName)) {
         return AspectJAfterAdvice.class;
     }
     else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
         return AspectJAfterReturningAdvice.class;
     }
     else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
         return AspectJAfterThrowingAdvice.class;
     }
     else if (AROUND.equals(elementName)) {
         return AspectJAroundAdvice.class;
     }
     else {
         throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
     }
 }
既然创建Bean定义,必然该Bean定义中要对应一个具体的Class,不同的切入方式对应不同的Class:
- before对应AspectJMethodBeforeAdvice
- After对应AspectJAfterAdvice
- after-returning对应AspectJAfterReturningAdvice
- after-throwing对应AspectJAfterThrowingAdvice
- around对应AspectJAroundAdvice
createAdviceDefinition方法剩余逻辑没什么,就是判断一下标签里面的属性并设置一下相应的值而已,至此<aop:before>、<aop:after>两个标签对应的AbstractBeanDefinition就创建出来了。
AOP Bean定义加载----将名为adviceDef的RootBeanDefinition转换成名为advisorDefinition的RootBeanDefinition
下面我们看一下第二步的操作,将名为adviceDef的RootBeanD转换成名为advisorDefinition的RootBeanDefinition,跟一下上面一部分ConfigBeanDefinitionParser类parseAdvice方法的第26行~32行的代码:
RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
advisorDefinition.getPropertyValues().add(
ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
}
这里相当于将上一步生成的RootBeanDefinition包装了一下,new一个新的RootBeanDefinition出来,Class类型是org.springframework.aop.aspectj.AspectJPointcutAdvisor。
第4行~第7行的代码是用于判断<aop:aspect>标签中有没有"order"属性的,有就设置一下,"order"属性是用来控制切入方法优先级的。
AOP Bean定义加载----将BeanDefinition注册到DefaultListableBeanFactory中
最后一步就是将BeanDefinition注册到DefaultListableBeanFactory中了,代码就是前面ConfigBeanDefinitionParser的parseAdvice方法的最后一部分了:
...
// register the final advisor
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
...
跟一下registerWithGeneratedName方法的实现:
 public String registerWithGeneratedName(BeanDefinition beanDefinition) {
     String generatedName = generateBeanName(beanDefinition);
     getRegistry().registerBeanDefinition(generatedName, beanDefinition);
     return generatedName;
 }
第2行获取注册的名字BeanName,和<bean>的注册差不多,使用的是Class全路径+"#"+全局计数器的方式,其中的Class全路径为org.springframework.aop.aspectj.AspectJPointcutAdvisor,依次类推,每一个BeanName应当为org.springframework.aop.aspectj.AspectJPointcutAdvisor#0、org.springframework.aop.aspectj.AspectJPointcutAdvisor#1、org.springframework.aop.aspectj.AspectJPointcutAdvisor#2这样下去。
第3行向DefaultListableBeanFactory中注册,BeanName已经有了,剩下的就是Bean定义,Bean定义的解析流程之前已经看过了,就不说了。
AOP Bean定义加载----AopNamespaceHandler处理<aop:pointcut>流程
回到ConfigBeanDefinitionParser的parseAspect方法:
 private void parseAspect(Element aspectElement, ParserContext parserContext) {
         ...   
         AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
                 aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
         parserContext.pushContainingComponent(aspectComponentDefinition);
         List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
         for (Element pointcutElement : pointcuts) {
             parsePointcut(pointcutElement, parserContext);
         }
         parserContext.popAndRegisterContainingComponent();
     }
     finally {
         this.parseState.pop();
     }
 }
省略号部分表示是解析的是<aop:before>、<aop:after>这种标签,上部分已经说过了,就不说了,下面看一下解析<aop:pointcut>部分的源码。
第5行~第7行的代码构建了一个Aspect标签组件定义,并将Apsect标签组件定义推到ParseContext即解析工具上下文中,这部分代码不是关键。
第9行的代码拿到所有<aop:aspect>下的pointcut标签,进行遍历,由parsePointcut方法进行处理:
 private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
     String id = pointcutElement.getAttribute(ID);
     String expression = pointcutElement.getAttribute(EXPRESSION);
     AbstractBeanDefinition pointcutDefinition = null;
     try {
         this.parseState.push(new PointcutEntry(id));
         pointcutDefinition = createPointcutDefinition(expression);
         pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
         String pointcutBeanName = id;
         if (StringUtils.hasText(pointcutBeanName)) {
             parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
         }
         else {
             pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
         }
         parserContext.registerComponent(
                 new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
     }
     finally {
         this.parseState.pop();
     }
     return pointcutDefinition;
 }
第2行~第3行的代码获取<aop:pointcut>标签下的"id"属性与"expression"属性。
第8行的代码推送一个PointcutEntry,表示当前Spring上下文正在解析Pointcut标签。
第9行的代码创建Pointcut的Bean定义,之后再看,先把其他方法都看一下。
第10行的代码不管它,最终从NullSourceExtractor的extractSource方法获取Source,就是个null。
第12行~第18行的代码用于注册获取到的Bean定义,默认pointcutBeanName为<aop:pointcut>标签中定义的id属性:
- 如果<aop:pointcut>标签中配置了id属性就执行的是第13行~第15行的代码,pointcutBeanName=id
- 如果<aop:pointcut>标签中没有配置id属性就执行的是第16行~第18行的代码,和Bean不配置id属性一样的规则,pointcutBeanName=org.springframework.aop.aspectj.AspectJExpressionPointcut#序号(从0开始累加)
第20行~第21行的代码向解析工具上下文中注册一个Pointcut组件定义
第23行~第25行的代码,finally块在<aop:pointcut>标签解析完毕后,让之前推送至栈顶的PointcutEntry出栈,表示此次<aop:pointcut>标签解析完毕。
最后回头来一下第9行代码createPointcutDefinition的实现,比较简单:
 protected AbstractBeanDefinition createPointcutDefinition(String expression) {
     RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
     beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
     beanDefinition.setSynthetic(true);
     beanDefinition.getPropertyValues().add(EXPRESSION, expression);
     return beanDefinition;
 }
关键就是注意一下两点:
- <aop:pointcut>标签对应解析出来的BeanDefinition是RootBeanDefinition,且RootBenaDefinitoin中的Class是org.springframework.aop.aspectj.AspectJExpressionPointcut
- <aop:pointcut>标签对应的Bean是prototype即原型的
这样一个流程下来,就解析了<aop:pointcut>标签中的内容并将之转换为RootBeanDefintion存储在Spring容器中。
【Spring源码分析】AOP源码解析(上篇)的更多相关文章
- 七、Spring之深入理解AOP源码
		Spring之深入理解AOP源码  在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析.  在之前写的那个AOP示例代码当中有这样一个注解:@Enable ... 
- springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)
		在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ... 
- [源码分析] 从源码入手看 Flink Watermark 之传播过程
		[源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ... 
- JVM源码分析-JVM源码编译与调试
		要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ... 
- k8s client-go源码分析 informer源码分析(2)-初始化与启动分析
		k8s client-go源码分析 informer源码分析(2)-初始化与启动分析 前面一篇文章对k8s informer做了概要分析,本篇文章将对informer的初始化与启动进行分析. info ... 
- k8s client-go源码分析 informer源码分析(3)-Reflector源码分析
		k8s client-go源码分析 informer源码分析(3)-Reflector源码分析 1.Reflector概述 Reflector从kube-apiserver中list&watc ... 
- spring源码分析系列 (15) 设计模式解析
		spring是目前使用最为广泛的Java框架之一.虽然spring最为核心是IOC和AOP,其中代码实现中很多设计模式得以应用,代码看起来简洁流畅,在日常的软件设计中很值得借鉴.以下是对一些设计模式的 ... 
- Spring源码分析(九)解析默认标签中的自定义标签元素
		摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 到这里我们已经完成了分析默认标签的解析与提取过程,或许涉及的内容太多,我 ... 
- MapReduce之提交job源码分析 FileInputFormat源码解析
		MapReduce之提交job源码分析 job 提交流程源码详解 //runner 类中提交job waitForCompletion() submit(); // 1 建立连接 connect(); ... 
- 精尽MyBatis源码分析 - MyBatis-Spring 源码分析
		该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ... 
随机推荐
- Ubuntu配置Nginx虚拟主机和支持ThinkPHP
			[Nginx配置虚拟主机] 每一个 server { listen 80; server_name www.a.com; ..... } 就表示一台虚拟域名, 然后对应的 ... 
- Android Camera  摄像 demo
			google 在Android 5.0推出 Camera2 这个类,用于替换 Camera,但是Camera2要求android sdk 最低版本为 minSdkVersion = 21 ... 
- web自动化一(selenium+python+pycharm环境搭建)
			年前公司刚刚搭起了web自动化测试框架的环境,趁着过完年还没全部忘掉,准备把如何搭建环境的方法和大家分享下,有哪里不对的地方,请批评指正,共同进步,共勉! 为此我把搭建环境所需的软件打包上传到百度云, ... 
- MyISAM 和InnoDB 讲解
			1.InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定. 2.基本的差别为:MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持. ... 
- CentOS7上LNMP安装包一步搭建LNMP环境
			系统需求: CentOS/RHEL/Fedora/Debian/Ubuntu/Raspbian Linux系统 需要5GB以上硬盘剩余空间 需要128MB以上内存(如果为128MB的小内存VPS,Xe ... 
- JS中的Undefined和Null的区别
			Undefined ①在声明变量时,如果没有给变量赋值,则这个变量就是undefined类型: ②访问未声明的变量会报错误消息,但这样的变量使用 typeof 测试,返回的值为Undefined. 即 ... 
- UVA-673 括号匹配--栈
			如果是一个合法的序列,每对配对的括号的两个字符('(' 和 ')' 或者 '[' 和 ']')一定是相邻的,每次判断下该字符是否有配对即可. 如果配对,将左括号出栈即可.特别注意:空格也是合法的. A ... 
- postman 中调试接口的小记录
			1.form-data: 就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开.既可以上传键值对,也可以上传文件.当上传的字段是文件 ... 
- 对于JAVA程序优化的一些想法,读书有感.治疗强迫症良药
			在深入了解Java虚拟机里读到:在try{}块里面执行代码,比if(x!=null)效率要高,前提是被catch的几率很低的情况下. 但是 在Effective Java里读到:因为异常机制的设计初衷 ... 
- 微信Token小识
			在调用自定义菜单接口的时候,调用接口 https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN返回: " ... 
