Spring boot构建自定义的启动注解

        当需要将直接的项目打成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 = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            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

  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.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

  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,则会采用默认的。结果如下



作者: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<EnableDiscoveryClient> {

    @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<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);
        }

        return factories.toArray(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 = 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

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值