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)查詢快取的內容。 - 沒有查到快取,就呼叫目標方法。將目標方法返回的結果放進快取中。
- 如果查到快取,就從快取中獲取,不執行方法。
- 方法執行之前,先查詢Cache快取元件,按照
-
@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));
-