6 spring

spring

1、spring容器

1、BeanFactoryPostProcessor 和BeanPostProcessor区别

1、factorypostprocessor是针对整个bean工厂的,对所有的beandefine进行修改,当然也可以不修改,其实修改的就是我们在xml中定义的各种标签值,例如将单例变成多例。

主要实现是通过实现BeanFactoryPostProcessor接口,之后将这个实现类也要加入到我们的xml中,这样因为它实现了这个接口,所以他会先被实例化(因为要用嘛)。在bean的生命周期中他是最先别调用的;

这个接口的方法参数ConfigurableListableBeanFactory可以获取的beandefine,从而对所有bean进行修改。

在加载bean定义的时候,spring会自动检测实现了这个接口的bean,并将其在实例化其他bean之前将他实例化。在执行refresh方法时调用实现,实现增强。

在每个bean被实例化之前被调用

/*允许自定义修改应用程序上下文的 bean 定义,调整上下文底层 bean 工厂的 bean 属性值。
应用程序上下文可以在其 bean 定义中自动检测 BeanFactoryPostProcessor bean,并在创建任何其他 bean 之前应用它们。
对于针对覆盖应用程序上下文中配置的 bean 属性的系统管理员的自定义配置文件很有用。
有关满足此类配置需求的开箱即用解决方案,请参阅 PropertyResourceConfigurer 及其具体实现。 
BeanFactoryPostProcessor 可以与 bean 定义交互和修改,但不能与 bean 实例交互。这样做可能会导致过早的 bean 实例化,违反容器并导致意外的副作用。
如果需要 bean 实例交互,请考虑实现 BeanPostProcessor。
*/
@FunctionalInterface
public interface BeanFactoryPostProcessor {
//修改工厂配置。因为它给的参数是工厂嘛
   void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
//通过名字来获取定义,从而修改定义
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

//具体使用
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");
		beanDefinition.setScope("prototype");
	}
}

2、而bean的处理器是应用于随后创建的任何 bean进行处理的。spring同样也会在获取beandefine时识别出他来。

在每个bean实例化之后被调用。

/*
允许自定义修改新 bean 实例的工厂钩子,例如检查标记接口或用代理包装它们。
ApplicationContexts 可以在它们的 bean 定义中自动检测 BeanPostProcessor bean,并将它们应用于随后创建的任何 bean。
普通 bean 工厂允许后处理器的编程注册,适用于通过该工厂创建的所有 bean。
*/
public interface BeanPostProcessor {
   @Nullable
   default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }
   @Nullable
   default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }
}

两者主要的不同在于调用时间不同。

2、bean初始化流程

https://javadoop.com/post/spring-ioc#toc_7

主要类为AbstractAutowireCapableBeanFactory的doCreateBean方法,起点是AbstractApplicationContext的refresh方法中的finishBeanFactoryInitialization(beanFactory);,之后一直往下获取单例bean就是这个方法。详情可以看上面的网址。

org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 主要的三个方法调用就在这里

真正做事的都是do什么的方法。抽象类中一般是模板,留给子类实现一些特殊的方法。

image-20220705211758228

执行到initializeBean的方法栈

image-20220705212035630

image-20220705212156244

3、什么是spring?

1、spring是一个开源的轻量级的java开发框架,提高了开发人员的开发效率和系统的可维护性。核心主要是IOC控制反转和AOP面向切面编程。spring开箱即用,具有很高的拓展性,有活跃的社区。

4、spring的优势

1、使用spring提供的ioc容器,我们将对象之间的依赖关系交由spring进行管理,不需要在使用new来创建对象,降低了耦合。

2、通过spring提供的aop功能,我们可以将应用业务和系统服务分开,实现解耦。

3、生态很好,能够轻松整合其他一些优秀的框架。

4、社区活跃,有问题可以及时解决。

5、能够开箱即用,能够实现快速开发。

5、spring体系结构

image-20220706144922316

主要分为四部分:

1、web:web模块

2、Core Container:这是spring的核心模块,spring的其他模块基本都依赖于该模块,提供IOC和依赖注入功能的支持。

3、Data Access/Integration数据访问/集成:提供了对数据的访问支持以及对消息队列的支持。

4、测试以及aop

6、谈谈你对springIOC的理解

IOC是控制反转的意思,这是一种设计思想,而不是一种具体实现,他的思想是将原本在程序中手动创建对象的控制权交由spring框架来管理。springioc容器是用来实现ioc的载体。IOC最常见的实现方式是依赖注入DI。

将对象之间的依赖关系交给IOC容器进行管理,由IOC容器来完成对象的注入。我们无需知道对象是如何被创建出来的,只要通过一些简单的配置,将我们的需求告诉IOC容器即可。

7、IOC配置的三种方式

1、XML配置

主要是声明命名空间和bean。

优点:可用于任何场景,结构清晰,通俗易懂

缺点:配置繁琐,不易维护,枯燥乏味,拓展性差

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- services -->
    <bean id="userService" class="tech.pdai.springframework.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>
    <!-- more bean definitions for services go here -->
</beans>

2、java配置类

这个和xml类似,只是将xml的格式改成java的格式了,同样适用于任何场景,但是如果配置过多则可读性较差。

使用方法:创建一个配置类,在类上添加@Configration注解,在每个方法上添加@Bean注解。@Bean的属性可以指定bena的名字。

@Bean的属性:

  • value:bean别名和name是相互依赖关联的,value,name如果都使用的话值必须要一致;
  • name:bean名称,如果不写会默认为注解的方法名称;
  • autowire:自定装配默认是不开启的,建议尽量不要开启,因为自动装配不能装配基本数据类型、字符串、数组等,这是自动装配设计的局限性,并且自动装配不如依赖注入精确;
  • initMethod:bean的初始化之前的执行方法,该参数一般不怎么用,因为完全可以在代码中实现;
  • destroyMethod:默认使用javaConfig配置的bean,如果存在close或者shutdown方法,则在bean销毁时会自动执行该方法,如果你不想执行该方法,则添加@Bean(destroyMethod=“”)来防止出发销毁方法;

@Profile 注解

@Profile的作用是把一些meta-data进行分类,分成Active和InActive这两种状态,然后你可以选择在active 和在Inactive这两种状态下配置bean,在Inactive状态通常的注解有一个!操作符,通常写为:@Profile(“!p”),这里的p是Profile的名字。

@Scope 注解

在Spring中对于bean的默认处理都是单例的,我们通过上下文容器.getBean方法拿到bean容器,并对其进行实例化,这个实例化的过程其实只进行一次,即多次getBean 获取的对象都是同一个对象,也就相当于这个bean的实例在IOC容器中是public的,对于所有的bean请求来讲都可以共享此bean。

@Lazy 注解

表明一个bean 是否延迟加载,可以作用在方法上,表示这个方法被延迟加载;可以作用在@Component (或者由@Component 作为原注解) 注释的类上,表明这个类中所有的bean 都被延迟加载。

@Primary 注解

指示当多个候选者有资格自动装配依赖项时,应优先考虑bean。此注解在语义上就等同于在Spring XML中定义的bean 元素的primary属性。

注意:除非使用component-scanning进行组件扫描,否则在类级别上使用@Primary不会有作用。如果@Primary 注解定义在XML中,那么@Primary 的注解元注解就会忽略,相反的话就可以优先使用。

3、注解配置

比较常用的配置方式,开发便捷,通俗易懂,方便维护,但是有些第三方类不支持,只能使用xml或者java来。

@Component,@Controller,@Service,@Repository

4、继承FactoryBean接口

这个接口会返回我们的代理类对象。但是首先得将实现了这个接口的类定义加入spring容器。之后spring初始化的时候就会自动调用FactoryBean的getObject方法返回代理对象。这个实现了接口的类就是代理工厂。

8、依赖注入的三种方式

1、setter注入

使用bean的set方法进行注入,在注解和java配置类中就是将@AutoWired加在setter方法上

2、构造方法注入

xml就是一个标签

注解和java配置类就是在bean的构造方法上面添加@AutoWired注解

3、自动注入

主要是引用不用写了,spring会根据字段的类型、beanname、constructor自动在容器中寻找合适的类注入

https://www.w3cschool.cn/wkspring/kp5i1ico.html

在要装配的类上添加autowire属性,值可以为

image-20220706202507290

   <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor" 
      autowire="byType">
      <property name="name" value="Generic Text Editor" />
   </bean>

9、Resource和Autowired的区别

  • 两者都是注入bean的方式,来源不同,Resource来自jdk,而Autowired来自spring
  • 默认查找不同,Resource默认查找Byname(就是和属性名一样的bean),而Autowired则是默认查找Bytype的(就是在容器中查找属性对应的类的bean),如果找不到会报错,如果有两个相同的实现类会按照byname来找,找不到报错。而Resource可以指定name和type属性进行指定,两者可以同时指定,但是找不回报错。
  • Autowired可搭配Qualifier注解来显示指定byname的值

10、bean的作用域

有五种

1、singleton:唯一bean,spring中bean默认都是单例的

2、prototype:每次请求都会创建一个新的bean实例。

3、request:每次HTTP请求都会产生一个新的bean,只在当前HTTP Request中有效

4、session:在web应用中,为每个会话创建一个bean

5、globalsession:每个全局的HTTP Session都会创建一个。spring5好像没有了。

11、spring中bean线程安全吗?如何解决?

在多线程环境下,只对bean的成员变量进行查询,不会修改成员变量的值,这样的bean叫做无状态bean,所以无状态bean是线程安全的。而多线程环境下需要对bean的成员变量进行修改bean称为有状态bean,有状态bean是存在线程安全问题的。

那么如何解决呢?

我们可以更改bean的作用域为prototype,这样每个需要bean的时候都会创建一个新的,这样就不存在线程安全问题

2、使用ThreadLocal来存储成员变量,这样每个成员变量在每个线程中都会保存一个副本。

12、bean的生命周期

首先容器会根据我们指定的xml文件路径去读取xml文件,将里面的bean提取出来称为beandefine。

bean的生命周期主要分为5个阶段:准备处理器,创建实例,属性填充,容器缓存,销毁。

1、创建准备阶段:在这个阶段spring会先去实例化beanfactory后处理器(只执行一次,用来修改bean定义,作用于工厂),和beanpost后处理器(每个bean都会执行一次),在创建bean的时候使用

2、执行beanfactory后处理器修改bean定义

3、执行InstantiationAwareBeanPostProcessor的方法

4、执行bean的构造器

5、执行InstantiationAwareBeanPostProcessor的后方法

6、进行属性填充

7、执行Aware接口的方法

8、执行普通的bean后处理器前置方法

9、查看bean是否实现initBean接口,如果实现了执行其方法后再执行bean定义中的init方法

10、后处理器后置方法

11、高级bean后处理器创建成功方法

12、正常调用

13、销毁bean

image-20220705211758228

image-20220708095508842

13、springIOC容器初始化refresh

创建容器是线程安全的,因为在创建的时候加了锁,先创建bean工厂,读取配置文件加载bean定义,之后手动注册几个必要的bean前置处理器,之后实例化BeanFactory后处理器,调用里面的方法。之后实例化beanpostProcessor,这个在bean填充完属性之后执行,特殊的Instantia那个除外。

之后就是一些附加功能,例如国际化,广播器,监听器,最后就是实例化那些单例bean,发送容器初始化完成事件。

obtainFreshBeanFactory();方法会加载beandefine和实例化解析各种xml标签的Handler,因为你要读取xml文件就必须要有相应的解析器来解析标签。

标签会被解析为一个beandefine

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
      // 设置spring状态为“已启动”,设置容器启动时间,处理配置文件占位符
      prepareRefresh();
      // 获取ConfigurableListableBeanFactory工厂,这一步同时还读取完了beandefine,并且注册到了BeanFactory中
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      // 设置几个必要的beanpostprocesser,和手动注册几个bean
      prepareBeanFactory(beanFactory);
      try {
         // 这一步给具体context实现,在标准初始化之后修改应用程序上下文的内部 bean 工厂。所有 bean 定义都将被加载,但还没有 bean 被实例化。这允许在某些 ApplicationContext 实现中注册特殊的 BeanPostProcessors 等。
          // 主要是用来添加beanpostprocessor,用来增强bean的
         postProcessBeanFactory(beanFactory);

         StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
         // 调用BeanFactoryPostProcessors的方法,spring会自动在加载beandefine时识别他并且在此函数内被实例化
         invokeBeanFactoryPostProcessors(beanFactory);

         // 同样也会去扫描实现了rBeanPostProcessors接口的beandefine,在这一步实例化
         registerBeanPostProcessors(beanFactory);
         beanPostProcess.end();

         // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
         initMessageSource();

         // 初始化当前 ApplicationContext 的事件广播器,
         initApplicationEventMulticaster();

         // 从方法名就可以知道,典型的模板方法(钩子方法),
         // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
         onRefresh();

         // 注册事件监听器,监听器需要实现 ApplicationListener 接口。
         registerListeners();

         // 实例化所有未被lazy-init的bean
         finishBeanFactoryInitialization(beanFactory);

         // 发送初始化完成事件
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
         contextRefresh.end();
      }
   }
}

14、循环依赖问题

回答:

先放入三级缓存(存的是a对象和一个lambda表达,键值对),之后放入二级缓存(在C取所有缓存中找的时候会去执行三级缓存中A的lambda表达式,返回一个A对象,并将A对象放入二级缓存),最后再放入一级缓存(C完成初始化操作)。

  1. 首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光,放在第三级缓存中),在
    初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建
    出来;
  2. 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来;
  3. 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A)。这
    个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 singletonFactories),通过
    ObjectFactory 提前曝光,所以可以通过 ObjectFactory#getObject() 方法来拿到 A 对象,并将A放入二级缓存中。C 拿 到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中;
  4. 回到 B,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路
    就已经完成了初始化过程了。

如果是构造器注入的话(假如有A、B类,A先B后),A第一次先把自己放入singletonsCurrentlyInCreation中,然后在createBeanInstance时会去调用@AutoWired标注的有参构造器(此时A没有实例化,连对象都没创建),然后会去getBean(B),这就回到了上方流程的开头,B在第一个getSingleton没有获取到A,然后就去getBean(A),对于A来说已经是第二次了,于是在向singletonsCurrentlyInCreation添加的时候就会报错,因为该集合已经有了A,因此异常在此处抛出。
如果是set注入,A在createBeanInstance时则会调用无参构造方法,在populateBean(此时A已经放入三级缓存了)时调用getBean(B),而B再去getBean(A)的时候(无论B是在@AutoWired标注的有参构造器还是无参构造去获取A),直接就能从三级缓存中得到,解决循环依赖。

构造器循环依赖不一定不能解决

根据上文最后一句话可知,A先B后,A用set注入,B是构造器注入,这样的循环依赖也是可以被解决的


https://blog.csdn.net/m0_43448868/article/details/113578628

代理模式就是持有一个原先的对象,我们实现同一个方法名,调用时同时执行增强逻辑和原方法,这里原对象是独立的,代理对象成员变量指向原对象,这样我们无论先实例化原对象或者不初始化他都是一样的,他最后都还是会被初始化。

在我们解决循环依赖的时候,先创建A,A依赖B,这时A(未进行依赖注入)先放在三级缓存中,创建B,这时B又依赖于A,B会去查找一级缓存,发现没有,在查找二级缓存,也没有,再找三级缓存这时候有了

(这时会调用getEarlyBeanReference方法,该方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,而这个接口下的实现类中只有两个类实现了这个方法,一个是AbstractAutoProxyCreator,一个是InstantiationAwareBeanPostProcessorAdapter,在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference方法,而该类就是用来进行AOP的。最终会调用AbstractAutoProxyCreator#wrapIfNecessary方法进行增强)

就将A的增强对象放进B中,此时A增强对象中的A是一个引用地址,还未被初始化。此时B完成初始化,加入一级缓存。

接着A从一级缓存中获取到B,完成依赖注入。此时的A是完整版,它指向的地址也是完整版,意味着B中的A是完整版。

在docreateBean执行完bean 的构造方法之后会判断是否需要提前暴露早期bean

		// Eagerly cache singletons to be able to resolve circular references 在填充属性之前
		// even when triggered by lifecycle interfaces like BeanFactoryAware.判断是否需要早期暴露bean未填充
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
            //将beanname和ObjectFactory加入三级缓存中,将未填充属性的bean移除二级缓存,getEarlyBeanReference是AOP的关键,这个执行这个方法会返回当前对象增强对象,这个代理对象里面的原始对象可能是未依赖注入的。
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

        //org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
        protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
                Assert.notNull(singletonFactory, "Singleton factory must not be null");
                synchronized (this.singletonObjects) {
                    if (!this.singletonObjects.containsKey(beanName)) {
                        this.singletonFactories.put(beanName, singletonFactory);
                        this.earlySingletonObjects.remove(beanName);
                        this.registeredSingletons.add(beanName);
                    }
                }
            }

下面的场景是B去中A的依赖。已经走完了A找B依赖,先找是在执行B的属性填充,因为在上面那里将A的早期对象放入三级缓存中,所以,这里能在三级缓存中找到A,至之后再将A的初级对象放入二级缓存。这时B已经拿到了A的增强后的对象(但是代理对象中A是没有被依赖注入的),之后对A进行依赖注入即可。

循环为:A早期对象放入三级缓存,A去找B发现没有则新建,B依赖注入的时候去找A,经过三重查找在三级缓存中找到A的增强对象,并将A的增强对象放入二级缓存。

image-20220707233218617

//这个是springgetbean的底层,没有就新建,有就直接拿,先找一级缓存,再找二级缓存,最后找三级缓存
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
    //先找一级缓存
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
       //找二级缓存
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         synchronized (this.singletonObjects) {
            // Consistent creation of early reference within full singleton lock
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                   //加个锁找三级缓存。
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                      //执行三级缓存中的工厂方法,返回一个只执行构造方法的bean
                     singletonObject = singletonFactory.getObject();
                      //存入二级缓存
                     this.earlySingletonObjects.put(beanName, singletonObject);
                      //将三级缓存移除,这一步已经将A增强对象移除三级缓存
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}
//DefaultSingletonBeanRegistry,将完整对象放入单例池中。
	protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			this.singletonObjects.put(beanName, singletonObject);
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

为什么要有第三级缓存?

因为这里存放的AOP逻辑,用来给那些有增强逻辑的Bean。二级缓存用来存放那些不需要增强

15、Aware接口含义

https://blog.csdn.net/yi524529990/article/details/119888367 这个比较好

https://blog.csdn.net/agonie201218/article/details/108489141

其实就是用来获取spring容器中的一些数据,只有实现这个接口,就能够获取spring内部的一些数据,否则是获取不到的。

比如说,我想要我的bean(就是自定义的类,类似于user之类的)获取到spring容器的一些底层bean,这些bean不能通过自动注入获取,之能够通过实现接口,通过方法上的参数进行获取。这就是Aware感知,让我能够感知到spring底层。

其实这些接口就是spring底层暴露出来的接口,让我能够使用一些数据罢了。

/**
 * 自定义一个实现类,一定要注入到容器中,这样我们就能拿到application了。
 */
@Component
public class ApplicationContextAwareImpl implements ApplicationContextAware {
    /**
     * 容器启动的时候会调用这个方法,只需要将applicationContext设置即可
     * @param applicationContext 容器启动会自动注入
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //将其设置到ApplicationContextUtil
        ApplicationContextUtil.setApplicationContext(applicationContext);
    }
}

16、注解方式生成beandefine

在我们写代码的时候,一般都是直接给要加入容器的bean加上相应的注解,这样他就能加入spring容器了。这个的实现逻辑是springboot的启动类上有包扫描注解,能够将添加了注解的类生成Beandefine。使用一个解析器来解析类,有点像反射一样,将class的属性拿到来分析。

这样我们就能够将他加入beandefine中了。

2、Spring AOP

1、aop理解

AOP:面向切面编程,将那些与业务无关的,却为业务模块所共同调用的逻辑如日志管理,权限管理等封装起来,减少系统的重复代码,降低模块间的耦合度,并有利于未来的拓展和可维护性。

springAOP的实现是基于动态代理的,如果目标对象实现了某个接口,则spring会使用jdk的动态代理来实现增强,否则使用cglib来实现。

2、相关概念

(1)切面(Aspect):被抽取的公共模块,可能会横切多个对象。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。切点和通知的集合

(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 在哪个地方执行增强方法,拦截地点

(3)通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。增强逻辑

(4)切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。连接点的集合

(5)引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。被代理对象

(7)织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。将增强逻辑通知和连接点融合在一起生成代理对象的过程

切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。

image-20220708134946408

3、5种通知类型及其执行顺序

1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

(2)返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

(3)抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。

(4)后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

(5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

同一个aspect,不同advice的执行顺序:

①没有异常情况下的执行顺序:

around before advice 1
before advice 2
target method 执行
around after advice 1
after advice 2
afterReturning

②有异常情况下的执行顺序:

around before advice 1
before advice 2
target method 执行
around after advice 1
after advice 2
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生

4、实现原理

https://www.cnblogs.com/yuanbeier/archive/2022/04/17/16155353.html 这个写完了创建流程,每次创建Bean都会在创建Bean的生命周期的 initializeBean 方法中,会执行 AnnotationAwareAspectJAutoProxyCreator的 postProcessAfterInitialization方法。该方法会拿缓存BeanFactoryAspectJAdvisorsBuilder.advisorsCache 中所有advisor的pointCut去匹配正在创建的实例Bean的所有方法。如果 advisor 和 Bean 的某一个方法能匹配上,则把该advisor添加到 advisor的候选集合中。直到找出匹配Bean的所有Adsivors。最后根据Adsivor的候选集合和Bean类型创建动态代理对象ProxyFactory。

回答:AOP的底层是通过AbstractAutoProxyCreator来创建的,AbstractAutoProxyCreator实现了BeanPostProcessor接口,这里他会去调用postProcessAfterInitialization方法,这里面又有wrapIfnecessary方法,这个方法的作用是获取增强方法和根据获取的增强器进行代理。

获取增强器是会去找bean工厂中被AspectJ注解标记的类,根据类里面的注解和切点生成增强器Advisor。之后根据当前创建bean的信息,来获取适合当前bean的增强器。有了增强器之后,就能生成代理了。

代理类的创建是使用ProxyFactory来创建的。将增强器advisor和目标类加入工厂中,之后根据目标类是否实现了接口来判断使用jdk的动态代理还是使用cglib的动态代理来创建代理类。

1、AbstractAutoProxyCreater结构图

image-20220709103244725

2、在容器初始化的时候就会自动创建AbstractAutoProxyCreater,这个顶层的抽象的创建代理的beanPostProcessor,他间接实现了beanPostProcessor,他实现的beanPostProcessor后置方法postProcessAfterInitialization中有创建代理对象的方法wrapIfNecessary,这个方法主要是获取拦截器(也就是Advice 是通知,Advisor 是增强器。增强器是通知加切点),使用拦截器来创建bean的代理。

当我们出现循环依赖的时候,第二个bean需要第一个时,会去第三级缓存中获取到objectFactory来执行他的创建代理方法。之后将代理对象放在二级缓存中,等待其完成依赖注入之后放入一级缓存。

新建一个bean的时候,如果为出现循环依赖,会在执行执行完依赖注入后执行这个创建代理方法。

image-20220709122328809

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // Create proxy if we have advice.
    //这里获取拦截器
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
   if (specificInterceptors != DO_NOT_PROXY) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
       //创建代理对象
      Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
   }

   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}
//获取拦截器advisor
	protected Object[] getAdvicesAndAdvisorsForBean(
			Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {

        //获取advisor,就是去解析切面类和xml中的切面配置,并将其封装为advisor,一个advisor包含一个通知和切点。
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}

//创建代理
	protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}

		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);

		if (proxyFactory.isProxyTargetClass()) {
			// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
			if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
				// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
				for (Class<?> ifc : beanClass.getInterfaces()) {
					proxyFactory.addInterface(ifc);
				}
			}
		}
		else {
			// No proxyTargetClass flag enforced, let's apply our default checks...
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}

		// Use original ClassLoader if bean class not locally loaded in overriding class loader
		ClassLoader classLoader = getProxyClassLoader();
		if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
			classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
		}
		return proxyFactory.getProxy(classLoader);
	}

5、advice与MethodInterceptor联系

先将各种切面类或者xml文件获取到的通知和切点组合成各种advisor,之后在将这些advisor变成环绕通知,统一进行调用增强,这样减少了底层调用的判断逻辑

MethodBeforeAdviceInterceptor是环绕通知

image-20220709124242468

advisor中的advice一般不是环绕通知,我们使用适配器将他转换为环绕通知后统一进行调用。

底层适配器MethodBeforeAdviceAdapter,将低级切面中的通知转换为同一的环绕通知,MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor

image-20220709124400731

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

   @Override
   public boolean supportsAdvice(Advice advice) {
      return (advice instanceof MethodBeforeAdvice);
   }

   @Override
   public MethodInterceptor getInterceptor(Advisor advisor) {
      MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
      return new MethodBeforeAdviceInterceptor(advice);
   }

}

统一转换为环绕通知, 体现的是设计模式中的适配器模式

  • 对外是为了方便使用要区分 before、afterReturning
  • 对内统一都是环绕通知, 统一用 MethodInterceptor 表示

代理方法执行时会做如下工作

  1. 通过 proxyFactory 的 getInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知
    • MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
    • AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
    • 这体现的是适配器设计模式
  2. 所谓静态通知,体现在上面方法的 Interceptors 部分,这些通知调用时无需再次检查切点,直接调用即可
  3. 结合目标与环绕通知链,创建 MethodInvocation 对象,通过它完成整个调用

高级切面@Before->Advice->MethodInvocation

6、Spring AOP 和 AspectJ AOP 有什么区别?

springAOP是运行时增强,而AspectJAOP是编译时增强。在spring中已经集成了AspectJ,springaop和他相辅相成。

springAOP是基于动态代理的,而AspectJAOP是基于字节码的

7、Advice和Advisor区别

简单来说:Advice 是通知,Advisor 是增强器。(说了跟没说一样…)

使用 spring aop 要定义切面,切面里面有 通知 和 切点。

在项目启动的过程中,项目中的所有切面会被 AnnotationAwareAspectJAutoProxyCreator 解析,它会找到切面中的每一个通知以及通知对应的切点,拿这二者构建一个新的对象,这个对象就是 Advisor()。最后将所有解析得到的增强器注入到容器中。
image-20220710092050930

8、springaop执行(重要

在bean初始化的时候,会先自动注册并实例化一个类AnnotationAwareAspectJAutoProxyCreator类,这个类实现了BeanPostProcessor接口,所以会在initialBean方法时,其他bean会去执行这个beanPostProcessor方法对bean进行增强,调用他的postProcessAfterInitialization方法,这个方法中有wrapIfNecessary方法的调用,用来决定是否需要创建代理类。

wrapIfNecessary方法里的findAdvisorsThatCanApply方法会去找所有的适用于当前bean的增强器根据切点和bean的方法信息做匹配。如果返回结果不为空,则会创建代理,否则不创建代理。底层是使用ProxyFactory来创建代理类的。

在真正的动态代理类中,我们存放的是各种低级切面,这时还未转换为Methodinterceptor(环绕通知),在执行的时候会调用MethodBeforeAdviceAdapter这样类似的适配器将低级切面(由@Before注解转换为的AspectJMethodBeforeAdvice)转换为环绕通知MethodBeforeAdviceInterceptor,这样在执行的时候就能够递归调用了。

以上是创建动态代理的过程,下面是执行动态代理方法的过程。

使用MethodInvocation来执行增强器方法,也就递归执行通知MethodBeforeAdviceInterceptor。看下面第九点。

around before advice
before advice
target method 执行
around after advice
after advice
afterReturning

9、JDK生成的动态代理

有两种成员变量,

1、通过外界传入并且使用静态代码块赋值的invocationHandler

2、使用静态代码块反射调获取到的接口方法对象Method

因为jdk的动态代理需要实现和目标对象一样的接口,所以代理对象也有对应的实现方法,在这些对应的实现方法中,是使用InvocationHandler来调用目标方法的,增强逻辑在InvocationHandler里面,我们通过传入目标方法就是上面的成员变量,最终执行的时候会根据InvocationHandler方法中目标方法的占位来进行增强。

所以spring中的增强类里面都是各种通知,相当于invocationHandler的地位,只不过他是使用调用链的形式来执行的。真正的invocationHandler是JdkDynamicAopProxy。

JdkDynamicAopProxy这里面有invoke方法,方法里有MethodInvocation,这就是执行增强逻辑的调用链。

image-20220714003258764

image-20220714003909986

创建代理对象与执行代理方法

image-20220714005310753

// org.springframework.aop.framework.JdkDynamicAopProxy#invoke
// 拦截器链就是在创建代理的时候加入进去了,这里直接那就可以了。
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

10、拦截器(interceptor、advice)与增强器advisor

https://blog.csdn.net/weixin_36359107/article/details/113016311

为拦截器定义切入点(一组连接点)之后产生增强器,增强器可以有选择性地拦截目标对象中的部分方法.

注意: 拦截器默认拦截所有目标对象中的方法

在我们生成代理对象的时候,需要将拦截器转变为增强器,使用连接点来实现。

image-20220714000120340

因为spring涉及过多拦截器advice、拦截方法MethodInterceptor,所以同一封装成advisor来进行代理创建。

11、MethodInvocation

这个是真正执行的方法,相当于jdk中的InvokeHandler中实际调用的增强方法与目标方法链,在通知中会继续递归调用MethodInvocation实现递归调用通知。

image-20220714103532315

3、springMVC

1、流程

img

2、口述MVC流程

主要方法:org.springframework.web.servlet.DispatcherServlet#doDispatch

当有一个请求过来之后,首先要经过DispatcherServlet,通过dodispatch来处理请求。这个方法会去找在dispatcherServlet在初始化时就加载的各种HandlerMapping,调用他们的getHandler方法根据请求路径获取执行链。之后调用getHandlerAdapter获取适配器,适配器会去调用handle方法处理请求。通过处理返回ModelAndView给doDispath方法中的mv。

之后doDispatch会去使用视图解析器ViewResolver根据视图名来解析视图(这里进行视图的解析,根据视图逻辑名将ModelAndView解析成真正的视图(view,View是一个接口, 它的实现类支持不同的视图类型,如jsp,freemarker,pdf等等),之后渲染就是对View进行填充属性),返回View对象,之后DispatcherServlet对View进行视图渲染,将模型数据填充到视图,返回视图给用户。

视图解析器的作用就是将Handler处理完返回的ModelAndView对象中的逻辑视图名转换为真正的物理地址,就比如ModelAndView中的view字段的值为index,在我们自定义的视图解析器中配置好了前缀(/prefix=/WEB-INF/)和后缀(.jsp),拼接在一起就是/WEB-INF/index.jsp,之后创建view对象,这个view对象就是对应的jsp文件,但是还未进行属性填充,里面有一个map保存了对应的属性值,视图解析器将view交给dispatchservlet后,dispatch会执行view中的render方法来填充属性,并且返回给客户端。

image-20220727140039641

3、ControllerAdvice

https://zhuanlan.zhihu.com/p/73087879

https://www.cnblogs.com/hcmwljfh/p/15767215.html

这个主要是用来拓展controller的一些功能,为执行controller提供一些拓展点,比如说,异常控制,参数解析,返回值解析等。

在处理controller异常时,先创建一个被@ControllerAdvice标注的类,里面的方法配合上@ExecptionHandler(异常类)注解,之后出现异常就会调用的这个handler来处理这个异常。

具体调用者是DispatchServlet检测到异常之后调用ExcptionHandlerExceptionResolver来调用ExceptionHandlerMethodResolver 处理异常。被@ControllerAdvice标注的类在DispatcherServlet初始化时就被加入了initHandlerExceptionResolvers(context);

4、spring事务

1、spring事务的实现原理

spring事务本质还是数据库对事务的支持,spring提供接口,具体事务还是由数据库实现。重写和回滚是靠数据库的redolog和undolog进行的。

2、spring的事务种类

  • 编程式事务,使用Transaction Template进行操作事务的开启结束回滚。
  • 声明式事务:本质上是通过AOP来操作的,对方法前后进行拦截,将事务处理功能通过AOP的方式加入方法中,在执行前开启事务,执行完后提交事务。

两者优缺点:编程式事务粒度较细,而声明式事务简单方便,不用将事务操作代码写入方法中,减少代码污染。

3、spring事务传播机制

https://blog.csdn.net/qq_52252193/article/details/125181180 这个通俗易懂

七个,两个抛异常,两个要,一个随缘,一个强盗。一个小三。

  • 需要:如果当前有事务则加入,没有事务就创建一个事务
  • 有钱:无论当前是否存在事务都新建一个事务
  • 随缘:如果当前存在事务就加入该事务,无则以无事务方式执行
  • 霸道:以非事务方式执行,如果有事务则将当前事务挂起
  • 嵌入:如果当前有事务则嵌套事务内执行,无则创建一个事务
  • 冷漠:需要事务,没有则抛出异常
  • 非常冷漠:不需要事务,有则抛出异常

需要 有钱 嵌入的区别

https://blog.csdn.net/yanxin1213/article/details/100582643

image-20220710101706985

总结:需要是一个事务,同生共死。有钱是连个独立的事务,两者异常会分别处理,异常会回滚。嵌入则看外面的,外面的回滚了则都要回滚,如果子死了不影响父,而需要则是一起回滚。

REQUIREDNEW是新建一个事务并且新开始的这个事务与原有事务无关,而NESTED则是当前存在事务时会开启一个嵌套事务,在NESTED**情况下,父事务回滚时,子事务也会回滚,而REQUIREDNEW情况下,原有事务回滚,不会影响新开启的事务

NESTED和REQUIRED的区别:

REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否catch异常,事务都会回滚,而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚。

4、事务隔离级别

这个和数据库的一样

有五个,多的一个是使用数据库默认的隔离级别

1、读未提交,有脏读问题

2、读已提交,强调插入,解决脏读问题

什么是脏读?一个事务在修改了数据,但是未提交,其他事务能够获取到修改但未提交的数据,这时这个事务回滚了,则读到的数据是错误的,这就脏读。

读已提交就让事务B只能读取到已经提交的事务。但是还会出现不可重复读问题

什么是不可重复读?

读已提交不会限制写事务,也就是事务A在一个事务中多次读取同一行数据,这时有一个事务B在A事务还未结束的时候修改了该行数据,这时,A多次读到的数据就会不一致,使用可重复读隔离级别可解决。

3、可重复读,强调更新

实现原理:添加读锁,在读事务为提交的时候,写事务不能修改该行

但是会出现幻读现象

什么是幻读?

在可重复读隔离级别下,他并没有全表加锁,而是添加的行锁,例如一个事务在进行全表更新的时候,还未提交事务,这时有一个线程来了新增一条数据,这时A提交事务后发现漏修改了一条数据,好像出现幻觉一样

这个问题可以通过序列化解决。

4、序列化

mysql的默认隔离级别是读已提交,通过MVCC来解决不可重复读(不能解决幻读问题)问题。MVCC是多版本并发控制的缩写,主要是通过添加版本号实现的。每次更新数据都会添加一个版本号,当别的线程来的时候发现版本号和自己的不一致,这时就会重新执行一遍,直到版本号和自己预期的一致才进行修改。

5、spring事务什么时候会失效?

1、bean对象没有被spring容器管理

2、方法的访问修饰符不是public

3、自身调用问题

4、数据源没有配置事务管理器

5、数据库不支持事务

6、异常被捕获

7、异常类型错误或者配置错误

5、spring其他相关问题

1、spring中使用到的设计模式

1、工厂模式,spring中使用BeanFactory来创建对象

2、代理模式:在spring中的AOP中有使用到,spring使用jdk和cglib来创建代理对象

3、适配器模式:在springAOP中使用适配器将各种advisor转换为MethodInterceptor,以及在MVC中使用适配器去找到执行前面Mapping返回Handle

4、模板方法模式:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate。我知道的是Springboot中的启动类run方法,还有初始化容器的refresh方法。

5、观察者模式:这个就是事件发布模式,在springboot中run方法执行期间会发送许多事件,他会根据事件来执行相应的方法。以及在容器初始化的时候也会发送事件

6、责任链模式:DispatcherServlet 中的 doDispatch() 方法中获取与请求匹配的处理器getHandleMapping

HandlerExecutionChain,this.getHandler() 方法的处理使用到了责任链模式。

2、ApplicationContext与BeanFactory区别

1、首先呢APplicationContext继承了BeanFactory,所以ApplicationContext肯定比BeanFactory更加强大

2、ApplicationContext还继承了MessageSource接口,能够实现国际化

3、继承了APplicationEvenPublisher,能够发送消息。比如在bean容器初始化时就会发送容器初始化成功的消息,实现了ApplicationListener的Bean都会收到这个消息, 能够根据消息做出回应。

4、还继承了ResourceLoader能够加载多个resource

3、Bean和Component注解区别

Bean注解是用在方法上面的,一般用来注册第三方的Bean,且这个配置类要加上Configuration注解。而Component注解一般是注册我们自定义的bean,一般通过类路径来扫描到,或者使用ComponentScan注解来指定。

4、Repository和Mapper区别

@Repository是spring的注解,单独使用是没有用的,用于声明一个bean,但是他是放在接口上,所以这个bean是没用的。spring中在mapper接口上写一个@Repository注解,只是为了标识,要想真正是这个接口被扫描,必须使用@MapperScannerConfigurer

而Mapper是mybatis提供的注解,在spring程序中,mybatis需要找到对应的mapper,在编译时生成动态代理类,与数据库进行交互,这时需要用到@Mapper注解

但是有时候当我们有很多mapper接口时,就需要写很多@Mappe注解,这样很麻烦,有一种简便的配置化方法便是在启动类上使
用@MapperScan注解。

5、ImportBeanDefinitionRegistrar

https://blog.csdn.net/baidu_19473529/article/details/90613661

ImportBeanDefinitionRegistrar接口是也是spring的扩展点之一,它可以支持我们自己写的代码封装成BeanDefinition对象;实现此接口的类会回调postProcessBeanDefinitionRegistry方法,注册到spring容器中。把bean注入到spring容器不止有 @Service @Component等注解方式;还可以实现此接口。

使用方式和选择器一样,将这个接口的实现类加入@Import中,这样在refresh调用处理invokeBeanFactoryPostProcessors时先加载配置类即@Configuration的ConfigurationClassPostProcessor后处理器(这个处理器会去处理@Configuration, @Component, @ComponentScan, @Import, @Bean),再去解析@Import注解中导入的注册器类,从而将注册器中定义的beandefine加入bean定义集合中。

6、谈谈你对spring boot Starter的理解

https://blog.csdn.net/qq_27828675/article/details/115700493

因为spring是一个很火的一个框架,其他第三方框架想要整合进spring中,就可以去创建一个starter,这个starter中有类似于spring自动装配的文件spring.factories,这个文件中包含了这个框架需要创建的类,这些类就是一些配置类,让spring能够使用这些类来进行操作第三方框架。

starter就好像一个容器,事先为我们定义好了一些类,使我能能够开箱即用。

命名规范:官方命名规范为:spring-boot-starter-xxx

第三方命名为:xxx-boot-starter

为什么添加了一个starter之后,他能够自动添加bean到spring容器中?

这时因为在添加了starter之后还要添加一个注解在springboot的启动类中,这个注解就是类似spring中自动配置注解@EnableAutoConfiguration,只不过他换了一个名字叫@EnableFeignClients,这里同样包含了一个@Import注解来导入选择器类来进行选择要自动配置的bean

这段是错的

真正的应该是:添加一个starter以后,这个starter中有同时导入了spring-boot-autoconfigure这个jar包,在MATA-INF/spring.factories中定义了要加载的自动配置类,在springboot自动配置的时候,会使用springfactoriesloader来扫描所有jar包中MATA-INF/spring.factories中的配置类,因此配置类中的bean就能加载到容器中,对于一些不是starter的jar包,想要实现自动配置,只要使用import注解,将配置类导入也能实现自动配置类的效果,只不过要使用jar包中提供的一个形如@Enablexxx的注解,这个注解中就包含了import注解, 这就相当于多了一步手动指定要要导入的配置类而已。

https://www.bilibili.com/video/BV1tw411f79E?p=86&spm_id_from=pageDriver&vd_source=7943439cc99094bee6c5f0fe67178c3d

这个网址就是说明这个情况的。

那为什么添加mybaits-plus的时候不需要添加这个注解呢?

因为它是starter,不是starter才需要这些注解

其实也可以不加这个注解,只要那个starter中有一个自动配置类即可,就是添加了@Configuration注解的类。springboot在启动过程中会自动加载一个bean工厂后处理器ConfigurationClassPostProcessor,这个后处理的方法会去查找所有被@Configuration注解标注的类,并将配置类中的@bean注解标注的方法生成bean对象。

因为这个处理器会找到对应configuration类,进行自动装配。

新疑问:究竟是谁来加载@configuration的,是ConfigurationClassPostProcessor还是spring的那个springfactoriesloader来加载项目下的所有starter的spring.factories来加载xxxAutoConfiguration类的

解答:

这个是ConfigurationClassPostProcessor上面的注释

image-20220810222026481

p406根据书上的所说的:是使用ConfigurationClassPostProcessor(自动生成的一个后处理器)来调用AntoConfigurationImportSelector中的selectImports方法,进而调用到@Import注解中传入的参数xxxSelector,就是选择器类,进而使用springfactoriesloader来加载所有JAR中的spring.factories定义的类。

在启动类上有ComponentScan注解,遇到这个注解spring就会去自动注册ConfigurationClassBeanPostProcessor这个bean工厂后处理器,之后就会去调用loader来加载spring.factories中的类,使用反射来创建实例,之后根据@bean注解来创对应的实例加入容器中,实现自动配置。

7、spring-boot-starter-parent

这个是一个版本控制坐标,里面只是定义常用starter的版本,比如说mysql驱动、redis、amqp、web等等,使用这个以后,我们导入maven坐标直接不用写版本号了,省得版本冲突。

就好像别人给你配好了这一套能够正常运行,直接用就好了。

但是这里并没有真正的导入依赖,只是提供版本号而已,我们还需要手动使用标签来进行引入。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.6</version>
</dependency>

6、springBoot

1、run方法与构造方法流程

在springboot的启动类中,首先会去执行springapplication的构造器方法,他的构造器主要做了

org\springframework\boot\spring-boot\2.2.2.RELEASE\spring-boot-2.2.2.RELEASE.jar!\META-INF\spring.factories

1、判断应用类型

2、加载/META-INF/springfactories中定义的的初始化器,这些初始化器主要用来在初始化容器之前做一些事情。

3、加载/META-INF/springfactories中定义的的监听器,这些监听器会监听在启动过程中发布的事件。

4、推断启动类

执行完构造方法之后,他就会去执行run方法

run方法主要做了

1、加载/META-INF/springfactories中定义的的事件发布器

2、将启动类中给的arg参数和一些命令行参数等一些配置文件环境封装起来

3、打印banner

4、加载org\springframework\boot\spring-boot\2.2.2.RELEASE\spring-boot-2.2.2.RELEASE.jar!\META-INF\spring.factories中的Failure Analyzers,用来处理在初始化容器时出现的一些错误

5、准备容器,根据前面获取到的配置信息准备容器,设置环境对象,配置监听器,加载资源

6、refresh容器,这一步和spring中的基本类似,只不过多了一个自动配置,这是一个bean的后处理器来实现的。

7、执行runner,这一步执行了实现了CommandLineRunner ApplicationRunner接口的方法,方法作用根据业务来决定,可以用来数据的预加载

8、发布容器准备完成事件。

image-20220711130520704

public ConfigurableApplicationContext run(String... args) {
    //记录初始化开始时间
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
    //容器
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    //设置一些属性到系统属性中
   configureHeadlessProperty();
    //1、获取事件发布器,和构造方法中不同,构造方法中的监听器,这个类的实现类叫做EventPublishingRunListener,也是通过springfactoriesloader去读取/META_INF/springfactories设置好的事件发布器
   SpringApplicationRunListeners listeners = getRunListeners(args);
    //事件发布器发布application staring事件
   listeners.starting();
   try {
       //2、封装启动类中传递的参数arg
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
       //准备 Environment 添加命令行参数,就是设置一些参数
       //3先获取默认环境变量源,4同一变量名规则,5添加其他变量源(application.yml等),6是将配置绑定到SpringApplication对象中
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
       //7、打印banneer
      Banner printedBanner = printBanner(environment);
       //8、根据应用类型创建容器
      context = createApplicationContext();
       //9、异常处理器,发生异常的时候调用这些处理器
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
       //10、准备容器,会发布一个容器初始事件。只是一个空的篮子
       prepareContext(context, environment, listeners, applicationArguments, printedBanner);
       //11、refresh方法,这个方法中有一个bean后处理器去实现了bean的自动装配,看下面
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

2、自动配置

回答:在springboot的启动类中,有一个叫做springbootapplication的注解,他是一个复合注解,由@EnableAutoConfiguration、@ComponentScan、@SpringConfiguration注解组成,其中@EnableAutoConfiguration注解是开启自动配置的注解。而@EnableAutoConfiguration注解中又是一个复合注解,他里面有个@Import注解导入一个实现了ImportSelecter接口的实现类,这个实现类中的方法会去使用springfactoryLoader这个加载器去读取/META-INF/下的spring-factories文件,根据条件进行过滤,返回需要自动装配的类的名字。

在springboot的启动方法run方法中,在后面的refresh方法的invokeBeanFactoryPostProcessors会去执行ConfigurationClassPostProcessor这个bean工厂后置处理的方法,这个方法会解析被@Configuration注解标识的类,而我们的springboot启动类上的@SpringbootApplication注解中的EnableAutoConfiguration中又包含了@Import注解,这个注解的作用是导入属性中选择器返回的类名集合,一般这个选择器返回的是第三方配置类,所以我们就自动将第三方的beanDefine加入了容器中。之后经历bean的初始化依赖注入之后我们就能用了。

更正ConfigurationClassPostProcessor并不会去解析被@Configuration注解标识的类,而是通过各种调用最终使用springfactoriesloader来加载spring.factoies中的定义的类。

解析@Bean注解的类是ConfigurationClassParser来进行的。

@Configuration注解作用只是用来存放@bean方法的。

image-20220810225317561

List<String> configurations = new ArrayList<>(
      SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));

选择器使用是SpringFactoriesLoader来加载,这个加载器很强大,能够扫描jar包中的META-INF/spring.factories文件,这个文件定义了很多自动配置类。找到key为getSpringFactoriesLoaderFactoryClass()返回值(EnableAutoConfiguration.class)的value,将他们的名字返回给@Import注解,这样import注解就知道要导入哪个bean了。

@Import先默认解析的是第三方的bean,也就是选择器中的第三方的自动配置类,且spring默认允许bean被覆盖,因此我们本项目中的bean会覆盖掉第三方的bean

遇到同名的bean,spring会优先选择我们本项目中bean。因为spring中bean同名会报错,如果我们更改了BeanFactory不可被覆盖,则先加载的第三方这样就会用不到我们自定以的数据源了,这是我们不希望看到的。

我们可以更换选择器的实现接口,将ImportSelector改成DeferedImportSelector,这样第三方的自动配置类就会慢于我们自定义的配置类了,前提是不允许bean覆盖。

image-20220712005421391

3、springApplication的构造方法

image-20220723095746098

image-20220723095753835

image-20220723095816243

image-20220723095821252

真正都构造方法,主要做了四件事情。

image-20220723095826914

image-20220723095835050

image-20220723095737233

第二次查证

lassParser来进行的。

@Configuration注解作用只是用来存放@bean方法的。

[外链图片转存中…(img-4LsPvUlG-1668352034494)]

List<String> configurations = new ArrayList<>(
      SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));

选择器使用是SpringFactoriesLoader来加载,这个加载器很强大,能够扫描jar包中的META-INF/spring.factories文件,这个文件定义了很多自动配置类。找到key为getSpringFactoriesLoaderFactoryClass()返回值(EnableAutoConfiguration.class)的value,将他们的名字返回给@Import注解,这样import注解就知道要导入哪个bean了。

@Import先默认解析的是第三方的bean,也就是选择器中的第三方的自动配置类,且spring默认允许bean被覆盖,因此我们本项目中的bean会覆盖掉第三方的bean

遇到同名的bean,spring会优先选择我们本项目中bean。因为spring中bean同名会报错,如果我们更改了BeanFactory不可被覆盖,则先加载的第三方这样就会用不到我们自定以的数据源了,这是我们不希望看到的。

我们可以更换选择器的实现接口,将ImportSelector改成DeferedImportSelector,这样第三方的自动配置类就会慢于我们自定义的配置类了,前提是不允许bean覆盖。

[外链图片转存中…(img-akeeXNpS-1668352034495)]

3、springApplication的构造方法

[外链图片转存中…(img-gMhey0Fh-1668352034496)]

[外链图片转存中…(img-KwNZXYHA-1668352034496)]

[外链图片转存中…(img-UkYHinUf-1668352034496)]

[外链图片转存中…(img-SkyE8eRT-1668352034496)]

真正都构造方法,主要做了四件事情。

[外链图片转存中…(img-lyupSlbc-1668352034497)]

[外链图片转存中…(img-4dY7FeeY-1668352034497)]

[外链图片转存中…(img-95NMChkW-1668352034498)]

第二次查证

在启动类上有ComponentScan注解,遇到这个注解spring就会去自动注册ConfigurationClassBeanPostProcessor这个bean工厂后处理器,之后就会去调用loader来加载spring.factories中的类,使用反射来创建实例,之后根据@bean注解来创对应的实例加入容器中,实现自动配置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值