Spring源码分析三:Bean标签解析之BeanDefinition
文章目录
- (一)序言
- (二)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相关推荐
- Spring源码分析系列——bean创建过程分析(三)——工厂方法创建bean
前言 spring创建bean的方式 测试代码准备 createBeanInstance()方法分析 instantiateUsingFactoryMethod()方法分析 总结 spring创建be ...
- Spring源码分析(三)
Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- 【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- Spring 源码分析(三) —— AOP(五)创建代理
2019独角兽企业重金招聘Python工程师标准>>> 创建代理 代理的定义其实非常简单,就是改变原来目标对象方法调用的运行轨迹.这种改变,首先会对这些方法进行拦截,从而为这些方法提 ...
- spring 源码分析(1)-xml文件解析
我们在最开始接触spring的时候,看到不少书spring入门的例子如下 ApplicationContext atx = new ClassPathXmlApplicationContext(&qu ...
- Spring源码分析:Bean加载流程概览及配置文件读取
很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...
- Spring 源码分析(三) —— AOP(二)Spring AOP 整体架构
2019独角兽企业重金招聘Python工程师标准>>> Spring AOP 架构 先是生成代理对象,然后是拦截器的作用,最后是编织的具体实现.这是AOP实现的三个步 ...
- Spring源码分析系列-Bean的生命周期(总结篇)
ApplicationContext和BeanFactory BeanFactory是Spring中的顶层接口,只负责管理bean,而ApplicationContext也实现了BeanFacto ...
最新文章
- 配置Linux两节点SSH密钥信任
- NYOJ 1085 数单词 (AC自动机模板题)
- 阿里云centos7安装和卸载图形化操作界面
- 《信号与系统》期中总结
- python获取计算机信息系统数据罪_使用 python 收集获取 Linux 系统主机信息
- css3层级穿透,css页面滑动穿透的两种解决办法
- post报文给mqtt服务器没有响应,post请求转为mqtt的方法
- 5.5使用Cucumber来测试
- 一名IT民工开通博客
- matlab中normfit的使用
- 增强现实入门实战,使用ArUco标记实现增强现实
- Python 医学知识图谱问答系统(一),建立医学知识图谱,基于neo4j知识图谱的医学问答体系
- SourceTree系列1:SourceTree连接github从无到有
- 2022年软件设计师考试复习资料(1)
- wampserver橙色解决方法汇总
- linux执行命令全称,Linux常用命令全称
- PTA 计算谱半径 —— 简单题
- 夏日PHP图书管理系统 v0.3(源码)
- 转:黑客讲故事:攻下隔壁女生路由器后,我都做了些什么
- pf与ckf_CKF Kadat—彪悍又带着野性,坚固强度与尺寸的战术折