当前位置: 首页 > news >正文

梳理Bean的创建流程

1.Bean的创建流程

1.1前置准备工作

        现在咱们要创建一个Bean托管到IOC容器中,来展示整个Bean创建的流程。

1.1.1准备Bean

        准备一个Bean:OrderService

@Service
public class OrderService {
}

        再准备一个Bean:UserService

        在这里Bean中定义了一个成员字段orderService,使用@Autowired将OrderService注入到字段中。

@Service
public class UserService {@Autowiredprivate OrderService orderService;public OrderService getOrderService() {return orderService;}}

1.1.2定义配置类

        使用一个配置类将两个注解扫描托管:

@Configuration
@ComponentScan("com.xinghai.service")
public class AppConfig {
}

1.1.3测试Bean的托管注册

        测试Bean的托管注册:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
System.out.println(userService);

        这里使用的是AnnotationConfigApplicationContext去加载Spring上下文ApplicationContext容器,一会讨论的也是AnnotationConfigApplicationContext这个容器的加载流程。

1.2梳理整个流程

        使用注解声明Bean的创建流程是:

        1.调用无参构造器创建对象。

        2.依赖注入(属性注入),向创建的空对象中注入字段数据。

        3.初始化前,调用Bena类中被@PostConstruct注解的成员方法。

        4.初始化,实现InitializingBean接口,调用重写的afterPropertiesSet方法。

        5.初始化后,进行AOP封装操作,生成代理对象(如果不需要AOP,就不会执行该流程,直接执行下一流程)

6.构造代理对象(如果执行了AOP),将原始对象/代理对象挂起到Bean容器中。

1.3详解所有流程

1.3.1依赖注入

        依赖注入其实就是扫描类中的所有字段,查找字段中是否有Autowired注解,如果有就去为字段设置Bean对象。

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

1.3.2初始化前

        初始化前执行的是Bean中被@PostConstruct注解的成员方法,在Bean创建的过程中这些方法会在初始化前,依赖注入后回调:

@PostConstruct
public void a() {}

        在Spring中的原理其实也是很简单,基本上就是使用的反射,看方法上是否有@PostConstruct注解,如果有就会回调:

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

1.3.3初始化

        初始化执行逻辑需要Bean的类去实现InitializingBean接口,重写其中的afterPropertiesSet方法,在初始化过程中会去回调这个方法中重写逻辑:

@Service
public class UserService implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {// 执行的逻辑}
}

        在Spring中的逻辑是通过instanceof实现的:

if (userService instanceof InitializingBean) {((InitializingBean) userService).afterPropertiesSet();
}

1.4Bean加载的要点

1.4.1推断构造方法

1.4.1.1使用哪个构造方法

        将Bean托管到IOC容器时,构造空壳子对象的时候调用的构造方法不一定是无参构造器。

        请记住以下准则:

        1.Bean类中如果只有一个构造方法,Spring就会调用这个构造方法。

        2.Bean类中如果有多个构造方法,且有无参构造器,就会调用无参构造器。

        3.Bean类中如果有多个构造方法,但是没有无参构造器,就会抛出错误。

        4.Bean类中如果没有显示声明构造器,就会使用默认的无参构造器。

1.4.1.2构造方法中Bean的注入

        Bean注入规则:先byType再byName。

        假如现在有以下的一个构造方法:

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

        这个构造方法接收一个OrderService类型的参数,Spring在进行依赖注入的时候,会先byType去寻找这个类型的参数,如果Spring找不到就会直接抛出错误:

        如果Spring中定义了很多OrderService类型的Bean对象:

@Configuration
@ComponentScan("com.xinghai.service")
public class AppConfig {@Beanpublic OrderService orderService() {return new OrderService();}@Beanpublic OrderService orderService2() {return new OrderService();}@Beanpublic OrderService orderService3() {return new OrderService();}
}

        Spring在寻找OrderService对象的时候,发现会找到多个OrderService对象,此时就会改变策略,根据Bean的名称去查找。

        值得注意的是,使用@Component/@Bean定义的对象都有自己的命名原则的:

        @Component注解的Bean,默认以类的名称作为Bean的名称,注解可以接收value定义Bena的名称。

        @Bean注解的Bean,默认以函数的名称作为Bean的名称。

        可以使用ApplicationContext中的getBean,传入Bean的名称获取到IOC中托管的Bean:

OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
System.out.println(orderService);
OrderService orderService2 = applicationContext.getBean("orderService2", OrderService.class);
System.out.println(orderService2);
OrderService orderService3 = applicationContext.getBean("orderService3", OrderService.class);
System.out.println(orderService3);

        如果在byName的过程中,找不到指定名称的对象,那么就会抛出错误:

OrderService orderService4 = applicationContext.getBean("orderService4", OrderService.class);

        其实完全可以理解为,寻找Bean的过程是这样的:

        1.首先会根据类型去检索byType:

        此时在Spring中维护了一个HashMap,key对应的是Class类型对象,value对应的是Class类型对象对应的对象。

        用伪代码进行表示:

Map<Class<?>, List<Object>> typeMap = new HashMap<>();

        里面维护的其实就是一个Class类型的对象key,List<Object>类型的对象value,根据key找到的List中如果只有一个对象,就直接返回。如果找到的List中有多个对象,就去走byName的逻辑。

        2.如果byType失败,就会根据名称取检索byName:

        用伪代码表示:

Map<String, Object> nameMap = new HashMap<>();

        里面维护的其实就是一个String类型的对象key,Object类型的对象value,根据name去找,如果找到了就成功返回,如果找不到就是没有,就直接抛出错误。

1.4.1.3指定构造方法

        指定构造方法意思就是有多个构造方法的时候,想要通过指定调用哪个构造方法的方式,实现Bean前身空对象构造的效果。

        这里Spring提供了@Autowired注解,在构造方法上使用@Autowired注解,就可以实现使用指定构造方法构造Bean对象:

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

1.4.2AOP执行流程

1.4.2.1整体流程梳理

        AOP使用的是CGLIB生成代理对象,这个执行过程是在初始化后置方法执行完毕后调用(即initializing中的afterPropertisSet方法)。

        AOP流程会将原始对象包装为代理对象,将代理对象托管到IOC容器中,也就是说生成的是由AOP包装的代理对象,而不是原始的代理对象了。

        即Bean在创建的流程中,如果需要调用AOP,IOC中托管的就是代理对象,如果不需要调用AOP,IOC中托管的是原始代理对象。

1.4.2.2方法调用现象观察

        现在以DEBUG的方式去调用被AOP代理的方法。

        现在先在Bean类中定义测试方法:

public void test() {System.out.println("test 测试执行");
}

        定义切面类对象:

@Aspect
@Component
public class TestAspect {@Before("execution(public void com.xinghai.service.UserService.test())")public void testBefore() {System.out.println("test Before");}
}

        以DEBUG的方式调用被AOP装载的方法:

        可以看到当前这个代理对象中的orderService字段是完全为null的。

  

        接着我们跳到原始对象中test方法内部:

      接着我们再去调用代理对象中的test方法,看一下orderService字段是否有值:

        可以发现在原始对象内部,orderService字段是有值。

        现在可以确定的现象就是:

        1.代理对象中的orderService字段是没有值的。

        2.原始对象中的orderService字段是有值的。

        那么为什么会出现这样的现象呢?

1.4.2.3原理刨析

        上面的现象其实需要从AOP原理的角度去分析:

        刚刚已经提及过了,Spring中的AOP的原理是CGLIB代理,即使用字节码操作,生成目标代理对象子类的方式去生成一个代理对象。

        注明:CGLIB代理是以字节码操作生成继承类的方式做的代理对象,Java Proxy代理是以实现接口的方式生成的代理对象。

        伪代码如下:

public class UserServiceProxy extends UserService {@Overridepublic void test() {super.test();}
}

        但是真的是这样吗?

        其实是不是的,因为如果是这样的,那为什么在基类方法调用中是有值的,派生类中方法调用时是没有值的呢?而且为什么派生类和基类对象是两个对象呢?如果是继承的这种方式,不应该是一个对象吗?

        可以发现当前两个对象的内存地址都是不一样的。

        所以其实在AOP真实的实现机制内部,是这样的:

public class UserServiceProxy extends UserService {private UserService target;public void test() {target.test();}}

        Spring在生成Bean对象的时候,检测到需要AOP操作时,在Bean对象初始化完成之后,会在初始化后的阶段进行AOP操作生成代理对象,借助CGLIB生成代理对象的时候,生成的代理对象类是以下的代码定义:

public class UserServiceProxy extends UserService {private UserService target;public void test() {target.test();}}
U

        UserServiceProxy代理类会继承UserService类,来保证类型一致性,代理类会有一个字段target,类型就是原始类UserService,而且也会生成和原始类一样的方法,方法中会调用target字段中原始类对象的方法即可。

        生成的代理类继承的原始类中的字段是不会进行赋值的,所以在进行查看代理类中字段的时候,里面原始类拥有的字段是空的,但是在调用原始类的方法时,里面的字段就不是空了。

1.4.2.4切面执行流程

        切片AspectJ中,里面定义了@Before这个切面,那么切面是怎么执行的呢?

        切面的执行,均依赖的是生成的代理类对象中进行的额外操作,这里我们暂时先不进行更深入的原理刨析,先简单介绍以下执行流程。

        当Spring扫描到AspectJ对象之后,会去AspectJ对象中扫描所有的切面,在扫描切片的时候判断切片对应的类和方法,将这些切片的对应逻辑织入到对应的方法中。

        @Before切片的逻辑会被织入到Spring生成的代理类中,在target.test()方法调用前去调用。

2.事务管理详解

2.1事务管理配置

        使用Spring整合JdbcTemplate操作MySQL需要配置DataSource数据源,JdbcTemplate,如果需要使用事务,需要配置PlatformTransactionManager事务管理器,并且在配置类上使用@EnableTransactionManagement注解启动事务配置。

@EnableAspectJAutoProxy
@Configuration
@ComponentScan("com.yangge")
@EnableTransactionManagement
public class AppConfig {@Beanpublic JdbcTemplate jdbcTemplate() {return new JdbcTemplate(dataSource());}@Beanpublic PlatformTransactionManager transactionManager() {DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource());return dataSourceTransactionManager;}@Beanpublic DataSource dataSource() {DriverManagerDataSource dataSource = new DriverManagerDataSource();dataSource.setUrl("jdbc:mysql://localhost:3306/test");dataSource.setUsername("root");dataSource.setPassword("123456");return dataSource;}}

2.2测试刨析Spring事务原理

        将JdbcTemplate注入到UserService中,在test方法注解上使用@Transactional注解,配置该方法被Spring的事务托管管理,并在test方法中使用jdbcTemplate进行执行SQL操作,在最后抛出了一个运行时异常,测试事务的回滚操作。

@Service
public class UserService {@Autowiredprivate OrderService orderService;@Autowiredprivate JdbcTemplate jdbcTemplate;public OrderService getOrderService() {return orderService;}@Transactionalpublic void test() {jdbcTemplate.execute("INSERT t1 VALUES(2,2)");throw new RuntimeException();}}

        进行DEBUG测试,可以发现在没有开启AOP的情况下,Spring事务会帮我们生成一个事务代理对象,帮我们执行一些对应的操作。

        事务执行的代理对象的代理逻辑如下:

public class UserServiceProxy extends UserService {private UserService target;public void test() {// 1. @Transactional// 2. 创建一个数据库连接connection(事务管理器进行创建)// 3. connection.autocommit = falsetarget.test();// 4. connection.commit()// 4. connection.rollback()}}

        1.首先先判断原始方法上是否有@Transactional注解,如果有就进行事务的相关操作,如果没有就不会执行事务的相关操作了。

        2.创建一个数据库连接connection,该数据库连接是由事务管理器进行创建。

        3.将connection的autocommit自动提交属性设置为false,执行SQL的时候,就不会自动提交了。

        4.当原始方法执行结束后,会判断是否抛出了异常,进行提交事务或者是回滚事务。

2.3dataSource问题

       在配置类中,可以发现配置JdbcTemplate和事务管理器的时候,调用了两次DataSouce方法,难道就是生成了两次数据源吗?JdbcTemplate和事务的数据源不一样嘛?

@EnableAspectJAutoProxy
@Configuration
@ComponentScan("com.yangge")
@EnableTransactionManagement
public class AppConfig {@Beanpublic JdbcTemplate jdbcTemplate() {return new JdbcTemplate(dataSource());}@Beanpublic PlatformTransactionManager transactionManager() {DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource());return dataSourceTransactionManager;}@Beanpublic DataSource dataSource() {DriverManagerDataSource dataSource = new DriverManagerDataSource();dataSource.setUrl("jdbc:mysql://localhost:3306/test");dataSource.setUsername("root");dataSource.setPassword("123456");return dataSource;}}

        其实这就得益于@Configuration注解帮助我们,将JdbcTemplate和事务管理的数据源设置为同一个,当dataSource方法被调用时,Spring框架会先查找DataSource对象在IOC容器中是否存在,如果不存在就会生成一个DataSource对象返回,如果有就返回已经存在的,这就保障了JdbcTemplate和事务管理器中设置的数据源是一样的。


文章转载自:
http://avadavat.zzyjnl.cn
http://allopatrically.zzyjnl.cn
http://amphiploid.zzyjnl.cn
http://cankerroot.zzyjnl.cn
http://angolan.zzyjnl.cn
http://brashly.zzyjnl.cn
http://batwing.zzyjnl.cn
http://carbolize.zzyjnl.cn
http://bluejacket.zzyjnl.cn
http://bate.zzyjnl.cn
http://cervical.zzyjnl.cn
http://cathouse.zzyjnl.cn
http://base.zzyjnl.cn
http://abstrusity.zzyjnl.cn
http://cheerful.zzyjnl.cn
http://chamiso.zzyjnl.cn
http://biochemistry.zzyjnl.cn
http://blench.zzyjnl.cn
http://binate.zzyjnl.cn
http://altimetry.zzyjnl.cn
http://catenarian.zzyjnl.cn
http://christabel.zzyjnl.cn
http://bitterly.zzyjnl.cn
http://chronometric.zzyjnl.cn
http://agenesis.zzyjnl.cn
http://ate.zzyjnl.cn
http://bonze.zzyjnl.cn
http://affirmable.zzyjnl.cn
http://anuclear.zzyjnl.cn
http://aniseed.zzyjnl.cn
http://www.dtcms.com/a/280816.html

相关文章:

  • mongoDB的CRUD
  • Visual Studio 现已支持新的、更简洁的解决方案文件(slnx)格式
  • 云服务器如何管理数据库(MySQL/MongoDB)?
  • 基于STM32G431无刷电机驱动FOC软硬件学习
  • iOS高级开发工程师面试——常见第三方框架架构设计
  • C++学习笔记五
  • Gemma-3n-E4B-it本地部署教程:谷歌开源轻量级多模态大模型,碾压 17B 级同类模型!
  • SHAP 值的数值尺度
  • Conda 核心命令快速查阅表
  • 技术演进中的开发沉思-35 MFC系列:消息映射与命令
  • Keepalived双机热备
  • 网络安全职业指南:探索网络安全领域的各种角色
  • 003大模型基础知识
  • React 实现老虎机滚动动画效果实例
  • AutojsPro 9.3.11 简单hook
  • Pixel Reasoner:通过好奇心驱动的强化学习激励像素空间推理
  • 简单2步配置CadenceSkill开发编辑器,支持关键字高亮
  • [AI-video] Web UI | Streamlit(py to web) | 应用配置config.toml
  • (李宏毅)deep learning(五)--learning rate
  • 从底层技术到产业落地:优秘企业智脑的 AI 革命路径解析
  • NAT的核心原理以及配置
  • CCF-GESP 等级考试 2025年6月认证Python四级真题解析
  • RDMA over RoCE V2设计2:系统框架设计考虑
  • Datawhale AI夏令营 机器学习2.1
  • 详解低速容错CAN(附与高速CAN对比表)
  • RabbitMQ第三章(企业级MQ应用方案)
  • 基于uniapp+vue3封装的一个日期选择组件
  • 【图像处理基石】什么是解析力?
  • A*算法详解
  • Transformer江湖录 第九章:大道归一 - 总结与展望