先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
- Spring AOP:非完整的 AOP 实现框架
Spring AOP 是基于 JDK 动态代理和 Cglib 提升实现的,两种代理方式都属于运行时的一个方式,所以它没有编译时的一个处理,那么因此 Spring 是通过 Java 代码实现的。AspectJ 自己有一个编译器,在编译时期可以修改 .class 文件,在运行时也会进行处理。
Spring AOP 有别于其他大多数 AOP 实现框架,目的不是提供最完整的 AOP 实现(尽管 Spring AOP 相当强大);相反,其目的是在 AOP 实现和 Spring IoC 之间提供紧密的集成,以提供企业级核心特性。
Spring AOP 从未打算与 AspectJ 竞争以提供全面的 AOP 解决方案,我们认为 Spring AOP 等基于代理实现的框架和 AspectJ 等成熟的框架都是有价值的,并且它们是互补的,而不是竞争关系。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以实现 AOP 的所有功能都可以在一个 Spring 应用中。这种集成不会影响 Spring AOP API 或 AOP Alliance API,保持向后兼容。
什么是 AOP 代理?
代理模式是一种结构性设计模式,通过代理类为其他对象提供一种代理以控制对这个对象的访问。AOP 代理是 AOP 框架中 AOP 的实现,主要分为静态代理和动态代理,如下:
-
静态代理:代理类需要实现被代理类所实现的接口,同时持有被代理类的引用,新增处理逻辑,进行拦截处理,不过方法还是由被代理类的引用所执行。静态代理通常需要由开发人员在编译阶段就定义好,不易于维护。
-
常用 OOP 继承和组合相结合
-
AspectJ,在编辑阶段会织入 Java 字节码,且在运行期间会进行增强。
-
动态代理:不会修改字节码,而是在 JVM 内存中根据目标对象新生成一个 Class 对象,这个对象包含了被代理对象的全部方法,并且在其中进行了增强。
-
JDK 动态代理
-
字节码提升,例如 CGLIB
讲讲 JDK 动态代理?
基于接口代理,通过反射机制生成一个实现代理接口的类,在调用具体方法时会调用 InvocationHandler 来处理。
需要借助 JDK 的 java.lang.reflect.Proxy
来创建代理对象,调用 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法创建一个代理对象,方法的三个入参分别是:
-
ClassLoader loader
:用于加载代理对象的 Class 类加载器 -
Class<?>[] interfaces
:代理对象需要实现的接口 -
InvocationHandler h
:代理对象的处理器
新生成的代理对象的 Class 对象会继承 Proxy
,且实现所有的入参 interfaces
中的接口,在实现的方法中实际是调用入参 InvocationHandler
的 invoke(..)
方法。
为什么 JDK 动态代理只能基于接口代理,不能基于类代理?
因为 JDK 动态代理生成的代理对象需要继承
Proxy
这个类,在 Java 中类只能是单继承关系,无法再继承一个代理类,所以只能基于接口代理。
为什么 InvocationHandler 不直接声明到这个代理对象里面,而是放入继承的
Proxy
父类中?
我觉得代理类既然是 JDK 动态生成的,那么 JDK 就需要识别出哪些类是生成的代理类,哪些是非代理类,或者说 JDK 需要对代理类做统一的处理,这时如果没有一个统一的类 Proxy 来进行引用根本无法处理。这只是笔者的想法,具体为什么这么做不知道有小伙伴知道不 ~
讲讲 CGLIB 动态代理?
JDK 动态代理的目标对象必须是一个接口,在我们日常生活中,无法避免开发人员不写接口直接写类,或者根本不需要接口,直接用类进行表达。这个时候我们就需要通过一些字节码提升的手段,来帮助做这个事情,在运行时,非编译时,来创建一个新的 Class 对象,这种方式称之为字节码提升。在 Spring 内部有两个字节码提升的框架,ASM(过于底层,直接操作字节码)和 CGLIB(相对于前者更加简便)。
CGLIB 动态代理则是基于类代理(字节码提升),通过 ASM(Java 字节码的操作和分析框架)将被代理类的 class 文件加载进来,修改其字节码生成一个子类。
需要借助于 CGLIB 的 org.springframework.cglib.proxy.Enhancer
类来创建代理对象,设置以下几个属性:
-
Class<?> superClass
:被代理的类 -
Callback callback
:回调接口
新生成的代理对象的 Class 对象会继承 superClass
被代理的类,在重写的方法中会调用 callback
回调接口(方法拦截器)进行处理。
如果你想设置一个 Callback[] 数组去处理不同的方法,那么需要设置一个 CallbackFilter 筛选器,用于选择具体的方法使用数组中的哪个 Callback 去处理
JDK 动态代理和 CGLIB 动态代理有什么不同?
两者都是在 JVM 运行时期新创建一个 Class 对象,实例化一个代理对象,对目标类(或接口)进行代理。JDK 动态代理只能基于接口进行代理,生成的代理类实现了这些接口;而 CGLIB 动态代理则是基于类进行代理的,生成的代理类继承目标类,但是不能代理被 final 修饰的类,也不能重写 final 或者 private 修饰的方法。
CGLIB 动态代理比 JDK 动态代理复杂许多,性能也相对比较差。
Spring AOP 和 AspectJ 有什么关联?
Spring AOP 和 AspectJ 都是 AOP 的实现框架,AspectJ 是 AOP 的完整实现,Spring AOP 则是部分实现。AspectJ 有一个很好的编程模型,包含了注解的方式,也包含了特殊语法。Spring 认为 AspectJ 的实现在 AOP 体系里面是完整的,不需要在做自己的一些实现。
Spring AOP 整合 AspectJ 注解与 Spring IoC 容器,比 AspectJ 的使用更加简单,也支持 API 和 XML 的方式进行使用。不过 Spring AOP 仅支持方法级别的 Pointcut 拦截。
为什么 Spring AOP 底层没有使用 AspectJ 动态代理创建代理对象?
我觉得是因为 AspectJ 的特殊语法对于 Spring 或者 Java 开发人员来说不是很友好,使用起来可能有点困难。Spring 也选择整合 AspectJ 的注解,使用起来非常方便。
Spring AOP 中有哪些 Advice 类型?
-
Around Advice,围绕型通知器,需要主动去触发目标方法的执行,这样可以在触发的前后进行相关相关逻辑处理
-
Before Advice,前置通知器,在目标方法执行前会被调用
-
After Advice,后置通知器
-
AfterReturning,在目标方法执行后被调用(方法执行过程中出现异常不会被调用)
-
After,在目标方法执行后被调用(执行过程出现异常也会被调用)
-
AfterThrowing,执行过程中抛出异常后会被调用(如果异常类型匹配)
执行顺序(Spring 5.2.7 之前的版本):Around “前处理” > Before > 方法执行 > Around “后处理” > After > AfterReturning|AfterThrowing
执行顺序(Spring 5.2.7 开始):Around “前处理” > Before > 方法执行 > AfterReturning|AfterThrowing > After > Around “后处理”
如下(在后续文章会进行分析,Spring 5.1.14):
Spring AOP 中 Advisor 接口是什么?
Advisor 是 Advice 的一个容器接口,与 Advice 是一对一的关系,它的子接口 PointcutAdvisor 是 Pointcut 和 Advice 的容器接口,将 Pointcut 过滤 Joinpoint 的能力和 Advice 进行整合,这样一来就将两者进行关联起来了。
Pointcut 提供 ClassFilter 和 MethedMatcher,分别支持筛选类和方法,通过 PointcutAdvisor 和 Advice 进行整合,可以说是形成了一个“切面”。
简述 Spring AOP 自动代理的实现
在我们有了 Join point(连接点)、Pointcut(切点)、Advice(通知)以及 AspectJ(切面)后,我们应该如何将他们“织入”我们的应用呢?在 Sping AOP 中提供了自动代理的实现,底层借助 JDK 动态代理和 CGLIB 动态代理创建对象。
回顾 Spring IoC 中 Bean 的加载过程,在整个过程中,Bean 的实例化前和初始化后等生命周期阶段都提供了扩展点,会调用相应的 BeanPostProcessor 处理器对 Bean 进行处理。当我们开启了 AspectJ 自动代理(例如通过 @EnableAspectJAutoProxy
注解),则会往 IoC 容器中注册一个 AbstractAutoProxyCreator
自动代理对象,该对象实现了几种 BeanPostProcessor,例如在每个 Bean 初始化后会被调用,解析出当前 Spring 上下文中所有的 Advisor(会缓存),如果这个 Bean 需要进行代理,则会通过 JDK 动态代理或者 CGLIB 动态代理创建一个代理对象并返回,所以得到的这个 Bean 实际上是一个代理对象。这样一来,开发人员只需要配置好 AspectJ 相关信息,Spring 则会进行自动代理,和 Spring IoC 完美地整合在一起。
参考:《死磕Spring之IoC篇 - Bean 的创建过程》
请解释 Spring @EnableAspectJAutoProxy 的原理?
使用了 @EnableAspectJAutoProxy
注解则会开启 Spring AOP 自动代理,该注解上面有一个 @Import(AspectJAutoProxyRegistrar.class)
注解,AspectJAutoProxyRegistrar
实现了 ImportBeanDefinitionRegistrar
这个接口,在实现的方法中会注册一个 AnnotationAwareAspectJAutoProxyCreator
自动代理对象(如果没有注册的话),且将其优先级设置为最高,同时解析 @EnableAspectJAutoProxy
注解的配置并进行设置。这个自动代理对象是一个 BeanPostProcessor 处理器,在 Spring 加载一个 Bean 的过程中,如果它需要被代理,那么会创建一个代理对象(JDK 动态代理或者 CGLIB 动态代理)。
除了注解的方式,也可以通过 <aop:aspectj-autoproxy />
标签开启 Spring AOP 自动代理,原理和注解相同,同样是注册一个自动代理对象。
@Import
注解的原理参考:《死磕Spring之IoC篇 - @Bean 等注解的实现原理》
XML 自定义标签的原理参考:《死磕Spring之IoC篇 - 解析自定义标签(XML 文件)》
Spring Configuration Class CGLIB 提升与 AOP 类代理关系?
在 Spring 底层 IoC 容器初始化后,会通过 BeanDefinitionRegistryPostProcessor
对其进行后置处理,其中会有一个 ConfigurationClassPostProcessor
处理器会对 @Configuration
标注的 BeanDefinition 进行处理,进行 CGLIB 提升,这样一来对于后续的 Spring AOP 工作就非常简单了,因为这个 Bean 天然就是一个 CGLIB 代理。
在 Spring 5.2 开始 @Configuration
注解中新增了一个 proxyBeanMethods
属性(默认为 true),支持显示的配置是否进行 CGLIB 提升,毕竟进行 CGLIB 提升在启动过程会有一定的性能损耗,且创建的代理对象会占有一定的内存,通过该配置进行关闭,可以减少不必要的麻烦,对 Java 云原生有一定的提升。
@Configuration
注解的 Bean 进行 CGLIB 提升后有什么作用呢?
举个例子,大多数情况下,
@Configuration
Class 会通过@Bean
注解为 Bean 定义,比如 @Bean User user() { return new User(); },那这样是不是每次主动调用这个方法都会返回一个新的 User 对象呢?
不是的,
@Configuration
Class 在得到 CGLIB 提升后,会设置一个拦截器专门对@Bean
方法进行拦截处理,通过依赖查找的方式从 IoC 容器中获取 Bean 对象,如果是单例 Bean,那么每次都是返回同一个对象,所以当主动调用这个方法时获取到的都是同一个 User 对象。
参考:《死磕Spring之IoC篇 - @Bean 等注解的实现原理》
Sping AOP 应用到哪些设计模式?
最后
ActiveMQ消息中间件面试专题
- 什么是ActiveMQ?
- ActiveMQ服务器宕机怎么办?
- 丢消息怎么办?
- 持久化消息非常慢怎么办?
- 消息的不均匀消费怎么办?
- 死信队列怎么办?
- ActiveMQ中的消息重发时间间隔和重发次数吗?
ActiveMQ消息中间件面试专题解析拓展:
redis面试专题及答案
- 支持一致性哈希的客户端有哪些?
- Redis与其他key-value存储有什么不同?
- Redis的内存占用情况怎么样?
- 都有哪些办法可以降低Redis的内存使用情况呢?
- 查看Redis使用情况及状态信息用什么命令?
- Redis的内存用完了会发生什么?
- Redis是单线程的,如何提高多核CPU的利用率?
Spring面试专题及答案
- 谈谈你对 Spring 的理解
- Spring 有哪些优点?
- Spring 中的设计模式
- 怎样开启注解装配以及常用注解
- 简单介绍下 Spring bean 的生命周期
Spring面试答案解析拓展
高并发多线程面试专题
- 现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?
- Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。
- Java 中 wait 和 sleep 方法有什么区别?
- 如何在 Java 中实现一个阻塞队列?
- 如何在 Java 中编写代码解决生产者消费者问题?
- 写一段死锁代码。你在 Java 中如何解决死锁?
高并发多线程面试解析与拓展
jvm面试专题与解析
- JVM 由哪些部分组成?
- JVM 内存划分?
- Java 的内存模型?
- 引用的分类?
- GC什么时候开始?
JVM面试专题解析与拓展!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
?
- 引用的分类?
- GC什么时候开始?
JVM面试专题解析与拓展!
[外链图片转存中…(img-4pA3rT0E-1713139671975)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-wxGBrp4Q-1713139671975)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!