第二章Spring

第二章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就通过这样实现

扫描二维码关注公众号,回复: 11218909 查看本文章

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注解即大功告成

原创文章 5 获赞 5 访问量 288

猜你喜欢

转载自blog.csdn.net/weixin_40503364/article/details/106171404