飞道的博客

第二章Spring

331人阅读  评论(0)

第二章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种方式

  1. Implement接口,重写intit post方法
  2. Xml配置 init post
  3. 加注解@
    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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场