spring装配

一、自动装配
    1.1、单个Bean
        (1)Person类
public class Person {
    private String name;
    private Integer age;
}

        (2)配置类

@Component
public class SingleConfiguration {
    @Bean
    public Person person(){
        Person person = new Person();
        person.setName("SK");
        person.setAge(18);
        return person;
    }
}

        (3)测试

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new
                AnnotationConfigApplicationContext(SingleConfiguration.class);
        Person person = (Person) context.getBean("person");
        System.out.println(person.getName()+"年龄"+person.getAge());
    }
}

测试结果:

    1.2、批量Bean

        (1)多个实体类

@Service
public class OrderService {
}

@Service
public class PersonService {
}

        (2)配置类

@Component
@ComponentScan("com.sk.cloud.spring.batch")
    public class BatchConfiguration {
}

        (3)测试

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BatchConfiguration.class);
                for(String name:context.getBeanDefinitionNames()){
                    System.out.println(name);
                }
    }
}

        测试结果:

    1.3、三方Bean

        (1)三方jar包:打成jar包

public class ThirdRedisConnection {
    private String host = "127.0.0.1";
    private String port = "61616";


    public void connect(){
        System.out.println("http://"+host+":"+port);
    }
}


public class ThirdRedisService {
    public void insert(){}
    public void update(){}
    public void delete(){}
}

        (2)引入三方依赖

 <dependency>
            <groupId>org.third</groupId>
            <artifactId>sk-third-spring-boot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        (3)调用三方方法    

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}

@RestController
@RequestMapping("/sk")
public class Controller {
    @Resource
    private ThirdRedisService thirdRedisService;

    @RequestMapping("/insert")
    public void insert(){
        thirdRedisService.insert();
    }
}

       启动报错:

    (4)Sping管理

           1.3.4.1、引用方配置类注入Bean

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
    @Bean
    public ThirdRedisService thirdRedisService () {
        return new ThirdRedisService();
    }
}

            1.3.4.2、三方包里把需要的Bean交给IOC容器

<!--三方包增加依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.24</version>
    </dependency>

/*
* 三方增加配置类将Bean交给Spring管理
*/
@Configuration
public class RedisAutoConfiguration {
    @Bean
    public ThirdRedisService thirdRedisService(){
        return  new ThirdRedisService();
    }
}

/*
* 调用方引入@Import三方配置类
*/
@SpringBootApplication
@Import(RedisAutoConfiguration.class)
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
     }
//    @Bean
//    public ThirdRedisService thirdRedisService () {
//        return new ThirdRedisService();
//    }
}

            1.3.4.3、自动装配:如果引入很多三方jar都要手动引入很麻烦

                1)、创建META-INF/spring.factories文件

                2)、指定配置类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.third.spring.RedisAutoConfiguration

                3)、调用方直接依赖就可以使用

@SpringBootApplication
//@Import(RedisAutoConfiguration.class)
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
//    @Bean
//    public ThirdRedisService thirdRedisService () {
//        return new ThirdRedisService();
//    }
}

spring boot原理:@SpringBootApplication->@EnableAutoConfiguration->@Import(AutoConfigurationImportSelect.class)->AutoConfigurationImportSelect#getAutoConfigurationEntry-

>getCandidateConfigurations

/*
*    1、AutoConfigurationImportSelect#getAutoConfigurationEntry
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

/*
*   2、org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}


/*
*    3、org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }


    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}


/*
*    4、org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
*/
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        Map<String, List<String>> result = new HashMap();


        try {
            Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");


            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();


                while(var6.hasNext()) {
                    Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    String[] var10 = factoryImplementationNames;
                    int var11 = factoryImplementationNames.length;


                    for(int var12 = 0; var12 < var11; ++var12) {
                        String factoryImplementationName = var10[var12];
                        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                            return new ArrayList();
                        })).add(factoryImplementationName.trim());
                    }
                }
            }


            result.replaceAll((factoryType, implementations) -> {
                return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
            });
            cache.put(classLoader, result);
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
    }
}
二、条件装配
7.9.3. Condition Annotations
You almost always want to include one or more @Conditional annotations on your auto-configuration class. The @ConditionalOnMissingBean annotation is one common example that is used to allow developers to override auto-configuration if they are not happy with your defaults.
Spring Boot includes a number of @Conditional annotations that you can reuse in your own code by annotating @Configuration classes or individual @Bean methods. These annotations include:
(1)ConditionalOnClass:当且仅当ClassPath存在指定的Class时,才创建标记上该注解的类的实例;相反ConditionalOnMissingBean是指如果没有找到目标Class, 那么就装载该类
(2)ConditionalOnBean: 当且仅当指定的bean classes and/or bean names在当前容器中,才创建
标记上该注解的类的实例;相反ConditionalOnMissingBean是指如果没有Class, 那么就装载该Bean
(3)ConditionalOnProperty:当且仅当Application.properties存在指定的配置项时,创建标记上
了该注解的类的实例
(4)ConditionalOnResource:在classpath下存在指定的resource时创建
(5)ConditionalOnWebApplication:在web环境下创建
    下面举例条件装配:
            1)、采用1.3.4.3自动装配基础上,三方jar新增类ThirdRedisConnection
/*
*    1、sk-third-spring-boot-starter 新增ThirdRedisConnection类
*/
public class ThirdRedisConnection {
    private String host = "127.0.0.1";
    private String port = "61616";


    public void connect(){
        System.out.println("http://"+host+":"+port);
    }
}

/*
*    2、RedisAutoConfiguration 新增ThirdRedisConnection类配置
*/
@Configuration
public class RedisAutoConfiguration {
    @Bean
    public ThirdRedisService thirdRedisService(){
        return  new ThirdRedisService();
    }

    @Bean
    public ThirdRedisConnection thirdRedisConnection(){return new ThirdRedisConnection();}
}

           2)、引用jar包新增调用方法

@Resource
private ThirdRedisConnection thirdRedisConnection;

@RequestMapping("/connect")
public void connect(){
    thirdRedisConnection.connect();
}

此时调试请求/connnct方法,正常调用

    2.1、@Conditional

            2.1.1、新增Redis连接条件装配逻辑方法
 

/*
* 根据Application属性判断是否加载Redis连接类链接
* */
public class OnRedisCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String host = context.getEnvironment().getProperty("redis.host");
        String port = context.getEnvironment().getProperty("redis.port");
        if(StringUtils.isEmpty(host)||StringUtils.isEmpty(port)){
            return false;
        }
        return true;
    }
}

            2.1.2、新增Redis连接条件装配ConditionalOnRedis注解

@Conditional(OnRedisCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
public @interface ConditionalOnRedis {
}

            2.1.2、重写RedisAutoConfiguration 新增ThirdRedisConnection类配置,为条件装配

@Configuration
public class RedisAutoConfiguration {
    @Bean
    public ThirdRedisService thirdRedisService(){
        return  new ThirdRedisService();
    }

    @ConditionalOnRedis
    @Bean
    public ThirdRedisConnection thirdRedisConnection(){return new ThirdRedisConnection();}
}

              2.1.3、重新编译三方包,启动服务异常(因为不满足2.1.1、新增Redis连接条件装配逻辑方法)

Action:

Consider defining a bean of type 'org.third.spring.ThirdRedisConnection' in your configuration.

            2.1.4、修改配置,满足Redis连接条件装配逻辑,启动服务正常

<!--application.properties-->
redis.host=172.17.1.150
redis.port=61616

三、属性装配

public class ThirdRedisConnection {
    private String host = "127.0.0.1";
    private String port = "61616";
    public void connect(){
        System.out.println("http://"+host+":"+port);
    }
}
<!--application.properties-->
redis.host=172.17.1.150
redis.port=61616
在条件装配中,虽然application.properties配置文件增加redis配置,但是依赖三方Redis连接属性无法从配置中注入,即使添加@Value也无法注入,如何实现在配置文件中配置对应值
    3.1、starter三方项目pom添加mave依赖        
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-autoconfigure</artifactId>
  <version>2.7.6</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <version>2.7.6</version>
</dependency>

    3.2、starter三方Redis连接实现类增加配置:增加ConfigurationProperties注解和SET方法

@ConfigurationProperties(prefix = "redis")
public class ThirdRedisConnection {

    private String host = "127.0.0.1";
    private String port = "61616";    
    public void setHost(String host) {
        this.host = host;
    }
    public void setPort(String port) {
        this.port = port;
    }

    public void connect(){
        System.out.println("http://"+host+":"+port);
    }
}

编译starter包,启动web执行结果如下:

    

    3.3、如果把注入配置项从Redis连接实现类独立出来,解耦类功能

            1)、新增配置类 剥离配置项

@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
    private String host = "127.0.0.1";
    private String port = "61616";

    public String getHost() {
        return host;
    }
    public void setHost(String host) {
        this.host = host;
    }
    public String getPort() {
        return port;
    }
    public void setPort(String port) {
        this.port = port;
    }
}

     2)、starter三方Redis连接实现类修改为从配置类获取配置信息

@ConfigurationProperties(prefix = "redis")
public class ThirdRedisConnection {
    @Resource
    private RedisProperties redisProperties;

    public void connect(){
        System.out.println("http://"+redisProperties.getHost()+":"+redisProperties.getPort());
    }

}

    3)、starter三方Redis连接实现类通过容器获取配置类,修改RedisAutoConfiguration配置类将RedisProperties注入到Spring容器中

@Configuration
public class RedisAutoConfiguration {
    @Bean
    public ThirdRedisService thirdRedisService(){return  new ThirdRedisService();}

    @ConditionalOnRedis
    @Bean
    public ThirdRedisConnection thirdRedisConnection(){return new ThirdRedisConnection();}

    @Bean
    public RedisProperties redisProperties(){return new RedisProperties();}
}

    或者通过注解@EnableConfigurationProperties(RedisProperties.class)

@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
    @Bean
    public ThirdRedisService thirdRedisService(){return  new ThirdRedisService();}

    @ConditionalOnRedis
    @Bean
    public ThirdRedisConnection thirdRedisConnection(){return new ThirdRedisConnection();}
}

三、条件装配扩展

    

    3.1、@ConditionalOnBean注解

这里举个Dubbo例子在做自动装配时,先寻找BASE_PACKAGES_BEAN_NAME这个Bean是否存在,存在自动装载 

@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true)
@Configuration
@AutoConfigureAfter(DubboRelaxedBindingAutoConfiguration.class)
@EnableConfigurationProperties(DubboConfigurationProperties.class)
@EnableDubboConfig
public class DubboAutoConfiguration {
    /**
     * Creates {@link ServiceAnnotationPostProcessor} Bean
     * dubbo.scan.base-packages
     * @param packagesToScan the packages to scan
     * @return {@link ServiceAnnotationPostProcessor}
     */
    @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME)
    
    // 先寻找BASE_PACKAGES_BEAN_NAME这个Bean是否存在, 存在serviceAnnotationBeanProcessor被Spring装载;否则不装载
    @ConditionalOnBean(name = BASE_PACKAGES_BEAN_NAME)
    @Bean
    public ServiceAnnotationPostProcessor serviceAnnotationBeanProcessor(@Qualifier(BASE_PACKAGES_BEAN_NAME)
                                                                       Set<String> packagesToScan) {
        return new ServiceAnnotationPostProcessor(packagesToScan);
    }
}

    3.2、 @ConditionalOnMissingBean注解

/**
* Dubbo Relaxed Binding Auto-{@link Configuration} for Spring Boot 2.0
*
* @see DubboRelaxedBindingAutoConfiguration
* @since 2.7.0
*/
@Configuration
@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true)
@ConditionalOnClass(name = "org.springframework.boot.context.properties.bind.Binder")
@AutoConfigureBefore(DubboRelaxedBindingAutoConfiguration.class)
public class DubboRelaxedBinding2AutoConfiguration {
    public PropertyResolver dubboScanBasePackagesPropertyResolver(ConfigurableEnvironment environment) {
        ConfigurableEnvironment propertyResolver = new AbstractEnvironment() {
            @Override
            protected void customizePropertySources(MutablePropertySources propertySources) {
                Map<String, Object> dubboScanProperties = getSubProperties(environment.getPropertySources(), DUBBO_SCAN_PREFIX);
                propertySources.addLast(new MapPropertySource("dubboScanProperties", dubboScanProperties));
            }
        };
        ConfigurationPropertySources.attach(propertyResolver);
        return propertyResolver;
    }
    /**
     * The bean is used to scan the packages of Dubbo Service classes
     * 如果没有就创建
     * @param environment {@link Environment} instance
     * @return non-null {@link Set}
     * @since 2.7.8
     */
    @ConditionalOnMissingBean(name = BASE_PACKAGES_BEAN_NAME)
    @Bean(name = BASE_PACKAGES_BEAN_NAME)
    public Set<String> dubboBasePackages(ConfigurableEnvironment environment) {
        PropertyResolver propertyResolver = dubboScanBasePackagesPropertyResolver(environment);
        return propertyResolver.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet());
    }
    @ConditionalOnMissingBean(name = RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME, value = ConfigurationBeanBinder.class)
    @Bean(RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME)
    @Scope(scopeName = SCOPE_PROTOTYPE)
    public ConfigurationBeanBinder relaxedDubboConfigBinder() {
        return new BinderDubboConfigBinder();
    }
}

  3.2、@ConditionalOnProperty注解

解析application.yml/application.properties 里的配置生成条件生效,与@Configuration注解一起使用

    1)、场景1:根据数据库配置类型,加载对应数据库工具,如果条件没有匹配上的话Spring扫描bean是会自动跳过该配置类

@Configuration
@ConditionalOnClass(SQLServerDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue ="com.microsoft.sqlserver.jdbc.SQLServerDriver",
        matchIfMissing = false
)
public class SqlServerDriver {

  2)、场景2:启动Redis缓存,如果没有开启Redis,则不启用Redis配置类

@Configuration
@ConditionalOnClass(RedisAutoConfiguration.class)
@ConditionalOnProperty(
        name = {"spring.boot.redis.switch"},
        matchIfMissing = false
)
public class RedisAutoConfiguration {

    如果设置matchIfMissing = true的话,默认配置类生效,如果application没有配置spring.boot.redis.switch或者设置true都生效,只有配置spring.boot.redis.switch=false才跳过加载此配置类

    2)、场景3:使用策略模式,根据环境配置注册不同bean

//1、定义接口
public interface DateSourceFace {
    void connection();
}

//2、定义策略抽象类
public abstract class AbstractDbConnetion implementsDateSourceFace{
    
}

//3、分别实现A 和 B类
@Slf4j
public class MysqlDateSource extendsAbstractDbConnetion{
    @Override
    public void connection() {
        log.info("mysql connect");
    }
}

@Slf4j
public class SqlServerDateSource extendsAbstractDbConnetion{
    @Override
    public void connection() {
        log.info("selserver connect");
    }
}

//4、配置类
@Configuration
public class DbConnectionConfig {
    @Bean
    @ConditionalOnProperty(name = "datasource.type",havingValue = "mysql")
    public DateSourceFace mysqlDateSource(){
        return new MysqlDateSource();
    }

    @Bean
    @ConditionalOnProperty(name = "datasource.type",havingValue = "sqlserver")
    public DateSourceFace sqlServerDateSource(){
        return new SqlServerDateSource();
    }
}

//5、配置文件
datasource:
 type: mysql

//6、业务
@Slf4j
@Component
@ConfigurationProperties(prefix = "datasource")
public class InitConfig implements CommandLineRunner {
    @Resource
    private DateSourceFace dateSourceFace;

    // 执行初始化逻辑
    @Override
    public void run(String... args) throws Exception {
        dateSourceFace.connection();
    }
}

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值