SpringBoot自动装配实现流程分析及阿里云短信自动装配实现

  • SpringBoot自动装配实现流程分析
  • 阿里云短信自动装配实现


这里以Redis的自动装配为例

SpringBoot自动装配实现流程分析

流程解析:
1.启动类上有@SpringBootApplication注解,进入此注解,会发现这个注解类上会有如下三个注解:

//与Configuration一样,把当前类标识为配置类
@SpringBootConfiguration
//包扫描,SpringBoot默认会扫描启动类所在包及其所在包中类上的注解
@ComponentScan
//开启自动配置
@EnableAutoConfiguration

2.进入@EnableAutoConfiguration注解

//@Import作用是导入其他的配置类
@Import({AutoConfigurationImportSelector.class})
//AutoConfigurationImportSelector实现了DeferredImportSelector,而DeferredImportSelector继承了ImportSelector (最终父类)
public interface ImportSelector {
//String[]数组中返回的就是配置类的类全名
    String[] selectImports(AnnotationMetadata var1);
}

3.SpringBoot是从那里读取这些全类名的呢?
AutoConfigurationImportSelector实现类父接口类ImportSelector 重写了selectImports()方法。

//AutoConfigurationImportSelector重写的selectImports()方法
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {

//获取自动配置类的全类名,并将集合转换为数组
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

4.获取自动配置类

springboot 消息发送_spring

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		//获取自动配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

5.进入getCandidateConfigurations方法获取配置类列表

springboot 消息发送_spring_02

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

6.会发现配置类的全类名是由这个文件下获取的:
META-INF/spring.factories
进入文件后

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

7.进入RedisAutoConfiguration

//标识当前类为配置类
@Configuration(
    proxyBeanMethods = false
)
 //	当给定的类名在类路径上存在,则实例化当前Bean
 //这个注解通俗的说就是Spring工程中引用了RedisOperations,才会构建这个bean
@ConditionalOnClass({RedisOperations.class})
//使使用 @ConfigurationProperties 注解的类生效。
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

//将方法的返回值制作成SpringIOC容器的Bean对象
    @Bean
    //它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果而注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个,当你注册多个相同的bean时,会出现异常,以此来告诉开发人员。
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    }

8.进入RedisProperties

//读取application.yml文件配置和redis相关信息,读到RedisProperties 对象中
public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String username;
    private String password;
    private int port = 6379;
}

阿里云短信自动装配实现

对照RedisTemplate的实现步骤

1.在项目的配置类包下的resources下创建META-INF/spring.factories,对照上方redis的spring.factories文件中内容进行修改如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.jacob.autoconfig.SmsAutoConfiguration
//com.jacob.autoconfig为包名
//SmsAutoConfiguration为类名

2.定义SmsAutoConfiguration类

@EnableConfigurationProperties(SmsProperties.class)
public class SmsAutoConfiguration {
    @Bean
    public SmsTemplate smsTemplate(SmsProperties prpos){
        return new SmsTemplate(prpos);
    }
}

3.定义SmsProperties 类,读取yml文件中的配置

@Data
@ConfigurationProperties(prefix = "message.sms")
public class SmsProperties {
    private String accessKeyId;
    private String accessKeySecret;
    private String signName;
    private String templateCode;
    private String endpoint;
}

4.定义SmsTemplate 类(发送短信业务类),可从阿里云官方导出并进行修改。

import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;


public class SmsTemplate {
    private SmsProperties smsProperties;

    public SmsTemplate(SmsProperties smsProperties) {
        this.smsProperties = smsProperties;
    }

    /**
     * 发送验证码短信
     *
     * @param mobile
     * @param code
     */
    public void sendSms(String mobile, String code) {
        try {
            Config config = new Config()
                    .setAccessKeyId(smsProperties.getAccessKeyId())
                    .setAccessKeySecret(smsProperties.getAccessKeySecret());
            config.endpoint=smsProperties.getEndpoint();
            // 访问的域名
            //config.endpoint = "dysmsapi.aliyuncs.com";
            Client client = new Client(config);

            SendSmsRequest sendSmsRequest = new SendSmsRequest()
                    .setPhoneNumbers(mobile)
                    .setSignName(smsProperties.getSignName())
                    .setTemplateCode(smsProperties.getTemplateCode())
                    .setTemplateParam("{\"code\":\"" + code + "\"}");

            // 复制代码运行请自行打印 API 的返回值
            SendSmsResponse response = client.sendSms(sendSmsRequest);
            if ("OK".equals(response.getBody().getCode())) {
                System.out.println("验证码发送成功");
            }
            System.out.println(response.getBody().getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5.在application.yml文件中编写配置

message:
  sms:
    accessKeyId: xxxxxxxxx
    accessKeySecret: xxxxxxxxx
    signName: xxxxxxxxx
    templateCode: xxxxxxxxx
    endpoint: dysmsapi.aliyuncs.com

6.编写测试类进行发送

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AutoConfigApplication.class)
public class SendMessage {
    @Autowired
    private SmsTemplate smsTemplate;

    @Test
    public void send(){
        smsTemplate.sendSms("131xxxxxxxx","xxxx");
    }
}