Spring、SpringBoot总结

Spring:1-8
SpringBoot: 9-18
Spring设计模式总结:19
SpringBoot自动装配原理:20

1.Spring模块简介

在这里插入图片描述

  • Spring Core
    核心模块, Spring 其他所有的功能基本都需要依赖于该模块,主要提供 IoC 依赖注入功能的支持。

  • Spring Aspects
    该模块为与 AspectJ 的集成提供支持。

  • Spring AOP
    提供了面向切面的编程实现。

  • Spring Data Access/Integration
    Spring Data Access/Integration 由以下5个模块组成:
    1、spring-jdbc : 提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。
    2、spring-tx : 提供对事务的支持。
    3、spring-orm : 提供对 Hibernate 等 ORM 框架的支持。
    4、spring-oxm : 提供对 Castor 等 OXM 框架的支持。
    5、spring-jms : Java 消息服务。

  • Spring Web
    Spring Web 由 4 个模块组成:
    1、spring-web :对 Web 功能的实现提供一些最基础的支持。
    2、spring-webmvc : 提供对 Spring MVC 的实现。
    3、spring-websocket : 提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。
    4、spring-webflux :提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步。

  • SpringTest
    Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。
    Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题比如无法模拟 final, static, private 方法)等等常用的测试框架支持的都比较好。


2. Spring IOC

IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。

为什么叫控制反转?

  • 控制 :指的是对象创建(实例化、管理)的权力
  • 反转 :控制权交给外部环境(Spring 框架、IoC 容器)

在这里插入图片描述
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。

在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。


3. Spring AOP

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
在这里插入图片描述

3.1 Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很。


4. Spring Bean

简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
在这里插入图片描述
org.springframework.beansorg.springframework.context这两个包是 IoC 实现的基础。

4.1 将一个类声明为Bean的注解有哪些?

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

4.2 @Component 和 @Bean 的区别是什么?

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

@Bean 使用示例:

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

4.3 注入Bean的注解有哪些?

Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。

AnnotaionPackageSource
@Autowiredorg.springframework.bean.factorySpring 2.5+
@Resourcejavax.annotationJava JSR-250
@Injectjavax.injectJava JSR-330

@Autowired 和@Resource使用的比较多一些。

4.4 @Autowired 和@Resource的区别是什么?

1、Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)

这会有什么问题呢? 当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。

这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。

举个栗子:
SmsService 接口有两个实现类: SmsServiceImpl1和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。

// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;

// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;

// 正确注入  SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;

我们还是建议通过 @Qualifier注解来显示指定名称而不是依赖变量的名称。

2、@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。

@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。

public @interface Resource {
    String name() default "";
    Class<?> type() default Object.class;
}

如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和 type属性(不建议这么做)则注入方式为byType+byName。

// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;

// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;

// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;

总结:

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
  • Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。
  • 当一个接口存在多个实现类的情况下,@Autowired 和@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显示指定名称,@Resource可以通过 name 属性来显示指定名称。

4.5 Bean的作用域有哪些?

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的,对单例设计模式的应用。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
  • global-session : 全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。

配置bean的作用域:
xml方式:

<bean id="..." class="..." scope="singleton"></bean>

注解方式:

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

4.6 单例 Bean 的线程安全问题

单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。

常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。

4.7 Bean的生命周期

在这里插入图片描述
Bean的生命周期,主要步骤为:

  1. 实例化Bean:通过反射调用构造方法实例化对象。
  2. 依赖注入:装配Bean的属性
  3. 实现了Aware接口的Bean,执行接口方法:如顺序执行BeanNameAware、BeanFactoryAware、
    ApplicationContextAware的接口方法。
  4. Bean对象初始化前,循环调用实现了BeanPostProcessor接口的预初始化方法
    (postProcessBeforeInitialization)
  5. Bean对象初始化:顺序执行@PostConstruct注解方法、InitializingBean接口方法、init-method
    方法
  6. Bean对象初始化后,循环调用实现了BeanPostProcessor接口的后初始化方法
    (postProcessAfterInitialization)
  7. 容器关闭时,执行Bean对象的销毁方法,顺序是:@PreDestroy注解方法、DisposableBean接口
    方法、destroy-method

5. Spring MVC

MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
在这里插入图片描述
MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

5.1 工作原理

在这里插入图片描述

  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
  2. DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
  3. 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
  4. HandlerAdapter 会根据 Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
  6. ViewResolver 会根据逻辑 View 查找实际的 View。
  7. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  8. 把 View 返回给请求者(浏览器)

6. Spring框架中用了哪些设计模式?

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

7. Spring事务总结

事务是逻辑上的一组操作,要么都执行,要么都不执行。

另外,需要格外注意的是:事务能否生效数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的 innodb引擎。但是,如果把数据库引擎变为 myisam,那么程序也就不再支持事务了!

事务的几大特性:

  • 原子性(Atomicity): 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
  • 一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
  • 隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

7.1 Spring管理事务的方式有几种

  • 编程式事务 : 在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
  • 声明式事务 : 在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

7.2 Spring事务的传播行为有几种

事务传播行为是为了解决业务层方法之间互相调用的事务问题。

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了。

Class A {
    @Transactional(propagation = Propagation.xxx)
    public void aMethod {
        //do something
        B b = new B();
        b.bMethod();
    }
}

Class B {
    @Transactional(propagation = Propagation.xxx)
    public void bMethod {
       //do something
    }
}

正确的事务传播行为可能的如下4种:

7.2.1 PROPAGATION_REQUIRED

TransactionDefinition.PROPAGATION_REQUIRED 使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

  • 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  • 如果外部方法开启事务并且被Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。

举个例子:如果我们上面的aMethod()和bMethod()使用的都是PROPAGATION_REQUIRED传播行为的话,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。

Class A {
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod {
        //do something
        B b = new B();
        b.bMethod();
    }
}

Class B {
    @Transactional(propagation = Propagation.REQUIRED)
    public void bMethod {
       //do something
    }
}

7.2.2 PROPAGATION_REQUIRES_NEW

TransactionDefinition.PROPAGATION_REQUIRES_NEW是指创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

举个例子:如果我们上面的bMethod()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰,aMethod还是用PROPAGATION_REQUIRED修饰的话。如果aMethod()发生异常回滚,bMethod()不会跟着回滚,因为 bMethod()开启了独立的事务。但是,如果 bMethod()抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod()同样也会回滚,因为这个异常被 aMethod()的事务管理机制检测到了。

Class A {
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod {
        //do something
        B b = new B();
        b.bMethod();
    }
}

Class B {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void bMethod {
       //do something
    }
}

7.2.3 PROPAGATION_NESTED

TransactionDefinition.PROPAGATION_NESTED是指如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition. PROPAGATION_REQUIRED的类似的操作。

  • 在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
  • 如果外部方法无事务,则单独开启一个事务,与 PROPAGATION_REQUIRED 类似。

这里还是简单举个例子:如果 bMethod() 回滚的话,aMethod()也会回滚。

Class A {
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod {
        //do something
        B b = new B();
        b.bMethod();
    }
}

Class B {
    @Transactional(propagation = Propagation.NESTED)
    public void bMethod {
       //do something
    }
}

7.2.4 PROPAGATION_MANDATORY

TransactionDefinition.PROPAGATION_MANDATORY是指:
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

7.2.5 其他不常用传播行为

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚:

  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

7.3 Spring事务中的隔离级别

和事务传播行为这块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation

public enum Isolation {

    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

    private final int value;

    Isolation(int value) {
        this.value = value;
    }

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

7.4 @Transactional(rollbackFor = Exception.class)注解

Exception 分为运行时异常 RuntimeException 和非运行时异常。

当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

在 @Transactional 注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。

7.5 Spring事务管理接口

Spring框架中,事务管理相关的最重要的3个接口是:

  • PlatformTransactionManager: (平台)事务管理器,Spring 事务策略的核心。
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
  • TransactionStatus: 事务运行状态。

PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等

7.5.1 PlatformTransactionManager:事务管理接口

Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是: PlatformTransactionManager 。

通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

7.5.2 TransactionDefinition:事务属性

事务管理器接口 PlatformTransactionManager通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类 ,这个类就定义了一些基本的事务属性。

什么是事务属性呢? 事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。

事务属性包含了 5 个方面:

  • 隔离级别
  • 传播行为
  • 回滚规则
  • 是否只读
  • 事务超时

TransactionDefinition 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    // 返回事务的传播行为,默认值为 REQUIRED。
    int getPropagationBehavior();
    //返回事务的隔离级别,默认值是 DEFAULT
    int getIsolationLevel();
    // 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    int getTimeout();
    // 返回是否为只读事务,默认值为 false
    boolean isReadOnly();

    @Nullable
    String getName();
}

7.5.3 TransactionStatus:事务状态

TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。

PlatformTransactionManager.getTransaction(…)方法返回一个 TransactionStatus 对象。

内容如下:

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事务
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
}

7.6 事务超时属性

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间。

7.7 事务只读属性

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

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

7.8 事务回滚规则

这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。

如果你想要回滚你定义的特定的异常类型的话,可以这样:

@Transactional(rollbackFor= MyException.class)

8. Spring循环依赖问题

从字面上来理解循环依赖就是A依赖B的同时B也依赖了A,就像下面这样
在这里插入图片描述
在代码层面体现:

@Component
public class A {
    // A中注入了B
	@Autowired
	private B b;
}

@Component
public class B {
    // B中也注入了A
	@Autowired
	private A a;
}

8.1 解决循环依赖的具体过程

Spring通过三级缓存的机制来解决循环依赖问题。
在这里插入图片描述

(1)singletonObjects:一级缓存,存储的是所有创建好了的单例Bean;
(2)earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象
(3)singletonFactories,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

循环依赖解决的过程:
在这里插入图片描述

1、实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行,A 只是一个半成品。
2、为 A 创建一个 Bean工厂,并放入到 singletonFactories 中。
3、发现 A 需要注入 B 对象,但是一级、二级、三级缓存均为发现对象 B。
4、实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行,B 只是一个半成品。
5、为 B 创建一个 Bean工厂,并放入到 singletonFactories 中。
6、发现 B 需要注入 A 对象,此时在一级、二级未发现对象A,但是在三级缓存中发现了对象 A,从三级缓存中得到对象 A,并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(注意,此时的 A还是一个半成品,并没有完成属性填充和执行初始化方法)
7、将对象 A 注入到对象 B 中。
8、对象 B 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 已经是一个成品)
9、对象 A 得到对象B,将对象 B 注入到对象 A 中。(对象 A 得到的是一个完整的对象 B)
对象 A完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 A。

8.2 Spring如何解决的循环依赖?

Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

8.3 为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

如果 Spring 选择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都需要在实例化完成之后就立马为其创建代理,而Spring 的设计原则是在 Bean 初始化完成之后才为其创建代理。所以,Spring 选择了三级缓存的工厂来获取被代理后的对象。因为循环依赖的出现,导致了 Spring 不得不提前去创建代理,因为如果不提前创建代理对象,那么注入的就是原始对象,这样就会产生错误。


9.SpringBoot常用注解

9.1 @SpringBootApplication

这个注解是 Spring Boot 项目的基石,创建 SpringBoot 项目之后会默认在主类加上。

@SpringBootApplication
public class SpringSecurityJwtGuideApplication {
      public static void main(java.lang.String[] args) {
        SpringApplication.run(SpringSecurityJwtGuideApplication.class, args);
    }
}

可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan注解的集合。

这三个注解的作用是:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包及其包下所有的类。
  • @Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类。

9.2 @Component,@Repository,@Service, @Controller

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

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

9.3 @RestController

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

单独使用 @Controller 不加 @ResponseBody的话一般是用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。

9.4 @Scope

用来声明Spring Bean的作用域:

@Bean
@Scope("singleton")
public Person personSingleton() {
    return new Person();
}

四种常见的 Spring Bean 的作用域:

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

9.5 @Configuration

一般用来声明配置类,可以使用 @Component注解替代,不过使用@Configuration注解声明配置类更加语义化。

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

10.常见的HTTP请求注解

  • GET :请求从服务器获取特定资源。举个例子:GET /users(获取所有学生)
  • POST :在服务器上创建一个新的资源。举个例子:POST /users(创建学生)
  • PUT :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /users/12(更新编号为 12 的学生)
  • DELETE :从服务器删除特定的资源。举个例子:DELETE /users/12(删除编号为 12 的学生)

10.1 GET请求

@GetMapping(“users”) 等价于@RequestMapping(value="/users",method=RequestMethod.GET)

@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() {
 return userRepository.findAll();
}

10.2 POST请求

@PostMapping(“users”) 等价于@RequestMapping(value="/users",method=RequestMethod.POST)

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) {
 return userRespository.save(userCreateRequest);
}

10.3 PUT请求

@PutMapping(“/users/{userId}”)等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)

@PutMapping("/users/{userId}")
public ResponseEntity<User> updateUser(@PathVariable(value = "userId") Long userId,
  @Valid @RequestBody UserUpdateRequest userUpdateRequest) {
  ......
}

10.4 DELETE请求

@DeleteMapping(“/users/{userId}”) 等价于
@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)

@DeleteMapping("/users/{userId}")
public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){
  ......
}

11. 前后端传值注解

11.1 @PathVariable 和 @RequestParam

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

@GetMapping("/klasses/{klassId}/teachers")
public List<Teacher> getKlassRelatedTeachers(
         @PathVariable("klassId") Long klassId,
         @RequestParam(value = "type", required = false) String type ) {
...
}

如果我们请求的 url 是:/klasses/123456/teachers?type=web
那么我们服务获取到的数据就是:klassId=123456,type=web

11.2 @RequestBody

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

系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。

示例:

@PostMapping("/sign-up")
public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) {
  userService.save(userRegisterRequest);
  return ResponseEntity.ok().build();
}

UserRegisterRequest对象:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterRequest {
    @NotBlank
    private String userName;
    @NotBlank
    private String password;
    @NotBlank
    private String fullName;
}

我们发送 post 请求到这个接口,并且 body 携带 JSON 数据:

{"userName":"coder","fullName":"shuangkou","password":"123456"}

这样我们的后端就可以直接把 json 格式的数据映射到我们的 UserRegisterRequest 类上。
在这里插入图片描述
👉 需要注意的是:一个请求方法只可以有一个@RequestBody,但是可以有多个@RequestParam和@PathVariable。


12. 读取配置信息的注解

比如我们在application.yml中的内容如下:

wuhan2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,但是,我相信一切都会过去!武汉加油!中国加油!

my-profile:
  name: Jared
  email: Jared@163.com

library:
  location: 湖北武汉加油中国加油
  books:
    - name: 天才基本法
      description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。
    - name: 时间的秩序
      description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。
    - name: 了不起的我
      description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻?

12.1 @Value(常用)

使用 @Value(“${property}”) 读取比较简单的配置信息,其中property:

@Value("${wuhan2020}")
String wuhan2020;

12.2 @ConfigurationProperties(常用)

通过@ConfigurationProperties读取配置信息并与 bean 绑定。

@Component
@ConfigurationProperties(prefix = "library")
class LibraryProperties {
    @NotEmpty
    private String location;
    private List<Book> books;

    @Setter
    @Getter
    @ToString
    static class Book {
        String name;
        String description;
    }
  省略getter/setter
  ......
}

12.3 @PropertySource(不常用)

@PropertySource读取指定 properties 文件

@Component
@PropertySource("classpath:website.properties")

class WebSite {
    @Value("${url}")
    private String url;

  省略getter/setter
  ......
}

13. 参数校验注解

13.1 常用的字段验证的注解

  • @NotEmpty 被注释的字符串的不能为 null 也不能为空
  • @NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符
  • @Null 被注释的元素必须为 null
  • @NotNull 被注释的元素必须不为 null
  • @AssertTrue 被注释的元素必须为 true
  • @AssertFalse 被注释的元素必须为 false
  • @Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式
  • @Email 被注释的元素必须是 Email 格式。
  • @Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max=, min=)被注释的元素的大小必须在指定的范围内
  • @Digits(integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
  • @Past被注释的元素必须是一个过去的日期
  • @Future 被注释的元素必须是一个将来的日期

13.2 验证请求体(RequestBody)

我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException。

@RestController
@RequestMapping("/api")
public class PersonController {

    @PostMapping("/person")
    public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
        return ResponseEntity.ok().body(person);
    }
}

13.3 验证请求参数(Path Variables 和 Request Parameters)

一定不要忘记在类上加上 @Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。

@RestController
@RequestMapping("/api")
@Validated
public class PersonController {

    @GetMapping("/person/{id}")
    public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
        return ResponseEntity.ok().body(id);
    }
}

14.全局处理Controller层异常注解

相关注解:

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

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    /**
     * 请求参数异常处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
       ......
    }
}

15. JPA相关注解

15.1创建表

@Entity声明一个类对应一个数据库实体。

@Table 设置表名

@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    // 省略getter/setter......
}

15.2 创建主键

@Id :声明一个字段为主键。

使用@Id声明之后,我们还需要定义主键的生成策略。我们可以使用 @GeneratedValue 指定主键生成策略。

15.3 设置字段类型

@Column 声明字段

设置属性 userName 对应的数据库字段名为 user_name,长度为 32,非空

@Column(name = "user_name", nullable = false, length=32)
private String userName;

设置字段类型并且加默认值

@Column(columnDefinition = "tinyint(1) default 1")
private Boolean enabled;

15.4 指定不持久化特定字段

@Transient :声明不需要与数据库映射的字段,在保存的时候不需要保存进数据库 。

如果我们想让secrect 这个字段不被持久化,可以使用 @Transient关键字声明。

@Entity(name="USER")
public class User {
    ......
    @Transient
    private String secrect; // not persistent because of @Transient
}

除了 @Transient关键字声明, 还可以采用下面几种方法:

static String secrect; // not persistent because of static
final String secrect = "Satish"; // not persistent because of final
transient String secrect; // not persistent because of transient

15.5 创建枚举类型的字段

可以使用枚举类型的字段,不过枚举字段要用@Enumerated注解修饰。

public enum Gender {
    MALE("男性"),
    FEMALE("女性");

    private String value;
    Gender(String str){
        value=str;
    }
}
@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    
    @Enumerated(EnumType.STRING)
    private Gender gender;
    // 省略getter/setter......
}

数据库里面对应存储的是 MALE/FEMALE。

15.6 修改/删除数据

@Modifying 注解提示 JPA 该操作是修改操作,注意还要配合@Transactional注解使用。

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

    @Modifying
    @Transactional(rollbackFor = Exception.class)
    void deleteByUserName(String userName);
}

15.7 关联关系

  • @OneToOne 声明一对一关系
  • @OneToMany 声明一对多关系
  • @ManyToOne 声明多对一关系
  • @MangToMang 声明多对多关系

16. @Transactional注解

在要开启事务的方法上使用@Transactional注解即可

16.1 @Transactional 的作用范围

  1. 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
  2. 类: 如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
  3. 接口: 不推荐在接口上使用。

注意:

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。
  • 作用于方法: 当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
@Transactional(rollbackFor = Exception.class)
public void save() {
  ......
}

我们知道 Exception 分为运行时异常 RuntimeException 和非运行时异常。在@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor = Exception.class,可以让事务在遇到非运行时异常时也回滚。

16.2 @Transactional的常用配置参数

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

16.3 @Transactional 事务注解原理

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

如果一个类或者一个类中的 public 方法上被标注@Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

16.4 @Transactional 注解失效情况

若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。

这是由于Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。

MyService 类中的method1()调用method2()就会导致method2()的事务失效。

@Service
public class MyService {

private void method1() {
     method2();
     //......
}
@Transactional
 public void method2() {
     //......
  }
}

16.5 总结

  • @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
  • 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
  • 正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败;
  • 被 @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
  • 底层使用的数据库必须支持事务机制,否则不生效;

17. json数据处理注解

17.1过滤 json 数据

@JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析。

//生成json时将userRoles属性过滤
@JsonIgnoreProperties({"userRoles"})
public class User {
    private String userName;
    private String fullName;
    private String password;
    private List<UserRole> userRoles = new ArrayList<>();
}

@JsonIgnore一般用于类的属性上,作用和上面的@JsonIgnoreProperties 一样。

public class User {
    private String userName;
    private String fullName;
    private String password;
    
   //生成json时将userRoles属性过滤
    @JsonIgnore
    private List<UserRole> userRoles = new ArrayList<>();
}

17.2 格式化json数据

@JsonFormat一般用来格式化 json 数据。

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
private Date date;

18. 测试相关注解

@ActiveProfiles一般作用于测试类上, 用于声明生效的 Spring 配置文件。

@SpringBootTest(webEnvironment = RANDOM_PORT)
@ActiveProfiles("test")
@Slf4j
public abstract class TestBase {
  ......
}

@Test声明一个方法为测试方法
@Transactional被声明的测试方法的数据会回滚,避免污染测试数据。
@WithMockUser Spring Security 提供的,用来模拟一个真实用户,并且可以赋予权限。


19. Spring设计模式总结

19.1 依赖注入模式

oC(Inversion of Control,控制反转) 是Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。它的主要目的是借助于“第三方”(Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦(IOC容器管理对象,你只管使用即可),从而降低代码之间的耦合度。IOC 是一个原则,而不是一个模式,以下模式(但不限于)实现了IoC原则。

控制反转怎么理解呢? 举个例子:“对象a 依赖了对象 b,当对象 a 需要使用 对象 b的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b的时候, 我们可以指定 IOC 容器去创建一个对象b注入到对象 a 中”。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权反转,这就是控制反转名字的由来。

DI(Dependecy Inject,依赖注入)是实现控制反转的一种设计模式,依赖注入就是将实例变量传入到一个对象中去。

19.2 工厂设计模式

Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象。

两者对比:

  • BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。
  • ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持, ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用 ApplicationContext会更多。

ApplicationContext的三个实现类:
1、ClassPathXmlApplication:把上下文文件当成类路径资源。
2、FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。
3、XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息。

例子:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
 
public class App {
	public static void main(String[] args) {
		ApplicationContext context = new FileSystemXmlApplicationContext(
				"C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");
 
		HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
		obj.getMsg();
	}
}

19.3 单例设计模式

在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。

使用单例模式的好处:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

Spring 中 bean 的默认作用域就是 singleton(单例)的。 除了 singleton 作用域,Spring 中 bean 还有下面几种作用域:

  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  • session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
  • global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话

Spring 实现单例的方式:

  • xml : <bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
  • 注解:@Scope(value = "singleton")

Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式。Spring 实现单例的核心代码如下

// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            // 检查缓存中是否存在实例  
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                //...省略了很多代码
                try {
                    singletonObject = singletonFactory.getObject();
                }
                //...省略了很多代码
                // 如果实例对象在不存在,我们注册到单例注册表中。
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }
    //将对象添加到单例注册表
    protected void addSingleton(String beanName, Object singletonObject) {
            synchronized (this.singletonObjects) {
                this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

            }
        }
}

19.4 代理设计模式

代理模式应用在 AOP 中。

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
在这里插入图片描述


19.5 模板方法

模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。

public abstract class Template {
    //这是我们的模板方法
    public final void TemplateMethod(){
        PrimitiveOperation1();  
        PrimitiveOperation2();
        PrimitiveOperation3();
    }

    protected void  PrimitiveOperation1(){
        //当前类实现
    }
    
    //被子类实现的方法
    protected abstract void PrimitiveOperation2();
    protected abstract void PrimitiveOperation3();

}
public class TemplateImpl extends Template {

    @Override
    public void PrimitiveOperation2() {
        //当前类实现
    }
    
    @Override
    public void PrimitiveOperation3() {
        //当前类实现
    }
}

Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。

19.6 观察者模式

观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。

19.7 适配器模式

适配器模式(Adapter Pattern) 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。

19.7.1 spring AOP中的适配器模式

我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter。
Advice 常用的类型有:BeforeAdvice(目标方法调用前,前置通知)、AfterAdvice(目标方法调用后,后置通知)、AfterReturningAdvice(目标方法执行结束后,return之前)等等。每个类型Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter、AfterReturningAdviceInterceptor。Spring预定义的通知要通过对应的适配器,适配成 MethodInterceptor接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)。

19.7.2 spring MVC中的适配器模式

在Spring MVC中,DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式? Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,

19.8 装饰者模式

装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。

其实在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。

在这里插入图片描述
Spring 框架中用到了哪些设计模式?

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

20. SpringBoot自动装配原理

自动装配可以说是 Spring Boot 的核心,自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。

Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值