25、Spring Boot 自定义启动器与 CLI 实践

Spring Boot 自定义启动器与 CLI 实践

1. 自定义启动器准备

在开发中,自定义 Spring Boot 启动器能为项目带来极大便利。首先,我们要添加必要的依赖,如 Spring Security 测试依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

此自动配置项目依赖于 Web、Lombok、Security、Hateoas 和 JsonPath。

2. Spring Boot 自动配置原理

Spring Boot 的自动配置是其核心特性之一,它基于类路径自动配置一切。当应用启动时,Spring Boot 自动配置会从 META-INF/spring.factories 文件加载所有类,为应用提供运行所需的默认配置。下面我们创建 spring.factories 文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.apress.todo.configuration.ToDoClientAutoConfiguration

该文件声明了 ToDoClientAutoConfiguration 类。

3. 自动配置类创建

接下来创建 ToDoClientAutoConfiguration 类:

package com.apress.todo.configuration;

import com.apress.todo.client.ToDoClient;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.Resource;
import org.springframework.web.client.RestTemplate;

@RequiredArgsConstructor
@Configuration
@ConditionalOnClass({Resource.class, RestTemplateBuilder.class})
@EnableConfigurationProperties(ToDoClientProperties.class)
public class ToDoClientAutoConfiguration {
    private final Logger log = LoggerFactory.getLogger(ToDoClientAutoConfiguration.class);
    private final ToDoClientProperties toDoClientProperties;

    @Bean
    public ToDoClient client(){
        log.info(">>> Creating a ToDo Client...");
        return new ToDoClient(new RestTemplate(),this.toDoClientProperties);
    }
}

该类使用 @ConditionalOnClass 注解,若类路径中存在 Resource.class RestTemplateBuilder.class ,则继续进行配置。它声明了一个 TodoClient bean,使用 RestTemplate ToDoClientProperties 实例。

4. 辅助类创建
  • ToDoClientProperties 类
package com.apress.todo.configuration;

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

@Data
@ConfigurationProperties(prefix="todo.client")
public class ToDoClientProperties {
    private String host = "http://localhost:8080";
    private String path = "/toDos";
}

此类包含主机和路径的默认值,可在 application.properties 文件中覆盖。
- ToDoClient 类

package com.apress.todo.client;

import com.apress.todo.configuration.ToDoClientProperties;
import com.apress.todo.domain.ToDo;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.client.Traverson;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Collection;

@AllArgsConstructor
@Data
public class ToDoClient {
    private RestTemplate restTemplate;
    private ToDoClientProperties props;

    public ToDo add(ToDo toDo){
        UriComponents uriComponents = UriComponentsBuilder.newInstance()
              .uri(URI.create(this.props.getHost())).path(this.props.getPath()).build();
        ResponseEntity<ToDo> response =
                this.restTemplate.exchange(
                        RequestEntity.post(uriComponents.toUri())
                               .body(toDo)
                        ,new ParameterizedTypeReference<ToDo>() {});
        return response.getBody();
    }

    public ToDo findById(String id){
        UriComponents uriComponents = UriComponentsBuilder.newInstance()
              .uri(URI.create(this.props.getHost())).pathSegment(this.props.getPath(), "/{id}")
              .buildAndExpand(id);
        ResponseEntity<ToDo> response =
                this.restTemplate.exchange(
                        RequestEntity.get(uriComponents.toUri())
                               .accept(MediaTypes.HAL_JSON).build()
                        ,new ParameterizedTypeReference<ToDo>() {});
        return response.getBody();
    }

    public Collection<ToDo> findAll() {
        UriComponents uriComponents = UriComponentsBuilder.newInstance()
              .uri(URI.create(this.props.getHost())).build();
        Traverson traverson = new Traverson(uriComponents.toUri(), MediaTypes.HAL_JSON, MediaType.APPLICATION_JSON_UTF8);
        Traverson.TraversalBuilder tb = traverson.follow(this.props.getPath().substring(1));
        ParameterizedTypeReference<Resources<ToDo>> typeRefDevices =  new ParameterizedTypeReference<Resources<ToDo>>() {};
        Resources<ToDo> toDos = tb.toObject(typeRefDevices);
        return toDos.getContent();
    }
}

该类使用 RestTemplate 实现了添加、查找单个和查找所有 ToDo 项的方法。

5. ToDo 领域类创建

为使用客户端,需创建 ToDo 领域类:

package com.apress.todo.domain;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;

@JsonIgnoreProperties(ignoreUnknown = true)
@NoArgsConstructor
@Data
public class ToDo {
    private String id;
    private String description;
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime created;
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime modified;
    private boolean completed;

    public ToDo(String description){
        this.description = description;
    }
}

该类使用了 @Json* 注解处理日期序列化。

6. 自定义 @Enable 特性创建

Spring 和 Spring Boot 提供了 @Enable* 特性,我们创建自定义的 @EnableToDoSecurity 特性。
- 创建注解

package com.apress.todo.annotation;

import com.apress.todo.security.ToDoSecurityConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(ToDoSecurityConfiguration.class)
public @interface EnableToDoSecurity {
    Algorithm algorithm() default Algorithm.BCRYPT;
}
  • 创建算法枚举
package com.apress.todo.annotation;

public enum Algorithm {
    BCRYPT, PBKDF2
}
  • 创建配置类
package com.apress.todo.security;

import com.apress.todo.annotation.Algorithm;
import com.apress.todo.annotation.EnableToDoSecurity;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

public class ToDoSecurityConfiguration implements ImportSelector {
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        AnnotationAttributes attributes =
                AnnotationAttributes.fromMap(
                        annotationMetadata.getAnnotationAttributes(EnableToDoSecurity.class.getName(), false));
        Algorithm algorithm = attributes.getEnum("algorithm");
        switch(algorithm){
            case PBKDF2:
                return new String[] {"com.apress.todo.security.Pbkdf2Encoder"};
            case BCRYPT:
            default:
                return new String[] {"com.apress.todo.security.BCryptEncoder"};
        }
    }
}

若声明了 @EnableToDoSecurity 注解,该类会根据所选算法返回相应的编码器类。

7. 编码器类创建
  • BCryptEncoder 类
package com.apress.todo.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class BCryptEncoder {
    @Bean
    public ToDoSecurity utils(){
        return new ToDoSecurity(new BCryptPasswordEncoder(16));
    }
}
  • Pbkdf2Encoder 类
package com.apress.todo.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;

@Configuration
public class Pbkdf2Encoder {
    @Bean
    public ToDoSecurity utils(){
        return new ToDoSecurity(new Pbkdf2PasswordEncoder());
    }
}

这两个类都声明了 ToDoSecurity bean。

8. ToDo REST API 服务准备

我们复用使用 Data JPA 和 Data REST 的 todo-rest 项目,对其进行回顾和配置。
- ToDo 领域类

package com.apress.todo.domain;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;

@Entity
@Data
@NoArgsConstructor
public class ToDo {
    @NotNull
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;
    @NotNull
    @NotBlank
    private String description;
    @Column(insertable = true, updatable = false)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime created;
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime modified;
    private boolean completed;

    public ToDo(String description){
        this.description = description;
    }

    @PrePersist
    void onCreate() {
        this.setCreated(LocalDateTime.now());
        this.setModified(LocalDateTime.now());
    }

    @PreUpdate
    void onUpdate() {
        this.setModified(LocalDateTime.now());
    }
}
  • ToDo 仓库接口
package com.apress.todo.repository;

import com.apress.todo.domain.ToDo;
import org.springframework.data.repository.CrudRepository;

public interface ToDoRepository extends CrudRepository<ToDo,String> {
}
  • ToDo REST 配置类
package com.apress.todo.config;

import com.apress.todo.domain.ToDo;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class ToDoRestConfig extends RepositoryRestConfigurerAdapter {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(ToDo.class);
    }
}

application.properties 中添加 JPA 配置:

# JPA
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
9. 安装和测试
  • 安装自定义启动器
    打开终端,进入 todo-client-starter 文件夹,执行以下命令:
$ mvn clean install

此命令将 JAR 安装到本地 .m2 目录。

  • 创建测试项目
    访问 Spring Initializr(https://start.spring.io),设置以下字段:

    • Group: com.apress.task
    • Artifact: task
    • Name: task
    • Package Name: com.apress.task
      选择 Maven 或 Gradle,点击生成项目按钮,下载 ZIP 文件,解压并导入到 IDE。
  • 添加依赖
    若使用 Maven,在 pom.xml 中添加依赖:

<dependency>
    <groupId>com.apress.todo</groupId>
    <artifactId>todo-client-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

若使用 Gradle,在 build.gradle 中添加依赖:

compile('com.apress.todo:todo-client-spring-boot-starter:0.0.1-SNAPSHOT')
  • 主类代码
package com.apress.task;

import com.apress.todo.annotation.EnableToDoSecurity;
import com.apress.todo.client.ToDoClient;
import com.apress.todo.domain.ToDo;
import com.apress.todo.security.ToDoSecurity;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@EnableToDoSecurity
@SpringBootApplication
public class TaskApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(TaskApplication.class);
        app.setWebApplicationType(WebApplicationType.NONE);
        app.run(args);
    }

    @Bean
    ApplicationRunner createToDos(ToDoClient client){
        return args -> {
            ToDo toDo = client.add(new ToDo("Read a Book"));
            ToDo review = client.findById(toDo.getId());
            System.out.println(review);
            System.out.println(client.findAll());
        };
    }

    @Bean
    ApplicationRunner secure(ToDoSecurity utils){
        return args -> {
            String text = "This text will be encrypted";
            String hash = utils.getEncoder().encode(text);
            System.out.println(">>> ENCRYPT: " + hash);
            System.out.println(">>> Verify: " + utils.getEncoder().matches(text,hash));
        };
    }
}

此代码包含两个 ApplicationRunner bean,分别使用 ToDoClient ToDoSecurity bean。

10. 运行任务应用

运行应用前,确保 todo-rest 应用在端口 8080 上运行。若 ToDo REST API 在不同的 IP、主机、端口或路径上运行,可在 application.properties 文件中使用 todo.client.* 属性更改默认值:

# ToDo Rest API
todo.client.host=http://some-new-server.com:9091
todo.client.path=/api/toDos

运行任务应用后,应看到类似以下输出:

INFO - [ main] c.a.t.c.ToDoClientAutoConfiguration      : >>> Creating a ToDo Client...
INFO - [ main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
INFO - [ main] com.apress.task.TaskApplication          : Started TaskApplication in 1.047 seconds (JVM running for 1.853)
ToDo(id=8a8080a365f427c00165f42adee50000, description=Read a Book, created=2018-09-19T17:29:34, modified=2018-09-19T17:29:34, completed=false)
[ToDo(id=8a8080a365f427c00165f42adee50000, description=Read a Book, created=2018-09-19T17:29:34, modified=2018-09-19T17:29:34, completed=false)]
>>> ENCRYPT: $2a$16$pVOI../twnLwN3GFiChdR.zRFfyCIZMEbwEXbAtRoIHqxeLB3gmUG
>>> Verify: true
11. Spring Boot CLI 介绍

Spring Boot CLI 是一个强大的工具,可帮助创建原型和生产就绪的应用程序。它不是 Maven 或 Gradle 插件或依赖,而是 Spring Boot 安装的一部分。

  • 安装方式
    • Mac/Linux 使用 Homebrew:
$ brew tap pivotal/tap
$ brew install springboot
- Linux 使用 SDKMAN:
$ sdk install springboot
  • 示例代码
    以下是 Groovy 和 Java 的简单示例:
    • Groovy 示例( app.groovy ):
@RestController
class WebApp{
    @RequestMapping("/")
    String greetings(){
        "Spring Boot Rocks"
    }
}
- Java 示例(`WebApp.java`):
package com.apress.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class WebApp {
    @RequestMapping("/")
    public String greetings(){
        return "Spring Boot Rocks in Java too!";
    }

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

综上所述,通过上述步骤,我们完成了自定义 Spring Boot 启动器的创建、测试,同时了解了 Spring Boot CLI 的使用。这些技术能极大提高开发效率,让我们更轻松地构建 Spring Boot 应用。

以下是创建自定义启动器和测试的流程图:

graph LR
    A[添加依赖] --> B[创建 spring.factories 文件]
    B --> C[创建自动配置类]
    C --> D[创建辅助类]
    D --> E[创建领域类]
    E --> F[创建自定义 @Enable 特性]
    F --> G[安装自定义启动器]
    G --> H[创建测试项目]
    H --> I[添加依赖]
    I --> J[编写主类代码]
    J --> K[运行测试]

通过这种方式,我们可以清晰地看到整个开发和测试流程。同时,Spring Boot 的自动配置和 CLI 工具为我们的开发工作带来了极大的便利,让我们能够更高效地构建应用程序。

Spring Boot 自定义启动器与 CLI 实践

12. 自定义启动器优势总结

自定义 Spring Boot 启动器具有诸多优势,以下为您详细介绍:
| 优势 | 说明 |
| — | — |
| 提高开发效率 | 封装通用配置和功能,减少重复代码编写,快速搭建项目框架。 |
| 增强可维护性 | 将相关配置和代码集中管理,便于修改和扩展。 |
| 促进团队协作 | 统一项目配置和依赖管理,避免因环境差异导致的问题。 |
| 方便复用 | 可在多个项目中复用启动器,节省开发时间和成本。 |

13. Spring Boot CLI 高级特性

Spring Boot CLI 除了基本的运行功能外,还有一些高级特性值得探索。

  • 交互式 shell :自 1.4 版本起,Spring Boot CLI 提供了交互式 shell,可在其中执行各种命令,如运行代码、查看帮助等。启动交互式 shell 只需在命令行输入 spring shell
  • 依赖管理 :在 Groovy 或 Java 文件中,可使用 @Grab 注解管理依赖。例如:
@Grab('org.springframework.boot:spring-boot-starter-web')
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RequestMapping

@RestController
@SpringBootApplication
class WebApp {
    @RequestMapping("/")
    String greetings() {
        "Spring Boot with Dependencies"
    }
}
14. 自定义启动器与 CLI 结合应用场景

以下是一些自定义启动器与 Spring Boot CLI 结合使用的常见应用场景:
- 快速原型开发 :使用 Spring Boot CLI 快速编写代码,结合自定义启动器提供的通用功能,迅速搭建原型应用。
- 微服务架构 :为每个微服务创建自定义启动器,使用 CLI 进行快速部署和测试,提高开发和维护效率。
- 持续集成与部署 :在 CI/CD 流程中,利用 CLI 自动化执行代码构建、测试和部署任务,结合自定义启动器确保服务的一致性。

15. 常见问题及解决方案

在使用自定义启动器和 Spring Boot CLI 过程中,可能会遇到一些问题,以下是常见问题及解决方案:
| 问题 | 解决方案 |
| — | — |
| 依赖冲突 | 检查 pom.xml build.gradle 文件,排除冲突依赖,或调整依赖版本。 |
| CLI 命令无法执行 | 确保 Spring Boot CLI 已正确安装,可通过 spring --version 验证。 |
| 自动配置不生效 | 检查 spring.factories 文件和自动配置类,确保类路径和注解正确。 |

16. 性能优化建议

为提高自定义启动器和 Spring Boot 应用的性能,可参考以下建议:
- 减少不必要的依赖 :只添加项目必需的依赖,避免引入过多无用的库。
- 优化自动配置 :确保自动配置类只在必要时执行,避免不必要的初始化。
- 使用缓存 :对于频繁访问的数据和资源,使用缓存机制减少重复计算和 I/O 操作。

17. 未来发展趋势

随着 Spring Boot 技术的不断发展,自定义启动器和 CLI 工具也将不断完善和扩展。未来可能会有更多的自动化配置和功能集成,进一步提高开发效率和用户体验。同时,与云原生技术的结合也将更加紧密,为构建分布式系统和微服务架构提供更强大的支持。

18. 总结与展望

通过本文的介绍,我们详细了解了如何创建自定义 Spring Boot 启动器以及使用 Spring Boot CLI。自定义启动器让我们能够根据项目需求定制化配置和功能,提高开发效率和可维护性;Spring Boot CLI 则为我们提供了快速创建和运行应用的便捷方式。

在实际开发中,您可以根据项目的具体需求,灵活运用这些技术,不断探索和尝试新的应用场景。同时,持续关注 Spring Boot 技术的发展动态,学习和掌握新的特性和功能,让您的开发工作更加高效和出色。

以下是 Spring Boot CLI 使用的流程图:

graph LR
    A[安装 Spring Boot CLI] --> B[编写代码(Groovy 或 Java)]
    B --> C{选择运行方式}
    C -->|普通运行| D[使用 spring run 命令]
    C -->|交互式 shell| E[启动 spring shell]
    D --> F[查看运行结果]
    E --> G[在 shell 中执行命令]
    G --> F

希望本文能为您在 Spring Boot 开发中提供有价值的参考,祝您开发顺利!

基于径向基函数神经网络RBFNN的自适应滑模控制学习(Matlab代码实现)内容概要:本文介绍了基于径向基函数神经网络(RBFNN)的自适应滑模控制方法,并提供了相应的Matlab代码实现。该方法结合了RBF神经网络的非线性逼近能力和滑模控制的强鲁棒性,用于解决复杂系统的控制问题,尤其适用于存在不确定性和外部干扰的动态系统。文中详细阐述了控制算法的设计思路、RBFNN的结构权重更新机制、滑模面的构建以及自适应律的推导过程,并通过Matlab仿真验证了所提方法的有效性和稳定性。此外,文档还列举了大量相关的科研方向和技术应用,涵盖智能优化算法、机器学习、电力系统、路径规划等多个领域,展示了该技术的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及工程技术人员,特别是从事智能控制、非线性系统控制及相关领域的研究人员; 使用场景及目标:①学习和掌握RBF神经网络滑模控制相结合的自适应控制策略设计方法;②应用于电机控制、机器人轨迹跟踪、电力电子系统等存在模型不确定性或外界扰动的实际控制系统中,提升控制精度鲁棒性; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,深入理解算法实现细节,同时可参考文中提及的相关技术方向拓展研究思路,注重理论分析仿真验证相结合。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值