Spring详解
Spring
面试题:IoC和DI的关系是什么?
首先,先回答IoC和DI的是什么:
- IoC: Inversion of Control,控制反转,将Bean的创建权由原来程序反转给第三方
 - DI:Dependency Injection,依赖注入,某个Bean的完整创建依赖于其他Bean(或普通参数)的注入
 
其次,在回答IoC和DI的关系:
- 第一种观点:IoC强调的是Bean创建权的反转,而DI强调的是Bean的依赖关系
 - 第二种观点:IoC强调的是Bean创建权的反转,而DI强调的是通过注入的方式反转Bean的创建权,认为DI是IoC的其中一种实现方式
 
感受下下面两种创建工厂的区别
//DefaultListableBeanFactory是BeanFactory的一个具体实现  | 
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");  | 
面试题:BeanFactory与ApplicationContext的关系
1)BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为Spring 容器;
2)ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装;
3)Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。
4)Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时(懒加载,用到哪个加载哪个)才进行Bean的创建,而ApplicationContext则是在容器一创建(也就是在你启动你的项目的时候就会创建了)就将Bean都实例化并初始化好。
总的来说就是这个关系: 
 ApplicationContext是对BeanFactory的进一步封装,并且提供了更多的功能
ApplicationContext文件结构: 
如果闻到bean标签的scope属性的回答: 当只是一个简单的spring项目时,只有singleton和prototype两种,一种是单例,用到的事件直接去singletonObjects(单例池)中去拿就可以了,另一种是每次创建都会创建一个新对象
但是当在web-MVC环境下,就会多出两个: 
 知道这个就行了
bean标签配置延迟加载: 
 这个在使用ApplicationContext的情况下可以用。但是我们知道,如果使用BeanFactory(工厂)创建bean,都是延迟加载,也就是说如果使用BeanFactory,配置这个就没用了!!!!
Spring的实例化
Spring的实例化方式主要如下两种:
构造方式实例化:底层通过构造方法对Bean进行实例化 (一般默认我们都是进行无参创建….所以这个没啥奇怪的,也没啥细说的)
工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化
工厂实例化
工厂实例化和@Bean注解的性质一样,可以创建不是我们写的代码的bean
工厂方式实例化Bean,又分为如下三种:
⚫ 静态工厂方法实例化Bean
⚫ 实例工厂方法实例化Bean
⚫ 实现FactoryBean规范延迟实例化Bean静态工厂实例化
<bean id="myUser" class="com.haohao.factory.MyFactory" factory-method="createUserByFactory">
<!-- 如果这个方法需要传入参数,这里可以用这个标签传递参数-->
<!-- <constructor-arg name="" value=""/>-->
</bean>package com.haohao.factory;
import com.haohao.Entity.User;
//静态工厂实例化
public class MyFactory {
 //注意这里的static,这里就是和实例工厂的区别
 public static User createUserByFactory(){
 //这里还可以创建一些非我们自己写的Bean
 return new User();
 }
}
```java  | 
实例工厂实例化
<!-- 先创建myFactory对象-->  | 
package com.haohao.factory;  | 
public void test3(){  | 
FactoryBean
<!-- 注意,这里配置的是类MyFactory,但是获取对象的却是user对象-->  | 
package com.haohao.factory;  | 
public void test3(){  | 
spring的标签
Spring 的 xml 标签大体上分为两类,一种是默认标签,一种是自定义标签
⚫ 默认标签:就是不用额外导入其他命名空间约束的标签,例如 标签
⚫ 自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如 context:property-placeholder/ 标签
默认标签

自定义标签

Spring的Bean实例实例化的基本流程
先来看两个关键的类 
 
 
 ** Bean 实例化的基本流程 :**
Spring容器在进行初始化时,会将xml配置的的信息封装成一个个BeanDefinition(一个bean标签对应一个BeanDefinition对象)对象,所有的 BeanDefinition存储到一个名为beanDefinitionMap的Map集合中去。在容器初始化阶段,Spring框架对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从singletonObjects中取出Bean实例对象返回
在容器的初始化阶段,Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,通过反射构造方法或调用指定的工厂方法 生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对 应的Bean的实例化操作(也就是说我们可以进行手动添加!)
之后:
 beanDefinitionMap中的单例的Bean实例的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去
总结流程
Bean 实例化的基本流程!!!
⚫ 加载xml配置文件,解析获取配置中的每个(标签)的信息,封装成一个个的BeanDefinition对象;
⚫ 将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
⚫ ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
⚫ 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;
⚫ 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。 
spring的后置处理器
- Spring的后处理器
Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册(也就是把创建好的BeanDefinition放到BeanDefinitionMap中去)BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:
⚫ BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕(也就是说这个玩意他只会执行一次,只在BeanDefinitionMap填充完毕之后执行),Bean实例化之前执行;
⚫ BeanPostProcessor:Bean后处理器,一般在Bean实例化之后(也就是说这个玩意会执行多次,每一个bean进行实例化后都会执行),填充到单例池singletonObjects之前执行。 
BeanFactoryPostProcessor
package com.haohao.postProcessor;  | 
  | 
  | 
除此之外: Spring 还提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册 BeanDefinition操作
图示总结: 
BeanPostProcessor
Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的 Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被 Spring自动调用。
BeanPostProcessor的接口定义如下:
package org.springframework.beans.factory.config;  | 
下面开始演示:
自定义MyBeanPostProcessor
public class MyBeanPostProcessor implements BeanPostProcessor {  | 
在核心配置文件中配置
<bean class="com.itheima.processors.MyBeanPostProcessor"></bean>  | 
案例 :对Bean方法进行执行时间日志增强
要求如下:
⚫ Bean的方法执行之前控制台打印当前时间;
⚫ Bean的方法执行之后控制台打印当前时间。
分析:
⚫ 对方法进行增强主要就是代理设计模式和包装设计模式;
⚫ 由于Bean方法不确定,所以使用动态代理在运行期间执行增强操作;
⚫ 在Bean实例创建完毕后,进入到单例池之前,使用Proxy代替真是的目标Bean
代码实现:
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  | 
之后把这个类配置到核心配置类中
可以进行验证了,创建的每一个bean调用的每一个方法都会打印运行时间 
Spring的Bean的生命周期
Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
⚫ Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
⚫ Bean的初始化阶段:Bean创建之后还仅仅是个”半成品”,还需要对Bean实例的属性进行填充(这里和suns讲的不一样)、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的;
⚫ Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
初始化阶段
由于Bean的初始化阶段的步骤比较复杂,所以着重研究Bean的初始化阶段
Spring Bean的初始化过程涉及如下几个过程:
⚫ Bean实例的属性填充
⚫ Aware接口属性注入
⚫ BeanPostProcessor的before()方法回调
⚫ InitializingBean接口的初始化方法回调
⚫ 自定义初始化方法init回调
⚫ BeanPostProcessor的after()方法回调
Bean实例属性填充
BeanDefinition 中有对当前Bean实体的注入信息通过属性propertyValues进行了存储,
例如UserService的属性信息如下:
 我们可以发现,对userservice对象进行了属性填充:
但是我们不仅仅可以填充userDao对象
还可以填充其他对象,这就出现了一些问题:
Spring在进行属性注入时,会分为如下几种情况:
⚫ 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
⚫ 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;
⚫ 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题,下面会详细阐述解决方案。
循环引用问题
多个实体之间相互依赖并形成闭环的情况就叫做”循环依赖”,也叫做”循环引用”
public class UserServiceImpl implements UserService{  | 
这种情况很少出现,但是也有可能出现,有可能的话,spring这个框架就得考虑到这种情况。。。。
画图来描述这种情况: 
 所以为了解决这个问题,出现了三级缓存的概念,其实就是三个map,然后存储不同时刻的beanMap集合
Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:
public class DefaultSingletonBeanRegistry ... {  | 
注意上面有一个Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16); 也就是第三级缓存,这里的value是ObjectFactory 
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下
- ⚫ UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
 - ⚫ UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
 - ⚫ UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
 - ⚫ UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
 - ⚫ UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
 - ⚫ UserService 注入UserDao;
 - ⚫ UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
 
Aware接口属性注入
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了 ,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。
常用的Aware接口 
Spring IoC容器生命周期过程总结

Spring整合其他框架的方式
Spring xml方式整合第三方框架
xml整合第三方框架有两种整合方案:
⚫ 不需要自定义名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如:MyBatis;
⚫ 需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容,例如:Dubbo。
不需要自定义命名空间
MyBatis整合Spring
Spring整合MyBatis的步骤如下:
⚫ 导入MyBatis整合Spring的相关坐标;
⚫ 编写Mapper和Mapper.xml;
⚫ 配置SqlSessionFactoryBean和MapperScannerConfigurer;
⚫ 编写测试代码
<!--配置数据源-->  | 
public interface UserMapper {  | 
  | 
ClassPathxmlApplicationContext applicationContext =new ClassPathxmlApplicationContext("applicationContext.xml");  | 
Spring整合MyBatis的原理剖析
整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象,SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
⚫ SqlSessionFactoryBean:需要进行配置,用于提供SqlSessionFactory;
⚫ MapperScannerConfigurer:需要进行配置,用于扫描指定mapper注册BeanDefinition;
⚫ MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法;
⚫ ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。
SqlSessionFactoryBean
配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean实现了 FactoryBean和InitializingBean两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法
SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean{  | 
MapperScannerConfigurer
配置MapperScannerConfigurer作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean, MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor和InitializingBean两个接口,会在 postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean
class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean{  | 
ClassPathMapperScanner
class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {  | 
ClassPathBeanDefinitionScanner
class ClassPathBeanDefinitionScanner{  | 
MapperFactoryBean
UserMapper userMapper = applicationContext.getBean(UserMapper.class);  | 
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {  | 
需要引入第三方框架命名空间
需求:加载外部properties文件,将键值对存储在Spring容器中
 其实,加载的properties文件中的属性最终通过Spring解析后会被存储到了Spring容器的environment中去,不仅自己定义的属性会进行存储,Spring也会把环境相关的一些属性进行存储
自定义命名空间标签
下面这个部分是讲的是自定义标签(也就是有命名空间头信息的标签!虽然也没咋用过)
首先了解这个:
spring.schemas 
<!--  | 
 下面这个handler的意思是如果你写了mvc这个命名空间,就会被这个handler给处理 
 看一下这个handler,这个handler的功能就是:根据你使用的不同标签,创建不同的解析器,我们上面就用到的是annotation-driven标签 
 看一下这个解析器具体干啥了 
 总结来说就是通过handler->parser->beanDefination
ok,下面我们来自己实现一个自定义标签。 <haohao:user id="" name="" password=""/> 文件结构: 
http\://www.haohao.com/schema/user=com.haohao.handler.UserHandler  | 
http\://www.haohao.com/schema/user.xsd=META-INF/user.xsd  | 
public class UserHandler extends NamespaceHandlerSupport {  | 
public class MyUserParser extends AbstractSingleBeanDefinitionParser {  | 
  | 
  | 
  | 
其中有很多对应关系,我真的弄了好久好久
总结一下步骤:Spring xml方式整合第三方框架
步骤分析:
- 确定命名空间名称、schema虚拟路径、标签名称;
 - 编写schema约束文件haohao-annotation.xsd
 - 在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
 - 编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser
 - 编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注册HaohaoBeanPostProcessor
 - 编写HaohaoBeanPostProcessor
==========以上五步是框架开发者写的,以下是框架使用者写的=========== - 在applicationContext.xml配置文件中引入命名空间
 - 在applicationContext.xml配置文件中使用自定义的标签
 
总结
通过上述分析,我们清楚的了解了外部命名空间标签的执行流程,如下:
⚫ 将自定义标签的约束 与 物理约束文件与网络约束名称的约束 以键值对形式存储到一个spring.schemas文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;
⚫ 将自定义命名空间的名称 与 自定义命名空间的处理器映射关系 以键值对形式存在到一个叫spring.handlers文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;
⚫ 准备好NamespaceHandler,如果命名空间只有一个标签,那么直接在parse方法中进行解析即可,一般解析结果就是注册该标签对应的BeanDefinition。如果命名空间里有多个标签,那么可以在init方法中为每个标签都注册一个BeanDefinitionParser,在执行NamespaceHandler的parse方法时在分流给不同的BeanDefinitionParser进行解析(重写doParse方法即可)
基于注解的Spring应用
bean标签属性的注解
 
 具体使用:
  | 
@Primary注解
@Primary注解用于标注相同类型的Bean优先被使用权,@Primary 是Spring3.0引入的,与@Component 和@Bean一起使用,标注该Bean的优先级更高,则在通过类型获取Bean或通过@Autowired根据类型进行注入时, 会选用优先级更高的
  | 
  | 
@Profile 注解
@Profile 注解的作用同于xml配置时学习profile属性,是进行环境切换使用的
<beans profile="test">  | 
注解 @Profile 标注在类或方法上,标注当前产生的Bean从属于哪个环境,只有激活了当前环境,被标注的Bean才 能被注册到Spring容器里,不指定环境的Bean,任何环境下都能注册到Spring容器里
  | 
可以使用以下两种方式指定被激活的环境:
⚫ 使用命令行动态参数,虚拟机参数位置加载 -Dspring.profiles.active=test
⚫ 使用代码的方式设置环境变量 System.setProperty(“spring.profiles.active”,”test”);
Spring注解的解析原理
使用@Component等注解配置完毕后,要配置组件扫描才能使注解生效
⚫ xml配置组件扫描:
<context:component-scan base-package="com.itheima"/>  | 
⚫ 配置类配置组件扫描:
  | 
xml配置组件扫描
使用xml方式配置组件扫描,而component-scan是一个context命名空间下的自定义标签,所以要找到对应的命名空间处理器NamespaceHandler 和 解析器,查看spring-context包下的spring.handlers文件
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler  | 
可以看见,这个处理器为ContextNamespaceHandler ,所以我们查看 ContextNamespaceHandler 类
public void init() {  | 
将ComponentScanBeanDefinitionParser进行了注册,对其源码进行跟踪,最终将标注的@Component的类,生成对应的BeanDefiition进行了注册
使用配置类配置组件扫描
使用配置类配置组件扫描,使用AnnotationConfigApplicationContext容器在进行创建时,内部调用了如下代码, 该工具注册了几个Bean后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);  | 
 其中,ConfigurationClassPostProcessor 是 一个 BeanDefinitionRegistryPostProcessor(这个就是之前我们见过的可以往beanDefinitionMap中注册bean的类。。,这个ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor的子类) ,经过一系列源码调用,最终也别指定到了 ClassPathBeanDefinitionScanner 的 doScan 方法(与xml方式最终终点一致)
两者对比

Spring注解方式整合第三方框架
第三方框架整合,依然使用MyBatis作为整合对象,之前我们已经使用xml方式整合了MyBatis,现在使用注解方式无非就是将xml标签替换为注解,将xml配置文件替换为配置类而已,原有xml方式整合配置如下:
<!--配置数据源-->  | 
使用@Bean将DataSource和SqlSessionFactoryBean存储到Spring容器中,而MapperScannerConfigurer使用注 解@MapperScan进行指明需要扫描的Mapper在哪个包下,使用注解整合MyBatis配置方式如下:
  | 
注解方式,Spring整合MyBatis的原理,关键在于@MapperScan,@MapperScan不是Spring提供的注解,是 MyBatis为了整合Spring,在整合包org.mybatis.spring.annotation中提供的注解,源码如下:
  | 
重点关注一下@Import({MapperScannerRegistrar.class}),当@MapperScan被扫描加载时,会解析@Import注 解,从而加载指定的类,此处就是加载了MapperScannerRegistrar
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,Spring会自动调用 registerBeanDefinitions方法,该方法中又注册MapperScannerConfigurer类,而MapperScannerConfigurer类 作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean,前面讲过,此处不在赘述了:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar,  | 
Spring与MyBatis注解方式整合有个重要的技术点就是@Import,第三方框架与Spring整合xml方式很多是凭借自定义标签完成的,而第三方框架与Spring整合注解方式很多是靠@Import注解完成的。
@Import可以导入如下三种类:
⚫ 普通的配置类
⚫ 实现ImportSelector接口的类
⚫ 实现ImportBeanDefinitionRegistrar接口的类
@Import导入实现了ImportSelector接口的类
  | 
public class MyImportSelector implements ImportSelector {  | 
ImportSelector接口selectImports方法的参数AnnotationMetadata代表注解的媒体数据,可以获得当前注解修饰 的类的元信息,例如:获得组件扫描的包名
public class MyImportSelector implements ImportSelector {  | 
@Import导入实现ImportBeanDefinitionRegistrar接口的类,实现了该接口的类的registerBeanDefinitions方法 会被自动调用,在该方法内可以注册BeanDefinition
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {  | 
SpringAOP
其实在之前学习BeanPostProcessor时,在BeanPostProcessor的after方法中使用动态代理对Bean进行了增强,实际存储到单例池singleObjects中的不是当前目标对象本身,而是当前目标对象的代理对象Proxy,这样在调用目标对象方法时,实际调用的是代理对象Proxy的同名方法,起到了目标方法前后都进行增强的功能。
- aspectj静态代理
- 原理:在编译期织入代码,编译成class文件
 - 优点:可以增强任何类,任何方法,包括(final,static修饰)
 - 缺点:需要使用aspecj提供的Ajc编译器来编译Aj文件
 
 - jdk动态代理
- 使用jdk的动态代理来增强接口实现类
 - 原理:使用Proxy类的newProxyInstance方法运行期通过反射动态的生成代理对象
 - 优点:不需要修改具体的业务代码,动态的增强方法,降低耦合性。
 - 缺点:代理的对象必须有接口实现。
 
 - cglib的动态代理
- 可以对jdk的动态代理作为补充
 - 原理:以创建目标类的子类来生成动态代理对象。
 - 优点:不需要修改具体的业务代码,动态的增强方法,降低耦合性。
 - 缺点:不能对final修饰的类,final修饰的方法或static的方法进行代理
 
 
AOP(面向切面编程)是一种编程范式,它可以让开发者在不修改原有代码的情况下,通过定义切面和切点来增强程序的功能。例如,可以在方法执行前、执行后或异常时插入切面逻辑,实现日志记录、性能监控、事务管理等功能。
在Java中,AOP框架可以分为基于代理的AOP和基于字节码生成的AOP两种实现方式。
Spring AOP是基于代理的AOP框架,它可以通过Java动态代理或CGLIB代理来实现对目标类方法的代理。Java动态代理只能代理实现接口的类,而CGLIB代理可以代理任意的类。Spring AOP默认使用Java动态代理来实现AOP,但当目标类没有实现接口时,它会自动切换到使用CGLIB代理。Spring AOP的切面和切点可以通过注解或XML配置文件来定义。例如,可以使用@Aspect注解来定义一个切面类,并使用@Pointcut注解来定义一个切点。在定义完切面和切点后,可以使用@Before、@After、@Around等注解来定义切面逻辑,并将切面类注册到Spring容器中。
AspectJ是一个独立的AOP框架,它提供了比Spring AOP更强大和灵活的AOP能力。AspectJ支持基于编译器、类加载器和运行时三种不同的AOP实现方式。其中,基于编译器和类加载器的实现方式需要在编译时或加载时对Java字节码进行增强,而基于运行时的实现方式则可以动态地对Java类进行增强。与Spring AOP不同,AspectJ使用注解或AspectJ语言(AJ)来定义切面和切点。例如,可以使用@Aspect注解来定义一个切面类,并使用@Pointcut注解来定义一个切点。在定义完切面和切点后,可以使用@Before、@After、@Around等注解或AJ语言来定义切面逻辑。
CGLIB是一个Java字节码生成库,它可以在运行时动态生成Java类,并在内存中加载。CGLIB动态代理是基于字节码生成的动态代理技术,它可以代理任意的类,包括没有实现接口的类。CGLIB动态代理通过生成目标类的子类来创建代理对象,代理类中的方法会调用MethodInterceptor接口的intercept方法,并在其中实现切面逻辑。
JDK动态代理是Java原生支持的动态代理技术,它是通过反射和接口实现的。JDK动态代理要求目标类必须是实现了接口的类,它会在运行时创建一个实现了代理接口的代理类,代理类中的方法会调用InvocationHandler接口的invoke方法,并在其中实现切面逻辑。
需要注意的是,由于CGLIB动态代理是基于字节码生成的技术,它需要在运行时生成代理类的字节码,并在内存中加载。因此,CGLIB动态代理可能会比JDK动态代理更耗时和占用更多的内存。同时,CGLIB动态代理也无法代理final方法和final类。
总之,Spring AOP、AspectJ和CGLIB都是Java中的AOP框架,它们可以用来实现切面编程。Spring AOP是Spring框架内置的AOP框架,基于代理实现;AspectJ是一个独立的AOP框架,提供更强大和灵活的AOP能力;CGLIB是一个Java字节码生成库,用于生成代理类。
基于JDK动态代理实现AOP的原始案例
现在给一个需求:用JDK动态代理的方式,使用BeanPostProcessor实现对com.itheima.service.impl包下的所有类的所有方法实现AOP代理
public class MyAdvice {  | 
public class UserServiceImpl implements UserService {  | 
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {  | 
需要注意的是,这三个类都需要注册到spring容器中才能完成这些操作
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");  | 
基于xml配置的AOP
第一种方式:aspect
public interface UserService {  | 
public class MyAdvice {  | 
<!--配置目标类,内部的方法是连接点-->  | 
通知方法的参数
通知方法(也就是上面的beforeAdvice、afterAdvice…等方法)在被调用时,Spring可以为其传递一些必要的参数
JoinPoint 对象
public void 通知方法名称(JoinPoint joinPoint){  | 
ProceedingJoinPoint对象
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {  | 
Throwable对象
public void afterThrowing(JoinPoint joinPoint,Throwable th){  | 
必须配置异常通知
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="th"/>  | 
第二种方式:advice
先看一下目录结构 
MethodBeforeAdvice和AfterReturningAdvice
public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice {  | 
<!--配置目标类-->  | 
MethodInterceptor
这个就相当于around
public class MyAdvice3 implements MethodInterceptor {  | 
<!--配置目标类-->  | 
二者区别
1)配置语法不同:
<!-- 使用advisor配置 -->  | 
2)通知类的定义要求不同:
advisor 需要的通知类需要实现Advice的子功能接口:
public class Advices implements MethodBeforeAdvice {  | 
aspect 不需要通知类实现任何接口,在配置的时候指定哪些方法属于哪种通知类型即可,更加灵活方便:
// 不需要实现接口  | 
3)可配置的切面数量不同:
⚫ 一个advisor只能配置一个固定通知和一个切点表达式;
⚫ 一个aspect可以配置多个通知和多个切点表达式任意组合,粒度更细。
也就是说advisor只能添加一个增强方法,aspect可以配置多个增强方法
4)使用场景不同:
⚫ 如果通知类型多、允许随意搭配情况下可以使用aspect进行配置;
⚫ 如果通知类型单一、且通知类中通知方法一次性都会使用到的情况下可以使用advisor进行配置;
⚫ 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如后面要学习的Spring事务控制的配置;
由于实际开发中,自定义aop功能的配置大多使用aspect的配置方式
xml方式AOP原理剖析
通过xml方式配置AOP时,我们引入了AOP的命名空间,根据讲解的,要去找spring-aop包下的META-INF,在去 找spring.handlers文件
 文件内容:
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler  | 
最终加载的是 AopNamespaceHandler,该Handler的init方法中注册了config标签对应的解析器
this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());  | 
以ConfigBeanDefinitionParser作为入口进行源码剖析,最终会注册一个AspectJAwareAdvisorAutoProxyCreator 进入到Spring容器中
看一下集成体系图
 AspectJAwareAdvisorAutoProxyCreator 的上上级父类AbstractAutoProxyCreator中的 postProcessAfterInitialization(after后置bean处理器)方法
//参数bean:为目标对象  | 
通过断点方式观察,当bean是匹配切点表达式时,this.wrapIfNecessary(bean, beanName, cacheKey)返回的是 一个**JDKDynamicAopProxy ** 
 对wrapIfNecessary在剖析一下,看看是不是我们熟知的通过JDK的 Proxy.newProxyInstance(ClassLoader loader, Class[]interfaces,InvocationHandler h) 的方式创建的代理对象呢?经过如下一系列源码跟踪
==> this.wrapIfNecessary(bean, beanName, cacheKey)  | 
 动态代理的实现的选择,在调用getProxy() 方法时,我们可选用的 AopProxy接口有两个实现类,如上图,这两种 都是动态生成代理对象的方式,一种就是基于JDK的,一种是基于Cglib的
 用图来表示这种关系: 
Cglib基于超类的动态代理
Target target = new Target();//目标对象  | 
基于注解配置的AOP
Spring的AOP也提供了注解方式配置,使用相应的注解替代之前的xml配置,xml配置AOP时,我们主要配置了三 部分:目标类被Spring容器管理、通知类被Spring管理、通知与切点的织入(切面),如下:
<!--配置目标-->  | 
用注解表示
目标类被Spring容器管理、通知类被Spring管理
public class TargetImpl implements Target{
public void show() {
System.out.println("show Target running...");
}
}
public class AnnoAdvice {
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知...");
joinPoint.proceed();
System.out.println("环绕后通知...");
}
}
2. 配置切面(织入)  | 
注解方式:
  | 
注解方式AOP原理剖析
之前在使用xml配置AOP时,是借助的Spring的外部命名空间的加载方式完成的,使用注解配置后,就抛弃了 标签,而该标签最终加载了名为AspectJAwareAdvisorAutoProxyCreator的BeanPostProcessor , 最终,在该BeanPostProcessor中完成了代理对象的生成。
同样,在注解配置AOP时,从aspectj-autoproxy标签的解析器入手
this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());  | 
而AspectJAutoProxyBeanDefinitionParser代码内部,最终也是执行了和xml方式AOP一样的代码
registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source)  | 
如果使用的是核心配置类的话
  | 
查看@EnableAspectJAutoProxy源码,使用的也是@Import导入相关解析类
  | 
使用@Import导入的AspectJAutoProxyRegistrar源码,一路追踪下去,最终还是注册了 AnnotationAwareAspectJAutoProxyCreator 这个类 ,其实还是那个beanPostProcessor,完成创建代理对象的环节
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {  | 

Spring的基于AOP的声明式事务控制
事务是开发中必不可少的东西,
使用JDBC开发时,我们使用connnection对事务进行控制;
使用MyBatis时,我们使用SqlSession对事务进行控制;
缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化, Spring 就将这些技术基础上,提供了统一的控制事务的接口。
Spring的事务分为:编程式事务控制 和 声明式事务控制
Spring事务编程相关的类主要有如下三个
 编程式事务控制对应的这些类我们需要了解一下,因为我们在通过配置的方式进行声明式事务控制时也会看到这些类的影子
基于xml声明式事务控制
可以使用AOP对Service的方法进行事务的增强。
⚫ 目标类:AccountServiceImpl
⚫ 切点:service业务类中的所有业务方法
⚫ 通知类:Spring提供的,通知方法已经定义好,只需要配置即可
配置目标类AccountServiceImpl
<bean id="accountService" class="com.itheima.service.impl.AccoutServiceImpl">
<property name="accountMapper" ref="accountMapper"></property>
</bean>使用advisor标签配置切面
<aop:config>
<aop:advisor advice-ref="Spring提供的通知类" pointcut="execution(* com.itheima.service.impl.*.*(..))"/>
</aop:config>注意,这里还没配置spring提供的通知类!!!下面会配置,这里我们倒着配置
<!--Spring提供的事务通知-->  | 
<aop:config>  | 
这里对上面的配置详解: 首先,平台事务管理器PlatformTransactionManager是Spring提供的封装事务具体操作的规范接口,封装了事务的提交和回滚方法
public interface PlatformTransactionManager extends TransactionManager {  | 
不同的持久层框架事务操作的方式有可能不同,所以不同的持久层框架有可能会有不同的平台事务管理器实现, 例如,MyBatis作为持久层框架时,使用的平台事务管理器实现是DataSourceTransactionManager(也是我们上面示例的管理器)。 Hibernate作为持久层框架时,使用的平台事务管理器HibernateTransactionManager。
其次,事务定义信息配置,每个事务有很多特性,例如:隔离级别、只读状态、超时时间等,这些信息在开发时可以通过connection进行指定,而此处要通过配置文件进行配置
<tx:attributes>  | 
name

方法名在配置时,也可以使用 * 进行模糊匹配,例如:
<tx:advice id="myAdvice" transaction-manager="transactionManager">  | 
isolation
isolation属性:指定事务的隔离级别,事务并发存在三大问题:脏读、不可重复读、幻读/虚读。可以通过设置事务的隔离级别来保证并发问题的出现,常用的是READ_COMMITTED 和 REPEATABLE_READ
read-only
read-only属性:设置当前的只读状态,如果是查询则设置为true,可以提高查询性能,如果是更新(增删改)操作则设置为false
<!-- 一般查询相关的业务操作都会设置为只读模式 -->  | 
timeout
timeout属性:设置事务执行的超时时间,单位是秒,如果超过该时间限制但事务还没有完成,则自动回滚事务 ,不在继续执行。默认值是-1,即没有超时时间限制
<!-- 设置查询操作的超时时间是3秒 -->  | 
propagation
propagation属性:设置事务的传播行为,主要解决是A方法调用B方法时,事务的传播方式问题的,例如:使用 单方的事务,还是A和B都使用自己的事务等。事务的传播行为有如下七种属性值可配置
xml方式声明式事务控制的原理浅析
tx:advice标签使用的命名空间处理器是TxNamespaceHandler,内部注册的是解析器是TxAdviceBeanDefinitionParser
this.registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());  | 
TxAdviceBeanDefinitionParser中指定了要注册的**BeanDefinition **
protected Class<?> getBeanClass(Element element) {  | 
TxAdviceBeanDefinitionParser二级父类AbstractBeanDefinitionParser的parse方法将TransactionInterceptor 以配置的名称注册到了Spring容器中
parserContext.registerComponent(componentDefinition);  | 
TransactionInterceptor(这个玩意就是我们当时学advice里面的环绕通知!!!)中的invoke方法会被执行,跟踪invoke方法,最终会看到事务的开启和提交
⚫ 在AbstractPlatformTransactionManager的132行中开启的事务;
⚫ 在TransactionAspectSupport的242行提交了事务
基于注解声明式事务控制
  | 
  | 
SpringMVC
SpringMVC是一个基于Spring开发的MVC轻量级框架,Spring3.0后发布的组件,SpringMVC和Spring可以无缝整合,使用DispatcherServlet作为前端控制器,且内部提供了处理器映射器、处理器适配器、视图解析器等组件,可以简化JavaBean封装,Json转化、文件上传等操作。
附:开发需要注意的点:
最好在开发的时候封装参数用包装类,也就是不要用int用integer,要不然可能会出错
用集合接收参数的时候必须加上@RequestParam注解,要不然也会报错的
三个核心组件
 用图片来表示 
SpringMVC关键组件浅析
SpringMVC的默认组件,SpringMVC 在前端控制器 DispatcherServlet加载时,就会进行初始化操作,在进行初始化时,就会加载SpringMVC默认指定的一些组件,这些默认组件配置在 DispatcherServlet.properties 文件中,该文 件存在与spring-webmvc-5.3.7.jar包下的org\springframework\web\servlet\DispatcherServlet.properties
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\  | 
这些默认的组件是在DispatcherServlet中进行初始化加载的,在DispatcherServlet中存在集合存储着这些组件, SpringMVC的默认组件会在 DispatcherServlet 中进行维护,但是并没有存储在与SpringMVC的容器中
public class DispatcherServlet extends FrameworkServlet {  | 
配置组件代替默认组件,如果不想使用默认组件,可以将替代方案使用Spring Bean的方式进行配置,例如,在 spring-mvc.xml中配置RequestMappingHandlerMapping (只是有这个功能!!这个后面会用到!!!理解这个就行)
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>  | 
当我们在Spring容器中配置了HandlerMapping,则就不会在加载默认的HandlerMapping策略了,原理比较简单, DispatcherServlet 在进行HandlerMapping初始化时,先从SpringMVC容器中找是否存在HandlerMapping,如果存在直接取出容器中的HandlerMapping,在存储到 DispatcherServlet 中的handlerMappings集合中去。
请求静态资源
静态资源请求失效的原因,当DispatcherServlet的映射路径配置为 / 的时候,那么就覆盖的Tomcat容器默认的缺省 Servlet,在Tomcat的config目录下有一个web.xml 是对所有的web项目的全局配置,其中有如下配置:
<servlet>  | 
url-pattern配置为 / 的Servlet我们称其为缺省的Servlet,作用是当其他Servlet都匹配不成功时,就找缺省的Servlet ,静态资源由于没有匹配成功的Servlet,所以会找缺省的DefaultServlet,该DefaultServlet具备二次去匹配静态资 源的功能。但是我们配置DispatcherServlet后就将其覆盖掉了,而DispatcherServlet会将请求的静态资源的名称当 成Controller的映射路径去匹配,即静态资源访问不成功了!
静态资源请求的三种解决方案:
第一种方案,可以再次激活Tomcat的DefaultServlet,Servlet的url-pattern的匹配优先级是:精确匹配>目录匹配>扩展名匹配>缺省匹配,所以可以指定某个目录下或某个扩展名的资源使用DefaultServlet进行解析:
<servlet-mapping>  | 
第二种方式,在spring-mvc.xml中去配置静态资源映射,匹配映射路径的请求到指定的位置去匹配资源
<!-- mapping是映射资源路径,location是对应资源所在的位置 -->  | 
第三种方式,在spring-mvc.xml中去配置< mvc:default-servlet-handler >,该方式是注册了一个 DefaultServletHttpRequestHandler 处理器,静态资源的访问都由该处理器去处理,这也是开发中使用最多的
<mvc:default-servlet-handler/>  | 
出现的问题
静态资源配置的第二第三种方式我们可以正常访问静态资源了,但是Controller又无法访问了,报错404,即找不到对应的资源
第二种方式是通过SpringMVC去解析mvc命名空间下的resources标签完成的静态资源解析,第三种方式式通过 SpringMVC去解析mvc命名空间下的default-servlet-handler标签完成的静态资源解析,根据前面所学习的自定义命名空间的解析的知识,可以发现不管是以上哪种方式,最终都会注册SimpleUrlHandlerMapping
public BeanDefinition parse(Element element, ParserContext context) {  | 
又结合组件浅析知识点,一旦SpringMVC容器中存在 HandlerMapping 类型的组件时,前端控制器 DispatcherServlet在进行初始化时,就会从容器中获得HandlerMapping(SimpleUrlHandlerMapping就是HandlerMapping) ,不在加载 dispatcherServlet.properties 中默认处理器映射器策略,那也就意味着RequestMappingHandlerMapping(这个是处理Controller请求的,SimpleUrlHandlerMapping不可以处理Controller请求)不会被加载到了。
所以我们手动将RequestMappingHandlerMapping也注册到SpringMVC容器中就可以了
注解驱动mvc:annotation-driven标签
<!-- 显示配置RequestMappingHandlerMapping -->  | 
可以简化成如下配置
<!--该标签内部会帮我们注册RequestMappingHandlerMapping、注册RequestMappingHandlerAdapter并注入Json消息转换器等-->  | 
SpringMVC的拦截器
 
拦截器 Interceptor 简介
实现了HandlerInterceptor接口,且被Spring管理的Bean都是拦截器,接口定义如下:
public interface HandlerInterceptor {  | 

拦截器的执行顺序
** 拦截器执行顺序取决于 interceptor 的配置顺序 **
比如我们先配置1,再配置2,再配置3: 
 
拦截器执行原理
请求到来时先会使用组件HandlerMapping去匹配Controller的方法(Handler)和符合拦截路径的Interceptor,** Handler和多个Interceptor**被封装成一个HandlerExecutionChain的对象
HandlerExecutionChain 定义如下:
public class HandlerExecutionChain {  | 
 在DispatcherServlet的doDispatch方法中执行拦截器
protected void doDispatch(HttpServletRequest request, HttpServletResponse response){  | 
前端控制器原理剖析
前端控制器初始化
前端控制器DispatcherServlet是SpringMVC的入口,也是SpringMVC的大脑,主流程的工作都是在此完成的,梳理一下DispatcherServlet 代码。DispatcherServlet 本质是个Servlet,当配置了 load-on-startup 时,会在服务器启动时就执行创建和执行初始化init方法,每次请求都会执行service方法
DispatcherServlet 的初始化主要做了两件事:
⚫ 获得了一个 SpringMVC 的 ApplicationContext容器;
⚫ 注册了 SpringMVC 的九大组件 
 SpringMVC 的ApplicationContext容器创建时机,Servlet 规范的 init(ServletConfig config) 方法经过子类重写 ,最终会调用 FrameworkServlet 抽象类的initWebApplicationContext() 方法,该方法中最终获得 一个根 Spring容器(Spring产生的),一个子Spring容器(SpringMVC产生的)
HttpServletBean 的初始化方法
public final void init() throws ServletException {  | 
FrameworkServlet的initServletBean方法
protected final void initServletBean() throws ServletException {  | 
在initWebApplicationContext方法中体现的父子容器的逻辑关系
//初始化ApplicationContext是一个及其关键的代码  | 
跟进创建子容器的源码
protected WebApplicationContext createWebApplicationContext( ApplicationContext parent) {  | 
子容器中的parent维护着父容器的引用
 父容器和子容器概念和关系:
⚫ 父容器:Spring 通过ContextLoaderListener为入口产生的applicationContext容器,内部主要维护的是applicationContext.xml(或相应配置类)配置的Bean信息;
⚫ 子容器:SpringMVC通过DispatcherServlet的init() 方法产生的applicationContext容器,内部主要维护的是spring-mvc.xml(或相应配置类)配置的Bean信息,且内部还通过parent属性维护这父容器的引用。
⚫ Bean的检索顺序:根据上面子父容器的概念,可以知道Controller存在与子容器中,而Controller中要注入Service时,会先从子容器本身去匹配,匹配不成功时在去父容器中去匹配,于是最终从父容器中匹配到的UserService,这样子父容器就可以进行联通了。但是父容器只能从自己容器中进行匹配,不能从子容器中进行匹配
注册 SpringMVC的九大组件,在初始化容器initWebApplicationContext方法中执行了onRefresh方法,进而执行了初始化策略initStrategies方法,注册了九个解析器组件
//DispatcherServlet初始化SpringMVC九大组件  | 
以 this.initHandlerMappings(context) 为例,进一步看一下初始化处理器映射器的细节:
HandlerMapping就是controller上面的路径
//定义List容器存储HandlerMapping  | 

前端控制器执行主流程
上面讲解了一下,当服务器启动时,DispatcherServlet 会执行初始化操作,接下来,每次访问都会执行service 方法,我们先宏观的看一下执行流程,在去研究源码和组件执行细节
 FrameworkServlet 复写了service(HttpServletRequest request, HttpServletResponse response) 、 doGet(HttpServletRequest request, HttpServletResponse response)、doPost(HttpServletRequest request, HttpServletResponse response)等方法,这些方法都会调用processRequest方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response){  | 
进一步调用了doService方法,该方法内部又调用了doDispatch方法,而SpringMVC 主流程最核心的方法就是 doDispatch 方法
protected void doService(HttpServletRequest request, HttpServletResponse response) {  | 
doDispatch方法源码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {  | 



