Spring

  1. spring
    1. spring常用注解
    2. Spring 是如何解决循环依赖的?
    3. Spring 事务的传播⾏为有⼏种
    4. spring AOP
    5. Spring 声明式事务在那些情况下会失效
    6. spring mvc的工作流程

spring

spring常用注解

  • @Component :标准一个普通的spring Bean类。
  • @Repository:标注一个DAO组件类。
  • @Service:标注一个业务逻辑组件类。
  • @Controller:标注一个控制器组件类
  • @Autowired:属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性. 构造器. 方法进行注值 ,结合@Qualifier指定注入的bean名称
  • @Resource:不属于spring的注解,而是来自于JSR-250位于java.annotation包下,使用该annotation为目标bean指定协作者Bean。
  • @PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作

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

什么样的依赖算是循环依赖?

用过 Spring 框架的人都对依赖注入这个词不陌生,一个 Java 类 A 中存在一个属性是类 B 的一个对象,那么我们就说类 A 的对象依赖类 B,而在 Spring 中是依靠的 IOC 来实现的对象注入,也就是说创建对象的过程是 IOC 容器来实现的,并不需要自己在使用的时候通过 new 关键字来创建对象。
那么当类 A 中依赖类 B 的对象,而类 B 中又依赖类 C 的对象,最后类 C 中又依赖类 A 的对象的时候,这种情况最终的依赖关系会形成一个环,这就是循环依赖。

循环依赖的类型
根据注入的时机可以分为两种:

  • 构造器循环依赖
    依赖的对象是通过构造方法传入的,在实例化 bean 的时候发生。
  • 赋值属性循环依赖
    依赖的对象是通过 setter 方法传入的,对象已经实例化,在属性赋值和依赖注入的时候发生。

构造器循环依赖,本质上是无解的,实例化 A 的时候调用 A 的构造器,发现依赖了 B,又去实例化 B,然后调用 B 的构造器,发现又依赖的 C,然后调用 C 的构造器去实例化,结果发起 C 的构造器里依赖了 A,这就是个死循环无解。所以 Spring 也是不支持构造器循环依赖的,当发现存在构造器循环依赖时,会直接抛出 BeanCurrentlyInCreationException 异常。

// 构造器注入
class ServiceA {

    private ServiceB serviceB;

    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

class ServiceB {
    private ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

class Constructor {
    public static void main(String[] args) {
        // 编译直接报错 
        new ServiceA(new ServiceB(new ServiceB(还需要依赖)));
    }
}
class ServiceSetA {

    private ServiceSetB serviceSetB;

    public void setServiceSetB(ServiceSetB serviceSetB) {
        this.serviceSetB = serviceSetB;
    }
}

class ServiceSetB {
    private ServiceSetA serviceSetA;

    public void setServiceSetA(ServiceSetA serviceSetA) {
        this.serviceSetA = serviceSetA;
    }
}

class SetMethod {
    // 执行成功
    public static void main(String[] args) {
        ServiceSetA serviceSetA = new ServiceSetA();
        ServiceSetB serviceSetB = new ServiceSetB();
        serviceSetA.setServiceSetB(serviceSetB);
        serviceSetB.setServiceSetA(serviceSetA);
    }
}

赋值属性循环依赖,Spring 只支持 bean 在单例模式下的循环依赖,其他模式下的循环依赖 Spring 也是会抛出 BeanCurrentlyInCreationException 异常的。Spring 通过对还在创建过程中的单例 bean,进行缓存并提前暴露该单例,使得其他实例可以提前引用到该单例 bean。

总结
Spring 使用三级缓存来解决循环依赖的问题,三级缓存分别是:

  • 一级缓存,singletonObjects 单例缓存,存储已经实例化的单例 bean。
  • 二级缓存,earlySingletonObjects 提前暴露的单例缓存,这里存储的 bean 是刚刚构造完成,但还会通过属性注入 bean。
  • 三级缓存,singletonFactories 生产单例的工厂缓存,存储工厂。

Spring 事务的传播⾏为有⼏种

事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B, A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。

Spring 事务的传播⾏为在 Propagation 枚举类中定义了如下⼏种选择
⽀持当前事务

  • REQUIRED :如果当前存在事务,则加⼊该事务。如果当前没有事务,则创建⼀个新的事务
  • SUPPORTS:如果当前存在事务,则加⼊该事务 。如果当前没有事务, 则以⾮事务的⽅式继续运
  • MANDATORY :如果当前存在事务,则加⼊该事务 。如果当前没有事务,则抛出异常

不⽀持当前事务

  • REQUIRES_NEW :创建⼀个新事务,如果当前存在事务,则把当前事务挂起
  • NOT_SUPPORTED : 以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起
  • NEVER : 以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常

其他情况

  • NESTED :如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来执⾏ 。如果当前没有事
    务,则该取值等价于 REQUIRED

spring AOP

面向切面编程(Aspect-oriented Programming,俗称 AOP)提供了一种面向对象编程(Object-oriented Programming,俗称 OOP)的补充,面向对象编程最核心的单元是类(class),然而面向切面编程最核心的单元是切面(Aspects)。与面向对象的顺序流程不同,AOP 采用的是横向切面的方式,注入与主业务流程无关的功能,例如事务管理和日志管理。

Spring AOP 实现
Spring AOP 是通过动态代理技术实现的,而动态代理是基于反射设计的。Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理,分别来理解一下

  • JDK 动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用 JDK 动态代理。目标对象必须实现接口
  • CGLIB 代理:如果目标对象没有实现接口,则可以使用 CGLIB 代理。

Spring 声明式事务在那些情况下会失效

  • 底层数据库引擎不支持事务
    如果数据库引擎不支持事务(MyISAM),则Spring自然无法支持事务。

  • 在非public修饰的方法使用
    @Transactional注解使用的是AOP,在使用动态代理的时候只能针对public方法进行代理,源码依据在AbstractFallbackTransactionAttributeSource类中的computeTransactionAttribute方法中

  • 直接 try{}catch

  • 方法中调用同类的方法
    简单的说就是一个类中的A方法(未标注声明式事务)在内部调用了B方法(标注了声明式事务),这样会导致B方法中的事务失效。

      public class Test{
          public void A(){
              //插入一条数据
              //调用B方法
              B();
          }
    
          @Transactional
          public void B(){
              //插入数据
          }
      }

    为什么会失效呢?:其实原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。

  • rollbackFor属性设置错误
    很容易理解,指定异常触发回滚,一旦设置错误,导致一些异常不能触发回滚,此时的声明式事务不就失效了吗。

  • noRollbackFor属性设置错误
    这个和rollbackFor属性设置错误类似,一旦设置错误,也会导致异常不能触发回滚,此时的声明式事务会失效。

  • propagation属性设置错误
    事务的传播属性在上面已经介绍了,默认的事务传播属性是Propagation.REQUIRED,但是一旦配置了错误的传播属性,也是会导致事务失效,如下三种配置将会导致事务失效:

    • Propagation.SUPPORTS
    • Propagation.NOT_SUPPORTED
    • Propagation.NEVER
  • 原始SSM项目,重复扫描导致事务失效

    1. 在原始的SSM项目中都配置了context:component-scan并且同时扫描了service层,此时事务将会失效。
    2. 按照Spring配置文件的加载顺序来说,会先加载Springmvc的配置文件,如果在加载Springmvc配置文件的时候把service也加载了,但是此时事务还没加载,将会导致事务无法成功生效。
    3. 解决方法很简单,把扫描service层的配置设置在Spring配置文件或者其他配置文件中即可。

spring mvc的工作流程

可以看到这里分为大致11个步骤,这11个步骤的任务是这样的:

  1. 用户发送请求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器找到具体的处理器(可以根据xml配置. 注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter处理器适配器。
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView。
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
    ​8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
    ​9. ViewReslover解析后返回具体View,这个view不是完整的,仅仅是一个页面(视图)名字,且没有后缀名。
  8. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  9. DispatcherServlet响应用户。
    图中可以看到,DispatcherServlet(前端控制器)占据了很大的一部分,事实也是这样,springMVC中,DispatcherServlet是他的核心。