第一节 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事件运行示例来看,是单线程、顺序执行。至于使用事件来实现业务,这方面确实不甚清楚。
总结
本人野生码农一枚,今日新手上路,不敢太过造次,也不想因自己不知所谓的看法误导大家。文中的问题,待我证实以后,再做修改。
谨此,止笔。