文章目录

  • (一)序言
  • (二)Spring生成BeanDefinition
    • 1、Spring Bean解析入口
    • 2、Spring Bean内置标签解析
      • (1)bean标签解析——processBeanDefinition方法
      • (2)bean元素解析——parseBeanDefinitionElement方法(核心)
        • ①创建AbstractBeanDefinition实例
        • ②spring内置硬编码处理
        • ③解析元数据
        • ④解析lookup-method
        • ⑤解析replaced-method
        • ⑥解析构造函数参数
        • ⑦解析property子元素
        • ⑧解析qualifier子元素

(一)序言

在Spring源码分析容器篇中提到将spring中的bean解析并注入到beanFactory中,当时因为容器篇幅太长仅仅是一笔带过,本文是对spring加载bean中生成BeanDefinition的详细分析,也是笔者对spring深入学习的过程,文中有出错的地方,还望大家能够及时指出。

(二)Spring生成BeanDefinition

本次入口分析是Spring容器篇中第二步obtainFreshBeanFactory#…#loadBeanDefinitions方法,obtainFreshBeanFactory方法是包含了创建容器和解析配置并生成BeanDefinition,创建容器是由DefaultListableBeanFactory直接创建,而解析配置文件是本文重点分析的对象。

1、Spring Bean解析入口

Spring源码的风格是将一切比较复杂的方法进行封装和抽象,便于其他地方可调用,然而真正意义上在干活的,也是我们所关心的是do-*开头的方法才是具体实现的地方,loadBeanDefinitions(resource)->loadBeanDefinitions(encodedResource)->doLoadBeanDefinitions,代码如下:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions(new EncodedResource(resource));//进行编码封装配置资源
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {if (logger.isTraceEnabled()) {logger.trace("Loading XML bean definitions from " + encodedResource);}Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");}try (InputStream inputStream = encodedResource.getResource().getInputStream()) {//获取资源输入流InputSource inputSource = new InputSource(inputStream);//流转化if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}return doLoadBeanDefinitions(inputSource, encodedResource.getResource());//开始调用真正解析BeanDefinition}catch (IOException ex) {throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);}finally {currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();}}
}

在spring进行一系列的包装、封装和转化操作后,进行调用doLoadBeanDefinitions对配置文件进行解析,首先是先对文件进行文档化Document转化,然后对doc进行标签解析并调用registerBeanDefinitions方法,主要就是根据配置文件中的各种标签和属性,进行封装BeanDefinition,代码如下:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {Document doc = doLoadDocument(inputSource, resource);//文件转化为文档类,底层是转化包,此处不多解释int count = registerBeanDefinitions(doc, resource);//生成BeanDefinition,重点分析if (logger.isDebugEnabled()) {logger.debug("Loaded " + count + " bean definitions from " + resource);}return count;//后面一连串的catch是捕获异常,便于spring能精确提示出解析异常}catch (BeanDefinitionStoreException ex) {throw ex;}catch (SAXParseException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);}catch (SAXException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),"XML document from " + resource + " is invalid", ex);}catch (ParserConfigurationException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Parser configuration exception parsing XML from " + resource, ex);}catch (IOException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"IOException parsing XML document from " + resource, ex);}catch (Throwable ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Unexpected exception parsing XML document from " + resource, ex);}}

根据上述方式中转化后的文档类进行注册BeanDefinition,创建读取对象,然后根据读取对象进行封装,如下:

 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();//创建读取器int countBefore = getRegistry().getBeanDefinitionCount();//统计已存在的BeanDefinition数量documentReader.registerBeanDefinitions(doc, createReaderContext(resource));//解析doc并生成BeanDefinitionreturn getRegistry().getBeanDefinitionCount() - countBefore;//此次文件解析的数量}

spring源码风格,概念封装,实际干活的是doRegisterBeanDefinitions,一笑而过,继续看码:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;doRegisterBeanDefinitions(doc.getDocumentElement());
}
protected void doRegisterBeanDefinitions(Element root) {this.delegate = createDelegate(getReaderContext(), root, parent);//创建委托类对象if (this.delegate.isDefaultNamespace(root)) {String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);//是否有profile属性设置,个性化属性生效if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isDebugEnabled()) {logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());}return;}}}preProcessXml(root);//预解析前子类重写,开放性设计,此处方法是默认为空parseBeanDefinitions(root, this.delegate);//核心点,真正开始处理各种标签和beanpostProcessXml(root);//解析后子类重写,开放性设计,默认为空实现this.delegate = parent;
}

望眼欲穿的点终于被发现,parseBeanDefinitions生成各种BeanDefinition的入口,分为自定义和默认标签解析,接着看:

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)) {//spring默认的定义空间,则使用默认的解析如配置文件指定的beanparseDefaultElement(ele, delegate);//普通的import、alias、bean、beans中被定义的bean}else{delegate.parseCustomElement(ele);//采用自定义的,比如采用tx定义命名空间、jdbc命名等等}}}}else{delegate.parseCustomElement(root);}
}

2、Spring Bean内置标签解析

Spring支持普通的import、alias、bean、beans中被定义的bean,本文主要是分析配置文件XMl定义的bean解析,注解方式实现的bean会放到后面分析Springboot实现方式时去重点分析,原理其实相差不多,直接上干货,parseDefaultElement代码如下:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {//import文件外部导入文件中解析importBeanDefinitionResource(ele);}else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {//alias别名bean处理processAliasRegistration(ele);}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//bean标签处理(也是配置文件中使用比较常用的,重点分析)processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {//beans标签处理doRegisterBeanDefinitions(ele);}
}

(1)bean标签解析——processBeanDefinition方法

在Spring配置文件中,比较核心、常见的也是bean标签定义Bean,同时最复杂的也是processBeanDefinition(重点分析),代码如下:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {//返回BeanDefinitionHolder对象,包含基本bean属性、元素BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {//判断bdHolder对象不为空时会判断是否存在子节点,持续解析bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {//将bdHolder注册到容器中BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}//发送事件通知,通知想关的监听器,表明bean加载完毕getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}
}

Spring的bean标签属性或元素解析,交由BeanDefinitionParserDelegate类来具体解析,parseBeanDefinitionElement代码如下:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {return parseBeanDefinitionElement(ele, null);
}

bean标签具体解析内容:id、name和多个name名称、别名alias、beanName唯一性检查,生成beanDefinition(实际上是GenericBeanDefinition生成),beanName是否命名等步骤,最后生成使用生成的beanDefinition来封装BeanDefinitionHolder实例,逻辑代码如下:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {String id = ele.getAttribute(ID_ATTRIBUTE);//id属性获取String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);//name属性获取List<String> aliases = new ArrayList<>();if (StringUtils.hasLength(nameAttr)) {//别名处理String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}String beanName = id;if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {//去除别名中第一个beanName值beanName = aliases.remove(0);if (logger.isTraceEnabled()) {logger.trace("No XML 'id' specified - using '" + beanName +"' as bean name and " + aliases + " as aliases");}}//检查beanName唯一性if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}//使用GenericBeanDefinition封装beanDefinition的各种属性AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {if (!StringUtils.hasText(beanName)) {try {//beanName不存在,则采用spring默认的命名规则if (containingBean != null) {//当前BeanDefinition已存在,则使用generateBeanName方法默认生成beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else{//不存在containingBean时则使用Spring自身定义的beanName和CLassName的相关约束beanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null &&beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (logger.isTraceEnabled()) {logger.trace("Neither XML 'id' nor 'name' specified - " +"using generated bean name [" + beanName + "]");}}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}String[] aliasesArray = StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;
}

(2)bean元素解析——parseBeanDefinitionElement方法(核心)

Spring对bean标签的属性采用硬编码处理方式,分别使用了以下八个核心点来处理,如下①②③④⑤⑥⑦⑧所示:

public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));String className = null;if (ele.hasAttribute(CLASS_ATTRIBUTE)) {//处理bean标签中的属性classclassName = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;if (ele.hasAttribute(PARENT_ATTRIBUTE)) {//处理bean标签中的属性parentparent = ele.getAttribute(PARENT_ATTRIBUTE);}try {//使用GenericBeanDefinition来生成AbstractBeanDefinition实例,代码如①AbstractBeanDefinition bd = createBeanDefinition(className, parent);//spring内置处理bean属性的硬编码处理逻辑,代码如②parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));//description属性处理parseMetaElements(ele, bd);//解析元数据如③parseLookupOverrideSubElements(ele, bd.getMethodOverrides());//解析lookup-method如④parseReplacedMethodSubElements(ele, bd.getMethodOverrides());//解析replaced-method如⑤parseConstructorArgElements(ele, bd);//解析构造函数参数如⑥parsePropertyElements(ele, bd);//解析property子元素如⑦parseQualifierElements(ele, bd);//解析qualifier子元素如⑧bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;}catch (ClassNotFoundException ex) {error("Bean class [" + className + "] not found", ele, ex);}catch (NoClassDefFoundError err) {error("Class that bean class [" + className + "] depends on not found", ele, err);}catch (Throwable ex) {error("Unexpected failure during bean definition parsing", ele, ex);}finally {this.parseState.pop();}return null;
}

①创建AbstractBeanDefinition实例

GenericBeanDefinition类创建具体实例,具体代码如下

protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)throws ClassNotFoundException {return BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader());
}
public static AbstractBeanDefinition createBeanDefinition(@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {GenericBeanDefinition bd = new GenericBeanDefinition();bd.setParentName(parentName);if (className != null) {if (classLoader != null) {bd.setBeanClass(ClassUtils.forName(className, classLoader));}else {bd.setBeanClassName(className);}}return bd;
}

②spring内置硬编码处理

bean属性:singleton、scope、abstract、lazy、autowire、depends_on、autowire_candidate、primary、init_method、destroy_method、factory_method和factory_bean,具体代码如下:

 public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,BeanDefinition containingBean, AbstractBeanDefinition bd) {//处理singleton属性if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);}else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {//处理scope属性bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));}else if (containingBean != null) {bd.setScope(containingBean.getScope());}//处理abstract属性if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));}//处理lazy懒加载属性String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);if (isDefaultValue(lazyInit)) {lazyInit = this.defaults.getLazyInit();}bd.setLazyInit(TRUE_VALUE.equals(lazyInit));//处理自动注入属性autowireString autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);bd.setAutowireMode(getAutowireMode(autowire));if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {//处理depends_on依赖String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));}//处理autowire_candidate属性String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);if (isDefaultValue(autowireCandidate)) {String candidatePattern = this.defaults.getAutowireCandidates();if (candidatePattern != null) {String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));}}else {bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));}//处理bean的primary属性if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));}//处理初始方法属性init_methodif (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);bd.setInitMethodName(initMethodName);}else if (this.defaults.getInitMethod() != null) {bd.setInitMethodName(this.defaults.getInitMethod());bd.setEnforceInitMethod(false);}//处理destroy_method销毁方法属性if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);bd.setDestroyMethodName(destroyMethodName);}else if (this.defaults.getDestroyMethod() != null) {bd.setDestroyMethodName(this.defaults.getDestroyMethod());bd.setEnforceDestroyMethod(false);}//处理factory_method属性if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));}//处理factory_bean属性if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));}return bd;}

③解析元数据

代码如下:

 public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {NodeList nl = ele.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {Element metaElement = (Element) node;String key = metaElement.getAttribute(KEY_ATTRIBUTE);String value = metaElement.getAttribute(VALUE_ATTRIBUTE);BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);attribute.setSource(extractSource(metaElement));attributeAccessor.addMetadataAttribute(attribute);}}}

④解析lookup-method

代码如下:

 public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {Element ele = (Element) node;String methodName = ele.getAttribute(NAME_ATTRIBUTE);String beanRef = ele.getAttribute(BEAN_ELEMENT);LookupOverride override = new LookupOverride(methodName, beanRef);override.setSource(extractSource(ele));overrides.addOverride(override);}}
}

⑤解析replaced-method

代码如下:

 public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {Element replacedMethodEle = (Element) node;String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);//name属性String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);//回调方法ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);for (Element argTypeEle : argTypeEles) {String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));if (StringUtils.hasText(match)) {replaceOverride.addTypeIdentifier(match);}}replaceOverride.setSource(extractSource(replacedMethodEle));overrides.addOverride(replaceOverride);}}
}

⑥解析构造函数参数

代码如下:

public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {parseConstructorArgElement((Element) node, bd);}}
}

⑦解析property子元素

代码如下:

public void parsePropertyElements(Element beanEle, BeanDefinition bd) {NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {parsePropertyElement((Element) node, bd);}}
}

⑧解析qualifier子元素

代码如下:

public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) {parseQualifierElement((Element) node, bd);}}
}

Spring源码分析三:Bean标签解析之BeanDefinition相关推荐

  1. Spring源码分析系列——bean创建过程分析(三)——工厂方法创建bean

    前言 spring创建bean的方式 测试代码准备 createBeanInstance()方法分析 instantiateUsingFactoryMethod()方法分析 总结 spring创建be ...

  2. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  3. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  4. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  5. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  6. Spring 源码分析(三) —— AOP(五)创建代理

    2019独角兽企业重金招聘Python工程师标准>>> 创建代理 代理的定义其实非常简单,就是改变原来目标对象方法调用的运行轨迹.这种改变,首先会对这些方法进行拦截,从而为这些方法提 ...

  7. spring 源码分析(1)-xml文件解析

    我们在最开始接触spring的时候,看到不少书spring入门的例子如下 ApplicationContext atx = new ClassPathXmlApplicationContext(&qu ...

  8. Spring源码分析:Bean加载流程概览及配置文件读取

    很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...

  9. Spring 源码分析(三) —— AOP(二)Spring AOP 整体架构

    2019独角兽企业重金招聘Python工程师标准>>> Spring AOP 架构         先是生成代理对象,然后是拦截器的作用,最后是编织的具体实现.这是AOP实现的三个步 ...

  10. Spring源码分析系列-Bean的生命周期(总结篇)

    ApplicationContext和BeanFactory   BeanFactory是Spring中的顶层接口,只负责管理bean,而ApplicationContext也实现了BeanFacto ...

最新文章

  1. 配置Linux两节点SSH密钥信任
  2. NYOJ 1085 数单词 (AC自动机模板题)
  3. 阿里云centos7安装和卸载图形化操作界面
  4. 《信号与系统》期中总结
  5. python获取计算机信息系统数据罪_使用 python 收集获取 Linux 系统主机信息
  6. css3层级穿透,css页面滑动穿透的两种解决办法
  7. post报文给mqtt服务器没有响应,post请求转为mqtt的方法
  8. 5.5使用Cucumber来测试
  9. 一名IT民工开通博客
  10. matlab中normfit的使用
  11. 增强现实入门实战,使用ArUco标记实现增强现实
  12. Python 医学知识图谱问答系统(一),建立医学知识图谱,基于neo4j知识图谱的医学问答体系
  13. SourceTree系列1:SourceTree连接github从无到有
  14. 2022年软件设计师考试复习资料(1)
  15. wampserver橙色解决方法汇总
  16. linux执行命令全称,Linux常用命令全称
  17. PTA 计算谱半径 —— 简单题
  18. 夏日PHP图书管理系统 v0.3(源码)
  19. 转:黑客讲故事:攻下隔壁女生路由器后,我都做了些什么
  20. pf与ckf_CKF Kadat—彪悍又带着野性,坚固强度与尺寸的战术折

热门文章

  1. ORCAD原理图检查
  2. ROS海龟跟随(坐标变换)
  3. cocos2dx 3d开源项目 fantasyWarrior3D 从零走起 6完结 [AttackManagerGameMaster]
  4. java对接支付宝三方登录
  5. java实现即时通讯软件
  6. video sematic segmentation视频语义分割方向相关论文罗列+数据集下载链接
  7. Vue 接入firebase验证、Google登陆、Facebook登陆完整流程
  8. 如何将手机里的PDF文件转换成Word文档
  9. 运维审计系统----堡垒机的部署
  10. 基于堡垒主机概念的运维审计系统