Spring Security 實(shí)戰(zhàn):Spring Boot 下的自動(dòng)配置

1. 前言

我們?cè)谇皫灼獙?duì) Spring Security 的用戶信息管理機(jī)制,密碼機(jī)制進(jìn)行了探討。沒有看的同學(xué)可通過(guò) https://felord.cn 獲取。我們發(fā)現(xiàn) Spring Security Starter相關(guān)的 Servlet 自動(dòng)配置都在spring-boot-autoconfigure-2.1.9.RELEASE(當(dāng)前 Spring Boot 版本為2.1.9.RELEASE) 模塊的路徑org.springframework.boot.autoconfigure.security.servlet 之下。其實(shí)官方提供的Starter組件的自動(dòng)配置你都能在spring-boot-autoconfigure-2.1.9.RELEASE下找到。今天我們進(jìn)一步來(lái)解密 Spring Security 在 Spring Boot 的配置和使用。

2. Spring Boot 下 Spring Security 的自動(dòng)配置
我們可以通過(guò) org.springframework.boot.autoconfigure.security.servlet 路徑下找到 Spring Security 關(guān)于Servlet的自動(dòng)配置類。我們來(lái)大致了解一下。

2.1 SecurityAutoConfiguration
  package org.springframework.boot.autoconfigure.security.servlet;

  import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  import org.springframework.boot.autoconfigure.security.SecurityDataConfiguration;
  import org.springframework.boot.autoconfigure.security.SecurityProperties;
  import org.springframework.boot.context.properties.EnableConfigurationProperties;
  import org.springframework.context.ApplicationEventPublisher;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.context.annotation.Import;
  import org.springframework.security.authentication.AuthenticationEventPublisher;
  import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;

  /**
   * {@link EnableAutoConfiguration Auto-configuration} for Spring Security.
   *
   * @author Dave Syer
   * @author Andy Wilkinson
   * @author Madhura Bhave
   * @since 1.0.0
   */
  @Configuration
  @ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
  @EnableConfigurationProperties(SecurityProperties.class)
  @Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
          SecurityDataConfiguration.class })
  public class SecurityAutoConfiguration {

      @Bean
      @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
      public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
          return new DefaultAuthenticationEventPublisher(publisher);
      }

  }
SecurityAutoConfiguration 顧名思義安全配置類。該類引入(@import)了 SpringBootWebSecurityConfiguration、WebSecurityEnablerConfiguration 和 SecurityDataConfiguration 三個(gè)配置類。 讓這三個(gè)模塊的類生效。是一個(gè)復(fù)合配置,是 Spring Security 自動(dòng)配置最重要的一個(gè)類之一。 Spring Boot 自動(dòng)配置經(jīng)常使用這種方式以達(dá)到靈活配置的目的,這也是我們研究 Spring Security 自動(dòng)配置的一個(gè)重要入口 同時(shí) SecurityAutoConfiguration 還將 DefaultAuthenticationEventPublisher 作為默認(rèn)的 AuthenticationEventPublisher 注入 Spring IoC 容器。如果你熟悉 Spring 中的事件機(jī)制你就會(huì)知道該類是一個(gè) Spring 事件發(fā)布器。該類內(nèi)置了一個(gè)HashMap<String, Constructor<? extends AbstractAuthenticationEvent>>維護(hù)了認(rèn)證異常處理和對(duì)應(yīng)異常事件處理邏輯的映射關(guān)系,比如賬戶過(guò)期異常 AccountExpiredException 對(duì)應(yīng)認(rèn)證過(guò)期事件AuthenticationFailureExpiredEvent ,也就是說(shuō)發(fā)生不同認(rèn)證的異常使用不同處理策略。

2.2 SpringBootWebSecurityConfiguration
  @Configuration
  @ConditionalOnClass(WebSecurityConfigurerAdapter.class)
  @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
  @ConditionalOnWebApplication(type = Type.SERVLET)
  public class SpringBootWebSecurityConfiguration {

      @Configuration
      @Order(SecurityProperties.BASIC_AUTH_ORDER)
      static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

      }

  }
這個(gè)類是Spring Security 對(duì) Spring Boot Servlet Web 應(yīng)用的默認(rèn)配置。核心在于WebSecurityConfigurerAdapter 適配器。從 @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) 我們就能看出 WebSecurityConfigurerAdapter 是安全配置的核心。 默認(rèn)情況下 DefaultConfigurerAdapter 將以SecurityProperties.BASIC_AUTH_ORDER(-5) 的順序注入 Spring IoC 容器,這是個(gè)空實(shí)現(xiàn)。如果我們需要個(gè)性化可以通過(guò)繼承 WebSecurityConfigurerAdapter 來(lái)實(shí)現(xiàn)。我們會(huì)在以后的博文重點(diǎn)介紹該類。

2.3 WebSecurityEnablerConfiguration
  @Configuration
  @ConditionalOnBean(WebSecurityConfigurerAdapter.class)
  @ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
  @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
  @EnableWebSecurity
  public class WebSecurityEnablerConfiguration {

  }
該配置類會(huì)在SpringBootWebSecurityConfiguration 注入 Spring IoC 容器后啟用 @EnableWebSecurity 注解。也就是說(shuō) WebSecurityEnablerConfiguration 目的僅僅就是在某些條件下激活 @EnableWebSecurity 注解。那么這個(gè)注解都有什么呢?

3. @EnableWebSecurity 注解
  @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
  @Target(value = { java.lang.annotation.ElementType.TYPE })
  @Documented
  @Import({ WebSecurityConfiguration.class,
          SpringWebMvcImportSelector.class,
          OAuth2ImportSelector.class })
  @EnableGlobalAuthentication
  @Configuration
  public @interface EnableWebSecurity {

      /**
       * Controls debugging support for Spring Security. Default is false.
       * @return if true, enables debug support with Spring Security
       */
      boolean debug() default false;
  }
@Enable* 這類注解都是帶配置導(dǎo)入的注解。通過(guò)導(dǎo)入一些配置來(lái)啟用一些特定功能。 @EnableWebSecurity 導(dǎo)入了 WebSecurityConfiguration 、SpringWebMvcImportSelector 、OAuth2ImportSelector 以及啟用了 @EnableGlobalAuthentication注解。

3.1 WebSecurityConfiguration
該配置類WebSecurityConfiguration使用一個(gè)WebSecurity對(duì)象基于用戶指定的或者默認(rèn)的安全配置,你可以通過(guò)繼承 WebSecurityConfigurerAdapter 或者實(shí)現(xiàn) WebSecurityConfigurer 來(lái)定制 WebSecurity 創(chuàng)建一個(gè)FilterChainProxy Bean來(lái)對(duì)用戶請(qǐng)求進(jìn)行安全過(guò)濾。這個(gè)FilterChainProxy的名稱就是 WebSecurityEnablerConfiguration上的 BeanIds.SPRING_SECURITY_FILTER_CHAIN 也就是 springSecurityFilterChain,它是一個(gè)Filter,最終會(huì)被作為Servlet過(guò)濾器鏈中的一個(gè)Filter應(yīng)用到Servlet容器中。安全處理的策略主要是過(guò)濾器的調(diào)用順序。WebSecurityConfiguration 最終會(huì)通過(guò) @EnableWebSecurity 應(yīng)用到系統(tǒng)。

源碼分析:

  package org.springframework.security.config.annotation.web.configuration;

  import java.util.Collections;
  import java.util.List;
  import java.util.Map;

  import javax.servlet.Filter;

  import org.springframework.beans.factory.BeanClassLoaderAware;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.context.annotation.DependsOn;
  import org.springframework.context.annotation.ImportAware;
  import org.springframework.core.OrderComparator;
  import org.springframework.core.Ordered;
  import org.springframework.core.annotation.AnnotationAttributes;
  import org.springframework.core.annotation.AnnotationUtils;
  import org.springframework.core.annotation.Order;
  import org.springframework.core.type.AnnotationMetadata;
  import org.springframework.security.access.expression.SecurityExpressionHandler;
  import org.springframework.security.config.annotation.ObjectPostProcessor;
  import org.springframework.security.config.annotation.SecurityConfigurer;
  import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
  import org.springframework.security.config.annotation.web.builders.WebSecurity;
  import org.springframework.security.context.DelegatingApplicationListener;
  import org.springframework.security.web.FilterChainProxy;
  import org.springframework.security.web.FilterInvocation;
  import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
  import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;


  /**
   * Spring Web Security 的配置類 :
   *  1. 使用一個(gè) WebSecurity 對(duì)象基于安全配置創(chuàng)建一個(gè) FilterChainProxy 對(duì)象來(lái)對(duì)用戶請(qǐng)求進(jìn)行安全過(guò)濾。
   *  2. 也會(huì)暴露諸如 安全SpEL表達(dá)式處理器 SecurityExpressionHandler 等一些類。
   *
   * @see EnableWebSecurity
   * @see WebSecurity
   *
   * @author Rob Winch
   * @author Keesun Baik
   * @since 3.2
   */
  @Configuration
  public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
      private WebSecurity webSecurity;
   // 是否啟用了調(diào)試模式,來(lái)自注解 @EnableWebSecurity 的屬性 debug,缺省值 false
      private Boolean debugEnabled;

      private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

      private ClassLoader beanClassLoader;

      @Autowired(required = false)
      private ObjectPostProcessor<Object> objectObjectPostProcessor;
    /**
     *
     * 代理監(jiān)聽器 應(yīng)該時(shí)監(jiān)聽 DefaultAuthenticationEventPublisher 的一些處理策略
     */   
      @Bean
      public static DelegatingApplicationListener delegatingApplicationListener() {
          return new DelegatingApplicationListener();
      }
     /**
      *
      * 安全SpEL表達(dá)式處理器 SecurityExpressionHandler 缺省為一個(gè) DefaultWebSecurityExpressionHandler
      */   
      @Bean
      @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
      public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
          return webSecurity.getExpressionHandler();
      }

      /**
       *  Spring Security 核心過(guò)濾器  Spring Security Filter Chain  , Bean ID 為 springSecurityFilterChain
       * @return the {@link Filter} that represents the security filter chain
       * @throws Exception
       */
      @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
      public Filter springSecurityFilterChain() throws Exception {
          boolean hasConfigurers = webSecurityConfigurers != null
                  && !webSecurityConfigurers.isEmpty();
          if (!hasConfigurers) {
              WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                      .postProcess(new WebSecurityConfigurerAdapter() {
                      });
              webSecurity.apply(adapter);
          }
          return webSecurity.build();
      }

      /**
     *
     * 用于模板 如JSP Freemarker 的一些頁(yè)面標(biāo)簽按鈕控制支持
       * Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
       * tag support.
       * @return the {@link WebInvocationPrivilegeEvaluator}
       * @throws Exception
       */
      @Bean
      @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
      public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
          return webSecurity.getPrivilegeEvaluator();
      }

      /**
     *
     * 用于創(chuàng)建web configuration的SecurityConfigurer實(shí)例,
     * 注意該參數(shù)通過(guò)@Value(...)方式注入,對(duì)應(yīng)的bean autowiredWebSecurityConfigurersIgnoreParents
     * 也在該類中定義
     *
       * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
       * {@link WebSecurity} instance
       * @param webSecurityConfigurers the
       * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
       * create the web configuration
       * @throws Exception
       */
      @Autowired(required = false)
      public void setFilterChainProxySecurityConfigurer(
              ObjectPostProcessor<Object> objectPostProcessor,
              @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
              throws Exception {
          webSecurity = objectPostProcessor
                  .postProcess(new WebSecurity(objectPostProcessor));
          if (debugEnabled != null) {
              webSecurity.debug(debugEnabled);
          }

          Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);

          Integer previousOrder = null;
          Object previousConfig = null;
          for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
              Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
              if (previousOrder != null && previousOrder.equals(order)) {
                  throw new IllegalStateException(
                          "@Order on WebSecurityConfigurers must be unique. Order of "
                                  + order + " was already used on " + previousConfig + ", so it cannot be used on "
                                  + config + " too.");
              }
              previousOrder = order;
              previousConfig = config;
          }
          for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
              webSecurity.apply(webSecurityConfigurer);
          }
          this.webSecurityConfigurers = webSecurityConfigurers;
      }
     /**
      * 從當(dāng)前bean容器中獲取所有的WebSecurityConfigurer bean。
      * 這些WebSecurityConfigurer通常是由開發(fā)人員實(shí)現(xiàn)的配置類,并且繼承自WebSecurityConfigurerAdapter
      *
      */   
      @Bean
      public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
              ConfigurableListableBeanFactory beanFactory){
          return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
      }

      /**
       * A custom verision of the Spring provided AnnotationAwareOrderComparator that uses
       * {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class
       * instances for the {@link Order} annotation.
       *
       * @author Rob Winch
       * @since 3.2
       */
      private static class AnnotationAwareOrderComparator extends OrderComparator {
          private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();

          @Override
          protected int getOrder(Object obj) {
              return lookupOrder(obj);
          }

          private static int lookupOrder(Object obj) {
              if (obj instanceof Ordered) {
                  return ((Ordered) obj).getOrder();
              }
              if (obj != null) {
                  Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
                  Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
                  if (order != null) {
                      return order.value();
                  }
              }
              return Ordered.LOWEST_PRECEDENCE;
          }
      }

      /*
       * 要是為了獲取注解 @EnableWebSecurity 的屬性 debugEnabled
       *
       * @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.
       * springframework.core.type.AnnotationMetadata)
       */
      public void setImportMetadata(AnnotationMetadata importMetadata) {
          Map<String, Object> enableWebSecurityAttrMap = importMetadata
                  .getAnnotationAttributes(EnableWebSecurity.class.getName());
          AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes
                  .fromMap(enableWebSecurityAttrMap);
          debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
          if (webSecurity != null) {
              webSecurity.debug(debugEnabled);
          }
      }

      /*
       * (non-Javadoc)
       *
       * @see
       * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.
       * lang.ClassLoader)
       */
      public void setBeanClassLoader(ClassLoader classLoader) {
          this.beanClassLoader = classLoader;
      }
  }
3.2 SpringWebMvcImportSelector
該類是為了對(duì) Spring Mvc 進(jìn)行支持的。一旦發(fā)現(xiàn)應(yīng)用使用 Spring Mvc 的核心前置控制器 DispatcherServlet 就會(huì)引入 WebMvcSecurityConfiguration 。主要是為了適配 Spring Mvc 。

3.3 OAuth2ImportSelector
該類是為了對(duì) OAuth2.0 開放授權(quán)協(xié)議進(jìn)行支持。ClientRegistration 如果被引用,具體點(diǎn)也就是 spring-security-oauth2 模塊被啟用(引入依賴jar)時(shí)。會(huì)啟用 OAuth2 客戶端配置 OAuth2ClientConfiguration 。

3.4 @EnableGlobalAuthentication
這個(gè)類主要引入了 AuthenticationConfiguration 目的主要為了構(gòu)造 認(rèn)證管理器 AuthenticationManager 。AuthenticationManager 十分重要后面我們會(huì)進(jìn)行專門的分析。多多關(guān)注公眾號(hào):Felordcn 或者通過(guò)https://felord.cn 第一時(shí)間獲取相關(guān)講解。

4. SecurityFilterAutoConfiguration
我們?cè)?org.springframework.boot.autoconfigure.security.servlet 路徑下還發(fā)現(xiàn)了一個(gè)配置類 SecurityFilterAutoConfiguration 。該類用于向Servlet容器注冊(cè)一個(gè)名稱為securityFilterChainRegistration的bean, 實(shí)現(xiàn)類是DelegatingFilterProxyRegistrationBean。該 bean 的目的是注冊(cè)另外一個(gè) Servlet Filter Bean 到 Servlet 容器,實(shí)現(xiàn)類為 DelegatingFilterProxy 。DelegatingFilterProxy 其實(shí)是一個(gè)代理過(guò)濾器,它被 Servlet 容器用于處理請(qǐng)求時(shí),會(huì)將任務(wù)委托給指定給自己另外一個(gè)Filter bean。對(duì)于 SecurityFilterAutoConfiguration,來(lái)講,這個(gè)被代理的Filter bean的名字為 springSecurityFilterChain , 也就是我們上面提到過(guò)的 Spring Security Web提供的用于請(qǐng)求安全處理的Filter bean,其實(shí)現(xiàn)類是 FilterChainProxy。

相關(guān)的源碼分析:

  package org.springframework.boot.autoconfigure.security.servlet;

  import java.util.EnumSet;
  import java.util.stream.Collectors;

  import javax.servlet.DispatcherType;

  import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
  import org.springframework.boot.autoconfigure.security.SecurityProperties;
  import org.springframework.boot.context.properties.EnableConfigurationProperties;
  import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
  import org.springframework.security.config.http.SessionCreationPolicy;
  import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

  @Configuration
  // 僅在 Servlet 環(huán)境下生效
  @ConditionalOnWebApplication(type = Type.SERVLET)
  // 確保安全屬性配置信息被加載并以bean形式被注冊(cè)到容器
  @EnableConfigurationProperties(SecurityProperties.class)
  // 僅在特定類存在于 classpath 上時(shí)才生效
  @ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,
          SessionCreationPolicy.class })
  // 指定該配置類在  SecurityAutoConfiguration 配置類應(yīng)用之后應(yīng)用
  @AutoConfigureAfter(SecurityAutoConfiguration.class)
  public class SecurityFilterAutoConfiguration {

      // 要注冊(cè)到 Servlet 容器的 DelegatingFilterProxy Filter的
      // 目標(biāo)代理Filter bean的名稱 :springSecurityFilterChain
      private static final String DEFAULT_FILTER_NAME =
              AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;


      // 定義一個(gè) bean securityFilterChainRegistration,
      // 該 bean 的目的是注冊(cè)另外一個(gè) bean 到 Servlet 容器 : 實(shí)現(xiàn)類為 DelegatingFilterProxy 的一個(gè) Servlet Filter
      // 該 DelegatingFilterProxy Filter 其實(shí)是一個(gè)代理過(guò)濾器,它被 Servlet 容器用于匹配特定URL模式的請(qǐng)求,
      // 而它會(huì)將任務(wù)委托給指定給自己的名字為 springSecurityFilterChain 的 Filter, 也就是 Spring Security Web
      // 提供的用于請(qǐng)求安全處理的一個(gè) Filter bean,其實(shí)現(xiàn)類是 FilterChainProxy
      // (可以將 1 個(gè) FilterChainProxy 理解為 1 HttpFirewall + n SecurityFilterChain)
      @Bean
      @ConditionalOnBean(name = DEFAULT_FILTER_NAME)
      public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
              SecurityProperties securityProperties) {
          DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
                  DEFAULT_FILTER_NAME);
          registration.setOrder(securityProperties.getFilter().getOrder());
          registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
          return registration;
      }

      private EnumSet<DispatcherType> getDispatcherTypes(
              SecurityProperties securityProperties) {
          if (securityProperties.getFilter().getDispatcherTypes() == null) {
              return null;
          }
          return securityProperties.getFilter().getDispatcherTypes().stream()
                  .map((type) -> DispatcherType.valueOf(type.name())).collect(Collectors
                          .collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
      }

  }
5. 總結(jié)
本文主要對(duì) Spring Security 在 Spring Boot 中的自動(dòng)配置一些機(jī)制進(jìn)行了粗略的講解。為什么沒有細(xì)講。因?yàn)閺膶W(xué)習(xí)出發(fā)有些東西不是我們必須要深入了解的,但是又要知道一點(diǎn)點(diǎn)相關(guān)的知識(shí)。我們先宏觀上有個(gè)大致的了解就行。所以在閱讀本文一定不要鉆牛角尖。粗略知道配置策略、加載策略和一些關(guān)鍵類的作用即可。在你對(duì) Spring Security 有了進(jìn)一步學(xué)習(xí)之后,回頭認(rèn)真來(lái)看這些配置類會(huì)有更深層的思考。 從另一個(gè)方面該文也給你閱讀 Spring 源碼提供了一些思路,學(xué)會(huì)這些才是最重要的。


作者:碼農(nóng)小胖哥



歡迎關(guān)注:碼農(nóng)小胖哥