微服务专题01-Spring Application

前言

前面的章节我们讲了源码分析专题分布式专题工程专题。从本节开始,进入微服务专题的讲解,共计16小节,分别是:

本节内容重点为:

  • 自定义 SpringApplication:介绍 SpringApplicationSpringApplicationBuilder 的API调整
  • 配置 Spring Boot 源:理解 Spring Boot 配置源
  • SpringAppliation 类型推断:Web 应用类型、Main Class 推断
  • Spring Boot 事件:介绍 Spring Boot 事件与 Spring Framework 事件之间的差异和联系

自定义 SpringApplication

如图所示,在官网上快速搭建一个 SpringBoot 项目,通常把它称为 SpringBoot 的脚手架。
在这里插入图片描述
最简单的SpringBoot项目也会包括SpringApplication启动类,所以通常我们将SpringApplication作为一切的起点,首先看看这个类是如何定义的

SpringApplication 的详细讲解

展开刚才我们通过脚手架创建的代码,直接查看启动类:

@SpringBootApplication
public class DemoApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DemoApplication.class, args);
    }
}

这就是最简化版本的 SpringBoot 启动类,这里有两个地方值得我们注意:
一个是类 SpringApplication ,另外一个是注解 @SpringBootApplication

Q: 什么是 SpringApplication

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

Q:怎么理解注解 @SpringBootApplication ?

A:直接查看此注解的源码,源码如下:

/**
* @since 1.2.0
*/
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
    
    
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
    
    ...
}

这里有三个注解值得注意:

  1. @ComponentScan 是用于定义扫描的路径,从中找出标识了需要装配的类自动装配到 Spring 的 Bean 容器中。
  2. @EnableAutoConfiguration 是激活自动装配,用于自动载入应用程序所需的所有 Bean。
  3. @SpringBootConfiguration 用于标注当前类是配置类,并会将当前类内声明的一个或多个以 @bean 注解标记的方法的实例纳入到 Spring 容器中,它与 @Configuration,在功能上是一致的。

Q: 如何理解 @Component 的“派生性”?

A:@Component 是用来把普通 pojo 实例化到 Spring 容器中,相当于配置文件中的<bean id="" class=""/>,通常我们将 @Component 称之为元注解,所谓的派生性指的是以元注解为基准,其他注解再次调用元注解而产生的派生。

Spring 注解编程模型

推荐一篇关于 Spring 注解编程模型的 Wiki

这里再看一下在 SpringBoot 常见的注解,诸如 @Service@Repository@Controller@Configuration。在 Spring 源码里,其实都是引入了 @component,作为这些注解的元注解。

@component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

  1. @Service :用于标注业务层组件。

    @Component
    public @interface Service {
          
          
        ...
    }
    
  2. @Repository:用于标注数据访问组件,即 DAO 组件。

    @Component
    public @interface Repository {
          
          
        ...
    }
    
  3. @Controller:用于标注控制层组件。

    @Component
    public @interface Controller {
          
          
        ...
    }
    
  4. @Configuration :允许通过调用同一类中的其他 @Bean 方法来定义 Bean 之间的依赖关系。

    @Component
    public @interface Configuration {
          
          
    	...
    }
    

Spring 模式注解:Stereotype Annotations

所谓的模式注解:@component逻辑上与@Service@Repository@Controller@Configuration都是一样,只是物理层面上不同,但都是为了找到BeanDefinition。

Spring 注解驱动示例

区别于ClassPathXmlApplicationContext以xml配置文件驱动,注解驱动的上下文是 AnnotationConfigApplicationContext ,这是在 Spring Framework 3.0 开始引入的

在springcloud的微服务架构中,会多次提到版本问题,因为不同版本之间差异性可能很大,比如springcloud的1.0与2.0差异就很明显,所以平时应注意版本问题!

@Configuration
public class SpringAnnotationDemo {
    
    

    public static void main(String[] args) {
    
    
        // @Bean @Configuration
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册一个 Configuration Class = SpringAnnotationDemo
        context.register(SpringAnnotationDemo.class);
        // 上下文启动
        context.refresh();
        System.out.println(context.getBean(SpringAnnotationDemo.class));
    }
}

无论是xml配置还是注解配置,其目的只有一个,就是找 BeanDefinition

现在我们来分析一下,@SpringBootApplication 这个注解,下面是通过源码显示的调用关系

@SpringBootApplication 标注当前一些功能。我们知道,注解不能像 Java 类一样继承,就通过以上的这样的方式层层调用,而注解的功能基本相似,我们就把注解的这种特性称之为派生性。

  • @SpringBootApplication
    在这里插入图片描述
  • @SpringBootConfiguration
    在这里插入图片描述
  • @Configuration
    在这里插入图片描述

@SpringBootApplication 标注当前一些功能。我们知道,注解不能像Java类一样继承,就通过以上的这样的方式层层调用,而注解的功能基本相似,我们就把注解的这种特性称之为派生性

SpringApplication 启动类的基本写法

SpringApplication 是 Spring Boot 应用的引导。

SpringApplication:

@SpringBootApplication
public class DemoApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DemoApplication.class, args);
    }
}

或:

@SpringBootApplication
public class DemoApplication{
    
    
    public static void main(String[] args) {
    
    
    SpringApplication springApplication = new SpringApplication(DemoApplication.class);
           Map<String,Object> properties = new LinkedHashMap<>();
           properties.put("server.port",0);
           springApplication.setDefaultProperties(properties);
           ConfigurableApplicationContext context = springApplication.run(args);
    }
}

SpringApplicationBuilder:

@SpringBootApplication
public class DemoApplication{
    
    
    public static void main(String[] args) {
    
    
        new SpringApplicationBuilder(DemoApplication.class) // Fluent API
                // 单元测试是 PORT = RANDOM
                .properties("server.port=0")  // 随机向 OS 要可用端口
                .run(args);
    }
}

非 Web 程序:

@SpringBootApplication
public class DemoApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication springApplication = new SpringApplication(DemoApplication.class);
        Map<String, Object> properties = new LinkedHashMap<>();
        properties.put("server.port", 0);
        springApplication.setDefaultProperties(properties);
        // 设置为 非 web 应用
        springApplication.setWebApplicationType(WebApplicationType.NONE);
        ConfigurableApplicationContext context = springApplication.run(args);
        // 当前 Spring 应用上下文的类:org.springframework.context.annotation.AnnotationConfigApplicationContext
        System.out.println("当前 Spring 应用上下文的类:" + context.getClass().getName());
    }
}

配置 Spring Boot 源

SpringAppliation 类型推断

我们看看 Spring 在创建 SpringApplication 这个对象的过程中是如何对于 WebApplicationType 赋值的?

在 Spring 源码里查看 SpringApplication 的构造方法里调用了 deduceWebApplicationType() 方法,如下图所示:
在这里插入图片描述

deduceWebApplicationType 方法的逻辑为:

private WebApplicationType deduceWebApplicationType() {
    
    
		//1. 返回 Spring WebFlux 类型
		if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
				&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
    
    
			return WebApplicationType.REACTIVE;
		}
		//2. 返回非 Web 类型
		for (String className : WEB_ENVIRONMENT_CLASSES) {
    
    
			if (!ClassUtils.isPresent(className, null)) {
    
    
				return WebApplicationType.NONE;
			}
		}
		//3. 返回 Spring MVC 类型
		return WebApplicationType.SERVLET;
}

此方法里判断的类名称对应为:
在这里插入图片描述

字段 对应类(取相对路径)
REACTIVE_WEB_ENVIRONMENT_CLASS DispatcherHandler
MVC_WEB_ENVIRONMENT_CLASS DispatcherServlet
WEB_ENVIRONMENT_CLASSES Servlet,ConfigurableWebApplicationContext

此方法一共返回三种枚举类型,分别是:
WebApplicationType.REACTIVE (对应 Spring WebFlux 应用)。
WebApplicationType.NONE (对应 非 Web 类型应用)。
WebApplicationType.SERVLET (对应 Spring MVC 应用)。

WebFlux 虽然类似于 SpringMVC ,但二者不等同,所以这里的三种参数将所有的启动模式都囊括了。

接下来开始判断(注意程序至上而下的顺序):

  1. 如果类 DispatcherHandler 存在,并且类 Servlet 不存在,则此时推断类型为 WebFlux 类型。

类 DispatcherHandler 存在,说明 spring-boot-starter-webflux 依赖存在,类 Servlet 不存在,说明 spring-boot-starter-web 依赖不存在。

  1. 如果类 Servlet 不存在,并且 Spring Web 应用上下文 ConfigurableWebApplicationContext 也不存在,则此时推断类型为非 Web 类型。

ConfigurableWebApplicationContext 不存在说明对应的依赖也应该不在此项目中:spring-boot-starter-web 和 spring-boot-starter-webflux。

  1. 1与2条件都不满足,则推断类型为 Spring MVC 类型。

如果推断类型为 MVC,则依赖 spring-boot-starter-web 存在。

Tips:上面推断中的依赖对应为(POM)

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

人工干预 Web 类型

Q:如何人工干预启动程序为 Web 类型?

A:可以设置 webApplicationType 属性 为 WebApplicationType.NONE,当然也可以设置 MVC 与 WebFlux 类型, 如下图所示:
在这里插入图片描述

Spring Boot 事件

首先我们运行一个 Spring 事件demo:

public class SpringEventListenerDemo {
    
    
 public static void main(String[] args) {
    
    
        GenericApplicationContext context = new GenericApplicationContext();
        // 添加自义定监听器
        context.addApplicationListener(new ClosedListener());
        context.addApplicationListener(new RefreshedListener());
        // 启动 Spring 应用上下文
        context.refresh();
        // Spring 应用上下文发布事件
        context.publishEvent("HelloWorld"); // 发布一个 HelloWorld 内容的事件
        // 一个是 MyEvent
        context.publishEvent(new MyEvent("HelloWorld 2020"));
        // 关闭应用上下文
        context.close();
    }

    private static class RefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
    
    

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
    
    
            System.out.println("上下文启动:" + event);
        }
    }

    private static class ClosedListener implements ApplicationListener<ContextClosedEvent> {
    
    

        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
    
    
            System.out.println("关闭上下文:" + event);
        }
    }

    private static class MyEvent extends ApplicationEvent {
    
    

        public MyEvent(Object source) {
    
    
            super(source);
        }
    }
}

Spring 内部发送事件

在上面演示的DEMO中,我们通过添加两个自定义监听器(ClosedListener 与 RefreshedListener),然后将其发布,最后关闭上下文。整体过程原理可以参考Spring源码,这里给出调用链关系。

开启上下文涉及的类的层级关系:ContextRefreshedEvent -> ApplicationContextEvent->ApplicationEvent

开启上下文调用链:refresh() -> finishRefresh() -> publishEvent(new ContextRefreshedEvent(this));

关闭上下文涉及的类的层级关系:ContextClosedEvent->ApplicationContextEvent->ApplicationEvent

关闭上下文调用链:close() -> doClose() -> publishEvent(new ContextClosedEvent(this));

自定义事件
Q:在上面的演示 DEMO 里,除了调用 Spring 内部的事件之外,我们又调用了自定义的事件,在 Spring 源码里是怎么判断的呢?

A:点击 context.publishEvent 的 publishEvent 方法,我们发现核心逻辑如下:

这里会判断是否是spring内置事件,不是内置事件交由PayloadApplicationEvent来处理

在这里插入图片描述

总结: 无论是自定义事件还是内置事件,Spring 事件都是 ApplicationEvent 类型,Spring 事件可以理解为消息。

  • Spring 事件的类型 ApplicationEvent(相当于消息内容)。
  • Spring 事件监听器 ApplicationListener(相当于消息消费者、订阅者)。
  • Spring 事件广播器 ApplicationEventMulticaster(相当于消息生产者、发布者)。

Q:发送 Spring 事件的源码逻辑是怎样的?

A: 通过 ApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType),实现类参考源码:SimpleApplicationEventMulticaster

接下来以 ApplicationEventMulticaster 做一个演示:

 public static void main(String[] args) {
    
    
        ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();

        // 添加监听器
        multicaster.addApplicationListener(event -> {
    
    
            if (event instanceof PayloadApplicationEvent) {
    
    
                System.out.println("接受到 PayloadApplicationEvent :"
                        + PayloadApplicationEvent.class.cast(event).getPayload());
            }else {
    
    
                System.out.println("接收到事件:" + event);
            }
        });
        // 发布/广播事件
        multicaster.multicastEvent(new MyEvent("Hello,World"));
        multicaster.multicastEvent(new PayloadApplicationEvent<Object>("2", "Hello,World"));

    }

    private static class MyEvent extends ApplicationEvent {
    
    

        public MyEvent(Object source) {
    
    
            super(source);
        }
    }

运行结果:
在这里插入图片描述

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();
        ; // 运行
    }
}

运行结果:
按照时间顺序打印的顺序如下:
在这里插入图片描述
在这里插入图片描述

运行结果按照时间顺序打印的顺序如下:

监听到事件 : ApplicationStartingEvent
监听到事件 : ApplicationEnvironmentPreparedEvent
监听到事件 : ApplicationPreparedEvent
监听到事件 : ContextRefreshedEvent
监听到事件 : ServletWebServerInitializedEvent
监听到事件 : ApplicationStartedEvent
监听到事件 : ApplicationReadyEvent
监听到事件 : ContextClosedEvent

实际上还有一种特殊事件,它是:ApplicationFailedEvent,在应用程序启动出错会加载此事件。

Spring Boot 事件监听器

在类 ApplicationListener 的配置文件里我们发现有如下配置:

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 利用 SpringApplication 用于引导和启动一个 Spring 应用程序。SpringApplication 类能够做如下事情:

  • 为应用创建一个 ApplicationContext,用于生命周期控制,注解驱动 Bean。
  • 为应用创建一个 ApplicationEventMulticaster,用于加载或者初始化组件。

后记

本节思考:
q1:webApplicationType分为三种都有什么实用地方?

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

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    
    

	@Nullable
	private Executor taskExecutor;
    ...
}

更多架构知识,欢迎关注本套Java系列文章Java架构师成长之路

猜你喜欢

转载自blog.csdn.net/qq_34361283/article/details/106144214
今日推荐