当需要将直接的项目打成jar包,提供给三方使用时。需要在三方中调用jar包中的bean,简单的方式可以在引入jar包方的包扫描中加入jar包中的路径,这种方式仅适用与内部项目。如果是提供给公用三方使用则不合适。
另外一种方式,可以增加一个注解配置。采用springcloud中的方式,做成动态插拔式的。步骤如下:
一.Springboot 自动配置原理分析
@SpringBootApplication
注解@SpringBootApplication
是一个复合注解,它包括@SpringBootConfiguration
,@EnableAutoConfiguration
,@ComponentScan
三个注解。其中最关键的莫过@EnableAutoConfiguration
这个注解。在它的源码中加入了这样一个注解@Import({EnableAutoConfigurationImportSelector.class})
,EnableAutoConfigurationImportSelector
,它使用SpringFactoriesLoader. loadFactoryNames
方法来扫描META-INF/spring.factories
文件,此文件中声明了有哪些自动配置。源码如下(我挑选出重要的一部分)
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List loadFactoryNames(Class> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List result = new ArrayList();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
我随便查看spring-boot-autoconfigure-1.5.3.RELEASE.jar中的spring.factories,有如下的自动配置。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
上述spring.factories对应key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值即为启动时候需要自动配置的类。
二. 实现自定义的starter
@ConfigurationProperties(prefix = "hello")
public class HelloServiceProperties {
private static final String MSG="world";
private String msg=MSG;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
@ConfigurationProperties(prefix = "hello")
是类型安全的属性获取。在application.properties 中通过hello.msg来设置,如果不设置默认就是“word"。public class HelloService {
private String msg;
public String sayHello(){
return msg;
}
public void setMsg(String msg){
this.msg=msg;
}
}
@Configuration //1
@EnableConfigurationProperties(HelloServiceProperties.class)//2
@ConditionalOnClass(HelloService.class) //3
@ConditionalOnProperty(prefix = "hello",value = "enabled",matchIfMissing = true) //4
public class HelloServiceAutoConfiguration {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
@ConditionalOnMissingBean(HelloService.class) //5
public HelloService helloService(){
HelloService helloService=new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
@Configuration
它告知 Spring 容器这个类是一个拥有 bean 定义和依赖项的配置类。@EnableConfigurationProperties
的bean可以以标准方式被注册(例如使用 @Bean 方法),即我定义HelloServiceProperties可以作为标准的Bean被容器管理。@ConditionalOnClass
表示该类在类路径下存在,自动配置该类下的Bean。@ConditionalOnProperty
当指定的属性等于指定的值的情况下加载当前配置类,在这里如果matchIfMissing
如果为false,则在application.properties中必须存在hello.enable(且不能为false)@ConditionalOnMissingBean()
表示指定的bean不在容器中,则重新新建@Bean注解的类,并交给容器管理。配置好之后,我们还需要在src\main\resources
下新建文件夹META-INF
,再新建文件spring.factories
里面的内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.springboot.mystartertool.HelloServiceAutoConfiguration
里面指定的类是上面自定义的那个配置类HelloServiceAutoConfiguration
定义
spring.factories
的原因是因为@EnableAutoConfiguration
会扫描jar包下所有spring.factories
文件,从而构造自动配置类。我们使用的时候使用@Autowired
注入就行。
在以上工作完成后,我们执行如下命令
mvn clean install
就将项目打包到本地maven仓库中,有条件的可以安装的到私服中。
三. 应用自定义starter
com.maskwang
Springboot-mystart
1.0-SNAPSHOT
application.properties
配置如下hello.msg=maskwang
我们就可以使用自定义的starter啦。
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@RequestMapping("/hello")
public String hello() {
return helloService.sayHello();
}
}
由于我们没有自定义HelloService
,所以会配置类会发挥作用,新建一个HelloService
,并把里面的msg设置成”maskwang"。没有配置msg,则会采用默认的。结果如下
作者:maskwang520
链接:https://www.jianshu.com/p/0f0feaaf8406
实现原理分析:springboot底层实现源码讲解
上一篇文章中讲述了@EnableEurekaClient和@EnableDiscoveryClient区别,原想可能底层会有较多不同,但是查看源码的时候发现@EnableEurekaClient本身就是用@EnableDiscoveryClient来实现的,因此没有多大的研究价值,但是如果继续讲@EnableEurekaClient源码的话,篇幅过长,因此另外单开一篇文章讲述@EnableDiscoveryClient的源码。
首先点进@EnableEurekaClient注解源码,如下:
/**
* Convenience annotation for clients to enable Eureka discovery configuration
* (specifically). Use this (optionally) in case you want discovery and know for sure that
* it is Eureka you want. All it does is turn on discovery and let the autoconfiguration
* find the eureka classes if they are available (i.e. you need Eureka on the classpath as
* well).
*
* @author Dave Syer
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
public @interface EnableEurekaClient {
}
这里使用了@EnableDiscoveryClient修饰,转到@EnableDiscoveryClient,如下:
/**
* Annotation to enable a DiscoveryClient implementation.
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
}
注解@EnableDiscoveryClient上有注解@Import(EnableDiscoveryClientImportSelector.class)修饰,
@Import不仅可以单独导入一个配置,另外也可以导入普通的java类,并将其声明为一个bean。此处导入EnableDiscoveryClientImportSelector.class后,加载我们用到的bean,EnableDiscoveryClientImportSelector.class源码如下:
/**
* @author Spencer Gibb
*/
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector
@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
}
@Override
protected boolean hasDefaultFactory() {
return true;
}
}
这个类中有一个覆盖父类的方法isEnabled(),返回默认为true,那么说明只要是引入了EnableDiscoveryClientImportSelector类,spring.cloud.discovery.enabled就处于enable状态。
EnableDiscoveryClientImportSelector继承了类SpringFactoryImportSelector,我们再来看这个类的源码:
在关键的方法selectImports中:
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled()) {
return new String[0];
}
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(this.annotationClass.getName(), true));
Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is "
+ metadata.getClassName() + " annotated with @" + getSimpleName() + "?");
// Find all possible auto configuration classes, filtering duplicates
List
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
if (factories.isEmpty() && !hasDefaultFactory()) {
throw new IllegalStateException("Annotation @" + getSimpleName()
+ " found, but there are no implementations. Did you forget to include a starter?");
}
if (factories.size() > 1) {
// there should only ever be one DiscoveryClient, but there might be more than
// one factory
log.warn("More than one implementation " + "of @" + getSimpleName()
+ " (now relying on @Conditionals to pick one): " + factories);
}
return factories.toArray(new String[factories.size()]);
}
关键代码:
// Find all possible auto configuration classes, filtering duplicates
List
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
根据这一步来找到configuration class,这里的SpringFactoriesLoader.loadFactoryNames就是根据配置文件来load class,转到SpringFactoriesLoader.loadFactoryNames,源码如下:
public static List
String factoryClassName = factoryClass.getName();
try {
Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(ex.hasMoreElements()) {
URL url = (URL)ex.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
这里loadMETA-INF下的spring.factories文件,这个文件中就是load那几个配置,这里要提一点的就是这个spring.factories指的是@EnableEurekaClient对应源码中META-INF下的spring.factories文件,图如下:
spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
打开EurekaClientConfigServerAutoConfiguration,如下:
package org.springframework.cloud.netflix.eureka.config;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.discovery.EurekaClient;
/**
* Extra configuration for config server if it happens to be a Eureka instance.
*
* @author Dave Syer
*/
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass({ EurekaInstanceConfigBean.class, EurekaClient.class,
ConfigServerProperties.class })
public class EurekaClientConfigServerAutoConfiguration {
@Autowired(required = false)
private EurekaInstanceConfig instance;
@Autowired(required = false)
private ConfigServerProperties server;
@PostConstruct
public void init() {
if (this.instance == null || this.server == null) {
return;
}
String prefix = this.server.getPrefix();
if (StringUtils.hasText(prefix)) {
this.instance.getMetadataMap().put("configPath", prefix);
}
}
}
这个类上的注解为:
@ConditionalOnClass({ EurekaInstanceConfigBean.class, EurekaClient.class,
ConfigServerProperties.class })
意义为当前程序中存在EurekaInstanceConfigBean或者EurekaClient,或者ConfigServerProperties的时候,当前这个配置类会被进行加载,否则不会加载。
这个在加载EurekaClient.class,实例化EurekaClient,但是EurekaClient本身是由DiscoveryClient来实现的,代码如下:
/**
* Define a simple interface over the current DiscoveryClient implementation.
*
* This interface does NOT try to clean up the current client interface for eureka 1.x. Rather it tries
* to provide an easier transition path from eureka 1.x to eureka 2.x.
*
* EurekaClient API contracts are:
* - provide the ability to get InstanceInfo(s) (in various different ways)
* - provide the ability to get data about the local Client (known regions, own AZ etc)
* - provide the ability to register and access the healthcheck handler for the client
*
* @author David Liu
*/
@ImplementedBy(DiscoveryClient.class)
public interface EurekaClient extends LookupService {
在实例化DiscoveryClient后,对应的服务注册服务就开始运行起来了,由此基本的@EnableEurekaClient源码讲解就讲完了。
---------------------
版权声明:本文为CSDN博主「叶长风」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012734441/article/details/78315086