1、背景:服务的发展从单体架构开始,随着用户量越来越大,以及业务越来越复杂我们开始进行优化,两方面横向增加服务器,单机变集群,按照业务垂直领域进行划分,减少业务耦合,单个jar包更灵活、功能更明确,更便于伸缩性提高。但是随着使用我们发现虽然我们把服务拆分的粒度小,功能明确,但是也有一些问题孤岛信息,就是每个独立运行无法共享造成浪费,甚至有些功能也会重复操作执行,所以后来又有了SOA也就是面向服务的架构,面向服务就是把服务作为一个模块而不是拆分功能,无论怎么拆分吧,服务变得细化、数量多了之后我们面临着,各个服务的故障排查、监控、运维、依赖,我们自己来管理这是一个很耗费精力的问题,包括细化来说还有其他比如配置管理、服务路由、负载均衡、熔断限流、链路监控等,所以引出了spirngcloud技术。
2、springcloud简介:springcloud的出现主要致力于为我们解决以下问题:分布式及版本化配置、服务注册与发现、路由转发服务、服务间调用、负载均衡、熔断限流、全局锁、分布式消息、Leader选举及集群状态。springcloud就是一套规范,而spring netflix ,spring cloud consul,spring cloud alibaba才是springcloud的实现。springcloud的版本是按照伦敦地铁站名字来发布的具体就不说了首字符ABCDEFGH分别开头的,目前是Hoxton版本,对应springboot的2.2.X;2.3.X。目前spring cloud 生态下服务治理的解决方案主要有两个:springcloud netflix 和springcloud alibaba。其中springcloud netflix 包括以下组件:Eureka(服务注册与发现)/Zuul(服务网关)/Ribbon(负载均衡)/Feign(远程服务的客户端代理)/Hystrix(断路器,提供服务熔断和限流功能)/Hystrix Dashboard(监控面板)/Turbine(将各个服务实力上的Hystrix监控信息进行统一聚合)需要说一下的是目前Zuul1/Ribbon/Hystrix/Hystrix Dashboard/Archaius等组件已经不再更新。当然springcloud官方也给出了相应的替换组件比如zuul1替换为gateway、Ribbon替换为springcloud loadbalancer等。并且springcloud alibaba合适很好的方向。
3、springcloud alibaba简介:springcloud alibaba是2018年10月31日成为springcloud官方孵化器项目,2019年8月1日发布第一个正式版本。springcloud alibaba包含的组件分别是Sentinel(流量控制和服务降级)、Nacos(服务注册与发现以及分布式配置中心)、RocketMq (消息驱动)、Seate(分布式事务)、Dubbo(RPC通信)、OSS 阿里云对象存储(收费)。springcloud alibaba截至目前为止也已经发布了多个版本,目前对应springboot 2.2.X的alibaba版本是2.2.x,springcloud版本是Spring Cloud Hoxton。
4、重新认识springboot:spring中最终的IOC概念:IOC说白了就是将之前new对象然后使用的方式改变为了交给spring容器管理而不是再创建。DI依赖注入,其实就是比如我们注入UserService这个实力,但是其关联的User被解析到的时候也会进行注入,官方解释Ioc容器在运行起见,动态的把某种依赖关系注入到组件中。关于Bean的装配方式,早期当然是XML来描述,但是随着注解越来越主流,再后来spring使用@Configuration与@Bean来描述注入,默认采用方法名称作为该Bean的id。其实依赖注入的主要实现方式有三种分别是:接口注入、构造方法注入以及setter方法注入。XML方式中比如定义一个bean标签的时候里面定义其属性标签
<bean id"beanDefine" class = "com.gupaodu.spring.BeanDefine">
<property name="dependencyBean" ref="dependencyBean/">
<bean>
<bean id = "dependencyBean" class = "class.gupaodu.spring.dependencyBean"/>
如果转化为注解就可以表示为
@Configuration
public class SpringConfigClass{
@Bean
public BeanDefine beanDefine(){
BeanDefine beanDefine = new BeanDefine();
beanDefine.setDependencyBean(dependencyBean());
return beanDefine;
}
@Bean
public DependencyBean dependencyBean(){
return new DependencyBean();
}
}
当前除了此种注解之外还有@CommponentScan对应XML形式的<context:component-scan base-package="">。以及@Import对应XML形式的<import resource="">。两者换汤不换药,但是spirng存在如下问题:依赖过多,spring几乎可疑整合所有常用技术框架,也就导致了不同版本的依赖包很多,容易出现版本兼容性问题。配置较多,比如spring使用javaConfig方式整合Mybatis需要配置注解驱动、配置参数、配置mybatis等,然后我们就引入了springboot技术,springboot2014年发布第一个版本1.0.0,spring的作用就是简化spring的开发,它的核心思想就是约定优于配置,springboot的出现使得我们在只需要在springboot的官网或者idea上直接创建后就可以直接编写业务逻辑,而不是再需要去添加各种基础jar包,配置XML或者JavaConfig。springboot最核心的技术就是自动装配,它是starter的基础也是springboot的核心,那么什么是自动装配呢?简单来说就是自动将bean装配到IoC容器中。举个例子,spring-boot-starter-data-redis,我们引入依赖包之后只需要在配置文件中配置host/port/即可在业务层中通过@Autowired注入RedisTemplate redistemplate;进行redis的函数操作。很显然这就是一个自动装配,那么springboot是怎么帮我们实现自动装配的呢?这个可以从springboot的启动类上的@springbootapplication注解开始分析,它里面包含的@enableautoconfiguration注解开启了自动装配。说一下@enable注解,JavaConfig中说过我们注入一个Bean需要使用@Configuration@Bean两个注解实现装配但是@Enable本质就是对这两个类的封装,@Enable类中都会带有一个@Import注解,Spring会解析到@Import导入的配置类,从而根据这配置类中的描述来实现Bean的装配。我们进入@EnableAutoConfiguration注解中会发现其包含了@AutoConfigurationPackage这个注解,这个注解的意思是将打上此注解的类所在的包及其子包下所有的组件扫描到IOC容器当中,然后我们看下@Import导入的是一个AutoConfigurationImportSelector类,因此这就是把AutoConfigurationImportSelector这个配合类进行了导入。AutoConfigurationImportSelector类实现了DeferredImportSelector这个接口又继承了ImportSelector接口,这个接口当中有一个selectImports抽象方法,并且返回一个String[],其实就是在这个数组当中指定了需要装配到IOC中的类。当在@Import中导入一个ImportSelector的实现类之后,会把该实现类中的返回的Class名称都装载到IoC容器中。@EnableAutoConfiguration和@Configuration不同的是,importSelector可以实现批量装配,并且还可以通过逻辑处理来实现Bean的选择性装配,也就是可以通过上下文来决定哪些类能够被Ioc容器初始化。下面举例演示一下,创建两个普通java类,FirstClass/secondClass,再创建一个ImportSelector实现类,然后只需要实现其selectImports方法中把上面两个类的Bean加入String[]。
@OverWrite
public String[] selectImports(AnnotationMetadata importingClassMetadata){
return new Stirng[] {FirstClass.class.getName(),SecondClass.class.getName()};
}
为了模拟@EnableAutoConfiguration我们可以自定义一个类似的注解,通过@Import导入自定义配置实现类。详见《springcloudalibaba 微服务原理与实战-page30》。我们继续分析原理,我们继续看这个selectImports,这个方法里面主要是两个功能方法,一个是AutoConfigurationMetadataLoader.loadMetadata从META-INF/spring-autoconfigure-metadata.properties文件中加载自动装配的条件元数据,简单来说就是只有满足条件的Bean才能够进行装配,而第二个功能方法就是autoConfigurationEntry.getConfigurations(),完成自动装配。需要注意的是在这个Importselector接口的实现类AutoConfigurationImportSelector中并不执行selectImports方法,而是通过ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法来扫描和注册所有配置类的Bean,最终碍事会调用getAutoCofigurationEntity方法获得所有需要自动装配的配置类。我们重点分析下配置类的收集方法,getAutoConfigurationEntiry,结合之前starter的作用不难猜到,这个方法应该会扫描指定路径下的文件解析得到需要装配的配置类,这里用到了SpringFactoriesLoader,主要有这么几件事情:1、getAttributes 获得@EnableAutoConfiguration注解中的属性exclude.excludeName等。2、getCandidateConfigurations获得所有自动装配的配置类,后续重点分析。3、removeDuplicates去除重复的配置项。4、getExclusions根据@EnableAutoConfiguration注解中配置的exclude等属性,把不需要自动装配的类移除掉。5、fireAutoConfigurationImportEvents广播事件。6、最后返回结果过多层判断和过滤之后的配置类集合。总的来说就是先获取所有的配置类,通过去重、exclude排除等操作得到最终需要实现自动装配的类。需重点关注的是getCandidateConfigurations,它是获得配置类最核心的方法。这个方法里用到了springfactoriesloader.loadfactorynames方法,它是sping内部提供的一种约定俗成的加载方式,类似于java的SPI。简单来说它会扫描classpath下的META-INF/spring.factories文件,spring.factories文件中的数据会以keyvalue形式存储,key就是enableautoconfiguration,value就是配置类。我们自定义封装的starter原理就是如此,在封装starter的时候注解声明类上会打上@EnableConfigurationProperties,@Configuration,一般还会有个@ConditionOnClass,这个条件控制机制在这里的用途是,判断classpath下是否存在RabbitTemplate和Channel这两个类,如果是,则把当前配置类注册到IoC容器。另外,@EnableConfigurationProperties是属性配置,也就是说我们可以在application.properties中配置starter的配置参数。
总结:
1、通过@Import(AutoConfigurationImportSelector)实现配置类的导入;
2、AutoConfigurationImportSelector类实现了ImportSelector接口,重写了方法SelectImports,它用于实现选择性批量配置类的装配。
3、通过spring提供的SpringFactoriesLoader机制,扫描classpath路径下的META-INF/spring.factories,读取需要实现自动装配的配置类。
4、通过条件筛选的方式,把不符合条件的配置类移除,最终完成自动装配。
5、@Conditional条件装配
@Conditional,它是spring framework的一个核心注解,这个注解的作用是提供自动装配的条件约束,一般与@Configuration@Bean一起使用。简单来说就是解析@Configuration配置类时,如果配置类增加了@Conditional那么就会根据该注解配置的条件来决定是否要实现该Bean的装配。我们进入这个数组会发现它接收一个Condition数组Class<? extends Condition> [] value(); Condition是一个函数式接口,里面提供了matches方法,返回true表示可以注入,否则不需要注入。我们可以自定义实现这个Condition接口,然后我们写一个@Condition@Bean的配置类,在@Bean下面打上@Conditional(CustomCondition.class)这个CustomCondition就是我们自定义实现Condition接口的实现类。然后我们可以进行测试
public class ConditionMain{
public static void main(String[] args){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class) ;
BeanClass beanClass =context.getBean(BeanClass.class);
System.out.println(beanClass);
}
}
说下这个测试类方法中的AnnotationConfigApplicationContext,对于main函数中使用spring的场景,有两个常见的高级容器。一个是ClassPathXmlApplicationContext,基于classpath下的xml配置文件;另一种是AnnotationConfigApplicationContext,基于java配置文件。AnnotationConfigApplicationContext的使用有两种,一种是指定java文件配置类,另一种是指定扫描路径。这里使用的就是第一种。
同样的这个注解到了Spirngboot中进行了扩展,扩展为@CondtitionalOnBean/@ConditionalOnMissingBean:容器中存在或者不存在某个Bean就进行装配,@ConditionalOnClas/ConditionalOnMissingClass:classpath下存在或者不存在某个类时进行装配,其他的还可以判断运行的平台、指定运行版本的java、是否是web应用、甚至是根据SpEl表达式、@ConditionOnProperty:系统中指定的对应的属性是否有对应的值、@ConditionalOnResource:要加载的Bean依赖指定资源是否存在于classpath中等。比如我们平时配置文件中判断是否开启就可以用这个@ConditionalOnProperty(value="gp.bean.enable",havingvalue="true",matchIfMissing=true) 这个意思就是配置为true或者不配置也默认开启。
6、Spring-autoconfigure-metadata
除了@Condition注解类,在springboot中还提供了spring-autoconfigure-metadata.properties文件来实现批量自动装配条件配置。他的作用和@Conditional一样,只不过这个是将条件配置放在了配置文件中。
7、手写starter
starter命名规范再多说一遍,官方命名一般是spring-boot-stater-模块名,而我们自定义的命名一般是 模块名-spring-boot-starter
封装样例:不再细说了,根据自己需求封装吧。