[Spring] SpringBoot2 简介(二)—— 高级配置

目录

一、@Conditional 注解

1、SpringBoot 如何获取 Bean 对象

2、SpringBoot 创建 Condition 类

3、切换内置 web 服务器

二、@EnableXXX 注解

1、SpringBoot 不能直接获取其他 jar 包/工程中的 Bean

2、原因分析

3、封装 @Import

4、@Import 注解

5、SpringBoot 自动配置原理

三、自定义 starter 起步依赖

1、步骤分析

2、实现步骤

3、@ConditionalOnMissingBean

四、SpringBoot 监听机制

1、Java 监听机制

2、SpringBoot 监听机制

3、CommandLineRunner 与 ApplicationRunner

4、ApplicationContextInitializer

5、SpringApplicationRunListener

五、SpringBoot 监控

1、监控概述

2、使用方法

3、info 或其他信息不显示

4、显示组件完整信息

六、SpringBoot 项目部署

1、部署 jar 包方式

2、部署 war 包方式

3、项目部署问题汇总


一、@Conditional 注解

Condition 是在 Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 对象的操作。

  • 我们可以想这么一个问题:有一个 User 对象,只有符合某种条件的时候,才能被 Spring 当作 Bean 对象,加入到 IOC 容器中。
  • 也就是,SpringBoot 是如何知道应该创建哪些 Bean。

通过 @Conditional 就可以实现这个操作。

1、SpringBoot 如何获取 Bean 对象

在解决上述问题之前,我们先来了解一下如何获取 Bean 对象。

(1)获取 IOC 容器

  • 想要获取 Bean 对象,首先要获取 IOC 容器对象,也就是 ApplicationContext;
  • 我们启动 SpringBoot 的 run 方法,其实就是返回了一个 IOC 容器;
ConfigurableApplicationContext context = SpringApplication.run(DemoConditionApplication.class, args);

(2)获取 Bean 对象

  • 这里我们获取一个 redisTemplate 的对象;
  • 注意此时还没有导入 redis 的相关依赖;
package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class DemoConditionApplication {
    public static void main(String[] args) {
        // 启动 SpringBoot,获取 IOC 容器
        ConfigurableApplicationContext context = SpringApplication.run(DemoConditionApplication.class, args);
        // 获取 Bean 对象
        Object redisTemplate = context.getBean("redisTemplate");
        System.out.println(redisTemplate);
    }
}
  • 启动后就会发现,SpringBoot 找不到 redisTemplate 这个 Bean; 

(3)补充 Redis 相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 添加 Redis 的起步依赖后,就可以获取到 redisTemplate 的 Bean 对象了:

2、SpringBoot 创建 Condition 类

实际上,创建 Bean 对象的条件可以有很多。

在这里选择上文中的导入依赖后就能创建 Bean 的条件进行举例说明 @Conditional 的具体使用。

(1)提出问题

在刚才获取 Bean 对象的过程中,一开始由于没有导入 Redis 依赖,导致获取不到 Bean 对象。而 redisTemplate 的 Bean 对象创建,依赖于 Redis 的起步依赖

  • 换句话说,SpringBoot 如何知道当没有引入 Redis 的起步依赖时,不该创建 redisTemplate 的 Bean 对象

下面通过一个案例来说明:

  • 在 Spring 的 IOC 容器中有一个 User 的 Bean。
  • 要求:导入 RedisTemplate 后,加载这个 Bean;没有导入,则不加载。

(2)创建 UserCondition 类(重点部分)

  • @Conditional() 修饰某个 Bean 对象时,需要传入 Condition 类数组;
  • Condition 类要求实现 matches 方法;
  • matches 方法就是创建 Bean 对象的判断条件,返回值为 true/false

现在我们的需求就是,导入 RedisTemplate 类的时候,才返回 true:

  • 当加载不到这个类的时候,出现异常,就返回 false;
public class UserCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        boolean flag = true;
        try {
            Class.forName("org.springframework.data.redis.core.RedisTemplate");
        } catch (ClassNotFoundException e) {
            flag = false;
        }
        return flag;
    }
}

(3)创建 SpringConfig 类

  • User 类随便新建一个即可;
  • SpringConfig 中,实现注入 User 的方法(@Bean 修饰方法);
  • @Conditional 传入我们刚写好的 UserCondition,其中的 matches 方法会判断是否创建这个 Bean;
@Configuration
public class SpringConfig {
    @Bean
    @Conditional(value = {UserCondition.class})
    public User createUser() {
        return new User();
    }
}

(4)启动测试

  • 由于我们刚才导入了 Redis 的起步依赖,已经包含了 RedisTemplate,此时获取 User 的 Bean 对象,是可以直接获取到的。

  • 然后我们将 Redis 的起步依赖注释掉,重新 Build 项目,启动 SpringBoot,就会发现 Bean 对象找不到了。

3、切换内置 web 服务器

SpringBoot 的 web 环境中默认使用 tomcat 作为内置服务器,其实 SpringBoot 提供了 4 种内置服务器供我们选择,我们可以很方便的进行切换。

(1)切换原理

  • SpringBoot 如何切换服务器。本质上也是通过 @Conditional 注解,来判断是否引入了依赖、传递了参数,最终决定启用哪一个服务器。

(2)切换方法

  • 首先要用 <exclusion> 将 tomcat 从依赖中移除;
  • 然后引入 jetty 的依赖;

二、@EnableXXX 注解

SpringBoot 中提供了很多 Enable 开头的注解(比如 @EnableAutoConfiguration),这些注解都是用于动态启用某些功能的。

而其底层原理是使用 @Import 注解导入一些配置类,实现 Bean 的动态加载。

  • 比如:引入了 redisTemplate 之后,就可以直接使用 @Autowired 注入属性了。

1、SpringBoot 不能直接获取其他 jar 包/工程中的 Bean

我们可以创建两个模块:一个用来启动 SpringBoot,一个用来定义 Bean。

注意,由于我们会给启动 SpringBoot 的模块,添加定义 Bean 的模块作为依赖,所以包路径不能相同,否则在同一项目下,是可以获取到 User 类的。

(1)创建实体类、配置类

  • 在 Demo-Enable-Bean 模块中,创建一个 User 类、一个 UserConfig 类;
  • 在 UserConfig 类中,注入一个 User 的 Bean;

(2)引入 Demo-Enable-Bean 作为依赖

  • 在 Demo-Enable 模块中,引入 Demo-Enable-Bean;

(3)启动 Demo-Enable 的 SpringBoot

  • 在启动类中,获取 User 的 Bean 对象,观察是否能获取到;
@SpringBootApplication
public class DemoEnableApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(DemoEnableApplication.class, args);
        User user = (User) context.getBean("createUser");
        System.out.println(user);
    }
}
  • 输出结果:

2、原因分析

原因其实很简单,前面也遇到过。

在启动类的注解 @SpringBootApplication 中,有 @ComponentScan 这个注解。默认情况下只会扫描当前引导类所在包及其子包。

因此只需要加上一行注解即可:

但是写字符串数组毕竟比较麻烦,所以还可以使用 @Import 导入我们需要的类:

3、封装 @Import

虽然 @Import 可以直接写上我们需要的 UserConfig 类,问题就在于:

  • 当我们需要这个定义 Bean 的模块(以后就是 jar 包)中的所有关于 User 的类呢?一个个导入会很麻烦。
  • 并且如果在启动 SpringBoot 的模块中,Import 大量其他 jar 包的类,也会提高耦合度。

这个时候就可以在定义 Bean 的模块中,给 User 写一个 @EnableUser 注解,在其中将所有与 User 相关的类全部 Import。

这样我们就可以在别的项目里,只需要写一个 @EnableUser,就能达到目的。

(1)@EnableUser

  • 写上元注解;
  • 添加关于 User 的类;
// 元注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 导入关于 User 的所有类
@Import(value = {UserConfig.class})
public @interface EnableUser {

}

(2)在 SpringBoot 的启动类中添加 @EnableUser

@SpringBootApplication
@EnableUser
public class DemoEnableApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(DemoEnableApplication.class, args);
        User user = (User) context.getBean("createUser");
        System.out.println(user);
    }
}

4、@Import 注解

@EnableXXX 底层依赖于 @lmport 注解导入一些类,使用 @lmport 导入的类会被 Spring 加载到 lOC 容器中。

@lmport 提供 4 种导入方法:

  • 导入Bean;
  • 导入配置类;
  • 导入 ImportSelector 的实现类;(一般用于加载配置文件中的类)
  • 导入 ImportBeanDefinitionRegistrar 的实现类。

5、SpringBoot 自动配置原理

SpringBoot 能够快速启动,得益于其自动配置的机制。关键就在于 @EnableAutoConfiguration 这个注解。

  • @EnableAutoConfiguration 注解内部使用 @Import(AutoconfigurationImportselector.class) 来加载配置类。
  • 而 Importselector 的实现类,又会从配置文件:META-INF/spring.factorles 中加载大量配置类。
  • 当 SpringBoot 启动时,就会自动加载这些配置类,初始化 Bean。
  • 并不是所有 Bean 都会被初始化,在配置类中使用 @Conditinal 来加载满足条件的配置类。

三、自定义 starter 起步依赖

需求:自定义 redis-starter,要求当导入 Jedis 依赖时,SpringBoot 自动创建 Jedis 的 Bean。

1、步骤分析

参考 MyBatis 的起步依赖构造,我们发现:

  • starter 模块不写代码,专门用来整合依赖,其中依赖了 autoconfigure;
  • autoconfigure 模块就包含了 MyBatis 的配置类 MyBatisAutoConfiguration,其中就定义了 Bean 对象;
  • 而要想 Spring 能够识别 MyBatis 的配置类,就会有一个 spring.factorles 配置文件,配置文件中就有 MyBatis 的配置类;

可以发现,后两步其实就是 SpringBoot 的自动配置原理。

因此,大致步骤为:

  • 创建 redis-spring-boot-autoconfigure 模块;
  • 创建 redis-spring-boot-starter 模块,依赖 redis-spring-boot-autoconfigure 模块;
  • 在 redis-spring-boot-autoconfigure 模块中初始化 Jedis 的Bean,并定义 META-INF/spring.factories 文件;
  • 在测试模块中引入自定义的 redis-starter 依赖,测试获取 Jedis 的 Bean,操作 redis。

2、实现步骤

(1)创建两个模块

  • 创建 starter 和 autoconfigure 模块;

  • starter 依赖 autoconfigure,autoconfigure 依赖 jedis;

(2)定义 RedisProperties

由于 Jedis 的初始化需要 host 和 port,并且我们不希望在代码中写死。

  • 因此可以使用配置文件来设置 host 和 port;
  • 相应的要增设一个 RedisProperties 类来绑定对应的配置文件(自行补上 set、get);
  • @ConfigurationProperties 绑定前缀为 redis 的配置文件;
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
    private String host = "localhost";
    private Integer port = 6379;
}

(3)定义 RedisAutoConfiguration

由于 @Bean 修饰的 Jedis 对象,需要 RedisProperties 类来作为参数,因此要添加 @EnableConfigurationProperties 来引入这个 Bean:

  • 需要注意的是,Spring boot 2.2.1 默认关闭对 @ConfigurationProperties 的扫描;
  • 有三种方式可以解决,但在这种自定义 starter 的需求下,只能使用 @EnableConfigurationProperties 来完成引入
@Configuration
@EnableConfigurationProperties(value = {RedisProperties.class})
public class RedisAutoConfiguration {
    @Bean
    public Jedis jedis(RedisProperties redisProperties) {
        return new Jedis(redisProperties.getHost(), redisProperties.getPort());
    }
}

(4)编写配置文件 spring.factories

  • 在 resource 下创建目录:META-INF/spring.factories;
  • 添加 RedisAutoConfiguration 的全类名;
  • 注意,一定要 RedisAutoConfiguration 的全类名是 绿色 才可以;

(5)测试

  • 在 SpringBoot 启动类的项目中添加我们所编写的 RedisStarter 依赖;
  • 获取其中 Jedis 的 Bean 对象;

3、@ConditionalOnMissingBean

当用户自定义了 Bean 对象时,可以替代我们自定义的 starter 中的 Bean 对象。

  • name = "xxx",表示用户定义了 id 为 xxx 的 Bean 时,就不会生成 @ConditionalOnMissingBean 修饰的 Bean。

四、SpringBoot 监听机制

1、Java 监听机制

SpringBoot 的监听机制,其实是对 Java 提供的事件监听机制的封装。

Java 中的事件监听机制定义了以下几个角色:

  • 事件:Event,继承 java.util.EventObject 类的对象;
  • 事件源:Source,任意对象 Object;
  • 监听器:Listener,实现 java.util.EventListener 接口的对象;

2、SpringBoot 监听机制

SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。

  • SpringBoot 内部已经将注册监听器等任务都做好了;
  • SpringBoot 将接口提供,我们只需要实现接口,就能完成监听到事件源之后的操作;

接口则有如下 4 个:

  • ApplicationContextInitializer(需要手动配置);
  • SpringApplicationRunListener(需要手动配置);
  • CommandLineRunner;
  • ApplicationRunner;

(1)实现 4 个接口,并启动 SpringBoot

  • 将上面 4 个接口都实现后,为每个实现方法添加输出;
  • 启动 SpringBoot,观察输出;

  • 由输出可知,只有后 2 个监听器被调用了,因为我们还没有配置前 2 个监听器;

3、CommandLineRunner 与 ApplicationRunner

先来看这两个不需要手动配置的监听器。

这 2 个监听器的 run 方法在 SpringBoot 启动后会自动调用,我们期望可以用 run() 方法来做一些事情。

(1)程序代码

  • 分别作对应的输出;

(2)run() 方法的参数 args

实际使用中,我们希望提前将 redis 中的一些数据加载进缓存中。

  • 打印 args,CommandLineRunner 中 输出 Arrays.asList(args);
  • 打印 args,ApplicationRunner 中 输出 Arrays.asList(args.getSourceArgs());

此时启动 SpringBoot,会发现这两个输出都是空的。

我们可以在启动项中添加一些参数:

  • 若没有“程序实参”这个选项,可以在旁边的“修改选项中添加”;
  • 在“程序实参”中写上一个键值对,然后启动 SpringBoot,就能输出到控制台了;

4、ApplicationContextInitializer

ApplicationContextInitializer 监听器需要使用 META-INF/spring.factories 配置文件来进行引入。

(1)spring.factories

  • 添加键值对:ApplicationContextInitializer 类路径 = 实现类的类路径;

(2)程序代码

  • 在 initialize 方法中,进行一个输出;
  • 注意:不需要 @Component; 

(3)启动 SpringBoot

  • 启动 SpringBoot,发现项目启动之前就已经有输出了;
  • 实际上,Initializer 可以用于在项目还未启动之前,去检测一些资源是否存在;

5、SpringApplicationRunListener

按照配置 ApplicationContextInitializer 的方法同样在 spring.factories 中进行配置:

(1)出现报错

  • <init> 报错一般是指:缺少了构造方法。
  • 这里的问题就是:没有一个有参构造,参数为(SpringApplication,String[])。

(2)SpringApplication

  • SpringApplication:就是项目启动后的事件源
  • 事件源上可以产生很多生命周期不同的相关事件,所以监听器需要事件源作为构造所需的参数;

(3)编写构造函数

  • 参考 SpringBoot 官方的实现类,定义构造函数,其中包含上述 2 个参数;
  • 在新版本中,SpringApplicationRunListener 的 running 方法已经被弃用了,使用 ready 取代它;

  • 下面给出完整代码: 
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
    public MySpringApplicationRunListener(SpringApplication springApplication, String[] args) {
        // structor
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("starting--项目启动中");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("environmentPrepared--环境对象准备");
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("contextPrepared--上下文对象准备");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("contextLoaded--上下文对象加载(耗时操作)");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("started--上下文对象加载完成");
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) { // running
        System.out.println("ready--项目启动完成,开始运行");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("failed--项目启动失败");
    }
}

(4)启动 SpringBoot

五、SpringBoot 监控

1、监控概述

SpringBoot 自带监控功能 Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、 Bean 加载情况、配置属性、日志信息等。

 可以通过监控信息来排除故障。

2、使用方法

  • 导入相关依赖:spring-boot-starter-actruator;
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 访问:http://localhost:8080/acruator

3、info 或其他信息不显示

我们看到当访问的页面只有 health 相关的功能,那是因为新版本中,info 端点默认是不启用的,在 application.properties 中配置的 info 开头的变量默认也是不启用的。

(1)解决方法

修改配置,添加下面两个参数:

  • management.endpoints.web.exposure.include = health, info;
  • management.info.env.enabled = true;

  • 再次访问,这时可以看到,info 端点已经出现了;

(2)为 info 添加参数信息

  • 同样在 application.properties 中配置 info 的参数值;

  • 然后访问 info 端点链接; 

4、显示组件完整信息

(1)显示 health 完整信息

  • 当访问 health 端点的时候,会发现只有一个 status 为 up 的信息。

  • 如果希望显示完整信息,可以修改 show-details 属性。

  • 再次访问 health,就能显示详细信息。 

  • 由于还没有开启其他组件,因此现在只有对磁盘和ping的监控。

(2)添加 redis 组件

  • 添加 redis 起步依赖;
  • 启动 redis 服务器:redis-server.exe;

(3)开启所有的 endpoint

  • 其实就是将上文中,只开启 info、health 的参数值,修改为 * 即可;
  • 将所有的监控 endpoint 暴露出来;

  • 访问 actuator 就会发现多出了一大堆信息;

六、SpringBoot 项目部署

当 SpringBoot 项目开发完成,应该怎么将它放到服务器、生产环境上运行。

SpringBoot 项目开发完毕后,支持 2 种方式部署到服务器:

  • jar 包(官方推荐,使用内置的 Tomcat)
  • war 包(使用外部的 Tomcat)

1、部署 jar 包方式

(1)创建工程

  • 添加 web 起步依赖;
  • 创建一个 Controller 类,包含一个访问路径;

(2)打包

  • 使用 maven 的 package 功能,将项目打包 jar 包;

(3)启动 jar 包

  • 输入命令:java -jar xxx.jar;
  • 启动成功即可访问 UserController;

 

2、部署 war 包方式

(1)继承父类、实现父类方法

  • 继承 SpringBootServletInitializer;
  • 实现 configure 方法,返回值为 SpringApplicationBuilder;
@SpringBootApplication
public class DemoDeployApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(DemoDeployApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(DemoDeployApplication.class);
    }
}

(2)修改 pom.xml 文件的打包方式

  • 将 <packaging> 改为 war 包方式;
  • 然后使用 maven 的 package 打成 war 包;

(3)将 war 包交给 tomcat

  • 将 war 放到 tomcat 目录的 webapps 目录下;

(4)启动 tomcat

  • 找到 bin 目录下的 startup.bat,启动 tomcat;

(5)访问 UserController

  • 先访问 locathost:8080,显示 tomcat 的主页面;
  • 注意:由于使用的是外置的 tomcat,因此 URL 需要加上工程路径;

 

3、项目部署问题汇总

(1)无效源发行版本、无效目标发行版本

主要原因是:编译环境使用的 JDK 和运行环境使用的 JDK 版本不一样。

(2)使用外置 tomcat 时,application.properties 等配置文件不生效

  • 因为 SpringBoot 中的 application 配置配置文件,对应的都是内置的服务器;
  • 当我们使用了外置服务器,就没有效果了;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值