`
QING____
  • 浏览: 2232025 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Springboot自动装配与bean注入

    博客分类:
  • JAVA
 
阅读更多

     自动装配是springboot中一大特性,即springboot在程序初始化时可以根据classpath类、property属性、context中实例、以及运行容器特征等各种动态条件,来按需初始化相应的bean并交付给终端使用。

     1、@ConditionalOnBean:如果此Bean实例已存在,则执行(如果修饰在class上,则此类加载;如果修饰在方法上 ,则此方法执行)。通常用于限定“如果必备的bean已经被初始化时,才会自动装备当前实例”。

     2、@ConditionalOnMissingBean:如果此Bean实例尚未被初始时,则执行。

     3、@ConditionalOnClass:如果classpath中存在此类(且可被加载)时,则执行。

     4、@ConditionalOnMissingClass:如果class中不存在此类时,则执行。

     5、@ConditionalOnProperty:如果Envrionment中存在此property、或者相应的property参数值匹配时,则执行。比如springboot中,各种内置的组件是否enabled,就是根据此条件判断。

     6、@ConditionalOnWebApplication:如果当前容器为WEB时,则执行。比如初始化springMVC、restTemplate、各种endpoint初始化等。

 

     此外springboot还提供了其他更多的condition判断,大家按需使用即可。在springbean容器初始化之前、Envrionment准备就绪之后,将根据spring.factories文件中指定的AutoConfiguration类列表逐个解析上述Condition,并在稍后有序加载、初始化相关bean。

    源码,请参见:ConfigurationClassParser,ConfigurationClassBeanDefinitionReader,SpringBootCondition。

 

 

     装配时机:

     1、@AutoConfigureAfter:在指定的其他自动装配类初始化之后,才装配当前类。

     2、@AutoConfigureBefore:在指定的其他装配类初始化之后,才装配当前类。

     3、@AutoConfigureOrder:指定当前类装配的优先级,类似于Ordered接口。

 

 

     每个可以自动装配的springboot类,通常具备如下几个特征:

     1)使用@Configuration修饰,且此类不应该被@ComponentScan包含。

     2)类上可以被@Conditional*修饰。

     3)构造函数不应该为private;构造函数中的参数类,可以被spring容器注入。

     4)此类必须在META-INF/spring.factories中声明(注册),才能被springboot感知。

     5)springboot中自动装配的类,需要设计好加载或者引用其他bean的顺序,否则可以引入“循环依赖”问题,导致容器初始化失败。

     6)自动装配类,通常配合@ConfigurationProperties完成一些属性的配置和bean加载,当然@ConfigurationProperties并不是必须的。

 

     本文展示一下,如何使用springboot自动装配,实现RestTemplate类的自定义设置。

 

一、HttpClientAutoConfiguration

import okhttp3.OkHttpClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.HttpClient;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

/**
 * @author liuguanqing
 * created 2018/6/28 上午11:33
 * http客户端
 **/
@Configuration
@ConditionalOnWebApplication
@ConditionalOnBean(Context.class)
@AutoConfigureAfter({ContextAutoConfiguration.class})
@EnableConfigurationProperties({HttpProperties.class})
public class HttpClientAutoConfiguration {

    private final HttpProperties httpProperties;

    private final Context context;
    public HttpClientAutoConfiguration(HttpProperties httpProperties,
                                       ObjectProvider<Context> contextObjectProvider,
                                       ObjectProvider<RestTemplateBuilder> restTemplateBuilderObjectProvider) {
        this.httpProperties = httpProperties;
        this.context = contextObjectProvider.getIfUnique();
    }


    @Bean
    @ConditionalOnMissingBean
    public HttpClientConfiguration httpClientConfiguration() {
        HttpClientConfiguration configuration = HttpClientConfiguration.common();
        
        String charset = httpProperties.getCharset();
        if (charset != null) {
            configuration.setCharset(charset);
        }
        .....
        return configuration;
    }


    @Bean
    public MeteorRestTemplateCustomizer meteorRestTemplateCustomizer(HttpClientConfiguration httpClientConfiguration) {
        return new MeteorRestTemplateCustomizer(httpClientConfiguration);
    }

  
    private static ClientHttpRequestFactory clientHttpRequestFactory(HttpClientConfiguration configuration) {
        String clientType = configuration.getClientType();

        if (HttpClientConfiguration.HTTP_CLIENT_TYPE_OK_HTTP.equalsIgnoreCase(clientType)) {
            OkHttpClient httpClient = OkHttpClientBuilder.build(configuration);
            return new OkHttp3ClientHttpRequestFactory(httpClient);
        }
        HttpClient httpClient = HttpComponentsClientBuilder.build(configuration);
        return new HttpComponentsClientHttpRequestFactory(httpClient);
    }


    public static class MeteorRestTemplateCustomizer implements RestTemplateCustomizer {

        private final HttpClientConfiguration configuration;

        public MeteorRestTemplateCustomizer(HttpClientConfiguration configuration) {
            this.configuration = configuration;
        }

        @Override
        public void customize(RestTemplate restTemplate) {
            restTemplate.setRequestFactory(clientHttpRequestFactory(configuration));
            //restTemplate.getInterceptors().add(new MeteorHttpClientRequestHeaderInterceptor(configuration));
        }
    }

}

 

    1)HttpClientConfiguration为自定义的构建配置,原则不影响对本文的阅读,感兴趣可以参见如下两个博客地址:“HttpComponents客户端”、“okHttp客户端”。

    2)HttpProperties为本文自定义的、用于配置http客户端参数的配置类,请参见下文。

    3)Context为本例的一个额外类,仅供演示自动装配时如何注入外部类。不影响阅读。

 

    @EnableConfigurationProperties注释中指定的Properties类(@ConfigurationProperties修饰的类,可以多个),可以被AutoConfiguration类构造函数中直接注入(最好它们已经被初始化结束);此外对于其他spring bean,则不能直接在构造函数中引入,我们需要使用ObjectProvider来“告知容器”指定和获取,比如本文中的Context实例类。

 

    在springboot环境下,我们初始化RestTemplate,尽量使用RestTemplateBuilder来构建,参见下文。

 

二、HttpProperties

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.time.Duration;
import java.util.Map;

/**
 * @author liuguanqing
 * created 2018/7/29 下午8:40
 * 用于构建Context实例,单例
 **/
@ConfigurationProperties("http")
public final class HttpProperties {

    private String charset = ContextConstants.DEFAULT_CHARSET;

    //连接创建,HTTP/TCP等RPC
    private Duration connectionTimeout = Duration.ofMillis(500);

    //SO_TIMEOUT
    private Duration socketTimeout = Duration.ofMillis(500);

    //连接池中的连接被保活的时长
    private Duration keepAliveTime = Duration.ofMillis(6000);

    //请求异常(exception),重试的次数,默认为0,不重试
    private Integer retryTimes = 0;
    //是否重试
    private Boolean retryOnFailure = false;

    //业务全局header,默认会添加到http请求中,每项为K-V
    private Map<String, String> globalHeaders;

    private String clientType = HttpClientConfiguration.HTTP_CLIENT_TYPE_HTTP_COMPONENTS;

    public String getCharset() {
        return charset;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    public Duration getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(Duration connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public Duration getSocketTimeout() {
        return socketTimeout;
    }

    public void setSocketTimeout(Duration socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

    public Duration getKeepAliveTime() {
        return keepAliveTime;
    }

    public void setKeepAliveTime(Duration keepAliveTime) {
        this.keepAliveTime = keepAliveTime;
    }

    public Integer getRetryTimes() {
        return retryTimes;
    }

    public void setRetryTimes(Integer retryTimes) {
        this.retryTimes = retryTimes;
    }

    public Boolean getRetryOnFailure() {
        return retryOnFailure;
    }

    public void setRetryOnFailure(Boolean retryOnFailure) {
        this.retryOnFailure = retryOnFailure;
    }

    public Map<String, String> getGlobalHeaders() {
        return globalHeaders;
    }

    public void setGlobalHeaders(Map<String, String> globalHeaders) {
        this.globalHeaders = globalHeaders;
    }

    public String getClientType() {
        return clientType;
    }

    public void setClientType(String clientType) {
        this.clientType = clientType;
    }
}

 

三、声明自动装配

    我们需要在META-INF/spring.factories文件中,声明所有的自动装配类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.test.commons.springboot.context.ContextAutoConfiguration,\
com.test.commons.springboot.httpclient.HttpClientAutoConfiguration,\
com.test.commons.springboot.web.WebApplicationAutoConfiguration,\
com.test.commons.springboot.metrics.HttpMetricsExportAutoConfiguration

 

    每行声明一个类,多个类时以“,”分割,“\”作为换行符。

 

四、初始化RestTemplate

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		Application.run(DemoApplication.class, args);
	}


	@Bean
	public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
		return restTemplateBuilder.build();
	}
}

 

    我们需要在使用@Configuration修饰的bean中,初始化RestTemplate,请注意不要尝试在AutoConfiguration类中为应用初始化RestTemplate,因为这是违反设计的。

    此外,我们通常使用RestTemplateBuilder来初始化restTemplate,此时我们在AutoConfiguration类中指定的各种Customizer则会生效。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics