SpringBoot学习总结(11)之自动装配

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jokeMqc/article/details/89315845

一、自动装配

SpringBoot提供了注解@Configuration,用来配置多个Bean,想我之前的SpringBoot学习总结总就使用多很多次这个注解类,例如配置Spring的数据源,然后SpringBoot我们大概知道他有自动装配功能,但是为什么能够自动装配,以及什么是自动装配毫不介绍,这节就是主要介绍SpringBoot的自动装配和Configuration。

1.1 @Configuration和@Bean

Spring的java配置的核心就是使用@Configuration作用在类上,并且是联合在此类上的多个@Bean注解的方法,声明Spring管理的Bean,他类似于XML的配置方式。

<beans>
    <bean id="testBean" class="xxx.TestBean"></bean>
</beans>

使用@Configuration注解的方式:

package com.mqc.config;

import com.mqc.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author maoqichuan
 * @ClassName: TestConfig
 * @description: @configuration 注解方式注册Bean
 * @date 2019-04-1517:28
 **/
@Configuration
public class TestConfig {
    @Bean(name = "testBean")
    public User getUser(){
        return new User();
    }
}

在以上的代码中,TestConfig类上使用了@Configuration注解,那么则向Spring表明这是一个配置类,类里面的所有标注有@Bean的方法都会被Spring调用,返回的对象都会被Spring容器管理的Bean,注解@Bean的name可以为一个bean指定一个名字,例如上面的代码中我指定的baan的名字是“testBean”,bean的名字我们一般是取类的类名或者某字母小写作为名字,如果该注解不指定name的名字,那么默认取方法名作为Bean的名称。

通常我们的配置类还需要获取外部属性,例如:配置文件、系统变量、环境变量等,Environment提供了获取这些外部属性的API。

可以在@Bean注解的方法参数上提供任意参数来说明依赖,比如我的TestConfig的UserService依赖数据源datasource。

@Bean
    public IUserService getUserService(DataSource dataSource){
        return new UserServiceImpl(dataSource);
    }

datasource是我们SpringBoot已经在其他地方配置好的数据源。

在配置好Bean后,我们可以通过@AutoWired在任何地方注入我们配置好的bean,比如我需要在某业务代码注入我的UserService。

@Autowired
    private IUserService userService;

1.2 Bean条件装配

SpringBoot可以通过指定有无Bean来装配Bean,使用@ConditionalOnBean,在当前上下文中存在某个对象,才会实例一个Bean,使用@ConditaionalOnMissBean,在当前上下文中不存在某个对象的时候才会实例化Bean。

/**
 * @author maoqichuan
 * @ClassName: MysqlConfig
 * @description: 当前的上下文中存在datasource这个对象的时候才会实例化该类
 * @date 2019-04-1517:44
 **/
@Configuration
@ConditionalOnBean(DataSource.class)
public class MysqlConfig {
}

MysqlConfig生效的前提就是在存在Datasource这个类。

package com.mqc.config;

import com.mqc.service.IUserService;
import com.mqc.service.impl.UserServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author maoqichuan
 * @ClassName: MyAutoConfiguration
 * @description: 自动装配介绍
 * @date 2019-04-1517:46
 **/
@Configuration
public class MyAutoConfiguration {

    /**
     * @description: 在当前上下文环境中不存在IUserService才会实例化
     * @return  IUserService
     * @throws
     * @author maoqichuan
     * @date 2019-04-15 17:48 
     */
    @Bean(name = "userService")
    @ConditionalOnMissingBean(IUserService.class)
    public IUserService getUserService(){
        return  new UserServiceImpl();
    }
}

配置装配IUserService就调用这个方法。

1.3 Class条件装配

Class条件是其用途是判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器。

package com.mqc.config;

import com.mqc.service.IUserService;
import com.mqc.service.impl.UserServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author maoqichuan
 * @ClassName: MyAutoConfiguration
 * @description: 自动装配介绍
 * @date 2019-04-1517:46
 **/
@Configuration
@ConditionalOnClass({IUserService.class})
public class MyAutoConfiguration {

    /**
     * @description: 在当前上下文环境中不存在IUserService才会实例化
     * @return  IUserService
     * @throws
     * @author maoqichuan
     * @date 2019-04-15 17:48 
     */
    @Bean(name = "userService")
    @ConditionalOnMissingBean(IUserService.class)
    public IUserService getUserService(){
        return  new UserServiceImpl();
    }
}

当在实例化IUserService的时候实例化该类。

1.4 Enviroment装配

可以根据SpringBoot的Enviroment的属性来决定是否生效。

package com.mqc.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

/**
 * @author maoqichuan
 * @ClassName: MessageAutoConfiguration
 * @description: 根据environment的属性来决定是否生效
 * @date 2019-04-1518:06
 **/
@Configuration
@ConditionalOnProperty(name = "message.center.enabled",havingValue = "true",
matchIfMissing = true)
public class MessageAutoConfiguration {
}

@ConditonalOnProperty注解根据name来读取SpringBoot的Enviroment的变量包含的属性,根据其值与havingValue的值比较结果决定配置是否生效,如果没有指定havingValue,那么只要属性不为false,配置都能生效。

matchMissing为true意味着如果Enviroment没有包含message.center.enabled,配置也能生效,默认为false。

1.5 其他条件装配

  • ConditionalOnExpression,当表达式为true时,才会实例化一个Bean,支持SpEL表达式,比如根据配置文件中的某个值来确实能够是否生效。
  • ConditionOnJava,当存在指定的Java版本的 时候。
@ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER,value = ConditionalOnJava.JavaVersion.EIGHT)

1.6 联合多个条件

以上介绍的注解都可以联合@Configuration来使用,如果满足条件,那么其下所有配置的类都会生效,也可以单独与@Bean使用。

以Spring Boot Cache 为例(支持多种缓存实现,如SimpleCache ),可以用Has协fap 实现,还可以是分布式的Red is 实现方式。无论是哪种实现方式,只需要配置好CacheManage「一缓存的抽象管理类。对应于SimpleCache ,是ConcurrentMapCacheManager 实现;对应于Redis 缓存,是RedisCacheManager 实现。

/**
 * @author maoqichuan
 * @ClassName: RedisCacheConfiguration
 * @description: RedisCacheConfiguration 配置类
 * @date 2019-04-1610:08
 **/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration . class )
@ConditionalOnBean(RedisTemplate . class)
@ConditionalOnMissingBean(CacheManager . class )
@Conditional(CacheCondition.class)
public class RedisCacheConfiguration {
}

注解@Configuration向Spring表明这是一个配置类,Red is 的缓存配置需要确保Red is T emplate 己经配置, 因此使用了注解@AutoConfigureAfter,表示此配置类需要在Redi s AutoConfiguration 配置类后再生效, ConditionalOnBean 则表示如果
成功配置好R e di sTemplat e ,此配置才能继续生效。@ConditionalOnMissingBean可以接受一个或者多个作为参数,如果还没有配置过CacheManager类则该类生效。

1.7Condition接口

当我们使用SpringBoot的@ConditionalOnBean,@ConditionalOnClass无法满足我们需求的时候,可以自己构造一个Condition实现,使用注解@Conditional来引用此Condition实现。

Condition的接口定义如下:

package com.mqc.condition;

import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * @author maoqichuan
 * @ClassName: MyCondition
 * @description: 自定义condition
 * @date 2019-04-1610:18
 **/
public interface MyCondition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

ConditionContext类可以用于帮助条件判断的辅助类:

  • Enviroment:可以读取系统属性,环境变量,配置参数等作为条件判断,比如当配置文件的某一项存在的时候生效。
  • ResourceLoader,一个String类,用来加载和判断资源文件,比如当某一个配置文件存在时才生效。
  • ConfigurableListableBeanFactory,Spring容器。

以下配置了一个对存入数据库的用户手机进行加密的类,使用@Conditional注解,要求存在salt.txt文件且允许手机加密时才生效。

package com.mqc.config;

import com.mqc.condition.MyCondition;
import com.mqc.util.PhoneUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

/**
 * @author maoqichuan
 * @ClassName: ModileEncryptCondition
 * @description: 若条件允许则生效
 * @date 2019-04-1610:38
 **/
@Configuration
@Conditional(MyCondition.class)
public class ModileEncryptCondition {
    @Bean(name = "PhoneUtils")
    public PhoneUtils getPhoneUtils(){
        return new PhoneUtils();
    }
}

自定义的Condition:

package com.mqc.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * @author maoqichuan
 * @ClassName: MyCondition
 * @description: 手机加密配置类
 * @date 2019-04-1610:36
 **/
public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Resource resource = ctx.getResourceLoader().getResource("salt.txt");
        Environment environment = ctx.getEnvironment();

        return resource.exists() && environment.containsProperty("mobile.encrypt.enabled.");
    }
}

我们这前面看到的@ConditionalOnMissBean、@ConditionalOnJava等注解实际上也是通过@Conditional来实现的,这里我们以@ConditionalOnJava为例:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnJavaCondition.class})
public @interface ConditionalOnJava {
    ConditionalOnJava.Range range() default ConditionalOnJava.Range.EQUAL_OR_NEWER;

    ConditionalOnJava.JavaVersion value();

满足@ConditionalOnJava的条件就是在@OnJavaCondition中实现的,而其具体的实现这里就不解释了。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure.condition;

import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;

@Order(-2147483628)
class OnJavaCondition extends SpringBootCondition {
    private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();

    OnJavaCondition() {
    }

    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
        Range range = (Range)attributes.get("range");
        JavaVersion version = (JavaVersion)attributes.get("value");
        return this.getMatchOutcome(range, JVM_VERSION, version);
    }

    protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) {
        boolean match = runningVersion.isWithin(range, version);
        String expected = String.format(range == Range.EQUAL_OR_NEWER ? "(%s or newer)" : "(older than %s)", version);
        ConditionMessage message = ConditionMessage.forCondition(ConditionalOnJava.class, new Object[]{expected}).foundExactly(runningVersion);
        return new ConditionOutcome(match, message);
    }
}

其关键代码使用了AnnotatedTypeMetadata 来获取@Conditiona!OnJava 注解的属性信息,比如value () 方法返回的String[]:

Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
        Range range = (Range)attributes.get("range");
        JavaVersion version = (JavaVersion)attributes.get("value");

到这里,SpringBoot自动配置的已经介绍完了,让我们一起努力,一起进步!加油!!!

猜你喜欢

转载自blog.csdn.net/jokeMqc/article/details/89315845