丙-Spring - 图1

Spring

Spring是一个开源框架,处于MVC模式中的控制层,它能应对需求快速的变化,其主要原因它有一种面向切面编程(AOP)的优势,其次它提升了系统性能,因为通过依赖倒置机制(IOC),系统中用到的对象不是在系统加载时就全部实例化,而是在调用到这个类时才会实例化该类的对象,从而提升了系统性能。

  • 降低了组件之间的耦合性,实现了软件各层之间的解耦。
  • 可以使用容易提供的众多服务,如事务管理,消息服务,日志记录等。
  • 容器提供了AOP技术,利用它很容易实现如权限拦截、运行期监控等功能。

设计模式

  • Spring结合了工厂模式和反射机制实现IOC容器。
  • 单例模式,提供了全局的访问点BeanFactory。
  • Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
  • 观察者模式:listener的实现。如ApplicationListener。
  • 模板方法: xxxApplicationContext 的refush()。
  • 代理:(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
  • 装饰器模式:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。(比如依赖注入就需要使用BeanWrapper)
  • 策略模式:Bean的实例化的时候决定采用何种方式初始化bean实例(反射或者CGLIB动态字节码生成)

策略模式:抽象了出了接口,将业务逻辑封装成一个一个的实现类,任意地替换。利用java8的Map与函数式接口来实现。

Spring Bean的生命周期(createBean中的doCreateBean)

概述 (实例化+属性赋值+初始化+销毁)

进入createBean方法,如果有代理方法(AOP)则调用postProcessBeforeInstantiation,换原本的Bean作为代理。进入doCreateBean方法,调用createBeanInstance创建BeanWrapper实例包装类。(这里判断了Spring的循环依赖问题)
这时进入populateBean方法,调用postProcessAfterInstantiation方法,如果调用返回false,表示不必继续进行依赖注入,直接返回。如果返回true,向 bean 的成员变量注入自定义的信息。最后通过BeanWrapper提供的设置属性的接口完成属性依赖注入(DI)。
下一步进入initializeBean(),首先判断是否实现了Aware接口,有的话就注入相关属性。接着调用初始化的前置(BeanPostProcessor)操作,接着执行初始化的方法,最后调用bean初始化的后置(BeanPostProcessor)操作。
注册Bean的销毁逻辑。当Bean不再需要时,销毁Bean。

详细步骤

InstantiationAwareBeanPostProcessor作用于实例化阶段的前后。

  1. 实例化 createBeanInstance()
    Ioc容器通过获取BeanDefinition对象中的信息进行实例化,实例化对象被包装在BeanWrapper对象中
  • postProcessBeforeInstantiation在doCreateBean之前调用,该方法的返回值会替换原本的Bean作为代理,这也是Aop等功能实现的关键点。
  • postProcessAfterInstantiation该方法在属性赋值方法内,但是在真正执行赋值操作之前。其返回值为boolean,返回false时可以阻断属性赋值阶段

Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法(我的策略模式就是这么干的),或者在配置文件中通过init-method指定,两种方式可以同时使用。

  1. 属性赋值 populateBean()
    通过BeanWrapper提供的设置属性的接口完成属性依赖注入(DI);

BeanPostProcessor作用于初始化阶段的前后,它也会注册为Bean

  1. 初始化 initializeBean()
    注入Aware接口+自定义的处理 (所有的Aware方法都是在初始化阶段之前调用的)
  2. 销毁 容器关闭时调用
  1. // 忽略了无关代码
  2. protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
  3. throws BeanCreationException {
  4. //bean实例包装类
  5. BeanWrapper instanceWrapper = null;
  6. if (instanceWrapper == null) {
  7. //创建bean的时候,这里创建bean的实例有三种方法
  8. //1.工厂方法创建
  9. //2.构造方法的方式注入
  10. //3.无参构造方法注入
  11. instanceWrapper = createBeanInstance(beanName, mbd, args);
  12. }
  13. // Initialize the bean instance.
  14. Object exposedObject = bean;
  15. try {
  16. //填充bean实例的属性
  17. populateBean(beanName, mbd, instanceWrapper);
  18. //初始化bean,过程如下:
  19. //1:判断是否实现了BeanNameAware,BeanClassLoaderAware,
  20. // BeanFactoryAware方法,如果有,则设置相关的属性
  21. //2: 调用bean初始化的前置(BeanPostProcessor)操作
  22. //3: 执行初始化的方法。
  23. // 如果有initializingBean,则调用afterPropertiesSet
  24. // 如果有InitMethod,则调用初始方法
  25. //4: 调用bean初始化的后置(BeanPostProcessor)操作
  26. exposedObject = initializeBean(beanName, exposedObject, mbd);
  27. }
  28. }

丙-Spring - 图2
第一大类:影响多个Bean的接口

  • BeanPostProcessor (SentinelBeanPostProcessor)
  • InstantiationAwareBeanPostProcessor
  1. postProcessBeforeInstantiation在doCreateBean之前调用,也就是在bean实 化之前调用的,该方法的返回值会替换原本的Bean作为代理,这也是Aop等功能实现的关键点
  2. postProcessAfterInstantiation在真正执行赋值操作之前,返回false时可以阻断属性赋值阶段。

执行顺序
BeanPostProcessor有很多个,而且每个BeanPostProcessor都影响多个Bean,其执行顺序至关重要,必须能够控制其执行顺序才行。
1 PriorityOrdered是一等公民,首先被执行,PriorityOrdered公民之间通过接口返回值排序
2 Ordered是二等公民,然后执行,Ordered公民之间通过接口返回值排序
3 都没有实现是三等公民,最后执行

第二大类:只调用一次的接口

  • Aware类型的接口
  • 生命周期接口
  1. Aware类型的接口的作用就是让我们能够拿到Spring容器中的一些资源。Aware都是在初始化阶段之前调用

Aware Group1
1 BeanNameAware
2 BeanClassLoaderAware
3 BeanFactoryAware
Aware Group2
1 EnvironmentAware
2 EmbeddedValueResolverAware 这个知道的人可能不多,实现该接口能够获取Spring EL解析器,用户的自定义注解需要支持spel表达式的时候可以使用,非常方便。
3 ApplicationContextAware

  1. 两个生命周期接口
  • InitializingBean 对应生命周期的初始化阶段。(在初始化方法中放心大胆的使用Aware接口获取的资源,这也是我们自定义扩展Spring的常用方式)
  • DisposableBean 类似于InitializingBean,对应生命周期的销毁阶段,以ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了DisposableBean接口的Bean然后调用其destroy()方法 。

Spring Bean的作用域

作用域 描述
singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。
session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。
application 限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。

丙-Spring - 图3

父子容器

Spring和SpringMVC的容器具有父子关系,Spring容器为父容器,SpringMVC为子容器,子容器可以引用父容器中的Bean,而父容器不可以引用子容器中的Bean。(例子:ssm框架,spring可以管理service,mapper,springmvc管理controller,mybatis编写mapper,controller就需要调用service,service调用mapper,因为springmvc容器是spring的子容器,可以通过父容器找到service和mapper,但是在service中却是找不到controller的。保证一种资源的局部性。)

在配置Ribbon时,它的Configuration就要放在@SpringBootConfiguration扫描不到的地方,防止父子上下文重叠。

Spring事务

Spring支持编程式事务管理以及声明式(基于AOP)事务管理两种方式。

事务的传播性: @Transactional(propagation=Propagation.REQUIRED)

事务的隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读,不可重复读) 基本不使用
只读: (一次执行多条查询语句,也需要回滚)
@Transactional(readOnly=true)
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。

事务的超时性:@Transactional(timeout=30)

回滚:
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class,Exception.class})

事务的隔离级别

和Mysql的一致,但多了个默认DEFAULT(-1)

事务的7种传播机制

如果想事务一起执行可以用Required满足大部分场景,如果不想让执行的子事务的结果影响到父事务的提交可以将子事务设置为RequiresNew。

一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行

  • PROPAGATION_REQUIRED (默认)
    如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
  • PROPAGATION_REQUES_NEW
    该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
  • PROPAGATION_SUPPORT
    如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
  • PROPAGATION_NOT_SUPPORT(只读的时候在设置个ReadOnly)
    该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
  • PROPAGATION_NEVER
    该传播机制不支持外层事务,即如果外层有事务就抛出异常
  • PROPAGATION_MANDATORY
    与NEVER相反,如果外层没有事务,则抛出异常
  • PROPAGATION_NESTED
    该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

@Transactional失效场景

  1. @Transactional 应用在 非 public 修饰的方法上
  2. @Transactional 注解属性 propagation 设置错误
  3. @Transactional 注解属性 rollbackFor 设置错误
  4. 同一个类中方法调用,导致@Transactional失效
    (方法A没有声明注解事务,而B方法有。则外部调用方法A)

Spring AOP是基于代理,同一个类内这样调用的话,只有第一次调用了动态代理生成的ProxyClass,之后调用是不带任何切面信息的方法本身,因为没有直接调用Spring生成的代理对象。

  1. 方法中的异常被自己catch
  2. 数据库引擎不支持事务
  3. spring、springMVC重复扫描,父子上下文重叠
    (实例理应由父容器进行初始化以保证事务的增强处理)

Spring的AOP事务管理默认是针对unchecked exception回滚(RuntimeException的子类)。

IOC容器(避免在各处new来创建类,做到统一维护)

Spring 框架的核心是 Spring 容器。容器创建对象,将它们装配在一起,配置它们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的组件。容器通过读取提供的配置元数据来接收对象进行实例化,配置和组装的指令。该元数据可以通过 XML Java 注解或 Java 代码提供。

(可以理解为,单元测试那种)

  1. 创建注解 (Controller、Service、Repository、Component)
  2. 提取标记对象(URL提取、获取类加载器、获取Class对象)
  3. 依赖注入(Autowired、成员变量实例的注入(解决多个实现类))
  4. 实现容器(单例模式(饿汉懒汉)、创建容器载体、实现容器加载)

    IoC 的实现原理就是工厂模式加反射机制。

解决了关键的问题:将对象之间的关系转而用配置管理

  • 依赖注入—-依赖关系在IOC容器里管理
  • 通过把对象包装在Bean中以达到管理对象和进行格外操作的目的

Spring推荐使用构造器注入

  1. 添加了final修饰说明我们注入的依赖不能再变动。
  2. 保证了构造属性已经完全初始化,避免循环依赖问题。

使用构造器注入

  1. 在类对象相互依赖时,通过setter方式解决循环依赖问题。
  2. 可以将依赖项部分注入。
  3. 不能保证类所有属性都被注入。

接口回调注入-Aware接口

  1. 提供Spring中获取容器本身一些功能资源,通过实现Spring Aware接口实现具体功能。

方法注入-@Autowired @Bean @Resource

可以通过多少种方式完成依赖注入?

通常,依赖注入可以通过三种方式完成,即:

  1. 构造函数注入(Spring推荐使用)

将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。
优点:
对象初始化完成后便可获得可使用的对象。
添加了final修饰说明我们注入的依赖不能再变动。
保证了构造属性已经完全初始化,避免循环依赖问题。
缺点:
当需要注入的对象很多时,构造器参数列表将会很长;不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,麻烦。

  1. setter 注入

IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。
优点:
灵活。可以选择性地注入需要的对象。
在类对象相互依赖时,通过setter方式解决循环依赖问题。
可以将依赖项部分注入。
缺点:
依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。
不能保证类所有属性都被注入。

  1. 接口注入-Aware接口

提供Spring中获取容器本身一些功能资源,通过实现Spring Aware接口实现具体功能。
依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象
优点
接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。
缺点:
侵入行太强,不建议使用。

  1. 方法注入-@Autowired @Bean @Resource

在 Spring Framework 中,仅使用构造函数setter 注入

构造函数注入 setter 注入
没有部分注入 有部分注入
不会覆盖 setter 属性 会覆盖 setter 属性
任意修改都会创建一个新实例 任意修改不会创建一个新实例
适用于设置很多属性 适用于设置少量属性

哪种依赖注入方式你建议使用

最好的解决方案是用构造器参数实现强制依赖,setter 方法实现可选依赖

循环依赖情况(- 不支持prototype 因为bean实例不唯一)

  • 构造器循环依赖(singleton prototype-通过@Scope()指定 ) 都不支持 (因为你一上来就要属性注入)
  • Setter注入循环依赖(singleton prototype) - 只支持singleton Setter注入

    scope=singleton(默认,单例,生成一个实例) 不是线程安全,性能高
    scope=prototype(原型多线程, 生成多个实例)

循环依赖问题 ——》setter方式如果解决循环依赖?(A-B-A)

三层缓存 + 提前暴露对象的方式(半成品)。

  1. 在实例化A对象之后就向容器中添加一个三级缓存,存放一个实例化但未初始化完成的对象(半成品)。
  2. A开始初始化属性赋值时,先完成B的实例化。
  3. B中初始化属性赋值,由于已经有A对象,通过A的属性赋值。(删除A的三级缓存,存入获取到的值到二级缓存)
  4. B完成初始化属性赋值(调用addSingleton方法放入一级缓存,删除二级三级缓存的值),初始化A对象。
    一级:完整的成品对象
    二级:非完整的半成品对象
    三级:lambada表达式 【废弃三级缓存也可以解决循环依赖,但配置AOP切面时会出错,无法生成代理对象。三级缓存是为了处理AOP中的循环依赖,在配置切面后,有可能在getEarlyBeanReference中把原始对象替换成代理对象,导致Bean版本不是最终的版本。】

在实例化A对象之后就向容器中添加一个三级缓存,存放一个实例化但未初始化完成的对象(半成品)。
A开始初始化属性赋值时,先完成B的实例化。
B中初始化属性赋值,由于已经有A对象,通过A的属性赋值。(删除A的三级缓存,存入获取到的值到二级缓存)
B完成初始化属性赋值(调用addSingleton方法放入一级缓存,删除二级三级缓存的值),初始化A对象。

  1. 初次创建A(getBean())时,调用addsingletonFactory将A实例对应的ObjectFactoy放到三级缓存中,调用populateBean赋值属性B;(addsingletonFactory属性没被注入,populateBean方法是用来赋值的
  2. 因为A依赖B,调用getBean获取B。将B的ObjectFactoy放到三级缓存,调用populateBean赋值B,此时因为B依赖A,此时进入getSingleton()方法, 判断到三级缓存singletonFactories中存在A(单例工厂实例),就调用单例工厂的getObject方法返回对象实例;将实例A放入二级缓存;从三级缓存中移除A。
  3. getBean(A),将A对应的实例赋值到B
  4. B实例放入一级缓存,并清空其它级java缓存。彻底完成B的创建,返回给A
  5. A放入一级缓存,并清空其它级缓存
  1. 一级缓存singletonObjectsConcurrentHashMap):单例对象缓存池。【完整的成品对象】
  2. 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
  3. 二级缓存earlySingletonObjectsHashMap):早期的单例对象。【非完整的半成品对象】
  4. 提前曝光的单例对象的cache,存放例化之后,属性未赋值的单例对象,用于解决循环依赖
  5. 三级缓存singletonFactoriesHashMap):单例工厂的缓存。beanName->ObjectFactory
  6. 单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖【lambada表达式

废弃三级缓存也可以解决循环依赖,但配置AOP切面时会出错,无法生成代理对象。三级缓存是为了处理AOP中的循环依赖,在配置切面后,有可能在getEarlyBeanReference中把原始对象替换成代理对象,导致Bean版本不是最终的版本。

自己设计IOC容器

  1. 创建自定义注解,标记对象。
  2. 遍历所有类,获取被注解标记的类。

    将目标类本身作为键,目标类的实例作为值,放入到beanMap中

  3. 通过类加载器获取到加载的资源信息,过滤出文件类型的资源。

  4. 通过反射机制获取对应的class对象并加入到classSet里,并加载进容器里。

    1.创建注解 (Controller、Service、Repository、Component)
    2.提取标记对象(URL提取、获取类加载器、获取Class对象)
    3.依赖注入(Autowired、成员变量实例的注入(解决多个实现类))
    4.实现容器(单例模式(饿汉懒汉)、创建容器载体、实现容器加载)

Bean

  • Bean的本质就是java对象,只是这个对象的生命周期由容器来管理
  • 不需要再java类上添加额外限制(低侵入)
  • spring根据配置生成描述bean的beanDefinition。(jdk用java.lang.class描述)

    这里需要注意Spring框架的5种作用域(@Scope)、懒加载(@lazy)、首选(@Primary)设置为true的bean是优先的实现类、factory-bean/method(@Bean和@Configuration)

容器初始化:解析配置->定位与注册对象

单例 bean 是线程安全的吗?

不是,Spring框架中的单例bean不是线程安全的。
spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。如果 bean 有状态的话(比如 view model )就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了, 保证线程安全了。

Spring如何处理线程并发问题?

一般只有无状态的Bean才可以在多线程下共享,大部分是无状态的Bean。当存有状态的Bean的时候,spring一般是使用ThreadLocal进行处理,解决线程安全问题。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。 同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。 ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,所以没有相同变量的访问冲突问题。所以在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

Spring AOP动态代理

ASM字节码框架动态生成技术,程序运行时动态创建字节码文件。
AOP引入责任链模式顺序执行每一个通知。(@Order注解)
Spring中的5种advice通知,在调用对象时,通过动态代理ASM技术,把需要执行的advice全部放在chain集合中。为保证整个链的调用默认先调用ExposeInvoactionInterceptor(责任链设计模式,它作为第一个通知方法保证所有的通知方法链式执行)去出发整个链路执行,在执行完每一个advice后都会回到super的proceed方法中,执行下一个adivce。若执行不通adivce会有对应的切面通知方法。当所有的advice都执行完再通过反射调用目标方法。【Spring AOP的声明事务也是通过advice实现】

AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP 对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理 :

ASM字节码框架动态生成技术,程序运行时动态创建字节码文件。
AOP引入责任链模式顺序执行每一个通知。(@Order注解)

Spring中的5种advice通知,在调用对象时,通过动态代理ASM技术,把需要执行的advice全部放在chain集合中。为保证整个链的调用默认先调用ExposeInvoactionInterceptor(责任链设计模式,它作为第一个通知方法保证所有的通知方法链式执行)去出发整个链路执行,在执行完每一个advice后都会回到super的proceed方法中,执行下一个adivce。若执行不通adivce会有对应的切面通知方法。当所有的advice都执行完再通过反射调用目标方法。【Spring AOP的声明事务也是通过advice实现】

  • JDK动态代理 (实现接口),java反射机制生成一个代理接口的匿名类,调用具体方法的时候调用invokeHandler

    ①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射 来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

通过bind方法建立代理与真实对象关系,通过Proxy.newProxyInstance(target)生成代理对象。
代理对象通过反射invoke方法实现调用真实对象的方法

  • CGLIB (asm字节码编辑技术创建类,基于classLoad装载,修改字节码生成子类处理)(如果被代理类被final关键字所修饰,那么抱歉会失败)

    ②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为fifinal,那么它是无法使用CGLIB做动态代理的

为什么JDK动态代理要实现接口

  • 动态代理生成的代理类父类是Proxy类,通过jdk代理生成的类都继承Proxy类。因为Java是单继承的,而代理类又必须继承自Proxy类,所以通过jdk代理的类必须实现接口
  • 需要反射获得代理类的有关参数,必须要通过某个类,反射获取有关方法
  • 成功返回的是object类型,要获取原类,只能继承/实现,或者就是那个代理类
  • 对具体实现的方法内部并不关心,这个交给InvocationHandler.invoke那个方法里去处理就好了,我只想根据你给我的接口反射出对我有用的东西。
  • 考虑到设计模式,以及proxy编者编写代码的逻辑使然

    动态代理:程序运行期间根据需要动态创建代理类及其实例已完成功能

静态代理

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
https://www.cnblogs.com/cC-Zhou/p/9525638.html
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
为什么叫做静态呢?因为它的类型是事先预定好的程序
运行前就已存在的编译好的代理类。实现步骤:
1、定义业务接口。
2、实现业务接口。
3、定义代理类并实现业务接口,最后通过客户端调用。

BeanFactory 、FactoryBean、ObjectFactory

BeanFactory

生产Bean的工厂,负责生产【利用反射机制实现】和管理各个bean实例。Spring容器暴露在外获取Bean的入口。
BeanFactory是底层IOC容器,ApplicationContext在其基础上增加了其他特性(国际化消息、事务的发布、资源访问)
想要获取BeanFactory可以通过applicationContext.getParentBeanFactory()获取
BeanFactory:其实就是 IOC 容器,Spring 里面的 Bean 都归它管,而FactoryBean也是 Bean 所以说也是归 BeanFactory 管理的。

FactoryBean

(封装了真实想要的对象复杂的创建过程)把你真实想要的 Bean 封装了一层,在真正要获取这个 Bean 的时候容器会调用 FactoryBean#getObject() 方法,而在这个方法里面你可以进行一些复杂的组装操作。
ObjectFactory 这个是用于延迟查找的场景,它就是一个普通工厂,当得到 ObjectFactory 对象时,相当于 Bean 没有被创建,只有当 getObject() 方法时,才会触发 Bean 实例化等生命周期。

BeanFactory 就是 IOC 容器,FactoryBean 是特殊的 Bean, 用来封装创建比较复杂的对象,而 ObjectFactory 主要用于延迟查找的场景,延迟实例化对象。

BeanFactory和ApplicationContext的区别

  • BeanFactory:
    是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;
    BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;
  • ApplicationContext:
    应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;
    ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
  1. 国际化(MessageSource)
  2. 访问资源,如URL和文件(ResourceLoader)
  3. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
  4. 消息发送、响应机制(ApplicationEventPublisher)
  5. AOP(拦截器)

Spring的启动流程

  1. web应用部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
  2. web容器启动时,会触发容器初始化事件。创建ContextLoaderListener,回调contextInitialized方法
  3. 创建并初始化spring容器。
  4. 创建WebApplicationContext对象(解析web.xml或者原生Servlet3.0的注解)。
  5. refresh容器,读取XML配置,创建beans。
  6. spring容器引用保存到ServletContext和ContextLoaderListener中。

这样每个servlet 就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean

SpringBoot - 开箱即用 约定大于配置

SpringBoot的自动装配原理,其实就是在项目启动的时候去加载META-INF下的spring.factories文件。
丙-Spring - 图4

  • 开箱即用
    SpringBoot所有自动配置类都是在启动的时候进行扫描并加载,通过spring.factories可以找到自动配置类的路径,但是不是所有存在于spring,factories中的配置都进行加载,而是通过@ConditionalOnClass注解进行判断条件是否成立(只要导入相应的stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。
  • 约定大于配置
    我们的配置文件(.yml)应该放在哪个目录下,配置文件的命名规范,项目启动时扫描的Bean,组件的默认配置是什么样的(比如SpringMVC的视图解析器)等等等等这一系列的东西,都可以被称为约定,

SpringApplication的初始化:

  1. 推断web应用类型 ( none, servlet, reactive响应式非阻塞)
  2. 初始化应用上下文
    从Map中根据org.springframework.context.ApplicationContextInitializer的类型拿到需要的类初始化类,进入getOrDefault把加载到的需要初始化的类进行实例化添加到一个集合中等待备用
  3. 初始化监听器类
    初始化监听类的时候和上面初始化应用上下文是一样的代码。
    唯一不同的是getSpringFactoriesInstances(ApplicationListener.class))传进去的是ApplicationListener.class,而初始化应用上下文传入的参数是ApplicationContextInitializer.class
  4. 推演出主程序类

应用上下文即是Spring容器抽象的一种实现;而我们常见的ApplicationContext本质上说就是一个维护Bean定义以及对象之间协作关系的高级接口。Spring的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型:一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;还有一种就是继承了BeanFactory后派生而来的应用上下文,其抽象接口也就是我们上面提到的的ApplicationContext,它能提供更多企业级的服务,例如解析配置文本信息等等,这也是应用上下文实例对象最常见的应用场景。有了上下文对象,我们就能向容器注册需要Spring管理的对象了。对于上下文抽象接口,Spring也为我们提供了多种类型的容器实现,供我们在不同的应用场景选择。

run()方法

  1. 加载运行监听器(各种listener)
  2. 根据Web应用类型来创建对应的环境
  3. 根据Web应用类型来创建容器
  4. 准备应用上下文 prepareContext
  5. 刷新应用上下文 (这时创建了Tomcat对象)
  6. 发布监听应用启动事件
  7. 执行Runner
  8. 发布上下文准备完成的事件

在SpringBoot中启动tomcat的工作在刷新应用上下文上下这一步。而tomcat的启动主要是实例化两个组件:Connector、Container,一个tomcat实例就是一个Server,一个Server包含多个Service,也就是多个应用程序,每个Service包含多个Connector和一个Container,而一个Container下又包含多个子容器。

常用注解

@SpringBootApplication(开启自动配置)
包含@SpringBootConfiguration 、@EnableAutoConfiguration、@ComponentScan通常用在主类上;

@ComponentScan:其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
@EnableAutoConfiguration:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
@SpringBootConfiguration:继承自@Configuration,二者功能也一致,标注当前类是配置类,
并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。

@Repository:
用于标注数据访问组件,即DAO组件;
@RestController:
用于标注控制层组件(如struts中的action),包含@Controller和@ResponseBody;
@Component:
泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注;
@RequestBody:
参数前加上这个注解之后,认为该参数必填。表示接受json字符串转为对象 List等;
@AutoWired:
byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作;
当加上(required=false)时,就算找不到bean也不报错;

@Qualifier:
当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用;

@RequestMapping:
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径;

该注解有六个属性:
params:指定request中必须包含某些参数值是,才让该方法处理。
headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。
value:指定请求的实际地址,指定的地址可以是URI Template 模式
method:指定请求的method类型, GET、POST、PUT、DELETE等
consumes:指定处理请求的提交内容类型(Content-Type),如application/json,text/html;
produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回。

@ControllerAdvice:
包含@Component。可以被扫描到。统一处理异常;

@ExceptionHandler(Exception.class):
用在方法上面表示遇到这个异常就执行以下方法。