本文内容索引:

1.Bean的生命周期底层原理
2.依赖注入底层原理
3.初始化底层原理
4.推断构造方法底层原理
5.AOP底层原理
6.Spring事务底层原理

​但都只是大致流程,后续会针对每个流程详细深入的分析源码实现。

先来看看入门使用Spring的代码:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
  • 第一行代码,会构造一个ClassPathXmlApplicationContext对象,ClassPathXmlApplicationContext该如何理解,调用该构造方法除开会实例化得到一个对象,还会做哪些事情?
  • 第二行代码,会调用ClassPathXmlApplicationContext的getBean方法,会得到一个UserService对象,getBean()是如何实现的?返回的UserService对象和我们自己直接new的UserService对象有区别吗?
  • 第三行代码,就是简单的调用UserService的test()方法,不难理解。

但是用ClassPathXmlApplicationContext其实已经过时了,在新版的Spring MVC和Spring Boot的底层主要用的都是AnnotationConfigApplicationContext,比如:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

可以看到AnnotationConfigApplicationContext的用法和ClassPathXmlApplicationContext是非常类似的,只不过需要传入的是一个class,而不是一个xml文件。

而AppConfig.class和spring.xml一样,表示Spring的配置,比如可以指定扫描路径,可以直接定义Bean,比如:

spring.xml中的内容为:

<context:component-scan base-package="com.zhouyu"/>
<bean id="userService" class="com.zhouyu.service.UserService"/>

AppConfig中的内容为:

@ComponentScan("com.cry")
public class AppConfig {@Beanpublic UserService userService(){return new UserService();}}

所以spring.xml和AppConfig.class本质上是一样的。

目前,我们基本很少直接使用上面这种方式来用Spring,而是使用Spring MVC,或者Spring Boot,但是它们都是基于上面这种方式的,都需要在内部去创建一个ApplicationContext的,只不过:

  • Spring MVC创建的是XmlWebApplicationContext,和ClassPathXmlApplicationContext类似,都是基于XML配置的
  • Spring Boot创建的是AnnotationConfigApplicationContext

其实不管是AnnotationConfigApplicationContext还是ClassPathXmlApplicationContext,目前,我们都可以简单的将它们理解为就是用来创建Java对象的,比如调用getBean()就会去创建对象(此处不严谨,getBean可能也不会去创建对象,后续解释)

在Java语言中,肯定是根据某个类来创建一个对象的。我们在看一下实例代码:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();

​当我们调用context.getBean(“userService”)时,就会去创建一个对象,但是getBean方法内部怎么知道"userService"对应的是UserService类呢?

所以,我们就可以分析出来,在调用AnnotationConfigApplicationContext的构造方法时,也就是第一行代码,会去做一些事情:

  • 1.解析AppConfig.class,得到扫描路径
  • 2.遍历扫描路径下的所有Java类,如果发现某个类上存在@Component、@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map<String, Class>。(实际上,Spring源码中确实存在类似的这么一个Map,叫做BeanDefinitionMap,后续会讲到)
  • 3.Spring会根据某个规则生成当前类对应的beanName,作为key存入Map,当前类作为value

这样,但调用context.getBean(“userService”)时,就可以根据"userService"找到UserService类,从而就可以去创建对象了。

SpringBean创建的生命周期

SpringFramework帮我们创建的bean, 和普通的object有什么区别?

UserService userService = (UserService) applicationContext.getBean("userService");
UserService userService1 = new UserService();

Spring创建bean所需要经过的流程:
UserService --> 无参构造方法 --> object --> 依赖注入(给object中带了@Autowired注解的属性赋值) --> bean

DI(依赖注入):

for (Field field: userService.gatClass().getFields()){if (field.isAnnotationPresent(Autowired.class)){field.set(userService, ??)}
}

从依赖注入到最终Spring帮我们生成bean之间还有一些步骤:初始化前 --> 初始化 --> 初始化后

@Component
public class Admin {private String username;private String password;
}
@Autowired
Admin admin; //new Admin()  //mysql --> Admin对象 --> admin属性/**
* 只需要把调用这个赋值方法的时机放在bean生成之前就可以了
*/
public void setAttribute(){//在这里查出mysql中的属性, 手动赋值给admin对象
}

我们现在有个Admin类,映射mysql中查出来的数据,那么在Spring通过@Autowired帮我们生成这个bean后,他是没有属性的,具体的属性username/password需要我们自己调用setAttribute()方法来手动注入,那么有没有办法在bean生成前自动调用setAttribute()方法来让Spring做DI的这一步呢?肯定是有的。

我们只需要在bean生命周期的初始化前(setAttribute),执行这个过程就行了。

@PostConstruct
public void setAttribute(){
//在这里查出mysql中的属性, 手动赋值给admin对象
}

我们只需要在方法上加一个@PostConstruct注解,Spring就知道要在bean的初始化前来调这个方法了。

为DI注入的对象自动赋值:

for (Method method: userService.gatClass().getMethods()){if (method.isAnnotationPresent(PostConstruct.class)){method.invoke(userService,null);}
}

还可以通过实现InitializingBean并重写afterPropertiesSet()方法来实现类似功能,唯一不同是Spring判断当前类是否实现InitializingBean接口的过程是在初始化(afterPropertiesSet)时

public class UserService implements InitializingBean{@Overridepublic void afterPropertiesSet() throws Exceptions{}
}

判断一个对象是否实现了某个接口:object instantof InterfaceName

if (object instant of InitializingBean){(InitializingBean)object.afterPropertiesSet();
}

Spring源码:搜索doCreateBean,进入initializeBean(beanName, exposedObject, mbd),进入invokeInitMethods(beanName, wrappedBean, mbd),

随后就是经过:初始化后(AOP) -> 代理对象 -> bean,最终得到一个bean

我们来总结一下,一个SpringBean创建的生命周期就是:

BeanClass–> 无参构造方法(推断构造方法) --> 普通对象–> 依赖注入(给object中带了@Autowired注解的属性赋值) -->初始化前(调用带了@PostConstruct注解的方法) --> 初始化(判断object instant of initializeBean,调用重写的afterPropertiesSet) --> 初始化后(AOP切面得到代理对象) --> SpringBean

我们再来仔细的分析一下创建SpringBean生命周期过程中的一些细节:

细节1:推断构造方法

public UserService() {    //1System.out.println("1");
}public UserService(OrderService orderService) {  //2 this.orderService = orderService;System.out.println("2");
}public UserService(OrderService orderService,OrderService orderService1) {  //3this.orderService = orderService;System.out.println("3");
}

比如我们现在UserService有3个构造方法,Spring会默认选择无参的构造方法,那么如果我们现在把默认的无参构造方法注释掉,Spring会怎么选择呢?

 No default constructor found; nested exception is java.lang.NoSuchMethodException: com.zhouyu.service.UserService.<init>()

对的,他会报一个异常,因为当只有2个有参构造方法的时候,Spring不知道要选择使用哪个有参构造方法,他会去尝试寻找default constructor,但是他没有找到,所以报了这个异常。可以通过在你想要让Spring调用的构造方法上加上@Autowired注解来让Spring能够正确识别。

细节2:Bean的创建过程

当我们只有一个有参构造方法的时候,他的入参有没有值呢? 如果有的话那么是什么时候创建的呢?

public UserService(OrderService orderService) {  //2 this.orderService = orderService;System.out.println("2");
}

我们能看到入参是有值的,这个对象是怎么来的呢? 肯定是从Spring容器中来的

Map<name,orderService> map // 根据名字查
for(int i=0;i<map.size();i++){if(map.get(name).getClass() == object.getClass){  // 根据类型查count++;}
}
if(count == 1){return map.get(name);
}

细节3:AOP大致流程

开启AOP后调用userService.test(),会看到userService里的orderService属性为null,但进入test()方法后,会看到userService里的orderService是有值的,为什么?

分析下来可以发现,是因为"没有必要"

cglib动态代理核心思想就是继承了目标类的父类:

public class CglibProxy extends UserService {private UserService target;/*** target赋值** @param wrappedBean 普通对象*/public void applyBeanPostProcessorsAfterInitialization(Object wrappedBean) {target = (UserService) wrappedBean;}@Overridepublic void test() {// 执行@Before的切面逻辑target.test();// 执行@After的切面逻辑}
}

jdk动态代理核心思想就是通过Class.forName().getMethod()拿到这个类中需要代理的那个方法,然后调用method.invoke()反射调用目标方法:

public class JdkProxy {private static Method m;/*** JDK动态代理类会先在静态代码块中通过反射把所有方法都拿到并存在静态变量中*/static {try {m = Class.forName("service.IUserService").getMethod("test",new Class[]{});} catch (NoSuchMethodException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}public void test(){try {// todo: before增强逻辑m.invoke(this,null);// todo: after增强逻辑} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}

Cglib在Spring中的实现:

public class CglibDynProxy implements Callback {/*** 要代理的目标对象*/private Object target;/*** 代理逻辑:  通过字节码生成目标类的子类*/public Object proxy(){// 1.创建代理增强对象Enhancer enhancer = new Enhancer();// 2.设置父类,也就是代理目标类,CGLIB是通过生成代理类子类的方式来实现动态代理的enhancer.setSuperclass(target.getClass());// 3.设置回调函数,这个this其实就是代理逻辑实现类,也就是切面,等价于JDK动态代理的InvocationHandlerenhancer.setCallback(this);// 4.创建代理对象,也就是目标类的子类return enhancer.create();}
}

jdk动态代理在Spring中的实现:

public class JdkDynProxy {private static Method m;/*** JDK动态代理类会先在静态代码块中通过反射把所有方法都拿到并存在静态变量中*/static {try {m = Class.forName("service.IUserService").getMethod("test",new Class[]{});} catch (NoSuchMethodException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}private InvocationHandler getInvocationHandler(){return (InvocationHandler) new JdkDynProxy();}public final void test() throws Throwable {InvocationHandler invocationHandler = getInvocationHandler();invocationHandler.invoke(this,m,new Object[]{});}}

创建UserServiceProxy类 —> 生成userServiceProxy代理对象 —> 代理对象.target = 普通对象

Spring在applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)方法中就把DI填充属性后的普通对象传进去了,然后创建一个AOP代理对象,让它的target属性 = wrappedBean;

AOP切面的总体创建过程:

遍历所有@Aspect注解的类,遍历其中@Before@After等注解的方法,全部取出来匹配一下是不是和Target类相关,然后丢到一个Map容器中缓存起来,代码运行时就可以直接使用了。

细节4:Spring事务

在@Transactional对某个方法开启事务后,需要在启动类上加上@Configuration注解,不然事务无法正常生效,为什么?

public class CglibProxy extends UserService {private UserService target;/*** target赋值** @param wrappedBean 普通对象*/public void applyBeanPostProcessorsAfterInitialization(Object wrappedBean) {target = (UserService) wrappedBean;}@Overridepublic void test() {// 1.判断方法上是否有@Transactional注解// 2.创建一个数据库连接conn(事务管理器通过dataSource创建)// 3.conn.autocommit = falsetarget.test();// 如果方法中没有抛出异常: conn.commit()// 如果方法中抛出异常: conn.rollback()}
}

Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法被调用时,要判断到底是不是直接被代理对象调用的,如果是则事务会生效,如果不是则失效。

可以通过另起一个类,注入的方式解决;或可以通过在当前类中注入当前类解决。

回到开始的问题,为什么在配置类上不加@Configuration或者@Component,就无法正常启动事务呢?

是因为Spring用到了代理模式,Spring会生成一个AppConfig的代理对象,代理会先去Spring容器中查找dataSource,如果有的话就直接返回,如果没有才真正调用AppConfig类中的dataSource()方法。而如果没有@Configuration或者@Component注解的话,jdbcTemplate和transactionManager所用到的dataSource是2个对象,他们并不相关,所以我们在用jdbcTemplate操作数据库的时候,自然无法受到transactionManager的影响了。

【Spring】Spring底层核心原理解析相关推荐

  1. Spring源码学习(一)--Spring底层核心原理解析

    目录 Spring中是如何创建一个对象? Bean的创建过程 推断构造方法 AOP大致流程 Spring事务 最近在跟视频学习spring源码,将每节课记录下来,以后好来复习. 首先把Spring中核 ...

  2. Spring框架(一) 底层核心原理解析

    感兴趣的话大家可以关注一下公众号 : 猿人刘先生 , 欢迎大家一起学习 , 一起进步 , 一起来交流吧! 说明 本系列文章以spring-framework-5.3.10为例 , 本篇文章的目的就是使 ...

  3. Spring学习篇底层核心原理解析

    说明 本系列文章以spring-framework-5.3.10为例 ,本篇文章的目的就是使各位读者能在使用Spring的基础上对Spring的一些比较核心的内容有一个大概的认识,并不是特别全面,会在 ...

  4. 【Spring】1.核心原理解析

      目录 概况 Bean的生命周期 推断构造方法 AOP流程 事务 概况 核心知识点串讲,对Spring有整体的了解 比如: 1. Bean的生命周期原理 2. 依赖注入原理 3. 初始化原理 4. ...

  5. Spring Boot(18)---启动原理解析

    Spring Boot(18)---启动原理解析 前言 前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会 ...

  6. spring源码分析01-(前期准备)spring核心原理解析和手写简易spring

    1.本文主要介绍内容 本文会把Spring中核心知识点大概解释下.可以对Spring的底层有一个整体的大致了解.主要内容包括: 手写简易spring框架,帮助更好理解spring. 代码点击链接自取 ...

  7. Spring底层核心原理

    Spring底层整体了解 Bean的生命周期底层原理 依赖注入底层原理 初始化底层原理 推断构造底层原理 AOP底层原理 Spring事务底层原理 Spring是如何创建一个对象的 Annotatio ...

  8. Spring AOP底层实现原理(动态代理)

    什么是AOP? AOP(面向切面编程)通过预编译的方式 和 运行期动态代理的方式来实现程序功能统一维护的一种方式,是OOP(面向对象编程)的延续.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业 ...

  9. react 流程图框架_【赠书】Preact(React)核心原理详解Preact(React) 核心原理解析...

    豆皮粉儿们,又见面了,今天这一期,由字节跳动数据平台的"winge(宝丁)",带大家见识见识前端"轮子"之一Preact框架. 提到Preact,你肯定会先想到 ...

最新文章

  1. HTML5调用手机的Datepicker(日期选择器)
  2. Linux下正确使用getifaddrs()函数避免内存泄露
  3. jset编写测试vue代码_详解使用jest对vue项目进行单元测试
  4. Selenium实例2-截图爬取漫画
  5. 广汽研究院BMS软件工程师_感·创未来 2020广汽科技日有哪些干货?
  6. 启动/删除Docker容器时出现问题 - 如何修复
  7. strtol() 字符串转长整型函数
  8. 第一次马拉松_成为数据科学家是一场马拉松而不是短跑
  9. python2卸载后yum不可用_centos7误删除python2导致的python和yum不可用处理-阿里云开发者社区...
  10. elasticsearch系列八:ES 集群管理(集群规划、集群搭建、集群管理)
  11. 软件_linux命令cp目录路径和通配符
  12. c语言识别按了esc键_憋了三年,史上最全的 F1~F12 键用法整理出来了
  13. C3P0连接池 jar包 下载
  14. ITIL4考试练习题
  15. List集合去重的三种方法
  16. [uboot]What is MLO file?
  17. 将Planet卫星影像数据添加到QGIS, ArcGIS Pro 或 ArcGIS 10.X方法,以ArcGIS Pro为例。
  18. mac创建文件服务器,mac命令行终端怎么创建文件 mac命令行终端创建文
  19. 手机WAN远程唤醒主机
  20. 诗词在线网络月刊2009年第五期

热门文章

  1. mysql查询:有关时间的筛选
  2. database - sqlalchemy
  3. 第1周 - 课程材料
  4. 《珠珠图案》教程三:串珠图案设计师。
  5. 中国移动的“野望”:张艺兴入职的背后,是5G时代快速抢占年轻人眼球的攻坚战
  6. python量化交易之路
  7. python 量化交易知识
  8. Tubi 快讯|中国团队 100 人啦
  9. Android Studio配置JavaCPP Presets for OpenCV
  10. java 操作excel jxl_java 中JXL操作Excel实例详解