Spring

月伴飞鱼 2024-10-12 12:52:33
框架相关
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

Bean作用域

image-20240114182133223

默认作用域是Singleton,多个线程访问同一个Bean时会存在线程不安全问题。

保障线程安全方法:

  • 在Bean对象中尽量避免定义可变的成员变量(不太现实)
  • 在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中。

启动初始化

@PostConstruct:

在Bean创建期间由 Spring 调用的初始化方法。

  • 使用:将 @PostConstruct 注解添加到方法上就行。

InitializingBean:

可以实现 InitializingBean 接口,让 Spring 调用某个初始化方法。

Controller和Service是线程安全的吗

默认情况下,Scope值是单例(Singleton)的,是线程不安全的。

尽量不要在@Controller/@Service等容器中定义静态变量,不论是单例(Singleton)还是多实例(Prototype)都是线程不安全的。

BeanFactoryPostProcessor 和 BeanPostProcessor

BeanFactoryPostProcessor:

  • 在 Bean 工厂实例化 Bean 之前对 Bean 的定义进行修改。

  • 可以读取和修改 Bean 的定义元数据,例如修改 Bean 的属性值、添加额外的配置信息等。

BeanPostProcessor:

  • 在 Bean 实例化后对 Bean 进行增强或修改。
  • 它可以在 Bean 的初始化过程中对 Bean 进行后处理,例如对 Bean 进行代理、添加额外的功能等。

Bean的生命周期

Spring的生命周期大致分为:创建 -> 属性填充 -> 初始化Bean -> 使用 -> 销毁 几个核心阶段。

创建阶段:

  • 主要是创建对象,对象的创建权交由Spring管理。

属性填充阶段:

  • 主要是进行依赖的注入,将当前对象依赖的bean对象,从Spring容器中找出来,然后填充到对应的属性中去。

初始化bean阶段:

  • 包括回调各种Aware接口、回调各种初始化方法、生成AOP代理对象也在该阶段进行,该阶段主要是完成初始化回调。

使用bean阶段:

  • 主要是bean创建完成,在程序运行期间,提供服务的阶段。

销毁bean阶段:

  • 主要是容器关闭或停止服务,对bean进行销毁处理。
image-20240114181239744

循环依赖

循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。

public class AService {
    private BService bService;
}

public class BService {
    private AService aService;
}

Bean的创建步骤:

在创建 Bean 之前,Spring 会通过扫描获取 BeanDefinition。

BeanDefinition就绪后会读取 BeanDefinition 中所对应的 class 来加载类。

实例化阶段:根据构造函数来完成实例化 (未属性注入以及初始化的对象(原始对象))。

属性注入阶段:对 Bean 的属性进行依赖注入。

如果 Bean 的某个方法有AOP操作,则需要根据原始对象生成代理对象

  • AOP代理是BeanPostProcessor实现的,而BeanPostProcessor是发生在属性注入阶段后的。

最后把代理对象放入单例池(一级缓存singletonObjects)中。

为什么 Spring Bean 会产生循环依赖问题?

AService 创建时,会先对 AService 实例化生成一个原始对象,然后在进行属性注入时发现了需要 BService 对应的 Bean,此时就会去为 BService 进行创建,在 BService 实例化后生成一个原始对象后进行属性注入,此时会发现也需要 AService 对应的 Bean。

image-20231014162629222

三大循环依赖问题场景:

单例作用域下的Setter方法注入/Field属性注入出现的循环依赖。

  • 在 Spring 中是不会产生循环依赖问题的,这主要是靠 三级缓存 机制解决。

单例作用域下的构造器注入出现的循环依赖。

  • 因为 构造器注入 发生在 实例化阶段,而 Spring 解决循环依赖问题依靠的 三级缓存属性注入阶段,也就是说调用构造函数时还未能放入三级缓存中,所以无法解决 构造器注入 的循环依赖问题。

原型作用域下的属性注入出现的循环依赖问题。

  • 因为 Spring 不会缓存 原型 作用域的 Bean,所以 Spring 无法解决 原型 作用域的 Bean。

三级缓存:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

一级缓存(singletonObjects):缓存的是已经实例化、属性注入、初始化后的 Bean 对象。

二级缓存(earlySingletonObjects):缓存的是实例化后,但未属性注入、初始化的 Bean对象(用于提前暴露 Bean)。

三级缓存(singletonFactories):缓存的是一个 ObjectFactory,主要作用是生成原始对象进行 AOP 操作后的代理对象(这一级缓存主要用于解决 AOP 问题)。

为什么缓存可以解决循环依赖问题?

在创建 AService 时,实例化后将 原始对象 存放到缓存中(提早暴露),然后依赖注入时发现需要 BService,便会去创建 BService,实例化后同样将 原始对象 存放到缓存中,然后依赖注入时发现需要 AService 便会从缓存中取出并注入,这样 BService 就完成了创建,随后 AService 也就能完成属性注入,最后也完成创建。

image-20231014163323751

为什么还需要第三级缓存?

第三级缓存(singletonFactories 是为了处理 Spring 的 AOP的。

如果 AService 中方法没有使用 AOP 操作,会发现 BService 注入的 原始对象 与最后 AService 完成创建后的最终对象是同一个对象

如果 AService 方法中有 AOP 操作时,当 AService 的原始对象赋值(注入)给 BServiceAService 会进行 AOP 操作产生一个 代理对象,这个代理对象最后会被放入单例池(一级缓存)中,也就是说此时 BService 中注入的对象是原始对象,而 AService 最终创建的完成后是代理对象,这样就会导致 BService 依赖的 AService 和 最终的 AService 不是同一个对象

出现这个问题原因:AOP 是通过 BeanPostProcessor 实现的,而 BeanPostProcessor 是在 属性注入阶段后 才执行的,所以会导致注入的对象有可能和最终的对象不一致

image-20231014163541994

Spring 是如何通过第三级缓存来避免 AOP 问题的?

三级缓存通过利用 ObjectFactorygetEarlyBeanReference() 做到了提前执行 AOP 操作从而生成代理对象。

在上移到二级缓存时,可以做到如果 Bean 中有 AOP 操作,那么提前暴露的对象会是 AOP 操作后返回的代理对象;如果没有 AOP 操作,那么提前暴露的对象会是原始对象。

  • 只有等完成了属性注入、初始化后的 Bean 才会上移到一级缓存(单例池)中。

这样就能做到出现循环依赖问题时,注入依赖的对象和最终生成的对象是同一个对象。

  • 相当于 AOP 提前在属性注入前完成,这样就不会导致后面生成的代理对象与属性注入时的对象的不一致。

设计模式

单例设计模式 :

  • Spring 中的 Bean 默认都是单例的。

⼯⼚设计模式 :

  • 通过 BeanFactory、 ApplicationContext 创建 Bean 对象。

代理设计模式 :

  • Spring AOP 功能的实现。

观察者模式:

  • Spring 事件驱动模型。

适配器模式:

  • Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、Spring MVC 中也是⽤到了适配器模式适配 Controller。

基本概念

IOC(控制反转)

Spring中有一个容器,我们将Bean放到这个容器中,让这个容器为我们创建实例,当需要时我们直接从这个容器中进行获取即可。

使用IOC最大的好处就是减少了代码的耦合度,降低了程序的维护成本。

img

实现原理:

Spring实现IOC容器的是通过:工厂 + 反射,实现的。

如果是基于全注解形式的话,只是将读取配置文件的步骤改成了读取配置类,然后通过配置类获取需要创建实现的Bean,并通过反射将其创建。

img

DI依赖注入

站在容器的角度,将对象创建依赖的其他对象注入到对象中。

AOP

AOP 面向切面的编程:在不改变源代码的情况下,实现对逻辑功能的修改。

  • 常用的场景包括记录日志、异常处理、性能监控、安全控制(例如拦截器)等。

AOP 原理:

动态的生成代理类,代理类的执行过程为:

  • 执行增加的代码(例如方法日志记录)-> 回调原方法 -> 增加的代码逻辑。

Spring AOP 动态代理采用 JDK 动态代理或 CGlib 动态生成代理类。

  • 判断被切面的类是否有其实现的接口,如果有对应的接口,则采用 JDK 动态代理,否则采用 CGlib 字节码生成机制动态代理方式。
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface()) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

SpringMVC

SpringMVC是一个基于Servlet容器的Web应用框架。

  • Servlet容器通常指Tomcat等服务容器。

Servlet容器会负责监听端口消息并映射为Request/Response对象,然后交给Servlet实例去处理。

SpringMVC框架的作用核心就是Servlet实例。

  • 这个实例在Spring中默认是DispatcherServlet。

Spring MVC结构图

客户端(浏览器)发送请求,直接请求到 DispatcherServlet。

DispatcherServlet 根据请求信息调⽤ HandlerMapping,解析请求对应的 Handler。

解析到对应的 Handler (也就是 Controller 控制器)后,开始由HandlerAdapter 适配器处理。

HandlerAdapter 会根据 Handler 来调⽤真正的处理器开处理请求,并处理相应的业务逻辑。

处理器处理完业务后,会返回⼀个 ModelAndView 对象, Model 是返回的数据对象。

ViewResolver 会根据逻辑 View 查找实际的 View。

DispaterServlet 把返回的 Model 传给 View(视图渲染)。

把 View 返回给请求者(浏览器)。

核心组件DispatcherServlet

在Spring容器启动阶段读取映射规则,如RequestMapping。

在请求到来的时候,按照加载的请求映射规则找到合适的处理方法。

当处理请求的过程中出现了异常,对异常进行处理,如返回合适的界面或状态码。

解析返回View的主题、时区等信息。

渲染返回的视图。

DispatcherServlet功能图

请求映射HandlerMapping

请求映射用于根据请求找到该请求需要调用的所有方法,包含过滤器和处理方法等。

比较常用的一种请求映射是RequestMappingHandlerMapping。

  • 从名称上可以看出来这个HandlerMapping是用于处理@RequestMapping注解请求映射的类。

该类的主要作用是在收到请求之后,按顺序拿到所有需要调用的方法(主要包含拦截器和处理方法),然后一一调用这些方法。

Handler Mapping原理

拦截器HandlerInterceptor

Spring的拦截器可以灵活的在请求处理前、请求处理后和请求完成三个阶段自定义操作,比如用户权限校验等。

Hadler Interceptor

处理方法Handler

处理方法在DispatcherServlet定义为Object类型,如果使用了@RequestMapping来根据请求查找处理方法,那么查找到的处理方法就是HandlerMethod类型,对应于Controller中添加了对应RequestMapping的方法。

处理方法适配器HandlerAdapter

DispatcherServlet从HandlerMapping中获取到的处理方法是Object类型,意味着不同的处理方法可能返回不同的对象。

DispatcherServlet本身是一个调度器,不应该关注如何调用不同的处理方法。

  • 所以Spring提供了HandlerAdapter列表用户处理不同的调度方法。

Hadler

异常处理HandlerExceptionResolver

HandlerExceptionResolver用于处理请求过程中出现的异常。

  • 日常开发中使用比较多的是ExceptionHandlerExceptionResolver,也就是@ExceptionHandler注解。

@ExceptionHander可以放在@ControllerAdvice注解的类中,表示对所有的Controller都会生效。

事务

嵌套事务

举例,methodA 中调用了 methodB,两个方法都有对数据库的操作,而且都需要事务。

这种多个方法调用链中都有事务的场景,就是嵌套事务。

要注意的是,并不是说多个方法使用一个事务才叫嵌套,哪怕是不同的事务,只要在这个方法的调用链中,都是嵌套事务。

// MethodA:
public void methodA(){
    int updated = connection.prepareStatement().executeUpdate();
    methodB();
    // ...
}

// MethodB:
public void methodB(){
    // ...
}

事务传播行为

方法调用链中的子方法,是用一个新事务,还是使用当前事务呢?

  • 这个子方法决定使用新事务还是当前事务(或不使用事务)的策略,就叫事务传播。
Spring传播行为 介绍
REQUIRED 支持当前事务,如果不存在,就新建一个
SUPPORTS 支持当前事务,如果不存在,就不使用事务
MANDATORY 支持当前事务,如果不存在,抛出异常
REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
NEVER 以非事务方式运行,如果有事务存在,抛出异常
NESTED 如果当前事务存在,则嵌套事务执行(嵌套式事务)

事务失效

1、事务方法被Final、Static关键字修饰:

如果一个方法被声明为final或者static,则该方法不能被子类重写

  • 也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。
@Service
public class ServiceImpl  {

    @Autowired
    private TestMapper1 mapper1;
    
    @Autowired
    private TestMapper2 mapper2;

    @Transactional
    public final void add(Test test) {
        mapper1.save();
        mapper2.save();
    }
}

2、同一个类中,方法内部调用:

事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时

  • 调用方法直接调用目标方法的代码,而不是通过代理类进行调用
    • 即调用目标add方法不是通过代理类进行的,因此事务不生效。

解决方案:可以新建多一个类,让这两个方法分开,分别在不同的类中。

@Service
public class ServiceImpl implements Service {

    @Autowired
    private TestMapper1 mapper1;
    
    @Autowired
    private TestMapper2 mapper2;
    
    public void add(Test test){
     // 调用内部的事务方法
     this.add(test);
   }

    @Transactional
    public void add(Test test) {
        mapper1.save();
        mapper2.save();
    }
}

3、方法的访问权限不是Public:

代理的事务方法不是public的话,springcomputeTransactionAttribute()就会返回null

  • 这时事务属性不存在了。

4、RollbackFor属性配置错误:

rollbackFor属性指定的异常必须是Throwable或者其子类。

  • 默认情况下,RuntimeExceptionError两种异常都是会自动回滚的。

代码例子:

  • 指定了rollbackFor = Error.class,但是抛出的异常又是Exception,因此事务就不生效。
    @Transactional(rollbackFor = Error.class)
    public void add(Test test) {
        mapper1.save(test);
        mapper2.save(test);
        //模拟异常抛出
        throw new Exception();
    }

5、事务注解被覆盖导致事务失效:

MyTestServiceMyService的子类,并且覆盖了doSomething()方法。

  • 在该方法中,使用了不同的传播行为(REQUIRES_NEW)来覆盖父类的@Transactional注解。

在这种情况下,当调用MyTestServicedoSomething()方法时

  • 由于子类方法中的注解覆盖了父类的注解,Spring框架将不会在父类的方法中启动事务。

因此,当MyRepositorysave()方法被调用时,事务将不会被启动,也不会回滚。

  • 这将导致数据不一致的问题,因为在MyRepositorysave()方法中进行的数据库操作将不会回滚。
public interface MyRepository {
    @Transactional
    void save(String data);
}

public class MyRepositoryImpl implements MyRepository {
    @Override
    public void save(String data) {
        // 数据库操作
    }
}

public class MyService {

    @Autowired
    private MyRepository myRepository;

    @Transactional
    public void doSomething(String data) {
        myRepository.save(data);
    }
}

public class MyTestService extends MyService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void doSomething(String data) {
        super.doSomething(data);
    }
}

6、嵌套事务:

如果saveFlow出现运行时异常,会继续往上抛,到外层add的方法

  • 导致mapper.save也会回滚啦。

如果不想因为被内部嵌套的事务影响,可以用try-catch包住。

@Service
public class ServiceInOutService {

    @Autowired
    private FlowService flowService;
    @Autowired
    private Mapper mapper;

    @Transactional
    public void add(Test test) throws Exception {
        mapper.save(test);
        flowService.saveFlow(test);
    }
}

@Service
public class FlowService {

    @Autowired
    private Mapper mapper;

    @Transactional(propagation = Propagation.NESTED)
    public void saveFlow(Test test) {
        mapper.save(test);
        throw new RuntimeException();
    }
}
@Transactional
public void add(Test test) throws Exception {
  mapper.save(test);
  try {
    flowService.saveFlow(test);
  } catch (Exception e) {
    log.error("save flow fail,message:{}",e.getMessage());
  }
}

7、事务多线程调用:

Spring事务是基于线程绑定的,每个线程都有自己的事务上下文

  • 而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务失效。

Spring事务管理器通过使用线程本地变量(ThreadLocal)来实现线程安全。

@Service
public class Service {

    @Autowired
    private Mapper1 mapper1;

    @Mapper1
    private FlowService flowService;

    @Transactional
    public void add(Test test) {
        mapper1.save(test);
        //多线程调用
        new Thread(() -> {
            flowService.save(test);
        }).start();
    }
}

@Service
public class FlowService {

    @Autowired
    private Mapper1 mapper1;

    @Transactional
    public void save(Test test) {
        mapper1.saveFlow(test);
    }
}

8、异常被捕获并处理了,没有重新抛出:

事务中的异常已经被业务代码捕获并处理

  • 而没有被正确地传播回事务管理器,事务将无法回滚。

spring事务方法中,当我们使用了try-catchcatch住异常,一定要重新把异常抛出来。

@Service
public class ServiceImpl implements Service {

    @Autowired
    private Mapper1 mapper1;

    @Autowired
    private Mapper2 mapper2;

    @Transactional
    public void add(Test test) {
        try {
            mapper1.save();
            mapper2.save();
        } catch (Exception e) {
            log.error("add error,id:{},message:{}", tianluo.getId(),e.getMessage());
            //throw e;
        }
    }

}

9、手动抛了别的异常:

如果手动抛了Exception异常,但是是不会回滚的

  • 因为Spring默认只处理RuntimeException和Error,对于普通的Exception不会回滚
    • 除非,用rollbackFor属性指定配置。

@Transactional加不加rollbackFor=Exception.class的区别

@Transactional只能回滚RuntimeExceptionRuntimeException下面的子类抛出的异常,不能回滚Exception异常。

如果需要支持回滚Exception异常请用@Transactional(rollbackFor = Exception.class)

这里如果是增删改的时候我建议都使用@Transactional(rollbackFor = Exception.class)

SpringBoot

Spring Boot Starter

实现一个Starter:

  • Starter 命名。
  • 自动配置类,用来初始化相关的 bean。
  • 指明自动配置类的配置文件 spring.factories
  • 自定义属性实体类,声明 starter 的应用配置属性。

名字:

官方的 starter 的命名格式为 spring-boot-starter-{name}

非官方的 starter 的命名格式为 {name}-spring-boot-starter,把自定义的 starter 命名为 fish-spring-boot-starter,命名在 pom 文件里。

<groupId>fish.springcloud</groupId>
<artifactId>fish-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>

引入自动配置包及其它相关依赖包:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

创建 spring.factories 文件:

resource/META-INF 目录下创建名称为 spring.factories 的文件。

当 Spring Boot 启动的时候,会在 classpath 下寻找所有名称为 spring.factories 的文件,然后运行里面的配置指定的自动加载类,将指定类(一个或多个)中的相关 bean 初始化。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  kite.springcloud.boot.starter.example.FishAutoConfigure

编写自动配置类:

自动配置类是用来初始化 starter 中的相关 bean 的。

@Configuration
@ConditionalOnClass(FishService.class)
@EnableConfigurationProperties(FishProperties.class)
@Slf4j
public class FishAutoConfigure {

    @Autowired
    private FishProperties fishProperties;

    @Bean
    @ConditionalOnMissingBean(FishService.class)
    @ConditionalOnProperty(prefix = "fish.example",value = "enabled", havingValue = "true")
    FishService fishService(){
        return new FishService(fishProperties);
    }
}

实现属性配置类:

@Data
@ConfigurationProperties("fish.example")
public class FishProperties {

    private String host;

    private int port;
}

实现相关功能类:

@Slf4j
public class FishService {

    private String host;

    private int port;

    public FishService(FishProperties fishProperties){
        this.host = fishProperties.getHost();
        this.port = fishProperties.getPort();
    }

    public void print(){
        log.info(this.host + ":" +this.port);
    }
}

打包:

通过 maven mvn install命令将 starter 安装到本地 maven 仓库。

也可以通过 mvn package deploy 发布到私服。或者发布到中央仓库。

自动配置原理

Spring启用全自动配置功能的注解就是@EnableAutoConfiguration,应用添加了@EnableAutoConfiguration注解之后,会读取所有jar包下面的spring.factories文件,获取文件中配置的自动装配模块,然后去装配对应的模块。

@EnableAutoConfiguration的功能可总结为:

  • 使Spring启用factories机制导入各个starter模块的配置。

启动流程

构造SpringBootApplication对象和执行run方法

核心注解@SpringBootConfiguration标识启动类为配置类。

@EnableAutoConfiguration通过内部@Import注解AutoConfigurationImportSelector.class实现自动装配。

@ComponentScan默认扫描当前目录及子目录下的Bean。

SpringBootApplication的构造方法主要做了几件事:

  • 根据是否加载servlet类判断是否是web环境。
  • 获取所有初始化器,扫描所有META-INF/spring.factories下的ApplicationContextInitializer子类通过反射拿到实例,在spring实例启动前后做一些回调工作。
  • 获取所有监听器,同2,也是扫描配置加载对应的类实例。

run方法主要创建了配置环境、事件监听、启动应用上下文,其中refresh方法贯穿springbean的生命周期。

执行bean的生命周期的前后置钩子方法,并且处理spring的注解标注的类。

onRefresh中通过Java代码构建出tomcat容器并启动。

img

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!