springboot -启动流程
功能分类
1 为准备环境
2 3 4 5 6 为准备 BeanFactory
7 8 9 10 12 为准备 ApplicationContext
11 为初始化 BeanFactory 中非延迟单例 bean
这一步创建和准备了 Environment 对象,它作为 ApplicationContext 的一个成员变量
管理各种 键值信息
@Value 值注入
beanFactory 和application 组合关系,application 有beanfactory的引用。
这一步获取(或创建) BeanFactory
理解 BeanFactory 的作用
理解 BeanDefinition 的作用
BeanDefinition 从何而来
BeanFactory 的作用
是负责 bean 的创建、依赖注入和初始化
BeanDefinition
作为 bean 的设计蓝图,规定了 bean 的特征,如单例多例、依赖关系、初始销毁方法等
BeanDefinition 的来源有多种多样,可以是通过 xml 获得
、通过配置类获得
、通过组件扫描获得
,也可以是编程添加
这一步会进一步完善 BeanFactory,为它的各项成员变量赋值
比如el 表达式解析器,类型转换器 内置的beanpostprocessor
模板方法设计模式
调用 beanFactory 后处理器
后置处理器扩展bean工厂充当 beanFactory 的扩展点
,可以用来补充或修改 BeanDefinition
比如 ConfigurationClassPostProcessor 解析配置类相关的注解 @Bean @Import @Congifuration。 @PropertySourcePlaceHolderConfigure 解析${}占位符
常见的 beanFactory 后处理器有
ConfigurationClassPostProcessor
– 解析 @Configuration、@Bean、@Import、@PropertySource 等PropertySourcesPlaceHolderConfigurer
– 替换 BeanDefinition 中的 ${ }MapperScannerConfigurer
– 补充 Mapper 接口对应的 BeanDefinition准备bean的后置处理器
,beanFactory 中找出 bean 后处理器,添加至 beanPostProcessors 集合中 ,用于第11步创建bean的各个阶段时用到的。
beanpostprocessor 作用 :,作用 充当 bean 的扩展点
,可以工作在 bean 的实例化、依赖注入、初始化阶段
,
掌握常见的bean 后处理器
理解bean的后置处理器的作用
掌握常见的bean 后处理器
充当 bean 的扩展点
,可以工作在 bean 的实例化、依赖注入、初始化阶段
,常见的有:
这一步是为 ApplicationContext 添加 messageSource 成员,作用 实现国际化功能
去 beanFactory 内找名为 messageSource 的 bean,如果没有,则提供空的 MessageSource 实现
作用 为ApplicationContext 准备事件发布器,用来发布事件。
事件广播成员
它的作用
是发布事件给监听器一句话: 为Application 准备监听器。用来接收事件广播器发布的事件
并添加至 applicationEventMulticaster
用来接收事件广播器发布的事件
,有如下来源
·初始化所有非延迟加载的单例bean,在初始化的过程中,执行bean的后置处理器.·
conversionService
也是一套转换机制,作为对 PropertyEditor 的补充
embeddedValueResolvers 即内嵌值解析器,用来解析 @Value 中的 ${ },借用的是 Environment 的功能
singletonObjects 即单例池,缓存所有单例对象
准备生命周期管理器。发布ContextRefreshed 事件。整个refresh 完成。
@Service
public class Service1 {
@Autowired
private AccountMapper accountMapper;
@Transactional
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
原因:Spring 默认只会回滚非检查异常 也就是运行时异常。
编译期异常,spring 不回滚
解法:配置 rollbackFor 属性
@Transactional(rollbackFor = Exception.class)
@Service
public class Service2 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
try {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
throw new RuntimeException(e);
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
@Service
public class Service3 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
@Aspect
public class MyAspect {
@Around("execution(* transfer(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
LoggerUtils.get().debug("log:{}", pjp.getTarget());
try {
return pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
最外层调用事物切面
中间一层 自定义切面
最里面一层 才是目标 方法
所以目标方法抛出异常,中间一层的自定义切面处理,
try catch 掉了异常,没有向外抛出。
所以最外层事物切面 不知道发生异常, 会提交事物。
在最外面一层
),但如果自定义的切面优先级和他一样, 则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…@Order(Ordered.LOWEST_PRECEDENCE - 1)
(不推荐)@Bean
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false);
}
父容器
现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效
下面代码 期望foo方法 和bar 都有事物。
@Service
public class Service6 {
// required 之前没有事物,创建事物。如果已经有了事物,加入。
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
this.bar();// this 不是代理对象,而是目标对象本身
}
// 不管当前有无事物,都开启事物
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
让bar 方法也开启事务
@Service
public class Service6 {
@Autowired
private Service6 proxy; // 本质上是一种循环依赖
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
System.out.println(proxy.getClass());// 代理对象
proxy.bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
解法2,还需要在 AppConfig 上添加 `@EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
((Service6) AopContext.currentProxy()).bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
上面的代码实际上是有 bug 的,假设 from 余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况
针对上面的问题,能否在方法上加 synchronized 锁来解决呢?
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public synchronized void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
答案是不行,原因如下:
synchronized 锁的是目标对象 里面的3条sql 语句,没有锁住代理对象的commit
所以不行。
synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内
解法1:synchronized 范围应扩大至代理方法调用
解法2:使用 select … for update 替换 select
事物失效
spring事务失效的12种场景
1.方法访问权限问题,只支持public
2.方法用final修饰,被static 修饰,动态代理不能代理final方法
3.方法内部调用,同一对象内调用没有使用代理 this 调用,未被aop事务管理器控制
4.未被spring管理 该类 没有加@service
5.多线程调用
,事务管理内部使用threadLocal,不同线程间不在同一事务
6.表不支持事务 比如myisam 引擎。
7.未配置事务
8.错误的传播属性
9.自己吞了异常 未在catch 块 抛出异常。
10.手动抛了别的异常 spring 只针对 runtimeException 和 error 回滚,对于普通的非运行异常 不回滚
11.`自定义了回滚异常与事务回滚异常不一致 ``
rollbackFor=BusinessException.class
抛出的异常 不是自定义异常
12.嵌套事务回滚多了,需要局部回滚的地方未做异常控制
给@bean 返回值 的是生成代理对象。
@bean 配合@Value , 返回 bean工厂 postprocessor对象,可能导致@value 注入失败。
解决 返回@Bean bean工厂处理器, 使用 static 修饰。不会影响@value的注入
默认后面的解析会覆盖前面的解析
。( 输出容器中的 bean1)如何能够让 MyConfig(主配置类)的配置优先
,(虽然覆盖方式能解决)解决 :
自动配置原理
@SpringBootConfiguration 是一个组合注解,由 @ComponentScan、@EnableAutoConfiguration 和 @SpringBootConfiguration 组成
META-INF/spring.factories
中的自动配置类为什么不使用 @Import 直接引入自动配置类
有两个原因:
@Import(自动配置类.class)
,引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置因此,采用了 @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector.class
去读取 META-INF/spring.factories
中的自动配置类,实现了弱耦合。AutoConfigurationImportSelector.class
实现了 DeferredImportSelector 接口,让自动配置的解析晚于主配置的解析单例模式 跟我们理解的不同。
构建者模式
工厂方法模式
适配器模式 adapter
代理模式
责任链模式
观察者模式
使用两级缓存 能解决平通对象循环依赖注入的问题,
但是不能解决代理对象的循环依赖注入。
因为代理对象创建对象过晚。
代理对象循环依赖解决方法。
思路1
思路2
注入的是代理对象
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(@Lazy B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private ObjectProvider<B> b;
public A(ObjectProvider<B> b) {
log.debug("A({})", b);
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
// a 里面用到b,属性b 是工厂对象,getObject 是B 对象
System.out.println(context.getBean(A.class).b.getObject());
System.out.println(context.getBean(B.class));
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)//创建b生成代理对象
@Component
class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
@Component
class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b; // b代理对象,那么就可以推迟b的创建
public A(B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
单例set方法 (包括成员变量) 循环依赖,spring 会例用三级缓存解决,不需要额外的配置
构造方法 以及 多例的循环依赖。
@Lazy 产生代理对象,解决。
@Scope 产生代理对象,解决。
ObjectFactory&ObjectProvider 工厂方式推迟注入对象的获取,解决。
new SpringApplication(primarySources)
.run(args);
应用容器
类型 根据jar包(非web 应用,servletweb 类型,reactorweb 类型)得到 SpringApplicationRunListeners,名字取得不好,实际是事件发布器
从配置中获取重要的事件发布器:SpringApplicationRunListeners·
: 事件发布器:在springboot 启动过程中 一些重要节点,执行完,发布对应事件。
封装启动 args
准备 Environment 添加命令行参数(*)
3-6 环境变量有关。
ConfigurationPropertySources 处理(*)
通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)
绑定 spring.main 到 SpringApplication 对象(*)
打印 banner(*)
创建容器 构造方法推断出的容器类型创建容器。
准备容器
回调调用之前(构造函数时)添加的初始化器,进行增强。
加载 bean 定义
read.register() beandefinition
进行加载beandefinition
refresh 容器
执行 runner
执行 CommandLineRunner 、ApplicationRunner 接口的run()方法。
commandLineRunner 和ApplicationRunner 区别 参数区别
c 参数来自main方法参数。
发布 application ready 事件6️⃣
这其中有异常,发布 application failed 事件7️⃣
整个spring框架启动分为两部分,构造SpringBootApplication对象和执行run方法
核心注解@SpringBootConfiguration标识启动类为配置类,
@EnableAutoConfiguration通过内部@Import注解AutoConfigurationImportSelector.class实现自动装配,
@ComponentScan默认扫描当前目录及子目录下的bean。
SpringBootApplication的构造方法主要做了几件事。
根据是否加载servlet类判断是否是web环境
获取所有初始化器,利用spi机制,扫描所有META-INF/spring.factories下的ApplicationContextInitializer子类通过反射拿到实例,在spring实例启动前后做一些回调工作。
获取所有监听器 applicationLister,同2,也是扫描配置加载对应的类实例。
定位main方法
run方法主要
1.读取META-INF/spring.factores,将事件发布器 SpringApplicationRunListeners类型存到集合中
事件发布器:在springboot 启动过程中 一些重要节点,执行完,发布对应事件。
2. prepareEnvironment(listeners, applicationArguments);配置环境 将配置文件读取到容器中
3.打印 banner(*)
4. 根据构造方法推断出的容器类型,创建容器。
5.准备容器
回调调用之前(构造函数时)添加的初始化器,进行增强。
6.加载 bean 定义
read.register() beandefinition 进行加载beandefinition
7.refreshContext(context); 刷新容器
8.执行 runner
执行 CommandLineRunner 、ApplicationRunner 接口的run()方法。
在SpringBootApplication 注解里面有一个@EnableAutoConfiguration 注解实现自动装配。
主要以考三步骤。
使用了spi机制。
异步发送事件