梳理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和事务管理器中设置的数据源是一样的。