第二章Spring
2.1 Spring基础
2.2 Spring生命周期完整源码流程
2.3 Spring实例化Bean源码过程(及三级缓存如何处理循环依赖
2.4 AOP底层原理及应用
2.5 SpringMVC到SpringBoot源码演变
2.6 Spring下mybatics源码原理
2.1 Spring基础
想想spring有啥基础好讲的啊哈哈,直接源码了
2.2 Spring生命周期完整源码流程
第一步
Spring中从main方法开始
Main中调用了 XXXApplicationContext(config.class)作为入口运行整个Spring
第二步
在这个ApplicationContext底层extend GeneericApplicationContext 并且实现了3个方法
3个方法:
1 this() : 会调用父类构造方法,父类的构造方法中实例化了Spring工厂
就是我们所知道的beanFactory,并会在后面给beanFactory加入信息,这里只是单纯先实例化
2 register(): 在这里会读取参数config.class,这个类就是配置文件,通过这个配置文件Spring才会知道真正要扫描那些地方比如@component
3 refresh() 核心,在这里synchronize了12个类并且依次执行
这里讲重点几个
a InvokeBeanFactoryPostProcessor(beanFactory)
a:
通过前面register告诉了扫描哪里,这个方法真正执行了所有类扫描。处理各种import(比如@ImportResource(“xxx.xml”,@Import(xxx.class),@MapperScan(…)等)
扫描类比如city.class 利用反射配置
RootBeanDefinition cityBeanDefinition = new ~;
cityBeanDefinition.setBeanClassName(“city”);
cityBeanDefinition.setBeanClass (city.class);
set是否abstract,是否byName/byType/@lazy/单例还是多例等等
所有 beanDefinition注册完后,同时也配置完一个 votaile list用来存贮这写beanDefinition的key比如该list中就存有key “city”。
然后通过遍历这个list把这写beanDefinition全部存入map(比如map.put(“city”,cityBeanDefinition))
这里的map就是上文实例化beanFactory中的一个map。就此整个类注册完毕
b BeanFactoryPostProcessor
b:
这里就是我们开发者可以配置的接口
我们如何自己提供BeanFactoryPostProcessor?
@Component
public woaiyunyunProcessor implements BeanFactoryPostProcessor{
@Override
public ~ BeanFactoryPostProcessor(~ beanFactory){
GenerBeanDefinition g = beanFactory.getBeanDefinition(“city”);
System.out.println(“我爱肉肉”);
c.setBeanClass(TestService.class);
}
}
这个配置后最后spring单例池中city.class就”消失了”,真正执行的是TestService.class
动态代理的也是利用这种机制实现,把xxx.class消失了,用xxxProxy.class在这里来代替他注册到单例池中,比如mybatics就通过这样实现
Spring中自身就有大量BeanFactoryPostProcessor,Spring会自身加上我们自己实现的一起参与构建整个BeanFactoryPostProcessor流程
c finishBeanFactoryInitialization(beanFactory)
c:
到了这里就是真正实例化bean的流程了,通过beanFactory的所有注册信息,调用这个方法实例化所有bean并且放入concurrentHashMap的单例池中
单例池中只有单例,原型和懒加载是不会实例化的
d finishRefresh() //bean实例化到此完全结束
2.3 Spring实例化Bean源码过程(及三级缓存如何处理循环依赖)
回溯到上一章的方法finishBeanFactoryInitalization
这章讲这个方法底层实现
首先要知道循环依赖问题 A中注入B,B中注入A。 当实例化A时候会实例化B,因为B也注入了A再实例化A,死循环反复。所以后面利用三级缓存,三个缓存来处理
几个重要定义
三级缓存都是map
一级缓存:concurrentHashMap单例池,存放已经实例化完整的bean
二级缓存: singletonFactories 放ObjectFactory工厂,存能够生产半成品对象的工厂
三级缓存:earlySingletonObjects 放Object,存半成品对象
半成品bean: 创建状态下的bean,不是完整的bean,存放在三级缓存中。
通过半成品概念,还区别是否处于循环依赖中间的状态
处理循环依赖真正执行流程
A getBean -> new A(此步骤会在二级缓存中存入A的工厂对象) -> 因为注入了B,B getBean -> new B -> 因为注入A,要找A,一级缓存中无A -> 三级缓存中也没有A -> 步骤6下A前面已经创建过,是创建创建状态。走下面第7步调用, 因为前面new A时候二级缓存已经有A工厂对象,用该工厂对象生产A半成品bean,A半成品bean放入三级缓存,二级缓存中的这个A工厂对象删除。死循环解除
实例化Bean完整执行流程
1 先判断是否是单例(Spring中百分之90以上是单例)
2 是单例走getBean(beanName)
3 先判断BeanName是否符合规则,符合然后走getSingleton(beanName)
4 走singletonObjects从上一章所说concurrentHashMap的单例池,这里单例池是一级缓存,一级缓存中找是否已创建过bean,找到则获取并直接返回,流程结束,找不到走5
5 判断是否能在三级缓存中拿到,拿到则返回,拿不到走6
6 判断是否是正在创建状态isSingletonCurrentlyInCreation
7 如果是创建状态,则从二级缓存工厂中拿出该生产对象,并通过二级缓存生产半成品状态bean。将该半成品bean存入三级缓存用并删除二级缓存中这个生产生产对象,返回该bean,bean实例化该流程结束
8 如果不是创建状态,则开始调用createBean方法
9 开始调用多个BeanPostProcessor(核心)
BeanPostProcessor是一个接口,两个方法 postProcessBeforeInitialization和 postProcessAfterInitialization.
所有后置处理器都继承了该接口,以策略设计模式,用几十个实现类共同实现了该接口
如自动注入,callback,AOP等都是不同独立的BeanPostProcessor实现类实现这些功能
第二个bean后置处理器利用反射才真正实例化bean
10 然后判断是否允许循环依赖,默认是true,我们可以改成false不允许循环依赖
11 允许循环依赖下,二级缓存中put该工厂缓存,this.singletonFactories.put(beanName,singletonFactory) 这个工厂缓存singletonFactory可以用来实例化半成品bean,但不是bean。也就是第7步,循环依赖下利用这个二级缓存产生bean保存到三级缓存中。
12 之后再经历n次bean后置处理器,bean实例化完全结束并加入一级缓存单例池中
相关问题
为什么需要三级缓存,直接二级缓存中拿不好吗?
因为二级缓存中存放的是能生产bean的工厂,工厂本身很复杂,代价高,不适合多次调用。每次调用相当于就是重新创建一次新的半成品bean。三级缓存只存半成品bean对象,取出性能高,并且保存后就会直接删除二级缓存中这个工厂对象。
为什么需要二级缓存?
主要因为要处理循环依赖问题,其次工厂能判断是否需要需要动态代理等等,利用策略模式+工厂设计模式生成合格的bean
AOP实现方式及几种实现方式执行顺序及为什么?
3种方式
- Implement接口,重写intit post方法
- Xml配置 init post
- 加注解@
3中执行顺序 方法3先于方法2先于方法1
Why? BeanPostProcessor处理执行顺序导致
2.4 AOP原理及应用
AOP大体过程
Spring AOP{
A a = new A();
Aop(a){
Return proxy.newInstance(a.getImpl(),InnovationHandler);
}
}
AOP也是重写 BeanPostProcessor接口下postProcessAfterInitialization干预bean初始化实现的。真实实现类不在单例池中,而是代理类注册在单例池中。2.2章节中重写BeanFactoryPostProcessor也提到过类似方法
AOP应用
@AspectJ注解 是一种第三方专门处理aop的技术,但是底层实现和Spring AOP毫无关系。那么为什么Spring注解也用Aspect呢。因为一开始Spring AOP使用极其复杂,后来借鉴了AspectJ的实用风格。
AOP是一种标准
Spring AOP动态织入(借助AspectJ语法风格)
AspectJ 静态织入
1 Config.class中加注解@EnableAspectJAutoProxy开启AOP,或者xml中加AspectJ
2 应用
@Component
@Aspect //定义切面
public class Aspect{
@Point cut(“within(com.xx.xx.xx)”)//定义切点
Public void pointCut(){
}
}
@Before(“pointCut()”)
public void advice(){
//业务逻辑
}
3 场景-横向切面
Controller层面 日志记录,
service层面 异常处理,
dao层面 事务,检查性能等等
都不关心纵向切面主要业务逻辑,AOP关心切面时间和顺序
AOP简单原理
目标类是接口则用 JDKProxy实现,否则用Cglib实现
JDKProxy:InvocationHandler接口和Proxy类。 在BeanPostProcessor下利用Java反射,重写postProcessAfterInitialization干预bean初始化实现的。真实实现类不在单例池中,而是代理类注册在单例池中。
Cglib: 通过ASM(二进制字节码操作类库)直接修改二进制字节码实现生成动态代理。
ASM -> AOP
ASM原来版本
ClassWriter cw = new ~;
ClassReader cr = new ~;
cr.accept(cw,0);//新的字节码产生。利用访问者设计模式,结构不变情况下动态改变对内部元素
byte[] res = cw.toByteArray();
ASM AOP版本
ClassWriter cw = new ~;
ClassReader cr = new ~;
ClassVisitor cv = new ~(){
重写访问者和适配器,实现AOP
}
cr.accept(cv,0);//
2.5 SpringMVC到SpringBoot源码演变
传统SpringMVC下配置
web.xml //功能初始化Spring上下文
applicationContext.xml
springmvc.xml
1 web.xml下
<context-param> //配置applicationContext.xml参数给listener
<listener>
<servlet> //给容器tomcat/Jetty注册一个servlet拦截所有请求
tomcat是一个程序入口,而tomcat入口是web.xml,web.xml启动spring上下文,tomcat启动时加载web.xml
2 applicationContext.xml
扫描业务类 DAO等
3 springmvc.xml
扫描controller,可以配置视图解析(不是必须要的)
SpringBoot没有web.xml如何注册DispatcherServlet?
boot使用java代码完成0配置注册和实例化
public void onStartup(ServletContext ~){
register(config.class);
refresh();
DispatcherServlet ~ = new ~
//这些Spring整个流程我们前几章也讲过
}
为什么tomcat/Jetty能够开启onStartup方法?
tomcat 8版本以后,对应servlet3.0以后版本
servlet3.0版本规定规范:META-INF下的services下实现的ServletContainerInitializer接口,容器(Tomcat)必须实现onStartup方法。
如果加上@HandleTypes注解,也必须启动该接口实现类onStartup方法
然后boot中gradle加入tomcat依赖后实现了tomcat类,利用该类调用tomcat的API
SpringBootApplicationContext包含三个注释
@EnableAutoConfiguration 启动自动bean加载机制
@ComponentScan 扫描应用程序所在的包
@Configuration 允许Spring注册额外的bean或导入其他配置类
@configuration和@Component区别
@Controller @Service @Repository @Aspect @configuration等等都是@Component元注解实现的 configuration实例化一次后就会从单例池中拿bean,而component会不断重复实例化
2.6 Spring下mybatics源码原理
第一步 实现mybatics代理对象
简易版本mybatics代理对象源码
public class Session{
public static Object queryMapper(Class clazz){
Class [] clas = new Class[]{clazz};
//为什么clas是数组,因为防止多个类impl该接口
Object proxy = Proxy.newProxyInstance(dao.class.classLoader,clas,new yunyunInvocationHandler);
}
}
public class yunyunInvocationHandler implements InvocationHandler{
@Override
~invoke(Object proxy, Method method, Object[] args){
//1 连接JDBC
//environment 环境下传入config->
//configuration 讲xml实例化对象 ->
//sqlsessionFactory sql 加入configuration参数 ->
//Dao mapper = sql.getMapper(Dao.class)
//实例化mybatics的Dao接口(用来连接mysql的接口),
//因为接口不能实例化,这里用到了JDK动态代理
//2 找Dao中select注解
Select selects = method.getAnnotation(Dao.class);
if(selects!=null){
String s = selects.value()[0];//拿到select中sql语句
}
//3 执行JDBC
return ~
}
}
public class Test{
~ main ~{
Dao dao = (Dao) Session.queryMapper(Dao.class);
dao.list();
}
}
第二步把代理对象注入Spring容器中
有4中方法
1 @Bean
2 API registerSingleton
3 factoryBean(真实mybatics使用的方法)
4 factoryMethod
方法1
//Appconfig类中{
@Bean
public Dao dao(){
Dao dao = session.querryMapper(Dao.class);
return dao;
}
}
//弊端也很大当有上千上万个dao则要重复配置上千上万个
方法2
//启动类下
AnnotationConfigApplicationContext ac = new ~;
ac.register(config.class);
//拆开context实现类底层,refresh初始化前,手动配置Dao.class
Dao dao = (Dao) session.querryMapper(Dao.class);
ac.refresh();
//缺点依然很明显,让不懂Spring底层的程序员怎么活
方法3(Mybatics使用的方法)
使用了FactoryBean,这是一个特殊bean
需要Impl FactoryBean接口重写接口三个方法,则可以注入spring容器
@Service
public class yunyunFactoryBean implement FactoryBean{
public Object getObject(){
return session.queryMapper(Dao.class);
}
public class<?> getObjectType(){
return mapperInterface;
}
~ isSingleton(){}
这段代码对应了xml配置
<bean id="userMapper" class="yunyunFactoryBean">
<property name="mapperInterface" value="Dao"/>
</bean>
那么一个xml只能配置一个,如何一次性扫描多个?
传入BeanDefinitions即可一次性扫描多个
public ~ yunyunBeanDefinitionRegister implement ImportBeanDefinition{
~ bd1 = BeanDefinitionBuilder.genericBeanDefinition(yunyunFactoryBean.class);
~ bd2 = bd1.getBeanDefinition(~);
bd2.getPropertyValues().add("xxx.mapper.Dao");
}
然后就是识别这个yunyunBeanDefinitionRegister类
@Retention(~)
@Import(yunyunBeanDefinitionRegister.class)
public @interface yunyunScan{
}
然后在config.class加上@yunyunScan注解即大功告成
转载:https://blog.csdn.net/weixin_40503364/article/details/106171404