2025/06/30

Spring Boot 3 Artemis

Spring Boot 支援

  • JMS

    Java message Service,支援 JmsTemplate

  • AMQP

    Advanced Message Queue Protocol

    為 RabbitMQ 提供 RabbitTemplate

  • Apache Kafka

    KafkaTemplate

  • STOMP

    Simple Text-Oriented Message Protocol

org.springframework.boot.autoconfigure.AutoConfiguration.imports

自動設定類別

  • JmsAutoConfiguration

  • ArtemisAutoConfiguration

  • RabbitAutoConfiguration

  • KafkaAutoConfiguration


ActiveMQ

JMS 主流 ActiveMQ, RocketMQ

JMS 有兩個版本

  • JMS 1.1

  • JMS 2.0

ActiveMQ 有兩個主要版本

  • ActiveMQ Classic

    ActiveMQ 5.x 以前

    JMS 1.1

  • ActiveMQ Artemis

    ActiveMQ 6.x+

    JMS 2.0


下載 apache-artemis-2.38.0-bin.tar.gz

tar zxvf apache-artemis-2.38.0-bin.tar.gz
mkdir -p /root/download/artemis/data/
apache-artemis-2.38.0/bin/artemis create mybroker

建立 broker

# ../apache-artemis-2.38.0/bin/artemis create mybroker
Creating ActiveMQ Artemis instance at: /root/download/artemis/data/mybroker

--user:
What is the default username?
admin

--password: is mandatory with this configuration:
What is the default password?


--allow-anonymous | --require-login:
Allow anonymous access?, valid values are Y, N, True, False
n

Auto tuning journal ...
done! Your system can make 7.58 writes per millisecond, your journal-buffer-timeout will be 132000

You can now start the broker by executing:

   "/root/download/artemis/data/mybroker/bin/artemis" run

Or you can run the broker in the background using:

   "/root/download/artemis/data/mybroker/bin/artemis-service" start

修改 /root/download/artemis/data/mybroker/etc/bootstrap.xml 的 artemis uri,由 localhost 改為 0.0.0.0

   <web path="web" rootRedirectLocation="console">
       <binding name="artemis" uri="http://0.0.0.0:8161">
           <app name="branding" url="activemq-branding" war="activemq-branding.war"/>
           <app name="plugin" url="artemis-plugin" war="artemis-plugin.war"/>
           <app name="console" url="console" war="console.war"/>
       </binding>
   </web>

啟動後,可瀏覽 web console http://192.168.1.89:8161/


pom.xml 增加 spring-boot-starter-artemis

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-artemis</artifactId>
        </dependency>

org.springframework.boot.autoconfigure.AutoConfiguration.imports

自動設定類別

  • JmsAutoConfiguration

  • JndiConnectionFactoryAutoConfiguration

  • ArtemisAutoConfiguration

只要設定 spring.jms.*spring.artemis.*

spring:
  jms:
    cache:
      enabled: true
      session-cache-size: 5 # default: 1
  artemis:
    mode: native
    broker-url: tcp://localhost:61616
    user: admin
    password: password

也可以加上 pool

spring:
  jms:
    cache:
      enabled: true
      session-cache-size: 5 # default: 1
  artemis:
    mode: native
    broker-url: tcp://localhost:61616
    user: admin
    password: password
    pool:
      enabled: true # default: false
      max-connections: 50 # default: 1
      idle-timeout: 5s # default: 30s
      max-sessions-per-connection: 100 # default: 500

還須要加上 library

        <dependency>
            <groupId>org.messaginghub</groupId>
            <artifactId>pooled-jms</artifactId>
        </dependency>

補上 web library

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

MsgController.java

package com.test.artemis;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@Slf4j
public class MsgController {

    private final JmsTemplate jmsTemplate;

    /**
     * send message
     * @param msg
     * @return
     */
    @RequestMapping("/send")
    public String sendMsg(@RequestParam("msg") String msg) {
        jmsTemplate.convertAndSend("test-queue", msg);
        return "message is sent";
    }

    /**
     * receive message
     * @param msg
     */
    @JmsListener(destination = "test-queue")
    public void receiveMsg(String msg) {
        log.info("receive ActiveMQ message:{}", msg);
    }

}

測試網址

http://localhost:8080/send?msg=test1234

console 可看到

receive ActiveMQ message:test1234

2025/06/23

Spring Boot 3 amqp

RabbitMQ 重要元件有

  • ConnectionFactory

  • Channel: 發送訊息

  • Exchange: 交換器,分發訊息

  • Queue: 儲存 producer 發送的訊息

  • RoutingKey: exchange 把 producer 的訊息寫入不同的 queue

  • BindingKey: exchange 綁定到不同的 queue

exchange 支援的訊息模式:

  • direct

    預設 路由模式。發送訊息到指定的 Routing Key,寫入不同的 queue

  • fanout

    廣播模式。將訊息送到綁定的所有 queues 裡面。性能最好,最常用

  • headers

    匹配模式。根據訊息的 header 跟 queue 的參數匹配。性能差,少用

  • topic

    匹配模式。可使用 * 匹配


rabbitmq cli 設定新增 user

rabbitmqctl add_user admin password
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
rabbitmqctl set_user_tags admin administrator
rabbitmqctl delete_user guest
rabbitmqctl list_users

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

org.springframework.boot.autoconfigure.AutoConfiguration.imports 註冊

RabbitAutoConfiguration。參數綁定類別為 RabbitProperties。參數為 spring.rabbitmq.*

application.yml

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: password

也可以設定為 uri 形式

spring:
  rabbitmq:
    addresses: "amqp://admin:password@localhost"

MsgController.java

package com.test.amqp;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@Slf4j
public class MsgController {

    private final RabbitTemplate rabbitTemplate;

    /**
     * send message: Direct mode
     * @param msg
     * @return
     */
    @RequestMapping("/send")
    public String sendMsg(@RequestParam("msg") String msg) {
        rabbitTemplate.convertAndSend("test-direct-exchange",
                "test-direct-routing-key", msg);
        return "message sent";
    }

    /**
     * receive message: Direct Mode
     * @param msg
     */
    @RabbitListener(queues = "test-direct-queue")
    public void receiveMsg(String msg) {
        log.info("message received:{}", msg);
    }

}

RabbitMQ 無法自動產生 queue, exchange。除了能在 web console 設定,也可以透過程式設定

package com.test.amqp;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    /**
     * create queue: Direct Mode
     * @return
     */
    @Bean
    public Queue testDirectQueue() {
        return new Queue("test-direct-queue");
    }

    /**
     * create exchange: Direct Mode
     * @return
     */
    @Bean
    public DirectExchange TestDirectExchange() {
        return new DirectExchange("test-direct-exchange");
    }

    /**
     * create routing key: Direct Mode
     * @param testDirectQueue
     * @return
     */
    @Bean
    public Binding testDirectBinding(Queue testDirectQueue) {
        return BindingBuilder.bind(testDirectQueue)
                .to(TestDirectExchange()).with("test-direct-routing-key");
    }

}

測試

http://localhost:8080/send?msg=test1234

2025/06/09

Spring Boot 3 09 Cache

spring 提供對 cache 的 API 介面

  • org.springframework.cache.Cache

  • org.springframework.cache.CacheManager

如果 application 沒有註冊 CacheManager 或 CacheResolver,spring 會依照以下順序檢測 cache component

Generic -> JCache (JSR-107) (EhCache3, Hazelcast, Infinispan..) -> Hazelcast -> Infinispan -> Couchbase -> Redis -> Caffeine -> Cache2k -> Simple

org.springframework.boot.autoconfigure.AutoConfiguration.imports 有註冊自動設定類別

自動設定類別為 CacheAutoConfiguration,參數綁定類別為 CacheProperties

設定參數 spring.cache.*

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

                <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

application.yml

spring:
  cache:
    type: none
    # none 代表禁用 cache
    # type: redis

type 可設定為 COUCHBASE/GENERIC/REDIS/HAZELCAST/CACHE2K/CAFFEINE/JCACHE/INFINISPAN/NONE/SIMPLE


預設簡單 cache

如果沒有設定任何 cache 元件,就是使用 Simple,也就是 thread-safe 的 ConcurrentHashMap

pom.xml 加上 web

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

application 要加上 @EnableCaching

@EnableCaching
@SpringBootApplication
public class CacheApplication {

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

}

先建立一個兩數相乘的 cache service

CacheService.java

package com.test.cache;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class CacheService {

    @Cacheable("calc")
    public int multiply(int a, int b) {
        int c = a * b;
        log.info("{} * {} = {}", a, b, c);
        return c;
    }

}

@Cacheable 代表該 method 使用 cache,這是用 AOP 的方法做的

一個測試用的 web service

package com.test.cache;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class CacheController {

    private final CacheService cacheService;

    @RequestMapping("/multiply")
    public int multiply(@RequestParam("a") int a,
                        @RequestParam("b") int b) {
        return cacheService.multiply(a, b);
    }

}

測試網址 http://localhost:8080/multiply?a=2&b=3

重複多測試幾次,console log 上面都還是只有一行 log。代表重複的參數,會直接從 cache 取得結果,不會進入 service

com.test.cache.CacheService              : 2 * 3 = 6

Redis Cache

pom.xml 加上 redis

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

application.yml

spring:
  data:
    redis:
      host: localhost
      port: 6379
      database: 0
      password: password

redis-cli

# redis-cli -a password
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> keys *
1) "calc::SimpleKey [2, 3]"
127.0.0.1:6379> get "calc::SimpleKey [2, 3]"
"\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x06"

可在建立 cache 名稱,time-to-live 代表只存放 10s

spring:
  data:
    redis:
      host: localhost
      port: 6379
      database: 0
      password: password
  cache:
    type: redis
    cache-names: "calc,test"
    redis:
      time-to-live: "10s"

針對不同 cache 設定不同規則,可透過 RedisCacheManagerBuilderCustomizer

package com.test.cache;

import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;

import java.time.Duration;

@Configuration
public class CacheConfiguration {

    /**
     * 比 application.yml 的設定內容優先權高
     * @return
     */
    @Bean
    public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("calc", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofSeconds(5)))
                .withCacheConfiguration("test", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));

    }

}

網址 http://localhost:8080/multiply?a=2&b=3

calc, test 兩個 redis cache 都有資料

# redis-cli -a password -n test
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> keys *
1) "calc::SimpleKey [2, 3]"

# redis-cli -a password -n calc
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> keys *
1) "calc::SimpleKey [2, 3]"