Spring(五):bean标签解析(二)

//针对子bean标签

else if (containingBean != null) {

//这个设置是针对子bean标签的,因为只有bean子标签才会有containning bean

//也就是父标签,所以默认的子bean的scope是父bean一样的!

//这里避免是内部bean,所以使用containning bean的scope来作为默认的参考

// Take default from containing bean in case of an inner bean definition.

bd.setScope(containingBean.getScope());

}

//判断有没有abstract属性

if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {

//有abstract属性就进行,并且还规定abstract属性必须只能为true或者false

//否则会失效

//通过用true字符串去进行equals比较

bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));

}

//解析lazyInit属性

String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);

//判断lazyInit属性是不是为default

//为空,或者值为default,返回true

if (isDefaultValue(lazyInit)) {

//为空,或者lazyInit属性为default,那就设置默认值

//默认值由DocumentDefaultsDefinition决定

lazyInit = this.defaults.getLazyInit();

}

//注入lazyInit属性进bean

bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

//获取autowire属性

String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);

//注入autowire属性,总共有4种,其中一种弃用

//1.byName,2.byType,3.construct,4.autodetect

bd.setAutowireMode(getAutowireMode(autowire));

//判断有没有depends-on属性

if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {

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);

//如果没有设置,或者为default

if (isDefaultValue(autowireCandidate)) {

//那就使用默认值

String candidatePattern = this.defaults.getAutowireCandidates();

//autowire-candidate属性可以设置多个,使用逗号间隔

//只有默认值,才会有多个的情况!

if (candidatePattern != null) {

//所以如果有设计的话,需要解析并且使用字符串数组来接收

String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);

bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));

}

}

else {

//如果不是默认值,只可能是true或者false

//同理使用true字符串去进行equals比较

bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));

}

//解析primary属性

if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {

//同理为true或者为false

//不为空就进行注入

bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));

}

//下面是生命周期的配置

//解析init-method属性

if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {

//不为空就进行注入

String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);

bd.setInitMethodName(initMethodName);

}

//如果init-method属性没有设置(创建的时候执行的方法)

//那就去判断有没有默认值

else if (this.defaults.getInitMethod() != null) {

//没有设置且有默认值,init-method属性就设为默认值

bd.setInitMethodName(this.defaults.getInitMethod());

//并且init-method的方法设置为不强制执行

//因为是默认的,所以不强制执行

bd.setEnforceInitMethod(false);

}

//解析destory-method属性

//bean销毁时执行的方法

//与method-init属性一样,有的注入,没有就判断默认的有没有

//没有设置且有默认的,是不会强制执行

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;

}

总结一下这个方法

  1. 创建BeanDefinition,为GenericBeanDefinition

  2. 对bean的所有属性进行解析

至此,该bean标签基本上已经所有属性已经解析完成了,剩下的就是里面的子标签了

scope作用域与init-method、destory-method生命周期

官方文档截图

在这里插入图片描述

在这里插入图片描述

parseMetaElements

parseBeanDefinitionAttributes已经帮我们将bean的属性都解析完了,接下来就是子标签的解析了

子标签的解析,首先是对元数据meta的解析

可能很少人用过meta子标签,因为meta标签的作用其实就是做一个额外的声明信息,声明了之后,该bean对应的BeanDefinition可以通过getAttribute方法去获取里面的信息

在这里插入图片描述

下面是源码

在这里插入图片描述

public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {

//meta是子标签

//所以,先要获取当前标签的所有子标签

NodeList nl = ele.getChildNodes();

//遍历子标签

for (int i = 0; i < nl.getLength(); i++) {

Node node = nl.item(i);

//提取出meta子标签出来

if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {

Element metaElement = (Element) node;

//取meta子标签的key和value属性

String key = metaElement.getAttribute(KEY_ATTRIBUTE);

String value = metaElement.getAttribute(VALUE_ATTRIBUTE);

//meta子标签对应的对象为BeanMetadataAttribute

BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);

attribute.setSource(extractSource(metaElement));

//存进BeanDefinition中(因为放进来的参数为BeanDefinition)

//底层为一个LinkedHashMap

//底层是交由AttributeAccessorSupport去实现的

attributeAccessor.addMetadataAttribute(attribute);

}

}

}

在这里插入图片描述

@Override

public void setAttribute(String name, @Nullable Object value) {

Assert.notNull(name, “Name must not be null”);

//如果value为null

//代表删除动作

//不为null,才是插入,插入进底层的LinkedHashMap中

if (value != null) {

this.attributes.put(name, value);

}

else {

removeAttribute(name);

}

}

在这里插入图片描述

parseLookupOverrideSubElements

解析来解析的是lookup-method子标签

这个子标签跟meta一样,我们都很少使用,但在某些场景,这个标签是非常有用的,这个标签通常称为获取器注入

它的作用是,可以去直接获取其他bean作为方法的返回值,即父bean把一个方法声明为返回某一个类型的bean,我们可以通过lookup-method的子标签,从spring容器中去取出这个bean,然后给父bean的方法进行返回,总的来说,就是替换了返回bean

public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {

//原理与meta解析几乎一致

//获取子标签

NodeList nl = beanEle.getChildNodes();

//遍历子标签

for (int i = 0; i < nl.getLength(); i++) {

Node node = nl.item(i);

//判断是否是lookup-method标签

if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {

Element ele = (Element) node;

//获取lookup-method标签的name和bean属性

//bean对应的是容器中的另外一个bean的id属性

//name属性对应的是父bean的方法名!

String methodName = ele.getAttribute(NAME_ATTRIBUTE);

String beanRef = ele.getAttribute(BEAN_ELEMENT);

//lookup-method标签对应的java对象为LookUpOverride

LookupOverride override = new LookupOverride(methodName, beanRef);

override.setSource(extractSource(ele));

//同理放入BeanDefinition中

//这里BeanDefinition存储的底层是一个CopyOnWriteArraySet

overrides.addOverride(override);

}

}

}

在这里插入图片描述

parseReplacedMethodSubElements

接下来是解析replaced-method子标签

这个标签也很少有,也还是说一下它的用途,replaced-method标签可以取替换返回实体bean,还可以动态地更改原有方法的逻辑,总的来说就是可以将父bean的方法整体给替换掉,也就是执行的方法已经是另外一个bean的方法了,但这个方法并不是可以随便写的,必须另外一个bean实现MethodReplacer接口,重写的那个方法才可以进行替换

下面是解析的源码

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;

//获取name属性,代表要被替换的方法名

String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);

//获取replace属性,代表要替换成哪个bean的id

String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);

//replace-method标签对应的Java实体为replaceOverride

ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);

// Look for arg-type match elements.

List 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));

//同样存放进BeanDefinitions,注意这里的存放地方与lookup-method是同一个Set

//因为都是MethodOverrides

overrides.addOverride(replaceOverride);

}

}

}

parseConstryctorArgElements

下面就是到我们常用的子标签了,解析construct-arg标签

该标签就是可以使用对应的构造函数去创建bean

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)) {

//判断如果是construct-arg子标签,进行解析

parseConstructorArgElement((Element) node, bd);

}

}

}

parseConstructorArgElement

这个方法是可以解析一个construct-arg标签的逻辑(这代码量一看就十分复杂)

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {

//解析construct-arg标签里面的index、type和name属性

String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);

String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);

String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

//判断index属性是否为空

if (StringUtils.hasLength(indexAttr)) {

//将索引值转为Integer类型,并且判断不能小于0

try {

int index = Integer.parseInt(indexAttr);

if (index < 0) {

//索引值小于0 报错

error(“‘index’ cannot be lower than 0”, ele);

}

else {

//索引值正确

try {

//开启跟踪

this.parseState.push(new ConstructorArgumentEntry(index));

//获取construct-arg的value子标签里面的值

Object value = parsePropertyValue(ele, bd, null);

//使用ValueHolder去封装value

//因为后续还需要保存该value对应构造函数的哪个变量名、哪种类型

ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);

//如果有给type和name属性(construct-arg标签)

//就与value一起保存进valueHolder里面

if (StringUtils.hasLength(typeAttr)) {

valueHolder.setType(typeAttr);

}

if (StringUtils.hasLength(nameAttr)) {

valueHolder.setName(nameAttr);

}

//valueHolder标明来源于哪个标签

valueHolder.setSource(extractSource(ele));

//这里是判断BeanDefinition里面是否已经存在这个索引了

//如果已经存在这个索引,就代表出现了相同的索引

//一个构造方法怎么可能会出现相同索引的参数value

if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {

//报错

error("Ambiguous constructor-arg entries for index " + index, ele);

}

//如果没有出现过

else {

//那就存进去

//从这里可以看到,存储构造函数的底层是一个LinkedHashMap

//ConstructorArgumentVlues就是存储BeanDefinition的构造参数的

//并且通过Map,来让索引值和value可以映射

bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);

}

}

finally {

//接触监控

this.parseState.pop();

}

}

}

catch (NumberFormatException ex) {

//索引值类型不正确,报错

error(“Attribute ‘index’ of tag ‘constructor-arg’ must be an integer”, ele);

}

}

//如果index属性为空

//那就按顺序

else {

try {

//与前面是一样的过程

this.parseState.push(new ConstructorArgumentEntry());

Object value = parsePropertyValue(ele, bd, null);

ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);

if (StringUtils.hasLength(typeAttr)) {

valueHolder.setType(typeAttr);

}

if (StringUtils.hasLength(nameAttr)) {

valueHolder.setName(nameAttr);

}

valueHolder.setSource(extractSource(ele));

//注意这里,使用ConstructorArgumentValues去存放构造参数值

//不再使用LinkedHashMap了,而是一个ArrayList

//因为都没给索引,不需要进行Key,value映射,直接用ArrayList的底层数组索引来代表即可

//可以按顺序进行存放

bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);

}

finally {

//解除监控

this.parseState.pop();

}

}

}

在这里插入图片描述

整个流程大体是

  • 解析construct-arg的name、type和index属性

  • 如果配置了Index属性

  • 使用ValueHolder去封装解析出来的元素

  • 并且将type、name和index一起封装进valueHolder

  • 判断ConstructorArgumentValues的indexArgumentValues中是否已经有这个该索引位置

  • 最后将valueHolder存放进ConstructorArgumentValues的indexArgumentValues中,即LinkedHashMap里面,使用(index,valueHolder)的键值对方式

  • 如果没有配置Index属性

  • 同样使用ValueHolder去封装解析出来的元素

  • 并且将Type、name和index一起封装进valueHolder

  • 最后将valueHolder存放进ConstructArgumentValues的genericArgumentValues中,即ArrayList里面,这样设计是想以默认升序顺序来顶替缺省的Index(ArrayList的索引可以充当value的索引,并且只能按顺序去存放,也是因为按顺序去存放value,所以不需要考虑这个索引位置已经存在value)

parsePropertyValue

在这里插入图片描述

这个方法就是处理当前construct-arg标签里面的value子标签(又是一大串代码)

@Nullable

public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {

String elementName = (propertyName != null ?

“ element for property '” + propertyName + “'” :

“ element”);

// Should only have one child element: ref, value, list, etc.

//按照约定,construct-arg应该只有一个子标签

NodeList nl = ele.getChildNodes();

Element subElement = null;

//遍历子标签

for (int i = 0; i < nl.getLength(); i++) {

Node node = nl.item(i);

//筛选出不是meta和description子标签的

//所以construct-arg是可以有多个子标签的,但只能是meta和description

//且倘若出现 ref, value, list, etc其中一个,后面的meta和description会导致报错

if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&

!nodeNameEquals(node, META_ELEMENT)) {

// Child element is what we’re looking for.

//筛选了之后,就代表这是我们想要的子标签

//这也说明了一个现象,在construct-arg中.meta和description子标签是失效的!

//下面这个判断就很有意思了

//它是限制了constrcuct-arg里面只能有一个非meta、description的子标签

//即外层if判断为true只能进来一次

//第一次会为null,但第二次循环如果还能进来就不再为null了

if (subElement != null) {

//设置了多个子标签,报错

error(elementName + " must not contain more than one sub-element", ele);

}

else {

//从这次循始将要结束时,subElement就不再为null了

//同时这里也是记录了value子标签或其他子标签

subElement = (Element) node;

}

}

}

//获取construct-arg的ref和value属性

boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);

boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);

//假如两个同时拥有,或者两个属性拥有一个,但解析出的子标签不为null,证明有子标签

//就代表出现了重复给构造参数了

if ((hasRefAttribute && hasValueAttribute) ||

((hasRefAttribute || hasValueAttribute) && subElement != null)) {

//重复给构造参数,报错处理

error(elementName +

" is only allowed to contain either ‘ref’ attribute OR ‘value’ attribute OR sub-element", ele);

}

//如果并没有重复,假如是ref属性

if (hasRefAttribute) {

String refName = ele.getAttribute(REF_ATTRIBUTE);

if (!StringUtils.hasText(refName)) {

error(elementName + " contains empty ‘ref’ attribute", ele);

}

//ref属性用RuntimeBeanReference对象存储

RuntimeBeanReference ref = new RuntimeBeanReference(refName);

//标明来源

ref.setSource(extractSource(ele));

//返回

return ref;

}

//如果是value属性

else if (hasValueAttribute) {

//使用TypedStringValue进行存储

TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));

valueHolder.setSource(extractSource(ele));

//返回

return valueHolder;

}

//如果是子标签形式

else if (subElement != null) {

//交由parsePropertySubElement继续处理子标签

//因为子标签也是有多种形式的

return parsePropertySubElement(subElement, bd);

}

//既没有value属性,又没有ref属性,又没有子标签

else {

// Neither child element nor “ref” or “value” attribute found.

error(elementName + " must specify a ref or value", ele);

return null;

}

}

从代码上可以看出几个重要点

  • 不可以同时存在ref、value和子标签

  • 子标签只能出现一次

  • meta、description标签会默认不参与循环计算,既标签失效

  • 对于ref、value、子标签分别写了一套处理,使用不同对象去承载并返回

parsePropertySubElement

这个方法是处理子标签的

下面是源码

在这里插入图片描述

@Nullable

public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {

//判断是不是默认的命名空间

if (!isDefaultNamespace(ele)) {

//不是的话,交由另外一组实现(注释方式,这里不做讲解了)

return parseNestedCustomElement(ele, bd);

}

//判断是不是bean标签

else if (nodeNameEquals(ele, BEAN_ELEMENT)) {

//bean标签的处理

BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);

if (nestedBd != null) {

nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);

}

return nestedBd;

}

//判断是不是ref标签

//做ref的处理

else if (nodeNameEquals(ele, REF_ELEMENT)) {

// A generic reference to any name of any bean.

String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);

boolean toParent = false;

if (!StringUtils.hasLength(refName)) {

// A reference to the id of another bean in a parent context.

refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);

toParent = true;

if (!StringUtils.hasLength(refName)) {

error(“‘bean’ or ‘parent’ is required for element”, ele);

return null;

}

}

if (!StringUtils.hasText(refName)) {

error(“ element contains empty target attribute”, ele);

return null;

}

RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);

ref.setSource(extractSource(ele));

return ref;

}

//判断是不是idref标签

//做idref的处理

else if (nodeNameEquals(ele, IDREF_ELEMENT)) {

return parseIdRefElement(ele);

}

//处理value标签

else if (nodeNameEquals(ele, VALUE_ELEMENT)) {

return parseValueElement(ele, defaultValueType);

}

//处理null标签

else if (nodeNameEquals(ele, NULL_ELEMENT)) {

// It’s a distinguished null value. Let’s wrap it in a TypedStringValue

// object in order to preserve the source location.

TypedStringValue nullHolder = new TypedStringValue(null);

nullHolder.setSource(extractSource(ele));

return nullHolder;

}

//处理array标签

else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {

return parseArrayElement(ele, bd);

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

最后,强调几点:

  • 1. 一定要谨慎对待写在简历上的东西,一定要对简历上的东西非常熟悉。因为一般情况下,面试官都是会根据你的简历来问的; 能有一个上得了台面的项目也非常重要,这很可能是面试官会大量发问的地方,所以在面试之前好好回顾一下自己所做的项目;
  • 2. 和面试官聊基础知识比如设计模式的使用、多线程的使用等等,可以结合具体的项目场景或者是自己在平时是如何使用的;
  • 3. 注意自己开源的Github项目,面试官可能会挖你的Github项目提问;

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目。

面试答案

三面头条+四面阿里+五面腾讯拿offer分享面经总结,最终入职阿里

三面头条+四面阿里+五面腾讯拿offer分享面经总结,最终入职阿里

三面头条+四面阿里+五面腾讯拿offer分享面经总结,最终入职阿里

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**[外链图片转存中…(img-MSqrcD9T-1713429709744)]

[外链图片转存中…(img-JsW6Jo9y-1713429709745)]

[外链图片转存中…(img-doPHngxu-1713429709745)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

最后,强调几点:

  • 1. 一定要谨慎对待写在简历上的东西,一定要对简历上的东西非常熟悉。因为一般情况下,面试官都是会根据你的简历来问的; 能有一个上得了台面的项目也非常重要,这很可能是面试官会大量发问的地方,所以在面试之前好好回顾一下自己所做的项目;
  • 2. 和面试官聊基础知识比如设计模式的使用、多线程的使用等等,可以结合具体的项目场景或者是自己在平时是如何使用的;
  • 3. 注意自己开源的Github项目,面试官可能会挖你的Github项目提问;

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目。

面试答案

[外链图片转存中…(img-zM7GlUTi-1713429709745)]

[外链图片转存中…(img-D9cAhwlj-1713429709746)]

[外链图片转存中…(img-xxB4dpow-1713429709746)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值