Springboot构建⾃定义的启动注解
当需要将直接的项⽬打成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<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = Name();
try {
Enumeration<URL> urls = (classLoader != null ? Resources(FACTORIES_RESOURCE_LOCATION) :
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = Element();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = Property(factoryClassName);
result.addAll(Arrays.asList(StringUtilsmaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + Name() +
"] 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
resource和autowired注解的区别1. ⾸先定义⼀个基本的对象类,⽤来接收application.properties⾥⾯特定字段的值。
@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"。
1. 先定义条件类(根据此类的存在与否来创建这个类的Bean,这个类可以是第三⽅类库的类)。
public class HelloService {
private String msg;
public String sayHello(){
return msg;
}
public void setMsg(String msg){
this.msg=msg;
}
}
1. ⾃动配置类
@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.Msg());
return helloService;
}
}
@Configuration 它告知 Spring 容器这个类是⼀个拥有 bean 定义和依赖项的配置类。
@EnableConfigurationProperties的bean可以以标准⽅式被注册(例如使⽤ @Bean ⽅法),即我定义HelloServiceProperties可以作为标准的Bean被容器管理。
@ConditionalOnClass表⽰该类在类路径下存在,⾃动配置该类下的Bean。
@ConditionalOnProperty当指定的属性等于指定的值的情况下加载当前配置类,在这⾥如果matchIfMissing如果为false,则在application.properties中必须存在able(且不能为false)
@ConditionalOnMissingBean()表⽰指定的bean不在容器中,则重新新建@Bean注解的类,并交给容器管理。
配置好之后,我们还需要在src\main\resources下新建⽂件夹META-INF,再新建⽂件spring.factories⾥⾯的内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
startertool.HelloServiceAutoConfiguration
⾥⾯指定的类是上⾯⾃定义的那个配置类HelloServiceAutoConfiguration
定义spring.factories的原因是因为@EnableAutoConfiguration会扫描jar包下所有spring.factories⽂件,从⽽构造⾃动配置类。我们使⽤的时候使⽤@Autowired注⼊就⾏。
在以上⼯作完成后,我们执⾏如下命令
mvn clean install
就将项⽬打包到本地maven仓库中,有条件的可以安装的到私服中。
三. 应⽤⾃定义starter
1. ⾸先引⼊⾃定义的starter的jar包
<!--引⼊我的start-->
<dependency>
<groupId>com.maskwang</groupId>
<artifactId>Springboot-mystart</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
1. 当我们在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,则会采⽤默认的。结果如下
实现原理分析: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<EnableDiscoveryClient> {
@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
"spring.abled", Boolean.class, Boolean.TRUE);
}
@Override
protected boolean hasDefaultFactory() {
return true;
}
}
这个类中有⼀个覆盖⽗类的⽅法isEnabled(),返回默认为true,那么说明只要是引⼊了EnableDiscoveryClientImportSelector
类,spring.abled就处于enable状态。
EnableDiscoveryClientImportSelector继承了类SpringFactoryImportSelector,我们再来看这个类的源码:
在关键的⽅法selectImports中:
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled()) {
return new String[0];
}
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
+ ClassName() + " annotated with @" + getSimpleName() + "?");
// Find all possible auto configuration classes, filtering duplicates
List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.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);
}
Array(new String[factories.size()]);
}
关键代码:
/
/ Find all possible auto configuration classes, filtering duplicates
List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
根据这⼀步来到configuration class,这⾥的SpringFactoriesLoader.loadFactoryNames就是根据配置⽂件来load class,转到SpringFactoriesLoader.loadFactoryNames,源码如下:
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = Name();
try {
Enumeration ex = classLoader != Resources("META-
INF/spring.factories"):SystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论