Apache Camel与Spring Boot集成

x33g5p2x  于2022-09-16 转载在 Spring  
字(12.3k)|赞(0)|评价(0)|浏览(1598)

在这篇文章中,我们将看看如何将Apache Camel与Spring Boot***集成。

1. 简介

Apache Camel是一个集成框架,目的是把不同的系统放在一起,使其稳健地工作。在企业系统中,总是有工作要连接不同的系统。Apache Camel为开发者提供了一种方法,让他们专注于自己的业务逻辑,而不需要将你的数据转换为规范的格式。Camel通过支持80多个协议和数据类型的API实现。因此,作为一个开发者,你只需要知道Camel是如何把所有东西粘在一起的。在这篇文章中,我们将看看将Apache Camel与Spring Boot整合的步骤。

在展示一个使用Spring Boot的例子之前,最好先了解一下Camel的核心概念和术语。

1.1. 消息

系统用于相互通信的实体。

1.2. 交换

交易所封装消息并提供系统间的交互。它是决定消息类型的消息容器。

1.3. Camel Context

Camel Context是Camel的核心模型,提供对Routes、Endpoints等服务的访问。

1.4. 路由

一种抽象,允许客户和服务器独立工作。我们用特定领域语言创建路由,它们是一连串的函数调用(处理器)。

1.5. 特定领域语言(DSL)

处理器和端点通过使用DSL将它们连在一起,最后形成路由。在我们的例子中,DSL是JAVA流畅的API,但如果我们将Camel与其他语言/框架一起使用,那么DSL也可以是XML等。

1.6. 处理器

处理器执行交换操作。我们可以认为路由是一个逻辑单元,它连接正确的处理器来处理一个消息。

1.7. 组件

组件是Apache Camel的扩展单元。它们是使Camel非常容易与其他系统集成的单元。请看core components支持的组件的完整列表。组件作为Endpoints的工厂,通过给定的URI创建它们。

1.8. 端点

端点是服务的连接点,将系统与其他系统连接起来。我们通过给定URI的组件来创建端点。例如,为了创建一个FTP连接,在路由中提供以下URI:<em>ftp://[[email protected]]hostname[:port]/directoryname[?options]</em>,组件以给定的配置创建一个FTP的端点。

1.9. 生产者

生产者是Camel的单元,它创建并发送消息到一个端点。

1.10. 消费者

消费者是Camel的单元,它接收由生产者创建的消息,将其包装成交换物并将其发送给处理器。

到目前为止,我们总结了Camel的主要部分。没有必要去了解每个概念的细节,但有一个Camel的架构概述是很好的,这有助于我们正确使用它。在我们下面的例子中,我们将展示它们是如何被集成到Spring Boot中的。

2. 应用程序概述

我们将创建一个应用程序。

  • 有产品和折扣实体
  • 我们在启动时插入产品
  • 折扣自动应用于某些时期的产品(Camel Timer + Camel JPA)。
  • 列出所有产品和折扣的REST端点(Camel REST)。
  • Swagger文档(Camel Swagger)。

为此,我们将使用H2、Spring Web、Spring JPA和Apache Camel。

3. 设置应用程序

用以下依赖项创建你的maven项目。你可以使用你的IDE或Spring Initializr来启动你的应用程序。以下是完整的pom.xml及其解释。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <!--Get required dependencies from a parent-->
   <parent>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-dependencies</artifactId>
      <version>3.3.0</version>
   </parent>
   <artifactId>spring-boot-camel</artifactId>
   <name>spring-boot-camel</name>
   <description>Spring Boot Camel integration tutorial</description>
   <properties>
      <spring-boot-version>2.2.7.RELEASE</spring-boot-version>
      <run.profiles>dev</run.profiles>
   </properties>
   <dependencyManagement>
      <dependencies>
         <!--Import as a pom to let spring-boot to manage spring-boot dependencies version -->
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot-version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
         <!--Import as a pom to let camel manage camel-spring-boot dependencies version-->
         <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-spring-boot-dependencies</artifactId>
            <version>${project.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>
   <dependencies>
      <!--Spring boot dependencies to enable REST, JPA and Core features-->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>
      <!--Camel Spring Boot Dependencies to enable REST, JSON, SWAGGER, JPA features-->
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-spring-boot-starter</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-servlet-starter</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-jackson-starter</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-swagger-java-starter</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-jpa-starter</artifactId>
      </dependency>
      <!--In memory database-->
      <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
         <scope>runtime</scope>
      </dependency>
      <!--Spring boot testing-->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot-version}</version>
            <executions>
               <execution>
                  <goals>
                     <goal>repackage</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
</project>

4. 设置实体

在处理Apache Camel之前,我们需要设置一些实体、服务和存储库。

4.1. 产品

创建一个带有id、name、price和discounted字段的Product实体。我们也要创建一个命名查询,它可以从Camel中调用其名称并返回查询结果。我们的discounted-products命名查询返回所有有折扣的产品。

@Entity
@Table(name = "products")
@NamedQuery(name = "discounted-products", query = "select product from Product product where product.discounted IS NOT NULL")
public class Product {

 @Id
 @GeneratedValue
 private int id;

 private String name;

 private Integer price;

 private Integer discounted;

 // Getters and setters
}

创建一个ProductRepository类,它扩展自Spring Data的CrudRepository。这个扩展为我们提供了随时可以调用的查询,如findAll, findById, save等。

public interface ProductRepository extends CrudRepository<Product, Integer> {
}
4.2. 服务类

创建一个ProductService类,并用服务注解对其进行注解。使用构造函数注入来从Spring Context中获取ProductRepository。我们提供基本的findById<code>, <code>findAll,并保存不言而喻的功能。

@Service
public class ProductService {

    private final ProductRepository products;

    @Autowired
    public ProductService(ProductRepository products) {
        this.products = products;
    }

    public Product findById(Integer id) {
        Optional < Product > product = products.findById(id);
        if (!product.isPresent()) {
            throw new IllegalStateException("Product could not found for given id:" + id);
        }
        return product.get();
    }

    public Iterable < Product > findAll() {
        return products.findAll();
    }

    public void save(Product product) {
        products.save(product);
    }
}

作为最后一步,在src/main/resources中创建一个data.sql文件并插入3个产品,如下所示。Spring将在启动时自动运行data.sql。阅读更多关于启动脚本的信息

INSERT INTO products (id, name, price, discounted)
  VALUES
      (1, 'Book', 25, NULL),
      (2, 'Watch', 100, NULL),
      (3, 'Shoes', 40, NULL);
4.3. 折扣

创建带有id、金额和产品字段的折扣实体。一个产品在给定的时间内可能发生一次折扣,所以在产品字段上建立OneToOne关系。

@Entity
@Table(name = "discounts")
public class Discount {

    @Id
    @GeneratedValue
    private int id;

    private Integer amount;

    @OneToOne
    private Product product;

    // Getters and setters
}

像我们一样创建DiscountRepository。

public interface DiscountRepository extends CrudRepository<Discount, Integer> {}

创建DiscountService类,类似于ProductService。除了findDiscount方法和findProduct的工作原理一样,我们还有makeDiscount函数。这个函数生成一个随机折扣,从数据库中获取随机产品,并将折扣应用于该产品。

@Service
public class DiscountService {

    private final DiscountRepository discounts;
    private final ProductService productService;

    private final Random random = new Random();

    @Autowired
    public DiscountService(DiscountRepository discounts,
        ProductService productService) {
        this.discounts = discounts;
        this.productService = productService;
    }

    public Discount makeDiscount() {
        // create a discount
        Discount discount = new Discount();
        int discountRate = this.random.nextInt(100);
        discount.setAmount(discountRate);

        // select random product
        int productId = this.random.nextInt(3) + 1;
        Product product = productService.findById(productId);

        // set the discount to product and save
        int discountedPrice = product.getPrice() - (discountRate * product.getPrice() / 100);
        product.setDiscounted(discountedPrice);
        productService.save(product);

        discount.setProduct(product);
        return discount;
    }

    public Discount findDiscount(Integer id) {
        Optional < Discount > discount = discounts.findById(id);
        if (!discount.isPresent()) {
            throw new IllegalStateException("Discount could not found for given id:" + id);
        }
        return discount.get();
    }
}

5. 应用程序配置

创建application-dev.yml来配置contextPath映射需要的Camel。添加自定义的折扣属性,这些属性将在我们的路线中使用。

camel:
  component:
    servlet:
      mapping:
        contextPath: /javadevjournal/*

discount:
  newDiscountPeriod: 2000
  listDiscountPeriod: 6000/pre>

6. Apache Camel集成

到目前为止,我们在处理Apache Camel之前配置了我们的数据。现在让我们来使用它。

6.1. 创建路由

Camel提供RouteBuilder作为创建路由的基类。我们需要扩展它,并用@Component来注释它。正如我们前面提到的,Apache Camel使用其上下文来引用对象。但是当与SpringBoot一起工作时,Camel首先搜索SpringBoot的上下文,然后将发现的对象注入到它的CamelContext中,比如我们例子中的RouteBuilder

在创建了从RouteBuilder延伸出来的Routes类后,我们需要重写它的configure方法。我们想有一个逻辑,在某个给定的时期自动生成折扣。让我们先把下面的路由添加到我们的configure函数中并解释一下。

@Component
class TimedJobs extends RouteBuilder {

@Override
public void configure() {
        from("timer:new-discount?delay=1000&period={{discount.newDiscountPeriod:2000}}")
            .routeId("make-discount")
            .bean("discountService", "makeDiscount")
            .to("jpa:org.apache.camel.example.spring.boot.rest.jpa.Discount")
            .log("Created %${body.amount} discount for ${body.product.name}");

        // additional route will be added in the next step
}

在这里最好考虑一下我们的Camel术语,同时把它和Spring Boot一起使用。我们正在使用Java DSL创建路由。然后我们使用timerComponent,它是Camel提供的一个扩展。在引擎盖下,Camel通过我们最初的延迟和运行期配置,到达定时器端点来启动其生产者。

在进一步使用之前,值得一提的是,Apache Camel支持使用Spring Boot属性,正如我们在这里使用的那样。你可以直接用它的名字和默认值来引用它们,比如{{property_name:default_value}}.

然后定义make-discount路线,这应该是唯一的,以后可以参考。然后我们在discountService Bean中调用我们的makeDiscount函数。MessageExchanged,可以用body前缀引用,Consumed由logger来记录。请参考Simple Language,了解你可以使用的表达式的完整列表。让我们也在前面的路由下面添加另一个路由,以列出所有的产品和它们的更新价格。

from("jpa:org.apache.camel.example.spring.boot.rest.jpa.Product"
    + "?namedQuery=discounted-products"
    + "&delay={{discount.listDiscountPeriod:6000}}"
    + "&consumeDelete=false")
    .routeId("list-discounted-products")
    .log(
        "Discounted product ${body.name}. Price dropped from ${body.price} to ${body.discounted}");

我们正在为我们的产品实体使用JPA组件,并将其称为namedQueryconsumeDelete查询意味着我们不想删除处理过的产品实体,查看JPA Component的完整配置列表。下面是我们工作的日志。

Created %27 discount for Watch
Created %84 discount for Book
Created %92 discount for Shoes
Discounted product Book. Price dropped from 25 to 4
Discounted product Watch. Price dropped from 100 to 73
Discounted product Shoes. Price dropped from 40 to 4
6.2. 创建REST端点

到目前为止,我们配置了定时器组件来触发我们的功能。让我们也来整合REST端点并生成Swagger文档。创建一个新的路由,扩展RouteBuilder,我们需要调用Camel的restConfiguration函数来配置我们的应用程序。

@Component
class RestApi extends RouteBuilder {

@Override
public void configure() {
        restConfiguration()
            .contextPath("/javadevjournal")
            .apiContextPath("/api-doc")
            .apiProperty("api.title", "JAVA DEV JOURNAL REST API")
            .apiProperty("api.version", "1.0")
            .apiProperty("cors", "true")
            .apiContextRouteId("doc-api")
            .port(env.getProperty("server.port", "8080"))
            .bindingMode(RestBindingMode.json);

        rest("/products").description("Details of products")
            .get("/").description("List of all products")
            .route().routeId("products-api")
            .bean(ProductService.class, "findAll")
            .endRest()
            .get("discounts/{id}").description("Discount of a product")
            .route().routeId("discount-api")
            .bean(DiscountService.class, "findDiscount(${header.id})");
    }
}

我们将contextPath设置为javadevjournal,API上下文路径设置为api-doc,用于Swagger。绑定模式默认是关闭的。由于我们在pom.xml中添加了json-jackson,我们可以使用json绑定格式。在我们配置的第二部分,我们定义了/products端点并返回Productservice.findAll的结果。另外,我们用/discounts/{id}扩展/products端点,并用从查询中获取的id调用Discountservice.findDiscount函数。{header}指的是之前简单语言中提到的{body}占位符传入的输入。

如果你访问http://localhost:8080/javadevjournal/api-doc,你会得到Swagger响应。点击http://localhost:8080/javadevjournal/products,你会得到。

[
    {
        "id": 1,
        "name": "Book",
        "price": 25,
        "discounted": 4
    },
    {
        "id": 2,
        "name": "Watch",
        "price": 100,
        "discounted": 73
    },
    {
        "id": 3,
        "name": "Shoes",
        "price": 40,
        "discounted": 4
    }
]

同样地,访问http://localhost:8080/javadevjournal/products/discounts/1,你会得到

{
    "id": 1,
    "amount": 92,
    "product": {
        "id": 3,
        "name": "Shoes",
        "price": 40,
        "discounted": 4
    }
}

###总结

在这篇文章中,我们看到了如何将Apache Camel与Spring Boot整合在一起.我们简要介绍了什么是Apache Camel,如何利用实际场景将其与Spring Boot整合在一起。这个应用程序的源代码可以在Github上找到。

相关文章