1. 程式人生 > 實用技巧 >SpringBoot入門(五)——快取、訊息

SpringBoot入門(五)——快取、訊息

iwehdio的部落格園:https://www.cnblogs.com/iwehdio/

1、快取

  • JSR-107:定義了五個核心介面(CachingProvider、CacheManager、Cache、Entry、Expiry),用於操作快取。

  • Spring快取抽象:定義了Cache和CacheManager介面來統一不同的快取技術。

    • CacheManager:快取管理器,管理各種Cache元件。
    • Cache:快取介面,定義快取操作,實現有RedisCache等。
    • @Cacheable註解:針對方法配置,能夠根據方法的請求引數(預設作為key)對方法返回值進行快取。如果有快取就不再呼叫方法,而從快取中獲取。
    • @CacheEvict註解:執行該方法後清空快取。
    • @CachePut註解:方法總是會被呼叫,而且呼叫的結果被更新到快取中。
    • @EnableCaching註解:開啟基於註解的快取。
    • KeyGenerator:快取資料時key的生成策略。
    • serialize:快取資料時value序列化策略。
  • 建立入門工程:

    • 建立工程和資料庫檔案.

    • 整合Mybatis。

      • 配置檔案:

        spring.datasource.url=jdbc:mysql:///jpa
        spring.datasource.username=root
        spring.datasource.password=root
        spring.datasource.driver-class-name=com.mysql.jdbc.Driver
        
        mybatis.configuration.map-underscore-to-camel-case=true
        
      • 建立Java Bean。

      • 建立Mapper,並在主程式中用MapperScan指定。

        @Mapper
        public interface EmployeeMapper {
        
            @Select("select * from employee where id=#{id}")
            public Employee getEmpById(Integer id);
        
            @Update("update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}")
            public void updateEmp(Employee employee);
        
            @Delete("delete employee where id=#{id}")
            public void deleteEmpById(Integer id);
        
            @Insert("insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})")
            public void insertEmpById(Employee employee);
        }
        
      • 建立service和controller:

        @Service
        public class EmployeeService {
            @Autowired
            EmployeeMapper employeeMapper;
        
            public Employee getEmp(Integer id) {
                return employeeMapper.getEmpById(id);
            }
        }
        
        @RestController
        public class EmployeeController {
            @Autowired
            EmployeeService employeeService;
        
            @GetMapping("/emp/{id}")
            public Employee getEmp(@PathVariable("id") Integer id){
                return employeeService.getEmp(id);
            }
        }
        
  • 使用快取:

    • 開啟基於註解的快取,在主程式上標註。

      @SpringBootApplication
      @MapperScan("cn.iwehdio.demo.mapper")
      @EnableCaching
      public class DemoApplication {
          public static void main(String[] args) {
              SpringApplication.run(DemoApplication.class, args);
          }
      }
      
    • @Cacheable註解:如加在Service層中的查詢方法上。

      • 先檢視快取,根據快取中有沒有確定是否呼叫方法。
      • 註解的屬性:
        • value/cacheNames:指定快取元件的名字,可以指定多個。
        • key:快取資料使用的鍵。預設使用方法引數的值。支援SpEL表示式。
        • keyGenerator:key的生成器,可以指定生成器。與key兩個屬性二選一。
        • cacheManager:快取管理器元件。
        • cacheResolver:快取解析器,與cacheManager二選一。
        • condition:指定符合條件的情況下才快取,支援SpEL表示式。
        • unless:指定條件為true時不快取,可以獲取結果(#result)進行判斷。
        • sync:是否使用非同步模式。
      • 執行原理:
        • 自動配置類:CacheAutoConfiguration。
        • 各種快取的配置類,包括各種快取中介軟體的配置。
        • 預設生效的配置類:SimpleCacheConfiguration。
        • 在容器中註冊了一個CacheManager:ConcurrentMapCacheManager。
        • 可以獲取和建立ConcurrentMapCache型別的快取元件,將資料儲存在ConcurrentMap。
      • 執行流程:
        • 方法執行之前,先查詢Cache快取元件,按照@Cacheable中的cacheNames指定名字獲取。也就是cacheManager(ConcurrentMapCacheManager)獲取cache(ConcurrentMapCache)。第一次獲取快取cache,如果沒有會自動建立。
        • 去cache中按照@Cacheable中的key(或自動生成,預設使用SimpleKeyGenerator)查詢快取的內容。
        • 沒有查到快取,就呼叫目標方法。將目標方法返回的結果放進快取中。
        • 如果查到快取,就從快取中獲取,不執行方法。
    • @CachePut註解:加在Service層中的更新方法上。

      • 先呼叫方法,獲取到返回值後存入快取。

      • 如果想要查詢和更新共用一個快取,需要指定key為相同。

        @Service
        public class EmployeeService {
            @Autowired
            EmployeeMapper employeeMapper;
            @Cacheable(cacheNames = "emp", key ="#id")
            public Employee getEmp(Integer id) {
                System.out.println("查詢" + id + "號員工");
                return employeeMapper.getEmpById(id);
            }
            @CachePut(cacheNames = "emp", key ="#employee.id")
            public Employee updateEmp(Employee employee) {
                System.out.println("修改" + employee);
                employeeMapper.updateEmp(employee);
                return employee;
            }
        }
        
    • @CacheEvict註解:加在Service層中的刪除方法上。

      • 清除指定的key的快取。
      • 屬性:
        • allEntries:為true時,會清除所有快取。
        • beforeInvocation:快取清除是否在方法之前執行。預設在方法執行之後執行。
    • @Caching註解:定義複雜的快取規則,相當於以上三個註解的組合。

      • 使用格式:

        @Caching(
            cacheable = {
                @Cacheable
            },
            put = {
                @CachePut
            },
            evict = {
                @CacheEvict
            }
        )
        
    • @CacheConfig註解:加在Service層的類上。

      • 指定這個類中相關快取註解的屬性。
  • 整合redis環境:

    • 引入redis的starter:spring-boot-starter-data-redis。

    • 配置檔案:

      spring.redis.host=127.0.0.1
      
    • 引入redis後,RedisAutoConfiguration自動配置類就生效了。提供了模板物件:

      @Autowired  //操作k-v都是字串的
      StringRedisTemplate stringRedisTemplate;
      @Autowired  //操作k-v都是物件的
      RedisTemplate redisTemplate;
      
    • 操作redis資料的方法:

      //分別操作字串、列表、集合、雜湊和有序集合
      stringRedisTemplate.opsForValue().
      stringRedisTemplate.opsForList().
      stringRedisTemplate.opsForSet().
      stringRedisTemplate.opsForHash().
      stringRedisTemplate.opsForZSet().
      
      //redisTemplate中也有類似方法
      
      //儲存和獲取方法
      stringRedisTemplate.opsForValue().append("message", "hello");
      stringRedisTemplate.opsForValue().get("message");
      stringRedisTemplate.opsForValue().leftPush("mylist","1");
      
      //儲存序列化物件(實體類已實現序列化介面)
      redisTemplate.opsForValue().set("emp", emp);
      
    • 改變預設序列化規則,自動序列化為JSON後儲存。編寫自動配置類,設定預設序列化機制為轉化為JSON(參考RedisAutoConfiguration下的redisTemplate方法):

      @Configuration
      public class ResdisJsonConfiguration {
          @Bean
          public RedisTemplate<Object, Employee> EmpRedisTemplate(
                  RedisConnectionFactory redisConnectionFactory)
                  throws UnknownHostException {
              RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
              template.setConnectionFactory(redisConnectionFactory);
              Jackson2JsonRedisSerializer<Employee> jsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
              template.setDefaultSerializer(jsonRedisSerializer);
              return template;
          }
      }
      
      • 測試:

        @Autowired
        RedisTemplate<Object,Employee> empRedisTemplate;
        @Test
        public void test01() {
            Employee empById = employeeMapper.getEmpById(1);
            empRedisTemplate.opsForValue().set("emp-01",empById);
        }
        
    • 執行原理:

      • redis在容器中註冊了RedisCacheManager,原來的SimpleCacheManager(檢測到容器中已經有CacheManager)不再存在。

      • RedisCacheManager建立RedisCache元件,操作redis使用的是RedisTemplate,預設使用JDK序列化機制。

      • 如果要序列化為JSON存入redis,需要自定義RedisCacheManager。此時原生RedisCacheManager不會建立。在配置類中新增(參考RedisCacheConfiguration.java):

        @Bean
        public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate) {
            RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
            cacheManager.setUsePrefix(true);
            return cacheManager;
        }
        
      • 但是這樣會導致所有物件從redis存入或讀取JSON都是以Employee物件為規則,讀取其他物件時會出錯。

      • 所以需要針對不同物件配置不同的RedisCacheManager和RedisTemplate。在使用時,在註解的屬性中指定不同的cacheManager屬性。

      • 多個CacheManager時,需要有一個作為主CacheManager,在其配置上加@Primary註解。

    • 編碼的方式存入快取:

      //自動注入快取管理器
      @Autowired
      @Qualifier("employeeCacheManager")
      RedisCacheManager employeeCacheManager;
      
      //根據id獲取快取
      Cache emp = employeeCacheManager.getCache("emp");
      //操作快取
      emp.put("emp:1", employee);
      

2、訊息

  • 在應用中,可通過訊息服務中介軟體來提升系統非同步通訊、擴充套件解耦能力。

  • 訊息服務中的兩個重要概念:

  • 訊息代理。

  • 目的地。

  • 當訊息傳送者傳送訊息以後,將由訊息代理接管,訊息代理保證訊息傳遞到指定目的地。

  • 訊息佇列主要有兩種形式的目的地:

    • 佇列:點對點訊息通訊。
    • 主題:釋出/訂閱訊息通訊。
  • JMS-Java訊息服務:

    • 基於JVM訊息代理的規範。ActiveMQ、HornetMQ是JMS的實現。
  • AMQP:

    • 高階訊息佇列協議,相容JMS。是網路線級協議,跨平臺跨語言的。RabbitMQ是AMQP的實現。
    • 提供了五種訊息模型。
  • RabbitMQ-AMQP的開源實現:

    • 核心概念:

      • 訊息:由訊息頭和訊息體組成。訊息頭由一系列可選熟悉組成,主要包括路由鍵routing key。
      • Publisher:訊息的生產者,也是一個想交換器釋出訊息的客戶端應用程式。
      • Exchange:交換器,用來接收生產者傳送的訊息並將這些訊息路由給伺服器中的佇列。有四種類型。
      • Queue:訊息佇列,用來儲存訊息指導傳送給消費者。是訊息的容器,也是訊息的終點。
      • Binding:繫結,用於訊息佇列和交換器之間的交換。交換器和訊息佇列是多對多的關係。
      • Connection:網路連線。
      • Channel:通道,多路複用連線中的一條獨立的雙向資料流通道。
      • Consumer:消費者,表示一個從訊息佇列中取得訊息的客戶端應用程式。
      • VirtualHost:虛擬主機,表示一批交換器、訊息佇列和相關物件。
      • Broker:表示訊息佇列伺服器實體。

    • 執行機制:

      • 訊息路由:
        • 交換器和繫結規則不同,會導致訊息被髮送到不同的佇列中。
        • Exchange型別:
          • direct:直連。訊息中的路由鍵如果和Binding中的binding key一致,交換器就將訊息發到對應的佇列中。 完全匹配、單播的點對點專屬。
          • fanout:廣播。每個發到fanout型別交換器的訊息都會分到所有繫結的佇列上去。不處理路由鍵,轉發訊息是最快的。
          • topic:模糊匹配。通過模式匹配分配訊息的路由鍵屬性。將路由鍵和繫結鍵的字元用點隔開,識別萬用字元(#匹配0個或多個單層,*匹配一個單詞)。
    • 執行環境:

      • docker下安裝:

        docker pull rabbitmq:3-management
        
      • 執行並暴露埠號(客戶端與RabbitMQ通訊的:5672;管理介面訪問Web頁面的:15672):

        docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq 0067598739d3
        
      • 連線15672埠,輸入預設使用者名稱和密碼guest,進入管理頁面。

      • 執行示例:

        • 建立交換器exchange.direct、exchange.fanout和exchange.topic。

        • 建立佇列atguigu、atguigu.news、atguigu.emps和gulixueyuan.news。

        • 建立繫結關係。

        • 測試傳送訊息,並檢視。

  • SpringBoot整合:

    • 引入starter:spring-boot-starter-amqp。

    • 自動配置原理:

      • 自動配置類RabbitAutoConfiguration。
      • 自動配置了連線工程ConnectionFactory。
      • RabbitProperties封裝了RabbitMQ的配置。
      • RabbitTemplate:用於給RabbitMQ傳送和接受訊息。
      • AmqpAdmin:RabbitMQ系統管理功能元件。建立交換器、佇列等。
    • 配置檔案:

      spring.rabbitmq.host=127.0.0.1
      spring.rabbitmq.username=guest
      spring.rabbitmq.password=guest
      spring.rabbitmq.port=5672
      spring.rabbitmq.virtual-host=/
      
    • 測試:

      • 點對點單播訊息(單播、廣播還是模糊匹配,只需要更改指定的交換器):

        @Autowired
        RabbitTemplate rabbitTemplate;
        //自己構造訊息體內容和訊息頭
        rabbitTemplate.send(exchange,routeKey,message);
        //傳入要傳送的物件,自動序列化
        rabbitTemplate.convertAndSend(exchange,routeKey,object);
        
        Map<String,Object> map = new HashMap<>();
        map.put("msg","第一個訊息");
        rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",map);
        
      • 接收訊息:

        Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
        System.out.println(o.getClass());
        
    • 訊息序列化為JSON(自定義MessageConverter,參考RabbitTemplate中使用的):

      @Configuration
      public class MyAmqpConfiguration {
          @Bean
          public MessageConverter messageConverter() {
              return new Jackson2JsonMessageConverter();
          }
      }
      
  • @RabbitListener註解:

    • 需要開啟基於註解的RabbitMQ:@EnableRabbit

    • queue屬性:監聽指定的訊息佇列中的內容,作為方法的輸入引數。只要有訊息就會被接收。

      @Service
      public class rabbitService {
          @RabbitListener(queues = "atguigu.news")
          public void receive(Object o){
              System.out.println(o);
          }
      }
      
    • 也可以獲取message訊息物件,獲取訊息頭message.getProperties/獲取訊息體message.getBody。

  • AmqpAdmin建立好刪除訊息佇列、交換器和繫結規則:

    • 建立交換器:

      @Autowired
      AmqpAdmin amqpAdmin;
      
      amqpAdmin.declareExchange(new DirectExchange("amqpAdmin.exchange"));
      
    • 建立佇列:

      amqpAdmin.declareQueue(new Queue("amqpAdmin.queue",true));
      
    • 繫結關係:

      //繫結佇列到交換器,並且指定
      amqpAdmin.declareBinding(new Binding("amqpAdmin.queue", Binding.DestinationType.QUEUE,"amqpAdmin.exchange","amqp.hh",null));
      

iwehdio的部落格園:https://www.cnblogs.com/iwehdio/