微服务学习笔记01

第一节 SpringApplication

本文是在预习和学习后,想要尽自己所能将学到的知识以自己的理解记录下来。当然,有些地方还是一知半解,所以是有很多内容是小马哥所讲所书,权当引经据典,还望小马哥见到不要怪罪。

虽然今年项目上已经接触过SpringBoot,目前仍在使用,但了解到的只是常用的用法。这篇文章主要讲一下微服务的概念,以SpringApplication启动类为切入点,讲解Spring注解机制,SpringApplication启动类的写法和程序类型推断,以及SpringBoot事件和事件监听。

一、微服务是什么?能做什么?

1、概念

微服务是一种细粒度(Fine-Grain)的SOA

SOA = Service-Oriented Architecture 面向服务架构

SOA的特征:

面向服务( Service-Oriented )

松耦合(Loose-Coupling)

模块化(Modular)

分布式计算(Distributed Computing)

平台无关性(Independent Platform)

集中管理(Center Government)

用到的技术:

Web Services

关于Web Services,小马哥文章中讲到下面一段话,因个人技术原因,暂时没有特别读懂,暂且放在这里供大家参考,也欢迎各位大牛指点迷津。

Web Services 技术演进的目的在于解决分布式计算中,统一异构系统的服务调用的通讯协议。前期的Web Services有XML-PRC、WSDL、SOAP等技术,不但解决了Windows平台COM+以及Java 平台RMI无法跨平台的问题,而且使用了可读性强的本文协议替代了复杂的二进制协议,如CORBA技术。现代的WebServices 技术主要代表有REST等。

此外,还提到微服务并非等同于单体应用,我所理解的意思是单体应用只有一个核心,是中心化的,任务是应用整体来完成的,有较高的耦合度。而微服务是去中心化的,多核的,将任务拆解,由各个模块分工协作完成的,耦合度是较前者要低的。

2、微服务的优势和适用场景

优势:

(1)效率:应用微服务化后,变得更加轻量级,在编译、打包、分发、部署等环节节约时间,提升效率;

(2)质量:面向持续集成友好,自动化编译、单元和集成测试用例执行和回归,提高整体质量。

这里讲的所谓“持续集成”,个人不是很清楚这个概念。

(3)稳定:当应用大而全时,一个服务的问题可能会导致整体受到影响。而微服务在服务A调用服务B出现问题时,可以选择降级或熔断的方式进行处理,等待服务B正常运行后,恢复正常。

(4)运维:微服务应用具备自动化编译、打包、分发、部署和运维的能力。

(5)成长:对新技术的适配能力,例如:Apache Kafka。在重要等级较低的微服务应用上,可以更好的做新技术的尝试。

3、不适用微服务的应用场景

(1)场景单一:如果应用本身规模就不大,就没必要使用微服务,因为本身就是一种微服务。举例如:一些静态通告页面。

(2)逻辑简单:微服务是为了解决业务逻辑复杂性问题,因此此场景无需微服务。

(3)业务渐逝:如果业务将要消逝或停用,就不需要实施了。

(4)“老成持重”:老--服役三年以上的应用;成--应用规模已经成型;持--应用场景还需要长时间维持;重--应用属于系统中较重要的模块。

二、微服务用到的技术

技术上,在阿里微服务的实践过程中也不能免俗,基本上也是以下三个套路:

Docker

DDD

Middleware(Java)

此处虽然有所讲解,但因主次问题,本文不多做介绍。

大家可以做一下扩展阅读:

《2016.11.19 微服务实践之路(厦门) 演讲稿》, 次凌均阁(小马哥微信公众号)

另外,由于小马哥提到Thymeleaf,所以网上查阅一些相关资料。在查阅时,在知乎上发现了一个beetl和Thymeleaf互怼的帖子,感觉很有意思。虽然只接触过framemark和vue,以上两种都没有用到过,但是感觉这样的讨论能够让自己对一些技术有一些大致的印象。道理未必懂得,但学习都是由浅入深,是有一个过程的。

三、SpringApplication是什么?

SpringApplication 是 Spring Boot 驱动 Spring 应用上下文的引导类。

示例:

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(excludeFilters = {

        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),

        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

public @interface SpringBootApplication {

    ...

}

涉及到的注解:

@ComponentScan: 它是版本引入的? Spring Framework 3.1。

这里强调一种版本意识,版本差异所带来的问题有时是很严重的。

@EnableAutoConfiguration: 激活自动装配

这里扩展了一下,由@Enable,提到@Enable 开头的:

@EnableWebMvc: 是使用Java 注解快捷配置Spring Webmvc的一个注解。

在使用该注解后配置一个继承于WebMvcConfigurerAdapter的配置类即可配置好Spring Webmvc。

通过查看@EnableWebMvc的源码,可以发现该注解就是为了引入一个DelegatingWebMvcConfiguration Java 配置类。并翻看DelegatingWebMvcConfiguration的源码会发现该类似继承于WebMvcConfigurationSupport的类。

其实不使用@EnableWebMvc注解也是可以实现配置Webmvc,只需要将配置类继承于WebMvcConfigurationSupport类即可。

@EnableTransactionManagement: 开启注解事务管理,等同于xml配置文件中的

@EnableAspectJAutoProxy: 表示开启AOP代理自动配置。

如果配@EnableAspectJAutoProxy表示使用cglib进行代理对象的生成;设置@EnableAspectJAutoProxy(exposeProxy=true)表示通过aop框架暴露该代理对象,aopContext能够访问。

从@EnableAspectJAutoProxy的定义可以看得出,它引入AspectJAutoProxyRegister.class对象,该对象是基于注解@EnableAspectJAutoProxy注册一个AnnotationAwareAspectJAutoProxyCreator,该对象通过调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);注册一个aop代理对象生成器。

@EnableAsync: 表示开启异步调用支持,配合@Async使用。

@SpringBootConfiguration: 等价于 @Configuration -> Configuration Class 注解

此处提个小疑问:

以下关系,大家应该都了解。

@SpringBootApplication= (默认属性)@Configuration+@EnableAutoConfiguration+@ComponentScan

但是,我在实际项目使用中,使用@SpringBootApplication注解后,无法扫描到包,还需要使用@ComponentScan注解,是因为没有在SpringApplication后指定扫描路径?启动类在外层,就算是扫描自身目录和子包也应该可以找到,不知道大家有没有遇到过类似的问题。

四、Spring 注解编程模型@Component

@Component

示例:

@Service

@Component

public @interface Service {

    ...

}

@Repository

@Component

public @interface Repository {

    ...

}

@Controller

@Component

public @interface Controller {

    ...

}

@Configuration

@Component

public @interface Configuration {

    ...

}

因为注解是没有继承关系的,所以小马哥讲之称为@Component的“派生性”。

五、Spring 模式注解(Stereotype Annotations)

Spring 注解驱动示例

注解驱动上下文AnnotationConfigApplicationContext,Spring Framework 3.0开始引入的

示例:

@Configuration

public class SpringAnnotationDemo {

    public static void main(String[] args) {

        //  XML 配置文件驱动      ClassPathXmlApplicationContext

        // Annotation 驱动

        // 找 BeanDefinition

        // @Bean @Configuration

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 注册一个 Configuration Class = SpringAnnotationDemo

        context.register(SpringAnnotationDemo.class);

        // 上下文启动

        context.refresh();

        System.out.println(context.getBean(SpringAnnotationDemo.class));

    }

}

这里讲解了两种Spring注解驱动方式,XML配置文件驱动和Annotation注解驱动,现在用的比较多的是Annotation驱动,经常听到XML配置方式被群里的大佬们诟病。感觉小项目使用XML配置还可以,如果项目规模较大,一堆配置文件就会很头疼了。

@SpringBootApplication 标注当前一些功能

下列列出的好像是层级关系,这里记的有些不清楚。

@SpringBootApplication

@SpringBootConfiguration

@Configuration

@Component

下面看一下 SpringApplication 也就是 Spring Boot 应用的引导方法。

基本写法

SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);

        Map properties = new LinkedHashMap<>();

        properties.put("server.port",0);

        springApplication.setDefaultProperties(properties);

        springApplication.run(args);

SpringApplicationBuilder

new SpringApplicationBuilder(MicroservicesProjectApplication.class) // Fluent API

                // 单元测试是 PORT = RANDOM

                .properties("server.port=0")  // 随机向 OS 要可用端口

                .run(args);

小马哥提到了两种书写风格,其中一种叫做Fluent API风格,另外一种没有听清。有没有大佬知道上面写法的名称?

六、Spring Boot 引导过程

示例:

@SpringBootApplication

public class MicroservicesProjectApplication {

    public static void main(String[] args) {

        SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);

        Map properties = new LinkedHashMap<>();

        properties.put("server.port",0);

        springApplication.setDefaultProperties(properties);

        ConfigurableApplicationContext context = springApplication.run(args);

        // 有异常?

        System.out.println(context.getBean(MicroservicesProjectApplication.class));

    }

}

这个示例运行结果是正常的,springApplication.run(args)和context.getBean(MicroservicesProjectApplication.class)并不会有冲突异常。

如果没有记错,通过控制台可以看出,运行了两次。实际结果,有条件后运行一下代码再下定论。

调整示例为 非 Web 程序

示例:

@SpringBootApplication

public class MicroservicesProjectApplication {

    public static void main(String[] args) {

        SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);

        Map properties = new LinkedHashMap<>();

        properties.put("server.port", 0);

        springApplication.setDefaultProperties(properties);

        // 设置为 非 web 应用

        springApplication.setWebApplicationType(WebApplicationType.NONE);

        ConfigurableApplicationContext context = springApplication.run(args);

        // 有异常?

        System.out.println(context.getBean(MicroservicesProjectApplication.class));

        // 输出当前 Spring Boot 应用的 ApplicationContext 的类名

        System.out.println("当前 Spring 应用上下文的类:" + context.getClass().getName());

    }

}

输出结果:

当前 Spring 应用上下文的类:org.springframework.context.annotation.AnnotationConfigApplicationContext

配置 Spring Boot 源

SpringAppliation 类型推断

当不加以设置 Web 类型,那么它采用推断:

->SpringAppliation()->deduceWebApplicationType() 第一次推断为WebApplicationType.SERVLET

WebApplicationType.NONE: 非 Web 类型

Servlet 不存在

Spring Web 应用上下文ConfigurableWebApplicationContext不存在

spring-boot-starter-web不存在

spring-boot-starter-webflux不存在

WebApplicationType.REACTIVE: Spring WebFlux

DispatcherHandler

spring-boot-starter-webflux存在

Servlet 不存在

spring-boot-starter-web不存在

WebApplicationType.SERVLET: Spring MVC

spring-boot-starter-web存在

人工干预 Web 类型

设置 webApplicationType 属性 为WebApplicationType.NONE

七、SpringBoot事件

Spring 事件

(1)Spring 内部发送事件

事件发布:

ContextRefreshedEvent

ApplicationContextEvent

ApplicationEvent

refresh() ->finishRefresh()->publishEvent(new ContextRefreshedEvent(this));

事件关闭:

ContextClosedEvent

ApplicationContextEvent

ApplicationEvent

close()->doClose()->publishEvent(new ContextClosedEvent(this));

(2)自定义事件

PayloadApplicationEvent

Spring 事件 都是ApplicationEvent类型

发送 Spring 事件通过ApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)

Spring 事件的类型ApplicationEvent

Spring 事件监听器ApplicationListener

Spring 事件广播器ApplicationEventMulticaster

实现类:SimpleApplicationEventMulticaster

Spring 事件理解为消息

ApplicationEvent相当于消息内容

ApplicationListener相当于消息消费者、订阅者

ApplicationEventMulticaster相当于消息生产者、发布者

注意:不能只发布事件,没有监听。否则,启动程序是获取不到任何事件的。

这里提到了“监听者模式”,大家应该都会联想到“观察者模式”,两者很相似,所以我去查阅了一些参考资料:

设计模式之监听模式(观察者模式与监听模式区别)

设计模式之观察者模式, 个人感觉相当的重要(七)

个人理解:

监听者模式包含事件源、事件和监听器,像是半自动化的监听,需要手动或者说是人工对事件源和事件做处理,然后提供给监听器;观察者模式包含观察者和被观察者(也就是事件),像是自动化监听,只需要对事件负责即可。

监听者模式更加灵活,比较适用于需要自我把控的场景,比如底层框架的实现:spring框架的ApplicationEvent,ApplicationListener。

观察者模式更加轻便,比较适用于约束比较固定的场景,比如前台按钮的点击事件,至于,后台用到观察者模式的场景暂时没有想到。

Spring Boot 事件监听示例

示例:

@EnableAutoConfiguration

public class SpringBootEventDemo {

    public static void main(String[] args) {

        new SpringApplicationBuilder(SpringBootEventDemo.class)

                .listeners(event -> { // 增加监听器

                    System.err.println("监听到事件 : " + event.getClass().getSimpleName());

                })

                .run(args)

                .close();

        ; // 运行

    }

}

通过运行程序可知,事件的运行顺序(括号中是官方文档中标注的顺序,好像官方文档中只提到了Application开头的事件,没有启动过程中看到的Context和Servlet事件):

ApplicationStartingEvent(1) 启动

ApplicationEnvironmentPreparedEvent(2) 环境配置

ApplicationPreparedEvent(3) 这里应该是

ContextRefreshedEvent 创建上下文context

ServletWebServerInitializedEvent

ApplicationStartedEvent(4)

ApplicationReadyEvent(5)

ContextClosedEvent

ApplicationFailedEvent (特殊情况)(6)

最后一种是启动失败的情况,所以做了特殊标注。

此外,这里提到了lambda表达式:

event -> { // 增加监听器

                    System.err.println("监听到事件 : " + event.getClass().getSimpleName());

                }

之前没有接触过,在此贴上两篇文章:

Java中Lambda表达式的使用

Lambda表达式详细总结

Spring Boot 事件监听器

org.springframework.context.ApplicationListener=\

org.springframework.boot.ClearCachesApplicationListener,\

org.springframework.boot.builder.ParentContextCloserApplicationListener,\

org.springframework.boot.context.FileEncodingApplicationListener,\

org.springframework.boot.context.config.AnsiOutputApplicationListener,\

org.springframework.boot.context.config.ConfigFileApplicationListener,\

org.springframework.boot.context.config.DelegatingApplicationListener,\

org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\

org.springframework.boot.context.logging.LoggingApplicationListener,\

org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

ConfigFileApplicationListener监听ApplicationEnvironmentPreparedEvent事件从而加载 application.properties 或者 application.yml 文件。

Spring Boot 很多组件依赖于 Spring Boot 事件监听器实现,本质是 Spring Framework 事件/监听机制。

SpringApplication利用:

Spring 应用上下文(ApplicationContext)生命周期控制 注解驱动 Bean

Spring 事件/监听(ApplicationEventMulticaster)机制加载或者初始化组件

扩展问题:

q1:webApplicationType分为三种,都有什么实用地方

个人鄙见:

类型为SERVLET时,就是常用的web服务,受眼界所限,我了解的大部分项目都可以使用这种类型。是不是因为常用,所以才会将SERVLET设置为默认类型?

类型为NONE时,意味着该应用启动后,不会加载任何东西。是不是在启动时需要做情况判断的时候可以用到?

类型为REACTIVE时,支持响应式编程,响应式编程是基于异步和事件驱动的非阻塞程序。

响应式编程技术上有RxJava等,可以提高代码的抽象程度,让开发者能够更加专注于业务逻辑。我觉得响应式编程可以将业务拆解成细粒度的事件,一方面代码维护性好(专注于业务逻辑,忽略具体实现),另一方面能够更好地应对业务逻辑的更改(有可能只需要改变部分环节的事件逻辑)。

参考资料:

响应式编程介绍

响应式编程,是明智的选择

响应式设计的优势有哪些?

Rxjava(3.响应式编程好处)

q2:框架底层的事件是单线程么?业务实现是否可以使用事件去实现?如果使用事件实现是不是会有性能问题?

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    @Nullable

    private Executor taskExecutor;

    ...

}

个人鄙见:

从SpringBoot事件运行示例来看,是单线程、顺序执行。至于使用事件来实现业务,这方面确实不甚清楚。

总结

本人野生码农一枚,今日新手上路,不敢太过造次,也不想因自己不知所谓的看法误导大家。文中的问题,待我证实以后,再做修改。

谨此,止笔。

你可能感兴趣的:(微服务学习笔记01)