跳至主要內容

Spring Boot核心原理

soulballad微服务SpringBootSpringBoot约 3252 字大约 11 分钟

Spring Boot核心原理

spring boot 的四大核心

  • SpringBoot Cli
  • starter 开箱即用
  • autoconfiguration 自动装配
  • actuator 监控

什么是 Starter

Starter 是 Spring Boot 中的一个非常重要的概念,Starter 相当于模块,它能将模块所需的依赖整合起来并对模块内的 Bean 根据环境( 条件)进行自动配置。使用者只需要依赖相应功能的 Starter,无需做过多的配置和依赖,Spring Boot 就能自动扫描并加载相应的模块。

在上一节课中,我们在 Maven 的依赖中加入 spring-boot-starter-web 就能使项目支持 Spring MVC,并且 Spring Boot 还为我们做了很多默认配置,无需再依赖 spring-web、spring-webmvc 等相关包及做相关配置就能够立即使用起来

SpringBoot 存在很多开箱即用的 Starter 依赖,使得我们在开发业务代码时能够非常方便的、不需要过多关注框架的配置,而只需要关注业务即可

starter 命名规范

是否官方命名规则
spring官方spring-boot-starter-(name),例如:spring-boot-starter-web
非官方(name)-spring-boot-starter,例如:mybatis-spring-boot-starter

自定义 starter

自定义 starter 项目,结构如下:

1563080945987

pom 依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.1.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
        <optional>true</optional><!--可选-->
    </dependency>
    <!-- 自定义装配需要的依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <version>2.1.6.RELEASE</version>
        <optional>true</optional>
    </dependency>
</dependencies>

format 目录下:

package com.gupaoedu.starter.format;

public interface FormatProcessor {
    //定义一个格式化的方法
    <T> String format(T obj);
}

public class JsonFormatProcessor implements FormatProcessor{
    @Override
    public <T> String format(T obj) {
        return "JsonFormatProcessor:"+ JSON.toJSONString(obj);
    }
}

public class StringFormatProcessor implements FormatProcessor{
    @Override
    public <T> String format(T obj) {
        return "StringFormatProcessor:"+Objects.toString(obj);
    }
}

提供一个 FormatAutoConfiguration 的自动装配类:

package com.gupaoedu.starter.autoconfiguration;

@Configuration
public class FormatAutoConfiguration {
    @ConditionalOnMissingClass("com.alibaba.fastjson.JSON")
    @Bean
    // 同一个接口有多个实现时,需要声明,否则ioc容器无法确定具体注册哪个bean
    @Primary 
    public FormatProcessor stringFormat(){
        return new StringFormatProcessor();
    }

    @ConditionalOnClass(name = "com.alibaba.fastjson.JSON")
    @Bean
    public FormatProcessor jsonFormat(){
        return new JsonFormatProcessor();
    }
}

提供一个对外调用的模板类 HelloFormatTemplate:

package com.gupaoedu.starter;

public class HelloFormatTemplate {

    private FormatProcessor formatProcessor;
    private HelloProperties helloProperties;

    // 这里通过构造方式将helloProperties和formatProcessor注入到HelloFormatTemplate的实例中,
    // 必须保证这两个对戏要初始化,所以分别使用了FormatAutoConfiguration和HelloProperties
    public HelloFormatTemplate(HelloProperties helloProperties,
                               	FormatProcessor formatProcessor) {
        this.helloProperties = helloProperties;
        this.formatProcessor = formatProcessor;
    }

    public <T> String doFormat(T obj){
        StringBuilder stringBuilder=new StringBuilder();
        stringBuilder.append("begin:Execute format").append("<br/>");
 		stringBuilder.append("HelloProperties:")
     		.append(formatProcessor.format(helloProperties.getInfo())).append("<br/>")
        	.append("Obj format  result:").append(formatProcessor.format(obj))
            .append("<br/>");
        return stringBuilder.toString();
    }
}

那么 HelloFormatTemplate 需要被自动装配:

@Import(FormatAutoConfiguration.class) // 导入format的自动装配,properties中只需要配置一个
@EnableConfigurationProperties(HelloProperties.class)
@Configuration
public class HelloAutoConfiguration {
    @Bean
    public HelloFormatTemplate helloFormatTemplate(HelloProperties helloProperties,FormatProcessor formatProcessor){
        return new HelloFormatTemplate(helloProperties,formatProcessor);
    }
}

用来给使用方自定义配置的 properties 类:

@ConfigurationProperties(prefix=HelloProperties.HELLO_FORMAT_PREFIX)
public class HelloProperties {
	// 自定义配置前缀
    public static final String HELLO_FORMAT_PREFIX="gupao.hello.format";
    private Map<String,Object> info;

    public Map<String, Object> getInfo() {
        return info;
    }

    public void setInfo(Map<String, Object> info) {
        this.info = info;
    }
}

spring.factories 中配置自动装配:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.gupaoedu.starter.autoconfiguration.HelloAutoConfiguration

外部调用方式:

application.properties

gupao.hello.format.info.country=CN
gupao.hello.format.info.provice=HuNan
gupao.hello.format.info.city=changsha
gupao.hello.format.info.org=GuPaoEdu

FormatController 类:

@RestController
public class FormatController {
    @Autowired
    HelloFormatTemplate helloFormatTemplate;
    @GetMapping("/format")
    public String format(){
        User user=new User();
        user.setAge(18);
        user.setName("Mic");
        return helloFormatTemplate.doFormat(user);
    }
}

结果:

1563081904143

spring-boot-starter-logging

在实际应用中,日志是最重要的一个组件:

  1. 它可以为系统提供错误以及日常的定位;
  2. 也可以对访问的记录进行跟踪;
  3. 当然,在很多大型的互联网应用中,基于日志的收集以及分析可以了解用户的用户画像,比如兴趣爱好、点击行为。

常见的日志框架

可能是太过于常见了,所以使得大家很少关注,只是要用到的时候复制粘贴一份就行,甚至连日志配置文件中的配置语法都不清楚。另外一方面,Java 中提供的日志组件太多了,一会儿 log4j,一会儿 logback,一会儿又是 log4j2. 不清楚其中的关联

Java 中常用的日志框架: Log4j、Log4j2、Commons Logging、Slf4j、Logback、Jul(Java Util Logging)

简单介绍日志的发展历史

最早的日志组件是 Apache 基金会提供的 Log4j,log4j 能够通过配置文件轻松的实现日志系统的管理和多样化配置,所以很快被广泛运用。也是我们接触得比较早和比较多的日志组件。它几乎成了 Java 社区的日志标准。

据说 Apache 基金会还曾经建议 Sun 引入 Log4j 到 java 的标准库中,但 Sun 拒绝了。 所以 sun 公司在 java1.4 版本中,增加了日志库(Java Util Logging)。其实现基本模仿了 Log4j 的实现。在 JUL 出来以前,Log4j 就已经成为一项成熟的技术,使得 Log4j 在选择上占据了一定的优势。

Sun 推出的 JUL 后,有一些项目使用 JUL,也有一些项目使用 log4j,这样就造成了开发者的混乱,因为这两个日志组件没有关联,所以要想实现统一管理或者替换就非常困难。怎么办呢?

这个时候又轮到 Apache 出手了,它推出了一个 Apache Commons Logging 组件,JCL 只是定义了一套日志接口(其内部也提供一个 Simple Log 的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用 Commons Logging 的接口,底层实现可以是 Log4j,也可以是 Java Util Logging

由于它很出色的完成了主流日志的兼容,所以基本上在后面很长一段时间,是无敌的存在。连 spring 也都是依赖 JCL 进行日志管理。

但是故事并没有结束
原 Log4J 的作者,它觉得 Apache Commons Logging 不够优秀,所以他想搞一套更优雅的方案,于是 slf4j 日志体系诞生了,slf4j 实际上就是一个日志门面接口,它的作用类似于 Commons Loggins。 并且他还为 slf4j 提供了一个日志的实现-logback。

因此大家可以发现 Java 的日志领域被划分为两个大营:Commons Logging 和 slf4j

另外,还有一个 log4j2 是怎么回事呢? 因为 slf4j 以及它的实现 logback 出来以后,很快就赶超了原本 apache 的
log4j 体系,所以 apache 在 2012 年重写了 log4j, 成立了新的项目 Log4j2

总的来说,日志的整个体系分为日志框架和日志系统

  • 日志框架:JCL/ Slf4j
  • 日志系统:Log4j、Log4j2、Logback、JUL。

而在我们现在的应用中,绝大部分都是使用 slf4j 作为门面,然后搭配 logback 或者 log4j2 日志系统

spring-boot-starter-jdbc

spring-boot-starter-jdbc初体验

spring-boot-starter-jdbc 的结构如下:

1563082416420

pom 中引入了 spring-boot-starter-jdbc 后,spring-boot-autoconfigure 的 spring.factories 中配置的自动装配就会生效

1563082866044

JdbcTemplateAutoConfiguration 内容如下:

@Configuration
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
// 表示JdbcTemplateAutoConfiguration的装配发生在DataSourceAutoConfiguration之后,前提条件
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcTemplateAutoConfiguration {
	...
}

然后就可以直接使用 JdbcTemplate。

配置多数据源

application.properties

#db1
app.datasource.db1.url=jdbc:mysql://192.168.8.126:3306/db1
app.datasource.db1.username=root
app.datasource.db1.password=root
app.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
#db2
app.datasource.db2.url=jdbc:mysql://192.168.8.126:3306/db2
app.datasource.db2.username=root
app.datasource.db2.password=root
app.datasource.db2.driver-class-name=com.mysql.jdbc.Driver

自定义配置类 JdbcDataSourceConfig,从数据源开始配置,不使用默认配置:

@Configuration
public class JdbcDataSourceConfig {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "app.datasource.db1")
    public DataSourceProperties db1DataSourceProperties(){
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "app.datasource.db2")
    public DataSourceProperties db2DataSourceProperties(){
        return new DataSourceProperties();
    }

    @Primary
    @Bean
    public DataSource db1DataSource(){
        return db1DataSourceProperties().initializeDataSourceBuilder().build();
    }

    @Bean
    public DataSource db2DataSource(){
        return db2DataSourceProperties().initializeDataSourceBuilder().build();
    }

    @Bean(name="db1JdbcTemplate")
    public JdbcTemplate db1JdbcTemplate(){
        return new JdbcTemplate(db1DataSource());
    }

    @Bean(name="db2JdbcTemplate")
    public JdbcTemplate db2JdbcTemplate(){
        return new JdbcTemplate(db2DataSource());
    }
}

使用时根据 name 进行注入:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootStarterDemoApplicationTests {
    @Autowired
    JdbcTemplate db1JdbcTemplate;

    @Test
    public void addDataData() {
        String sql = "insert into user_info(name,age) values('mic1',18)";
        db1JdbcTemplate.execute(sql);
    }
}

SpringBoot 另一大神器-Actuator

微服务应用开发完成以后,最终目的是为了发布到生产环境上给用户试用,开发结束并不意味着研发的生命周期结束,更多的时候他只是一个开始,因为服务在本地测试完成以后,并不一定能够非常完善的考虑到各种场景。所以需要通过运维来保障服务的稳定。

在以前的传统应用中,我们可以靠人工来监控。但是微服务中,几千上万个服务,我们需要了解每个服务的健康状态,就必须要依靠监控平台来实现。

所以在 SpringBoot 框架中提供了 spring-boot-starter-actuator 自动配置模块来支持对于 SpringBoot 应用的监控

Actuator

Spring Boot Actuator 的关键特性是在应用程序里提供众多 Web 端点,通过它们了解应用程序运行时的内部状况。

有了 Actuator,你可以知道 Bean 在 Spring 应用程序上下文里是如何组装在一起的,掌握应用程序可以获取的环境属性信息

在 spring-boot 项目中,添加 actuator 的一个 starter.

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

Actuator 提供的 endpoint

启动服务之后,可以通过下面这个地址看到 actuator 提供的所有 Endpoint 地址

http://localhost:8080/actuator

可以看到非常多的 Endpoint。 有一些 Endpoint 是不能访问的,涉及到安全问题。

如果想开启访问那些安全相关的 url , 可以在 application.xml 中配置, 开启所有的 endpoint

  • health
    针对当前 SpringBoot 应用的健康检查,默认情况下,会通过“up”或者“down”; 可以基于下面这个配置,来打印 heath 更详细的信息:
  • Loggers
    显示当前 spring-boot 应用中的日志配置信息,针对每个 package 对应的日志级别
  • beans
    获取当前 spring-boot 应用中 IoC 容器中所有的 bean
  • Dump
    获取活动线程的快照
  • Mappings
    返回全部的 uri 路径,以及和控制器的映射关系
  • conditions
    显示当前所有的条件注解,提供一份自动配置生效的条件情况,记录哪些自动配置条件通过了,哪些没通过
  • shutdown
    关闭应用程序,需要添加这个配置:
    这个 Endpoint 是比较危险的,如果没有一定的安全保障,不要开启
  • Env
    获取全部的环境信息

关于 health 的原理

应用健康状态的检查应该是监控系统中最基本的需求,所以我们基于 health 来分析一下它是如何实现的。
SpringBoot 预先通过

org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration

这个就是基于 spring-boot 的自动装配来载入的。

所以,我们可以在 actuator-autoconfigure 这个包下找到 spring.factories。

1563073121812

Actuator 中提供了非常多的扩展点,默认情况下提供了一些常见的服务的监控检查的支持。

  • DataSourceHealthIndicator
  • DiskSpaceHealthIndicator
  • RedisHealthIndicator

其中,有一些服务的检查,需要依赖于当前应用是否集成了对应的组件 , 比如 redis , 如果没有集 成 , 那么
RedisHealthIndicatorAutoConfiguration 就不会被装载。因为它有 condition 的条件判断

1563073184136

Actuator 对于 JMX 支持

除了 REST 方式发布的 Endpoint,Actuator 还把它的端点以 JMX MBean 的方式发布出来,可以通过 JMX 来查看和管理。

操作步骤

在 cmd 中输入 jconsole,连接到 spring-boot 的应用

1563073254283

就可以看到 JBean 的信息以及相应的操作。比如可以在操作菜单中访问 shutdown 的 endpoint 来关闭服务

1563073267825

什么是 JMX

JMX 全称是 Java Management Extensions。 Java 管理扩展。它提供了对 Java 应用程序和 JVM 的监控和管理功能。

通过 JMX,我们可以监控:

  1. 服务器中的各种资源的使用情况,CPU、内存
  2. JVM 内存的使用情况
  3. JVM 线程使用情况
    比如前面讲的 Actuator 中,就是基于 JMX 的技术来实现对 endpoint 的访问

jmx + prometheus + grafana 可以实现监控预警

自定义JMX监控

自定义 MBean

//把需要发布出去的指标信息,通过MB来进行发布
public interface MechineMBean {

    //属性、  操作
    int getCpuCore();
    long getFreeMemory();
    void shutdown();
}

public class Mechine implements MechineMBean {
    @Override
    public int getCpuCore() {
        return Runtime.getRuntime().availableProcessors();
    }

    @Override
    public long getFreeMemory() {
        return Runtime.getRuntime().freeMemory();
    }

    @Override
    public void shutdown() {
        System.exit(0);
    }
}

把 Mbean 注册到 server 上:

public class JMXMain {

    public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException, IOException {
        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName on = new ObjectName("com.gupaoedu.example.springbootstarterdemo.jmxdemo.Mechine:type=mechine");
        MechineMBean mechineMBean = new Mechine();
        beanServer.registerMBean(mechineMBean, on);
        System.in.read();
    }
}

结果:

1563083935507

远程连接JMX

启动参数 VM options 中添加配置:

#远程连接的端口
-Dcom.sun.management.jmxremote.port=9999
#是否需要认证
-Dcom.sun.management.jmxremote.authenticate=false
#是否使用ssl安全协议
-Dcom.sun.management.jmxremote.ssl=false

1563084452985

使用 jconsole 进行连接

1563084519246

连接结果:

1563084685679

上次编辑于:
贡献者: soulballad