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。
-
Group:
-
添加依赖 :
若使用 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):
-
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 开发中提供有价值的参考,祝您开发顺利!
超级会员免费看
2万+

被折叠的 条评论
为什么被折叠?



