SpringBoot 新瓶装旧酒它不香吗

一  SpringBoot 爱之初体验

1,what is?

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".

We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.

2,Features

  • Create stand-alone Spring applications

  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)

  • Provide opinionated 'starter' dependencies to simplify your build configuration

  • Automatically configure Spring and 3rd party libraries whenever possible

  • Provide production-ready features such as metrics, health checks, and externalized configuration

  • Absolutely no code generation and no requirement for XML configuration

总结:1,独立的spring应用 2,集成了Tomcat、Jetty等 3,starter 4,自动装配 5,监控及外部配置 6,非XML配置

下面将根据以上重点特征进行讲述

 

二  SpringBoot之浓情热恋

1,约定大于配置

SpringBoot框架诞生原因之一就是要对Spring框架进行最小化配置的实现,所以就出现了“约定大于配置"这个解决方案!

约定优于配置的主要体现:

  • 1. maven 的目录结构:默认有 resources 文件夹存放配置文件、 默认打包方式为 jar
  • 2. spring-boot-starter-web 中默认包含 spring mvc 相关依赖以及内置的 tomcat 容器,使得构建一个 web 应用更加简单
  • 3. 默认提供 application.properties/yml 文件
  • 4. 默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件
  • 5. EnableAutoConfiguration 默认对于依赖的 starter 进行自动装载

2,@SpringBootApplication解码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  ...
}

核心来讲就@SpringBootConfiguration  @EnableAutoConfiguration @ComponentScan

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
  ...
}

@SpringBootConfiguration 其实就是@Configuration的派生

SpringBootApplication 本质上是由 3 个注解组成,分别是
1. @Configuration
2. @EnableAutoConfiguration
3. @ComponentScan

2.1 简单分析@Configuration 

Configuration 这个注解大家应该有用过,它是 JavaConfig形式的基于 Spring IOC 容器的配置类使用的一种注解。因为SpringBoot 本质上就是一个 spring 应用,所以通过这个注解来加载 IOC 容器的配置是很正常的。所以在启动类里面标注了@Configuration,意味着它其实也是一个 IoC容器的配置类。传统意义上的 spring 应用都是基于 xml 形式来配置 bean的依赖关系。然后通过 spring 容器在启动的时候,把 bean进行初始化并且,如果 bean 之间存在依赖关系,则分析这些已经在 IoC 容器中的 bean 根据依赖关系进行组装。

直到 Java5 中,引入了 Annotations 这个特性,Spring 框架也紧随大流并且推出了基于 Java 代码和 Annotation 元信息的依赖关系绑定描述的方式,也就是 JavaConfig。 从 spring3 开始,spring 就支持了两种 bean 的配置方式,一种是基于 xml 文件方式、另一种就是 javaConfig,任何一个标注了@Configuration 的 Java 类定义都是一个JavaConfig 配置类。而在这个配置类中,任何标注了@Bean 的方法,它的返回值都会作为 Bean 定义注册到Spring 的 IOC 容器,方法名默认成为这个 bean 的 id

核心就是IOC支持的三种配置方式:
1.XML
2.@Configuration --> @Bean (JavaConfig)

//该注解表示这个类为javaConfig类
@Configuration
public class MyConfig {
	//该注解表示:向容器中注册一个叫做myCar的对象
	@Bean("myCar")
	public Car getCar() {
		return new Car("保时捷","911",300);
	}
	//该注解表示:向容器中注册一个叫做person的对象
	//并且通过byType的方式注入car
	@Bean(name="person",autowire=Autowire.BY_TYPE)
	public Person getPerson() {
		return new Person(1001,"望穿秋水见伊人");
	}
}

3.Annotation注解

 

2.2  简单分析 ComponentScan 

ComponentScan 这个注解是大家接触得最多的了,相当于 xml 配置文件中的<context:component-scan>。 它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到 spring 的 Ioc 容器中。标识需 要装配的类的 形式主要是: @Component 、
@Repository、@Service、@Controller 这类的注解标识的类。ComponentScan 默认会扫描当前 package 下的的所有加
了相关注解标识的类到 IoC 容器中

2.3  重点分析 EnableAutoConfiguration:实现自动装配的核心

我们把 EnableAutoConfiguration 放在最后讲的目的并不是说它是一个新的东西,只是他对于 springboot 来说意义重大。
 
2.3.1 Enable 并不是新鲜玩意
在 spring3.1 版本中,提供了一系列的@Enable 开头的注解,Enable 主机应该是在 JavaConfig 框架上更进一步的完善,是的用户在使用 spring 相关的框架是,避免配置大量的代码从而降低使用的难度。
比如常见的一些 Enable 注解:EnableWebMvc,(这个注解引入了 MVC 框架在 Spring 应用中需要用到的所有bean);
比如说@EnableScheduling,开启计划任务的支持;
我们可以看到每一个涉及到 Enable 开头的注解,都会带有一个@Import 的注解。
 
2.3.2  Import 注解
import 注解是什么意思呢? 联想到 xml 形式下有一个<import resource/> 形式的注解,就明白它的作用了。
import 就是把多个分来的容器配置合并在一个配置中。在JavaConfig 中所表达的意义是一样的。
 
EnableAutoConfiguration 的 主 要 作 用 其 实 就 是 帮 助 springboot 应用把所有符合条件的@Configuration 配置都加载到当前 SpringBoot 创建并使用的 IoC 容器中。 再回到 EnableAutoConfiguration 这个注解中,我们发现 它的 import 是这样
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
 ...
}

2.3.3 AutoConfigurationImportSelector? 
Enable注解不仅仅可以像前面演示的案例一样很简单的实现多个 Configuration 的整合,还可以实现一些复杂的场景,比如可以根据上下文来激活不同类型的 bean

@Import 注解可以配置三种不同的 class实现方式
1. 第一种就是基于普通 bean 或者带有@Configuration 的 bean 进行注入
2. 实现 ImportSelector 接口进行动态注入
3. 实现 ImportBeanDefinitionRegistrar 接口进行动态注入

2.3.4  @EnableAutoConfiguration 注解的实现原理
了解了 ImportSelector 和 ImportBeanDefinitionRegistrar 后,对于 EnableAutoConfiguration 的理解就容易一些了
它会通过 import 导入第三方提供的 bean 的配置类: AutoConfigurationImportSelector
@Import (AutoConfigurationImportSele
ctor. class )
从名字来看,可以猜到它是基于 ImportSelector 来实现 基于动态 bean 的加载功能。 Springboot @Enable*注解的工作原理 ImportSelector 接口 ,selectImports 返回的数组(类的全类名)都会被纳入到 spring 容器中。
 
那么可以猜想到这里的实现原理也一定是一样的,定位到 AutoConfigurationImportSelector 这个类中的 selectImports 方法
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
 ...
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
本质上来说,其实 EnableAutoConfiguration 会帮助 springboot 应用把所有符合@Configuration 配置都加载 到当前 SpringBoot 创建的 IoC 容器,而这里面借助了 Spring 框架提供的一个工具类 SpringFactoriesLoader 的支持。以及用到了 Spring 提供的条件注解 @Conditional ,选择性的针对需要加载的 bean 进行条件 过滤

SpringFactoriesLoader

简单分析一下 SpringFactoriesLoader 这个工具类的使用。它其实和 java 中的 SPI 机制的原理是一样的,不过它比 SPI 更好的
点在于不会一次性加载所有的类,而是根据 key 进行加载。首先, SpringFactoriesLoader 的作用是从 classpath/META-INF/spring.factories 文件中,根据 key 来加载对应的类到 spring IoC 容器中。
 
深入理解条件过滤 Conditional
在分析 AutoConfigurationImportSelector 的源码时,会先扫描 spring-autoconfiguration-metadata.properties文件,最后在扫描 spring.factories 对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的。@Configuration 其实是依托于其他的框架来加载的,如果当前的 classpath 环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以通过这种条件过滤可以有效的减少@configuration 类的数量从而降低SpringBoot 的启动时间。

@Conditional注解是个什么东西呢,它可以根据代码中设置的条件装载不同的bean,比如说当一个接口有两个实现类时,我们要把这个接口交给Spring管理时通常会只选择实现其中一个实现类,这个时候我们总不能使用if-else吧,所以这个@Conditional的注解就出现了。

//@ConditionalOnBean的示例代码
/*这个方法上加了@ConditionalOnBean注解,注解里的属性是RedisConnectionFactory。它的意思呢,就是说*如果你配置了redis的相关配置信息那么我就实例化RedisTemplate供你进行操作,如果你没有配置redis的相关*配置那么我就不实例化(毕竟没有配置实例化也报错不是)
*/
@Bean
@ConditionalOnBean(RedisConnectionFactory.class)
   public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
       RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
       template.setConnectionFactory(connectionFactory);
       template.setKeySerializer(new StringRedisSerializer());
       template.afterPropertiesSet();
       return template;
   }
Conditional 中的注解:
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean
@ConditionalOnClass:某个class位于类路径上才会实例化一个Bean
@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean
@ConditionalOnMissingClass:某个class不位于类路径上才会实例化一个Bean
@ConditionalOnNotWebApplication:不是web应用

补充:

  • src不是classpath,WEB-INF/classes和lib才是classpath,WEB-INF/是资源目录,客户端不能直接访问
  • WEB-INF/classes目录存放src目录Java文件编译后的class文件,xml,properties等资源配置文件,这是一个定位资源的入口。
  • lib和classes同属classpath,两者的访问优先级是:lib > classes

3  starter

pring-boot 在配置上相比spring要简单许多, 其核心在于spring-boot-starter, 在使用spring-boot来搭建一个项目时, 只需要引入官方提供的starter, 就可以直接使用, 免去了各种配置, 原因在于spring-boot的自动发现,比如当classpath下面有tomcat-embedded.jar 时,对应的bean就会被加载.其核心就是上面讲的自动装配。

下面会介绍如何自己写一个简单的starter,并在自己的工程中使用这个starter

3.1 自定义starter

项目结构:

3.1.1  MyProperties :配置文件读取类

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "my-properties")
public class MyProperties {
    private String name;
    private String msg;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "MyProperties{" +
                "name='" + name + '\'' +
                ", msg='" + msg + '\'' +
                '}';
    }
}

3.1.2  MyService :自定义功能类 

import com.example.properties.MyProperties;

public class MyService {

    private MyProperties myProperties;

    public MyService(MyProperties myProperties) {
        this.myProperties = myProperties;
    }

    public String getMyProperties(){
        return myProperties.toString();
    }
}
3.1.3 MyAutoConfiguration :自动装配类
import com.example.properties.MyProperties;
import com.example.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
@Configuration
public class MyAutoConfiguration {

    @Autowired
    private MyProperties myProperties;

    @Bean
    @ConditionalOnMissingBean(MyService.class)
    public MyService getMyService(){
        return new MyService(myProperties);
    }
}

3.1.4 在resources/META-INF下构建spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.configuration.MyAutoConfiguration

3.1.4 pom.xml :引入spring-boot-starter

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>starter-my</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>starter-my</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.3.0.RELEASE</version>
        </dependency>

    </dependencies>

</project>

3.1.5 打成jar包供其它地方使用

3.1.6 测试该starter

测试项目

首先在测试项目中引入该jar包

        <dependency>
            <groupId>com.example</groupId>
            <artifactId>starter-my</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

在项目目录resources/application.properties下写入测试数据,注意前缀要与MyProperties标注的前缀保持一致。

my-properties.name=jack
my-properties.msg=this is my autoConfiguration

测试类

import com.example.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TestApplication implements CommandLineRunner {
    @Autowired
    private MyService service;

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

    @Override
    public void run(String... args) throws Exception {
        System.out.println(service.getMyProperties());
    }
}

结果:

4  Actuator

SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、环境变量、日志信息、线程信息等

4.1 实践

  • pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
/actuator
/actuator/health
/actuator/health/{component}
/actuator/health/{component}/{instance}
/actuator/info

如果要查看其它状态就得设置相应权限:

  • 如果要看到所有支持的状态查询,需要配置
management.endpoints.web.exposure.include=*
  • 显示所有健康状态,需要加配置
management.endpoint.health.show-details=always
{
	"_links": {
		"self": {
			"href": "http://localhost:8080/actuator",
			"templated": false
		},
		"beans": {
			"href": "http://localhost:8080/actuator/beans",
			"templated": false
		},
		"caches-cache": {
			"href": "http://localhost:8080/actuator/caches/{cache}",
			"templated": true
		},
		"caches": {
			"href": "http://localhost:8080/actuator/caches",
			"templated": false
		},
		"health": {
			"href": "http://localhost:8080/actuator/health",
			"templated": false
		},
		"health-path": {
			"href": "http://localhost:8080/actuator/health/{*path}",
			"templated": true
		},
		"info": {
			"href": "http://localhost:8080/actuator/info",
			"templated": false
		},
		"conditions": {
			"href": "http://localhost:8080/actuator/conditions",
			"templated": false
		},
		"configprops": {
			"href": "http://localhost:8080/actuator/configprops",
			"templated": false
		},
		"env": {
			"href": "http://localhost:8080/actuator/env",
			"templated": false
		},
		"env-toMatch": {
			"href": "http://localhost:8080/actuator/env/{toMatch}",
			"templated": true
		},
		"loggers": {
			"href": "http://localhost:8080/actuator/loggers",
			"templated": false
		},
		"loggers-name": {
			"href": "http://localhost:8080/actuator/loggers/{name}",
			"templated": true
		},
		"heapdump": {
			"href": "http://localhost:8080/actuator/heapdump",
			"templated": false
		},
		"threaddump": {
			"href": "http://localhost:8080/actuator/threaddump",
			"templated": false
		},
		"metrics-requiredMetricName": {
			"href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
			"templated": true
		},
		"metrics": {
			"href": "http://localhost:8080/actuator/metrics",
			"templated": false
		},
		"scheduledtasks": {
			"href": "http://localhost:8080/actuator/scheduledtasks",
			"templated": false
		},
		"mappings": {
			"href": "http://localhost:8080/actuator/mappings",
			"templated": false
		}
	}
}

Loggers
显示当前 spring-boot 应用中的日志配置信息,针对每个package 对应的日志级别
beans
获取当前 spring-boot 应用中 IoC 容器中所有的 bean
Dump
获取活动线程的快照
Mappings
返回全部的 uri 路径,以及和控制器的映射关系
conditions
显示当前所有的条件注解,提供一份自动配置生效的条件情况,记录哪些自动配置条件通过了,哪些没通过

{
    "contexts":{
        "application":{
            "beans":{
                "endpointCachingOperationInvokerAdvisor":{
                    "aliases":[

                    ],
                    "scope":"singleton",
                    "type":"org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor",
                    "resource":"class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.class]",
                    "dependencies":[
                        "environment"
                    ]
                }
            }
        }
    }
}
{
    "activeProfiles":[

    ],
    "propertySources":[
        {
            "name":"server.ports",
            "properties":{
                "local.server.port":{
                    "value":8080
                }
            }
        },
        {
            "name":"servletContextInitParams",
            "properties":{

            }
        },
        {
            "name":"systemProperties",
            "properties":{
                "java.vendor":{
                    "value":"Oracle Corporation"
                },
                "sun.java.launcher":{
                    "value":"SUN_STANDARD"
                },
                "catalina.base":{
                    "value":"C:%users\timxia\AppData\Local\Temp\tomcat.2979281870254394426.8080"
                }
            }
        }
    ]
}

4.2 端点(endpoint)的启用和暴露

启用端点

默认情况下,除shutdown以外的所有端点均已启用。要配置单个端点的启用,请使用management.endpoint.<id>.enabled属性。以下示例启用shutdown端点:

management.endpoint.shutdown.enabled=true

另外可以通过management.endpoints.enabled-by-default来修改全局端口默认配置,以下示例启用info端点并禁用所有其他端点:

management.endpoints.enabled-by-default=false
management.endpoint.info.enabled=true

注意:禁用的端点将从应用程序上下文中完全删除。如果您只想更改端点公开(对外暴露)的技术,请改为使用include和exclude属性,详情见下文

暴露端点

要更改公开哪些端点,请使用以下技术特定的include和exclude属性:

PropertyDefault
management.endpoints.jmx.exposure.exclude*
management.endpoints.jmx.exposure.include*
management.endpoints.web.exposure.exclude*
management.endpoints.web.exposure.includeinfo, health

include属性列出了公开的端点的ID,exclude属性列出了不应该公开的端点的ID
exclude属性优先于include属性。包含和排除属性都可以使用端点ID列表进行配置。

注意:这里的优先级是指同一端点ID,同时出现在include属性表和exclude属性表里,exclude属性优先于include属性,即此端点没有暴露

  • 例如,要停止通过JMX公开所有端点并仅公开healthinfo端点,请使用以下属性:
management.endpoints.jmx.exposure.include=health,info

*可以用来选择所有端点。例如,要通过HTTP公开除envbeans端点之外的所有内容,请使用以下属性:

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,beans

4.3 拓展点:health原理

应用健康状态的检查应该是监控系统中最基本的需求,所以我们基于 health 来分析一下它是如何实现的。SpringBoot 预先通过
org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration,这个就是基于 spring-boot 的自动装配来载入的。所以,我们可以在 actuator-autoconfigure 这个包下找到spring.factories。

其核心还是运用了spring boot的自动装配原理。

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnAvailableEndpoint(
    endpoint = HealthEndpoint.class
)
@EnableConfigurationProperties
@Import({LegacyHealthEndpointAdaptersConfiguration.class, LegacyHealthEndpointCompatibilityConfiguration.class, HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class})
public class HealthEndpointAutoConfiguration {
    public HealthEndpointAutoConfiguration() {
    }

    @Bean
    HealthEndpointProperties healthEndpointProperties(HealthIndicatorProperties healthIndicatorProperties) {
        HealthEndpointProperties healthEndpointProperties = new HealthEndpointProperties();
        healthEndpointProperties.getStatus().getOrder().addAll(healthIndicatorProperties.getOrder());
        healthEndpointProperties.getStatus().getHttpMapping().putAll(healthIndicatorProperties.getHttpMapping());
        return healthEndpointProperties;
    }
}

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值