spring框架(含springboot):你感受到春天的温暖了吗?

前言

用了springboot很久,也看过源码,但是一直没有机会专门总结一下spring很多关键技术点和工作流程等,借秋招之际,准备好好复盘一下,姑妄试之。

正文

1.Spring的启动流程

Spring的启动过程实际上就是IOC容器初始化以及载入Bean的过程。Spring是通过web.xml文件中配置的ContextLoaderListener监听器来进行容器初始化的。web.xml中配置各种监听、过滤器,servlet等,一般web.xml文件配置如下:

<listener>  
     <listener-class>org.springframework.web.context.ContextLoaderListener
     </listener-class>  
</listener> 
<context-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>classpath:applicationContext.xml</param-value>  
</context-param> 

对于IOC容器初始化,可以分为以下三步(留个印象):

  • step1:创建一个WebApplicationContext
  • step2:配置并且刷新Bean
  • step3:将容器初始化到servlet上下文中

第一步中,ContextLoaderListener监听器(上下文加载监听器,spring容器的启动就从这里开始)继承了ContextLoader,并且重写了ServletContextListener中的contextInitialized和contextDestroyed方法。在contextInitialized方法中,通过调用父类(ContextLoader)的initWebApplicationContext方法进行容器创建:

简单来说,ContextLoaderListener是一个实现了ServletContextListener接口的监听器,在启动项目时会触发它的contextInitialized方法(该方法主要完成ApplicationContext对象的创建),在关闭项目时会触发contextDestroyed方法(该方法会执行ApplicationContext清理操作)。

@Override
public void contextInitialized(ServletContextEvent event) {
    
    
    initWebApplicationContext(event.getServletContext());
}

关于initWebApplicationContext的源码:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    
    
    //1:判断当前容器是否存在,如果存在则报容器已经存在的异常信息
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
    
    
      throw new IllegalStateException(
          "Cannot initialize context because there is already a root application context present - " +
          "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }
    Log logger = LogFactory.getLog(ContextLoader.class);
    //下面这个日志就是我们经常在启动Spring项目时看到的日志信息: 
    //Initializing Spring root WebApplicationContext
    //Root WebApplicationContext: initialization started
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
    
    
      logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
    
    
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      //如果当前容器为null,则创建一个容器,并将servletContext上下文作为参数传递进去,
      if (this.context == null) {
    
    
      	//**第一步,创建一个webapplicationcontext**
        this.context = createWebApplicationContext(servletContext);
      }
       //判断当前容器是否为可配置的,只有是Configurable的容器,才能进行后续的配置
      if (this.context instanceof ConfigurableWebApplicationContext) {
    
    
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
        if (!cwac.isActive()) {
    
    
          // The context has not yet been refreshed -> provide services such as
          // setting the parent context, setting the application context id, etc
          if (cwac.getParent() == null) {
    
    
            // The context instance was injected without an explicit parent ->
            // determine parent for root web application context, if any.
            ApplicationContext parent = loadParentContext(servletContext);
            cwac.setParent(parent);
          }
           //**三步走中的第二步:配置并且刷新当前容器**
          configureAndRefreshWebApplicationContext(cwac, servletContext);
        }
      }
       //将配置并且刷新过的容器存入servlet上下文中,并以WebApplicationContext的类名作为key值

    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      if (ccl == ContextLoader.class.getClassLoader()) {
    
    
        currentContext = this.context;
      }
      else if (ccl != null) {
    
    
        currentContextPerThread.put(ccl, this.context);
      }

      if (logger.isDebugEnabled()) {
    
    
        logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
      }
      if (logger.isInfoEnabled()) {
    
    
        long elapsedTime = System.currentTimeMillis() - startTime;
        logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
      }
       //返回创建好的容器
      return this.context;
    }
    catch (RuntimeException ex) {
    
    
      logger.error("Context initialization failed", ex);
    
//第三步,放入servlet context中  
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
      throw ex;
    }
    catch (Error err) {
    
    
      logger.error("Context initialization failed", err);
    
	// Binds an object to a given attribute name in this servlet context.
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
      throw err;
    }
  }

总结:

  1. 创建 WebApplicationContext;
  2. 加载对应的Spring文件创建里面的Bean实例;
  3. 将WebApplicationContext放入 ServletContext(Java Web的全局变量)中。【看到这里,一定有和多人和我一样疑惑servletContext的定义,或者概念模糊了,看这里:传送门

一、第一步,关于createWebApplicationContext()方法的源码:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    
    
    //首先来确定context是由什么类定义的,并且判断当前容器是否为可配置的
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    
    
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
          "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    //创建可配置的上下文容器
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  }

关于这其中determineContextClass()方法的源码:

protected Class<?> determineContextClass(ServletContext servletContext) {
    
    
    //首先从web.xml中查看用户是否自己定义了context
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    //如果有,则通过反射创建实例
    if (contextClassName != null) {
    
    
      try {
    
    
        return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
      catch (ClassNotFoundException ex) {
    
    
        throw new ApplicationContextException(
            "Failed to load custom context class [" + contextClassName + "]", ex);
      }
    }
    /*如果没有,则去defaultStrategies里面取【defaultStrategies是Propertites类的/对象,在ContextLoader中的静态代码块中初始化的;具体可看下面】;默认容器是XmlWebApplicationContext*/
  else {
    
    
   contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
      try {
    
    
        return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
      }
      catch (ClassNotFoundException ex) {
    
    
        throw new ApplicationContextException(
            "Failed to load default context class [" + contextClassName + "]", ex);
      }
    }
  }

这个方法的主要工作就是,获取初始化参数contextClass,如果我们在web.xml中配置了该参数,则使用ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()),即前面提到的上下文对象。如果没有在web.xml中配置该参数,则会进入else处理逻辑,加载默认的配置项,defaultStrategies的初始化代码为:

static {
    
    
        // Load default strategy implementations from properties file.
        // This is currently strictly internal and not meant to be customized
        // by application developers.
        try {
    
    
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
    
    
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
        }
    }

读取spring默认的上下文类,全文搜索一下这个DEFAULT_STRATEGIES_PATH文件:

org.springframework.web.context.WebApplicationContext= \\
org.springframework.web.context.support.XmlWebApplicationContext

默认的WebApplicationContextd的类型为XmlWebApplicationContext。determineContextClass方法返回后就可以利用反射来加载这个applicationContext上下文对象并且返回。
之后返回createApplicationContext方法继续执行逻辑,首先判断指定的webApplicationContext是否是ConfigurableWebApplicationContext类型,如果不是则抛出异常终止容器启动,如果是则实例化该webApplicationContext对象,这里我们就是创建XmlWebApplicationContext对象,并返回到initWebApplicationContext方法中。到此整个spring容器的初始化就完成了,也就是容器启动完成了。至此,上述第一步即完成。

2.Spring IOC如何实现 (DefaultListAbleBeanFactory)

二、第二步,加载对应的Spring文件创建里面的Bean实例(接前问)

主要的方法就是configureAndRefreshWebApplicationContext()方法,参数是上一步确定的xmlWebApplicationContext:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    
    
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
    
    
            // The application context id is still set to its original default value
            // -> assign a more useful id based on available information
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
    
    
                wac.setId(idParam);
            }
            else {
    
    
                // Generate default id...
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        wac.setServletContext(sc);
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
    
    
            wac.setConfigLocation(configLocationParam);
        }

        // The wac environment's #initPropertySources will be called in any case when the context
        // is refreshed; do it eagerly here to ensure servlet property sources are in place for
        // use in any post-processing or initialization that occurs below prior to #refresh
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
    
    
            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
        }

        customizeContext(sc, wac);
        wac.refresh();
    }

customizeContext(sc, wac)初始化定制的容器,在此就不做深入,重点看一下wac.refresh()方法,它就是spring的各种bean创建的入口了。

以AbstractApplicationContext的refresh()为例,该方法的主要内容如下

@Override
    public void refresh() throws BeansException, IllegalStateException {
    
    
        synchronized (this.startupShutdownMonitor) {
    
    
            // Prepare this context for refreshing.
            //刷新前预处理工作:
            //1-设置启动的时间戳:
			//2-设置容器为未关闭状态
			//3-设置容器为活动状态
			//4-在上下文环境中初始化任何占位符属性源 initPropertySources() 给子类留空,可以在子类自定义个性化的属性设置
			//5-在环境变量中验证必须的属性  用属性解析器resolver校验属性合法等
			//6-初始化上下文事件容器(本质是一个LinkedHashSet)保存容器中早期events,等派发器ok就可以派发出去
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            //获取bean工厂:
	            //1.refreshBeanFactory(),刷新beanFactory,该方法的具体实现在
	            //AbstractRefreshableApplicationContext中,如果已经存在了
	            //beanFactory,先销毁所有的单例bean,然后关闭beanFactory,接着就开始创
	            //建beanFactory,
	            
            	//1.2.refreshBeanFactory(),在GenericApplicationContext类中创建一个
            	//bean factory对象DefaultListAbleBeanFactory,并设置序列化ID

				//2.getBeanFactory():返回刚创建好的BeanFactory。
				
				//3.定制beanFacotory属性(allowBeanDefinitionOverriding和
				//allowCircularReferences),之后这开始加载bean定义-
				//loadBeanDefinitions(beanFactory),加载bean定义这里就变得比较灵活
				//了,不同的webApplocationContext都有自己的实现方式
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//以上创建了一个DefaultListAbleBeanFactory
			*******************************************
			
            // Prepare the bean factory for use in this context.
            //设置刚创建好的beanfactory
            	//1.设置beanfactory的类加载器、支持表达式解析器
            	//2.添加部分的beanPostProcessor,比如		
            	//applicationContextAwareProcessor
            	//3.设置忽略的自动装配的接口,比如EnvironmentAware等
            	//注册可以解析的自动装配,可以在任何组件中自动注入:BeanFactory、
            	//ApplicationContext等
            	//4.添加ApplicationListenerDetector功能的BeanPostProcessor
            	//5.注册一些能用的组件:判断是否包含environment、systemProperties、
            	//systemEnvironment(map),如果存在则进行单例注册
            prepareBeanFactory(beanFactory);

            try {
    
    
                // Allows post-processing of the bean factory in context subclasses.beanfactory准备完成后进行的后置处理工作
                	//1.子类可以重写这个方法beanfactory准备好后做进一步设置
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                //调用上下文中注册为bean的工厂处理器。执行beanfactoryPostProcessor:在BeanFactory标准初始化之后执行。
                //两个接口:BeanFactoryPostProcessor和
                //BeanDefinitionRegistryPostProcessor(两者中的方法会依次实现)
                //1)先获取所有的BeanDefinitionRegistryPostProcessor并按照优先级执行
                //(实现了PriorityOrdered接口,再是Ordered接口,最后是无实现接口)的
                //PostProcessBeanDefinitionRegistry方法(执行过的processor方法会放入ProcessedBeans中)
                //2)再执行BeanFactoryPostProcessor的postProcessBeanFactory方法
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that **intercept bean creation**.注册bean的后置处理器
                //1.按照优先级实例化和调用所有已注册(保存不执行)的BeanPostProcessor
                //(BeanPostProcessor、InstantiationAwareBeanPostProcessor、
                //MergedBeanDefinitionPostProcessor...都像前面一样有优先级,注册就是添加到beanfactory中)
				//如果给出显式顺序,则按顺序执行
				//必须在应用程序bean的任何实例化之前调用
				//其中的详细流程包含以下几步:
				//获取所有类型为BeanPostProcessor的bean的name的数组
				//添加BeanPostProcessorChecker子类的后置处理器
				//分离实现了PriorityOrdered、Ordered和其他接口的BeanPostProcessors
				//注册实现了PriorityOrdered的BeanPostProcessors
				//注册实现了Ordered的BeanPostProcessors
				//注册常规的BeanPostProcessors
				//最后重新排序,然后重新注册所有的BeanPostProcessors
				//将检测内部bean的后处理器重新注册为ApplicationListeners(监听),并将其移动到处理器链的末尾
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                //初始化MessageSource组件,主要为做国际化功能;消息绑定,消息解析做准备
                //1)获取beanfactory
                //2)看里面有没有id和类型为messagesource的组件,有则赋值给MessageSource,
                //无则用默认的DelegatingMessagSource,能按照区域获取配置中的值
                //3)把创建好的MessageSource注册在容器中,以后获取国际化配置文件的时候,可
                //以自动注入MessageSource组件
                initMessageSource();

                //Initialize event multicaster for this context.初始化事件派发器
                //首先会查看当前beanFactory中是否包含了名为
                //applicationEventMulticaster的bean,如果有则通过名称beanFactory中获
                //取这个bean,赋给当前容器的applicationEventMulticaster对象,无则创建
                //一个SimpleApplicationEventMulticaster,并添加到beanfactory中,可以自动注入
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                //留给子类重写,在容器中可以自定义逻辑
                onRefresh();

                // Check for listener beans and register them.注册进容器
                //从容器中拿到所有的ApplicationListener
                //将每个监听器添加到事件派发器中
                //派发之前步骤产生的事件earlyApplicationEvents
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.初始化所有剩下的单实例bean。
                //**整个容器最核心的部分**
                //1. 获取容器中所有bean,依次进行初始化创建对象
                //2.获取bean的定义信息:RootBeanDefinition
                //3.只作用于单实例、懒加载、并且不是抽象的bean
                	//1)判断是否为factory bean(是否实现FactoryBean接口)
                	//2)如果不是,用getBean(beanName)创建对象
                		//调用doGetBean
                		//先获取缓存中的单实例bean(之前被创建过的,存在一个map里)
               		//3)缓存中获取不到,就开始bean的创建流程
               		//4)标记当前bean已经被创建
               		//5)获取bean的定义信息
               		//6)获取当前bean依赖的其他bean(depends-on),利用getBean()创建
               		//7)启动单实例bean的创建流程
               			//1)createBean()
               			//2)resolveBeforeInstantiation():给个机会(返回代理对象)执行
               			//InstantiationAwareBeanPostProcessor的postProcessBeforeInstatiation方法,有返回值则继续执行postProcessAfterInstatiation方法
               			//3)如果上式没有返回代理对象 调用4
               			//4)调用doGetBean创建bean
               				//1)创建Bean实例:createBeanInstance()方法
               					//1)利用工厂方法或者对象的构造器创建出Bean实例
               				//2)applyMergedBeanDefinitionPostProcessors
               					//依次调用MergedBeanDefinitionPostProcessor的PostProcessMergedBeanDefinition方法
             		   	   //3)populateBean():bean属性赋值
             		   	   		//1)赋值之前,拿到并执行InstantiationAwareBeanPostProcessor的postProcessAfterInstatiation方法	
             		   	   		//2)拿到并执行InstantiationAwareBeanPostProcessor的postProcessPropertyValues方法
             		   	   		===========赋值之前===================
             		   	   		
             		   	   		//3)应用bean属性值,为属性利用setter方法进行赋值:applyPropertyValues()
            		   	   //4)【Bean初始化】initializeBean
            		   	   		//1)invokeAwareMethods():执行xxxAware接口的方法	
            		   	   		//2)【执行后置处理器初始化之前的方法】applyBeanPostProcessorsBeforeInstantiation(),调用postProcessBeforeInstantiation方法。
            		   	   		//3)【执行初始化方法】invokeInitMethods:
            		   	   			1)判断是否为InitializingBean接口的实现,是则执行接口规定的方法。
            		   	   			2)有自定义初始化方法则执行
           		   	   			//4)【执行后置处理器初始化之后的方法】applyBeanPostProcessorsAfterInstantiation(),调用postProcessAfterInstatiation方法。	
          		   	   	 //5)【注册bean的销毁方法】RegisterDisposable()方法
         		   	  //5)**将创建的bean添加到缓存中SingletonObjects(map实现),而这些						
         		   	  ***************************************
         		   	  //缓存实际就是IOC容器,是很多的map**	
         		   	  ******************************
      		   	 //所有的bean都利用getBean创建好之后:检查所有的Bean是否是SmartInitializeingsingleton接口的,如果是则执行afterSingletonsInstantiated方法。
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.完成BeanFactory的初始化创建工作
                //初始化生命周期相关的后置处理器LifeCycleProcessor,可以自定义子类,重写方法,完成拦截。
                //默认从容器中找,没有则用default的。
                //加入到容器中
                //onrefresh()
                //发布容器refresh完成事件publishEvent
                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();
            }
        }
    }
    

核心:
1.spring容器启动的时候,首先保存所有注册进来的Bean定义信息BeanDefinition
a.XML注册bean
b.注解注册Bean @Service @Component @Bean
2.spring容器会再合适的时机创建bean:
a.用到这个Bean的时候,利用getBean创建这个Bean,创建好以后保存在容器中
b.统一创建剩下的所有Bean的时候:finishBeanFactoryInitialization()方法
3.后置处理器
每一个bean创建完成后,都会使用各种后置处理器进行处理,来增强bean的功能。比如:
AutoWiredAnnotationBeanPostProcessor:处理自动注入
AnnotationAwareAspectJAutoProxyCreator:AOP功能(代理对象)
4.事件驱动模型
ApplicationListener:事件监听
ApplicationEventMulticaster:事件派发器
拓展

再次总结:

  1. 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
  2. 其 次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时 contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是 XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。
  3. 在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;

    补充:
  4. 之后,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这里是DispatcherServlet,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。【Servlet(Server Applet),全称Java Servlet。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。】
  5. 在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE 先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。
  6. 有了这个 parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是 mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为 Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet 就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些 bean。【拓展

最后,附上完整流程图
在这里插入图片描述

3.Spring AoP如何实现,有什么作用

没想到你还能坚持到这里?缘分!
那么在送你一份大礼,spring第二块难啃的骨头——AOP,没有基础的先去看这篇文章,虽然有些地方不是非常严谨但是通俗易懂,可以作为入门理解,后面细节可以慢慢理解透彻。

面向切面编程是指在程序运行期间动态将某段代码切入到指定方法的指定位置运行的编程方式。

以下所有内容都将围绕上述定义进行讲解!
AOP的实现技术有多种,其中与Java无缝对接的是一种称为AspectJ的技术。以下内容是基于AspectJ源码的解析。

使用:

  1. 将业务类和切面类都加入到容器中,@Aspect
  2. 在切面类的每个通知方法上标注通知注释,告诉此方法何时何位置执行@Before @After @AfterReturing @AfterThrowing @Around
  3. 开启基于注解的aop模式,@EnableAspectJAutoProxy
    具体的使用例子可以参考这个--------》例子

AOP原理:
1)@EnableAspectJAutoProxy注解就是给容器中导入了一个AspectJAutoProxyRegistrar,利用它在容器中自定义注册bean:AnnotationAwareAspectJAutoProxyCreator
2)AnnotationAwareAspectJAutoProxyCreator多层实现了SmartInstantiationAwareBeanPostProcessor(bean初始化前后执行方法)和BeanFactoryAware接口(可传入bean factory)【工作量一变三

流程:
1)传入配置类,创建IOC容器
2)注册配置类,调用refresh()刷新容器
3)registerBeanPostProcessors()注册bean的后置处理器用来拦截bean的生成

  1. 先获取容器中已经定义了的但还没创建对象的后置处理器(比如autowiredAnnotationProcessor)
  2. 给容器中加入其他的beanpostprocessor
  3. 优先注册实现了priorityOrder接口的beanpostProcessor。。。再依序
  4. 注册beanPostProcessor,实际就是创建它并加入容器(流程为创建bean,bean赋值,在初始化bean
  5. 初始化bean前后执行beanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法。

至此创建和注册了AnnotationAwareAspectJAutoProxyCreator组件


之后,因为AnnotationAwareAspectJAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor接口,所以它会在bean创建之前就有一个拦截(before/afterInstantiation),尝试返回一个代理对象,而其他的beanpostProcessor是在bean对象完成之后初始化前后才调用其对应的前后(before/afterInitialization)方法。

1.在每一个bean创建之前,调用postProcessBeforeInstantiation方法
a.判断当前bean是否在advisedBean(需要增强的beans)中
b.判断是否为基础类的、PointCut、Advice、Aspect的Bean
c.判断是否需要跳过
2.在每一个bean创建之后,调用postProcessAfterInstantiation方法
a.获取bean的所有能在bean中使用的增强器(即通知方法),并排序
b.这些可用的增强器(eligibleAdvisors)换了个名字,拦截器(Interceptor)!
c.并且这些需要增强的bean也换了个名字,advisedBean!
d.创建当前advisedBean的代理对象:把所有的增强器放入proxyFactory-》创建动态代理对象(jdk原生 需要业务类实现接口;cglib 可以利用父子继承)
3.最终,给容器中返回使用了cglib增强后的组件(比如对象业务类 calculator)的代理对象。

最最后,使用的就是增强后的代理对象了,如何指向目标方法的呢?

  1. 进入CglibAopProxy的intercept()方法

  2. 根据ProxyFactory(回忆下,之前步骤保存过啥?)获取拦截器链chain

    1. intercepterList
    2. 遍历所有增强器,用AdvisorAdaptor将其转为interceptor()
  3. 如果没有拦截器链,直接执行目标方法

  4. 如果有,则用以上信息创建一个CglibMethodInvocation对象(返回一个拦截器链),调用其proceed方法。

  5. 拦截器的触发过程:(非常巧妙,务必看源码)

    1. 只有在没有拦截器或者拦截器索引等于拦截器数组长度减一时,执行目标方法。
    2. 链式获取每一个拦截器,并执行其invoke方法,每一个拦截器都等待下一个拦截器执行目标方法结束以后才执行目标方法。

    在这里插入图片描述
    (截图来自于雷神的视频,受益良多,感恩。)

以上内容有一说一排版不咋地,但是却是AOP的精妙所在,本部分没有结合源码,建议大家先收藏再去看看源码部分,回头再看这里,你会豁然开朗。
推荐文章---->【拓展1】拓展2

拓展:
一. BeanPostProcessor—》bean的后置处理器—》bean创建对象初始化前后拦截工作
二. BeanFactoryPostProcessor----》beanFactory的后置处理器—》beanFactory标准初始化后,所有的beanDefinition已经保存进beanfactory但还未创建。这是我们可以利用BeanFactoryPostProcessor方法定制和修改BeanFactory。在这里插入图片描述
流程:

  1. ioc容器创建对象。
  2. refresh()中的invokeBeanFactoryPostProcessors(beanFactory),执行BeanFactoryPostProcessor。过程是,先在beanFactory中找到所有BeanFactoryPostProcessors并依次执行,之后才是bean的初始化工作。
    三、BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor—>postProcessBeanDefinitionRegistry方法—》在所有bean定义信息将被加载,但还未实例化时执行,所以是先于BeanFactoryPostProcessor执行的。这样我们可以额外添加自定义postprocessor并自定义bean定义信息。在这里插入图片描述
    流程:
  1. 创建ioc容器
  2. 先从容器中获取所有BeanDefinitionRegistryPostProcessor并依次执行postProcessBeanDefinitionRegistry方法
  3. 之后再执行postProcessBeanFactory

四、ApplicationListener—》监听容器中发布的事件,事件驱动模型开发,需要实现ApplicationListener接口,重写方法onApplicationEvent(ApplcaitionEvent event)
比如:
在这里插入图片描述
步骤:

  1. 写一个监听器监听某个事件(ApplicationEvent子类)
  2. 把监听器加入容器中
  3. 容器中有相关时间发布,则触发方法
  4. (可以用ApplicatinContext.publishEvent发布事件)
    原理:
  5. 创建容器对象 refresh()
  6. finishRefresh方法中,publishEvent()—》getApplicationEventMulticaster(),遍历容器中所有的Listener----》有异步执行的用executor执行—》没有的invokeListener(Listener,event)----》回调Listener的onApplicationEvent(event)方法(可以自定义需要listen哪些event)
    3.无论是用ac发还是自己发布事件,都是以上流程
    4.其他关于多播器(ApplicationEventMulticaster())初始化和先后顺序,以及监听器注册等,请看第一问详解。注意也可以用@EventListener注解(原理是利用EventListenerMethodProcessor解析注解,它实现了SmartInitialzingSingleton接口,初始化bean之后,执行它的AfterSingletonInstantiated()方法),之后全部创建结束后发布完成事件。

4.Spring事务传播机制有哪几种

(前面是引子,后面会回答问题)
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。

spring提供的统一接口如下:

Public interface PlatformTransactionManager()...{
    
      
    // 由TransactionDefinition得到TransactionStatus对象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滚
    Void rollback(TransactionStatus status) throws TransactionException;  
    } 

spring事务管理可以表示如下:
在这里插入图片描述
事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。TransactionDefinition接口内容如下:

public interface TransactionDefinition {
    
    
    int getPropagationBehavior(); // 返回事务的传播行为,本题问
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
} 

1)事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:
在这里插入图片描述
拓展

2)隔离级别定义了一个事务可能受其他并发事务影响的程度。在这里插入图片描述

编程式和声明式事务的区别:
Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。
简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

事务管理的原理:
在这里插入图片描述
核心就是再执行拦截器链时,如果发生异常就直接回滚。
在这里插入图片描述

5.Spring Bean的注入方式

注册:@Bean @Configuration @ComponentScan @Import @Component

注入:@Resource和@Autowired:
@Resource默认是使用byName进行装配,@Autowired默认使用byType进行装配。

6.Spring Bean的初始化过程(见第一问)

7.Spring如何解决循环依赖

spring单例对象的初始化大略分为三步:

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  3. initializeBean:调用spring xml中的init 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖field循环依赖。第一种直接抛出BeanCurrentlylnCreationException异常,第二种使用三级缓存。

/** Cache of singleton objects: bean name –> bean instance 一级缓存*/
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of early singleton objects: bean name –> bean instance 二级缓存*/
private final Map earlySingletonObjects = new HashMap(16);
/** Cache of singleton factories: bean name –> ObjectFactory 三级缓存*/
private final Map> singletonFactories = new HashMap>(16);
  • singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)
  • earlySingletonObjects :完成实例化但是尚未初始化的,提前加入的单例对象的Cache (二级缓存)
  • singletonObjects:完成初始化的单例对象的cache(一级缓存)

核心是以下AbstractBeanFactory里的doCreateBean()方法:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    
    
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
    
    
        if (!this.singletonObjects.containsKey(beanName)) {
    
    
        //在createBeanInstance之后,populateBean()之前,也就是说单例对象此时已经被创建
        //出来(调用了构造器)。这个对象已经被生产出来了,此时将这个对象提前曝光出来,让大家使用。
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

总结:
对于“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。【拓展

8.Spring如何实现懒加载

针对单实例bean,Spring提供了懒加载机制。所谓的懒加载机制就是可以规定指定的bean不在启动时立即创建,而是在后续第一次用到时才创建,从而减轻在启动过程中对时间和内存的消耗。
注解@Lazy 或者 配置文件default-lazy-init=“true”
这部分原理第一问都可以找到,贴出该部分代码一目了然(前提是你吃透了IOC)。

看下面finishBeanFactoryInitialization(beanFactory)中的初始化non-lazy-init bean的函数 preInstantiateSingletons():

public void preInstantiateSingletons() throws BeansException {
    
    
   // 所有beanDefinition集合
   List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
   // 触发所有非懒加载单例bean的初始化
   for (String beanName : beanNames) {
    
    
       // 获取bean 定义
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      // 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在Spring 容器
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
    
    
          // 判断是否是FactoryBean
         if (isFactoryBean(beanName)) {
    
    
                final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                boolean isEagerInit;
                if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
    
    
                   isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
    
    
                      @Override
                      public Boolean run() {
    
    
                         return ((SmartFactoryBean<?>) factory).isEagerInit();
                      }
                   }, getAccessControlContext());
                }
         }else {
    
    
             // 如果是普通bean则进行初始化依赖注入,此 getBean(beanName)接下来触发的逻辑跟
             // context.getBean("beanName") 所触发的逻辑是一样的
            getBean(beanName);
         }
      }
   }
}

9.Springboot启动流程(自动配置原理)

(以下截图均来自于雷丰阳老师的教学视频)
先来个总体流程,扫一眼,留个印象(无非以下两个步骤):
在这里插入图片描述

在这里插入图片描述

  1. 启动类入口
@SpringBootApplication
public class Application {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(Application.class, args);
        ****************  ****
    }
}
  1. @SpringBootApplication注解
@Target(ElementType.TYPE) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan(excludeFilters = {
    
     // 扫描路径设置(具体使用待确认)
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
    
...
}  

几个重要的事件回调机制:
配置在META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener

只需要放在ioc容器中:
ApplicationRunner
CommandLineRunner

启动流程:

1、创建SpringApplication对象

核心在执行此类的initialize(sources方法,实际是new了一个SpringApplication类,并配置其相关属性。

initialize(sources);
private void initialize(Object[] sources) {
    
    
    //保存主配置类,sources来自于主配置类的参数
    if (sources != null && sources.length > 0) {
    
    
        this.sources.addAll(Arrays.asList(sources));
    }
    //判断当前是否一个web应用
    this.webEnvironment = deduceWebEnvironment();
    
    //**从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来**
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    //从类路径下找到META-INF/spring.factories配置的所有ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //从多个配置类中找到有main方法的主配置类,确定一个主配置类
    this.mainApplicationClass = deduceMainApplicationClass();
}

在这里插入图片描述
在这里插入图片描述
以上主要工作就是在类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer和ApplicationListener,并保存。

2、运行run方法
执行配置好的类的SpringApplication中的run方法:

public ConfigurableApplicationContext run(String... args) {
    
    
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
    
   //*获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories获取************
   SpringApplicationRunListeners listeners = getRunListeners(args);
    //回调所有的获取SpringApplicationRunListener.starting()方法
   listeners.starting();
   try {
    
    
       //封装命令行参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      //准备环境
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
       		//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
       //打印图标
      Banner printedBanner = printBanner(environment);
       
       //创建ApplicationContext;决定反射创建web的ioc还是普通的ioc
       *********************************************
      context = createApplicationContext();
       
      analyzers = new FailureAnalyzers(context);
      
       //准备上下文环境;将environment保存到ioc中;而且applyInitializers();
       //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
         *******************
       //回调所有的SpringApplicationRunListener的contextPrepared();
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
       //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
       
       //刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版
       **********************
       //扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
      refreshContext(context);
       //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
       //ApplicationRunner先回调,CommandLineRunner再回调
      afterRefresh(context, applicationArguments);
      
       //所有的SpringApplicationRunListener回调finished方法
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
    
    
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
       //整个SpringBoot应用启动完成以后返回启动的ioc容器;
                                   *****************   
      return context;
   }
   catch (Throwable ex) {
    
    
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

BeanFactory 和ApplicationContext的区别
BeanFactory和ApplicationContext都是接口,并且ApplicationContext间接继承了BeanFactory。
BeanFactory是Spring中最底层的接口,提供了最简单的容器的功能,只提供了实例化对象和获取对象的功能,而ApplicationContext是Spring的一个更高级的容器,提供了更多的有用的功能。
ApplicationContext提供的额外的功能:获取Bean的详细信息(如定义、类型)、国际化的功能、统一加载资源的功能、强大的事件机制、对Web应用的支持等等。
**加载方式的区别:**BeanFactory采用的是延迟加载的形式来注入Bean;ApplicationContext则相反的,它是在Ioc启动时就一次性创建所有的Bean,好处是可以马上发现Spring配置文件中的错误,坏处是造成浪费。【拓展1】【拓展2

3、事件监听机制

配置在META-INF/spring.factories

ApplicationContextInitializer

public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
    
    
        System.out.println("ApplicationContextInitializer...initialize..."+applicationContext);
    }
}

SpringApplicationRunListener

public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
    
    

    //必须有的构造器
    public HelloSpringApplicationRunListener(SpringApplication application, String[] args){
    
    

    }

    @Override
    public void starting() {
    
    
        System.out.println("SpringApplicationRunListener...starting...");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
    
    
        Object o = environment.getSystemProperties().get("os.name");
        System.out.println("SpringApplicationRunListener...environmentPrepared.."+o);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
    
    
        System.out.println("SpringApplicationRunListener...contextPrepared...");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
    
    
        System.out.println("SpringApplicationRunListener...contextLoaded...");
    }

    @Override
    public void finished(ConfigurableApplicationContext context, Throwable exception) {
    
    
        System.out.println("SpringApplicationRunListener...finished...");
    }
}

配置(META-INF/spring.factories)

org.springframework.context.ApplicationContextInitializer=\
com.atguigu.springboot.listener.HelloApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=\
com.atguigu.springboot.listener.HelloSpringApplicationRunListener

只需要放在ioc容器中

ApplicationRunner

@Component
public class HelloApplicationRunner implements ApplicationRunner {
    
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
    
    
        System.out.println("ApplicationRunner...run....");
    }
}

CommandLineRunner

@Component
public class HelloCommandLineRunner implements CommandLineRunner {
    
    
    @Override
    public void run(String... args) throws Exception {
    
    
        System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));
    }
}

在这里插入图片描述

10.Spring和Springboot的区别

Spring Boot、Spring MVC 和 Spring 区别
Spring 框架整个家族家族,共同的基础是Spring 的ioc和 aop,ioc 提供了依赖注入的容器, aop解决了面向切面编程,然后在此两者的基础上实现了其他延伸产品的高级功能。
Spring MVC提供了一种轻度耦合的方式来开发web应用;它是Spring的一个模块,是一个web框架;通过DispatcherServlet, ModelAndView 和 View Resolver,开发web应用变得很容易;解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。
Spring Boot实现了auto-configuration自动配置(另外三大神器actuator监控,cli命令行接口,starter依赖),降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题,所以它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具;同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用。

11.SpringBoot的SPI机制是如何实现的

SPI的全称是Service Provider Interface, 即"服务提供商接口"。一个接口可以有很多种实现. 例如搜索,可以是搜索系统的硬盘,也可以是搜索数据库.系统的设计者为了降低耦合,并不想在硬编码里面写死具体的搜索方式,而是希望由服务提供者来选择使用哪种搜索方式, 这个时候就可以选择使用SPI机制。

普通SPI机制使用流程:
SPI机制已经定义好了加载服务的流程框架, 你只需要按照约定, 在META-INF/services目录下面, 以接口的全限定名称为名创建一个文件夹(com.north.spilat.service.Search), 文件夹下再放具体的实现类的全限定名称(com.north.spilat.service.impl.DatabaseSearch), 系统就能根据这些文件,加载不同的实现类。

 package com.north.spilat.main;
 import com.north.spilat.service.Search;
 import java.util.Iterator;
 import java.util.ServiceLoader;
 public class Main {
    
    
     public static void main(String[] args) {
    
    
         System.out.println("Hello World!");
         ServiceLoader<Search> s = ServiceLoader.load(Search.class);
                                   ******************//指定位置加载实体类
         Iterator<Search> searchList = s.iterator();
         while (searchList.hasNext()) {
    
    
             Search curSearch = searchList.next();
             curSearch.search("test");
         }
     }
 }

JDK的SPI逻辑:服务提供商按照约定,将具体的实现类名称放到/META-INF/services/xxx下, ServiceLoader就可以根据服务提供者的意愿, 加载不同的实现了, 避免硬编码写死逻辑, 从而达到解耦的目的。

SpringBoot的SPI机制
spring框架里面一定是有一个类似于ServiceLoader的类, 专门从META-INF/spring.factories里面的配置,加载特定接口的实现。

    /**
    * 参数type就是要加载的接口的class
    */
    private <T> Collection<? extends T>
    getSpringFactoriesInstances(Class<T> type) {
    
    
        // 直接调用重载方法getSpringFactoriesInstances
		return getSpringFactoriesInstances(type, new Class<?>[] {
    
    });
	}

	private <T> Collection<? extends T>
	        getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, 
			Object... args) {
    
    
		// 获取当前线程的classLoader	
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		// 翻译一下原文注释就是用names来去重
		// 注意这里, 我们寻找的"ServiceLoader"终于出现了
		// 就是SpringFactoriesLoader
		Set<String> names = new LinkedHashSet<String>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
				**********************//就是你啦!你穿上马甲我照样认得你,ServiceLoader!
		// 是用java反射来实例化		
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		// 根据@Order注解来排一下序		
		AnnotationAwareOrderComparator.sort(instances);
		// 返回这个接口的所有实现实例
		return instances;
	}

以上过程可以描述为:

  1. FACTORIES_RESOURCE_LOCATION 正是指向我们上面所说的META-INF/spring.factories
  2. loadFactories, 从META-INF/spring.factories查找指定的接口实现类并实例化, 其中查找是通过调用loadFactoryNames
  3. loadFactoryNames从指定的位置查找特定接口的实现类的全限定名称
  4. instantiateFactory 实例化

starter也可以认为是一种SPI机制,引入同一接口的不同实现类可以实现不同的逻辑。【拓展

【实例】自定义starter

starter()利用了SPI机制

​ 1、这个场景需要使用到的依赖是什么?

​ 2、如何编写自动配置

@Configuration  //指定这个类是一个配置类
@ConditionalOnXXX  //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter  //指定自动配置类的顺序
@Bean  //给容器中添加组件

@ConfigurationProperties结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中

自动配置类要能加载
将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

​ 3、模式:

启动器只用来做依赖导入;
专门来写一个自动配置模块;
启动器依赖自动配置;别人只需要引入启动器(starter)
mybatis-spring-boot-starter;自定义启动器名-spring-boot-starter

步骤:

1)、启动器模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atguigu.starter</groupId>
    <artifactId>atguigu-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--启动器-->
    <dependencies>

        <!--引入自动配置模块-->
        <dependency>
            <groupId>com.atguigu.starter</groupId>
            <artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

2)、自动配置模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.atguigu.starter</groupId>
   <artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>atguigu-spring-boot-starter-autoconfigurer</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.10.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>

      <!--引入spring-boot-starter;所有starter的基本配置-->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter</artifactId>
      </dependency>

   </dependencies>



</project>

package com.atguigu.starter;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "atguigu.hello")
public class HelloProperties {
    
    

    private String prefix;
    private String suffix;

    public String getPrefix() {
    
    
        return prefix;
    }

    public void setPrefix(String prefix) {
    
    
        this.prefix = prefix;
    }

    public String getSuffix() {
    
    
        return suffix;
    }

    public void setSuffix(String suffix) {
    
    
        this.suffix = suffix;
    }
}

package com.atguigu.starter;

public class HelloService {
    
    

    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
    
    
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
    
    
        this.helloProperties = helloProperties;
    }

    public String sayHellAtguigu(String name){
    
    
        return helloProperties.getPrefix()+"-" +name + helloProperties.getSuffix();
    }
}

package com.atguigu.starter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnWebApplication //web应用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
    
    

    @Autowired
    HelloProperties helloProperties;
    
    @Bean
    public HelloService helloService(){
    
    
    	//把自动配置类全类名写入类路径的META-INFO/spring.factories文件中,在启动时就可以返回
    	//一个设置了自定义property的对象
        HelloService service = new HelloService();
        //与helloProperties绑定了
        service.setHelloProperties(helloProperties);
        return service;
    }
}

12.SpringMVC的工作流程

SpringMVC处理请求的流程:

1、用户发送请求至前端控制器DispatcherServlet

2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。

3、处理器映射器根据请求url找到具体的处理器,生成处理器对象Handler及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、DispatcherServlet通过HandlerAdapter(让Handler实现更加灵活)处理器适配器调用处理器

5、执行处理器(Controller,也叫后端控制器)。

6、Controller执行完成返回ModelAndView(连接业务逻辑层和展示层的桥梁,持有一个ModelMap对象和一个View对象)。

7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet

8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器

9、ViewReslover解析后返回具体View

10、DispatcherServlet对View进行渲染视图(将ModelMap模型数据填充至视图中)。

11、DispatcherServlet响应用户【拓展

13.SpringCloud的各种组件

SpringCloudAlibaba:
微服务环境搭建
Nacos Discovery–服务治理
Sentinel–服务容错
Gateway–服务网关
Sleuth–链路追踪
Rocketmq–消息驱动
SMS–短信服务
Nacos Config–服务配置
Seata-分布式事务

14.服务注册中心的工作流程(后续补充)

15.负载均衡如何实现,有哪几种方式(考虑非网关的方式实现)

  1. 重定向
    这种方式,是通过将请求全部发送到前置机,由前置机通过算法 得出要分配给那台 应用服务器,然后响应给客户端,由客户端重定向到应用服务器的一种方式。
    这种方式,由于每一个的请求,都要重定向一下,所以效率不是很高

  2. 反向代理
    这种方式,是通过在前置机,使用反向代理的方式,将请求分发到应用服务器,客户端无需再请求一次,实现方式通常有两种,一种是用交换机实现,还有一种是用nginx这一类的软件实现
    这种方式,由于不需要再次重定向,所以较第一种,效率较高,但是由于请求和响应都是通过前置机来的,所以对前置机的考验很大

  3. 数据链路返回
    这种方式,通过给应用服务器设置虚拟IP,然后通过修改mac地址的方式,将请求分发出去,而应用服务器 收到请求后,可以直接响应给客户端,而不需要经过前置机。
    这种方式,由于 前置机 只需要接受请求,不需要响应数据,所以,效率较第二种较高。

负载均衡算法:

  1. 轮询法

将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。

  1. 随机法

通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,

其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。

  1. 源地址哈希法

源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。

  1. 加权轮询法

不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。

  1. 加权随机法

与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

  1. 最小连接数法

最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前 积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。

16.谈谈你对微服务的理解

引用微服务之父,马丁.福勒的话,此为译文:
就目前而言,对于微服务业界并没有一个统一的、标准的定义(While there is no precise definition of this architectural style ) 。
但通常在其而言,微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分成一组小的服务,每个服务运行独立的自己的进程中,服务之间互相协调、互相配合,为用户提供最终价值。
服务之间采用轻量级的通信机制互相沟通(通常是基于 HTTP 的 RESTful API ) 。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。
另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务。可以使用不同的语言来编写服务,也可以使用不同的数据存储。

17.SOA和微服务的区别

SOA与微服务的区别在于如下几个方面:

  1. 微服务相比于SOA更加精细,微服务更多的以独立的进程的方式存在,互相之间并无影响;
  2. 微服务提供的接口方式更加通用化,例如HTTP RESTful方式,各种终端都可以调用,无关语言、平台限制;
  3. 微服务更倾向于分布式去中心化的部署方式,在互联网业务场景下更适合。

18.CAP理论和base定理

CAP:要满足分区容错性的分布式系统,只能在一致性和可用性两者中,选择其中一个。
在这里插入图片描述

BASE(basically available, soft state, eventually consistent)是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。【拓展

19.分布式系统需要考虑哪些问题

1.如何将系统拆分成多个子系统
2.如何规划子系统的通信问题
3.如何实现通信过程中的安全
4.如何才能实现子系统的可扩展性
5.如何保证子系统的可靠性
6.如何实现数据的一致性

20.分布式系统如何实现数据一致性

幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品使用约支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条.

  1. 强一致
    当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。
  2. 弱一致性
    系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。
  3. 最终一致性
    弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。

方法:
此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。

BASE 的可用性是通过支持局部故障而不是系统全局故障来实现的。

如果产生了一笔交易,需要在交易表增加记录,同时还要修改用户表的金额。这两个表属于不同的远程服务,所以就涉及到分布式事务一致性的问题。

eBay提出了一个经典的解决方法,将主要修改操作以及更新用户表的消息放在一个本地事务来完成。同时为了避免重复消费用户表消息带来的问题,达到多次重试的幂等性,增加一个更新记录表 updates_applied 来记录已经处理过的消息。
第一阶段,通过本地的数据库的事务保障,增加了 transaction 表及消息队列 。
第二阶段,分别读出消息队列(但不删除),通过判断更新记录表 updates_applied 来检测相关记录是否被执行,未被执行的记录会修改 user 表,然后增加一条操作记录到 updates_applied,事务执行成功之后再删除队列。
通过以上方法,达到了分布式系统的最终一致性。【拓展

21.如何实现分布式锁

MySql
Zk
Redis在这里插入图片描述
拓展

22.手写限流算法(后续)

23.你的系统你会从哪些方面考虑去优化(查19问)

24.你的服务挂了怎么处理

什么原因造成的?比如请求量过大造成的:
可用方法:缓存、服务降级、限流。

缓存,就是用内存来顶替一部分DB的查询+数据的处理。这应该是所有业务开发人员的必修课。业务上大致可以把缓存分为三类:浏览器缓存(HTTP Cache-Control Header),CDN和服务器业务缓存。而业务缓存根据实现的结构可以分多个层级,可以用in-memory cache (如Guava Cache),或者是分布式共享Cache(如Redis)。在设计缓存一致性更新模式时,无非就是Cache Aside、Read/Write Through和Write Behind这三大种模式。有些超级NB的缓存系统自带Cluster加持(比如Ehcache即可单机用,也可以组集群)。限于本文主题,具体的缓存设计不赘述。

留意下这里说的缓存仅仅是利用了内存访问比磁盘访问快得多的特性(大概可以理解为2~3个数量级),并不会让用户感知到数据一致性哪里不对劲(与下面的降级不同)。

服务降级,是指通过降低服务质量的方法,达到节省资源的目的。简单来说就是弃车保帅。比如你的服务有ABC,平时消耗差不多的资源。突发事件时,A的请求量极大的增高了,B和C没有变化。那么可以比如减少或者暂停B和C的服务,省出资源给A用。

再比如,一个热点新闻的业务,有新闻内容,有评论,有点赞数等等。一旦新闻热点了,就可以把所有这些内容“静态化”,不必再查DB。这时虽然评论,点赞的数暂时就不准了,但是主要的服务——内容,还是让用户可以看到。这就足够了。

可以看到,降级的具体的方案,要结合业务和系统实现来综合设计,并没有定法。

降级很多时候也会用到缓存。只不过这时候使用缓存的方法就可能会以牺牲数据一致性为代价——内存里的数据和DB不一样,就不一样吧,业务上可接受,并且这段热点时间段过去了,能够恢复为一致就可以。

限流,即限制用户的请求流量。具体的做法有计数器、滑动窗口、滴漏、服务token、请求队列化等办法。这些方法的详细解释,在这里都说得比较清楚,所以我就不重复了。只是值得注意的是,现在很多生产级别的服务都是多节点分布式架构。很多单机上容易做的算法和控制逻辑到了分布式下就会带来一些实现上的麻烦。这又涉及到了分布式一致性、CAP的权衡等等问题。【拓展

25. 一致性算法(paxos,Raft,ZAB)

在这里插入图片描述
在这里插入图片描述
一、主从复制
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二、Basic Paxos
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(以上情况由多个Proposer保证可用性)-----》multi Paxos

在这里插入图片描述
活锁:proposer不断提交议案,并且编号不断增加,则acceptor就一直不能成功决议,谓之活锁。
在这里插入图片描述
三、Multi Paxos

在这里插入图片描述
N号老大提出第I号内容为Vm的提案,则议员必须接受。(只有一次RPC,除了第一次选老大两次RPC)
在这里插入图片描述
简化角色(servers中选一个老大(上图中proposer和acceptor合并))

四、Raft算法(paxos简化版)
在这里插入图片描述
以下讲解log replication。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如何保证leader election呢?
election timeout
在这里插入图片描述
来自leader的心跳周期发送,follower超过随机事件timeout未收到心跳则认为无leader。第一个timeout到期的节点会先投票给自己,之后发送投票请求给其他follower,如果接收到请求的follower还未投票就会投票给他,之后超过半数票即成为leader。注意,数据也包含在心跳包中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
指的是两个节点同时到期,并选举自己,咋办?他们在等待随机事件后再发请求。
在这里插入图片描述
以上截图来源:Raft

五、ZAB算法
在这里插入图片描述
六、Gossip算法
Gossip算法每个节点都是对等的,即没有角色之分。Gossip算法中的每个节点都会将数据改动告诉其他节点,节点收到数据改动,又会再次将改动传播给其他4个节点,传播路径表示为较粗的4条线。

猜你喜欢

转载自blog.csdn.net/weixin_41896265/article/details/108657615