所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

spring底层原理与源码分析|核心机制实战拆解让你彻底搞懂

spring底层原理与源码分析|核心机制实战拆解让你彻底搞懂 一

文章目录CloseOpen

从IOC容器启动到Bean创建,源码里的“隐藏步骤”我帮你扒出来了

其实Spring的IOC容器没那么神秘,本质就是一个“管理Bean的大仓库”——你把Bean的定义告诉它,它帮你创建、存储、注入。我当时扒源码的时候,选的入口是最常用的ClassPathXmlApplicationContext(现在注解开发用AnnotationConfigApplicationContext,但原理差不多),它的核心方法就是refresh(),我给你说说我跟着断点走的过程: new ClassPathXmlApplicationContext()的时候,会调用refresh(),这个方法里的每一行代码,都是容器启动的关键步骤。比如obtainFreshBeanFactory()是获取新鲜的BeanFactory(其实就是XmlBeanFactory),它会加载配置文件里的BeanDefinition——你写的标签或者@Service注解,都会被转换成BeanDefinition,存在BeanFactory的一个Map里。然后prepareBeanFactory()会给BeanFactory配置一些基本的东西,比如类加载器、表达式解析器,还有添加一些BeanPostProcessor(比如处理@Autowired的AutowiredAnnotationBeanPostProcessor)。接下来到了finishBeanFactoryInitialization(),这一步是初始化所有单例Bean(就是scope="singleton"的Bean,默认都是单例),也是最容易出问题的地方。

我之前遇到过一个坑:有个Bean的@PostConstruct方法没执行,我查了半天,才发现是在finishBeanFactoryInitialization()里的initializeBean()方法没调用。为什么?因为那个Bean的scope设成了prototype,而prototype的Bean不会自动初始化,得你自己调用getBean()的时候才会创建,所以@PostConstruct自然不会执行。你看,要是不懂源码里的这个逻辑,根本找不到问题原因。

再说说Bean的生命周期,你可能听过“实例化→属性填充→初始化→销毁”,但源码里对应的方法你知道吗?我整理了个表格,帮你把步骤和源码对应起来:

生命周期阶段 对应源码方法 作用说明
Bean定义加载 XmlBeanDefinitionReader#loadBeanDefinitions 从XML/注解中读取Bean的配置信息,生成BeanDefinition
实例化 AbstractAutowireCapableBeanFactory#createBeanInstance 根据BeanDefinition创建Bean实例(比如用反射new对象)
属性填充 AbstractAutowireCapableBeanFactory#populateBean 给Bean的属性赋值(比如@Autowired注入的属性)
初始化 AbstractAutowireCapableBeanFactory#initializeBean 执行初始化方法(比如@PostConstruct、InitializingBean的afterPropertiesSet())
销毁 DisposableBean#destroy 容器关闭时执行销毁方法(比如@PreDestroy)

你看,每一步都有对应的源码方法,我之前就是对着这个表格,把每个方法都看了一遍,才明白为什么有时候Bean的属性注入失败——比如populateBean()方法里,会检查Bean的属性是否需要自动注入,如果是@Autowired,就会调用AutowiredAnnotationBeanPostProcessorpostProcessProperties()方法,去BeanFactory里找对应的Bean。我之前遇到过一个情况,两个Bean互相依赖(A依赖B,B依赖A),结果启动时报循环依赖错误,后来发现是因为我用了构造器注入,而构造器注入的循环依赖Spring解决不了,得用属性注入或者setter注入。这是因为Spring解决循环依赖的核心是“三级缓存”——singletonObjects(一级缓存,存成品Bean)、earlySingletonObjects(二级缓存,存半成品Bean)、singletonFactories(三级缓存,存Bean工厂),构造器注入的时候,Bean还没实例化完成,没法放进三级缓存,所以解决不了循环依赖。这些细节,你要是不看源码,根本不会知道。

AOP动态代理不是玄学,跟着源码走一遍就懂了

说完IOC,再说说AOP——你肯定用过@Aspect注解写日志、事务吧?但你知道这些增强逻辑是怎么加到目标方法里的吗?其实AOP的核心就是“动态代理”——生成一个代理对象,代替原始Bean执行方法,在方法前后插入增强逻辑。我当时扒AOP源码的时候,最困惑的是“Spring怎么判断一个Bean需要代理?”后来发现,关键类是AnnotationAwareAspectJAutoProxyCreator,它是一个BeanPostProcessor,会在Bean初始化之后(也就是initializeBean()方法之后)检查这个Bean是否匹配任何切面的切点,如果匹配,就生成代理对象。

比如,你写了一个@Aspect切面,里面有个@Before("execution( com.xxx.service..*(..))"),Spring会把这个切面转换成Advisor(通知器),里面包含切点和通知(也就是你写的日志逻辑)。然后,当创建Service Bean的时候,AnnotationAwareAspectJAutoProxyCreator会检查这个Bean的类是否符合切点表达式,如果符合,就用对应的代理方式生成代理对象——如果Bean实现了接口,就用JDK动态代理;如果没实现接口,就用CGLIB代理(现在Spring默认用CGLIB,不管有没有接口)。

我之前做过一个测试,写了一个UserService类,没有实现接口,用@Aspect加了一个日志切面,然后debug看代理对象的类型,发现是UserService$$EnhancerBySpringCGLIB$$xxxx,这就是CGLIB生成的代理类。然后我看了CGLIB代理的源码,核心是CglibAopProxyintercept()方法,这个方法会先执行前置通知,再调用原始方法,最后执行后置通知。比如:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

// 执行前置通知(比如@Before)

invokeBeforeAdvices();

// 调用原始方法

Object result = methodProxy.invokeSuper(proxy, args);

// 执行后置通知(比如@After)

invokeAfterAdvices();

return result;

}

而JDK动态代理的核心是JdkDynamicAopProxyinvoke()方法,逻辑差不多,只是它是实现接口的方法。我之前遇到过一个问题,用JDK代理的时候,目标方法是接口的默认方法,结果代理对象没法执行这个方法,后来查源码才知道,JDK代理只能代理接口的抽象方法,默认方法是在接口里实现的,代理对象不会覆盖,所以得用CGLIB代理。这又是一个踩坑的经验,你记住就行。

对了,Spring官方文档里也提到过AOP的实现方式:“Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. JDK dynamic proxies are used if the target object implements an interface, CGLIB is used otherwise.”(链接:Spring官方AOP文档),这说明我讲的是对的,不是自己瞎猜的。

现在你再回头看Spring的AOP,是不是觉得没那么神秘了?其实就是“找切点→生成代理→插入增强逻辑”这三步,源码里的每一行,都是在实现这个逻辑。我之前用AOP做事务的时候,遇到过一个问题,事务没生效,后来发现是因为我在Service类里自己调用了带@Transactional的方法,而代理对象只能拦截外部调用,内部调用不会经过代理,所以事务没生效。解决办法是把方法拆到另一个Service里,或者用AopContext.currentProxy()获取代理对象再调用,这些细节,都是我看源码加踩坑才明白的。

如果你按我讲的方法,去源码里打个断点,跟着走一遍,肯定会有不一样的收获。比如我之前跟着CglibAopProxyintercept()方法走的时候,发现增强逻辑是按顺序执行的——@Before先执行,然后是@Around的proceed()之前的逻辑,再是原始方法,然后是@Around的proceed()之后的逻辑,最后是@After和@AfterReturning。这些顺序,你要是不看源码,只看文档,可能记不住,但跟着断点走一遍,一辈子都不会忘。

如果你试了我讲的方法,比如去看refresh()方法,或者跟着代理对象的生成流程走一遍,欢迎回来告诉我你的发现!比如你有没有遇到什么奇怪的问题,或者发现了什么我没提到的细节,咱们一起讨论讨论,毕竟源码这东西,越讨论越明白。


Spring的IOC容器启动的核心方法是什么?我看源码该从哪入手?

Spring的IOC容器启动核心方法是refresh(),不管是用ClassPathXmlApplicationContext(XML配置)还是AnnotationConfigApplicationContext(注解配置),都会调用这个方法。我 你从最常用的ClassPathXmlApplicationContext入手,new这个对象的时候会触发refresh(),里面每一行都是容器启动的关键步骤——比如obtainFreshBeanFactory()加载BeanDefinition,prepareBeanFactory()配置基本参数,finishBeanFactoryInitialization()初始化单例Bean,跟着断点走一遍,就能摸清楚容器启动的逻辑。

Bean的@PostConstruct方法为什么有时候不执行?

最常见的原因是Bean的scope设成了prototype(多例)。因为Spring默认的单例Bean会在容器启动时通过finishBeanFactoryInitialization()里的initializeBean()方法执行@PostConstruct,但prototype的Bean不会自动初始化,得你自己调用getBean()的时候才会创建,所以@PostConstruct自然不会执行。我之前就遇到过这个坑,后来查源码才发现是scope的问题。

两个Bean互相依赖时,为什么构造器注入会报错,属性注入却不会?

这和Spring解决循环依赖的“三级缓存”有关——一级缓存存成品Bean,二级缓存存半成品Bean,三级缓存存Bean工厂。构造器注入的时候,Bean还没实例化完成,没法放进三级缓存,所以Spring没法提前暴露半成品Bean解决循环依赖;但属性注入或setter注入时,Bean实例化后会放进三级缓存,另一个Bean能拿到半成品Bean,就不会报错。我之前遇到过互相依赖的情况,改了属性注入就好了。

Spring怎么判断用JDK动态代理还是CGLIB代理做AOP?

以前Spring的规则是:如果Bean实现了接口,就用JDK动态代理;如果没实现接口,就用CGLIB代理。现在Spring默认用CGLIB代理,不管有没有实现接口。你可以看AOP源码里的AnnotationAwareAspectJAutoProxyCreator,它会在Bean初始化后检查是否匹配切面,然后选择代理方式——比如你写的Service类没实现接口,代理对象就是CGLIB生成的“UserService$$EnhancerBySpringCGLIB$$xxxx”类。

为什么Service类内部调用带@Transactional的方法,事务没生效?

因为Spring的事务是通过AOP动态代理实现的,代理对象只会拦截外部调用。如果是Service内部调用自己的方法,比如A方法调用B方法(B带@Transactional),这时候是原始Bean在调用,没经过代理对象,所以事务增强逻辑不会执行。解决办法要么把B方法拆到另一个Service里,要么用AopContext.currentProxy()获取代理对象再调用,我之前就是用第二种方法解决的线上问题。

原文链接:https://www.mayiym.com/48084.html,转载请注明出处。
0
显示验证码
没有账号?注册  忘记密码?

社交账号快速登录

微信扫一扫关注
如已关注,请回复“登录”二字获取验证码