1.Spring Bean 的生命周期
(1)实例化 ApplicationContext 对象
(2)扫描类、解析类
(3)生成 BeanDefinition 对象,并把解析到的信息:如 类名、父类、类型:原型 prototype 还是 单例 Singleton;有无延迟加载等信息作为对象的属性。
(4)把 BeanDefiniation put 到 map 中。
(5)Spring 会遍历这个 map,先验证,返回 String 类型的 BeanName
(6)推断构造方法,获取所有的 Constructor,根据合适的构造方法通过反射构建对象,并把对象封装成 BeanWrapper 包装类对象。
(7)看是否允许循环依赖,默认是单例模式,是允许循环依赖的,会把对象封装成 SingletonFactory,放置在 SingletonFactories 中。这是二级缓存,本质是个 HashMap,key 是 String 类型,value 是 ObjectFactory 类型。
(8)populateBean 进行属性注入
(9)回调 Aware 接口
(10)执行初始化生命周期方法 invokeInitMethod,顺序是 注解、接口、XML
(11)是否需要代理,需要的话会完成 AOP
(12)经过以上步骤后会把对象放置到一级缓存 SingletonObjects 中,这是个 ConcurrentHashMap,key 是 String 类型,value 是 Object ,放置的是经过完整生命周期的 Bean
以上是单例模式,如果是原型或者延迟加载,只有获取对象时才会创建对象,而容器关闭时单例对象会销毁,而对于原型,一个类不只一个对象,所以不会销毁。
2.Spring 是如何解决循环依赖的
先说说什么是循环依赖:比如 a 中有属性 b,并通过 setB 方法设置;同理,b 中有属性 a,并通过 setA 设置,这样就出现了循环依赖。默认 单例模式是允许循坏依赖的,而原型模式下是会报错的。
假设先走 A 的生命周期,是允许循环依赖的,就会在二级缓存 SingltonFactories 中放置 A 的工厂对象,而且还有一个 Set 是放置“正在创建中”的对象的 beanName 集合,A 的 beanName 会被放置到这个集合中,到了要进行属性注入的时候,会调用 getBean(B) 尝试获取 B 对象,先去一级缓存 SingletonObjects 中尝试获取,是获取不到的,接下来会判断 B 是否在创建中,到 Set 中获取,也获取不到,这时会去创建 B ,走 B 的生命周期,那相同地,B 也会在二级缓存中生成工厂对象、在 Set 中放置 B 的 beanName,到了 B 需要注入属性时,调用 getBean(A) 尝试获取对象 A,先到一级缓存中,获取不到,这时会判断 A 是否正在创建中,在 Set 中是可以获取到 A 的 beanName 的,就会先去三级缓存 EarlySingletonObjects 中,获取不到,再去二级缓存 SingletonFactories 中,可以获取到 A 的工厂,通过 getObject() 方法就可以通过工厂获取到 A 对象,而且会把工厂从二级缓存中 remove() 移除,并把对象放置到三级缓存中,那么获取到 A 对象就可以作为属性给 B 注入了,B继续向下走生命周期,直到放置到 单例池 SingltonObjects 中,就生成了完整的 bean B,然后回退,把它作为 属性注入给 A,A再继续向下走生命周期,这样 A、B 就分别走完了完整的生命周期并进行了相互的属性注入。
3.Bean 三级缓存的作用
一级:SingletonObjects 放置经过完整生命周期的对象
二级:SingletonFactories 用于解决循环依赖,提供工厂,方便代理
三级:EalrySingletonObjects 用于提前暴露对象,如复杂的循环依赖 a、b、c,如果 b 注入属性 a,会从 二级缓存获取到 a,接着移到三级缓存中,接下来 c 也需要注入属性 a 时,就会先从三级缓存中获取,避免重复生产。
4.@Resource 和 @Autowired
@Resource 是 JDK 的 注解,而 @Autowired 是 Spring 框架中的注解。
@Autowired 默认情况下是按照 类型 type 装配 bean 的,而 @Resource 默认是按照 名称 name 装配,如果没有匹配的 name ,才会按照 类型 type 进行装配。
处理这两个注解的后置处理器不同,@Resource 是 CommonAnnotationBeanPostProcessors 处理的。 @Autowired 是AutoWiredAnnotationBeanPostProcessers 处理的。
5.Mybatis
SqlSeesionFactoryBuilder.build() 内部是使用 parse() 解析每一个子节点,比如会把 Configuration 节点解析成 configuration 类,这是个全局配置类,对于 mappers 节点,有 mapper 或者 package 两种子节点,会去相应的逻辑中处理。 mapper 有三种属性:resource、url、class ,这三个中只能指定一个;package 中有 interface、class、xml,有个 isInterface() 方法可以获取到接口,然后判断是否加载过资源,如果没有加载过就会使用 loadResource(),这里的资源是指和接口在同一个包下的 xml,build() 方法会返回一个 DefaultSqlSessinFactory。再使用 sqlsessionFactory.openSession() 会开启一个会话,每个 SqlSession 会对应一个数据库,然后使用 sqlsession.getMapper() ,是通过 MapperProxyFactory 的 newInstance() 产生一个 MapperProxy实例,它是实现了 InvocationHandler 接口的,有实现 invoke() 方法,在这个方法中完成了 SQL 语句的执行。
对于不同类型的语句 select、update、delete、insert 会生成对应的 mapperStatement 对象,对于参数,如果是一个参数,生成 Object 类型的对象,如果是多个参数,生成一个 map,比如说 String 类型的参数,就有 StringTypeHandler() ,会执行 setString() 把参数设置进去,比如说 查询就会执行 doQuery() 方法,会通过 Configuration 类获取数据库的信息,会生成 prepareStatement,本质上也是 JDBC 操作。
6.#{} 和 ${} 的区别
MyBatis 会把 SQL 语句作为 SQLSource 处理的,#{} 会作为静态的 SQLSource,解析时会用 ? 占位符来代替 #{},执行的时候将 ? 占位符用参数变量替换掉。
而 只要带 $,就会作为 动态的 SQLSource 进行处理,解析时不会用占位符,而是保持原先的 SQL 语句,执行的时候才用参数变量替换,这样容易出现 SQL 注入的问题。