栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot自定义Starter(二十四)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot自定义Starter(二十四)

即使有一天,我放弃了自己的身体,也请你,不要放弃我,我亲爱的灵魂.

上一章简单介绍了Spring_Session解决Session共享的问题(二十三),如果没有看过,请观看上一章

一. 自定义Starter 基础知识 一.一 自定义Starter

以前我们在学习 Spring 时,依赖的依赖,都是某一个具体的依赖信息。

在Spring Boot 阶段,我们导入的依赖 的 artifactId 都拥有 starter

如: mybatis-spring-boot-starter pagehelper-spring-boot-starter 等.

这些其实,都是比较官方的自定义 Starter。

在日常开发里面,对于独立于业务模块的一些功能,常常会放置在一个单独的包里,
如 切面实现日志功能, 在项目A里面使用到了,在项目B 里面也使用到了,
以前的做法,是什么样子的呢? 通常将这个日志功能实现的代码复制A项目里面,
同时复制到B项目里面,这样 A项目便拥有了日志的功能,B项目也拥有了日志的功能。
如果实现日志功能的代码发生改变,那么项目A,项目B都需要进行修改。

如果能够将 这个切面实现日志功能的代码 提取出来,变成一个 依赖的jar包,
项目A,项目B直接导入依赖,便可以使用,那就好了。

我们可以通过自定义Starter 实现这些功能。

SpringBoot提供的starter以spring-boot-starter-xxx的方式命名的。

官方建议自定义的starter使用xxx-spring-boot-starter命名规则。以区分SpringBoot生态提供的starter。

这一章节,先简单实现一个基础的自定义Starter 的功能。

下一章节,老蝴蝶带着大家通过自定义 Starter 实现切面日志记录的功能。

一.二 自定义Starter 需要什么

我们自定义 Starter 需要什么呢? 想一想,以前的配置, 如 redis的配置,mysql数据库的配置信息.

  1. 我们需要传入一些参数,可以更改配置 (在application.yml 中配置参数) ,同时有默认的配置参数.
  2. 有一个功能实现,可以获取到用户传入的配置参数,使用这些配置参数,可以实现我们想要的功能.
  3. 动态的进行配置,热插拔效果。达到 我们拥有这个自定义starter,就拥有这些东西,没有自定义starter,就没有这些东西的效果。
  4. 自动化的配置,可以被 SpringBoot 官方识别并承认 (需要固定的格式).

我们需要拥有这些东西,才可以进行自定义 Starter

二. 实现用户信息打印的 自定义 Starter

先创建一个默认的 Maven 项目, 非 SpringBoot 项目。

主要依靠的是 SpringBoot 的自动配置

我们做一个简单的,配置信息打印的 Starter

1,2,3,4 其实对应的就是 一.二 部分的 1,2,3,4

二.一 pom.xml 导入依赖


    4.0.0

    top.yueshushu
    starter
    1.0-SNAPSHOT
    ButterflyStarter

    
        
        
            org.springframework.boot
            spring-boot-autoconfigure
            2.2.2.RELEASE
        
        
        
            org.springframework.boot
            spring-boot-configuration-processor
            2.2.2.RELEASE
            true
        
    


除些之外,不需要引入其他的.

二.二 属性类接收用户自定义参数

在 application.yml 中配置参数,通常有很多个,且格式固定, 我们常常通过一个配置属性类进行接收用户的自定义参数。

我们传入 名称,年龄,描述 三个基础的参数 信息.

UserProperties.java

package top.yueshushu.starter.mode;

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

import java.io.Serializable;



@SuppressWarnings("serial")
@ConfigurationProperties(prefix = "butterfly")
public class UserProperties implements Serializable {
    
    private final String DEFAULT_NAME="两个蝴蝶飞";
    private final Integer DEFAULT_AGE=26;
    private final String DEFAULT_DEscriptION="一个快乐的程序员";

    
    private String name=DEFAULT_NAME;
    private Integer age=DEFAULT_AGE;
    private String description=DEFAULT_DEscriptION;

    
    public UserProperties() {

    }

    public UserProperties(String name, Integer age, String description) {
        this.name = name;
        this.age = age;
        this.description = description;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", description='" + description + ''' +
                '}';
    }
}

二.三 做什么, 要实现的功能

我们拿到用户的自定义参数 (没有的话,走默认的参数)后,要做实现事情,即要实现的功能.

UserPropertiesService.java

package top.yueshushu.starter.service;
import java.util.logging.Logger;


public class UserPropertiesService {
   //定义日志
    Logger logger=Logger.getLogger(UserPropertiesService.class.getSimpleName());
    //参数信息,自定义starter 提供的全部的参数信息。
    private String name;
    private Integer age;
    private String description;
    public UserPropertiesService() {

    }
    // 接收参数
    public UserPropertiesService(String name,Integer age,String description) {
         this.name=name;
         this.age=age;
         this.description=description;
    }
    
    public String println(){
        String message = String.format("大家好,我叫: %s, 今年 %s岁, 个人描述: %s",
                name, age,description);
        logger.info(">>>用户的信息:"+message);
        return message;
    }
}

二.四 动态热插拔配置

主要是将 这个 XxxService 管控起来。

UserPropertiesServiceConfiguration.java

package top.yueshushu.starter.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.yueshushu.starter.mode.UserProperties;
import top.yueshushu.starter.service.UserPropertiesService;


//表示是一个配置类
@Configuration
//用户输入的参数信息,可以放置到 UserProperties参数里面。一一对应起来
@EnableConfigurationProperties(UserProperties.class)
//存在某个条件类时触发, 即寻找到 UserPropertiesService.class时,才生效
@ConditionalOnClass(UserPropertiesService.class)
public class UserPropertiesServiceConfiguration {
   
    @Autowired
    private UserProperties userProperties;

    @Bean
    public UserPropertiesService userPropertiesService(){
        //注意,当条件触发时,才会创建 Bean 
        UserPropertiesService userService=new UserPropertiesService(
                userProperties.getName(),
                userProperties.getAge(),
                userProperties.getDescription()
        );
        return userService;
    }
}

二.五 配置,能被 SpringBoot 官方承认

必须要进行自动装配,否则这些东西,不被 SpringBoot 官方承认,那么在SpringBoot 项目里面进行引用,就找不到,造成功能不生效.

在 resources 目录下 创建 meta-INF 目录,下面放置 spring.factories 文件 (必须叫这个名字)

# 添加配置信息,后面跟的是 自定义的那个configuration的全限定类名
org.springframework.boot.autoconfigure.EnableAutoConfiguration=top.yueshushu.starter.configuration.UserPropertiesServiceConfiguration  

现在,这个自定义配置的 ButterflyStarter 就算了成功了。

将其进行打包,安装到本地仓库 (老蝴蝶这儿是通过 mvn clean install 命令实现的)

也可以通过 Lifecycle 生命周期按照进行实现

二.六 测试自定义 Starter

新创建一个 SpringBoot 的项目, StarterApply, 可以正常的启动和访问即可。

二.六.一 pom.xml 中添加依赖

除了 SpringBoot 该有的依赖外,导入刚才 install 生成的自定义Starter 依赖

 
        
            top.yueshushu
            starter
            1.0-SNAPSHOT
        
二.六.二 测试自定义Starter

在 StarterApply 项目里面,添加 StarterTest 测试类,进行测试

package top.yueshushu.learn;

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.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
import top.yueshushu.condition.Animal;
import top.yueshushu.condition.JavaAnimalConfig;
import top.yueshushu.starter.service.UserPropertiesService;


@RunWith(SpringRunner.class)
@SpringBootTest
public class StarterTest {

    @Autowired
    private UserPropertiesService userPropertiesService;
    @Test
    public void starterPrintlnTest(){
        String message = userPropertiesService.println();
        System.out.println("Apply项目输出信息:"+message);
    }
}

二.六.二.一 进行测试

此时, application.yml 配置文件里面,没有关于自定义 Starter 的配置信息

进行测试,发现可以打印, 但是出现了乱码。 是 starter 本身出现的乱码。

二.六.二.二 解决 Starter 乱码问题

在 自定义的Starter pom.xml 添加编译插件,设置编码为 UTF-8

 
    
        
            
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    1.8
                    1.8
                    utf-8
                
            
        
    

重新 mvn clean install ,然后刷新依赖即可。

解决了中文乱码的问题,

自定义 starter 里面的 日志信息会输出,同时项目里面的输出打印语言也生效。 输出的信息,是默认的信息

二.六.二.三 用户自定义参数测试

在 application.yml 中, 输入 butt 发现,有相应的属性提示

输入自定义的参数信息

butterfly:
  name: 岳泽霖
  age: 26
  description:  一个孤独的小孩子

进行测试

可以打印出用户自定义输入的参数信息。

说明 自定义 Starter 是生效的。

三. 自定义Starter 深入理解

自定义的Starter 能够起作用,条件注解起了很大的作用.

演示一下条件注解的使用.

仍然使用 刚才的自定义 Starter

三.一 pom.xml 中添加依赖
   
        
        
            org.springframework.boot
            spring-boot-autoconfigure
            2.2.2.RELEASE
        
        
        
            org.springframework.boot
            spring-boot-configuration-processor
            2.2.2.RELEASE
            true
        
        
        
            org.springframework
            spring-context
            5.2.2.RELEASE
        
    
三.二 定义一个接口和两个实现类

定义 Animal 接口

package top.yueshushu.condition;


public interface Animal {
    
    public void voice();
}

有两个实现类, 一个是 Cat 一个是 Dog

Cat.java

package top.yueshushu.condition;


public class Cat implements Animal {

    @Override
    public void voice() {
        System.out.println(">>>喵喵喵");
    }
}

Dog.java

package top.yueshushu.condition;



public class Dog implements Animal {

    @Override
    public void voice() {
        System.out.println(">>>>汪汪汪");
    }
}

三.三 定义实现类起作用的条件

Animal 接口有两个实现类 Cat, Dog 在使用中,要使用哪一个呢?

总不能

@Resource("cat")
private Animal animal

@Resource("dog")
private Animal animal
    

这样肯定是不行的。

可以定义条件, 当Cat 的条件满足时,就用 Cat 的实现类, 当 Dog 的条件满足时,就用 Dog 的实现类

三.三.一 Cat 实现条件 CatCondition
package top.yueshushu.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypemetadata;


// 实现 Condition 接口
public class CatCondition implements Condition {

   @Override
    public boolean matches(ConditionContext context, AnnotatedTypemetadata metadata) {
        String animal = context.getEnvironment().getProperty("animal");
        //为空属性,不创建
        if(StringUtils.isEmpty(animal)){
            return false;
        }
        return "Cat".equalsIgnoreCase(animal);
    }
}

三.三.二 Dog 实现条件 DogCondition
package top.yueshushu.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypemetadata;
import org.springframework.util.StringUtils;


public class DogCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypemetadata metadata) {
        //没有获取到这个属性时,走 Dog 
        String animal = context.getEnvironment().getProperty("animal");
        if(StringUtils.isEmpty(animal)){
            return true;
        }
        return "Dog".equalsIgnoreCase(animal);
    }
}
三.四 让条件被管控

现在有 Cat 的条件,也有 Dog 的条件。

但这两个条件,只是实现了 Condition 接口, 并没有添加形如 @Component @Configuration 等类似的注解

说明,这两个条件,还没有办法被SpringBoot 管控到.

JavaAnimalConfig 管控条件

package top.yueshushu.condition;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;


@Configuration
public class JavaAnimalConfig {
    
    @Bean("animal")
    @Conditional(DogCondition.class)
    Animal dog(){
        return new Dog();
    }
    
    @Bean("animal")
    @Conditional(CatCondition.class)
    Animal cat(){
        return new Cat();
    }
}

两者创建的,bean 都是 animal

三.五 测试
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        //ctx.getEnvironment().getSystemProperties().put("animal", "Cat");
       // ctx.getEnvironment().getSystemProperties().put("animal", "Dog");
        //  ctx.getEnvironment().getSystemProperties().put("animal", "YJL");
        ctx.register(JavaAnimalConfig.class);
        ctx.refresh();
        Animal animal = (Animal) ctx.getBean("animal");
        animal.voice();
    }
}

设置环境变量值的信息, 都去掉, 会生成默认的 Dog

当使用 Cat 时, 会打印 Cat 的实现类

当使用 Dog 时,会打印 Dog 的实现类

当使用 YJL 时, 因为都不符合,所以创建不了相应的 Bean

也可以将这个自定义的Starter clean install 安装后, StarterApply 引用

@RunWith(SpringRunner.class)
@SpringBootTest
public class StarterTest {

    @Autowired
    private UserPropertiesService userPropertiesService;
    @Test
    public void starterPrintlnTest(){
        String message = userPropertiesService.println();
        System.out.println("Apply项目输出信息:"+message);
    }
   // 运行测试一下 
    @Test
    public void animalJavaTes(){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().getSystemProperties().put("animal", "Cat");
        ctx.register(JavaAnimalConfig.class);
        ctx.refresh();
        Animal animal = (Animal) ctx.getBean("animal");
        animal.voice();
    }
}

发现,也是相同的测试结果.

三.六 Profile 多环境配置

在多环境配置时,我们使用到了 Profile

在 JavaAnimalConfig 里面,

public class JavaAnimalConfig {
    
    @Bean("animal")
    @Conditional(DogCondition.class)
    Animal dog(){
        return new Dog();
    }
    
    @Bean("animal")
    @Conditional(CatCondition.class)
    Animal cat(){
        return new Cat();
    }
}

我们定义了 DogCondition, 能不能将这个换成 Profile 呢? 这样就不用根据环境变量的值来确定实例化哪一个实现了,而是根据环境配置来实现化具体的实现.

@Configuration
public class JavaAnimalConfig {
    
    @Bean("animal")
  //  @Conditional(DogCondition.class)  不使用
    @Profile("Dog") //触发条件
    Animal dog(){
        return new Dog();
    }
    
    @Bean("animal")
    //@Conditional(CatCondition.class)
    @Profile("Cat") //触发条件
    Animal cat(){
        return new Cat();
    }
}


进行测试

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        //ctx.getEnvironment().getSystemProperties().put("animal", "Cat");
       // ctx.getEnvironment().getSystemProperties().put("animal", "Dog");
       // ctx.getEnvironment().getSystemProperties().put("animal", "YJL");


       // ctx.getEnvironment().setActiveProfiles("Dog");
        //ctx.getEnvironment().setActiveProfiles("Cat");
       // ctx.getEnvironment().setActiveProfiles("YJL");

        ctx.register(JavaAnimalConfig.class);
        ctx.refresh();
        Animal animal = (Animal) ctx.getBean("animal");
        animal.voice();
    }
}

当启用 Dog 时, 实现的是 Dog

当启用的是 Cat 时,实现的是 Cat 实现

当启用的是 YJL 时,会报异常信息。与上面的测试结果一致.

也同样可以在 StarterTest 类中进行测试,

 @Test
    public void animalJavaTes(){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        //ctx.getEnvironment().getSystemProperties().put("animal", "Cat");
        // ctx.getEnvironment().getSystemProperties().put("animal", "Dog");
        // ctx.getEnvironment().getSystemProperties().put("animal", "YJL");

         ctx.getEnvironment().setActiveProfiles("Dog");
        //ctx.getEnvironment().setActiveProfiles("Cat");
        // ctx.getEnvironment().setActiveProfiles("YJL");

        ctx.register(JavaAnimalConfig.class);
        ctx.refresh();
        Animal animal = (Animal) ctx.getBean("animal");
        animal.voice();
    }

测试结果,也是一样的。

但如果将 setActiveProfiles 都去掉,在 application.yml 中进行设置

是不可以的。 还是会报错误

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘animal’ available

本章节的代码放置在 github 上:

https://github.com/yuejianli/springboot/tree/develop/ButterflyStarter

https://github.com/yuejianli/springboot/tree/develop/StarterApply

谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/356137.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号