Spring

59 阅读9分钟

一、基本组件

Spring的核心特性是什么?

  • IOC(控制翻转)DI(依赖注入)

控制翻转,也叫依赖注入,他就是不会直接创建对象,只是把对象声明出来,在代码中不直接与对象和服务进行连接,但是在配置文件中描述了哪一项组件需要哪一项服务,容器负责去加载创建对象,并将他们联系在一起,等到需要使用的时候才把他们声明出来。

  • AOP(面对切面编程)

面向切面编程,是一种编程模式,他允许通过自定义的横切点进行模块化,将那些影响多个类的行为封装到可重用的模块中。比如日志输出,不使用AOP的话就需要把日志的输出语句放在所有类中和方法中,但是有了AOP就可以把日志输出语句封装一个可重用模块,再以声明的方式将他们放在类中,每次使用类就自动完成了日志输出。

二、AOP

项目中主要用AOP做什么,具体怎么做的?

  • 结合自定义注解,使用AOP做日志记录或者缓存

    • 第一步:创建自定义注解

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface PrintLog {}
      
    • 第二步:创建切面类

      @Aspect
      @Component
      public class RequestLogAspect {
          @Pointcut("@annotation(注解类路径)")
          //@Pointcut("execution(public * com.example.controller..*.*(..))")
          public void printLog() {}
        
          @Before("printLog()")
          public void doBefore(JoinPoint joinPoint) throws Throwable {
              // TODO
          }
        
          @AfterReturning(returning = "ret", pointcut = "printLog()")
          public void doAfterReturning(Object ret) throws Throwable {
              // TODO
          }
      }
      
    • 第三步:在需要的地方添加注解

AOP和AspectJ的区别

Spring AOPAspectJ
在纯 Java 中实现使用 Java 编程语言的扩展实现
不需要单独的编译过程除非设置 LTW,否则需要 AspectJ 编译器 (ajc)
只能使用运行时织入运行时织入不可用。支持编译时、编译后和加载时织入
功能不强-仅支持方法级编织更强大 - 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等…。
只能在由 Spring 容器管理的 bean 上实现可以在所有域对象上实现
仅支持方法执行切入点支持所有切入点
代理是由目标对象创建的, 并且切面应用在这些代理上在执行应用程序之前 (在运行时) 前, 各方面直接在代码中进行织入
比 AspectJ 慢多了更好的性能
易于学习和应用相对于 Spring AOP 来说更复杂

Spring AOP使用了AspectJ的Annotation,使用了Aspect来定义切面,使用Pointcut来定义切入点,使用Advice来定义增强处理。虽然使用了Aspect的Annotation,但是并没有使用它的编译器和织入器。其实现原理是JDK动态代理,在运行时生成代理类。

什么要引入AOP的编程范式? AOP的好处及适用场景是什么?AOP的两大核心要点是什么

  • 解决非功能性代码重复问题,如日志打印和事务控制等,实现关注点分离;
  • AOP能保持编程的内聚性,使代码高可用,减少代码的耦合性,AOP是低侵入易分离的,代码的可读性较好
  • 适用于独立于业务功能的服务开发;
  • AOP的两大核心一是方面,指需要定义什么方面和业务(Aspect);二是切入点(PointCut)。

Advice注解有哪几种,分别表示什么意思?

  • @Before,前置通知
  • @After(finally),后置通知,方法执行完之后
  • @AfterReturning,返回通知,成功执行之后
  • @AfterThrowing,异常通知,抛出异常之后
  • @Around,环绕通知

说说 Spring 的 AOP 的原理是什么?实现 AOP 有哪些方式?

Spring AOP 的底层使用的是动态代理,有两种实现方式:

  • JDK 动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。

  • CGlib 动态代理:以 CGLIB(Code Generation Library)的方式进行代理,它采用底层字节码技术,将代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。

  • 区别:JDK 代理只能对实现接口的类生成代理;CGLIB 是针对类实现代理,继承指定类并生成一个子类,因此不能代理 final 修饰的类。

在 Spring 中,优先使用哪种 AOP 呢?

  • 如果目标对象实现了接口,默认会采用 JDK 的动态代理,但也可以强制使用 CGLIB;
  • 如果目标对象没有实现了接口,则必须采用 CGLIB 库。
  • Spring 会自动在 JDK 动态代理和 CGLIB 之间转换。

为什么AOP动态代理需要实现接口?

因为生成代理类的时候已经继承了Proxy类,只能通过实现被代理类接口的方式来建立代理类和被代理类的联系。

@Async注解是用来做什么的?怎么使用?

@Async用来将方法标记为异步执行

前提条件:

  • 启动类或配置类上加入@EnableAsync注解;
  • 需要在异步执行的方法必须是被Spring管理的JavaBean中;
  • 需要异步执行的方法上必须加入@Async注解;

适用场景:

  • 日志;
  • 异步执行代码段;

注:

  • 因为@Async是基于AOP实现的,调用本类中加入@Async的其他方法,不生效

Spring 在扫描bean的时候会扫描方法上是否包含@Async注解,动态地生成一个子类(即proxy代理类),当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用时增加异步作用。

如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就失效了。

所以调用方与被调方不能在同一个类,主要是使用了动态代理,同一个类的时候直接调用,不是通过生成的动态代理类调用。

一般将要异步执行的方法单独抽取成一个类。

三、IoC

什么是Spring IoC容器,其初始化过程是怎样的?

IoC容器是指的Spring Bean工厂里存储了Bean实例的Map存储结构

img

BeanFactory、ApplicationContext和FactoryBean的区别是什么?

BeanFactoryApplicationContext都是Spring的容器。

  • BeanFactory:低级容器,结构类似于HashMap,Key是BeanName,Value是Bean实例。一般只提供注册(put)获取(get)这两个功能。

  • ApplicationContext: 高级容器。通过继承实现了比BeanFactory更多的功能,比如refesh等等。

  • Spring的两种IoC:BeanFactory和ApplicationContext,都是Java Interface。

    ApplicationContext 继承于 BeanFactory,BeanFactory 和 ApplicationContext 都提供了一种方式,使用getBean("bean name")获取bean。

  • BeanFactory是IoC容器的顶级接口,是IoC容器的最基础实现,也是访问Spring容器的根接口,负责对bean的创建,访问等工作。实现类功能单一,特点是在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化。

    延迟实例化的优点:应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势。

  • ApplicationContext拥有BeanFactory的全部功能,并且扩展了很多高级特性,每次容器启动时就会创建所有的对象。

    不延迟实例化的优点: 所有的Bean在启动的时候都加载,系统运行的速度快;在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题;建议web应用,在启动的时候就把所有的Bean都加载。

  • FactoryBean也属于Spring Bean,常规的Bean都是使用Class的反射获取具体实例,如果Bean的获取过程比较复杂,那么常规的xml配置需要配置大量属性值,这个可以使用FactoryBean,实现这个接口,在其getObject()方法中初始化这个bean。

四、Spring Bean

Bean的生命周期是怎样的?

大体可以分为四部分:实例化属性注入初始化销毁

img

  • Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
  • Bean实例化后对将Bean的引入和值注入到Bean的属性中
  • 如果Bean实现了BeanNameAware接口,Spring将Bean的Id传递给setBeanName()方法
  • 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  • 如果Bean实现了ApplicationContextAware接口,Spring将调用Bean的setApplicationContext()方法,将应用上下文的引用传入到bean中
  • 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法
  • 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
  • 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法
  • 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  • 如果Bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用

五、事务

Spring有哪些事务传播机制?

  • PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

Spring有哪些事务隔离机制?

Spring事务的隔离机制和数据库的差不多。

  • ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
  • ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
  • ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
  • ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
  • ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。