1. 程式人生 > >Spring Boot核心特性之元件自動裝配

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

​ spring boot能夠根據依賴的jar包自動配置spring boot的應用,例如: 如果類路徑中存在DispatcherServlet類,就會自動配置springMvc相關的Bean。spring boot的自動裝配來源於spring的裝配,功能也是隨時spring的不斷升級不斷完善的,spring boot正是在spring的基礎上實現的自動裝配。

spring模式註解裝配

模式註解介紹

​ 模式註解是應用程式中用來標註元件的註解,例如:@Repository是spring框架中用來標註資料訪問物件(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 資料倉儲模式註解 2.0
@Component 通用元件模式註解 2.5
@Service 服務模式註解 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相關的配置引入到專案中,而不需要其他配置,方便使用spring MVC。這種裝配方式是通過@Import註解引入其他配置類來實現的,@EnableWebMvc通過引入DelegatingWebMvcConfiguration配置類,實現spring MVC的自動配置。

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

​ 引入的配置類有兩種實現形式,一種是直接使用模式註解@Configuration的類,另一種是實現ImportSelector介面的selectImports方法,來引入配置類。

註解方式

@EnableWebMvc就是這種實現方式。

​ 下面列舉User模組的裝配來具體說明實現的方式。可以看出EnableUserConfig是通過直接匯入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就是這種實現方式。這種方式是通過UserConfigurationSelector來引入User的配置類UserConfiguration

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

​ 下面列舉User模組的裝配來具體說明實現的方式。

  • UserConfigurationSelector類用來匯入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註解,可以根據環境Environment的不同引入不同的配置。spring4.0開始,Conditional註解可以更靈活的根據不同條件引入不同的配置。

@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註解方式,通過引入實現Condition介面的類,來判斷條件是否成立,從而確定是否引入某個元件。這個類是通過實現matches方法來判斷條件是否成立。spring4.0開始,由於引入了@Conditional註解,Profile也是通過@Conditional來實現的。

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

​ spring boot中大量使用了@Conditional註解的方式,來自動裝配不同的元件。@ConditionalOnClass用來表示類路徑存在某些類時載入;@ConditionalOnMissingBean用來判斷某些類的例項不存在時載入;ConditionalOnWebApplication用來判斷某種應用型別時載入。例如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 {
}

​ 下面的例子是根據系統變數的值來決定是否裝配UserDao

  • OnSystemPropertyCondition 實現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的自動裝配結合了上面介紹的spring模式註解、@Enable模組裝配和條件裝配。另外,spring boot自身還使用了工廠載入機制,用SpringFactoriesLoader來裝載配置類。

實現方法

  1. 實現自動自動裝配的類

  2. META-INF/spring.factories檔案中配置第一步中的類

  3. @EnableAutoConfiguration註解啟用配置

    下面以User模組的自動裝配為例,來介紹具體的實現步驟

  • 實現裝配類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