微服务-03-SpringBoot

1. Spring Boot简介

Spring Boot的设计目的是用来简化新Spring应用的初始搭建以及开发过程。SpringBoot所具备的特征有:

  1. 可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs
  2. 内嵌Tomcat或Jetty等Servlet容器
  3. 提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置
  4. 尽可能自动配置Spring容器
  5. 提供准备好的特性,如指标、健康检查和外部化配置
  6. 绝对没有代码生成,不需要XML配置

Spring Boot官网:https://spring.io/projects/spring-boot/

Spring Boot启动结构图(图片太大了,可单独查看图片):

Spring Boot启动过程:

一路追踪源码:
SpringApplication.run()
// 先new对象,然后run
new SpringApplication(primarySources).run(args)

// new对象的构造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 配置Source
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 判断是否为web环境
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
    // 创建初始化构造器,加载spring.factories文件中ApplicationContextInitializer对应的所有类
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 创建应用监听器,加载spring.factories文件中ApplicationListener对应的所有类
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 配置应用的主方法所在类
    this.mainApplicationClass = deduceMainApplicationClass();
}

// run方法:运行Spring应用程序,创建并刷新新的应用程序上下文
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    // 应用启动计时器开始计时
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    // 获取spring.factories文件中SpringApplicationRunListener对应的监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 广播事件,应用启动监听器开始监听
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 准备环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 应用启动时的提示图标,可以在resources下创建一个banner.txt来修改启动时的打印信息
        Banner printedBanner = printBanner(environment);
        // 创建新的应用程序上下文
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 准备新的应用程序上下文
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 刷新新的应用程序上下文
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        // 应用启动计时器结束计时
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 上下文已经刷新,应用程序已经启动
        listeners.started(context);
        // 调用CommandLineRunner和ApplicationRunner
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

2. 创建Spring Boot工程

需求:在页面展示hello, world

2.1 手工创建

  1. 创建Maven工程

  2. 在pom.xml中配置parent和web的起步依赖

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.52lhp</groupId>
        <artifactId>hello</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <!--1. 继承spring-boot-starter-parent
        Spring Boot版本控制的原理:
        spring-boot-starter-parent继承了spring-boot-dependencies,
        而spring-boot-dependencies中定义了各个依赖的版本
        -->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.4.RELEASE</version>
        </parent>
        <!--2. 添加web的起步依赖-->
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  3. 创建启动类

    package com.lhp;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class HelloWorldApplication {
        public static void main(String[] args) {
            SpringApplication.run(HelloWorldApplication.class, args);
        }
    }
    
  4. 编写业务代码:

    package com.lhp.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloWorldController {
        @RequestMapping("/hello")
        public String hello() {
            return "hello, world";
        }
    }
    
  5. 运行启动类的main方法

2.2 通过Spring Initializr创建

在IDEA中创建工程时使用Spring Initializr

  1. 点击Next转到Spring Initializr Project Settings页面-->配置Group、Artifact、Packaging、Java Version、Package等
  2. 点击Next转到Dependencies页面-->选择Spring Boot的版本和需要的依赖-->Next-->Finish
  3. 右键模块-->Add Framework Support...-->选中Maven

2.3 部署Spring Boot项目

jar包部署:

  1. 在pom.xml中配置spring-boot-maven-plugin插件,将项目打成jar包

  2. 控制台执行命令

    java -jar xxx.jar
    

war包部署:

  1. 让启动类继承SpringBootServletInitializer
  2. 在pom.xml中配置spring-boot-maven-plugin插件,将项目打成war包
  3. 在Web服务器(如:Tomcat)中部署war包

3. Spring Boot配置文件

Spring Boot的很多配置都有默认值,可以使用application.properties或application.yml(application.yaml)来修改默认配置。SpringBoot默认从Resource目录加载自定义配置文件。

如果同时存在properties和yaml/yml,则properties优先级高于yaml/yml。

yaml/yml基本语法,参考:https://www.runoob.com/w3cnote/yaml-intro.html

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • #表示注释
  • 值前面要加一个空格
  • 对象键值对使用冒号结构表示key: value
  • 以-开头的行表示构成一个数组
  • 纯量包括:字符串、布尔值、整数、浮点数、Null、时间、日期
  • &用来建立锚点,*用来引用锚点

3.1 获取配置文件中的值

  1. application.yml配置文件内容:

    person:
      name: lhp
      age: 21
      # 纯量数据类型
      sex: 男
      # 数组数据类型
      hobbies:
        - study
        - reading
      # 对象数据类型
      pet:
        name: 狗
        food: 骨头
        # 参数引用
        age: ${person.age}
    
  2. POJO:

    @Data
    public class Pet implements Serializable {
        private String name;
        private String food;
        private Integer age;
    }
    
    // 1. 通过ConfigurationProperties把POJO的属性和yml中key的值自动建立映射关系
    // 2. 将POJO交给Spring容器管理(POJO需要有Setter方法)
    // 3. 使用时直接从Spring容器中获取该POJO
    // 对象中的属性若有自身,则可能映射失败;如Person中含有Person
    @Data
    @Component
    @ConfigurationProperties(prefix = "person")
    public class Person implements Serializable {
        private String name;
        private Integer age;
        private String sex;
        private String[] hobbies;
        private Pet pet;
    }
    
  3. 获取配置文件中的值:

    import com.lhp.demo.pojo.Person;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.core.env.Environment;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/test")
    public class TestController {
        // @Value("${属性名}")只能获取纯量
        @Value("${person.hobbies[0]}")
        private String personHobbies0;
        // 对象和数组的获取方式相同
        @Autowired
        private Person person;
        // 通过Environment可以获取纯量
        @Autowired
        private Environment environment;
    
        @GetMapping("/scalars")
        private String scalars() {
            System.out.println(environment.getProperty("person.hobbies[0]"));
            return "person.hobbies[0]=" + personHobbies0;
        }
    
        @GetMapping("/object")
        private String object() {
            return "person=" + person;
        }
    }
    

3.2 使用指定的环境配置

  1. 方式1:编写多个配置文件application-xxx

    1. application.yml:

      # 通过active指定要使用的配置文件
      spring:
        profiles:
          # active的值为application-后面的部分
          active: pro
      
    2. application-dev.yml:

      # 开发环境
      server:
        port: 8081
      
    3. application-pro.yml:

      # 生产环境
      server:
        port: 8082
      
    4. application-test.yml:

      # 测试环境
      server:
        port: 8083
      
  2. 方式2:在application.yml中使用---来分割

    spring:
      profiles:
        active: dev
    ---
    # 开发环境
    server:
      port: 8081
    spring:
      profiles: dev
    ---
    # 生产环境
    server:
      port: 8082
    spring:
      profiles: pro
    ---
    # 测试环境
    server:
      port: 8083
    spring:
      profiles: test
    
  • 激活profiles的方式:

    1. 方式1:如上,在配置文件中配置spring.profiles.active=环境

    2. 方式2:运行时指定参数

      java -jar xxx.jar --spring.profiles.active=test
      
    3. 方式3:配置jvm参数

      -Dspring.profiles.active=dev
      

4. Spring Boot整合其他框架

4.1 整合MyBatis

  1. 添加依赖:

    <!--mybatis的起步依赖-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
  2. 在application.yml中配置:

    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db01?useSSL=false&serverTimezone=UTC&characterEncoding=utf-8
        username: root
        password: 123456
    mybatis:
      # 配置mybatis映射文件的位置
      mapper-locations: classpath:com/lhp/integration/dao/*Dao.xml
    
  3. 在启动类上添加MapperScan注解,扫描Dao接口所在的包:

    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    // MapperScan会扫描指定包下的所有的接口,然后将接口的代理对象交给Spring容器
    @MapperScan(basePackages = "com.lhp.integration.dao")
    public class IntegrationApplication {
        public static void main(String[] args) {
            SpringApplication.run(IntegrationApplication.class, args);
        }
    }
    

4.2 整合JUnit

  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
  2. 测试类所在的包最好在启动类所在的包下

  3. 低版本Spring Boot使用:

    package com.lhp.integration;
    
    import com.lhp.integration.dao.UserDao;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class IntegrationApplicationTests {
        @Autowired
        private UserDao userDao;
    
        @Test
        public void contextLoads() {
            System.out.println(userDao.findAll());
        }
    }
    
  4. 高版本Spring Boot使用:

    package com.lhp.integration;
    
    import com.lhp.integration.dao.UserDao;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    public class IntegrationApplicationTests {
        @Autowired
        private UserDao userDao;
    
        @Test
        public void contextLoads() {
            System.out.println(userDao.findAll());
        }
    }
    

4.3 整合Redis

  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 在application.yml中配置:

    spring:
      redis:
        host: localhost
        port: 6379
    
  3. 在启动类中配置Redis序列化机制:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @SpringBootApplication
    public class IntegrationApplication {
        public static void main(String[] args) {
            SpringApplication.run(IntegrationApplication.class, args);
        }
    
        // 配置Redis序列化机制
        @Bean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            // 设置Key的序列化机制为StringRedisSerializer
            template.setKeySerializer(new StringRedisSerializer());
            // 设置value的序列化机制为JdkSerializationRedisSerializer
            template.setValueSerializer(new JdkSerializationRedisSerializer());
            return template;
        }
    }
    
  4. 使用:

    package com.lhp.integration;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.RedisTemplate;
    
    @SpringBootTest
    public class IntegrationApplicationTests {
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        public void redisTest() {
            redisTemplate.boundValueOps("name").set("lhp");
            String name = String.valueOf(redisTemplate.boundValueOps("name").get());
            System.out.println(name);
        }
    }
    

RedisTemplate支持的Redis序列化机制(RedisSerializer接口的实现类):

  1. GenericJackson2JsonRedisSerializer
  2. GenericToStringSerializer
  3. Jackson2JsonRedisSerializer
  4. JdkSerializationRedisSerializer(默认):要求key和value都实现Serializable接口
  5. OxmSerializer
  6. StringRedisSerializer
  7. 自己定义的RedisSerializer接口实现类

5. Spring Boot自动配置的原理

5.1 Condition接口

Condition接口和@Conditional注解是Spring4之后提供的,增加条件判断功能,可以选择性的注入Bean对象到Spring容器中。

自定义Condition实现类,需求:根据是否导入坐标,来选择是否加载Bean

  1. 是否导入的坐标:

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>
    
  2. POJO:

    public class User {
    }
    
  3. 自定义Condition实现类:

    package com.lhp.study.condition;
    
    import com.lhp.study.annotation.ConditionalOnClass;
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    import java.util.Map;
    
    public class OnClassCondition implements Condition {
        /**
         * @param context  上下文信息对象:可以获取环境信息、容器工程、类加载器对象
         * @param metadata 注解的元数据:可以获取注解的属性信息
         * @return 是否匹配条件,true匹配,false不匹配
         */
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            /*try {
                Class.forName("redis.clients.jedis.Jedis");
                // 如果加载成功,则依赖已经导入,返回true
                return true;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                // 如果加载失败,则有依赖没有导入,返回false
                return false;
            }*/
            // 获取注解的属性信息
            Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
            // 获取注解中name的值
            String[] names = (String[]) annotationAttributes.get("name");
            for (String name : names) {
                try {
                    Class.forName(name);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    // 如果加载失败,则有依赖没有导入,返回false
                    return false;
                }
            }
            // 如果指定的类都加载成功,则对应的依赖已经导入,返回true
            return true;
        }
    }
    
  4. 自定义一个注解:

    package com.lhp.study.annotation;
    
    import com.lhp.study.condition.OnClassCondition;
    import org.springframework.context.annotation.Conditional;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional({OnClassCondition.class})
    public @interface ConditionalOnClass {
        // 类全限定名的字符串数组
        String[] name() default {};
    }
    
  5. 配置类:

    package com.lhp.study.config;
    
    import com.lhp.study.annotation.ConditionalOnClass;
    import com.lhp.study.condition.OnClassCondition;
    import com.lhp.study.pojo.User;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class UserConfig {
        @Bean
        // @Conditional注解表示一个组件只有在value指定的所有条件都匹配时才有资格注册
        // @Conditional(value = OnClassCondition.class)
        @ConditionalOnClass(name = "redis.clients.jedis.Jedis")
        public User user() {
            return new User();
        }
    }
    
  6. 在启动类中测试:

    package com.lhp.study;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    @SpringBootApplication
    public class StudyApplication {
        public static void main(String[] args) {
            ConfigurableApplicationContext context = SpringApplication.run(StudyApplication.class, args);
            // 若导入坐标,则打印类信息;若没有导入坐标,则抛出NoSuchBeanDefinitionException
            System.out.println(context.getBean("user"));
        }
    }
    

在org.springframework.boot.autoconfigure.condition包下有很多Spring已经定义好的Condition实现类和注解,如:

  1. ConditionalOnBean:当Spring容器中有某一个Bean时使用
  2. ConditionalOnClass:当目前类路径下有某一个类时使用
  3. ConditionalOnMissingBean:当Spring容器中没有某一个Bean时使用
  4. ConditionalOnMissingClass:当目前类路径下没有某一个类时使用
  5. ConditionalOnProperty:当配置文件中有某一个键值对时使用

5.2 切换内置的Web容器

org.springframework.boot.autoconfigure.web.embedded包下有4种Web容器定制器:

  1. JettyWebServerFactoryCustomizer
  2. NettyWebServerFactoryCustomizer
  3. TomcatWebServerFactoryCustomizer
  4. UndertowWebServerFactoryCustomizer

在spring-boot-starter-web中排除spring-boot-starter-tomcat依赖,然后添加其他的Web容器依赖即可:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!--在spring-boot-starter-web中排除spring-boot-starter-tomcat依赖-->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--添加其他的Web容器依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

5.3 加载第三方的Bean

@SpringBootApplication上有@SpringBootConfiguration,而@SpringBootConfiguration上有@Configuration,因此启动类本身也是一个配置类,该配置类相当于Spring中的applicationContext.xml文件,用于加载配置使用。

@SpringBootApplication上有@EnableAutoConfiguration,这种@EnableXxx开头的注解是Spring Boot中定义的一些动态启用某些功能的注解,其底层原理实际上是用@import注解导入一些配置,来自动进行配置、加载Bean。

一路追踪源码:
@SpringBootApplication
@EnableAutoConfiguration
@Import({AutoConfigurationImportSelector.class})
String[] selectImports(AnnotationMetadata annotationMetadata)
AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
META-INF/spring.factories

在spring-boot-autoconfigure-x.x.x.jar中的META-INF/spring.factories文件中就有各种自动配置类

@SpringBootApplication上有@ComponentScan,而@ComponentScan类似于xml中的包扫描context:component-scan;如果不指定扫描路径,则扫描该注解修饰的启动类所在的包及子包。

@Import注解用于导入其他的配置,让Spring容器进行加载和初始化;其使用方式有:

  1. 直接导入Bean
  2. 导入配置类
  3. 导入ImportSelector的实现类
  4. 导入ImportBeanDefinitionRegistrar实现类

实现加载第三方的Bean示例:创建两个工程enable1和enable2,enable2中有Bean,enable1依赖enable2,期望在enable1中直接使用enable2中的Bean

  1. 在enable2中创建POJO和配置类:

    // POJO
    package com.lhp.enable2.pojo;
    
    public class User {
    }
    
    // 配置类
    package com.lhp.enable2.config;
    
    import com.lhp.enable2.pojo.User;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class UserConfig {
        @Bean
        public User user() {
            return new User();
        }
    }
    
  2. 在enable1中添加第三方依赖enable2的坐标

  3. 在enable1的启动类中直接加载使用第三方依赖enable2中的Bean

  4. 解决NoSuchBeanDefinitionException:

    1. 方式1:在enable1的启动类上使用@ComponentScan("com")注解将包扫描路径放大

    2. 方式2:在enable1的启动类上使用@Import({UserConfig.class})注解导入配置类

    3. 方式3:自定义一个注解@EnableUser,然后在enable1的启动类上使用该注解

      // 自定义注解
      package com.lhp.enable2.config;
      
      import org.springframework.context.annotation.Import;
      
      import java.lang.annotation.*;
      
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Import({UserConfig.class})
      public @interface EnableUser {
      }
      
    4. 方式4:自定义一个ImportSelector实现类,然后在enable1的启动类上使用@Import注解导入该实现类;此时不需要配置类

      // ImportSelector实现类
      package com.lhp.enable2.config;
      
      import org.springframework.context.annotation.ImportSelector;
      import org.springframework.core.type.AnnotationMetadata;
      
      public class MyImportSelector implements ImportSelector {
          /**
           * @return 需要加载的Bean的全限定名数组
           */
          @Override
          public String[] selectImports(AnnotationMetadata importingClassMetadata) {
              // 全限定名数组可以从配置文件中读取
              // 此时Bean的id为全限定名,可以通过getBean("com.lhp.enable2.pojo.User")或getBean(User.class)获取
              return new String[]{"com.lhp.enable2.pojo.User"};
          }
      }
      
    5. 方式5:自定义一个ImportBeanDefinitionRegistrar实现类,然后在enable1的启动类上使用@Import注解导入该实现类;此时不需要配置类

      // ImportBeanDefinitionRegistrar实现类
      package com.lhp.enable2.config;
      
      import com.lhp.enable2.pojo.User;
      import org.springframework.beans.factory.support.AbstractBeanDefinition;
      import org.springframework.beans.factory.support.BeanDefinitionBuilder;
      import org.springframework.beans.factory.support.BeanDefinitionRegistry;
      import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
      import org.springframework.core.type.AnnotationMetadata;
      
      public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
          @Override
          public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
              String beanName = "user";
              // 创建beanDefinition
              AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
              // 注册Bean
              registry.registerBeanDefinition(beanName, beanDefinition);
          }
      }
      

5.4 自定义starter起步依赖

需求:当加入jedis的坐标时,自动配置jedis的Bean,加载到Spring容器中

  1. 创建起步依赖工程starter,添加spring-boot-starter和jedis的坐标

  2. 创建配置的POJO:

    package com.lhp.starter.pojo;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    @Data
    @ConfigurationProperties(prefix = "redis")
    public class RedisProperties {
        // 给定默认值
        private String host = "localhost";
        private Integer port = 6379;
    }
    
  3. 创建自动配置类

    // 自动配置类
    package com.lhp.starter.autoconfigure;
    
    import com.lhp.starter.pojo.RedisProperties;
    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;
    import redis.clients.jedis.Jedis;
    
    @Configuration
    // 启用POJO,交给Spring容器管理,与配置文件建立映射关系
    @EnableConfigurationProperties(RedisProperties.class)
    // 当类路径下有Jedis这个类时再使用这个配置类
    @ConditionalOnClass(Jedis.class)
    public class RedisAutoConfiguration {
        @Bean
        // 当没有jedis这个Bean时再配置,避免和用户自己的jedis冲突
        @ConditionalOnMissingBean(name = "jedis")
        public Jedis jedis(RedisProperties redisProperties) {
            System.out.println(redisProperties);
            return new Jedis(redisProperties.getHost(), redisProperties.getPort());
        }
    }
    
  4. 在resources下创建META-INF/spring.factories文件:

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.lhp.starter.autoconfigure.RedisAutoConfiguration
    
  5. 在其他工程中加入自定义的starter起步依赖的坐标,便可直接使用自定义starter起步依赖中的Bean

6. Spring Boot监控

6.1 Spring Boot Actuator

Spring Boot Actuator是Spring Boot自带的组件,可以监控Spring Boot应用,如健康检查、审计、统计、HTTP追踪等

使用Spring Boot Actuator:

  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. 在application.yml中配置:

    management:
      # 配置健康端点开启所有详情信息
      endpoint:
        health:
          show-details: always
      # 设置开放所有web相关的端点信息
      endpoints:
        web:
          exposure:
            include: "*"
    # 设置info前缀的信息
    info:
      name: lhp
      age: 21
    
  3. 浏览器访问:http://localhost:8080/actuator

部分监控路径列表:

  1. /beans:描述应用程序上下文里全部的Bean及它们的关系
  2. /env:获取全部环境属性
  3. /env/{name}:根据名称获取特定的环境属性值
  4. /health:报告应用程序的健康指标,这些值由HealthIndicator的实现类提供
  5. /info:获取应用程序的定制信息,这些信息由info打头的属性提供
  6. /mappings:描述全部的URI路径,以及它们和控制器(包含Actuator端点)的映射关系
  7. /metrics:报告各种应用程序度量信息,比如内存用量和HTTP请求计数
  8. /metrics/{name}:报告指定名称的应用程序度量值
  9. /trace:提供基本的HTTP请求跟踪信息(时间戳、HTTP头等)

6.2 Spring Boot Admin

Spring Boot Admin是一个开源社区项目;它有两个角色,Client和Server;应用程序作为Client向Server注册;Server可以通过图形化界面展示Client的监控信息

  1. 每一个Spring Boot工程都是一个Client
  2. Server可以收集统计所有相关Client注册过来的信息,并进行汇总展示

使用Spring Boot Admin Server:

  1. 创建server工程,导入坐标:

    <properties>
        <java.version>1.8</java.version>
        <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-dependencies</artifactId>
                <version>${spring-boot-admin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  2. 在启动类上添加@EnableAdminServer注解来启用Server功能

  3. 在application.yml中配置:

    server:
      port: 8088
    

使用Spring Boot Admin Client:

  1. 创建server工程,导入坐标:

    <properties>
        <java.version>1.8</java.version>
        <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-dependencies</artifactId>
                <version>${spring-boot-admin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  2. 在application.yml中配置:

    spring:
      # 配置server的地址
      boot:
        admin:
          client:
            url: http://localhost:8088
      # 配置系统名称
      application:
        name: client
    management:
      endpoint:
        health:
          # 启用健康检查(默认就是true)
          enabled: true
          # 配置显示所有的监控详情
          show-details: always
      endpoints:
        web:
          # 开放所有端点
          exposure:
            include: "*"
    
  3. 浏览器访问服务端地址:http://localhost:8088/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容