Spring 是如何创建一个bean对象的
什么是单例池,作用是什么
单例池也就是这个ConcurrentHashmap,用来保存单例的bean对象,如果是多例的bean对象,那么创建bean的步骤中,就不会有下面的放入map这一步
PostConstruct注解是如何工作的
我们现在的需求是,想让UserService中的User属性,能在UserService初始化前被赋值,且赋的值是从数据库中读取到的
实现原理,就是在初始化UserService对象的过程中,遍历UserService中所有带有PostConstruct注解的方法,然后通过反射的方式,来调用这些方法
Bean的初始化是如何做的
上面的给user属性赋值的需求,也可以通过实现innitianlalizBean接口来实现
Bean的实例化:通过无参构造方法得到一个对象
Bean的初始化:就是调用上面构造出来的对象中的某个方法(afterProperteirSet方法)
初始化后
最后,就把代理对象,放入单例池中,这样我们在使用时,直接用代理对象,就能用到用户定义的aop的逻辑
推断构造方法
Spring 在调用构造方法实例化对象时,会先判断调用哪个构造方法,在程序员没有显示写构造方法时,也默认调用无参构造方法,当程序员显示写有参构造方法时, 但没写无参构造方法时,无参构造方法就没有了
我们可以给构造方法加上面的注解,来明确的告诉spring 调用哪个构造方法
什么是推断构造方法中的先byType再byName
单例bean,是指在spring 容器中,同一个beanName只对应一个bean实例,不代表说,一个类只有一个实例,可能一个类有多个实例在容器中,分别叫不同的beanName
依赖注入时,先判断容器中是否已经有OrderService类型实例,如果没有则先创建再注入给UserService,如果有,则先通过byType的方式去匹配,同一个Type有多个实例,则再通过byName的方式匹配,如果再通过name的方式匹配时,匹配不到相同的name也会报错
spring aop是怎么工作的
注意到这里,代理对象从父类继承过来的orderService属性是没有值的,因为spring 只有针对普通对象的依赖注入逻辑,并没有针对代理对象的依赖注入逻辑
所以,如果这里直接用super.test 调用test方法,而test方法中依赖了还为空的orderService属性,所以肯定会报错
上面直接用super调用父类的test方法,是不行的
必须,在代理类中整一个target引用,把已经完成依赖注入的普通对象引用过来,然后调用普通对象的test方法
如果直接用super调用父类中的test方法
Spring 事务是怎么工作的
Spring 事务是通过aop来实现的,
Configuration注解不加,事务是无法使用的
上面就往spring 容器中注入了一个事务管理器
Spring 事务,是通过spring 自己的事务管理器来建立连接,然后再这同一个连接中开始一个事务,在同一个事务中执行多条sql,这多条sql中有一条报错,因为在同一个事务中,所以才能执行回滚。
如果像上面这样,jdbcTemplate.execute时才建立连接,那多条sql就有多个事务,当然也就无法达到事务真正的目的了
Spring 事务失效的根本原因
Spring 事务,就是给加了Transactional注解的方法所在的对象,对这个对象生成一个代理对象
上面这种情况,UserService的代理对象所引用的UserService普通对象,在直接调用a方法, 当然a方法上的事务注解,不会生效
自己注入自己,也能使事务生效
总结:
思考某个方法上的Transactional注解,会不会生效,就看当前是普通对象还是代理对象在调用这个方法。只有调用代理对象的方法时,事务才能生效
Configuration注解的作用是什么
不加configuration注解时,事务好像就失效了一样,
所以,spring 的事务,多个线程中的sql就没办法公用同一个事务,就是因为拿不到同一个连接,因为连接是存在ThreadLocal当中的。
因为,spring 要考虑,一个线程中调用多个方法,不同方法调用的是不同的数据源,也就是为了支持多数据源,所以ThreadLocal的存储格式是上面这样。
此时,jdbcTemplate执行sql是,就需要拿到连接,拿事务管理器中存放的连接,怎么拿,就通过上面的ThreadLocal去自身的线程对象中拿,
首先会拿到一个Map,然后jdbcTemplate就以和自己绑定的dataSource为key去Map中拿连接。
JdbcTemplate需要一个dataSource,事务管理器也需要一个dataSource,站在java的角度,当没有加configuration注解时,上面就是两次方法调用,两次方法调用就会返回两个dataSource对象
因为,没加configuration注解,所以有两个dataSource,所以上面jdbcTemplate以自己的dataSource为kex去Map中拿连接,肯定拿不到事务管理器中的对应连接,所以它会自己另外创建一个连接,然后这个连接不是事务管理器管理的连接,autoCommit默认就不是false(此时,每天sql执行完,立马就会提交),从而就导致了spring事务的失效。
加了configuration注解后,AppConfig对象就变成了代理对象,代理对象中的代理对象逻辑是,先判断dateSource()是否已经被调用过,如果自己调用过,那么ioc容器中就有了一个数据源对象,那么另外的地方再调用dataSource()方法时,就不会再重新创建数据源对象,而是直接去容器中取出已经创建好的数据源对象返回,
上面这种情况,当项目中配置了多数据源,并且加了configuration注解,但是事务仍然会失效,因为事务管理器和jdbcTemplate引用的不是同一个数据源
@Bean注解和@Conponent注解
相同点:都可以给ioc容器注册bean对象
不同点:@Bean需要与@Configuration配合,保证bean对象的唯一性。 @Bean更灵活,比如第三方类库,如果需要注册到ioc容器中,就没办法使用Conponent 注解,只能使用@Bean注解
Spring 用三级缓存来解决循环依赖问题
本身加一个zhouyuMap,就能解决普通的循环依赖问题,如上图
但是,上面在有aop的情况下,就会有问题
有aop时,放入b对象a属性的是a的普通对象,但是,最终放入单例池的是a的代理对象
针对上面的问题,我们就需要解决方案:需要考虑,什么时候需要对a提前进行aop生成a的代理对象
当a出现循环依赖时,就可以考虑对a提前进行aop
二级缓存
为了防止提前AOP生成两个A的代理对象,所以要搞一个二级缓存,让A的代理对象,生成一次后就存起来
C在需要A对象时,就先去二级缓存中查了
二级缓存的目的:当创建b的过程中,发现a出现了循环依赖,要对a提前进行aop生成代理对象,为了保证后续创建c时同样发现a出现循环依赖,不再次重复生成a的代理对象
earlySingletonObjects专门用来存临时的没有经过完整生命周期的单例bean对象/单例bean代理对象,
创建a的普通对象后,往三级缓存中,放入一个lambda表达式 (此时,表达式还没有执行)
流程:
创建b时,先去一级缓存池中找a,没找到则再去二级缓存池中找a,如果还没找到,则从三级缓存中把lambda表达式中把a对应的表达式找出来,执行该表达式,如果a身上有aop那么该表达式执行后,就会生成a的代理对象,否则得到的就是a的普通对象。
不管怎样,得到对象后,还是会最后将生成的对象,放入二级缓存中。此时因为对象,还没有经过完整的bean生命周期,所以就放入二级缓存中。
总结:
真正打破循环的,就是第三级缓存
5.5还会把代理对象,从二级缓存中取出来,放入一级缓存单例池中
这里,是判断第五步还需不需要进行aop,如果第二步提前进行了aop,那么第五步就不需要再进行aop了
@Async注解为什么会导致循环依赖解决不了?
如果test方法上,既有aop拦截, 又有Async注解,那么就会各自生成一个代理对象,aop生成一个,async注解要生效也是通过生成代理对象,这是两个代理对象,流程走不下去就报错了
构造方法导致循环依赖报错
报错原因:
卡死在第1步,创建普通对象失败,因为构造a的普通对象时需要传入b,但是没有b,构造b时又需要传入a,所以就直接报错
Spring MVC
Springboot选择tomcat还是jetty
就看pom文件中,引入的是tomcat还是jetty(如果都引入了,就会报错)
ConditionOnClass注解也就是表示,括号内所包含的所有类,都在classpath中出现,这个注解修饰的类,才生效 。这个生效的类中注入的bean,才会真正进入ioc容器中。
Spring事务怎么实现的
Transactional注解,为什么会失效
Spring 中后置处理器的作用
Spring aop怎么实现的
Spring的单例bean不等于单例模式
如何理解spring boot的starter机制
如何实现一个ioc
Ioc实现机制
简单工厂+反射技术
把创建对象,交给工厂来统一管理
上面是简单工厂
上面是通过反射,传入类全路径,通过简单工厂的方式,来返回对象
ioc和DI的区别
ioc是用来解决耦合的一种设计思想,它来集中管理所有对象,而DI是实现ioc中的重要一环,来注入对象。
松耦合和紧耦合
单一职责
接口分离原则,接口不动,实现换掉就可以了
依赖倒置
BeanFactory作用
BeanFactory也是容器,spring 容器管理着bean的生命周期
BeanDefinition
通过Class.forName得到Class对象,然后通过反射newInstance就可以得到真正的bean对象
BeanFactory和ApplicationContext上下文区别
共同点:都可以作为容器,都可以管理bean的生命周期
不同点:上下文不直接生产bean,而是通过门面模式,依赖beanFactory来getBean,上下文是更多的和用户打交道的,
上面,写错了是BeanFactory
被FactoryBean修饰的bean获取,就变成懒加载了,用到时才生成
Spring ioc的加载过程,重点
先找到invokeBeanFactoryPostProcessor方法(这个方法,就会把类,转成beanDefinition ),找到componentScan注解包路径,扫描类有没有component注解,有这个注解,就把当前这个类,注册为beanDefinition
然后就是生产bean,交给BeanFactory,通过简单工厂模式,调用getBean方法进行生产,
Spring 提供了哪些扩展点
什么是扩展点:spring 对外提供的很多接口或者说钩子方法,当我们实现了这些接口,它就会在特定的点,帮我们执行这些钩子方法,从而我们就可以来对底层进行扩展
也就是,整个bean加载的各个阶段,都有扩展点
1 bean的注册阶段
2 bean的生产阶段,9次
3 bean的初始化阶段,各种Aware接口
4 初始化阶段,各种生命周期的回调方法(也就是,初始化和销毁阶段的各种回调)
被spring 容器管理的,从ioc容器中获取的就是spring bean
Java bean 就是自己new出来的
单例bean的好处
Spring 线程安全问题
自动装配的几种方式
生命周期的几种回调方式
Spring bean在加载过程中,有几种形态
解释bean的生命周期
Spring 如何避免在并发的情况下,获取到不完整的bean
因为加了双重检查锁
线程2走过一级缓存获取后,发现后续的流程被线程1锁住了,线程2就只能等,等线程1走完后,线程2去二三级缓存中找不到想要的对象,但这是线程2并不会直接开始自己动手创建,而是再次调用getSingleton方法,从一级缓存中获取对象,因为线程1走完流程后,已经把自己创建的对象放入了一级缓存,所以线程2这次就可以过去到对象了,就避免了一级创建
双重检查锁,
为什么不把锁直接加到一级缓存处,因为性能
描述beanDefinition加载过程
Bean工厂的后置处理器接口,就可以完成在所有beanDefinition注册完后的扩展
改成多例bean
如何在spring 所有bean创建完后做扩展
方式二,监听refresh 事件,
spring在refresh方法执行完,也就是说所有bean创建完了,会发布一个refresh event事件
Ioc容器加载就是在refresh 方法中完成的
javaConfig是如何替代xml的
两个容器,都有共同点父类AbstractApplicationContext
重点:
在new spring上下文的过程中,在上下文的构造函数中,会注册很多支撑ioc容器整个加载过程的,一些内置的bean处理器
比如上面,解析@Configuration和解析@Autowired注解的处理器
ConfigurationClassPostprosessor实现BeanDefinitionRegistryPostProcessor的原因,就是为了让自己具有动态地注册BeanDefinition的能力,因为它需要解析@Bean等注解,然后把他们注册成为BeanDefinition
Spring事务的传播行为是怎么实现的
上面的倒数第二行,在内嵌事务函数 最后,会判断内嵌事务的newTransaction属性是否为true,因为此时是融入的事务传播行为方式,所以此时内嵌事务函数,对应的代理对象,在最后就不会执行事务提交。
当带有@Transactional注解的函数A,调用带有@Transactional注解的函数B时,就相当于有两个代理对象,是A的代理对象,调用B的代理对象。
A的代理对象逻辑是创建连接,B的代理对象逻辑是,去ThreadLocal中发现有连接,就知道自己是一个内嵌事务,就去判断发现是融入的传播行为,则将newTransaction属性置为false,这样,B的代理对象逻辑,在最后检查newTransaction属性置为false,也B自己就不去提交事务了,而直接返回A中,A在代理逻辑的最后,统一的去提交事务。
Spring 如何管理mybatis的mapper接口的
因为,mybatis的mapper接口,是没有实现类的,只有接口,我们知道接口是无法实例化,也就无法直接被spring ioc容器进行管理的
通过上面,我们知道spring肯定是管理了UserMapper对应的bean的,不然是不可能通过Autowired注入进来的
上面,是普通类,被scanner扫描并进入spring管理过程的
可以看到,spring默认的ClasspathBeanDefinitiinScanner是不会把接口扫描并注册成BeanDefinition的,所以我们需要自己通过BeanDefinitionRegistryPostProcessor这个扩展接口,来把mybatis的UserMapper接口注册成为spring内的BeanDefinition。
又因为,spring默认的ClasspathBeanDefinitiinScanner在扫描.class文件并注册成BeanDefinition的的过程中,会把接口排除出去,所以我们需要自己写一个Scaner类继承ClasspathBeandefinitionScaner,并覆写它的扫描方法,从而不把接口排除在外
又因为,接口本身虽然已经扫描,并注册成为了BeanDefinition,但是它是接口,还是不能进行实例化的,所以需要将该BeanDefinition的beanClass属性,替换称为Jdk动态代理的实例
Spring mvc流程
视图解析器,就是两,字符串类型的视图名hello,返回hello.jsp这个视图View模版,然后最后用数据渲染这个视图模版,成为最后的响应内容
Spring boot整个启动过程,一共发布了9次事件,发布事件就是为了能让外部进行扩展,发布事件对内部来说就是实现解耦
Spring boot内嵌tomcat 启动原理
上面,就是通过ConditionOnClass……,来启用了tomcat(这个类是通过上面的上面那幅图中的类引过来的)
TomcatServetWebServerFactory,这个工厂,就可以帮我们创建tomcat并且启动tomcat,
如上,这里就创建了tomcat,上面这个方法,是在spring boot启动的时候调用的
在自动配置类中启用了内嵌tomcat,并通过上面的@Bean注解,给ioc容器中注册了一个tomcat服务的工厂(tomcat server的工厂)
上面,表示spring boot应用开始启动时, 会先创建spring boot上下文容器,
接着调用spring boot容器类的refresh方法时,来加载真正的spring 容器
也就是真正会调用到AbstractApplicationContext的refresh方法,
然后通过533行的方法,会解析@bean等等注解,加载所有的自动配置类,这其中自然就包括web服务器的自动配置类,
因为系统中有Tomcat 相关的类,所以就启用tomcat 相关的自动配置,这样也就把TomcatServetWebServerFactory这个bean对应的BeanDefinition注册到了ioc容器中
紧接着
执行545行
会获取web server的工厂,也就是 获取到了前面注册进来的TomcatServetWebServerFactory,
执行到上面这个getWebServer方法 ,就会创建tomcat并启动tomcat,并让tomcat阻塞住,等待客户端用户发送请求
Spring mvc怎么集成进来的
Spring boot里面的spring mvc的核心类,DispatcherServlet也是通过自动配置类注入进来的,不是自己以前那样,在web.xml中自己配置的
Spring boot配置文件读取原理和读取顺序
Spring 启动时,会发布上面的事件
这个事件,有7个地方进行了监听,其中第一个就是配置文件监听器,这个监听器就会去加载配置文件,按照上面图中描述的读取顺序读取。
Spring boot默认使用cglib代理,上面就是切换回老的有接口就先使用jdk代理的方式