Spring Boot核心特性之組件自動裝配

spring boot能夠根據(jù)依賴的jar包自動配置spring boot的應(yīng)用,例如: 如果類路徑中存在DispatcherServlet類,就會自動配置springMvc相關(guān)的Bean。spring boot的自動裝配來源于spring的裝配,功能也是隨時spring的不斷升級不斷完善的,spring boot正是在spring的基礎(chǔ)上實現(xiàn)的自動裝配。


spring模式注解裝配

模式注解介紹

? 模式注解是應(yīng)用程序中用來標注組件的注解,例如:@Repository是spring框架中用來標注數(shù)據(jù)訪問對象(DAO)的注解。@Component是用來標注被spring管理的通用的組件,@Component標注的類都可以被spring容器掃描到。并且任何標注@Component元注解的的注解也能被spring掃描到,比如@Service

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    @AliasFor(annotation = Component.class)
    String value() default "";
}

? 下面是spring中常用的模式注解

spring注解 使用場景 起始版本
@Repository 數(shù)據(jù)倉儲模式注解 2.0
@Component 通用組件模式注解 2.5
@Service 服務(wù)模式注解 2.5
@Controller Web 控制器模式注解 2.5
@Configuration 配置類模式注解 3.0

裝配方式

? spring中通過配置掃描的包 ,就能掃描到注解的組件,有兩種配置的方式:

XML配置

通過context:component-scan標簽的base-package屬性,配置需要掃描的包

  <context:component-scan base-package="com.laoliangcode.service,com.laoliangcode.dao"/>

注解方式裝配

  @ComponentScan(basePackages = "com.laoliangcode.service,com.laoliangcode.dao")

自定義模式注解

? 可以通過在自定義注解上加上元注解的方式,自定義模式注解。例如:@UserRepository注解上加上元注解@Repository,這樣@UserRepository也是模式注解了。這是由于注解具有派生性的特點,@UserRepository派生至@Repository,@Repository派生至@Component。

@Repository
public @interface UserRepository {
    String value() default "";
}

spring @Enable模塊裝配

? spring3.1開始支持@Enable模塊裝配,所謂模塊是指,把具有相同功能的組件組合在一起,引入到項目中。比如@EnableWebMvc注解,就是把spring MVC相關(guān)的配置引入到項目中,而不需要其他配置,方便使用spring MVC。這種裝配方式是通過@Import注解引入其他配置類來實現(xiàn)的,@EnableWebMvc通過引入DelegatingWebMvcConfiguration配置類,實現(xiàn)spring MVC的自動配置。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

? 引入的配置類有兩種實現(xiàn)形式,一種是直接使用模式注解@Configuration的類,另一種是實現(xiàn)ImportSelector接口的selectImports方法,來引入配置類。

注解方式

? @EnableWebMvc就是這種實現(xiàn)方式。
? 下面列舉User模塊的裝配來具體說明實現(xiàn)的方式??梢钥闯?code>EnableUserConfig是通過直接導(dǎo)入UserConfiguration來裝配User模塊的。

UserConfiguration配置類

  @Configuration
  public class UserConfiguration {
      @Bean
      public UserService userService(UserDao userDao){
          return new UserService(userDao);
      @Bean
      public UserDao userDao() {
          return new UserDao();
      }
  }

EnableUserConfig注解

  @Target({ElementType.TYPE})
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @Import(UserConfiguration.class)
  public @interface EnableUserConfig {
  }

使用啟動類

  @EnableUserConfig
  public class EnableUserConfigBootstrap {
      public static void main(String[] args) {
          AnnotationConfigApplicationContext context = new
                 AnnotationConfigApplicationContext(EnableUserConfigBootstrap.class);
        UserService userService = context.getBean("userService", UserService.class);
          System.out.println("EnableUserConfigBootstrap.main" + userService.findBId(1));
          context.close();
      }
  }

ImportSelector接口方式

? spring中的EnableCaching就是這種實現(xiàn)方式。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
}

? 下面列舉User模塊的裝配來具體說明實現(xiàn)的方式。這種方式是通過UserConfigurationSelector來引入User的配置類UserConfiguration。

UserConfigurationSelector類用來導(dǎo)入UserConfiguration配置

public class UserConfigurationSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {UserConfiguration.class.getName()};
    }
}

EnableUserSelector注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfigurationSelector.class)
public @interface EnableUserSelector {
}

使用啟動類

@EnableUserSelector
public class EnableUserSelectorBootstrap {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new
                AnnotationConfigApplicationContext(EnableUserSelectorBootstrap.class);
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println("EnableUserSelectorBootstrap.main" + userService.findBId(1));
        context.close();
    }
}

spring條件裝配

? spring3.1開始,spring引入了@Profile注解,可以根據(jù)環(huán)境Environment的不同引入不同的配置。spring4.0開始,Conditional注解可以更靈活的根據(jù)不同條件引入不同的配置。

@Profile注解方式的條件裝配

? 使用User模塊的不能dao裝配來說明@Profile的條件裝配。

UserProfileConfiguration配置

@Configuration
public class UserProfileConfiguration {
    @Bean
    public UserServiceForProfile userServiceForProfile(IUserDao userDao) {
        return new UserServiceForProfile(userDao);
    }
    @Bean
    @Profile("mysql")
    public IUserDao userMysqlDao() {
        return new UserMysqlDao();
    }
    @Bean
    @Profile("oracle")
    public IUserDao userOracleDao() {
        return new UserOracleDao();
    }
}

Environment激活使用

通過context.getEnvironment().setActiveProfiles("oracle");的方式,來激活不同的Profile

public class ProfileBootstrap {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new
                AnnotationConfigApplicationContext();
        context.register(UserProfileConfiguration.class);
        context.getEnvironment().setActiveProfiles("oracle");
        context.refresh();
        UserServiceForProfile userService = context.getBean("userServiceForProfile",
                UserServiceForProfile.class);
        System.out.println("ProfileBootstrap.main" + userService.findBId(1));
        context.close();
    }
}

@Conditional注解方式的條件裝配

? @Conditional注解方式,通過引入實現(xiàn)Condition接口的類,來判斷條件是否成立,從而確定是否引入某個組件。這個類是通過實現(xiàn)matches方法來判斷條件是否成立。spring4.0開始,由于引入了@Conditional注解,Profile也是通過@Conditional來實現(xiàn)的。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
    String[] value();
}

? spring boot中大量使用了@Conditional注解的方式,來自動裝配不同的組件。@ConditionalOnClass用來表示類路徑存在某些類時加載;@ConditionalOnMissingBean用來判斷某些類的實例不存在時加載;ConditionalOnWebApplication用來判斷某種應(yīng)用類型時加載。例如webmvc的自動加載配置WebMvcAutoConfiguration:

    @Configuration
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
            TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {
    }

? 下面的例子是根據(jù)系統(tǒng)變量的值來決定是否裝配UserDao

OnSystemPropertyCondition 實現(xiàn)Condition接口的matches方法,用來判斷是否符合條件

  public class OnSystemPropertyCondition implements Condition {
      @Override
      public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
          Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
          String propertyName = String.valueOf(annotationAttributes.get("name"));
          String propertyValue = String.valueOf(annotationAttributes.get("value"));
          String systemPropertyValue = System.getProperty(propertyName);
          return propertyValue.equals(systemPropertyValue);
      }
  }

ConditionalOnSystemProperty注解

  @Target({ElementType.TYPE, ElementType.METHOD})
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @Conditional(OnSystemPropertyCondition.class)
  public @interface ConditionalOnSystemProperty {
      String name();
      String value();
  }

啟動類

  public class ConditionalOnSystemPropertyBootstrap {
      @ConditionalOnSystemProperty(name = "user.name", value = "Administrator")
      @Bean
      public UserDao userDao() {
          return new UserDao();
      }
      public static void main(String[] args) {
          AnnotationConfigApplicationContext context = new
                  AnnotationConfigApplicationContext(ConditionalOnSystemPropertyBootstrap.class);
          UserDao userDao = context.getBean("userDao", UserDao.class);
          System.out.println("ConditionalOnSystemPropertyBootstrap.main" + userDao.findBId(1));
          context.close();
      }
  }

如果value指定錯誤,就會報錯:

  Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'userDao' available

spring boot自動裝配

? spring boot的自動裝配結(jié)合了上面介紹的spring模式注解、@Enable模塊裝配和條件裝配。另外,spring boot自身還使用了工廠加載機制,用SpringFactoriesLoader來裝載配置類。

實現(xiàn)方法

  1. 實現(xiàn)自動裝配的類
  2. META-INF/spring.factories文件中配置第一步中的類
  3. @EnableAutoConfiguration注解激活配置
    下面以User模塊的自動裝配為例,來介紹具體的實現(xiàn)步驟

實現(xiàn)裝配類UserAutoConfiguration

這里用到了前面介紹的@Enable模塊裝配和條件裝配

@EnableUserSelector
@ConditionalOnSystemProperty(name = "user.name", value = "Administrator")
public class UserAutoConfiguration {
}

META-INF/spring.factories文件中添加配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.laoliangcode.configuration.UserAutoConfiguration

啟動類中添加@EnableAutoConfiguration注解激活配置

@EnableAutoConfiguration
public class AutoConfigurationBootstrap {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new
                AnnotationConfigApplicationContext(AutoConfigurationBootstrap.class);
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println("AutoConfigurationBootstrap.main" + userService.findBId(1));
        context.close();
    }
}

如果在第一步中,讓條件裝配不符合條件,就會報錯:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'userService' available

文中詳細代碼在github上

https://github.com/laoliangcode/springboot-demo/tree/master/autoconfigure
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容