Spring Boot核心特性之组件自动装配

Posted laoliangcode

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于Spring Boot核心特性之组件自动装配的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 2.0深度实践之核心技术篇

SpringBoot——SpringBoot四大核心之自动装配(源码解析)

SpringBoot——SpringBoot四大核心之自动装配(源码解析)

Spring boot 自动装配

Spring boot 自动装配

Spring Boot 单元测试未检测到自动装配组件的模块