大家好,我是二哥呀。
快手也定薪了,我这里也给大家同步一波手头上已有的信息,方便对比做个参考,或者去 A 薪资,下一届的小伙伴也好对快手做个评估。
信息来源于二哥的 Java 面试指南专栏
-
硕士 211,客户端岗位,开到了 25k 的 base,算是小 SP,不过网上都是劝退客户端的,有点担心。
-
西北工业大学,测开开到了 26k,没想到快手也能开这么高,有点惊,但对互联网有点小恐惧
-
上海某 top3,后端开发,给到了 26.5*16,政府还能补贴 2.5k,除此之外,房补每个月还有 2k,杭州地区
-
本科后端开发给了 23k,算是大白菜,但感觉已经很不错了
-
后端开发岗,给到了 28.5k,但没有签字费,八点下班有 30 能量卷,早10.30 晚 9.30,base 北京
-
本科 211,Java 岗,给到了 27k,本科这薪资真不少了呀,至少 SP 了
整体上,快手给的薪资比去年多一点。并且有很多本科学历的样本,不像之前的一些公司,样本集中在硕士上。
目前市面上还没有开的知名互联网公司还有小红书、得物、拼多多、阿里系,估计最迟这个月底,也就意味着秋招即将进入最后的尾声。
11 月底,应该是腰部选手的机会,12 月应该是尾部选手的机会,大家一定要注意时间节点。
这个时候,可能难度比之前要小很多。
那接下来,我们就以
Java 面试指南
中收录的快手同学 4 一面为例来看看快手的面试标准,好做到知彼知己百战不殆。
背八股就认准三分恶的面渣逆袭
-
-
2、三分恶面渣逆袭在线版:https://javabetter.cn/sidebar/sanfene/nixi.html
快手同学 4 一面
解释下什么是IOC和AOP?分别解决了什么问题?
所谓的
IoC
,就是由容器来控制对象的生命周期和对象之间的关系。控制对象生命周期的不再是引用它的对象,而是容器,这就叫
控制反转
。
三分恶面渣逆袭:控制反转示意图
Spring 倡导的开发方式就是这样,所有类的创建和销毁都通过 Spring 容器来,不再是开发者去 new,去
= null
,这样就实现了对象的解耦。
AOP,也就是面向切面编程,简单点说,AOP 就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清爽。
三分恶面渣逆袭:横向抽取
业务代码不再关心这些通用逻辑,只需要关心自己的业务实现,这样就实现了业务逻辑和通用逻辑的分离。
IOC和DI的区别?
IOC 是一种思想,DI 是实现 IOC 的具体方式,比如说利用注入机制(如构造器注入、Setter 注入)将依赖传递给目标对象。
Martin Fowler’s Definition
Spring AOP的实现原理?JDK动态代理和CGLib动态代理的各自实现及其区别?
AOP 是通过
动态代理
实现的,代理方式有两种:JDK 动态代理和 CGLIB 代理。
①、JDK 动态代理是基于接口的代理,只能代理实现了接口的类。
使用 JDK 动态代理时,Spring AOP 会创建一个代理对象,该代理对象实现了目标对象所实现的接口,并在方法调用前后插入横切逻辑。
②、CGLIB 动态代理是基于继承的代理,可以代理没有实现接口的类。
使用 CGLIB 动态代理时,Spring AOP 会生成目标类的子类,并在方法调用前后插入横切逻辑。
图片来源于网络
现在需要统计方法的具体执行时间,说下如何使用AOP来实现?
我在技术派实战项目中有应用,比如说利用 AOP 打印接口的入参和出参日志、执行时间,方便后期 bug 溯源和性能调优。
沉默王二:技术派教程
第一步,自定义注解作为切点
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MdcDot {
String bizCode() default "";
}
第二步,配置 AOP 切面:
-
-
@Pointcut
:设置切点,这里以自定义注解为切点
-
@Around
:环绕切点,打印方法签名和执行时间
技术派项目:配置 AOP 切面
第三步,在使用的地方加上自定义注解
技术派项目:使用注解
第四步,当接口被调用时,就可以看到对应的执行日志。
2023-06-16 11:06:13,008 [http-nio-8080-exec-3] INFO |00000000.1686884772947.468581113|101|c.g.p.forum.core.mdc.MdcAspect.handle(MdcAspect.java:47) - 方法执行耗时: com.github.paicoding.forum.web.front.article.rest.ArticleRestController#recommend = 47
介绍下Bean的生命周期?
Bean 的生命周期大致分为五个阶段:
三分恶面渣逆袭:Bean生命周期五个阶段
-
实例化
:Spring 首先使用构造方法或者工厂方法创建一个 Bean 的实例。在这个阶段,Bean 只是一个空的 Java 对象,还未设置任何属性。
-
属性赋值
:Spring 将配置文件中的属性值或依赖的 Bean 注入到该 Bean 中。这个过程称为依赖注入,确保 Bean 所需的所有依赖都被注入。
-
初始化
:Spring 调用 afterPropertiesSet 方法,或通过配置文件指定的 init-method 方法,完成初始化。
-
-
销毁
:在容器关闭时,Spring 会调用 destroy 方法或 destroy-method 方法,完成 Bean 的清理工作。
Aware 类型的接口有什么作用?
通过实现 Aware 接口,Bean 可以获取 Spring 容器的相关信息,如 BeanFactory、ApplicationContext 等。
常见 Aware 接口有:
接口
|
作用
|
BeanNameAware
|
获取当前 Bean 的名称。
|
BeanFactoryAware
|
获取当前 Bean 所在的 BeanFactory 实例,可以直接操作容器。
|
ApplicationContextAware
|
获取当前 Bean 所在的 ApplicationContext 实例。
|
EnvironmentAware
|
获取 Environment 对象,用于获取配置文件中的属性或环境变量。
|
如果配置了 init-method 和 destroy-method,Spring 会在什么时候调用其配置的方法?
init-method 在 Bean 初始化阶段调用,依赖注入完成后且 postProcessBeforeInitialization 调用之后执行。
destroy-method 在 Bean 销毁阶段调用,容器关闭时调用。
二哥的Java 进阶之路:init-method 和 destroy-method
循环依赖有了解过吗?出现循环依赖的原因?
A 依赖 B,B 依赖 A,或者 C 依赖 C,就成了循环依赖。
三分恶面渣逆袭:Spring循环依赖
原因很简单,AB 循环依赖,A 实例化的时候,发现依赖 B,创建 B 实例,创建 B 的时候发现需要 A,创建 A1 实例……无限套娃。。。。
如何解决循环依赖?三大缓存存储内容的区别?
通过三级缓存机制:
-
-
二级缓存:存放正在创建但未完全初始化的 Bean 实例。
-
三级缓存:存放 Bean 工厂对象,用于提前暴露 Bean。
三分恶面渣逆袭:三级缓存
如果缺少第二级缓存会有什么问题?
三分恶面渣逆袭:二级缓存不行的原因
如果没有二级缓存,Spring 无法在未完成初始化的情况下暴露 Bean。会导致代理 Bean 的循环依赖问题,因为某些代理逻辑无法在三级缓存中提前暴露。最终可能抛出 BeanCurrentlyInCreationException。
为什么使用SpringBoot?
Spring Boot 提供了一套默认配置,它通过约定大于配置的理念,来帮助我们快速搭建 Spring 项目骨架。
SpringBoot图标
以前的 Spring 开发需要配置大量的 xml 文件,并且需要引入大量的第三方 jar 包,还需要手动放到 classpath 下。现在只需要引入一个 Starter,或者一个注解,就可以轻松搞定。
SpringBoot自动装配的原理及流程?@Import的作用?
在 Spring 中,自动装配是指容器利用反射技术,根据 Bean 的类型、名称等自动注入所需的依赖。
三分恶面渣逆袭:SpringBoot自动配置原理
在 Spring Boot 中,开启自动装配的注解是
@EnableAutoConfiguration
。
Spring Boot 为了进一步简化,直接通过
@SpringBootApplication
注解一步搞定,该注解包含了
@EnableAutoConfiguration
注解。
二哥的 Java 进阶之路:@EnableAutoConfiguration 源码
main 类启动的时候,Spring Boot 会通过底层的
AutoConfigurationImportSelector
类加载自动装配类。
@AutoConfigurationPackage //将main同级的包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class>[] exclude() default {};
String[] excludeName() default {};
}
AutoConfigurationImportSelector
实现了
ImportSelector
接口,该接口的作用是收集需要导入的配置类,配合
@Import()
将相应的类导入到 Spring 容器中。
二哥的 Java 进阶之路:AutoConfigurationImportSelector源码
获取注入类的方法是
selectImports()
,它实际调用的是
getAutoConfigurationEntry()
,这个方法是获取自动装配类的关键。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 检查自动配置是否启用。如果@ConditionalOnClass等条件注解使得自动配置不适用于当前环境,则返回一个空的配置条目。
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取启动类上的@EnableAutoConfiguration注解的属性,这可能包括对特定自动配置类的排除。
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从spring.factories中获取所有候选的自动配置类。这是通过加载META-INF/spring.factories文件中对应的条目来实现的。
List configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 移除配置列表中的重复项,确保每个自动配置类只被考虑一次。
configurations = removeDuplicates(configurations);
// 根据注解属性解析出需要排除的自动配置类。
Set exclusions = getExclusions(annotationMetadata, attributes);
// 检查排除的类是否存在于候选配置中,如果存在,则抛出异常。
checkExcludedClasses(configurations, exclusions);
// 从候选配置中移除排除的类。
configurations.removeAll(exclusions);
// 应用过滤器进一步筛选自动配置类。过滤器可能基于条件注解如@ConditionalOnBean等来排除特定的配置类。
configurations = getConfigurationClassFilter().filter(configurations);
// 触发自动配置导入事件,允许监听器对自动配置过程进行干预。
fireAutoConfigurationImportEvents(configurations, exclusions);
// 创建并返回一个包含最终确定的自动配置类和排除的配置类的AutoConfigurationEntry对象。
return new AutoConfigurationEntry(configurations, exclusions);
}
如果想让SpringBoot对自定义的jar包进行自动配置的话,需要怎么做?
第一步,创建一个新的 Maven 项目,例如命名为 my-spring-boot-starter。在 pom.xml 文件中添加必要的依赖和配置:
<properties>
<spring.boot.version>2.3.1.RELEASEspring.boot.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-autoconfigureartifactId>
<version>${spring.boot.version}version>
dependency>
<dependency>
<groupId
>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>${spring.boot.version}version>
dependency>
dependencies>
第二步,在
src/main/java
下创建一个自动配置类,比如 MyServiceAutoConfiguration.java:(通常是 autoconfigure 包下)。
@Configuration
@EnableConfigurationProperties(MyStarterProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyStarterProperties properties) {
return new MyService(properties.getMessage());
}
}
第三步,创建一个配置属性类 MyStarterProperties.java:
@ConfigurationProperties(prefix = "mystarter")
public class MyStarterProperties {
private String message = "二哥的 Java 进阶之路不错啊!";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
第四步,创建一个简单的服务类 MyService.java:
public class MyService {
private final String message;
public MyService(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
第五步,配置 spring.factories,在
src/main/resources/META-INF
目录下创建 spring.factories 文件,并添加:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itwanger.mystarter.autoconfigure.MyServiceAutoConfiguration
第六步,使用 Maven 打包这个项目:
mvn clean install
第七步,在其他的 Spring Boot 项目中,通过 Maven 来添加这个自定义的 Starter 依赖,并通过 application.properties 配置欢迎消息:
mystarter.message=javabetter.cn
然后就可以在 Spring Boot 项目中注入 MyStarterProperties 来使用它。
启动项目,然后在浏览器中输入
localhost:8081/hello
,就可以看到欢迎消息了。
二哥的 Java 进阶之路
Spring中使用了哪些设计模式,以其中一种模式举例说明?
Spring 框架中用了蛮多设计模式的:
三分恶面渣逆袭:Spring中用到的设计模式
①、比如说工厂模式用于 BeanFactory 和 ApplicationContext,实现 Bean 的创建和管理。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);
②、比如说单例模式,这样可以保证 Bean 的唯一性,减少系统开销。
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService1 = context.getBean(MyService.class);
MyService myService2 = context.getBean(MyService.class);
// This will print "true" because both references point to the same instance
System.out.println(myService1 == myService2);
Spring如何实现单例模式?
Spring 通过 IOC 容器实现单例模式,具体步骤是:
单例 Bean 在容器初始化时创建并使用 DefaultSingletonBeanRegistry 提供的 singletonObjects 进行缓存。
// 单例缓存
private final Map singletonObjects = new ConcurrentHashMap<>();
public Object getSingleton(String beanName) {
return this.singletonObjects.get(beanName);
}
protected void addSingleton(String beanName, Object singletonObject) {
this.singletonObjects.put(beanName, singletonObject);
}
在请求 Bean 时,Spring 会先从缓存中获取。
刚刚提到了Spring使用ConcurrentHashMap来实现单例模式,大致说下ConcurrentHashMap的put和get方法流程?
①、put 流程
一句话:通过计算键的哈希值确定存储位置,如果桶为空,使用 CAS 插入节点;如果存在冲突,通过链表或红黑树插入。在冲突时,如果 CAS 操作失败,会退化为 synchronized 操作。写操作可能触发扩容或链表转为红黑树。
三分恶面渣逆袭:Java 8 put 流程
②、get 查询
通过计算哈希值快速定位桶,在桶中查找目标节点,多个 key 值时链表遍历和红黑树查找。读操作是无锁的,依赖 volatile 保证线程可见性。
如何判断死亡对象?
Java 使用的是可达性分析算法,通过一组名为 “GC Roots” 的根对象,进行递归扫描。那些无法从根对象到达的对象是不可达的,可以被回收;反之,是可达的,不会被回收。
三分恶面渣逆袭:GC Root
GC Roots有哪些?
所谓的 GC Roots,就是一组必须活跃的引用,不是对象,它们是程序运行时的起点,是一切引用链的源头。在 Java 中,GC Roots 包括以下几种:
-
-
-
-
运行时常量池中的常量(String 或 Class 类型)
空间分配担保是什么?
空间分配担保是指在进行 Minor GC(新生代垃圾回收)前,JVM 会确保老年代有足够的空间存放从新生代晋升的对象。如果老年代空间不足,可能会触发 Full GC。
类装载的执行过程?
三分恶面渣逆袭:类的生命周期
类装载过程包括三个阶段:载入、链接(包括验证、准备、解析)、初始化。
①、载入:将类的二进制字节码加载到内存中。
②、链接可以细分为三个小的阶段:
③、初始化:执行静态代码块和静态变量初始化。
双亲委派模式是什么?
双亲委派模型要求类加载器在加载类时,先委托父加载器尝试加载,只有父加载器无法加载时,子加载器才会加载。
三分恶面渣逆袭:双亲委派模型
为什么使用这种模式?
①、避免类的重复加载
:父加载器加载的类,子加载器无需重复加载。
②、保证核心类库的安全性
:如
java.lang.*
只能由 Bootstrap ClassLoader 加载,防止被篡改。
服务器的CPU占用持续升高,有哪些排查问题的手段?
三分恶面渣逆袭:CPU飙高
首先,使用 top 命令查看 CPU 占用情况,找到占用 CPU 较高的进程 ID。
top