Spring总结

1

Spring IOC总结

Spring 框架

Spring 是一个java企业级应用的开源开发框架。Spring主要用来开发
Java 应用,但是有些扩展是针对构建J2EE平台的web 应用。它是很多
模块的集合,使用这些模块可以很方便地协助我们进行开发

Spring模块

  1. Spring Core:Spring 其他所有的功能都需要依赖于该类库。主要提
    供IoC 依赖注入功能
  2. Spring AOP:提供了面向切面的编程实现
  3. Spring JDBC:Java数据库连接
  4. Spring Web:为创建Web应用程序提供支持

Spring框架的好处

  1. 轻量:Spring 是轻量的,基本的版本大约2MB
  2. 声明式事务的支持:在Spring中,我们可以从单调烦闷的事务管理代
    码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和
    质量
  3. 面向切面的编程(AOP):通过Spring提供的AOP功能,方便进行面向切面
    的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付
  4. 方便解耦,简化开发:通过Spring提供的IoC容器,我们可以将对象之间
    的依赖关系交由Spring 进行控制

@RestController和@Controller

  1. Controller返回一个页面
    单独使用@Controller 不加@ResponseBody的话一般使用在要返回一个视图
    的情况,这种情况属于Spring MVC的应用,对应于前后端不分离的情况
  2. @RestController返回JSON 或XML 形式数据
    但@RestController只返回对象,对象数据直接以JSON 或 XML 形式写入
    HTTP响应(Response)中,这种情况属于 RESTful Web服务,这也是目前
    日常开发所接触的最常用的情况(前后端分离)
  3. @Controller +@ResponseBody 返回JSON 或XML 形式数据
    @ResponseBody 注解的作用是将Controller 的方法返回的对象通过适当的
    转换器转换为指定的格式之后,写入到HTTP响应(Response)对象的body 中
    ,通常用来返回JSON 或者XML 数据,返回JSON 数据的情况比较多

JavaBean

  1. 所有属性为private
  2. 提供默认构造方法
  3. 提供getter和setter
  4. 实现serializable接口

Spring IOC和AOP

IOC

IoC(控制反转)是一种设计思想,就是将原本在程序中手动创建对象的
控制权,交由Spring框架来管理。IoC 容器是Spring 用来实现IoC 的
载体,IoC 容器实际上就是个Map(key,value),Map中存放的是各种
对象。一般通过XML 文件来配置Bean,后来开发人员觉得XML文件来配
置不太好,于是SpringBoot 注解配置就慢慢开始流行起来

IOC的好处

将对象之间的相互依赖关系交给IoC 容器来管理,并由IoC容器完成对象
的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系
中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象
的时候,只需要配置好配置文件/ 注解即可,完全不用考虑对象是如何
被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个
类作为它的底层,假如我们需要实例化这个Service,你可能要每次都
要搞清这个Service 所有底层类的构造函数。如果利用 IoC 的话,你
只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可
维护性且降低了开发难度

依赖注入

在依赖注入中,您不必创建对象,但必须描述如何创建它们。您不是直接
在代码中将组件和服务连接在一起,而是描述配置文件中哪些组件需要
哪些服务。由IoC 容器将它们装配在一起,侧重于实现

IOC(依赖注入)方式

  1. 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实
    现的,该类有一系列参数,每个参数代表一个对其他类的依赖
  2. Setter方法注入:Setter方法注入是容器通过调用无参构造器或无
    参static工厂,方法实例化bean之后,调用该bean的setter方法,即
    实现了基于setter的依赖注入。底层通过反射实现
  3. 静态工厂注入:通过调用静态工厂的方法来获取自己需要的对象,为了
    让spring 管理所有对象,我们不能直接通过”工程类.静态方法()”来获取
    对象,而是依然通过spring 注入的形式获取
  4. 实例工厂:获取对象实例的方法不是静态的,所以你需要首先new 工
    厂类,再调用普通的实例方法

AOP

AOP(面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的
逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减
少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和
可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口
,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实
现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring
AOP会使用Cglib ,这时候 Spring AOP会使用Cglib 生成一个被代理
对象的子类来作为代理

为什么使用AOP

比如有几个方法含有一些相同代码,如果把这些代码抽成一个方法,这就是面
向对象编程,如果这个方法改名字那么所有引用这个方法的地方都要改名字,
如果这个方法因为逻辑要改变位置那么所有引用这个方法的地方都要进行修
改。以上问题可以用AOP解决。根据以上如果许多方法需要使用这个抽取的方
法,不需要写在那些方法中,直接切入那些方法中,如果修改抽取方法的名
字或位置时不需要修改,因为这些方法并没有引用

Spring AOP和AspectJ AOP

  1. Spring AOP属于运行时增强,而AspectJ 是编译时增强。 Spring
    AOP 基于代理(Proxying),而AspectJ 基于字节码操作
  2. Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java
    生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能
    更加强大,但是 Spring AOP 相对来说更简单
  3. 如果我们的切面比较少,那么两者性能差异不大。但是当切面太多的
    话,最好选择AspectJ,它比Spring AOP快很多
  4. Spring AOP 基于动态代理方式实现,AspectJ 基于静态代理方式实现
    。Spring AOP 仅支持方法级别的PointCut,提供了完全的AOP 支持,它
    还支持属性级别的 PointCut

BeanFactory和ApplicationContext的区别

是Spring的两大核心接口,都可以当做Spring的容器。其中
ApplicationContext是BeanFactory的子接口

  1. 加载方式 第一个使用懒加载,第二个使用即时加载
  2. 第一个不支持基于依赖的注解,第二个支持基于依赖的注解
  3. BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义
    ,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期
    ,维护bean之间的依赖关系
  4. ApplicationContext接口作为BeanFactory的派生,除了提供
    BeanFactory所具有的功能外,还提供了更完整的框架功能
  5. BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某
    个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就
    不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入
    ,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常
  6. ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。
    这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利
    于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单
    实例Bean,通过预载入单实例bean,确保当你需要的时候,你就不用等待
    ,因为它们已经创建好了

BeanFactory和ApplicationContext的优缺点

  1. BeanFactory的优缺点:
  • 优点:应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势
  • 缺点:运行速度会相对来说慢一些。而且有可能会出现空指针异常的错误,
    而且通过Bean工厂创建的Bean生命周期会简单一些
  1. ApplicationContext的优缺点:
  • 优点:所有的Bean在启动的时候都进行了加载,系统运行的速度快,在系
    统启动的时候,可以发现系统中的配置问题
  • 缺点:把费时的操作放到系统启动中完成,所有的对象都可以预加载,缺点
    就是内存占用较大

Spring提供的配置方式

  1. 通过XML配置文件注入JavaBean
    配置bean所需的依赖项和服务在XML格式的配置文件中指定。这些配置文件
    通常包含许多bean定义和特定于应用程序的配置选项。它们通常以bean标
    签开头
  2. 通过注解注入JavaBean
    在类上添加@Configuration注解来标注该类是一个Spring 的配置类,也
    就是告诉Spring它是一个配置类,最后通过@Bean注解将类注入到Spring
    的IOC容器中
  3. 使用@ComponentScan自动扫描组件
    在配置类上添加@ComponentScan注解并指定扫描包,在指定的包或其子
    包中的类上标注@Repository、@Service、@Controller、@Component
    注解的类都会被扫描到,并将这个类注入到Spring容器中
  4. @Import注解,快速向Spring容器中导入一个组件

Spring中的bean的作用域

@Scope注解能够设置组件的作用域

  1. singleton : 唯一bean实例,Spring中的bean默认都是单例的,启动
    spring 容器,便会创建对象
  2. prototype : 每次从容器中获取时都会创建一个新的bean实例
  3. request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前
    HTTP request内有效,request作用域要在Spring容器的Web环境中
  4. session : 同一个session范围内,该bean仅在当前HTTP session
    内有效
  5. application:全局Web应用级别的作用域,也是在Web环境中使用,
    一个Web应用程序对应一个bean实例,和单例效果类似

request和session作用域是需要Web环境来支持的,这两个值基本上使用
不到。当我们使用Web容器来运行Spring应用时,如果需要将组件的实例
对象的作用域设置为request和session,那么我们通常会使用

1
2
request.setAttribute("key", object);
session.setAttribute("key", object);

懒加载

懒加载,也称延时加载,仅针对单实例bean生效。 单实例bean是在Spring
容器启动的时候加载的,添加@Lazy注解后就会延迟加载,在Spring容器启
动的时候并不会加载,而是在第一次使用此bean 的时候才会加载,但当你
多次获取bean 的时候并不会重复加载,只是在第一次获取的时候才会加载
,这不是延迟加载的特性,而是单实例bean的特性

自定义组件作用域

  1. 第一步实现Scope接口
  2. 第二步将自定义Scope注册到容器中,通过registerScope方法
  3. 使用自定义的作用域。也就是在定义bean的时候,指定bean的scope属性
    为自定义的作用域名称

Spring中的单例bean的线程安全问题

单实例bean是整个应用所共享的,所以需要考虑到线程安全问题,对这个对
象的成员变量的写操作会存在线程安全问题,但是,一般情况下我们常用的
Controller、Service、Dao 这些Bean 是无状态的。无状态的Bean 不能保
存数据,因此是线程安全的。有两种解决办法

  1. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存
    在 ThreadLocal 中
  2. 改变Bean 的作用域为 “prototype”:每次请求都会创建一个新的bean
    实例,自然不会存在线程安全问题

将一个类声明为bean 的注解

一般使用@Autowired 注解自动装配bean

  1. @Component :通用的注解,可标注任意类为Spring组件。如果一个Bean
    不知道属于哪个层,可以使用@Component 注解标注
  2. @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作
  3. @Controller : 对应Spring MVC 控制层,主要用于接受用户请求并调
    用Service 层返回数据给前端页面
  4. @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到Dao层

Bean的创建方式

  1. 利用默认的构造方法 在applicationContext.xml文件中配置id和class
    使用有参数构造器进行定义,使用此中方式,可以指定构造器参数值,其中
    index表示位置,value表示常量值,也可以指定引用,指定引用使用ref来
    引用另一个Bean定义

  2. 利用静态工厂方法 使用这种方式除了指定必须的class属性,还要指定
    factory-method属性来指定实例化Bean的方法,而且使用静态工厂方法也
    允许指定方法参数,spring IoC容器将调用此属性指定的方法来获取Bean
    ,spring容器只负责调用静态工厂方法,而这个静态工厂方法内部实现由
    程序员完成

  3. 利用实例工厂方法 使用这种方式不能指定class属性,此时必须使用
    factory-bean属性来指定工厂Bean,factory-method属性指定实例化
    Bean的方法,而且使用实例工厂方法允许指定方法参数,方式和使用构
    造器方式一样

  4. 利用setter方式 这种方式,只要写上对应的set、get方法,然后再
    bean.xml文件中利用property注入值即可

  5. 通过反射机制利用bean的class属性指定实现类来实例化bean的

  6. 使用FactoryBean向Spring容器中注册bean,重写getObject方法
    ,实现该定制实例化bean的逻辑

Spring 中的bean 生命周期

bean的生命周期,指的是bean从创建到初始化,经过一系列的流程,最终
销毁的过程。在Spring中,我们可以自己来指定bean的初始化和销毁的方
法。当容器在bean进行到当前生命周期的阶段时,会自动调用我们自定义
的初始化和销毁方法。流程如下

  1. Bean 容器找到配置文件中 Spring Bean 的定义
  2. Bean 容器利用 Java Reflection API 创建一个Bean的实例
  3. 如果涉及到一些属性值利用set()方法设置一些属性值
  4. 如果Bean 实现了BeanNameAware 接口,调用setBeanName()方法,
    传入Bean的名字
  5. 如果Bean实现BeanClassLoaderAware接口,调用setBeanClassLoader()
    方法,传入 ClassLoader对象的实例
  6. 与上面的类似,如果实现了其他*.Aware接口,就调用相应的方法
  7. 如果有和加载这个Bean 的Spring 容器相关的BeanPostProcessor
    对象,执行postProcessBeforeInitialization()方法
  8. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法
  9. 如果Bean 在配置文件中的定义包含init-method 属性,执行指定的方法
  10. 如果有和加载这个Bean的 Spring 容器相关的 BeanPostProcessor
    对象,执行postProcessAfterInitialization()方法
  11. 当要销毁Bean 的时候,如果Bean 实现了DisposableBean 接
    口,执行 destroy() 方法
  12. 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含
    destroy-method 属性,执行指定的方法

定义初始化和销毁方法

  1. 使用XML配置文件的方式配置bean
    指定 init-method=”init” destroy-method=”destroy”,在类中需要
    存在init()方法和destroy()方法。而且Spring中还规定这两个方法必须
    是无参方法,但可以抛出异常
  2. 通过@Bean注解指定初始化和销毁方法
    @Bean(initMethod=”init”, destroyMethod=”destroy”),先是调用
    类的构造方法来创建对象,接下来调用对象的init()方法来进行初始化
    ,bean的销毁方法是在容器关闭的时候被调用的
  3. 实现InitializingBean接口和DisposableBean接口
  4. @PostConstruct注解和@PreDestroy注解
  5. BeanPostProcessor后置处理器

指定初始化和销毁方法的使用场景

一个典型的使用场景就是对于数据源的管理。例如在配置数据源时,在初始
化的时候,会对很多的数据源的属性进行赋值操作;在销毁的时候,我们
需要对数据源的连接等信息进行关闭和清理。这个时候,我们就可以在自
定义的初始化和销毁方法中来做这些事情了

初始化和销毁方法调用的时机

  1. bean对象的初始化方法调用的时机:对象创建完成,如果对象中存在
    一些属性,并且这些属性也都赋好值之后,那么就会调用bean 的初始化
    方法。对于单实例bean来说,在Spring容器创建完成后,Spring容器会
    自动调用bean的初始化方法;对于多实例bean来说,在每次获取bean对
    象的时候,调用bean的初始化方法
  2. bean对象的销毁方法调用的时机:对于单实例bean来说,在容器关闭
    的时候,会调用bean的销毁方法;对于多实例bean来说,Spring容器不
    会管理这个bean,也就不会自动调用这个bean的销毁方法了

InitializingBean和DisposableBean

  1. Spring中提供了一个InitializingBean接口,该接口为bean提供了
    属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继
    承该接口的类,在bean的属性初始化后都会执行该方法
  2. bean在销毁前,Spring将会调用DisposableBean接口的destroy()
    方法

afterPropertiesSet()方法调用时机

先调用afterPropertiesSet方法,后执行init-method指定的方法。如
果调用afterPropertiesSet 方法出错,那么就不会调用init-method
指定的方法

destroy()方法调用时机

多实例bean的生命周期不归Spring容器来管理,如果一个多实例bean实
现了DisposableBean接口是没有意义的

@PostConstruct注解和@PreDestroy注解

  1. @PostConstruct注解是Java中的注解,用来修饰一个非静态的void()
    方法,在构造函数之后,init()方法之前执行
  2. @PreDestroy注解同样是Java提供的,会在destroy()方法之后执行

BeanPostProcessor

是一个接口,其中有两个方法,即postProcessBeforeInitialization
和postProcessAfterInitialization这两个方法,这两个方法分别是
在Spring容器中的bean初始化前后执行

  1. postProcessBeforeInitialization方法会在bean实例化和属性设
    置之后,自定义初始化方法之前被调用
  2. postProcessAfterInitialization方法会在自定义初始化方法之后
    被调用

BeanPostProcessor后置处理器作用

后置处理器可用于bean对象初始化前后进行逻辑增强,用于@Autowired
注解的实现,用于Spring AOP的动态代理等等,bean对象初始化完成之
后,后置处理器会判断该bean是否注册了切面,若是,则生成代理对象
注入到容器中

自定义组件中注入Spring底层的组件

自定义的组件要想使用Spring容器底层一些组件,ApplicationContext
(IOC容器)、底层的BeanFactory 等等,那么只需要让自定义组件实现
XxxAware接口即可。Spring在创建对象的时候,会调用XxxAware接口中
定义的方法注入相关的组件。
Spring中形如XxxAware这样的接口都继承了Aware接口

  1. 通过BeanNameAware接口获取到当前bean在Spring容器中的名称
  2. ApplicationContextAware接口我们可以获取到IOC容器

XxxAware原理

XxxAware接口的底层原理是由XxxAwareProcessor实现类实现的,也就是
说每一个XxxAware接口都有它自己对应的XxxAwareProcessor实现类,其
实现了BeanPostProcessor接口,本质上是一个后置处理器。
在容器初始化的时候有一个refresh()方法,向spring容器注册了后置处
理器,postProcessBeforeInitialization方法会在每个bean实例化后
,初始化前被调用,实现Aware接口的类调用各个Aware接口中setXxx方
法将需要的对象传入

Spring 中出现同名bean怎么办?

  1. 同一个配置文件内同名的Bean,以最上面定义的为准
  2. 不同配置文件中存在同名Bean,后解析的配置文件会覆盖先解析的配置
    文件
  3. 同文件中ComponentScan和@Bean出现同名Bean。同文件下@Bean的会
    生效,@ComponentScan扫描进来不会生效。通过@ComponentScan扫描进
    来的优先级是最低的,原因就是它扫描进来的Bean定义是最先被注册的

Spring 怎么解决循环依赖问题?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方
,最终形成闭环。spring对循环依赖的处理有三种情况:

  1. 构造器的循环依赖:这种依赖spring是处理不了的,直接抛出异常
  2. 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖
  3. 非单例循环依赖:无法处理

三级缓存是什么?

spring内部有三级缓存:

  1. singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的
    bean实例
  2. earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
  3. singletonFactories 三级缓存,用于保存bean创建工厂,以便于后
    面扩展有机会创建代理对象

单例模式下的setter循环依赖如何解决

Spring的单例对象的初始化主要分为三步,循环依赖主要发生在第一、第二步
。也就是构造器循环依赖和field循环依赖

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  3. initializeBean:调用spring xml中的init 方法

Spring容器创建对象的时机分几种?

分为两种

  1. 默认情况下,启动spring 容器便创建对象
  2. 在spring的配置文件bean中有一个属性 lazy-init=”default/true/false”
  • 如果lazy-init为”default/false”在启动spring容器时创建对象(默认情况)
  • 如果lazy-init为”true”,在context.getBean时才要创建对象

哪些是重要的bean生命周期方法?你能重载它们吗?

有两个重要的bean 生命周期方法,第一个是setup ,它是在容器加载bean
的时候被调用。第二个方法是 teardown它是在容器卸载类的时候被调用

什么是bean的自动装配,有哪几种方式?

约定优于配置,不需要在bean中配置,能够自动找到引用,自动装配可以减少
代码量但是降低可读性

  1. no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配
  2. byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的
    autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性
    具有相同名字的bean
  3. byType:通过参数类型自动装配,Spring容器在配置文件中发现bean的
    autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具
    有相同类型的bean。如果有多个bean符合条件,则抛出错误
  4. constructor:这个方式类似于byType,但是要提供给构造器参数,如
    果没有确定的带参数的构造器参数类型,将会抛出异常

注解

@SpringBootApplication

这个注解是Spring Boot 项目的基石,创建SpringBoot 项目之后会默
认在主类加上,标注在某个类上就说明该类是主配置类,运行该类main
方法启动应用。
可以把该注解看作是三个注解的集合

  1. @Configuration 允许在Spring上下文中注册额外的bean或导入其
    他配置类
  2. @EnableAutoConfiguration 启用SpringBoot 的自动配置机制
  3. @ComponentScan 扫描被@Component注解的bean,注解默认会扫描
    该类所在的包下所有的类

@Autowired

@Autowired注解可以对类成员变量、方法和构造函数进行标注,完成自动
装配的工作,支持Spring的@Primary注解优先注入

  1. 默认是优先按照类型去容器中找对应的组件
  2. 如果找到多个相同类型的组件,那么是将属性名称作为组件的id

Component

我们一般使用 @Autowired 注解让Spring 容器帮我们自动装配 bean。要
想把类标识成可用于@Autowired 注解自动装配的bean 的类,可以采用以
下注解实现:

  1. @Component :通用的注解,可标注任意类为 Spring 组件。如果一个
    Bean 不知道属于哪个层,可以使用@Component 注解标注
  2. @Repository : 对应持久层即Dao 层,主要用于数据库相关操作
  3. @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到Dao层
  4. @Controller : 对应Spring MVC 控制层,主要用户接受用户请求并
    调用Service 层返回数据给前端页面

@Component 和@Bean 的区别

  1. 作用对象不同: @Component 注解作用于类,而@Bean注解作用于方法
  2. @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容
    器中(我们可以使用 @ComponentScan注解定义要扫描的路径从中找出标识
    了需要装配的类自动装配到Spring 的bean 容器中)。@Bean注解通常是我
    们在标有该注解的方法中定义产生这个bean
  3. @Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只
    能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配
    到 Spring容器时,则只能通过@Bean来实现

下面这个例子是通过 @Component 无法实现的

1
2
3
4
5
6
7
8
9
10
11
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}

@RestController

@RestController 注解是@Controller 和@ResponseBody 的合集,
表示这是个控制器bean,并且是将函数的返回值直接填入HTTP 响应
体中,是REST 风格的控制器,返回JSON 或XML 形式数据

@Scope

声明Spring Bean 的作用域,四种常见的Spring Bean 的作用域:

  1. singleton : 唯一bean 实例,Spring 中的bean 默认都是单例的
  2. prototype : 每次请求都会创建一个新的bean 实例
  3. request : 每一次HTTP 请求都会产生一个新的bean,该bean 仅
    在当前 HTTP request 内有效
  4. session : 每一次HTTP 请求都会产生一个新的 bean,该 bean
    仅在当前 HTTP session 内有效

@Configuration

一般用来声明配置类,可以使用 @Component 注解替代,不过使用
Configuration 注解声明配置类更加语义化,指示一个类声明一个
或者多个@Bean 声明的方法并且由Spring容器统一管理,以便在运
行时为这些bean生成,@Bean注解表示此方法将要返回一个对象,
作为一个bean注册进Spring应用上下文

处理常见的HTTP 请求类型

  1. Get 请求
    @GetMapping(“users”) 等价于 @RequestMapping(value=”/users”,
    method=RequestMethod.GET),请求从服务器获取特定资源
  2. POST 请求
    @PostMapping(“users”) 等价于 @RequestMapping(value=”/users”,
    method=RequestMethod.POST),在服务器上创建一个新的资源
  3. PUT 请求
    @PutMapping(“/users/{userId}”) 等价于 @RequestMapping(
    value=”/users/{userId}”,method=RequestMethod.PUT),更
    新服务器上的资源(客户端提供更新后的整个资源)。举个例子:
    PUT /users/12(更新编号为 12 的学生)
  4. DELETE 请求
    @DeleteMapping(“/users/{userId}”) 等价于 @RequestMapping(
    value=”/users/{userId}”,method=RequestMethod.DELETE),从
    服务器删除特定的资源
  5. PATCH 请求
    更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),
    一般实际项目中,我们都是PUT不够用了之后才用PATCH请求去更新数据

@PathVariable 和 @RequestParam

@PathVariable用于获取路径参数,@RequestParam用于获取查询参数

1
2
3
4
5
6
7
8
9
public class SpringMvcHandler{ 
@RequestMapping("welcome/{name}")
public String welcome(
@PathVariable("name") String name,
@RequestParam(value="uage",required=false,defaultValue="23")
String type){
..
}
}

@RequestBody

用于读取Request 请求(可能是POST,PUT,DELETE,GET 请求)的body
部分并且Content-Type 为 application/json 格式的数据,接收到
数据之后会自动将数据绑定到 Java 对象上去

@Value

@Value注解可以为bean中的属性赋值,可以标注在字段、方法、参数以及
注解上,而且在程序运行期间生效,也可以读取比较简单的配置信息

1
2
3
4
5
6
//不通过配置文件注入
@Value("李阿昀")
private String name; // 注入普通字符串
//通过配置文件注入属性的情况
@Value("${person.nickName}")
private String nickName; // 昵称

@Qualifier

@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,那么
就需要配合@Qualifier注解来使用了,组件的id默认是类名首字母小写

@Primary注解

使用@Primary注解来标注优先使用哪一个实现类

@Resource

该注解默认按照名称进行装配,名称可以通过name属性进行指定,如果没
有指定name属性,当注解写在字段上时,那么默认取字段名将其作为组件
的名称在IOC容器中进行查找,如果属性名称不匹配则会按照class 类型
进行匹配,不支持@Primary注解优先注入的功能

@Inject

该注解默认是根据参数名去寻找bean注入,支持Spring的@Primary注解优
先注入,@Inject注解还可以增加@Named注解指定要注入的bean

@Profile

@Profile注解是Spring为我们提供的可以根据当前环境,动态地激活和切
换一系列组件的功能,开发环境、测试环境、生产环境使用不同的数据源

  1. @Profile注解不仅可以标注在方法上,也可以标注在配置类上
  2. 如果@Profile注解标注在配置类上,那么只有是在指定的环境的时候,
    整个配置类里面的所有配置才会生效
  3. 如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任
    何环境下都会被注册到IOC容器中,前提是在整个配置类生效的情况下

全局处理Controller 层异常

介绍一下我们Spring 项目必备的全局处理Controller 层异常

  1. @ControllerAdvice :注解定义全局异常处理类
  2. @ExceptionHandler :注解声明异常处理方法

新建异常处理类,只需要在类上加上@ControllerAdvice注解这个类就成
为了全局异常处理类,@ExceptionHandler作用在方法上,拦截所有异常

@Aspect

告诉Spring当前类是一个切面类,而不是一些其他普通的类

@EnableAspectJAutoProxy

作用在配置类上,开启基于注解的AOP模式

Spring事务

作用位置

  1. 作用于类:当把@Transactional 注解放在类上时,表示所有该类的
    public 方法都配置相同的事务属性信息
  2. 作用于方法:当类配置@Transactional,方法也配置@Transactional
    ,方法的事务会覆盖类的事务配置信息

Spring事务管理的方式

Spring支持两种类型的事务管理:

  1. 编程式事务管理:所谓编程式事务指的是通过编码方式实现事务,允许
    用户在代码中精确定义事务的边界。即类似于JDBC编程实现事务管理
  2. 声明式事务管理:管理建立在AOP之上的。其本质是对方法前后进行拦截
    ,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后
    根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编
    程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码
    ,只需在配置文件中做相关事务规则声明(或通过基于 @Transactional
    注解的方式),便可以将事务规则应用到业务逻辑中。你只需要通过注解
    或者XML配置管理事务

两种事务管理方式的优劣

  1. 编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式
    事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影
    响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注
    解的方式,便可以将事务规则应用到业务逻辑中
  2. 显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵
    入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,
    而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成
    声明式事务管理的配置

传播机制生效条件

因为spring 是使用 aop 来代理事务控制,是针对于接口或类的,所以在同
一个service类中两个方法的调用,传播机制是不生效的。也就是说类内部方
法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用
@Transactional注解进行修饰

Transactional失效场景

  1. 第一种Transactional注解标注方法修饰符为非public时,@Transactional
    注解将会不起作用
  2. 调用一个方法在类内部调用内部被@Transactional标注的事务方法,运行结果
    是事务不会正常开启
  3. 事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚

Spring事务传播行为

事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一
个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到
外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行,
方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务
中运行。spring使用AOP来支持声明式事务,会根据事务属性,自动在方法
调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚
事务

支持当前事务的情况

  1. REQUIRED:
  • 表示当前方法必须运行在事务中。如果外层有事务,则当前事务加入到
    外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
    ,Spring默认的传播机制
  • 比如两个方法A和B的事务传播属性都是REQUIRED,A包含B。比如单独调
    用B,那么B就是一个事务级别的方法,如果调用A,那么B就加入到A所在
    的事务中,如果B中抛出异常整个A都会回滚
  1. SUPPORTS:
  • 如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非
    事务方式执行。完全依赖外层的事务
  • A方法事务属性是REQUIRED,B方法事务属性是SUPPORTS,A包含B。单
    独调用B时是非事务的执行的,调用A时那么B也事务地执行
  1. MANDATORY:
  • 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
  • A方法事务属性是REQUIRED,B方法事务属性是MANDATORY,A包含B,单
    独调用B时因为当前没有一个活动的事务,则会抛出异常,调用A时B也事务
    地执行

不支持当前事务的情况

  1. REQUIRES_NEW:
  • 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果
    存在当前事务,在该方法执行期间,当前事务会被挂起,如果外层没有事
    务,执行当前新开启的事务即可
  • A方法事务属性是REQUIRED,B方法事务属性是REQUIRES_NEW,A包含B
    ,比如A的内容是A1、B和A2,首先执行A1的逻辑部分,然后挂起A事务,
    然后执行B事务,B执行完后执行A2部分,如果在A2部分抛出异常,那么
    A会回滚,但是B事务已经提交不受影响,所以只有A1和A2回滚
  1. NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前
    事务挂起,无论是否异常都不会回滚当前的代码
  2. NEVER:以非事务方式运行,如果当前存在事务,则抛出异常

其他情况

NESTED:

  • 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌
    套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在
    ,那么其行为与PROPAGATION_REQUIRED一样
  • A方法事务属性是 REQUIRED,B方法事务属性是NESTED,A包含B。单独调
    用B方法,则按REQUIRED属性执行,如果调用A 方法,比如A分为A1、B和A2
    ,在A1中设置一个保存点,如果B中失败那么会回到A1中的保存点,如果A2
    中失败那么会回滚全部。嵌套事务一个非常重要的概念就是内层事务依赖于
    外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作
    失败并不会引起外层事务的回滚

NESTED与REQUIRES_NEW的区别

  1. 都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务
  2. 使用 REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样
    ,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不
    影响。两个事务不是一个真正的嵌套事务
  3. 使用 NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务
    的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务,只有外部事
    务结束后它才会被提交

回滚规则

在默认设置下,事务只在出现运行时异常时回滚,而在出现受检查异常时
不回滚不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。
同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异
常是运行时异常

1
@Transactional(rollbackFor = Exception.class)

事务的隔离级别

事务的隔离级别定义一个事务可能受其他并发务活动活动影响的程度

  1. ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认采
    用的 REPEATABLE_READ 隔离级别Oracle 默认采用的 READ_COMMITTED
    隔离级别
  2. READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为
    它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  3. READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读
    ,但是幻读或不可重复读仍有可能发生
  4. REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据
    是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能
    发生
  5. SERIALIZABLE:最高的隔离级别,完全服从ACID 的隔离级别。所有的
    事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该
    级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能
    。通常情况下也不会用到该级别

InnoDB存储引擎在分布式事务的情况下一般会用到SERIALIZABLE 隔离级
别。InnoDB存储引擎提供了对XA事务的支持,并通过XA事务来支持分布式
事务的实现。分布式事务指的是允许多个独立的事务资源参与到一个全局
的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资
源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这
对于事务原有的ACID要求又有了提高。另外在使用分布式事务时InnoDB
存储引擎的事务隔离级别必须设置为SERIALIZABLE

事务超时属性

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间
限制但事务还没有完成,则自动回滚事务。在TransactionDefinition
中以int 的值来表示超时时间timeout,其单位是秒,默认值为-1,这
里的超时时间是指sql的执行时间,而不是整个业务

事务只读属性

对于只有读取数据查询的事务,可以指定事务类型为readonly,即只读事
务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在
有多条数据库查询操作的方法中

为什么数据查询操作还要启用事务

MySQL 默认对每一个新建立的连接都启用了autocommit模式。在该模式下
,每一个发送到 MySQL 服务器的sql语句都会在一个单独的事务中进行处
理,执行结束后会自动提交事务,并开启一个新的事务。
但是,如果你给方法加上了Transactional注解的话,这个方法执行的所
有sql会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化
它的执行,并不会带来其他的什么收益。如果不加Transactional,每条
sql会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到
最新值

  1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默
    认支持 SQL 执行期间的读一致性
  2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景
    下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之
    后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询
    将会出现读数据不一致的状态,此时应该启用事务支持

@Transactional的常用配置参数

  1. propagation 事务的传播行为,默认值为REQUIRED
  2. isolation 事务的隔离级别,默认值采用DEFAULT
  3. timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间
    限制但事务还没有完成,则自动回滚事务
  4. readOnly 指定事务是否为只读事务,默认值为false
  5. rollbackFor 用于指定能够触发事务回滚的异常类型,并且可以指定
    多个异常类型

@Transactional 事务注解原理

@Transactional 的工作机制是基于AOP 实现的,AOP 又是使用动态代
理实现的。如果目标对象实现了接口,默认情况下会采用JDK的动态代理
,如果目标对象没有实现了接口,会使用 CGLIB 动态代理

设计模式

Spring框架中用到了哪些设计模式

  1. 工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext
    创建bean对象
  2. 代理设计模式:Spring AOP功能的实现
  3. 单例设计模式:Spring中的bean默认都是单例的
  4. 模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template
    结尾的对数据库操作的类,它们就使用到了模板模式
  5. 包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每
    次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的
    需求能够动态切换不同的数据源
  6. 观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用
  7. 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、
    Spring MVC中也是用到了适配器模式适配Controller

AOP

什么是AOP

直译就是面向切面编程。AOP是一种编程思想,是面向对象编程的一种补充。面
向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各
个切面。所谓切面其实就相当于应用对象间的横切点,我们可以将其单独抽象
为单独的模块。
总之一句话:AOP是指在程序的运行期间动态地将某段代码切入到指定方法、
指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的

AOP的通知类型

在配置类中将切面类加入到容器,开启基于注解的AOP模式,即加上
@EnableAspectJAutoProxy注解

  1. 前置通知(对应的注解是@Before):在目标方法运行之前运行
  2. 后置通知(对应的注解是@After):在目标方法运行结束之后运行,无论
    目标方法是正常结束还是异常结束都会执行
  3. 返回通知(对应的注解是@AfterReturning):在目标方法正常返回之后
    运行
  4. 异常通知(对应的注解是@AfterThrowing):在目标方法运行出现异常之
    后运行
  5. 环绕通知(对应的注解是@Around):动态代理,我们可以直接手动推进
    目标方法运行(joinPoint.procced())

JoinPoint

通过JoinPoint可以获取参数列表和运行结果

  1. joinPoint.getArgs() 拿到参数列表,即目标方法运行需要的参数列表
  2. joinPoint.getSignature().getName() 方法名

@EnableAspectJAutoProxy注解的作用

在配置类上添加@EnableAspectJAutoProxy注解,便能够开启注解版的AOP
功能。使用@Import注解给容器中引入了AspectJAutoProxyRegister组件。
使用AspectJAutoProxyRegistrar对象自定义组件,并将相应的组件注册
到了IOC容器中。
注册就是拿到所有的 BeanPostProcessor ,然后调用 beanFactory 的
addBeanPostProcessor()将BeanPostProcessor注册到BeanFactory中

切入点与切面的区别?

切入点定义抽取方法在哪个位置执行,切面就是抽取方法切到切入点

AOP 有哪些实现方式?

实现 AOP 的技术,主要分为两大类:静态代理和动态代理

  1. 静态代理 指使用AOP 框架提供的命令进行编译,从而在编译阶段就可生
    成AOP 代理类,因此也称为编译时增强,编译时编织(特殊编译器实现)类
    加载时编织(特殊的类加载器实现)
  2. 动态代理 在运行时在内存中“临时”生成AOP 动态代理类,因此也被称为
    运行时增强
  • JDK 动态代理:通过反射来接收被代理的类,并且要求被代理的类必须实
    现一个接口。JDK 动态代理的核心是InvocationHandler 接口和Proxy 类
  • CGLIB动态代理: 如果目标类没有实现接口,那么Spring AOP 会选择
    使用CGLIB 来动态代理目标类。CGLIB 是一个代码生成的类库,可以在运
    行时动态的生成某个类的子类,CGLIB 是通过继承的方式做的动态代理,
    因此如果某个类被标记为final,那么它是无法使用CGLIB 做动态代理的
Author: 高明
Link: https://skysea-gaoming.github.io/2021/05/17/Spring%E6%80%BB%E7%BB%93/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.