写在前面

Spring

IOC

  • 讲讲什么是IOC?

    通过IOC容器来控制Bean对象的产生,在Spring之前要得到所依赖的对象,就得自己一个一个new(正转),有了IOC就是他帮我找一个,也就是把所需要依赖的对象通过IOC容器注入进来,反转的是去所依赖的对象

  • SpringBean

    • 依赖注入(DI)注入Bean的方法

      • @Autowired
      • @Resource
      • @Inject
    • 告诉IOC容器我是一个Bean

      • XML

      • 注解:@Bean @Component(不知道那一层直接用) @Service @Dao @Controller

Bean生命周期

spring容器帮助我们去管理对象,从对象的产生到销毁的环节都由容器来控制,关键环节就是 实例化 和 初始化 以及 扩展点
1. 实例化bean对象,通过反射的方式来生成,在源码中有一个createBeanInstance的方法专门生成对象
2. 创建完bean后,其属性值都是默认值,需要对bean填充属性,通过populateBean方法来完成
3. 向bean对象中设置容器相关属性,invokeAwareMethods方法将容器对象设置到具体的bean对象中。(这里就是要检查是否实现了Aware先骨干的接口)
4. 调用BeanPostProcessor中的前置处理方法来进行bean对象的扩展工作(这是一个扩展点,可以实现自己的前置操作)
5. 调用invokeInitMethods方法来完成初始化方法的调用,如果bean对象实现了initializingBean接口,如果实现了调用afterPropertiesset方法设置
6. 调用BeanPostProcessor后置处理工作,AOP就是在此处实现的。
7. 获取到完成对象,通过getBean的方式进行对象的获取和使用
8. 等待容器关闭时,先判断是否实现DisposableBean接口,再调用destroyMethod方法

Bean创建过程中循环依赖

  • 问题描述:当A中有B,B中有A的时候,Spring是如何去实例化,来防止循环依赖的?为什么constructer循环依赖无法解决,而setter可以?为什么单例模式bean可以而prototype类型不可以?

  • 三级缓存

    一级缓存:singletonObjects,存放成熟的对象,也就是初始化成功完成后的对象
    - 二级缓存:earlySingletonObjects,存放已经实例化,但是为进行初始化复制的半成品对象
    - 三级缓存:singletonFactories,存放的是一个函数式接口ObjectFactory
    - 解决循环依赖,主要就是利用Spring的二三级缓存,提前暴露未完全创建成功的单例对象来实现

  • 举个例子:A与B互相依赖,A先进行实例化,实例化后其实已经可以找到当前实例化后的对象,将A放入三级缓存singletonFactories,然后去进行初始化,等需要初始化内部属性B时,会依次去找一二三级缓存,发现没有就会create创建B,等B在依赖A时,也会去找一二三级缓存,发现一二级没有,就会再去第三级缓存中是否存在A,有就就会取出放入二级缓存,然后B走完整个流程,将自己放入一级缓存SingletonFactories,然后A就可以找到B,并且是完整的B,走完剩下的创建流程。不能解决构造函注入方法的循环依赖,在调用构造函数之前,未将其缓存到第三级缓存中 2. 不能解决prototype作用域下的循环依赖,3.不能解决多例模式下的循环依赖,这些情况下的循环依赖,可以通过@lazy延迟加载,@DependOn()指定先后加载顺序来解决

  • 为什么是三级缓存?

    • 不是一级的原因:因为如果只有一层的话,那么就会把半成品对象和成品对象混在一起,而半成品对象是不能暴露给外部访问的

      • 只有两级?如果整个应用不涉及AOP的话,两级就可以,但是如果存在AOP循环依赖那么就不行
      • 三级解决问题:因为三级缓存缓存中,缓存的是ObjectFactory,其提供了getObject方法,存在代理时保证了只创建一个同名的Bean,基于这种设计,没有发生循环依赖的bean就是正常的创建流程,有相互引用的bean会触发getEarlyBeanReference。

作用域

  • singleton单例,就存在一个实例对象
  • prototye,每次调用getBean都会创建新的bean返回
  • request,每次http请求都会创建一个bean
  • session:同一个HTTP Session共享一个Bean,不同Session使用不同的Bean
  • application:限定一个Bean的作用域为ServletContext的生命周期

AOP

  • 前置基础

    • 反射

      • 可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性
      • 具体操作,是通过全类名获取类,然后取其方法,最后通过method.invoke()执行
    • 静态代理

      • 没创建一个被代理类,就得创建一个代理类,因为继承自同一个接口,实现同一个方法,本质上代理类的对象作为了代理类的依赖对象
    • 动态代理

      • JDK

        • 代码

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          public class JdkProxyFactory {
          public static Object getProxy(Object target) {
          return Proxy.newProxyInstance(
          target.getClass().getClassLoader(), // 目标类的类加载
          target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
          new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
          );
          }
          }

          public class DebugInvocationHandler implements InvocationHandler {
          /**
          * 代理类中的真实对象
          */
          private final Object target;

          public DebugInvocationHandler(Object target) {
          this.target = target;
          }

          public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
          //调用方法之前,我们可以添加自己的操作
          System.out.println("before method " + method.getName());
          Object result = method.invoke(target, args);
          //调用方法之后,我们同样可以添加自己的操作
          System.out.println("after method " + method.getName());
          return result;
          }
          }


        • 需要像静态代理那样,写出代理类实现InvocationHandler接口,实现invoke方法,内部调用通过method.invoke()反射实现

        • 必须要有接口,本质上代理是调用了Proxy.newProxyInstance()

      • CGLIB

        • 代码

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          /**
          * 自定义MethodInterceptor
          */
          public class DebugMethodInterceptor implements MethodInterceptor {


          /**
          * @param o 代理对象(增强的对象)
          * @param method 被拦截的方法(需要增强的方法)
          * @param args 方法入参
          * @param methodProxy 用于调用原始方法
          */
          @Override
          public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
          //调用方法之前,我们可以添加自己的操作
          System.out.println('before method ' + method.getName());
          Object object = methodProxy.invokeSuper(o, args);
          //调用方法之后,我们同样可以添加自己的操作
          System.out.println('after method ' + method.getName());
          return object;
          }

          }
          import net.sf.cglib.proxy.Enhancer;

          public class CglibProxyFactory {

          public static Object getProxy(Class<?> clazz) {
          // 创建动态代理增强类
          Enhancer enhancer = new Enhancer();
          // 设置类加载器
          enhancer.setClassLoader(clazz.getClassLoader());
          // 设置被代理类
          enhancer.setSuperclass(clazz);
          // 设置方法拦截器
          enhancer.setCallback(new DebugMethodInterceptor());
          // 创建代理类
          return enhancer.create();
          }
          }


        • 关键实现:Enhancer + MethodInterceptor,需要继承MethodInterceptor,重写intercept方法,内部通过MethodProxy.invokeSuper()实现

        • 需要有继承,本质上代理通过Enhancer创建动态代理增强类

  • 通知:前置,后置,环绕,事后返回通知和异常通知

  • Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理,就是在代理对象方法反射调用前后进行一些逻辑处理(前置,后置,环绕的理解)

  • 不能增强的类

    • Spring AOP只能对IoC容器中的Bean进行增强,对于不受容器管理的对象不能增强。
    • 由于CGLib采用动态创建子类的方式生成代理对象,所以不能对final修饰的类进行代理。

事务

  • 事务隔离级别和数据库一样,但是如果Spring设置的隔离级别和Mysql的隔离级别不一样,采用Spring配置的隔离级别

  • @Transactional 注解

    • 这是一个声明式业务,本地底层通过AOP代理增强实现

    • 事务失效情况

      • 类内部调用:类Test中有A,B方法,A无B有,A调用B,其外部调用A方法时,B的事务也不生效
      • 非Public修饰(底层源码中,会判断修饰符是不是public)
      • rollbackfor:异常不匹配,Spring默认处理运行时异常,即只有抛出运行时异常时,才会触发事务回滚,否则并不会回滚
      • @Transactional 注解属性 propagation 设置错误
    • 事务传播:A内调用B(后面参数修饰被调用者B)

      • REQUIRED(默认):A有B加入,A无B新建
      • REQUIRES_NEW:A有事务,B会重新开启一个事务(与A事务之间独立,各自回滚不影响对方)
      • NESTED:A有事务,B会生成一个嵌套事务(嵌套在A事务中,比如A回滚,那么B也是需要回滚,而B异常回滚,如果A捕获其异常,可以只做B的回滚)
      • support:A有B加入,A无B无
      • not_support:以非事务方式执行
      • MANDATORY:必须要有,A有B加入,不然抛出异常
      • NEVER:不允许有事务,不然抛出异常

BeanFactory和FactoryBean的区别

  • BeanFactory:Spring容器最核心也是最基础的接口,本质是一个工厂类,管理Bean的工厂,一般使用其子接口
  • FactoryBean:工厂Bean,实现该接口可以定义索要创建bean实例,只需要实现其getObject方法

SpringMvc

MVC架构

  • Model:处理应用程序数据逻辑的部分
  • View:负责数据显示部分
  • Controller:负责具体过来的请求处理,就是用户交互
  • Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。

执行流程

    1. 当一个请求过来,首先会被DispatcherServelet拦截住,请求多个或一个HandlerMapping,返回一个执行链
  • 2.DispatcherServelet将执行链中包含的Handler交给HandlerAdapter包装然后找到真正需要执行的Handle(也就是Controller)
  • 3.处理完handle的逻辑后,返回一个ModelAndView(包含模型和视图),DispatcherServlet收到后,会请求ViewResolver去解析视图
  • 4.ViewResolver根据view的内容进行解析后,回传给DispatcherServlet,进行视图渲染,将Model中的数据填充到view中,返回到具体的View视图(JSP,FreeMarker),最后用户可以在浏览器看到

Interceptor拦截器实现原理

  • 核心接口就是HandlerInterceptor

  • 本质上是利用了AOP的原理,就是动态代理+反射实现,进入到Controller前进行一些处理,然后执行完页面选然后,再进行一些处理

  • 先执行preHandle()方法,后执行postHandle()方法,调用是在DispatcherServerlet核心方法doDispatcher()中

  • 源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

    try {
    ...........
    try {

    // 获取可以执行当前Handler的适配器
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // Process last-modified header, if supported by the handler.
    String method = request.getMethod();
    boolean isGet = 'GET'.equals(method);
    if (isGet || 'HEAD'.equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    if (logger.isDebugEnabled()) {
    logger.debug('Last-Modified value for [' + getRequestUri(request) + '] is: ' + lastModified);
    }
    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    return;
    }
    }
    // 注意: 执行Interceptor中PreHandle()方法
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
    }

    // 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    if (asyncManager.isConcurrentHandlingStarted()) {
    return;
    }
    applyDefaultViewName(processedRequest, mv);

    // 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    }
    ...........
    }

SpringBoot

自动装配原理

  • @SpringBootApplication

  • @SpringBootConfiguration:表示当前是注解类,标注当前类是一个Java Config类,会被扫描加载到IOC容器

  • @EnableAutoConfiguration:开启springboot的注解功能

    • @AutoConfigurationPackage
    • @Import({AutoConfigurationImportSelector.class}):借助AutoConfigurationImportSelector,将SpringBoot应用将所有符合条件(spring.factories)的bean定义(如Java Config@Configuration配置)都加载到当前SpringBoot创建并使用的IoC容器,底层其实是通过SpringFactoriesLoader.loadFactoryNames()这个方法,传入factoryClass.getName()到项目系统路径下所有的spring.factories文件中找到相应的key,从而加载里面的类。
  • @ComponentScan:扫描路径设置,默认扫描@SBA根目录下的所有包

自启动原理

  • SpringApplication.run(RuoYiApplication.class, args);

    • 构造函数,初始化一些关键属性,比如设置ApplicationContextInitializer和一些listener

    • run方法:整个执行流程(重要部分),大致流程就是EventPublishingRunListener中事件发布的流程

      • 发布SprintBoot开始启动事件(EventPublishingRunListener#starting())
      • 创建配置environment(EnvironmentPrepared())
      • 创建对应的ApplicationContext,一般都是Web类型
      • 完成ApplicationContext的一个初始化流程,从contextPrepared()到contextLoaded(),中间会有一系列的配置,重点的是调用AbstractApplicationContext的refresh()方法,也就是完成容器的创建,和自动装配
      • afterRefesh –> 发布SpringBoot程序已启动事件(started())started –> 最后发布就绪事件ApplicationReadyEventrunning
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      public ConfigurableApplicationContext run(String... args) {
      // 创建一个StopWatch实例,用来记录SpringBoot的启动时间
      StopWatch stopWatch = new StopWatch();
      stopWatch.start();
      ConfigurableApplicationContext context = null;
      Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
      configureHeadlessProperty();
      // 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener
      SpringApplicationRunListeners listeners = getRunListeners(args);
      // 发布SprintBoot启动事件:ApplicationStartingEvent
      listeners.starting();
      try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 创建和配置environment,发布事件:SpringApplicationRunListeners#environmentPrepared
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      // 打印SpringBoot的banner和版本
      Banner printedBanner = printBanner(environment);
      // 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);
      // 准备ApplicationContext,Initializers设置到ApplicationContext后发布事件:ApplicationContextInitializedEvent
      // 打印启动日志,打印profile信息(如dev, test, prod)
      // 调用EventPublishingRunListener发布ApplicationContext加载完毕事件:ApplicationPreparedEvent
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作
      // 以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
      refreshContext(context);
      // hook方法
      afterRefresh(context, applicationArguments);
      // stopWatch停止计时,日志打印总共启动的时间
      stopWatch.stop();
      if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      // 发布SpringBoot程序已启动事件ApplicationStartedEvent
      listeners.started(context);
      // 调用ApplicationRunner和CommandLineRunner
      callRunners(context, applicationArguments);
      }
      catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
      }

      try {
      // 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了
      listeners.running(context);
      }
      catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
      }
      return context;
      }