spring-amqp.md 467.9 KB
Newer Older
dallascao's avatar
dallascao 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 6270 6271 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477 6478 6479 6480 6481 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 6497 6498 6499 6500 6501 6502 6503 6504 6505 6506 6507 6508 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 6554 6555 6556 6557 6558 6559 6560 6561 6562 6563 6564 6565 6566 6567 6568 6569 6570 6571 6572 6573 6574 6575 6576 6577 6578 6579 6580 6581 6582 6583 6584 6585 6586 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 6769 6770 6771 6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 6784 6785 6786 6787 6788 6789 6790 6791 6792 6793 6794 6795 6796 6797 6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819 6820 6821 6822 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964
# Spring AMQP

## [](#preface)1。前言

Spring AMQP 项目将核心 Spring 概念应用于基于 AMQP 的消息传递解决方案的开发。我们提供了一个“模板”,作为发送和接收消息的高级抽象。我们还为消息驱动的 POJO 提供支持。这些库促进了 AMQP 资源的管理,同时促进了依赖注入和声明式配置的使用。在所有这些情况下,你都可以看到与 Spring 框架中的 JMS 支持的相似之处。有关其他项目相关信息,请访问 Spring AMQP 项目[homepage](https://projects.spring.io/spring-amqp/)

## [](#whats-new)2。最新更新

### [](#changes-in-2-4-since-2-3)2.1。2.4 自 2.3 以来的变化

本部分描述了版本 2.4 和版本 2.4 之间的更改。有关以前版本的更改,请参见[变更历史](#change-history)

#### [](#rabbitlistener-changes)2.1.1。`@RabbitListener`变化

`MessageProperties`现在可用于参数匹配。有关更多信息,请参见[带注释的端点方法签名](#async-annotation-driven-enable-signature)

#### [](#rabbitadmin-changes)2.1.2。`RabbitAdmin`变化

一个新的属性`recoverManualDeclarations`允许恢复手动声明的队列/交换/绑定。有关更多信息,请参见[恢复自动删除声明](#declarable-recovery)

#### [](#remoting-support)2.1.3。远程支持

使用 Spring Framework 的 RMI 支持的远程支持已被弃用,并将在 3.0 中删除。有关更多信息,请参见[Spring Remoting with AMQP](#remoting)

## [](#introduction)3。导言

参考文档的第一部分是对 Spring AMQP 和底层概念的高级概述。它包括一些代码片段,可以让你尽快启动和运行。

### [](#quick-tour)3.1。不耐烦人士的快速之旅

#### [](#introduction-2)3.1.1。导言

这是从 AMQP 开始的五分钟之旅。

先决条件:安装并运行 RabbitMQ 代理([https://www.rabbitmq.com/download.html](https://www.rabbitmq.com/download.html))。然后获取 Spring-Rabbit JAR 及其所有依赖项——这样做的最简单方法是在构建工具中声明一个依赖项。例如,对于 Maven,你可以执行类似于以下内容的操作:

```
<dependency>
  <groupId>org.springframework.amqp</groupId>
  <artifactId>spring-rabbit</artifactId>
  <version>2.4.2</version>
</dependency>
```

对于 Gradle,你可以做以下类似的事情:

```
compile 'org.springframework.amqp:spring-rabbit:2.4.2'
```

##### [](#compatibility)兼容性

Spring Framework 版本的最小依赖关系是 5.2.0。

最小`amqp-client`Java 客户端库版本是 5.7.0。

##### [](#very-very-quick)非常,非常快

这一节提供了最快的介绍。

首先,添加以下`import`语句,以使本节后面的示例有效:

```
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
```

下面的示例使用普通的、命令式的 Java 来发送和接收消息:

```
ConnectionFactory connectionFactory = new CachingConnectionFactory();
AmqpAdmin admin = new RabbitAdmin(connectionFactory);
admin.declareQueue(new Queue("myqueue"));
AmqpTemplate template = new RabbitTemplate(connectionFactory);
template.convertAndSend("myqueue", "foo");
String foo = (String) template.receiveAndConvert("myqueue");
```

请注意,在本机 Java Rabbit 客户机中也有一个`ConnectionFactory`。我们在前面的代码中使用了 Spring 抽象。它缓存通道(以及可选的连接)以供重用。我们依赖于代理中的默认交换(因为在 SEND 中没有指定),以及所有队列的默认绑定到默认交换的默认名称(因此,我们可以在 SEND 中使用队列名称作为路由键)。这些行为是在 AMQP 规范中定义的。

##### 带有 XML 配置的[](#with-xml-configuration)

下面的示例与前面的示例相同,但将资源配置具体化为 XML:

```
ApplicationContext context =
    new GenericXmlApplicationContext("classpath:/rabbit-context.xml");
AmqpTemplate template = context.getBean(AmqpTemplate.class);
template.convertAndSend("myqueue", "foo");
String foo = (String) template.receiveAndConvert("myqueue");
```

```
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/rabbit
           https://www.springframework.org/schema/rabbit/spring-rabbit.xsd
           http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <rabbit:connection-factory id="connectionFactory"/>

    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>

    <rabbit:admin connection-factory="connectionFactory"/>

    <rabbit:queue name="myqueue"/>

</beans>
```

默认情况下,`<rabbit:admin/>`声明会自动查找类型为`Queue``Exchange``Binding`的 bean,并代表用户向代理声明它们。因此,你不需要在简单的 Java 驱动程序中显式地使用这个 Bean。有很多选项可以配置 XML 模式中组件的属性。你可以使用 XML 编辑器的自动完成功能来探索它们并查看它们的文档。

##### 使用 Java 配置的[](#with-java-configuration)

下面的示例重复了与前面的示例相同的示例,但使用了在 Java 中定义的外部配置:

```
ApplicationContext context =
    new AnnotationConfigApplicationContext(RabbitConfiguration.class);
AmqpTemplate template = context.getBean(AmqpTemplate.class);
template.convertAndSend("myqueue", "foo");
String foo = (String) template.receiveAndConvert("myqueue");

........

@Configuration
public class RabbitConfiguration {

    @Bean
    public CachingConnectionFactory connectionFactory() {
        return new CachingConnectionFactory("localhost");
    }

    @Bean
    public RabbitAdmin amqpAdmin() {
        return new RabbitAdmin(connectionFactory());
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        return new RabbitTemplate(connectionFactory());
    }

    @Bean
    public Queue myQueue() {
       return new Queue("myqueue");
    }
}
```

##### [](#with-spring-boot-auto-configuration-and-an-async-pojo-listener)具有 Spring 引导自动配置和异步 POJO 侦听器

Spring 引导会自动配置基础设施 bean,如下例所示:

```
@SpringBootApplication
public class Application {

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

    @Bean
    public ApplicationRunner runner(AmqpTemplate template) {
        return args -> template.convertAndSend("myqueue", "foo");
    }

    @Bean
    public Queue myQueue() {
        return new Queue("myqueue");
    }

    @RabbitListener(queues = "myqueue")
    public void listen(String in) {
        System.out.println(in);
    }

}
```

## [](#reference)4。参考文献

参考文档的这一部分详细介绍了构成 Spring AMQP 的各个组件。[主要章节](#amqp)涵盖了开发 AMQP 应用程序的核心类。这一部分还包括关于[示例应用程序](#sample-apps)的一章。

### [](#amqp)4.1。使用 Spring AMQP

本章探讨了接口和类,它们是使用 Spring AMQP 开发应用程序所必需的组件。

#### [](#amqp-abstractions)4.1.1。AMQP 抽象

Spring AMQP 由两个模块组成(每个模块由分布中的 JAR 表示):和。“ Spring-AMQP”模块包含`org.springframework.amqp.core`包。在这个包中,你可以找到代表核心 AMQP“模型”的类。我们的目的是提供不依赖于任何特定 AMQP 代理实现或客户库的通用抽象。最终用户代码可以在供应商的实现中更具可移植性,因为它可以仅针对抽象层进行开发。然后,这些抽象由特定于代理的模块实现,例如“ Spring-Rabbit”。目前只有一个 RabbitMQ 实现。然而,除了 RabbitMQ 之外,还使用 Apache QPID 在.NET 中验证了这些抽象。由于 AMQP 在协议级别上运行,原则上,你可以将 RabbitMQ 客户机与支持相同协议版本的任何代理一起使用,但是我们目前不测试任何其他代理。

本概述假定你已经熟悉 AMQP 规范的基础知识。如果没有,请查看[其他资源](#resources)中列出的资源。

##### [](#message)`Message`

0-9-1AMQP 规范没有定义`Message`类或接口。相反,当执行`basicPublish()`之类的操作时,内容将作为字节数组参数传递,其他属性将作为单独的参数传递。 Spring AMQP 将`Message`类定义为更通用的 AMQP 域模型表示的一部分。`Message`类的目的是将主体和属性封装在一个实例中,这样 API 就可以变得更简单。下面的示例显示了`Message`类定义:

```
public class Message {

    private final MessageProperties messageProperties;

    private final byte[] body;

    public Message(byte[] body, MessageProperties messageProperties) {
        this.body = body;
        this.messageProperties = messageProperties;
    }

    public byte[] getBody() {
        return this.body;
    }

    public MessageProperties getMessageProperties() {
        return this.messageProperties;
    }
}
```

`MessageProperties`接口定义了几个常见的属性,如“messageID”、“timestamp”、“ContentType”等。你还可以通过调用`setHeader(String key, Object value)`方法,使用用户定义的“headers”扩展这些属性。

|   |从版本`1.5.7``1.6.11``1.7.4``2.0.0`开始,如果消息体是序列化的`Serializable`Java 对象,则在执行`toString()`操作(例如在日志消息中)时,它不再被反序列化(默认情况下)。<br/>这是为了防止不安全的反序列化。<br/>默认情况下,只有`java.util``java.lang`类被反序列化。<br/>要恢复到以前的行为,你可以通过调用`Message.addAllowedListPatterns(…​)`来添加允许的类/包模式。<br/>支持简单的``**通配符,例如`com.something.`**`, *.MyClass`。<br/>不能反序列化的主体在日志消息中用`byte[<size>]`表示。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#exchange)exchange

`Exchange`接口表示一个 AMQP 交换,这是消息生成器发送到的内容。在代理的虚拟主机中,每个交换都有一个唯一的名称以及一些其他属性。下面的示例显示了`Exchange`接口:

```
public interface Exchange {

    String getName();

    String getExchangeType();

    boolean isDurable();

    boolean isAutoDelete();

    Map<String, Object> getArguments();

}
```

正如你所看到的,`Exchange`还具有由`ExchangeTypes`中定义的常量表示的“类型”。基本类型是:`direct`,`topic`,`fanout`,和`headers`。在核心包中,你可以为每种类型找到`Exchange`接口的实现。这些`Exchange`类型的行为在它们处理与队列绑定的方式方面有所不同。例如,`Direct`交换允许队列由固定的路由密钥(通常是队列的名称)绑定。`Topic`Exchange 支持使用路由模式的绑定,其中可能包括“exactly-one”和“zero-or-more”的“\*”和“#”通配符。`Fanout`Exchange 将发布到绑定到它的所有队列,而不考虑任何路由密钥。有关这些和其他交换类型的更多信息,请参见[其他资源](#resources)。

|   |AMQP 规范还要求任何代理提供没有名称的“默认”直接交换。<br/>所有声明的队列都绑定到默认的`Exchange`,并将其名称作为路由键。<br/>你可以在[`AmqpTemplate`](#AMQP-template)中了解 Spring AMQP 中默认交换的使用情况。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#queue)队列

`Queue`类表示消息使用者从其接收消息的组件。像各种`Exchange`类一样,我们的实现旨在作为这种核心 AMQP 类型的抽象表示。下面的清单显示了`Queue`类:

```
public class Queue  {

    private final String name;

    private volatile boolean durable;

    private volatile boolean exclusive;

    private volatile boolean autoDelete;

    private volatile Map<String, Object> arguments;

    /**
     * The queue is durable, non-exclusive and non auto-delete.
     *
     * @param name the name of the queue.
     */
    public Queue(String name) {
        this(name, true, false, false);
    }

    // Getters and Setters omitted for brevity

}
```

请注意,构造函数使用队列名。根据实现的不同,管理模板可以提供生成唯一命名的队列的方法。这样的队列可以作为“回复”地址或在其他**临时的**情况下很有用。因此,自动生成队列的“独占”和“自动删除”属性都将设置为“true”。

|   |有关使用名称空间支持声明队列的信息,包括队列参数,请参见[配置代理](#broker-configuration)中有关队列的部分。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#binding)绑定

考虑到生产者向交易所发送消息,而消费者从队列接收消息,将队列连接到交易所的绑定对于通过消息传递将生产者和消费者连接起来至关重要。在 Spring AMQP 中,我们定义了一个`Binding`类来表示这些连接。本节回顾了将队列绑定到交易所的基本选项。

你可以使用固定的路由密钥将队列绑定到`DirectExchange`,如下例所示:

```
new Binding(someQueue, someDirectExchange, "foo.bar");
```

可以使用路由模式将队列绑定到`TopicExchange`,如下例所示:

```
new Binding(someQueue, someTopicExchange, "foo.*");
```

可以将队列绑定到没有路由密钥的`FanoutExchange`,如下例所示:

```
new Binding(someQueue, someFanoutExchange);
```

我们还提供了`BindingBuilder`以促进“Fluent API”风格,如下例所示:

```
Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*");
```

|   |为了清楚起见,前面的示例显示了`BindingBuilder`类,但是当为“bind()”方法使用静态导入时,这种样式很好用。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------|

就其本身而言,`Binding`类的实例仅保存有关连接的数据。换句话说,它不是一个“活跃”的组件。但是,正如你将在后面的[配置代理](#broker-configuration)中看到的那样,`AmqpAdmin`类可以使用`Binding`实例来实际触发代理上的绑定操作。此外,正如你在同一部分中看到的那样,你可以在`@Configuration`类中使用 Spring 的`@Bean`注释来定义`Binding`实例。 Bean 还有一种方便的基类,它进一步简化了用于生成与 AMQP 相关的定义的方法,并识别队列、交换和绑定,以便它们在应用程序启动时都在 AMQP 代理上声明。

`AmqpTemplate`也在核心包中定义。作为实际 AMQP 消息传递所涉及的主要组件之一,它在其自己的部分中进行了详细讨论(参见[`AmqpTemplate`](#AMQP-template))。

#### [](#connections)4.1.2。连接和资源管理

尽管我们在上一节中描述的 AMQP 模型是通用的,并且适用于所有实现,但是当我们进入资源管理时,细节是特定于代理实现的。因此,在本节中,我们将重点关注仅存在于我们的“ Spring-Rabbit”模块中的代码,因为在这一点上,RabbitMQ 是唯一受支持的实现。

管理到 RabbitMQ 代理的连接的中心组件是`ConnectionFactory`接口。`ConnectionFactory`实现的职责是提供`org.springframework.amqp.rabbit.connection.Connection`的实例,它是`com.rabbitmq.client.Connection`的包装器。

##### [](#choosing-factory)选择连接工厂

有三家连接工厂可供选择。

* `PooledChannelConnectionFactory`

* `ThreadChannelConnectionFactory`

* `CachingConnectionFactory`

前两个是在 2.3 版本中添加的。

对于大多数用例,应该使用`PooledChannelConnectionFactory`。如果你希望确保严格的消息排序,而不需要使用[作用域操作](#scoped-operations),则可以使用`ThreadChannelConnectionFactory`。如果你想要使用相关的发布者确认,或者如果你希望通过其`CacheMode`打开多个连接,则应该使用`CachingConnectionFactory`。

这三家工厂都支持简单的发布者确认。

在配置`RabbitTemplate`以使用[独立连接](#separate-connection)时,现在可以从版本 2.3.2 开始,将发布连接工厂配置为不同的类型。默认情况下,发布工厂是相同的类型,主工厂上设置的任何属性也会传播到发布工厂。

###### [](#pooledchannelconnectionfactory)`PooledChannelConnectionFactory`

该工厂基于 Apache 池 2 管理单个连接和两个通道池。一个池用于事务通道,另一个池用于非事务通道。池是带有默认配置的`GenericObjectPool`s;提供了一个回调来配置池;有关更多信息,请参阅 Apache 文档。

Apache `commons-pool2`jar 必须位于类路径上才能使用这个工厂。

```
@Bean
PooledChannelConnectionFactory pcf() throws Exception {
    ConnectionFactory rabbitConnectionFactory = new ConnectionFactory();
    rabbitConnectionFactory.setHost("localhost");
    PooledChannelConnectionFactory pcf = new PooledChannelConnectionFactory(rabbitConnectionFactory);
    pcf.setPoolConfigurer((pool, tx) -> {
        if (tx) {
            // configure the transactional pool
        }
        else {
            // configure the non-transactional pool
        }
    });
    return pcf;
}
```

###### [](#threadchannelconnectionfactory)`ThreadChannelConnectionFactory`

这个工厂管理一个连接和两个`ThreadLocal`s,一个用于事务通道,另一个用于非事务通道。这个工厂确保同一线程上的所有操作使用相同的通道(只要它保持打开状态)。这便于在不需要[作用域操作](#scoped-operations)的情况下进行严格的消息排序。为了避免内存泄漏,如果你的应用程序使用许多短期线程,你必须调用工厂的`closeThreadChannel()`来释放通道资源。从版本 2.3.7 开始,一个线程可以将其通道传输到另一个线程。有关更多信息,请参见[多线程环境中的严格消息排序](#multi-strict)。

###### [](#cachingconnectionfactory)`CachingConnectionFactory`

提供的第三个实现是`CachingConnectionFactory`,默认情况下,它建立一个可以由应用程序共享的单个连接代理。共享连接是可能的,因为与 AMQP 进行消息传递的“工作单元”实际上是一个“通道”(在某些方面,这类似于 JMS 中的连接与会话之间的关系)。连接实例提供了`createChannel`方法。`CachingConnectionFactory`实现支持对这些通道的缓存,并且它根据通道是否是事务性的,为它们维护单独的缓存。在创建`CachingConnectionFactory`实例时,可以通过构造函数提供“hostname”。你还应该提供“用户名”和“密码”属性。要配置通道缓存的大小(默认为 25),可以调用`setChannelCacheSize()`方法。

从版本 1.3 开始,你可以将`CachingConnectionFactory`配置为缓存连接以及仅缓存通道。在这种情况下,对`createConnection()`的每次调用都会创建一个新的连接(或从缓存中检索一个空闲的连接)。关闭连接将其返回到缓存(如果未达到缓存大小)。在这种连接上创建的通道也会被缓存。在某些环境中,使用单独的连接可能是有用的,例如从 HA 集群消费,与负载均衡器结合,以连接到不同的集群成员,以及其他环境。要缓存连接,请将`cacheMode`设置为`CacheMode.CONNECTION`。

|   |这并不限制连接的数量。<br/>相反,它指定了允许多少空闲的打开连接。|
|---|-------------------------------------------------------------------------------------------------------------------|

从版本 1.5.5 开始,提供了一个名为`connectionLimit`的新属性。设置此属性时,将限制允许的连接总数。设置时,如果达到限制,则使用`channelCheckoutTimeLimit`来等待连接变为空闲。如果超过了时间,则抛出一个`AmqpTimeoutException`。

|   |当缓存模式`CONNECTION`时,不支持自动声明队列和其他<br/>(参见[交换、队列和绑定的自动声明](#automatic-declaration))。<br/><br/>此外,在编写本文时,默认情况下,`amqp-client`库为每个连接创建一个固定的线程池(默认大小:`Runtime.getRuntime().availableProcessors() * 2`线程)。<br/>当使用大量连接时,你应该考虑在`CachingConnectionFactory`上设置一个自定义的`executor`。<br/>然后,相同的执行器可以被所有的连接使用,并且它的线程可以被共享。<br/>执行器的线程池应该是无界的,或者为预期的使用进行适当的设置(通常,每个连接至少有一个线程)。<br/>如果在每个连接上创建了多个通道,池的大小会影响并发性,因此变量(或简单缓存的)线程池执行器将是最合适的。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

重要的是要理解,缓存大小(默认情况下)不是一个限制,而仅仅是可以缓存的通道数量。如果缓存大小为 10,那么实际上可以使用任意数量的通道。如果使用了超过 10 个通道,并且它们都返回到缓存中,则在缓存中使用 10 个。其余部分是封闭的。

从 1.6 版本开始,默认通道缓存大小从 1 增加到 25。在大容量、多线程的环境中,较小的缓存意味着以较高的速率创建和关闭通道。增加默认的缓存大小可以避免这种开销。你应该通过 RabbitMQ 管理 UI 监视正在使用的通道,并且如果你看到许多通道正在创建和关闭,请考虑进一步增加缓存大小。缓存仅按需增长(以满足应用程序的并发性需求),因此此更改不会影响现有的低容量应用程序。

从版本 1.4.2 开始,`CachingConnectionFactory`具有一个名为`channelCheckoutTimeout`的属性。当此属性大于零时,`channelCacheSize`将成为连接上可以创建的通道数量的限制。如果达到限制,则调用线程块,直到通道可用或达到超时为止,在这种情况下,将抛出`AmqpTimeoutException`。

|   |框架内使用的通道(例如,`RabbitTemplate`)将可靠地返回到缓存。<br/>如果你在框架外创建通道,(例如,<br/>通过直接访问连接并调用`createChannel()`),则必须可靠地(通过关闭)返回它们,可能是在`finally`块中,以避免渠道耗尽。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例展示了如何创建一个新的`connection`:

```
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("somehost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");

Connection connection = connectionFactory.createConnection();
```

在使用 XML 时,配置可能看起来像以下示例:

```
<bean id="connectionFactory"
      class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
    <constructor-arg value="somehost"/>
    <property name="username" value="guest"/>
    <property name="password" value="guest"/>
</bean>
```

|   |还有一个`SingleConnectionFactory`实现,它仅在框架的单元测试代码中可用。<br/>它比`CachingConnectionFactory`更简单,因为它不缓存通道,但由于缺乏性能和弹性,它并不打算用于简单测试之外的实际使用。<br/>如果出于某种原因需要实现自己的`ConnectionFactory`,`AbstractConnectionFactory`基类可能提供了一个很好的起点。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

使用 Rabbit 名称空间可以快速方便地创建`ConnectionFactory`,如下所示:

```
<rabbit:connection-factory id="connectionFactory"/>
```

在大多数情况下,这种方法更可取,因为框架可以为你选择最好的默认值。创建的实例是`CachingConnectionFactory`。请记住,通道的默认缓存大小是 25。如果你希望有更多的通道是 cachedm,那么可以通过设置“channelcachesize”属性来设置一个更大的值。在 XML 中,它将如下所示:

```
<bean id="connectionFactory"
      class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
    <constructor-arg value="somehost"/>
    <property name="username" value="guest"/>
    <property name="password" value="guest"/>
    <property name="channelCacheSize" value="50"/>
</bean>
```

此外,在名称空间中,你可以添加“channel-cache-size”属性,如下所示:

```
<rabbit:connection-factory
    id="connectionFactory" channel-cache-size="50"/>
```

默认的缓存模式是`CHANNEL`,但你可以将其配置为缓存连接。在下面的示例中,我们使用`connection-cache-size`:

```
<rabbit:connection-factory
    id="connectionFactory" cache-mode="CONNECTION" connection-cache-size="25"/>
```

你可以使用命名空间提供主机和端口属性,如下所示:

```
<rabbit:connection-factory
    id="connectionFactory" host="somehost" port="5672"/>
```

或者,如果在集群环境中运行,则可以使用 Addresses 属性,如下所示:

```
<rabbit:connection-factory
    id="connectionFactory" addresses="host1:5672,host2:5672" address-shuffle-mode="RANDOM"/>
```

有关`address-shuffle-mode`的信息,请参见[连接到集群](#cluster)。

下面是一个自定义线程工厂的示例,该工厂使用`rabbitmq-`为线程名称添加前缀:

```
<rabbit:connection-factory id="multiHost" virtual-host="/bar" addresses="host1:1234,host2,host3:4567"
    thread-factory="tf"
    channel-cache-size="10" username="user" password="password" />

<bean id="tf" class="org.springframework.scheduling.concurrent.CustomizableThreadFactory">
    <constructor-arg value="rabbitmq-" />
</bean>
```

##### [](#addressresolver)addresolver

从版本 2.1.15 开始,你现在可以使用`AddressResover`来解析连接地址。这将覆盖`addresses`和`host/port`属性的任何设置。

##### [](#naming-connections)命名连接

从版本 1.7 开始,为注入`AbstractionConnectionFactory`提供了一个`ConnectionNameStrategy`。生成的名称用于特定于应用程序的目标 RabbitMQ 连接的标识。如果 RabbitMQ 服务器支持该连接名,则会在管理 UI 中显示该连接名。这个值不一定是唯一的,也不能用作连接标识符——例如,在 HTTPAPI 请求中。这个值应该是人类可读的,并且是`connection_name`键下`ClientProperties`的一部分。你可以使用一个简单的 lambda,如下所示:

```
connectionFactory.setConnectionNameStrategy(connectionFactory -> "MY_CONNECTION");
```

`ConnectionFactory`参数可用于通过某种逻辑来区分目标连接名。默认情况下,`AbstractConnectionFactory`的`beanName`、表示对象的十六进制字符串和内部计数器用于生成`connection_name`。`<rabbit:connection-factory>`名称空间组件还提供了`connection-name-strategy`属性。

`SimplePropertyValueConnectionNameStrategy`的实现将连接名设置为应用程序属性。你可以将其声明为`@Bean`,并将其注入到连接工厂中,如下例所示:

```
@Bean
public SimplePropertyValueConnectionNameStrategy cns() {
    return new SimplePropertyValueConnectionNameStrategy("spring.application.name");
}

@Bean
public ConnectionFactory rabbitConnectionFactory(ConnectionNameStrategy cns) {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
    ...
    connectionFactory.setConnectionNameStrategy(cns);
    return connectionFactory;
}
```

属性必须存在于应用程序上下文的`Environment`中。

|   |当使用 Spring 引导及其自动配置的连接工厂时,只需要声明`ConnectionNameStrategy``@Bean`。<br/>引导自动检测 Bean 并将其连接到工厂。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#blocked-connections-and-resource-constraints)阻塞的连接和资源约束

与[内存闹钟](https://www.rabbitmq.com/memory.html)对应的代理可能会阻止连接以进行交互。从版本 2.0 开始,`org.springframework.amqp.rabbit.connection.Connection`可以提供`com.rabbitmq.client.BlockedListener`实例,以通知连接阻塞和未阻塞事件。此外,`AbstractConnectionFactory`分别通过其内部的`BlockedListener`实现了`ConnectionBlockedEvent`和`ConnectionUnblockedEvent`。这些允许你提供应用程序逻辑,以便对代理上的问题做出适当的反应,并(例如)采取一些纠正措施。

|   |当应用程序被配置为单个`CachingConnectionFactory`时,就像默认的 Spring 引导自动配置一样,当连接被代理阻塞时,应用程序停止工作。<br/>并且当它被代理阻塞时,它的任何客户机停止工作。<br/>如果我们在同一个应用程序中有生产者和消费者,当生产者阻塞连接时(因为代理上不再有资源了),而消费者无法释放它们(因为连接被阻塞了),我们最终可能会陷入死锁。,<br/>为了缓解这个问题,我们建议再有一个单独的`CachingConnectionFactory`实例,具有相同的选项——一个用于生产者,一个用于消费者,<br/>对于在消费者线程上执行的事务生产者,单独的`CachingConnectionFactory`是不可能的,因为它们应该重用与消费者事务关联的`Channel`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 2.0.2 开始,`RabbitTemplate`有一个配置选项,可以自动使用第二个连接工厂,除非正在使用事务。有关更多信息,请参见[使用单独的连接](#separate-connection)。发布者连接的`ConnectionNameStrategy`与主策略相同,将`.publisher`附加到调用方法的结果中。

从版本 1.7.7 开始,将提供一个`AmqpResourceNotAvailableException`,当`SimpleConnection.createChannel()`无法创建`Channel`时,将抛出该参数(例如,因为已达到`channelMax`限制,并且缓存中没有可用通道)。你可以在`RetryPolicy`中使用此异常来恢复一些后退后的操作。

##### [](#connection-factory)配置底层客户端连接工厂

`CachingConnectionFactory`使用了 Rabbit 客户机`ConnectionFactory`的实例。在`CachingConnectionFactory`上设置等效属性时,会传递许多配置属性(例如`host, port, userName, password, requestedHeartBeat, and connectionTimeout`)。要设置其他属性(例如`clientProperties`),你可以定义 Rabbit Factory 的实例,并使用`CachingConnectionFactory`的适当构造函数提供对它的引用。当使用名称空间([如前所述](#connections))时,你需要在`connection-factory`属性中提供对已配置工厂的引用。为了方便起见,提供了工厂 Bean 以协助在 Spring 应用程序上下文中配置连接工厂,如[下一节](#rabbitconnectionfactorybean-configuring-ssl)中所讨论的。

```
<rabbit:connection-factory
      id="connectionFactory" connection-factory="rabbitConnectionFactory"/>
```

|   |4.0.x 客户端默认支持自动恢复。<br/>虽然与此功能兼容, Spring AMQP 有自己的恢复机制,通常不需要客户端恢复功能。<br/>我们建议禁用`amqp-client`自动恢复,为了避免在代理可用但连接尚未恢复时获得`AutoRecoverConnectionNotCurrentlyOpenException`实例,你可能会注意到此异常,例如,当`RetryTemplate`在`RabbitTemplate`中配置了`RetryTemplate`时,<br/>由于自动恢复连接在定时器上恢复,因此可以通过使用 Spring AMQP 的恢复机制更快地恢复连接,<br/>从 1.7.1 版本开始, Spring AMQP 禁用`amqp-client`自动恢复,除非你显式地创建自己的 RabbitMQ 连接工厂并将其提供给`CachingConnectionFactory`。<br/>RabbitMQ`ConnectionFactory`由`RabbitConnectionFactoryBean`创建的实例还具有默认禁用的选项。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#rabbitconnectionfactorybean-configuring-ssl)`RabbitConnectionFactoryBean`和配置 SSL

从版本 1.4 开始,提供了一个方便的`RabbitConnectionFactoryBean`,以便通过使用依赖项注入在底层客户机连接工厂上方便地配置 SSL 属性。其他设置者将委托给底层工厂。以前,你必须以编程方式配置 SSL 选项。下面的示例展示了如何配置`RabbitConnectionFactoryBean`:

```
<rabbit:connection-factory id="rabbitConnectionFactory"
    connection-factory="clientConnectionFactory"
    host="${host}"
    port="${port}"
    virtual-host="${vhost}"
    username="${username}" password="${password}" />

<bean id="clientConnectionFactory"
        class="org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean">
    <property name="useSSL" value="true" />
    <property name="sslPropertiesLocation" value="file:/secrets/rabbitSSL.properties"/>
</bean>
```

有关配置 SSL 的信息,请参见[RabbitMQ 文档](https://www.rabbitmq.com/ssl.html)。省略`keyStore`和`trustStore`配置以在没有证书验证的情况下通过 SSL 连接。下一个示例展示了如何提供密钥和信任存储区配置。

`sslPropertiesLocation`属性是一个 Spring `Resource`指向包含以下键的属性文件:

```
keyStore=file:/secret/keycert.p12
trustStore=file:/secret/trustStore
keyStore.passPhrase=secret
trustStore.passPhrase=secret
```

`keyStore`和`truststore`是指向存储的 Spring `Resources`。通常,这个属性文件是由具有读访问权限的应用程序的操作系统保护的。

从 Spring AMQP 版本 1.5 开始,你可以直接在工厂 Bean 上设置这些属性。如果同时提供离散属性和`sslPropertiesLocation`,则后者中的属性将覆盖离散值。

|   |从版本 2.0 开始,默认情况下服务器证书是经过验证的,因为它更安全。,如果你出于某种原因希望跳过此验证,请将工厂 Bean 的`skipServerCertificateValidation`属性设置为`true`,<br/>从版本 2.1 开始,`RabbitConnectionFactoryBean`现在默认调用`enableHostnameVerification()`。<br/>要恢复到以前的行为,请将`enableHostnameVerification`属性设置为`false`。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |从版本 2.2.5 开始,工厂 Bean 默认情况下将始终使用 TLSv1.2;以前,它在某些情况下使用 v1.1,在其他情况下使用 v1.2(取决于其他属性)。<gtr="524"/>如果出于某种原因需要使用 v1.1,请设置<gtr="522"/>属性:<gtr="523"/>。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#cluster)连接到集群

要连接到群集,请在`CachingConnectionFactory`上配置`addresses`属性:

```
@Bean
public CachingConnectionFactory ccf() {
    CachingConnectionFactory ccf = new CachingConnectionFactory();
    ccf.setAddresses("host1:5672,host2:5672,host3:5672");
    return ccf;
}
```

每当建立新的连接时,底层连接工厂将尝试连接到每个主机。从版本 2.1.8 开始,通过将`addressShuffleMode`属性设置为`RANDOM`,可以使连接顺序是随机的;在创建任何新连接之前,将应用 shuffle。从版本 2.6 开始,添加了`INORDER`shuffle 模式,这意味着在创建连接后,第一个地址将被移动到末尾。如果你希望从所有节点上的所有碎片中消费,那么你可能希望在[RabbitMQ 分片插件](https://github.com/rabbitmq/rabbitmq-sharding)与`CacheMode.CONNECTION`之间使用此模式,并使用适当的并发性。

```
@Bean
public CachingConnectionFactory ccf() {
    CachingConnectionFactory ccf = new CachingConnectionFactory();
    ccf.setAddresses("host1:5672,host2:5672,host3:5672");
    ccf.setAddressShuffleMode(AddressShuffleMode.RANDOM);
    return ccf;
}
```

##### [](#routing-connection-factory)路由连接工厂

从 1.3 版本开始,引入了`AbstractRoutingConnectionFactory`。这个工厂提供了一种机制来配置几个`ConnectionFactories`的映射,并在运行时通过一些`lookupKey`确定一个目标`ConnectionFactory`。通常,实现会检查线程绑定的上下文。为了方便起见, Spring AMQP 提供了`SimpleRoutingConnectionFactory`,它从`SimpleResourceHolder`获得当前线程绑定的`lookupKey`。以下示例展示了如何在 XML 和 Java 中配置`SimpleRoutingConnectionFactory`:

```
<bean id="connectionFactory"
      class="org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory">
    <property name="targetConnectionFactories">
        <map>
            <entry key="#{connectionFactory1.virtualHost}" ref="connectionFactory1"/>
            <entry key="#{connectionFactory2.virtualHost}" ref="connectionFactory2"/>
        </map>
    </property>
</bean>

<rabbit:template id="template" connection-factory="connectionFactory" />
```

```
public class MyService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void service(String vHost, String payload) {
        SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost);
        rabbitTemplate.convertAndSend(payload);
        SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory());
    }

}
```

在使用后解除对资源的绑定是很重要的。有关更多信息,请参见[JavaDoc](https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.html)的`AbstractRoutingConnectionFactory`。

从版本 1.4 开始,`RabbitTemplate`支持 SPEL`sendConnectionFactorySelectorExpression`和`receiveConnectionFactorySelectorExpression`属性,这些属性在每个 AMQP 协议交互操作(`send`、`sendAndReceive`、`receive`或`receiveAndReply`)上进行评估,将所提供的`lookupKey`值解析为`AbstractRoutingConnectionFactory`。你可以在表达式中使用 Bean 引用,例如`@vHostResolver.getVHost(#root)`。对于`send`操作,要发送的消息是根求值对象。对于`receive`操作,`queueName`是根求值对象。

该路由算法如下:如果选择器表达式是`null`或者是被求值为`null`或者所提供的`ConnectionFactory`不是`AbstractRoutingConnectionFactory`的实例,则一切都像以前一样工作,依赖于所提供的`ConnectionFactory`实现。如果评估结果不是`null`,但是对于`lookupKey`没有目标`ConnectionFactory`,并且`AbstractRoutingConnectionFactory`配置为`lenientFallback = true`,则会发生相同的情况。在`AbstractRoutingConnectionFactory`的情况下,它确实回退到基于`determineCurrentLookupKey()`的`routing`实现。但是,如果`lenientFallback = false`,则抛出一个`IllegalStateException`。

名称空间支持还在`<rabbit:template>`组件上提供`send-connection-factory-selector-expression`和`receive-connection-factory-selector-expression`属性。

此外,从版本 1.4 开始,你可以在侦听器容器中配置路由连接工厂。在这种情况下,队列名称列表被用作查找键。例如,如果你使用`setQueueNames("thing1", "thing2")`配置容器,那么查找键是`[thing1,thing]"`(请注意,该键中没有空格)。

从 1.6.9 版本开始,你可以在侦听器容器上使用`setLookupKeyQualifier`向查找键添加限定符。例如,这样做可以监听具有相同名称但位于不同虚拟主机中的队列(在该虚拟主机中,每个虚拟主机都有一个连接工厂)。

例如,使用查找键限定符`thing1`和监听队列`thing2`的容器,可以用来注册目标连接工厂的查找键可以是`thing1[thing2]`。

|   |目标(如果提供的话,也是默认的)连接工厂必须具有相同的 Publisher 确认和返回设置。<br/>参见[发布者确认并返回](#cf-pub-conf-ret)。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#queue-affinity)队列亲和性和`LocalizedQueueConnectionFactory`

在集群中使用 HA 队列时,为了获得最佳性能,你可能希望连接到领先队列所在的物理代理。`CachingConnectionFactory`可以配置多个代理地址。这是故障转移,客户端尝试按顺序连接。`LocalizedQueueConnectionFactory`使用管理插件提供的 REST API 来确定哪个节点是队列的领头节点。然后,它创建(或从缓存检索)一个`CachingConnectionFactory`,该节点仅连接到该节点。如果连接失败,则确定新的引导节点,并由使用者连接到该节点。`LocalizedQueueConnectionFactory`配置了默认的连接工厂,以防无法确定队列的物理位置,在这种情况下,它将正常地连接到集群。

`LocalizedQueueConnectionFactory`是`RoutingConnectionFactory`,而`SimpleMessageListenerContainer`使用队列名称作为查找键,如上面[路由连接工厂](#routing-connection-factory)中讨论的那样。

|   |由于这个原因(使用队列名进行查找),只有当容器被配置为侦听单个队列时,才能使用`LocalizedQueueConnectionFactory`。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |必须在每个节点上启用 RabbitMQ 管理插件。|
|---|------------------------------------------------------------|

|   |这个连接工厂用于长寿命的连接,例如`SimpleMessageListenerContainer`使用的连接。<br/>它不用于短连接,例如使用`RabbitTemplate`,因为在建立连接之前调用 REST API 的开销很大,<br/>也用于发布操作,队列是未知的,并且消息无论如何都会发布给所有集群成员,因此查找节点的逻辑没有什么价值。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的配置示例展示了如何配置工厂:

```
@Autowired
private ConfigurationProperties props;

@Bean
public CachingConnectionFactory defaultConnectionFactory() {
    CachingConnectionFactory cf = new CachingConnectionFactory();
    cf.setAddresses(this.props.getAddresses());
    cf.setUsername(this.props.getUsername());
    cf.setPassword(this.props.getPassword());
    cf.setVirtualHost(this.props.getVirtualHost());
    return cf;
}

@Bean
public LocalizedQueueConnectionFactory queueAffinityCF(
        @Qualifier("defaultConnectionFactory") ConnectionFactory defaultCF) {
    return new LocalizedQueueConnectionFactory(defaultCF,
            StringUtils.commaDelimitedListToStringArray(this.props.getAddresses()),
            StringUtils.commaDelimitedListToStringArray(this.props.getAdminUris()),
            StringUtils.commaDelimitedListToStringArray(this.props.getNodes()),
            this.props.getVirtualHost(), this.props.getUsername(), this.props.getPassword(),
            false, null);
}
```

注意,前三个参数是`addresses`、`adminUris`和`nodes`的数组。这些位置在于,当容器试图连接到队列时,它使用 Admin API 来确定哪个节点是队列的领导者,并以与该节点相同的阵列位置连接到该地址。

##### [](#cf-pub-conf-ret)发布者确认并返回

通过将`CachingConnectionFactory`属性`publisherConfirmType`设置为`ConfirmType.CORRELATED`,并将`publisherReturns`属性设置为“true”,可以支持确认(具有相关性)和返回的消息。

当设置这些选项时,由工厂创建的`Channel`实例被包装在`PublisherCallbackChannel`中,这用于促进回调。当获得这样的信道时,客户端可以将`PublisherCallbackChannel.Listener`注册为`Channel`。`PublisherCallbackChannel`实现包含将确认或返回路由到适当侦听器的逻辑。这些特性将在下面的小节中进一步解释。

另见`simplePublisherConfirms`in[作用域操作](#scoped-operations)。

|   |有关更多的背景信息,请参见 RabbitMQ 团队的博客文章[介绍出版商确认](https://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms/)。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#connection-channel-listeners)连接和通道侦听器

连接工厂支持注册`ConnectionListener`和`ChannelListener`实现。这允许你接收关于连接和通道相关事件的通知。(`ConnectionListener`用于`RabbitAdmin`在建立连接时执行声明-有关更多信息,请参见[交换、队列和绑定的自动声明](#automatic-declaration))。下面的清单显示了`ConnectionListener`接口定义:

```
@FunctionalInterface
public interface ConnectionListener {

    void onCreate(Connection connection);

    default void onClose(Connection connection) {
    }

    default void onShutDown(ShutdownSignalException signal) {
    }

}
```

从版本 2.0 开始,`org.springframework.amqp.rabbit.connection.Connection`对象可以提供`com.rabbitmq.client.BlockedListener`实例,以通知连接阻塞和未阻塞事件。下面的示例展示了 Channellistener 接口定义:

```
@FunctionalInterface
public interface ChannelListener {

    void onCreate(Channel channel, boolean transactional);

    default void onShutDown(ShutdownSignalException signal) {
    }

}
```

关于可能需要注册`ChannelListener`的一种情况,请参见[发布是异步的——如何检测成功和失败](#publishing-is-async)。

##### [](#channel-close-logging)记录通道关闭事件

版本 1.5 引入了一种机制,使用户能够控制日志记录级别。

`CachingConnectionFactory`使用默认策略记录通道闭包,如下所示:

* 正常的通道关闭(200OK)没有记录.

* 如果由于被动队列声明失败而关闭了通道,则将在调试级别记录该通道。

* 如果一个通道由于`basic.consume`由于独占消费者条件而被拒绝而被关闭,则该通道将在 Info 级别记录。

* 所有其他记录在错误级别。

要修改此行为,可以在其`closeExceptionLogger`属性中将自定义`ConditionalExceptionLogger`插入到`CachingConnectionFactory`中。

另见[消费者活动](#consumer-events)。

##### [](#runtime-cache-properties)运行时缓存属性

从版本 1.6 开始,`CachingConnectionFactory`现在通过`getCacheProperties()`方法提供缓存统计信息。这些统计信息可用于优化缓存,以在生产中对其进行优化。例如,可以使用高水位标记来确定是否应该增加缓存大小。如果它等于缓存大小,你可能需要考虑进一步增加。下表描述了`CacheMode.CHANNEL`属性:

|                 Property                 |意义|
|------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
|      ```<br/>connectionName<br/>```      |由`ConnectionNameStrategy`生成的连接的名称。|
|     ```<br/>channelCacheSize<br/>```     |当前配置的允许空闲的最大通道。|
|        ```<br/>localPort<br/>```         |连接的本地端口(如果可用)。<br/>这可用于与 RabbitMQ 管理 UI 上的连接和通道关联。|
|      ```<br/>idleChannelsTx<br/>```      |当前空闲(缓存)的事务通道的数量。|
|    ```<br/>idleChannelsNotTx<br/>```     |当前空闲(缓存)的非事务通道的数量。|
| ```<br/>idleChannelsTxHighWater<br/>```  |已并发空闲(缓存)的事务通道的最大数量。|
|```<br/>idleChannelsNotTxHighWater<br/>```|已并发空闲(缓存)的非事务通道的最大数量。|

下表描述了`CacheMode.CONNECTION`属性:

|                       Property                       |意义|
|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|      ```<br/>connectionName:<localPort><br/>```      |由`ConnectionNameStrategy`生成的连接的名称。|
|           ```<br/>openConnections<br/>```            |表示到代理的连接的连接对象的数量。|
|           ```<br/>channelCacheSize<br/>```           |当前配置的允许空闲的最大通道。|
|         ```<br/>connectionCacheSize<br/>```          |当前配置的允许空闲的最大连接数.|
|           ```<br/>idleConnections<br/>```            |当前空闲的连接数。|
|       ```<br/>idleConnectionsHighWater<br/>```       |并发空闲的连接的最大数量。|
|      ```<br/>idleChannelsTx:<localPort><br/>```      |此连接当前空闲(缓存)的事务通道的数量。<br/>你可以使用属性名称的`localPort`部分来关联 RabbitMQ 管理 UI 上的连接和通道。|
|    ```<br/>idleChannelsNotTx:<localPort><br/>```     |此连接当前空闲(缓存)的非事务通道的数量。<br/>属性名称的`localPort`部分可用于与 RabbitMQ 管理 UI 上的连接和通道关联。|
| ```<br/>idleChannelsTxHighWater:<localPort><br/>```  |并发空闲(缓存)的事务通道的最大数量。<br/>属性名称的 localport 部分可用于与 RabbitMQ 管理 UI 上的连接和通道关联。|
|```<br/>idleChannelsNotTxHighWater:<localPort><br/>```|非事务通道的最大数量已同时空闲(缓存)。<br/>你可以使用属性名称的`localPort`部分来关联 RabbitMQ 管理 UI 上的连接和通道。|

还包括`cacheMode`属性(`CHANNEL`或`CONNECTION`)。

![cacheStats](images/cacheStats.png)

图 1。JVisualVM 示例

##### [](#auto-recovery)RabbitMQ 自动连接/拓扑恢复

自 Spring AMQP 的第一个版本以来,该框架已经在代理失败的情况下提供了自己的连接和通道恢复。同样,如[配置代理](#broker-configuration)中所讨论的,`RabbitAdmin`在重新建立连接时重新声明任何基础设施 bean(队列和其他)。因此,它不依赖现在由`amqp-client`库提供的[自动恢复](https://www.rabbitmq.com/api-guide.html#recovery)。 Spring AMQP 现在使用`4.0.x`的`amqp-client`版本,该版本在默认情况下具有启用的自动恢复功能。 Spring 如果你愿意,AMQP 仍然可以使用其自己的恢复机制,在客户端中禁用它,(通过将底层`automaticRecoveryEnabled`上的`RabbitMQ connectionFactory`属性设置为`false`)。然而,该框架与启用的自动恢复完全兼容。这意味着你在代码中创建的任何消费者(可能通过`RabbitTemplate.execute()`)都可以自动恢复。

|   |只有被定义为 bean 的元素(队列、交换、绑定)在连接失败后才会重新声明。<br/>直接从用户代码调用`RabbitAdmin.declare*()`方法声明的元素对于框架来说是未知的,因此无法恢复。<br/>如果你需要变量数量的声明,考虑定义类型`Declarables`的 Bean 或 bean,如[声明交换、队列和绑定的集合](#collection-declaration)中讨论的那样。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### [](#custom-client-props)4.1.3。添加自定义客户端连接属性

现在,`CachingConnectionFactory`允许你访问底层连接工厂,以允许例如设置自定义客户机属性。下面的示例展示了如何做到这一点:

```
connectionFactory.getRabbitConnectionFactory().getClientProperties().put("thing1", "thing2");
```

当查看连接时,这些属性会出现在 RabbitMQ 管理 UI 中。

#### [](#amqp-template)4.1.4。`AmqpTemplate`

与 Spring 框架和相关项目提供的许多其他高级抽象一样, Spring AMQP 提供了一个发挥核心作用的“模板”。定义主要操作的接口称为`AmqpTemplate`。这些操作涵盖了发送和接收消息的一般行为。换句话说,它们不是任何实现所独有的——因此名称中的“AMQP”。另一方面,该接口的一些实现与 AMQP 协议的实现相关联。与 JMS 本身是一个接口级 API 不同,AMQP 是一个线路级协议。该协议的实现提供了自己的客户端库,因此模板接口的每个实现都依赖于特定的客户端库。目前,只有一个实现:`RabbitTemplate`。在下面的示例中,我们经常使用`AmqpTemplate`。然而,当你查看配置示例或在其中实例化模板或调用 setter 的任何代码摘录时,你可以看到实现类型(例如,`RabbitTemplate`)。

如前所述,`AmqpTemplate`接口定义了用于发送和接收消息的所有基本操作。我们将分别在[发送消息](#sending-messages)和[接收消息](#receiving-messages)中研究消息的发送和接收。

另见[异步兔子模板](#async-template)。

##### [](#template-retry)添加重试功能

从版本 1.3 开始,你现在可以将`RabbitTemplate`配置为使用`RetryTemplate`来帮助处理代理连接问题。有关完整信息,请参见[spring-retry](https://github.com/spring-projects/spring-retry)项目。下面只是一个使用指数后退策略和默认`SimpleRetryPolicy`的示例,它在向调用方抛出异常之前进行了三次尝试。

下面的示例使用 XML 名称空间:

```
<rabbit:template id="template" connection-factory="connectionFactory" retry-template="retryTemplate"/>

<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
    <property name="backOffPolicy">
        <bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
            <property name="initialInterval" value="500" />
            <property name="multiplier" value="10.0" />
            <property name="maxInterval" value="10000" />
        </bean>
    </property>
</bean>
```

下面的示例使用了 Java 中的`@Configuration`注释:

```
@Bean
public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    RetryTemplate retryTemplate = new RetryTemplate();
    ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
    backOffPolicy.setInitialInterval(500);
    backOffPolicy.setMultiplier(10.0);
    backOffPolicy.setMaxInterval(10000);
    retryTemplate.setBackOffPolicy(backOffPolicy);
    template.setRetryTemplate(retryTemplate);
    return template;
}
```

从版本 1.4 开始,除了`retryTemplate`属性外,`recoveryCallback`选项还支持`RabbitTemplate`选项。它被用作`RetryTemplate.execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback)`的第二个参数。

|   |`RecoveryCallback`在某种程度上是有限的,因为重试上下文只包含`lastThrowable`字段。,对于更复杂的用例,<br/>,你应该使用一个外部`RetryTemplate`,这样你就可以通过上下文的属性向`RecoveryCallback`传递额外的信息。<br/>下面的示例展示了如何做到这一点:|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

```
retryTemplate.execute(
    new RetryCallback<Object, Exception>() {

        @Override
        public Object doWithRetry(RetryContext context) throws Exception {
            context.setAttribute("message", message);
            return rabbitTemplate.convertAndSend(exchange, routingKey, message);
        }

    }, new RecoveryCallback<Object>() {

        @Override
        public Object recover(RetryContext context) throws Exception {
            Object message = context.getAttribute("message");
            Throwable t = context.getLastThrowable();
            // Do something with message
            return null;
        }
    });
}
```

在这种情况下,将**不是**将`RetryTemplate`注入到`RabbitTemplate`中。

##### [](#publishing-is-async)发布是异步的——如何检测成功和失败

发布消息是一种异步机制,默认情况下,RabbitMQ 会删除无法路由的消息。对于成功的发布,你可以收到异步确认,如[相关发布者确认并返回](#template-confirms)中所述。考虑两种失败场景:

* 发布到交易所,但没有匹配的目标队列。

* 发布到一个不存在的交易所。

第一种情况由 Publisher Returns 覆盖,如[相关发布者确认并返回](#template-confirms)中所述。

对于第二种情况,消息会被删除,并且不会生成返回。底层通道已关闭,但有一个例外。默认情况下,该异常会被记录,但是你可以用`ChannelListener`注册`CachingConnectionFactory`来获得此类事件的通知。下面的示例展示了如何添加`ConnectionListener`:

```
this.connectionFactory.addConnectionListener(new ConnectionListener() {

    @Override
    public void onCreate(Connection connection) {
    }

    @Override
    public void onShutDown(ShutdownSignalException signal) {
        ...
    }

});
```

你可以检查信号的`reason`属性,以确定发生的问题。

要检测发送线程上的异常,可以在`RabbitTemplate`上检测`setChannelTransacted(true)`,并在`txCommit()`上检测异常。但是,**交易大大妨碍了业绩。**,因此在仅为这个用例启用事务之前,请仔细考虑这一点。

##### [](#template-confirms)相关发布者确认并返回

`RabbitTemplate`的`AmqpTemplate`实现支持 Publisher 确认和返回。

对于返回的消息,对于特定的消息,模板的`mandatory`属性必须设置为`true`,或者`mandatory-expression`必须计算为`true`。此功能需要一个`CachingConnectionFactory`,其`publisherReturns`属性设置为`true`(参见[发布者确认并返回](#cf-pub-conf-ret))。它通过调用`setReturnsCallback(ReturnsCallback callback)`来注册`RabbitTemplate.ReturnsCallback`,从而将返回发送到客户机。回调必须实现以下方法:

```
void returnedMessage(ReturnedMessage returned);
```

`ReturnedMessage`具有以下属性:

* `message`-返回的消息本身

* `replyCode`-指示返回原因的代码

* `replyText`-返回的文本原因-例如`NO_ROUTE`

* `exchange`-消息发送到的交换器

* `routingKey`-使用的路由密钥

每个`RabbitTemplate`只支持一个`ReturnsCallback`。另见[回复超时](#reply-timeout)。

对于 Publisher 确认(也称为 Publisher 确认),模板需要一个`CachingConnectionFactory`,其`publisherConfirm`属性设置为`ConfirmType.CORRELATED`。它通过调用`setConfirmCallback(ConfirmCallback callback)`来注册`RabbitTemplate.ConfirmCallback`,从而将确认信息发送到客户机。回调必须实现这个方法:

```
void confirm(CorrelationData correlationData, boolean ack, String cause);
```

`CorrelationData`是客户端在发送原始消息时提供的对象。对于`ack`,`ack`为真,对于`nack`,为假。对于`nack`实例,如果在生成`nack`时可用,则原因可能包含`nack`的原因。一个例子是,当向不存在的交易所发送消息时。在这种情况下,代理将关闭该通道。闭包的原因包括在`cause`中。在 1.4 版本中添加了`cause`。

只有一个`ConfirmCallback`被`RabbitTemplate`所支持。

|   |当 Rabbit 模板发送操作完成时,通道将关闭。<br/>这将阻止在连接工厂缓存已满(当缓存中有空间时,通道没有物理关闭,返回和确认正常进行)。<br/>当缓存已满时,框架将延迟关闭最多五秒钟,以便留出时间进行确认和返回。<br/>当使用确认时,当接收到最后一次确认时,通道关闭。<br/>当只使用返回时,通道在整整五秒钟内保持打开。<br/>我们通常建议将连接工厂的`channelCacheSize`设置为足够大的值,以便将发布消息的通道返回到缓存中,而不是关闭。<br/>你可以通过使用 RabbitMQ 管理插件来监视通道的使用情况。<br/>如果你看到通道正在快速打开和关闭,你应该考虑增加缓存大小,以减少服务器上的开销。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |在版本 2.1 之前,在收到确认信息之前,已将启用用于发布确认的通道返回到缓存。<br/>其他一些进程可以签出该通道并执行某些操作这会导致通道关闭——例如将消息发布到一个不存在的 Exchange。<br/>这可能会导致确认丢失。<br/>版本 2.1 并在以后不再将信道返回到缓存中,而确认是未执行的。<br/>该`RabbitTemplate`在每次操作之后对信道执行逻辑`close()`。<br/>通常,这意味着一次在一个信道上只有一个确认是未执行的。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |从版本 2.2 开始,回调是在连接工厂的`executor`线程之一上调用的,<br/>这是为了避免在回调中执行兔子操作时可能出现死锁,<br/>与以前的版本一样,回调是在`amqp-client`连接 I/O 线程上直接调用的;如果你执行一些 RPC 操作(例如打开一个新通道),这将导致死锁,因为 I/O 线程块在等待结果,但是结果需要由 I/O 线程本身来处理。<br/>对于那些版本,有必要将工作(例如发送消息)交给回调中的另一个线程。<br/>这不再是必需的,因为框架现在将回调调用交给执行器。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |只要返回回调在 60 秒或更短的时间内执行,在 ACK 之前接收返回消息的保证仍然保持。<br/>确认计划在返回回调结束之后或 60 秒之后交付,以先到者为准。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 2.1 开始,`CorrelationData`对象有一个`ListenableFuture`,你可以使用它来获取结果,而不是在模板上使用`ConfirmCallback`。下面的示例展示了如何配置`CorrelationData`实例:

```
CorrelationData cd1 = new CorrelationData();
this.templateWithConfirmsEnabled.convertAndSend("exchange", queue.getName(), "foo", cd1);
assertTrue(cd1.getFuture().get(10, TimeUnit.SECONDS).isAck());
```

由于它是`ListenableFuture<Confirm>`,所以你可以在准备就绪时获得`get()`结果,或者为异步回调添加侦听器。`Confirm`对象是一个简单的 Bean,具有 2 个属性:`ack`和`reason`(对于`nack`实例)。对于代理生成的`nack`实例,原因未填充。它是为框架生成的`nack`实例填充的(例如,在`ack`实例未完成时关闭连接)。

此外,当同时启用确认和返回时,`CorrelationData`将填充返回的消息,只要`CorrelationData`具有唯一的`id`;默认情况下,从版本 2.3 开始总是这样。可以保证,返回的消息在使用`ack`设置 future 之前已经设置好了。

另请参见[作用域操作](#scoped-operations),以获得等待发布者确认的更简单机制。

##### [](#scoped-operations)作用域操作

通常,当使用模板时,一个`Channel`将从缓存中签出(或创建),用于操作,并返回到缓存中进行重用。在多线程环境中,不能保证下一个操作使用相同的通道。然而,有时你可能希望对通道的使用有更多的控制,并确保多个操作都在同一个通道上执行。

从版本 2.0 开始,提供了一个名为`invoke`的新方法,并提供了一个`OperationsCallback`。在回调范围内和提供的`RabbitOperations`参数上执行的任何操作都使用相同的专用`Channel`,它将在结束时关闭(不返回到缓存)。如果通道是`PublisherCallbackChannel`,则在接收到所有确认后将其返回到缓存中(参见[相关发布者确认并返回](#template-confirms))。

```
@FunctionalInterface
public interface OperationsCallback<T> {

    T doInRabbit(RabbitOperations operations);

}
```

如果你希望在底层`Channel`上使用`waitForConfirms()`方法,那么你可能需要此方法的一个示例。 Spring API 以前没有公开这种方法,因为通道通常是缓存和共享的,正如前面讨论的那样。`RabbitTemplate`现在提供`waitForConfirms(long timeout)`和`waitForConfirmsOrDie(long timeout)`,将其委托给在`OperationsCallback`范围内使用的专用通道。出于显而易见的原因,这些方法不能在该范围之外使用。

请注意,允许你与请求进行关联确认的更高级别的抽象在其他地方提供(参见[相关发布者确认并返回](#template-confirms))。如果你只想等待代理确认交付,那么可以使用以下示例中所示的技术:

```
Collection<?> messages = getMessagesToSend();
Boolean result = this.template.invoke(t -> {
    messages.forEach(m -> t.convertAndSend(ROUTE, m));
    t.waitForConfirmsOrDie(10_000);
    return true;
});
```

如果你希望`RabbitAdmin`操作在`OperationsCallback`范围内的同一通道上被调用,则必须使用与`RabbitTemplate`操作相同的`RabbitTemplate`构造管理。

|   |如果模板操作已经在现有事务的范围内执行,那么前面的讨论是没有意义的,例如,当运行在一个事务侦听器容器线程上并在一个事务模板上执行操作时,<br/>在这种情况下,该操作在该通道上执行,并在线程返回到容器时提交。<br/>在该场景中无需使用`invoke`。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

以这种方式使用确认时,实际上并不需要为将确认与请求关联而设置的许多基础设施(除非还启用了返回)。从版本 2.2 开始,连接工厂支持一个名为`publisherConfirmType`的新属性。当这被设置为`ConfirmType.SIMPLE`时,避免了基础设施的确认,并且可以更有效地进行处理。

此外,`RabbitTemplate`在已发送消息`MessageProperties`中设置`publisherSequenceNumber`属性。如果你希望检查(或记录或以其他方式使用)特定的确认,则可以使用重载的`invoke`方法进行检查,如下例所示:

```
public <T> T invoke(OperationsCallback<T> action, com.rabbitmq.client.ConfirmCallback acks,
        com.rabbitmq.client.ConfirmCallback nacks);
```

|   |这些`ConfirmCallback`对象(对于`ack`和`nack`实例)是 Rabbit 客户机回调,而不是模板回调。|
|---|----------------------------------------------------------------------------------------------------------------------------|

下面的示例日志记录`ack`和`nack`实例:

```
Collection<?> messages = getMessagesToSend();
Boolean result = this.template.invoke(t -> {
    messages.forEach(m -> t.convertAndSend(ROUTE, m));
    t.waitForConfirmsOrDie(10_000);
    return true;
}, (tag, multiple) -> {
        log.info("Ack: " + tag + ":" + multiple);
}, (tag, multiple) -> {
        log.info("Nack: " + tag + ":" + multiple);
}));
```

|   |作用域操作绑定到线程。<br/>有关多线程环境中严格排序的讨论,请参见[多线程环境中的严格消息排序](#multi-strict)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#multi-strict)多线程环境中的严格消息排序

[作用域操作](#scoped-operations)中的讨论仅在同一线程上执行操作时才适用。

考虑以下情况:

* `thread-1`向队列发送消息,并将工作移交给`thread-2`

* `thread-2`将消息发送到相同的队列

由于 RabbitMQ 的异步特性和缓存通道的使用,不确定是否使用相同的通道,因此不能保证消息到达队列的顺序。(在大多数情况下,它们会按顺序到达,但超量交付的可能性不是零)。要解决此用例,你可以使用大小`1`的有界通道缓存(连同`channelCheckoutTimeout`)来确保消息总是发布在相同的通道上,并且将保证顺序。要做到这一点,如果你对连接工厂有其他用途,例如消费者,那么你应该为模板使用专用的连接工厂,或者将模板配置为使用嵌入在主连接工厂中的发布者连接工厂(参见[使用单独的连接](#separate-connection))。

用一个简单的 Spring 引导应用程序最好地说明了这一点:

```
@SpringBootApplication
public class Application {

	private static final Logger log = LoggerFactory.getLogger(Application.class);

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

	@Bean
	TaskExecutor exec() {
		ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
		exec.setCorePoolSize(10);
		return exec;
	}

	@Bean
	CachingConnectionFactory ccf() {
		CachingConnectionFactory ccf = new CachingConnectionFactory("localhost");
		CachingConnectionFactory publisherCF = (CachingConnectionFactory) ccf.getPublisherConnectionFactory();
		publisherCF.setChannelCacheSize(1);
		publisherCF.setChannelCheckoutTimeout(1000L);
		return ccf;
	}

	@RabbitListener(queues = "queue")
	void listen(String in) {
		log.info(in);
	}

	@Bean
	Queue queue() {
		return new Queue("queue");
	}

	@Bean
	public ApplicationRunner runner(Service service, TaskExecutor exec) {
		return args -> {
			exec.execute(() -> service.mainService("test"));
		};
	}

}

@Component
class Service {

	private static final Logger LOG = LoggerFactory.getLogger(Service.class);

	private final RabbitTemplate template;

	private final TaskExecutor exec;

	Service(RabbitTemplate template, TaskExecutor exec) {
		template.setUsePublisherConnection(true);
		this.template = template;
		this.exec = exec;
	}

	void mainService(String toSend) {
		LOG.info("Publishing from main service");
		this.template.convertAndSend("queue", toSend);
		this.exec.execute(() -> secondaryService(toSend.toUpperCase()));
	}

	void secondaryService(String toSend) {
		LOG.info("Publishing from secondary service");
		this.template.convertAndSend("queue", toSend);
	}

}
```

尽管发布是在两个不同的线程上执行的,但它们都将使用相同的通道,因为缓存的上限是单个通道。

从版本 2.3.7 开始,`ThreadChannelConnectionFactory`支持使用`prepareContextSwitch`和`switchContext`方法将一个线程的通道转移到另一个线程。第一个方法返回一个上下文,该上下文被传递给调用第二个方法的第二个线程。线程可以有一个非事务通道,也可以有一个绑定到它的事务通道(或其中一个);除非使用两个连接工厂,否则不能单独传输它们。以下是一个例子:

```
@SpringBootApplication
public class Application {

	private static final Logger log = LoggerFactory.getLogger(Application.class);

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

	@Bean
	TaskExecutor exec() {
		ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
		exec.setCorePoolSize(10);
		return exec;
	}

	@Bean
	ThreadChannelConnectionFactory tccf() {
		ConnectionFactory rabbitConnectionFactory = new ConnectionFactory();
		rabbitConnectionFactory.setHost("localhost");
		return new ThreadChannelConnectionFactory(rabbitConnectionFactory);
	}

	@RabbitListener(queues = "queue")
	void listen(String in) {
		log.info(in);
	}

	@Bean
	Queue queue() {
		return new Queue("queue");
	}

	@Bean
	public ApplicationRunner runner(Service service, TaskExecutor exec) {
		return args -> {
			exec.execute(() -> service.mainService("test"));
		};
	}

}

@Component
class Service {

	private static final Logger LOG = LoggerFactory.getLogger(Service.class);

	private final RabbitTemplate template;

	private final TaskExecutor exec;

	private final ThreadChannelConnectionFactory connFactory;

	Service(RabbitTemplate template, TaskExecutor exec,
			ThreadChannelConnectionFactory tccf) {

		this.template = template;
		this.exec = exec;
		this.connFactory = tccf;
	}

	void mainService(String toSend) {
		LOG.info("Publishing from main service");
		this.template.convertAndSend("queue", toSend);
		Object context = this.connFactory.prepareSwitchContext();
		this.exec.execute(() -> secondaryService(toSend.toUpperCase(), context));
	}

	void secondaryService(String toSend, Object threadContext) {
		LOG.info("Publishing from secondary service");
		this.connFactory.switchContext(threadContext);
		this.template.convertAndSend("queue", toSend);
		this.connFactory.closeThreadChannel();
	}

}
```

|   |一旦调用`prepareSwitchContext`,如果当前线程执行更多的操作,它们将在新的通道上执行。<br/>当不再需要线程绑定通道时,关闭该通道非常重要。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#template-messaging)消息传递集成

从版本 1.4 开始,`RabbitMessagingTemplate`(构建在`RabbitTemplate`之上)提供了与 Spring Framework 消息传递抽象的集成——即`org.springframework.messaging.Message`。这允许你通过使用`spring-messaging``Message<?>`抽象来发送和接收消息。这个抽象被其他 Spring 项目所使用,例如 Spring 集成和 Spring 的 STOMP 支持。涉及两个消息转换器:一个用于在 Spring-messaging`Message<?>`和 Spring AMQP 的`Message`抽象之间进行转换,另一个用于在 Spring AMQP 的`Message`抽象和底层 RabbitMQ 客户端库所需的格式之间进行转换。默认情况下,消息有效负载由提供的`RabbitTemplate`实例的消息转换器进行转换。或者,可以将自定义`MessagingMessageConverter`与其他有效负载转换器一起注入,如下例所示:

```
MessagingMessageConverter amqpMessageConverter = new MessagingMessageConverter();
amqpMessageConverter.setPayloadConverter(myPayloadConverter);
rabbitMessagingTemplate.setAmqpMessageConverter(amqpMessageConverter);
```

##### [](#template-user-id)验证用户 ID

从版本 1.6 开始,模板现在支持`user-id-expression`(使用 Java 配置时`userIdExpression`)。如果发送了消息,则在计算该表达式后设置 User ID 属性(如果尚未设置)。评估的根对象是要发送的消息。

以下示例展示了如何使用`user-id-expression`属性:

```
<rabbit:template ... user-id-expression="'guest'" />

<rabbit:template ... user-id-expression="@myConnectionFactory.username" />
```

第一个例子是一个字面表达式。第二个从应用程序上下文中的连接工厂 Bean 获得`username`属性。

##### [](#separate-connection)使用单独的连接

从版本 2.0.2 开始,你可以将`usePublisherConnection`属性设置为`true`,以便在可能的情况下使用与侦听器容器使用的不同的连接。这是为了避免当生产者由于任何原因而被封锁时,消费者被封锁。为此目的,连接工厂维护第二个内部连接工厂;默认情况下,它与主工厂的类型相同,但是如果你希望使用不同的工厂类型进行发布,则可以明确地设置该类型。如果 Rabbit 模板在侦听器容器启动的事务中运行,则使用容器的通道,而不考虑此设置。

|   |通常,你不应该在模板中使用`RabbitAdmin`,而该模板将此设置为`true`。<br/>使用获取连接工厂的`RabbitAdmin`构造函数。<br/>如果你使用另一个获取模板的构造函数,请确保模板的属性为`false`。<br/>这是因为,通常,管理员用于声明侦听器容器的队列。<br/>使用属性设置为`true`的模板将意味着在与侦听器容器使用的连接不同的连接上声明排他队列(例如`AnonymousQueue`)。,在这种情况下,<br/>,容器不能使用队列。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### [](#sending-messages)4.1.5。发送消息

在发送消息时,可以使用以下任何一种方法:

```
void send(Message message) throws AmqpException;

void send(String routingKey, Message message) throws AmqpException;

void send(String exchange, String routingKey, Message message) throws AmqpException;
```

我们可以从前面清单中的最后一个方法开始讨论,因为它实际上是最明确的方法。它允许在运行时提供 AMQP 交换名(以及路由密钥)。最后一个参数是负责实际创建消息实例的回调。使用此方法发送消息的示例可能如下所示:下面的示例展示了如何使用`send`方法发送消息:

```
amqpTemplate.send("marketData.topic", "quotes.nasdaq.THING1",
    new Message("12.34".getBytes(), someProperties));
```

如果你计划在大部分时间或全部时间使用该模板实例发送到相同的 Exchange,则可以在模板本身上设置`exchange`属性。在这种情况下,你可以使用前面列表中的第二种方法。下面的示例在功能上与前面的示例等价:

```
amqpTemplate.setExchange("marketData.topic");
amqpTemplate.send("quotes.nasdaq.FOO", new Message("12.34".getBytes(), someProperties));
```

如果在模板上同时设置了`exchange`和`routingKey`属性,则可以使用仅接受`Message`的方法。下面的示例展示了如何做到这一点:

```
amqpTemplate.setExchange("marketData.topic");
amqpTemplate.setRoutingKey("quotes.nasdaq.FOO");
amqpTemplate.send(new Message("12.34".getBytes(), someProperties));
```

考虑 Exchange 和 Routing 键属性的一种更好的方法是,显式方法参数总是覆盖模板的默认值。实际上,即使你没有显式地在模板上设置这些属性,也始终存在默认值。在这两种情况下,默认值都是空的`String`,但这实际上是一个合理的默认值。就路由密钥而言,它首先并不总是必要的(例如,对于`Fanout`交换)。此外,队列可以绑定到带有空`String`的交换。对于模板的路由密钥属性,这两种情况都是依赖默认的空`String`值的合法场景。就交换名称而言,空`String`是常用的,因为 AMQP 规范将“默认交换”定义为没有名称。由于所有队列都自动绑定到默认的 Exchange(这是一个直接的 Exchange),使用它们的名称作为绑定值,因此前面列表中的第二个方法可以用于通过默认 Exchange 向任何队列发送简单的点对点消息。通过在运行时提供方法参数,可以将队列名提供为`routingKey`。下面的示例展示了如何做到这一点:

```
RabbitTemplate template = new RabbitTemplate(); // using default no-name Exchange
template.send("queue.helloWorld", new Message("Hello World".getBytes(), someProperties));
```

或者,你可以创建一个模板,该模板可以主要用于发布,也可以专门用于发布到单个队列。下面的示例展示了如何做到这一点:

```
RabbitTemplate template = new RabbitTemplate(); // using default no-name Exchange
template.setRoutingKey("queue.helloWorld"); // but we'll always send to this Queue
template.send(new Message("Hello World".getBytes(), someProperties));
```

##### [](#message-builder)Message Builder API

从版本 1.3 开始,消息生成器 API 由`MessageBuilder`和`MessagePropertiesBuilder`提供。这些方法为创建消息或消息属性提供了一种方便的“fluent”方法。以下示例展示了 Fluent API 的实际应用:

```
Message message = MessageBuilder.withBody("foo".getBytes())
    .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
    .setMessageId("123")
    .setHeader("bar", "baz")
    .build();
```

```
MessageProperties props = MessagePropertiesBuilder.newInstance()
    .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
    .setMessageId("123")
    .setHeader("bar", "baz")
    .build();
Message message = MessageBuilder.withBody("foo".getBytes())
    .andProperties(props)
    .build();
```

在[`MessageProperties`](https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframework/amqp/core/messageproperties.html)上定义的每个属性都可以被设置。其他方法包括`setHeader(String key, String value)`、`removeHeader(String key)`、`removeHeaders()`和`copyProperties(MessageProperties properties)`。每个属性设置方法都有一个`set*IfAbsent()`变量。在存在默认初始值的情况下,该方法被命名为`set*IfAbsentOrDefault()`。

提供了五种静态方法来创建初始消息生成器:

```
public static MessageBuilder withBody(byte[] body) (1)

public static MessageBuilder withClonedBody(byte[] body) (2)

public static MessageBuilder withBody(byte[] body, int from, int to) (3)

public static MessageBuilder fromMessage(Message message) (4)

public static MessageBuilder fromClonedMessage(Message message) (5)
```

|**1**|构建器创建的消息有一个直接引用该参数的主体。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|构建器创建的消息有一个主体,它是一个新的数组,在参数中包含一个字节的副本。|
|**3**|生成器创建的消息有一个主体,它是一个新的数组,其中包含来自参数的字节范围。<br/>有关更多详细信息,请参见[`Arrays.copyOfRange()`](https://DOCS.oracle.com/javase/7/DOCS/api/java/util/array.html)。|
|**4**|构建器创建的消息有一个直接引用到参数主体的主体。<br/>该参数的属性被复制到一个新的`MessageProperties`对象。|
|**5**|构建器创建的消息有一个主体,它是一个新的数组,其中包含一个参数主体的副本。<br/>该参数的属性被复制到一个新的`MessageProperties`对象。|

提供了三个静态方法来创建`MessagePropertiesBuilder`实例:

```
public static MessagePropertiesBuilder newInstance() (1)

public static MessagePropertiesBuilder fromProperties(MessageProperties properties) (2)

public static MessagePropertiesBuilder fromClonedProperties(MessageProperties properties) (3)
```

|**1**|使用默认值初始化一个新的消息属性对象。|
|-----|--------------------------------------------------------------------------------------------|
|**2**|“构建器是用提供的属性对象初始化的,`build()`将返回该对象。,|
|**3**|参数的属性被复制到一个新的`MessageProperties`对象。|

使用`RabbitTemplate`的`AmqpTemplate`实现,每个`send()`方法都有一个重载版本,该版本接受一个额外的`CorrelationData`对象。当启用了 Publisher Confirms 时,将在[`AmqpTemplate`]中描述的回调中返回此对象。这使得发送者将确认(`ack`或`nack`)与发送的消息关联起来。

从 1.6.7 版本开始,引入了`CorrelationAwareMessagePostProcessor`接口,允许在消息转换后修改相关数据。下面的示例展示了如何使用它:

```
Message postProcessMessage(Message message, Correlation correlation);
```

在 2.0 版本中,此接口已被弃用。该方法已被移动到`MessagePostProcessor`,并使用一个将其委托给`postProcessMessage(Message message)`的默认实现。

从 1.6.7 版本开始,还提供了一个名为`CorrelationDataPostProcessor`的新回调接口。这是在所有`MessagePostProcessor`实例(在`send()`方法中提供,以及在`setBeforePublishPostProcessors()`中提供)之后调用的。实现方式可以更新或替换`send()`方法(如果有的话)中提供的相关数据。`Message`和原始`CorrelationData`(如果有的话)作为参数提供。下面的示例展示了如何使用`postProcess`方法:

```
CorrelationData postProcess(Message message, CorrelationData correlationData);
```

##### [](#publisher-returns)Publisher 返回

当模板的`mandatory`属性是`true`时,返回的消息由[`AmqpTemplate`]中描述的回调提供。

从版本 1.4 开始,`RabbitTemplate`支持 spel`mandatoryExpression`属性,该属性根据每个请求消息作为根求值对象进行求值,解析为`boolean`值。 Bean 引用,例如`@myBean.isMandatory(#root)`,可以在表达式中使用。

在发送和接收操作中,`RabbitTemplate`还可以在内部使用发布服务器返回。有关更多信息,请参见[回复超时](#reply-timeout)。

##### [](#template-batching)批处理

版本 1.4.2 引入了`BatchingRabbitTemplate`。这是`RabbitTemplate`的一个子类,具有一个重写的`send`方法,该方法根据`BatchingStrategy`批处理消息。只有当批处理完成时,消息才会发送到 RabbitMQ。下面的清单显示了`BatchingStrategy`接口定义:

```
public interface BatchingStrategy {

    MessageBatch addToBatch(String exchange, String routingKey, Message message);

    Date nextRelease();

    Collection<MessageBatch> releaseBatches();

}
```

|   |批处理的数据保存在内存中。<br/>未发送的消息可能会在系统故障时丢失。|
|---|-------------------------------------------------------------------------------------------------|

a`SimpleBatchingStrategy`。它支持将消息发送到单个 Exchange 或 Routing Key。它具有以下特性:

* `batchSize`:一批消息在发送之前的数量。

* `bufferLimit`:批处理消息的最大大小。如果超过了`batchSize`,这会抢占该参数,并导致发送部分批处理。

* `timeout`:当没有新的活动向批处理添加消息时,发送部分批处理的时间。

`SimpleBatchingStrategy`通过在每条嵌入的消息之前使用四字节的二进制长度来格式化批处理。这是通过将`springBatchFormat`消息属性设置为`lengthHeader4`来与接收系统通信的。

|   |批处理的消息在默认情况下由侦听器容器自动去批处理(通过使用`springBatchFormat`消息头)。<br/>拒绝批处理中的任何消息将导致整个批处理被拒绝。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

但是,有关更多信息,请参见[@RabbitListener 与批处理](#receiving-batch)。

#### [](#receiving-messages)4.1.6。接收消息

消息接收总是比发送复杂一点。有两种方法可以接收`Message`。更简单的选项是使用轮询方法调用一次轮询一个`Message`。更复杂但更常见的方法是注册一个侦听器,该侦听器异步地按需接收`Messages`。在接下来的两个子节中,我们将考虑每种方法的示例。

##### [](#polling-consumer)轮询消费者

`AmqpTemplate`本身可以用于对`Message`的接收进行民意测验。默认情况下,如果没有可用的消息,`null`将立即返回。不存在阻塞。从版本 1.5 开始,你可以设置`receiveTimeout`,以毫秒为单位,并且 ReceiveMethods 块等待消息的时间最长可达那么长。小于零的值意味着无限期地阻塞(或至少直到与代理的连接丢失为止)。版本 1.6 引入了`receive`方法的变体,这些方法允许在每次调用时传入超时。

|   |由于接收操作为每条消息创建了一个新的`QueueingConsumer`,因此这种技术并不适合于大容量环境。<br/>考虑使用异步消费者或对这些用例使用零的`receiveTimeout`。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

有四种简单的`receive`方法可用。与发送端的`Exchange`一样,有一个方法要求在模板本身上直接设置默认的队列属性,并且有一个方法在运行时接受队列参数。版本 1.6 引入了一些变体,可以在每个请求的基础上接受`receiveTimeout`来覆盖`receiveTimeout`。下面的清单显示了这四种方法的定义:

```
Message receive() throws AmqpException;

Message receive(String queueName) throws AmqpException;

Message receive(long timeoutMillis) throws AmqpException;

Message receive(String queueName, long timeoutMillis) throws AmqpException;
```

与发送消息的情况一样,`AmqpTemplate`具有一些用于接收 POJO 而不是`Message`实例的方便方法,并且实现提供了一种定制`MessageConverter`的方法,用于创建返回的`Object`:下面的列表显示了这些方法:

```
Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;

Object receiveAndConvert(long timeoutMillis) throws AmqpException;

Object receiveAndConvert(String queueName, long timeoutMillis) throws AmqpException;
```

从版本 2.0 开始,这些方法有一些变体,它们接受一个额外的`ParameterizedTypeReference`参数来转换复杂类型。模板必须配置为`SmartMessageConverter`。有关更多信息,请参见[从`Message`转换为`RabbitTemplate`]。

与`sendAndReceive`方法类似,从版本 1.3 开始,`AmqpTemplate`具有几种方便的`receiveAndReply`方法,用于同步接收、处理和回复消息。下面的清单显示了这些方法定义:

```
<R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback)
       throws AmqpException;

<R, S> boolean receiveAndReply(String queueName, ReceiveAndReplyCallback<R, S> callback)
     throws AmqpException;

<R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback,
    String replyExchange, String replyRoutingKey) throws AmqpException;

<R, S> boolean receiveAndReply(String queueName, ReceiveAndReplyCallback<R, S> callback,
    String replyExchange, String replyRoutingKey) throws AmqpException;

<R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback,
     ReplyToAddressCallback<S> replyToAddressCallback) throws AmqpException;

<R, S> boolean receiveAndReply(String queueName, ReceiveAndReplyCallback<R, S> callback,
            ReplyToAddressCallback<S> replyToAddressCallback) throws AmqpException;
```

`AmqpTemplate`实现处理`receive`和`reply`阶段。在大多数情况下,你应该只提供`ReceiveAndReplyCallback`的实现,以便为接收到的消息执行某些业务逻辑,并在需要时构建一个应答对象或消息。注意,a`ReceiveAndReplyCallback`可能返回`null`。在这种情况下,不会发送任何回复,并且`receiveAndReply`的工作原理与`receive`方法类似。这使得相同的队列可以用于混合消息,其中一些消息可能不需要回复。

只有当所提供的回调不是`ReceiveAndReplyMessageCallback`的实例时,才应用自动消息(请求和回复)转换,该实例提供了原始消息交换契约。

对于需要自定义逻辑在运行时根据接收到的消息确定`replyTo`地址并从`ReceiveAndReplyCallback`回复的情况,`ReplyToAddressCallback`是有用的。默认情况下,请求消息中的`replyTo`信息用于路由答复。

下面的清单展示了一个基于 POJO 的接收和回复示例:

```
boolean received =
        this.template.receiveAndReply(ROUTE, new ReceiveAndReplyCallback<Order, Invoice>() {

                public Invoice handle(Order order) {
                        return processOrder(order);
                }
        });
if (received) {
        log.info("We received an order!");
}
```

##### [](#async-consumer)异步消费者

|   |Spring AMQP 还通过使用`@RabbitListener`注释来支持带注释的侦听器端点,并提供一个开放的基础设施来以编程方式注册端点。<br/>这是迄今为止设置异步消费者的最方便的方式。<br/>有关更多详细信息,请参见[注释驱动的监听器端点](#async-annotation-driven)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |预取默认值过去是 1,这可能会导致有效的使用者利用率不足,<br/>从版本 2.0 开始,默认的预取值现在是 250,这应该会使使用者在大多数常见的情况下都很忙,<br/>因此提高了吞吐量。,尽管如此,仍然存在,预取值应该较低的场景:对于大消息,<br/><br/>*,特别是如果处理速度较慢(消息在客户端进程中可能会增加大量内存)<br/><br/>* 当需要严格的消息排序时(在这种情况下,预取值应设置为 1)<br/><br/>* 其他特殊情况<br/><br/>同样,对于低容量的消息传递和多个消费者(包括单个侦听器容器实例中的并发性),你可能希望减少预取,以便在消费者之间获得更均匀的消息分布。<br/><br/>关于预取的更多背景信息,请参见[消息侦听器容器配置](#containerAttributes)。`x-expires`<br/>,请参阅这篇关于[RabbitMQ 中的消费者利用](https://www.rabbitmq.com/blog/2014/04/14/finding-bottlenecks-with-rabbitmq-3-3/)的文章和这篇关于[排队论](https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/)的文章。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###### [](#message-listener)消息侦听器

对于异步`Message`接收,涉及一个专用组件(而不是`AmqpTemplate`)。该组件是用于`Message`消费回调的容器。在本节的后面部分,我们将考虑容器及其属性。不过,首先我们应该看看回调,因为这是应用程序代码与消息传递系统集成的地方。回调有几个选项,从`MessageListener`接口的实现开始,下面的清单显示了这个选项:

```
public interface MessageListener {
    void onMessage(Message message);
}
```

如果回调逻辑出于任何原因依赖于 AMQP 通道实例,则可以使用`ChannelAwareMessageListener`。它看起来很相似,但有一个额外的参数。下面的清单显示了`ChannelAwareMessageListener`接口定义:

```
public interface ChannelAwareMessageListener {
    void onMessage(Message message, Channel channel) throws Exception;
}
```

|   |在版本 2.1 中,此接口从包`o.s.amqp.rabbit.core`移动到`o.s.amqp.rabbit.listener.api`。|
|---|-----------------------------------------------------------------------------------------------------------|

###### [](#message-listener-adapter)`MessageListenerAdapter`

如果希望在应用程序逻辑和消息传递 API 之间保持更严格的分离,则可以依赖框架提供的适配器实现。这通常被称为“消息驱动的 POJO”支持。

|   |版本 1.5 引入了一种更灵活的 POJO 消息传递机制,`@RabbitListener`注释。<br/>有关更多信息,请参见[注释驱动的监听器端点](#async-annotation-driven)。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在使用适配器时,你只需要提供对适配器本身应该调用的实例的引用。下面的示例展示了如何做到这一点:

```
MessageListenerAdapter listener = new MessageListenerAdapter(somePojo);
listener.setDefaultListenerMethod("myMethod");
```

你可以对适配器进行子类,并提供`getListenerMethodName()`的实现,以根据消息动态地选择不同的方法。这个方法有两个参数,`originalMessage`和`extractedMessage`,后者是任何转换的结果。默认情况下,配置了`SimpleMessageConverter`。有关其他可用转换器的更多信息和信息,请参见[`SimpleMessageConverter`]。

从版本 1.4.2 开始,原始消息具有`consumerQueue`和`consumerTag`属性,可用于确定接收消息的队列。

从版本 1.5 开始,你可以将消费者队列或标记的映射配置为方法名,以动态地选择要调用的方法。如果映射中没有条目,我们将返回到默认的侦听器方法。默认的侦听器方法(如果未设置)是`handleMessage`。

从版本 2.0 开始,提供了一个方便的`FunctionalInterface`。下面的清单显示了`FunctionalInterface`的定义:

```
@FunctionalInterface
public interface ReplyingMessageListener<T, R> {

    R handleMessage(T t);

}
```

该接口通过使用 Java8Lambdas 方便了适配器的配置,如下例所示:

```
new MessageListenerAdapter((ReplyingMessageListener<String, String>) data -> {
    ...
    return result;
}));
```

从版本 2.2 开始,`buildListenerArguments(Object)`已被弃用,取而代之的是新的`buildListenerArguments(Object, Channel, Message)`。新方法帮助 Listener 获得`@SendTo`和`Message`参数,以执行更多操作,例如在手动确认模式下调用`channel.basicReject(long, boolean)`。下面的清单展示了最基本的示例:

```
public class ExtendedListenerAdapter extends MessageListenerAdapter {

    @Override
    protected Object[] buildListenerArguments(Object extractedMessage, Channel channel, Message message) {
        return new Object[]{extractedMessage, channel, message};
    }

}
```

现在,如果需要接收“通道”和“消息”,你可以将`ExtendedListenerAdapter`配置为与`MessageListenerAdapter`相同的配置。Listener 的参数应设置为`buildListenerArguments(Object, Channel, Message)`返回,如下面的 Listener 示例所示:

```
public void handleMessage(Object object, Channel channel, Message message) throws IOException {
    ...
}
```

###### [](#container)容器

既然你已经看到了`ChannelAwareMessageListener`-listening 回调的各种选项,那么我们就可以将注意力转向容器了。基本上,容器处理“主动”职责,以便侦听器回调可以保持被动。容器是“生命周期”组件的一个示例。它提供了启动和停止的方法。在配置容器时,实质上是在 AMQP 队列和`MessageListener`实例之间建立桥梁。你必须提供对`ConnectionFactory`的引用,以及该侦听器应该从其中使用消息的队列名称或队列实例。

在 2.0 版本之前,有一个侦听器容器,`SimpleMessageListenerContainer`。现在有第二个容器,`DirectMessageListenerContainer`。容器和你在选择要使用的容器时可能应用的条件之间的差异在[选择容器](#choose-container)中进行了描述。

下面的清单显示了最基本的示例,该示例通过使用`SimpleMessageListenerContainer`来工作:

```
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory);
container.setQueueNames("some.queue");
container.setMessageListener(new MessageListenerAdapter(somePojo));
```

作为一个“活动”组件,最常见的方法是创建具有 Bean 定义的侦听器容器,以便它可以在后台运行。下面的示例展示了使用 XML 的一种方法:

```
<rabbit:listener-container connection-factory="rabbitConnectionFactory">
    <rabbit:listener queues="some.queue" ref="somePojo" method="handle"/>
</rabbit:listener-container>
```

下面的清单展示了使用 XML 的另一种方法:

```
<rabbit:listener-container connection-factory="rabbitConnectionFactory" type="direct">
    <rabbit:listener queues="some.queue" ref="somePojo" method="handle"/>
</rabbit:listener-container>
```

前面的两个示例都创建了`DirectMessageListenerContainer`(请注意`type`属性——它默认为`simple`)。

或者,你可能更喜欢使用 Java 配置,它看起来类似于前面的代码片段:

```
@Configuration
public class ExampleAmqpConfiguration {

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setQueueName("some.queue");
        container.setMessageListener(exampleListener());
        return container;
    }

    @Bean
    public CachingConnectionFactory rabbitConnectionFactory() {
        CachingConnectionFactory connectionFactory =
            new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
    public MessageListener exampleListener() {
        return new MessageListener() {
            public void onMessage(Message message) {
                System.out.println("received: " + message);
            }
        };
    }
}
```

###### [](#consumer-priority)消费者优先权

从 RabbitMQ3.2 版本开始,代理现在支持消费者优先权(参见[使用 RabbitMQ 的消费者优先级](https://www.rabbitmq.com/blog/2013/12/16/using-consumer-priorities-with-rabbitmq/))。这可以通过在消费者上设置`x-priority`参数来实现。`SimpleMessageListenerContainer`现在支持设置消费者参数,如下例所示:

```
container.setConsumerArguments(Collections.
<String, Object> singletonMap("x-priority", Integer.valueOf(10)));
```

为了方便起见,名称空间在`listener`元素上提供了`priority`属性,如下例所示:

```
<rabbit:listener-container connection-factory="rabbitConnectionFactory">
    <rabbit:listener queues="some.queue" ref="somePojo" method="handle" priority="10" />
</rabbit:listener-container>
```

从版本 1.3 开始,你可以修改容器在运行时监听的队列。见[监听器容器队列](#listener-queues)。

###### [](#lc-auto-delete)`auto-delete`队列

当容器被配置为侦听`auto-delete`队列时,队列具有`x-expires`选项,或者在代理上配置了[活着的时间](https://www.rabbitmq.com/ttl.html)策略,当容器停止时(即最后一个使用者被取消时),代理将队列删除。在版本 1.3 之前,由于缺少队列,无法重新启动容器。`RabbitAdmin`仅在连接关闭或打开时自动重新声明队列,以此类推,而在停止和启动容器时不会发生这种情况。

从版本 1.3 开始,容器使用[选择容器](#choose-container)在启动过程中重新声明任何丢失的队列。

你还可以使用条件声明(参见[有条件声明](#conditional-declaration))和`@RabbitHandler`管理来延迟队列声明,直到容器启动。下面的示例展示了如何做到这一点:

```
<rabbit:queue id="otherAnon" declared-by="containerAdmin" />

<rabbit:direct-exchange name="otherExchange" auto-delete="true" declared-by="containerAdmin">
    <rabbit:bindings>
        <rabbit:binding queue="otherAnon" key="otherAnon" />
    </rabbit:bindings>
</rabbit:direct-exchange>

<rabbit:listener-container id="container2" auto-startup="false">
    <rabbit:listener id="listener2" ref="foo" queues="otherAnon" admin="containerAdmin" />
</rabbit:listener-container>

<rabbit:admin id="containerAdmin" connection-factory="rabbitConnectionFactory"
    auto-startup="false" />
```

在这种情况下,队列和交换由`containerAdmin`声明,其具有,因此在上下文初始化期间不会声明元素。同样,由于同样的原因,容器没有启动。当容器稍后被启动时,它使用其对`containerAdmin`的引用来声明元素。

##### [](#de-batching)批处理消息

批处理的消息(由生成者创建)由侦听器容器(使用`springBatchFormat`消息头)自动进行去批处理。拒绝批处理中的任何消息都会导致整个批处理被拒绝。有关批处理的更多信息,请参见[Batching](#template-batching)。

从版本 2.2 开始,`SimpleMessageListenerContainer`可以用于在消费者端(生产者发送离散消息的地方)创建批处理。

设置容器属性`consumerBatchEnabled`以启用此功能。`deBatchingEnabled`也必须为真,以便容器负责处理这两种类型的批。当`consumerBatchEnabled`为真时实现`BatchMessageListener`或`ChannelAwareBatchMessageListener`。从版本 2.2.7 开始,`SimpleMessageListenerContainer`和`DirectMessageListenerContainer`都可以将[生产者创建了批](#template-batching)删除为`List<Message>`。有关在`@RabbitListener`中使用此功能的信息,请参见`DirectMessageListenerContainer`。

##### [](#consumer-events)消费者活动

容器在侦听器(使用者)遇到某种故障时发布应用程序事件。事件`ListenerContainerConsumerFailedEvent`具有以下属性:

* `container`:消费者遇到问题的侦听器容器。

* `reason`:失败的文本原因。

* `fatal`:表示失败是否致命的布尔值。对于非致命的异常,容器会根据`recoveryInterval`或`recoveryBackoff`(对于`SimpleMessageListenerContainer`)或`recoveryBackoff`(对于`DirectMessageListenerContainer`)尝试重新启动消费者。

* `throwable`:抓到的`Throwable`。

可以通过实现`ApplicationListener<ListenerContainerConsumerFailedEvent>`来消耗这些事件。

|   |当`concurrentConsumers`大于 1 时,所有使用者都会发布系统范围的事件(例如连接失败)。|
|---|-----------------------------------------------------------------------------------------------------------------------------|

如果一个使用者因其队列被独占使用而失败,那么默认情况下,以及在发布事件时,将发布`WARN`日志。要更改此日志记录行为,请在`SimpleMessageListenerContainer`实例的`exclusiveConsumerExceptionLogger`属性中提供一个自定义`ConditionalExceptionLogger`。另见[记录通道关闭事件](#channel-close-logging)。

致命错误总是记录在`ERROR`级别。这是不可更改的。

在容器生命周期的各个阶段还发布了其他几个事件:

* `AsyncConsumerStartedEvent`:启动消费者时。

* `AsyncConsumerRestartedEvent`:当使用者在失败后重新启动时-`SimpleMessageListenerContainer`仅。

* `AsyncConsumerTerminatedEvent`:当消费者被正常停止时。

* `AsyncConsumerStoppedEvent`:仅当消费者被停止时-`SimpleMessageListenerContainer`。

* `ConsumeOkEvent`:当从代理收到`consumeOk`时,包含队列名和`consumerTag`

* `ListenerContainerIdleEvent`:见[检测空闲异步消费者](#idle-containers)。

* [检测空闲异步消费者](#idle-containers):检测到缺少队列时。

##### [](#consumerTags)消费者标签

你可以提供一种生成消费者标签的策略。默认情况下,消费者标记是由代理生成的。下面的清单显示了`ConsumerTagStrategy`接口定义:

```
public interface ConsumerTagStrategy {

    String createConsumerTag(String queue);

}
```

队列是可用的,以便它可以(可选地)在标记中使用。

见[消息侦听器容器配置](#containerAttributes)。

##### [](#async-annotation-driven)注释驱动的侦听器端点

异步接收消息的最简单方法是使用带注释的侦听器端点基础结构。简而言之,它允许你将托管 Bean 方法公开为 Rabbit 侦听器端点。下面的示例展示了如何使用`@RabbitListener`注释:

```
@Component
public class MyService {

    @RabbitListener(queues = "myQueue")
    public void processOrder(String data) {
        ...
    }

}
```

前面示例的思想是,每当名为`myQueue`的队列上有消息可用时,都会相应地调用`ClassMapper`方法(在这种情况下,使用消息的有效负载)。

通过使用`RabbitListenerContainerFactory`,带注释的端点基础设施在幕后为每个带注释的方法创建了一个消息侦听器容器。

在前面的示例中,`myQueue`必须已经存在并绑定到某个交换。只要应用程序上下文中存在`RabbitAdmin`,就可以自动声明和绑定队列。

|   |可以为注释属性(`queues`等)指定属性占位符(`${some.property}`)或 spel 表达式(`#{someExpression}`)。<br/>参见[监听多个队列](#annotation-multiple-queues)有关为什么可能使用 spel 而不是属性占位符的示例。<br/>下面的列表显示了如何声明 Rabbit 侦听器的三个示例:|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

```
@Component
public class MyService {

  @RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "myQueue", durable = "true"),
        exchange = @Exchange(value = "auto.exch", ignoreDeclarationExceptions = "true"),
        key = "orderRoutingKey")
  )
  public void processOrder(Order order) {
    ...
  }

  @RabbitListener(bindings = @QueueBinding(
        value = @Queue,
        exchange = @Exchange(value = "auto.exch"),
        key = "invoiceRoutingKey")
  )
  public void processInvoice(Invoice invoice) {
    ...
  }

  @RabbitListener(queuesToDeclare = @Queue(name = "${my.queue}", durable = "true"))
  public String handleWithSimpleDeclare(String data) {
      ...
  }

}
```

在第一个示例中,如果需要,一个队列`myQueue`将与交换器一起自动(持久)声明,并用路由密钥绑定到交换器。在第二个示例中,声明并绑定了一个匿名(排他的、自动删除的)队列。可以提供多个`QueueBinding`条目,让侦听器监听多个队列。在第三个示例中,如果有必要,将使用默认绑定到默认的 Exchange,并使用队列名称作为路由密钥,声明一个具有从属性`my.queue`中检索到的名称的队列。

自 2.0 版本以来,`@Exchange`注释支持任何交换类型,包括自定义。有关更多信息,请参见[AMQP 概念](https://www.rabbitmq.com/tutorials/amqp-concepts.html)。

当需要更高级的配置时,可以使用普通的`@Bean`定义。

请注意第一个示例中的 Exchange 上的`ignoreDeclarationExceptions`。例如,这允许绑定到可能具有不同设置的现有交换机(例如`internal`)。默认情况下,现有交易所的属性必须匹配。

从版本 2.0 开始,你现在可以将队列绑定到具有多个路由密钥的交易所,如下例所示:

```
...
    key = { "red", "yellow" }
...
```

还可以在`@QueueBinding`注释中为队列、交换和绑定指定参数,如下例所示:

```
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "auto.headers", autoDelete = "true",
                        arguments = @Argument(name = "x-message-ttl", value = "10000",
                                                type = "java.lang.Integer")),
        exchange = @Exchange(value = "auto.headers", type = ExchangeTypes.HEADERS, autoDelete = "true"),
        arguments = {
                @Argument(name = "x-match", value = "all"),
                @Argument(name = "thing1", value = "somevalue"),
                @Argument(name = "thing2")
        })
)
public String handleWithHeadersExchange(String foo) {
    ...
}
```

注意,对于队列,`x-message-ttl`参数被设置为 10 秒。因为参数类型不是`String`,所以我们必须指定它的类型——在本例中,`Integer`。与所有此类声明一样,如果队列已经存在,则参数必须与队列上的参数匹配。对于头交换,我们将绑定参数设置为匹配将`thing1`头设置为`somevalue`的消息,并且`thing2`头必须带有任何值。`x-match`参数意味着这两个条件都必须满足。

参数名称、值和类型可以是属性占位符(`${…​}`)或 SPEL 表达式(`#{…​}`)。`name`必须解析为`String`。`type`的表达式必须解析为`Class`或类的完全限定名称。`value`必须解析为可以由`DefaultConversionService`转换为类型的内容(例如前面示例中的`x-message-ttl`)。

如果名称解析为`null`或空的`String`,则忽略`@Argument`。

###### [](#meta-annotation-driven)元注解

有时你可能希望对多个侦听器使用相同的配置。为了减少样板配置,你可以使用元注释来创建自己的侦听器注释。下面的示例展示了如何做到这一点:

```
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@RabbitListener(bindings = @QueueBinding(
        value = @Queue,
        exchange = @Exchange(value = "metaFanout", type = ExchangeTypes.FANOUT)))
public @interface MyAnonFanoutListener {
}

public class MetaListener {

    @MyAnonFanoutListener
    public void handle1(String foo) {
        ...
    }

    @MyAnonFanoutListener
    public void handle2(String foo) {
        ...
    }

}
```

在前面的示例中,由`@MyAnonFanoutListener`注释创建的每个侦听器都将一个匿名的自动删除队列绑定到 FanOut Exchange,`metaFanout`。从版本 2.2.3 开始,支持`@AliasFor`,以允许在元注释注释上覆盖属性。此外,用户注释现在可以是`@Repeatable`,从而允许为一个方法创建多个容器。

```
@Component
static class MetaAnnotationTestBean {

    @MyListener("queue1")
    @MyListener("queue2")
    public void handleIt(String body) {
    }

}

@RabbitListener
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyListeners.class)
static @interface MyListener {

    @AliasFor(annotation = RabbitListener.class, attribute = "queues")
    String[] value() default {};

}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
static @interface MyListeners {

    MyListener[] value();

}
```

###### [](#async-annotation-driven-enable)启用侦听器端点注释

要启用对`@RabbitListener`注释的支持,你可以将`@EnableRabbit`添加到你的一个`@Configuration`类中。下面的示例展示了如何做到这一点:

```
@Configuration
@EnableRabbit
public class AppConfig {

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(10);
        factory.setContainerCustomizer(container -> /* customize the container */);
        return factory;
    }
}
```

自 2.0 版本以来,`DirectMessageListenerContainerFactory`也可用。它创建`DirectMessageListenerContainer`实例。

|   |有关帮助你在`SimpleRabbitListenerContainerFactory`和`DirectRabbitListenerContainerFactory`之间进行选择的信息,请参见[选择容器](#choose-container)。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 2.2.2 开始,你可以提供`ContainerCustomizer`实现(如上图所示)。在创建和配置了容器之后,可以使用它来进一步配置容器;例如,你可以使用它来设置容器工厂不公开的属性。

默认情况下,基础结构会寻找一个名为`rabbitListenerContainerFactory`的 Bean 作为工厂用来创建消息侦听器容器的源。在这种情况下,忽略 RabbitMQ 基础设施设置,`processOrder`方法可以通过三个线程的核心轮询大小和十个线程的最大池大小来调用。

你可以自定义用于每个注释的侦听器容器工厂,或者可以通过实现`RabbitListenerConfigurer`接口来配置显式默认值。只有当至少有一个端点在没有特定容器工厂的情况下注册时,才需要默认设置。有关详细信息和示例,请参见[Javadoc](https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/annotation/RabbitListenerConfigurer.html)。

容器工厂提供了用于添加`MessagePostProcessor`实例的方法,这些实例是在接收消息(调用侦听器之前)和发送回复之前应用的。

有关回复的信息,请参见[回复管理](#async-annotation-driven-reply)。

从版本 2.0.6 开始,你可以向监听器容器工厂添加`RetryTemplate`和`RecoveryCallback`。在发送回复时使用它。当重试用完时,将调用`RecoveryCallback`。你可以使用`SendRetryContextAccessor`从上下文中获取信息。下面的示例展示了如何做到这一点:

```
factory.setRetryTemplate(retryTemplate);
factory.setReplyRecoveryCallback(ctx -> {
    Message failed = SendRetryContextAccessor.getMessage(ctx);
    Address replyTo = SendRetryContextAccessor.getAddress(ctx);
    Throwable t = ctx.getLastThrowable();
    ...
    return null;
});
```

如果你更喜欢 XML 配置,那么可以使用`<rabbit:annotation-driven>`元素。检测到任何带有`@RabbitListener`注释的 bean。

对于`SimpleRabbitListenerContainer`实例,你可以使用类似于以下的 XML:

```
<rabbit:annotation-driven/>

<bean id="rabbitListenerContainerFactory"
      class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="concurrentConsumers" value="3"/>
    <property name="maxConcurrentConsumers" value="10"/>
</bean>
```

对于`DirectMessageListenerContainer`实例,你可以使用类似于以下的 XML:

```
<rabbit:annotation-driven/>

<bean id="rabbitListenerContainerFactory"
      class="org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="consumersPerQueue" value="3"/>
</bean>
```

从版本 2.0 开始,`@RabbitListener`注释具有`concurrency`属性。它支持 SPEL 表达式(`@RabbitListener`)和属性占位符(`${…​}`)。其含义和允许的值取决于容器类型,如下所示:

* 对于`DirectMessageListenerContainer`,该值必须是一个整数值,该值设置容器上的`consumersPerQueue`属性。

* 对于`SimpleRabbitListenerContainer`,该值可以是一个整数值,它在容器上设置`concurrentConsumers`属性,或者它可以具有以下形式,`m`,其中`m`是`concurrentConsumers`属性,`n`是`maxConcurrentConsumers`属性。

在这两种情况下,此设置都会覆盖工厂上的设置。以前,如果你的侦听器需要不同的并发性,则必须定义不同的容器工厂。

注释还允许通过`autoStartup`和`executor`(自 2.2)注释属性重写工厂`taskExecutor`属性。对每个执行器使用不同的执行器可能有助于在日志和线程转储中识别与每个侦听器关联的线程。

版本 2.2 还添加了`ackMode`属性,它允许你覆盖容器工厂的`ackMode`属性。

```
@RabbitListener(id = "manual.acks.1", queues = "manual.acks.1", ackMode = "MANUAL")
public void manual1(String in, Channel channel,
    @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {

    ...
    channel.basicAck(tag, false);
}
```

###### [](#async-annotation-conversion)带注释方法的消息转换

在调用侦听器之前,管道中有两个转换步骤。第一步使用`MessageConverter`将传入的 Spring AMQP`Message`转换为 Spring-messaging`Message`。当调用目标方法时,如果需要,消息有效负载将被转换为方法参数类型。

第一步的默认`MessageConverter`是一个 Spring AMQP`SimpleMessageConverter`,它处理向`String`和`java.io.Serializable`对象的转换。所有其他参数保持为`byte[]`。在下面的讨论中,我们将其称为“消息转换器”。

第二步的默认转换器是`GenericMessageConverter`,它委托给一个转换服务(`DefaultFormattingConversionService`的实例)。在下面的讨论中,我们将其称为“方法参数转换器”。

要更改消息转换器,可以将其作为一个属性添加到容器工厂 Bean。下面的示例展示了如何做到这一点:

```
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    ...
    factory.setMessageConverter(new Jackson2JsonMessageConverter());
    ...
    return factory;
}
```

这配置了一个 Jackson2 转换器,该转换器期望出现头信息以指导转换。

还可以使用`ContentTypeDelegatingMessageConverter`,它可以处理不同内容类型的转换。

从版本 2.3 开始,你可以通过在`Message`属性中指定 Bean 名称来覆盖工厂转换器。

```
@Bean
public Jackson2JsonMessageConverter jsonConverter() {
    return new Jackson2JsonMessageConverter();
}

@RabbitListener(..., messageConverter = "jsonConverter")
public void listen(String in) {
    ...
}
```

这样就避免了仅仅为了更改转换器就必须声明一个不同的容器工厂。

在大多数情况下,不需要自定义方法参数转换器,除非你希望使用自定义`ConversionService`。

在 1.6 之前的版本中,转换 JSON 的类型信息必须在消息头中提供,或者需要自定义`ClassMapper`。从版本 1.6 开始,如果没有类型信息标头,则可以从目标方法参数推断类型。

|   |这种类型推理在方法级别上仅适用于`@RabbitListener`。|
|---|-------------------------------------------------------------------------|

有关更多信息,请参见[Jackson2JSONMessageConverter](#json-message-converter)。

如果你希望自定义方法参数转换器,可以按以下方式进行:

```
@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

    ...

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setMessageConverter(new GenericMessageConverter(myConversionService()));
        return factory;
    }

    @Bean
    public DefaultConversionService myConversionService() {
        DefaultConversionService conv = new DefaultConversionService();
        conv.addConverter(mySpecialConverter());
        return conv;
    }

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    ...

}
```

|   |对于多方法侦听器(参见[多方法侦听器](#annotation-method-selection)),方法选择是基于消息的有效负载**消息转换后**。<br/>只有在方法已被选择之后才调用方法参数转换器。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###### [](#custom-argument-resolver)向 @RabbitListener 添加自定义`HandlerMethodArgumentResolver`

从版本 2.3.7 开始,你可以添加自己的`HandlerMethodArgumentResolver`并解析自定义方法参数。你所需要的只是实现`ClassMapper`并使用来自类`RabbitListenerEndpointRegistrar`的方法`setCustomMethodArgumentResolvers()`。

```
@Configuration
class CustomRabbitConfig implements RabbitListenerConfigurer {

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setCustomMethodArgumentResolvers(
				new HandlerMethodArgumentResolver() {

					@Override
					public boolean supportsParameter(MethodParameter parameter) {
						return CustomMethodArgument.class.isAssignableFrom(parameter.getParameterType());
					}

					@Override
					public Object resolveArgument(MethodParameter parameter, org.springframework.messaging.Message<?> message) {
						return new CustomMethodArgument(
								(String) message.getPayload(),
								message.getHeaders().get("customHeader", String.class)
						);
					}

				}
			);
    }

}
```

###### [](#async-annotation-driven-registration)程序化端点注册

`RabbitListenerEndpoint`提供了一个 Rabbit 端点的模型,并负责为该模型配置容器。除了通过`RabbitListener`注释检测到的端点之外,该基础结构还允许你以编程方式配置端点。下面的示例展示了如何做到这一点:

```
@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint();
        endpoint.setQueueNames("anotherQueue");
        endpoint.setMessageListener(message -> {
            // processing
        });
        registrar.registerEndpoint(endpoint);
    }
}
```

在前面的示例中,我们使用了`SimpleRabbitListenerEndpoint`,它提供了要调用的实际`MessageListener`,但是你也可以构建自己的端点变体来描述自定义调用机制。

需要注意的是,你也可以完全跳过`@RabbitListener`的使用,并通过`RabbitListenerConfigurer`以编程方式注册端点。

###### [](#async-annotation-driven-enable-signature)带注释的端点方法签名

到目前为止,我们已经在我们的端点中注入了一个简单的`String`,但是它实际上可以有一个非常灵活的方法签名。下面的示例重写它,以使用自定义标头注入`Order`:

```
@Component
public class MyService {

    @RabbitListener(queues = "myQueue")
    public void processOrder(Order order, @Header("order_type") String orderType) {
        ...
    }
}
```

下面的列表显示了可用于与侦听器端点中的参数进行匹配的参数:

* RAW`org.springframework.amqp.core.Message`。

* 来自 RAW`Message`的`MessageProperties`。

* 在其上接收消息的`com.rabbitmq.client.Channel`。

* 从传入的 AMQP 消息转换的`${…​}`。

* `@Header`-带注释的方法参数,以提取特定的标头值,包括标准的 AMQP 标头。

* `@Headers`-带注释的参数,该参数还必须分配给`java.util.Map`,以获得对所有标题的访问权限。

* 转换后的有效载荷

不是受支持类型之一的非注释元素(即`Message`,`MessageProperties`,`Message<?>`和`Channel`)与有效负载匹配。你可以通过使用`@Payload`对参数进行注释来使其显式。你还可以通过添加一个额外的`@Valid`来打开验证。

注入 Spring 的消息抽象的能力特别有用,可以从存储在特定传输消息中的所有信息中受益,而无需依赖特定传输 API。下面的示例展示了如何做到这一点:

```
@RabbitListener(queues = "myQueue")
public void processOrder(Message<Order> order) { ...
}
```

方法参数的处理由`DefaultMessageH和lerMethodFactory`提供,你可以进一步自定义该参数以支持其他方法参数。转换和验证支持也可以在那里进行定制。

例如,如果我们想在处理`Order`之前确保我们的`Order`是有效的,我们可以用`@Valid`注释有效负载,并配置必要的验证器,如下所示:

```
@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setValidator(myValidator());
        return factory;
    }
}
```

###### [](#rabbit-validation)@rabbitlistener@payload validation

从版本 2.3.7 开始,现在更容易添加`Validator`来验证`@RabbitListener`和`@RabbitHandler``@Payload`参数。现在,你只需将验证器添加到注册商本身即可。

```
@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(new MyValidator());
    }
}
```

|   |当使用 Spring boot 和验证启动器时,`LocalValidatorFactoryBean`是自动配置的:|
|---|-----------------------------------------------------------------------------------------------------|

```
@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    @Autowired
    private LocalValidatorFactoryBean validator;
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(this.validator);
    }
}
```

验证:

```
public static class ValidatedClass {
  @Max(10)
  private int bar;
  public int getBar() {
    return this.bar;
  }
  public void setBar(int bar) {
    this.bar = bar;
  }
}
```

and

```
@RabbitListener(id="validated", queues = "queue1", errorHandler = "validationErrorHandler",
      containerFactory = "jsonListenerContainerFactory")
public void validatedListener(@Payload @Valid ValidatedClass val) {
    ...
}
@Bean
public RabbitListenerErrorHandler validationErrorHandler() {
    return (m, e) -> {
        ...
    };
}
```

###### [](#annotation-multiple-queues)监听多个队列

当你使用`queues`属性时,你可以指定关联的容器可以侦听多个队列。你可以使用`@Header`注释使 POJO 方法可以使用接收消息的队列名称。下面的示例展示了如何做到这一点:

```
@Component
public class MyService {

    @RabbitListener(queues = { "queue1", "queue2" } )
    public void processOrder(String data, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) {
        ...
    }

}
```

从版本 1.5 开始,你可以使用属性占位符和 SPEL 来外部化队列名称。下面的示例展示了如何做到这一点:

```
@Component
public class MyService {

    @RabbitListener(queues = "#{'${property.with.comma.delimited.queue.names}'.split(',')}" )
    public void processOrder(String data, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) {
        ...
    }

}
```

在版本 1.5 之前,只能以这种方式指定单个队列。每个队列都需要一个单独的属性。

###### [](#async-annotation-driven-reply)回复管理

`MessageListenerAdapter`中现有的支持已经允许你的方法具有一个非 void 返回类型。在这种情况下,调用的结果被封装在一条消息中,发送到原始消息的`ReplyToAddress`头中指定的地址,或发送到侦听器上配置的默认地址。你可以通过使用消息抽象的`@SendTo`注释来设置默认地址。

假设我们的`processOrder`方法现在应该返回一个`OrderStatus`,我们可以按以下方式编写它来自动发送回复:

```
@RabbitListener(destination = "myQueue")
@SendTo("status")
public OrderStatus processOrder(Order order) {
    // order processing
    return status;
}
```

如果需要以与传输无关的方式设置额外的头,则可以返回`Message`,如下所示:

```
@RabbitListener(destination = "myQueue")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
    // order processing
    return MessageBuilder
        .withPayload(status)
        .setHeader("code", 1234)
        .build();
}
```

或者,你可以在`beforeSendReplyMessagePostProcessors`容器工厂属性中使用`MessagePostProcessor`来添加更多标题。从版本 2.2.3 开始,被调用的 Bean/方法在回复消息中是可用的,该回复消息可以在消息后置处理器中用来将信息通信回调用者:

```
factory.setBeforeSendReplyPostProcessors(msg -> {
    msg.getMessageProperties().setHeader("calledBean",
            msg.getMessageProperties().getTargetBean().getClass().getSimpleName());
    msg.getMessageProperties().setHeader("calledMethod",
            msg.getMessageProperties().getTargetMethod().getName());
    return m;
});
```

从版本 2.2.5 开始,你可以配置一个`ReplyPostProcessor`来修改发送之前的回复消息;在`correlationId`头设置为与请求匹配后调用它。

```
@RabbitListener(queues = "test.header", group = "testGroup", replyPostProcessor = "echoCustomHeader")
public String capitalizeWithHeader(String in) {
    return in.toUpperCase();
}

@Bean
public ReplyPostProcessor echoCustomHeader() {
    return (req, resp) -> {
        resp.getMessageProperties().setHeader("myHeader", req.getMessageProperties().getHeader("myHeader"));
        return resp;
    };
}
```

将`@SendTo`值假定为一个响应`exchange`和`routingKey`对,该对遵循`exchange/routingKey`模式,其中一个部分可以省略。有效数值如下:

* `thing1/thing2`:`replyTo`交易所和`routingKey`.`queues`交易所:`replyTo`交易所和默认(空)`routingKey`。`/thing2`或`/thing2`交易所和默认(空)交易所。`/`或空:默认`routingKey`交易所和“默认<gt="1295"/>交易所。

同样,你可以使用`@SendTo`而不使用`value`属性。这种情况等于一个空的`!{…​}`模式。`@SendTo`仅在入站消息没有`replyToAddress`属性的情况下才使用。

从版本 1.5 开始,`@SendTo`值可以是 Bean 初始化 SPEL 表达式,如下例所示:

```
@RabbitListener(queues = "test.sendTo.spel")
@SendTo("#{spelReplyTo}")
public String capitalizeWithSendToSpel(String foo) {
    return foo.toUpperCase();
}
...
@Bean
public String spelReplyTo() {
    return "test.sendTo.reply.spel";
}
```

表达式必须求值为`String`,它可以是一个简单的队列名称(发送到默认的 Exchange),也可以是与前面示例之前讨论的`exchange/routingKey`形式一起。

|   |在初始化过程中,对`#{…​}`表达式进行一次求值。|
|---|----------------------------------------------------------------|

对于动态回复路由,消息发送方应该包括`reply_to`消息属性,或者使用备用运行时 SPEL 表达式(在下一个示例之后描述)。

从版本 1.6 开始,`@SendTo`可以是一个 SPEL 表达式,在运行时根据请求和回复进行求值,如下例所示:

```
@RabbitListener(queues = "test.sendTo.spel")
@SendTo("!{'some.reply.queue.with.' + result.queueName}")
public Bar capitalizeWithSendToSpel(Foo foo) {
    return processTheFooAndReturnABar(foo);
}
```

spel 表达式的运行时性质用`!{…​}`分隔符表示。表达式的求值上下文`#root`对象具有三个属性:

* `request`:`o.s.amqp.core.Message`请求对象。

* `source`:转换后的`o.s.messaging.Message<?>`。

* `result`:方法结果。

上下文有一个映射属性访问器、一个标准类型转换器和一个 Bean 解析器,它允许引用上下文中的其他 bean(例如,`@someBeanName.determineReplyQ(request, result)`)。

总之,`#{…​}`在初始化过程中被求值一次,而`#root`对象是应用程序上下文。bean 由它们的名称引用。`!{…​}`在运行时对每个消息进行求值,根对象具有前面列出的属性。bean 的名字是引用的,前缀是`@`。

从版本 2.1 开始,还支持简单属性占位符(例如,`${some.reply.to}`)。对于较早的版本,可以使用以下方法,如下例所示:

```
@RabbitListener(queues = "foo")
@SendTo("#{environment['my.send.to']}")
public String listen(Message in) {
    ...
    return ...
}
```

###### [](#reply-content-type)回复 ContentType

如果使用复杂的消息转换器,例如`ContentTypeDelegatingMessageConverter`,则可以通过在侦听器上设置`replyContentType`属性来控制回复的内容类型。这允许转换器为应答选择适当的委托转换器。

```
@RabbitListener(queues = "q1", messageConverter = "delegating",
        replyContentType = "application/json")
public Thing2 listen(Thing1 in) {
    ...
}
```

默认情况下,为了向后兼容,转换器设置的任何内容类型属性在转换后都将被这个值覆盖。诸如`SimpleMessageConverter`之类的转换器使用回复类型而不是内容类型来确定所需的转换,并在回复消息中适当地设置内容类型。这可能不是所需的操作,可以通过将`converterWinsContentType`属性设置为`SimpleRabbitListenerEndpoint`来重写。例如,如果返回一个包含 JSON 的`String`,则`SimpleMessageConverter`将在答复中将内容类型设置为`text/plain`。下面的配置将确保正确设置内容类型,即使使用`SimpleMessageConverter`。

```
@RabbitListener(queues = "q1", replyContentType = "application/json",
        converterWinsContentType = "false")
public String listen(Thing in) {
    ...
    return someJsonString;
}
```

当返回类型是 Spring AMQP`Message`或 Spring 消息传递`Message<?>`时,这些属性(`replyContentType`和`converterWinsContentType`)不适用。在第一种情况下,不涉及转换;只需设置`contentType`消息属性。在第二种情况下,使用消息头来控制行为:

```
@RabbitListener(queues = "q1", messageConverter = "delegating")
@SendTo("q2")
public Message<String> listen(String in) {
    ...
    return MessageBuilder.withPayload(in.toUpperCase())
            .setHeader(MessageHeaders.CONTENT_TYPE, "application/xml")
            .build();
}
```

此内容类型将在`MessageProperties`中传递给转换器。默认情况下,为了向后兼容,转换器设置的任何内容类型属性在转换后都将被这个值覆盖。如果你希望重写该行为,还可以将`AmqpHeaders.CONTENT_TYPE_CONVERTER_WINS`设置为`true`,并且转换器设置的任何值都将被保留。

###### [](#annotation-method-selection)多方法侦听器

从版本 1.5.0 开始,你可以在类级别上指定`@RabbitListener`注释。与新的`@RabbitHandler`注释一起,这允许单个侦听器根据传入消息的有效负载类型调用不同的方法。这一点最好用一个例子来描述:

```
@RabbitListener(id="multi", queues = "someQueue")
@SendTo("my.reply.queue")
public class MultiListenerBean {

    @RabbitHandler
    public String thing2(Thing2 thing2) {
        ...
    }

    @RabbitHandler
    public String cat(Cat cat) {
        ...
    }

    @RabbitHandler
    public String hat(@Header("amqp_receivedRoutingKey") String rk, @Payload Hat hat) {
        ...
    }

    @RabbitHandler(isDefault = true)
    public String defaultMethod(Object object) {
        ...
    }

}
```

在这种情况下,如果转换的有效负载是`Thing2`、`Cat`或`Hat`,则会调用单独的`@RabbitHandler`方法。你应该理解,系统必须能够基于有效负载类型来识别唯一的方法。将检查类型是否可分配给没有注释或使用`@Payload`注释的单个参数。请注意,同样的方法签名也适用,如方法级别`@RabbitListener`([前面描述的](#message-listener-adapter))中所讨论的那样。

从版本 2.0.3 开始,可以将`@RabbitHandler`方法指定为默认方法,如果其他方法不匹配,则调用该方法。最多只能指定一种方法。

|   |`@RabbitHandler`仅用于处理转换后的消息负载,如果希望接收未转换的 RAW`Message`对象,则必须在方法上使用`@RabbitListener`,而不是在类上。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###### [](#repeatable-rabbit-listener)`@Repeatable` `@RabbitListener`

从版本 1.6 开始,`@RabbitListener`注释被标记为`@Repeatable`。这意味着注释可以多次出现在相同的注释元素(方法或类)上。在这种情况下,将为每个注释创建一个单独的侦听器容器,每个注释都调用相同的侦听器`@Bean`。可重复的注释可以用于 Java8 或更高版本。

###### [](#proxy-rabbitlistener-and-generics)代理`@RabbitListener`和泛型

如果你的服务旨在被代理(例如,在`@Transactional`的情况下),那么当接口具有通用参数时,你应该记住一些考虑因素。考虑以下示例:

```
interface TxService<P> {

   String handle(P payload, String header);

}

static class TxServiceImpl implements TxService<Foo> {

    @Override
    @RabbitListener(...)
    public String handle(Thing thing, String rk) {
         ...
    }

}
```

对于通用接口和特定实现,你将被迫切换到 CGLIB 目标类代理,因为接口`handle`方法的实际实现是一种桥接方法。在事务管理的情况下,通过使用一个注释选项来配置 CGLIB 的使用:`@EnableTransactionManagement(proxyTargetClass = true)`。在这种情况下,所有注释都必须在实现中的目标方法上声明,如下例所示:

```
static class TxServiceImpl implements TxService<Foo> {

    @Override
    @Transactional
    @RabbitListener(...)
    public String handle(@Payload Foo foo, @Header("amqp_receivedRoutingKey") String rk) {
        ...
    }

}
```

###### [](#annotation-error-handling)处理异常

默认情况下,如果一个带注释的侦听器方法抛出一个异常,它将被抛出到容器,消息将被重新请求并重新交付、丢弃或路由到死信交换,这取决于容器和代理配置。任何东西都不会退还给寄件人。

从版本 2.0 开始,`@RabbitListener`注释有两个新属性:`errorHandler`和`returnExceptions`。

默认情况下,不会对它们进行配置。

你可以使用`errorHandler`来提供`RabbitListenerErrorHandler`实现的 Bean 名称。此功能接口有一种方法,如下所示:

```
@FunctionalInterface
public interface RabbitListenerErrorHandler {

    Object handleError(Message amqpMessage, org.springframework.messaging.Message<?> message,
              ListenerExecutionFailedException exception) throws Exception;

}
```

如你所见,你可以访问从容器接收的原始消息、消息转换器产生的 Spring-messaging`Message<?>`对象,以及侦听器抛出的异常(包装在`ListenerExecutionFailedException`中)。错误处理程序可以返回一些结果(作为回复发送),也可以抛出原始异常或新的异常(根据`returnExceptions`设置,将其抛出到容器或返回给发送方)。

当`true`时,`returnExceptions`属性将导致异常返回给发送方。异常包装在`RemoteInvocationResult`对象中。在发送端,有一个可用的`RemoteInvocationAwareMessageConverterAdapter`,如果将其配置为`RabbitTemplate`,则会重新抛出服务器端异常,并包装在`AmqpRemoteException`中。服务器异常的堆栈跟踪是通过合并服务器和客户端堆栈跟踪来合成的。

|   |这种机制通常仅对默认的`SimpleMessageConverter`有效,后者使用 Java 序列化。<br/>异常通常不是“Jackson 友好的”,并且不能序列化到 JSON。<br/>如果使用 JSON,请考虑在抛出异常时使用`errorHandler`返回一些其他 Jackson 友好的`Error`对象。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |在版本 2.1 中,此接口从包`o.s.amqp.rabbit.listener`移动到`o.s.amqp.rabbit.listener.api`。|
|---|---------------------------------------------------------------------------------------------------------------|

从版本 2.1.7 开始,`Channel`在消息消息头中可用;这允许你在使用`AcknowledgeMode.MANUAL`时对失败的消息进行 ACK 或 NACK:

```
public Object handleError(Message amqpMessage, org.springframework.messaging.Message<?> message,
          ListenerExecutionFailedException exception) {
              ...
              message.getHeaders().get(AmqpHeaders.CHANNEL, Channel.class)
                  .basicReject(message.getHeaders().get(AmqpHeaders.DELIVERY_TAG, Long.class),
                               true);
          }
```

从版本 2.2.18 开始,如果抛出消息转换异常,将调用错误处理程序,在`message`参数中使用`null`。这允许应用程序向调用者发送一些结果,表明收到了格式错误的消息。以前,此类错误是由容器抛出和处理的。

###### [](#container-management)容器管理

为注释创建的容器未在应用程序上下文中注册。你可以通过在`RabbitListenerEndpointRegistry` Bean 上调用`getListenerContainers()`来获得所有容器的集合。然后可以迭代这个集合,例如,停止或启动所有容器,或者调用注册表本身上的`Lifecycle`方法,这些方法将调用每个容器上的操作。

你还可以通过使用其`id`,使用`getListenerContainer(String id)`,获得对单个容器的引用——例如,对于上面的代码片段创建的容器,`registry.getListenerContainer("multi")`。

从版本 1.5.2 开始,你可以使用`getListenerContainerIds()`获得已注册容器的`id`值。

从版本 1.5 开始,你现在可以将`group`分配到`RabbitListener`端点上的容器。这提供了一种机制来获取对容器子集的引用。添加一个`group`属性会导致类型`Collection<MessageListenerContainer>`的 Bean 被注册到带有组名的上下文中。

##### [](#receiving-batch)@rabbitlistener with batching

当接收到[a batch](#template-batching)的消息时,解批处理通常由容器执行,侦听器在一次调用一条消息。从版本 2.2 开始,你可以将侦听器容器工厂和侦听器配置为在一个调用中接收整个批处理,只需设置工厂的`batchListener`属性,并将方法有效负载参数设置为`List`:

```
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory());
    factory.setBatchListener(true);
    return factory;
}

@RabbitListener(queues = "batch.1")
public void listen1(List<Thing> in) {
    ...
}

// or

@RabbitListener(queues = "batch.2")
public void listen2(List<Message<Thing>> in) {
    ...
}
```

将`batchListener`属性设置为 true 会自动关闭工厂创建的容器中的`deBatchingEnabled`容器属性(除非`consumerBatchEnabled`是`true`-见下文)。实际上,将 debatching 从容器移动到侦听器适配器,并且适配器将创建传递给侦听器的列表。

启用批处理的工厂不能与[多方法监听器](#annotation-method-selection)一起使用。

也从版本 2.2 开始。当一次只接收一条批处理消息时,最后一条消息包含一个布尔头,该头设置为`true`。这个头可以通过添加`@Header(AmqpHeaders.LAST_IN_BATCH)`BooleanLast<gtr="1416"/>MessageProperties.islastinBatch()<gtr="1417"/>AmQpHeaders 来获得。batch_size` 是用每个消息片段中批处理的大小填充的。

此外,在`SimpleMessageListenerContainer`中添加了一个新的属性`consumerBatchEnabled`。如果这是真的,容器将创建一批消息,最多为`batchSize`;如果`receiveTimeout`在没有新消息到达的情况下经过,则传递部分批消息。如果收到了生产者创建的批处理,则将其删除并添加到消费者侧批处理中;因此,实际交付的消息数量可能超过`batchSize`,表示从代理收到的消息的数量。`deBatchingEnabled``consumerBatchEnabled`为真时必须为真;容器工厂将强制执行此要求。

```
@Bean
public SimpleRabbitListenerContainerFactory consumerBatchContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(rabbitConnectionFactory());
    factory.setConsumerTagStrategy(consumerTagStrategy());
    factory.setBatchListener(true); // configures a BatchMessageListenerAdapter
    factory.setBatchSize(2);
    factory.setConsumerBatchEnabled(true);
    return factory;
}
```

当使用`consumerBatchEnabled``@RabbitListener`时:

```
@RabbitListener(queues = "batch.1", containerFactory = "consumerBatchContainerFactory")
public void consumerBatch1(List<Message> amqpMessages) {
    this.amqpMessagesReceived = amqpMessages;
    this.batch1Latch.countDown();
}

@RabbitListener(queues = "batch.2", containerFactory = "consumerBatchContainerFactory")
public void consumerBatch2(List<org.springframework.messaging.Message<Invoice>> messages) {
    this.messagingMessagesReceived = messages;
    this.batch2Latch.countDown();
}

@RabbitListener(queues = "batch.3", containerFactory = "consumerBatchContainerFactory")
public void consumerBatch3(List<Invoice> strings) {
    this.batch3Strings = strings;
    this.batch3Latch.countDown();
}
```

* 第一个是用 RAW 调用的,未转换的`org.springframework.amqp.core.Message`s 接收到的。

* 第二个是用`org.springframework.messaging.Message<?>`s 调用的,带有转换的有效负载和映射的头/属性。

* 第三种是使用转换后的有效负载进行调用,不访问 headers/properteis。

还可以添加`Channel`参数,该参数在使用`MANUAL`ACK 模式时经常使用。这在第三个示例中不是很有用,因为你无法访问`delivery_tag`属性。

##### [](#using-container-factories)使用容器工厂

引入了监听器容器工厂来支持`@RabbitListener`并使用`RabbitListenerEndpointRegistry`注册容器,如[程序化端点注册](#async-annotation-driven-registration)中所讨论的那样。

从版本 2.1 开始,它们可以用于创建任何侦听器容器——甚至是没有侦听器的容器(例如在 Spring 集成中使用的容器)。当然,必须在容器启动之前添加一个侦听器。

创建这样的容器有两种方法:

* 使用 simplerabbitlistenerendPoint

* 创建后添加监听器

下面的示例展示了如何使用`SimpleRabbitListenerEndpoint`创建侦听器容器:

```
@Bean
public SimpleMessageListenerContainer factoryCreatedContainerSimpleListener(
        SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory) {
    SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint();
    endpoint.setQueueNames("queue.1");
    endpoint.setMessageListener(message -> {
        ...
    });
    return rabbitListenerContainerFactory.createListenerContainer(endpoint);
}
```

下面的示例展示了如何在创建后添加侦听器:

```
@Bean
public SimpleMessageListenerContainer factoryCreatedContainerNoListener(
        SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory) {
    SimpleMessageListenerContainer container = rabbitListenerContainerFactory.createListenerContainer();
    container.setMessageListener(message -> {
        ...
    });
    container.setQueueNames("test.no.listener.yet");
    return container;
}
```

在这两种情况下,侦听器也可以是`ChannelAwareMessageListener`,因为它现在是`MessageListener`的子接口。

如果你希望创建具有类似属性的多个容器,或者使用预先配置的容器工厂(例如 Spring Boot Auto Configuration 提供的容器工厂),那么这些技术是有用的。

|   |以这种方式创建的容器是正常的`@Bean`实例,并且不在`RabbitListenerEndpointRegistry`中注册。|
|---|------------------------------------------------------------------------------------------------------------------------|

##### [](#async-returns)异步`@RabbitListener`返回类型

从版本 2.1 开始,`@RabbitListener`(和`@RabbitHandler`)方法可以用异步返回类型`ListenableFuture<?>``Mono<?>`来指定,让回复被异步发送。

|   |侦听器容器工厂必须配置`AcknowledgeMode.MANUAL`,这样使用者线程就不会对消息进行 ACK;相反,异步完成将在异步操作完成时对消息进行 ACK 或 NACK,<br/>当异步结果以错误完成时,消息是否被重新请求取决于抛出的异常类型、容器配置和容器错误处理程序,<br/>默认情况下,消息将被重新请求,除非容器的`defaultRequeueRejected`属性被设置为`false`(默认情况下是`true`)。<br/>如果异步结果是用`AmqpRejectAndDontRequeueException`完成的,则消息将不会被重新请求。,<br/>如果容器的`defaultRequeueRejected`属性是`false`,你可以通过将 Future 的异常设置为`ImmediateRequeueException`来重写它,并且消息将被重新请求,<br/>如果侦听器方法中发生了某些异常,从而阻止了异步结果对象的创建,你必须捕获该异常并返回一个适当的返回对象,该对象将导致消息被确认或重新请求。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 2.2.21、2.3.13、2.4.1 开始,当检测到异步返回类型时,`AcknowledgeMode`将自动设置`MANUAL`。此外,带有致命异常的传入消息将被单独地负面确认,以前任何先前未确认的消息也被负面确认。

##### [](#threading)线程和异步消费者

异步消费者涉及许多不同的线程。

`SimpleMessageListenerContainer`中配置的`TaskExecutor`中的线程用于在`RabbitMQ Client`传递新消息时调用`MessageListener`。如果未配置,则使用`SimpleAsyncTaskExecutor`。如果使用池执行程序,则需要确保池大小足以处理配置的并发性。使用`DirectMessageListenerContainer`,在`RabbitMQ Client`线程上直接调用`MessageListener`。在这种情况下,`taskExecutor`用于监视消费者的任务。

|   |当使用默认`SimpleAsyncTaskExecutor`时,对于侦听器所调用的线程,侦听器容器`beanName``threadNamePrefix`中使用。<br/>这对于日志分析很有用。<br/>我们通常建议始终在日志附录配置中包括线程名称。<br/>`TaskExecutor`通过容器上的`taskExecutor`属性特别提供时,它被原封不动地使用,<br/>建议你使用类似的技术来命名由自定义`TaskExecutor` Bean 定义创建的线程,以帮助在日志消息中进行线程标识。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在创建连接时,在`CachingConnectionFactory`中配置的`Executor`被传递到`RabbitMQ Client`中,其线程用于将新消息传递到侦听器容器。如果未对此进行配置,则客户机将使用一个内部线程池执行器,每个连接的池大小(在编写时)为`Runtime.getRuntime().availableProcessors() * 2`

如果你有大量的工厂或者正在使用`CacheMode.CONNECTION`,那么你可能希望考虑使用带有足够线程的共享`ThreadPoolTaskExecutor`来满足你的工作负载。

|   |使用`DirectMessageListenerContainer`,你需要确保连接工厂配置了一个任务执行器,该执行器具有足够的线程来支持跨使用该工厂的所有侦听器容器的所需并发性。<br/>缺省池大小(在编写本文时)为`Runtime.getRuntime().availableProcessors() * 2`。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

`RabbitMQ client`使用`ThreadFactory`为低级 I/O 操作创建线程。要修改这个工厂,你需要配置底层的 RabbitMQ`ConnectionFactory`,如[配置底层客户机连接工厂](#connection-factory)中所讨论的那样。

##### [](#choose-container)选择容器

2.0 版引入了`DirectMessageListenerContainer`。在此之前,只有`SimpleMessageListenerContainer`可用。SMLC 为每个使用者使用一个内部队列和一个专用线程。如果一个容器被配置为监听多个队列,那么将使用同一个使用者线程来处理所有队列。并发性由`concurrentConsumers`和其他属性控制。当消息从 RabbitMQ 客户机到达时,客户机线程通过队列将消息传递给消费者线程。之所以需要这种架构,是因为在 RabbitMQ 客户端的早期版本中,不可能实现多个并发交付。较新版本的客户机具有修订后的线程模型,现在可以支持并发。这允许引入 DMLC,现在在 RabbitMQ 客户端线程上直接调用侦听器。因此,它的架构实际上比 SMLC“更简单”。然而,这种方法有一些局限性,并且 SMLC 的某些功能在 DMLC 中不可用。此外,并发性由`consumersPerQueue`(以及客户库的线程池)控制。`concurrentConsumers`和相关属性在此容器中不可用。

以下特性可用于 SMLC,但不适用于 DMLC:

* `batchSize`:使用 SMLC,你可以将其设置为控制事务中传递的消息数量或减少 ACK 的数量,但它可能会导致失败后重复传递的数量增加。(DMLC 确实有`messagesPerAck`,你可以使用它来减少 ACK,与`batchSize`和 SMLC 相同,但它不能用于事务——每个消息都在单独的事务中传递和 ACK’d)。

* `consumerBatchEnabled`:在消费者中启用离散消息的批处理;有关更多信息,请参见[消息侦听器容器配置](#containerAttributes)

* `maxConcurrentConsumers`和使用者缩放间隔或触发器——DMLC 中没有自动缩放。但是,它确实允许你以编程方式更改`consumersPerQueue`属性,并对消费者进行相应的调整。

然而,与 SMLC 相比,DMLC 有以下好处:

* 在运行时添加和删除队列更有效。使用 SMLC,整个使用者线程将被重新启动(所有使用者将被取消并重新创建)。使用 DMLC,不受影响的消费者不会被取消。

* 避免了 RabbitMQ 客户端线程和使用者线程之间的上下文切换。

* 线程在消费者之间共享,而不是在 SMLC 中为每个消费者拥有一个专用线程。但是,请参见[线程和异步消费者](#threading)中有关连接工厂配置的重要注释。

有关将哪些配置属性应用于每个容器的信息,请参见[消息侦听器容器配置](#containerAttributes)

##### [](#idle-containers)检测空闲异步消费者

尽管效率很高,但异步用户的一个问题是检测它们何时空闲——如果一段时间内没有消息到达,用户可能希望采取一些措施。

从版本 1.6 开始,现在可以将侦听器容器配置为在一段时间之后没有消息传递的情况下发布`ListenerContainerIdleEvent`。当容器处于空闲状态时,每`idleEventInterval`毫秒就会发布一个事件。

要配置此功能,请在容器上设置`idleEventInterval`。下面的示例展示了如何在 XML 和 Java 中这样做(对于`SimpleMessageListenerContainer``SimpleRabbitListenerContainerFactory`):

```
<rabbit:listener-container connection-factory="connectionFactory"
        ...
        idle-event-interval="60000"
        ...
        >
    <rabbit:listener id="container1" queue-names="foo" ref="myListener" method="handle" />
</rabbit:listener-container>
```

```
@Bean
public SimpleMessageListenerContainer(ConnectionFactory connectionFactory) {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    ...
    container.setIdleEventInterval(60000L);
    ...
    return container;
}
```

```
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(rabbitConnectionFactory());
    factory.setIdleEventInterval(60000L);
    ...
    return factory;
}
```

在每种情况下,当容器处于空闲状态时,每分钟都会发布一次事件。

###### [](#event-consumption)事件消耗

你可以通过实现`ApplicationListener`来捕获空闲事件——它可以是一个普通的侦听器,也可以是一个窄到只接收这个特定事件的侦听器。还可以使用 Spring Framework4.2 中介绍的`@EventListener`

下面的示例将`@RabbitListener``@EventListener`合并为一个类。你需要理解,应用程序侦听器获取所有容器的事件,因此,如果你想根据哪个容器空闲来采取特定的操作,你可能需要检查侦听器 ID。你也可以为此目的使用`@EventListener``condition`

这些事件有四个属性:

* `source`:侦听器容器实例

* `id`:侦听器 ID(或容器 Bean 名称)

* `idleTime`:事件发布时容器处于空闲状态的时间

* `queueNames`:容器监听的队列的名称

下面的示例展示了如何同时使用`@RabbitListener``@EventListener`注释来创建侦听器:

```
public class Listener {

    @RabbitListener(id="someId", queues="#{queue.name}")
    public String listen(String foo) {
        return foo.toUpperCase();
    }

    @EventListener(condition = "event.listenerId == 'someId'")
    public void onApplicationEvent(ListenerContainerIdleEvent event) {
        ...
    }

}
```

|   |事件侦听器看到所有容器的事件。<br/>因此,在前面的示例中,我们根据侦听器 ID 缩小接收到的事件的范围。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------|

|   |如果希望使用 IDLE 事件停止 Lister 容器,则不应在调用侦听器的线程上调用`container.stop()`<br/>这样做总是会导致延迟和不必要的日志消息。,相反,<br/>,你应该将事件传递给一个不同的线程,然后该线程可以停止容器。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#micrometer)监视侦听器性能

从版本 2.2 开始,如果在类路径上检测到`Micrometer`,并且在应用程序上下文中存在`MeterRegistry`,则侦听器容器将自动为侦听器创建和更新微米计`Timer`s。可以通过将容器属性`micrometerEnabled`设置为`false`来禁用计时器。

两个计时器被维护-一个用于对听者的成功调用,另一个用于失败调用。对于一个简单的`MessageListener`,每个配置的队列都有一对计时器。

计时器名为`spring.rabbitmq.listener`,并具有以下标记:

* `listenerId`:(侦听器 ID 或容器 Bean 名称)

* `queue`:(当`consumerBatchEnabled``true`时,一个简单侦听器或配置的队列名称列表的队列名称是`true`-因为批处理可能包含来自多个队列的消息)

* `result`:`success``failure`

* `exception`:`none``ListenerExecutionFailedException`

可以使用`micrometerTags`容器属性添加其他标记。

#### [](#containers-and-broker-named-queues)4.1.7。容器和以代理命名的队列

虽然使用`AnonymousQueue`实例作为自动删除队列是可取的,但从版本 2.1 开始,你可以使用带有侦听器容器的代理命名队列。下面的示例展示了如何做到这一点:

```
@Bean
public Queue queue() {
    return new Queue("", false, true, true);
}

@Bean
public SimpleMessageListenerContainer container() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(cf());
    container.setQueues(queue());
    container.setMessageListener(m -> {
        ...
    });
    container.setMissingQueuesFatal(false);
    return container;
}
```

请注意名称的空`String`。当`RabbitAdmin`声明队列时,它使用代理返回的名称更新`Queue.actualName`属性。在配置容器以使其工作时,必须使用`setQueues()`,以便容器可以在运行时访问声明的名称。仅仅设置名字是不够的。

|   |在容器运行时,不能将以代理命名的队列添加到容器中。|
|---|----------------------------------------------------------------------------|

|   |当一个连接被重置并建立了一个新的连接时,新的队列将获得一个新的名称,<br/>由于在重新启动的容器和重新声明的队列之间存在竞争条件,因此将容器的`missingQueuesFatal`属性设置为`false`非常重要,因为容器最初可能会尝试重新连接到旧队列。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### [](#message-converters)4.1.8。消息转换器

`AmqpTemplate`还定义了用于发送和接收委托给`MessageConverter`的消息的几种方法。`MessageConverter`为每个方向提供了一个方法:一个用于转换**to**a`Message`,另一个用于转换**来自**a`Message`。请注意,当转换为`Message`时,除了对象之外,还可以提供属性。`object`参数通常对应于消息体。下面的清单显示了`MessageConverter`接口定义:

```
public interface MessageConverter {

    Message toMessage(Object object, MessageProperties messageProperties)
            throws MessageConversionException;

    Object fromMessage(Message message) throws MessageConversionException;

}
```

`AmqpTemplate`上的相关`Message`-发送方法比我们前面讨论的方法更简单,因为它们不需要`Message`实例。相反,`MessageConverter`负责“创建”每个`Message`,方法是将提供的对象转换为`Message`主体的字节数组,然后添加任何提供的`MessageProperties`。下面的清单显示了各种方法的定义:

```
void convertAndSend(Object message) throws AmqpException;

void convertAndSend(String routingKey, Object message) throws AmqpException;

void convertAndSend(String exchange, String routingKey, Object message)
    throws AmqpException;

void convertAndSend(Object message, MessagePostProcessor messagePostProcessor)
    throws AmqpException;

void convertAndSend(String routingKey, Object message,
    MessagePostProcessor messagePostProcessor) throws AmqpException;

void convertAndSend(String exchange, String routingKey, Object message,
    MessagePostProcessor messagePostProcessor) throws AmqpException;
```

在接收端,只有两种方法:一种接受队列名称,另一种依赖于模板的“queue”属性已设置。下面的清单显示了这两种方法的定义:

```
Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;
```

|   |在[异步消费者](#async-consumer)中提到的`MessageListenerAdapter`也使用了`MessageConverter`。|
|---|------------------------------------------------------------------------------------------------------------------|

##### [](#simple-message-converter)`SimpleMessageConverter`

`MessageConverter`策略的默认实现称为`SimpleMessageConverter`。这是`RabbitTemplate`的实例所使用的转换器,如果你没有显式地配置替代选项。它处理基于文本的内容、序列化的 Java 对象和字节数组。

###### [](#converting-from-a-message)从`Message`转换

如果输入`Message`的内容类型以“text”开头(例如,“text/plain”),则它还会检查 Content-Encoding 属性,以确定将`Message`Body 字节数组转换为 Java`String`时要使用的字符集。如果输入`Message`上没有设置内容编码属性,则默认情况下它使用 UTF-8 字符集。如果需要覆盖该默认设置,可以配置`SimpleMessageConverter`的实例,设置其`defaultCharset`属性,并将其注入`RabbitTemplate`实例。

如果输入`Message`的 Content-Type 属性值设置为“application/x-java-serialized-object”,则`SimpleMessageConverter`将尝试将字节数组反序列化为 Java 对象。虽然这对于简单的原型设计可能很有用,但我们不建议依赖 Java 序列化,因为它会导致生产者和消费者之间的紧密耦合。当然,它也排除了任何一方使用非 Java 系统的可能性。由于 AMQP 是一种线级协议,因此很不幸的是,在这样的限制下,它将失去很多这种优势。在接下来的两节中,我们将探索在不依赖 Java 序列化的情况下传递富领域对象内容的一些替代方案。

对于所有其他内容类型,`SimpleMessageConverter`直接以字节数组的形式返回`Message`正文内容。

有关重要信息,请参见[Java 反序列化](#java-deserialization)

###### [](#converting-to-a-message)转换为`Message`

当从任意 Java 对象转换为`Message`时,`SimpleMessageConverter`同样处理字节数组、字符串和可序列化实例。它将这些转换为字节(在字节数组的情况下,没有任何可转换的内容),并且相应地 SESContent-Type 属性。如果要转换的`Object`与这些类型之一不匹配,则`Message`正文为空。

##### [](#serializer-message-converter)`SerializerMessageConverter`

这种转换器类似于`SimpleMessageConverter`,只是它可以配置与其它 Spring 框架`Serializer``Deserializer`实现的`application/x-java-serialized-object`转换。

有关重要信息,请参见[Java 反序列化](#java-deserialization)

##### [](#json-message-converter)Jackson2jsonmessageconverter

本节介绍使用`Jackson2JsonMessageConverter`转换到`Message`和从`Message`转换的情况。它有以下几个部分:

* [转换为`Message`](#Jackson2jsonMessageConverter-to-Message)

* [转换自`Message`](#Jackson2jsonmessageconverter-from-message)

###### [](#Jackson2JsonMessageConverter-to-message)转换为`Message`

正如上一节中提到的,通常不建议依赖 Java 序列化。JSON(JavaScript Object Notation,JavaScript Object Notation,JavaScript Object Notation)是一种比较常见的替代方法,它在不同的语言和平台上更灵活、更可移植。转换器可以在任何`RabbitTemplate`实例上配置,以覆盖其对`SimpleMessageConverter`默认值的使用。`Jackson2JsonMessageConverter`使用`com.fasterxml.jackson`2.x 库。下面的示例配置`Jackson2JsonMessageConverter`:

```
<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
    <property name="connectionFactory" ref="rabbitConnectionFactory"/>
    <property name="messageConverter">
        <bean class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
            <!-- if necessary, override the DefaultClassMapper -->
            <property name="classMapper" ref="customClassMapper"/>
        </bean>
    </property>
</bean>
```

如上面所示,`Jackson2JsonMessageConverter`默认使用`DefaultClassMapper`。类型信息被添加到(并从)`MessageProperties`。如果入站消息在`MessageProperties`中不包含类型信息,但你知道期望的类型,则可以使用`defaultType`属性配置静态类型,如下例所示:

```
<bean id="jsonConverterWithDefaultType"
      class="o.s.amqp.support.converter.Jackson2JsonMessageConverter">
    <property name="classMapper">
        <bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
            <property name="defaultType" value="thing1.PurchaseOrder"/>
        </bean>
    </property>
</bean>
```

此外,你还可以从`*TypeId*`标头中的值提供自定义映射。下面的示例展示了如何做到这一点:

```
@Bean
public Jackson2JsonMessageConverter jsonMessageConverter() {
    Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
    jsonConverter.setClassMapper(classMapper());
    return jsonConverter;
}

@Bean
public DefaultClassMapper classMapper() {
    DefaultClassMapper classMapper = new DefaultClassMapper();
    Map<String, Class<?>> idClassMapping = new HashMap<>();
    idClassMapping.put("thing1", Thing1.class);
    idClassMapping.put("thing2", Thing2.class);
    classMapper.setIdClassMapping(idClassMapping);
    return classMapper;
}
```

现在,如果发送系统将头设置为`thing1`,转换器将创建一个`Thing1`对象,依此类推。有关从非 Spring 应用程序转换消息的完整讨论,请参见[Receiving JSON from Non-Spring Applications](#spring-rabbit-json)示例应用程序。

###### [](#Jackson2JsonMessageConverter-from-message)从`Message`转换

根据发送系统添加到头部的类型信息,将入站消息转换为对象。

在 1.6 之前的版本中,如果不存在类型信息,则转换将失败。从版本 1.6 开始,如果缺少类型信息,转换器将使用 Jackson 默认值(通常是映射)来转换 JSON。

此外,从版本 1.6 开始,当使用`@RabbitListener`注释(在方法上)时,推断的类型信息将添加到`MessageProperties`中。这使得转换器可以转换为目标方法的参数类型。这仅在存在一个没有注释的参数或一个带有`@Payload`注释的参数时才适用。在分析过程中忽略类型`Message`的参数。

|   |默认情况下,推断的类型信息将覆盖由发送系统创建的入站`*TypeId*`和相关标题<br/><br/>这将使接收系统自动转换为不同的域对象。<br/>这仅适用于此。如果参数类型是具体的(不是抽象的或接口的),或者它来自`java.util`包。<br/>在所有其他情况下,使用`*TypeId*`和相关的标题。<br/>在某些情况下,你可能希望重写缺省行为,并始终使用`*TypeId*`信息。,例如,<br/>,假设你有一个`@RabbitListener`,它接受一个`Thing1`参数,但是消息包含一个`Thing2`,该<br/>`Thing1`的子类(这是具体的)。<br/>推断出的类型将是不正确的。<br/>来处理这种情况,将`TYPE_ID`上的`TypePrecedence`属性设置为`TYPE_ID`,而不是默认`INFERRED`<br/><br/>(该属性实际上位于转换器的`DefaultJackson2JavaTypeMapper`上,但转换器<br/>上提供了一个设置器,以方便使用。)<br/>如果你注入一个自定义的类型映射器,你应该在映射器上设置属性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |当从`Message`转换时,传入的`MessageProperties.getContentType()`必须是符合 JSON 的(`contentType.contains("json")`用于检查)。<br/>从版本 2.2 开始,如果没有`contentType`属性,则假定`application/json`,或者它具有默认值`application/octet-stream`<br/>以恢复到以前的行为(返回一个未转换的`byte[]`),将转换器的`assumeSupportedContentType`属性设置为`false`<br/>如果不支持内容类型,则生成`WARN`日志消息`Could not convert incoming message with content-type […​]`,是发出的,并且`message.getBody()`按原样返回-作为`byte[]`<br/>因此,为了满足`Jackson2JsonMessageConverter`在消费者方面的要求,生产者必须添加`contentType`消息属性——例如,作为`application/json``text/x-json`,或者通过使用`Jackson2JsonMessageConverter`,它会自动设置标题。<br/>下面的清单显示了许多转换器调用:|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

```
@RabbitListener
public void thing1(Thing1 thing1) {...}

@RabbitListener
public void thing1(@Payload Thing1 thing1, @Header("amqp_consumerQueue") String queue) {...}

@RabbitListener
public void thing1(Thing1 thing1, o.s.amqp.core.Message message) {...}

@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<Foo> message) {...}

@RabbitListener
public void thing1(Thing1 thing1, String bar) {...}

@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<?> message) {...}
```

在前面清单中的前四种情况中,转换器尝试转换为`Thing1`类型。第五个示例是无效的,因为我们无法确定哪个参数应该接收消息负载。在第六个示例中,由于泛型类型是`WildcardType`,所以 Jackson 默认应用。

但是,你可以创建一个自定义转换器,并使用`targetMethod`消息属性来决定将 JSON 转换为哪种类型。

|   |只有在方法级别声明`@RabbitListener`注释时,才能实现这种类型推断。<br/>对于类级别`@RabbitListener`,转换后的类型用于选择调用哪个`@RabbitHandler`方法。<br/>由于这个原因,基础设施提供了`targetObject`属性,你可以在自定义的<br/>转换器中使用它来确定类型。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |从版本 1.6.11 开始,`Jackson2JsonMessageConverter`,因此,`DefaultJackson2JavaTypeMapper``DefaultClassMapper`)提供`trustedPackages`选项,以克服[序列化小工具](https://pivotal.io/security/cve-2017-4995)漏洞。<br/>默认情况下,对于向后兼容,`Jackson2JsonMessageConverter`信任所有包——也就是说,它使用`*`作为选项。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###### [](#jackson-abstract)反序列化抽象类

在版本 2.2.8 之前,如果`@RabbitListener`的推断类型是抽象类(包括接口),则转换器将返回到头部中查找类型信息,如果存在,则使用该信息;如果不存在,则尝试创建抽象类。当使用自定义反序列化器来处理抽象类的自定义`ObjectMapper`时,但传入的消息具有无效的类型标头,这会导致一个问题。

从版本 2.2.8 开始,默认情况下保留以前的行为。如果你有这样的自定义`ObjectMapper`,并且希望忽略类型头,并且总是使用推断类型进行转换,那么将`alwaysConvertToInferredType`设置为`true`。这是向后兼容性所必需的,并且可以避免尝试转换失败时的开销(使用标准`ObjectMapper`)。

###### [](#data-projection)使用 Spring 数据投影接口

从版本 2.2 开始,你可以将 JSON 转换为 Spring 数据投影接口,而不是具体的类型。这允许对数据进行非常有选择性的、低耦合的绑定,包括从 JSON 文档中的多个位置查找值。例如,以下接口可以定义为消息有效负载类型:

```
interface SomeSample {

  @JsonPath({ "$.username", "$.user.name" })
  String getUsername();

}
```

```
@RabbitListener(queues = "projection")
public void projection(SomeSample in) {
    String username = in.getUsername();
    ...
}
```

默认情况下,访问器方法将用于在接收的 JSON 文档中查找属性名称 AS 字段。`@JsonPath`表达式允许定制值查找,甚至可以定义多个 JSON 路径表达式,从多个位置查找值,直到表达式返回实际值。

要启用此功能,请将消息转换器上的`useProjectionForInterfaces`设置为`true`。你还必须将`spring-data:spring-data-commons``com.jayway.jsonpath:json-path`添加到类路径。

当用作`@RabbitListener`方法的参数时,接口类型将作为正常类型自动传递给转换器。

###### [](#json-complex)从`Message`转换为`RabbitTemplate`

如前所述,类型信息在消息头中传递,以在从消息转换时协助转换器。这在大多数情况下都行得通。然而,当使用泛型类型时,它只能转换简单的对象和已知的“容器”对象(列表、数组和映射)。从版本 2.0 开始,`Jackson2JsonMessageConverter`实现了`SmartMessageConverter`,这使得它可以与新的`RabbitTemplate`方法一起使用,该方法接受`ParameterizedTypeReference`参数。这允许转换复杂的泛型类型,如下例所示:

```
Thing1<Thing2<Cat, Hat>> thing1 =
    rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference<Thing1<Thing2<Cat, Hat>>>() { });
```

|   |从版本 2.1 开始,`AbstractJsonMessageConverter`类已被删除。<br/>它不再是`Jackson2JsonMessageConverter`的基类。<br/>它已被`AbstractJackson2MessageConverter`取代。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#marshallingmessageconverter)`MarshallingMessageConverter`

还有一个选择是`MarshallingMessageConverter`。它委托给 Spring OXM 库的`Marshaller``Unmarshaller`策略接口的实现。你可以阅读有关该库的更多信息[here](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/oxm.html)。在配置方面,最常见的是仅提供构造函数参数,因为`Marshaller`的大多数实现也实现`Unmarshaller`。下面的示例展示了如何配置`MarshallingMessageConverter`:

```
<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
    <property name="connectionFactory" ref="rabbitConnectionFactory"/>
    <property name="messageConverter">
        <bean class="org.springframework.amqp.support.converter.MarshallingMessageConverter">
            <constructor-arg ref="someImplemenationOfMarshallerAndUnmarshaller"/>
        </bean>
    </property>
</bean>
```

##### [](#jackson2xml)`Jackson2XmlMessageConverter`

这个类是在版本 2.1 中引入的,可以用于将消息从 XML 转换为 XML。

`Jackson2XmlMessageConverter``Jackson2JsonMessageConverter`都具有相同的基类:`AbstractJackson2MessageConverter`

|   |引入`AbstractJackson2MessageConverter`类来替换已删除的类:`AbstractJsonMessageConverter`。|
|---|----------------------------------------------------------------------------------------------------------------------|

`Jackson2XmlMessageConverter`使用`com.fasterxml.jackson`2.x 库。

你可以用与`Jackson2JsonMessageConverter`相同的方式使用它,只是它支持 XML 而不是 JSON。下面的示例配置`Jackson2JsonMessageConverter`:

```
<bean id="xmlConverterWithDefaultType"
        class="org.springframework.amqp.support.converter.Jackson2XmlMessageConverter">
    <property name="classMapper">
        <bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
            <property name="defaultType" value="foo.PurchaseOrder"/>
        </bean>
    </property>
</bean>
```

有关更多信息,请参见[Jackson2JSONMessageConverter](#json-message-converter)

|   |从版本 2.2 开始,如果不存在`contentType`属性,或者它具有默认值`application/octet-stream`,则假定`application/xml`<br/>恢复到以前的行为(返回未转换的`byte[]`),将转换器的`assumeSupportedContentType`属性设置为`false`。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#contenttypedelegatingmessageconverter)`ContentTypeDelegatingMessageConverter`

这个类是在版本 1.4.2 中引入的,允许基于`MessageProperties`中的 Content type 属性将任务委托给特定的`MessageConverter`。默认情况下,如果没有`contentType`属性,或者有一个值与所有配置的转换器都不匹配,那么它将委托给`SimpleMessageConverter`。下面的示例配置`ContentTypeDelegatingMessageConverter`:

```
<bean id="contentTypeConverter" class="ContentTypeDelegatingMessageConverter">
    <property name="delegates">
        <map>
            <entry key="application/json" value-ref="jsonMessageConverter" />
            <entry key="application/xml" value-ref="xmlMessageConverter" />
        </map>
    </property>
</bean>
```

##### [](#java-deserialization)Java 反序列化

本节介绍如何反序列化 Java 对象。

|   |当从不受信任的源反序列化 Java 对象时,可能存在一个漏洞,<br/><br/>如果你接受来自不受信任的源的消息,并且`content-type`的值为`application/x-java-serialized-object`,你应该<br/>考虑配置哪些包和类被允许进行反序列化。<br/>这适用于`SimpleMessageConverter``SerializerMessageConverter`,当它被配置为隐式或通过配置使用`DefaultDeserializer`时。<br/>默认情况下,允许的列表为空,这意味着所有的类都是反序列化的。<br/><br/>你可以设置一个模式列表,例如`thing1.`**`thing1.thing2.Cat``.MySafeClass`<br/>在找到匹配之前,将按顺序检查模式。<br/>如果不匹配,则将不匹配,抛出一个`SecurityException`<br/><br/>可以在这些转换器上使用`allowedListPatterns`属性设置模式。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#message-properties-converters)消息属性转换器

`MessagePropertiesConverter`策略接口用于在兔子客户端`BasicProperties`和 Spring AMQP`MessageProperties`之间进行转换。默认的实现(`DefaultMessagePropertiesConverter`)对于大多数目的来说通常是足够的,但是如果需要,你可以实现自己的实现。当大小不大于`1024`字节时,默认属性转换器将类型`BasicProperties`的元素转换为`String`实例。较大的`LongString`实例未被转换(请参见下一段)。可以使用构造函数参数重写此限制。

从版本 1.6 开始,长于长字符串限制(默认值:1024)的头现在由`DefaultMessagePropertiesConverter`默认设置为`LongString`实例。你可以通过`getBytes[]``toString()``getStream()`方法访问内容。

以前,`DefaultMessagePropertiesConverter`将这样的标题“转换”为`DataInputStream`(实际上,它只引用了`LongString`实例的`DataInputStream`)。在输出时,这个头不会被转换(除了转换为字符串——例如,通过在流上调用`toString()``[[email protected]](/cdn-cgi/l/email-protection)`)。

大型传入`LongString`头现在也可以在输出上正确地“转换”(默认情况下)。

提供了一个新的构造函数,使你可以将转换器配置为像以前一样工作。下面的清单显示了该方法的 Javadoc 注释和声明:

```
/**
 * Construct an instance where LongStrings will be returned
 * unconverted or as a java.io.DataInputStream when longer than this limit.
 * Use this constructor with 'true' to restore pre-1.6 behavior.
 * @param longStringLimit the limit.
 * @param convertLongLongStrings LongString when false,
 * DataInputStream when true.
 * @since 1.6
 */
public DefaultMessagePropertiesConverter(int longStringLimit, boolean convertLongLongStrings) { ... }
```

同样从版本 1.6 开始,在`MessageProperties`中添加了一个名为`correlationIdString`的新属性。以前,当从 RabbitMQ 客户端使用的`BasicProperties`转换到和转换时,执行一个不必要的`byte[] <→ String`转换,因为`MessageProperties.correlationId`是一个`byte[]`,但是`BasicProperties`使用一个`String`。(最终,RabbitMQ 客户机使用 UTF-8 将`String`转换为字节以放入协议消息)。

为了提供最大的向后兼容性,在`DefaultMessagePropertiesConverter`中添加了一个名为`correlationIdPolicy`的新属性。这需要一个`DefaultMessagePropertiesConverter.CorrelationIdPolicy`枚举参数。默认情况下,它被设置为`BYTES`,它复制了之前的行为。

对于入站消息:

* `STRING`:仅映射`correlationIdString`属性

* `BYTES`:仅映射`correlationId`属性

* `BOTH`:两个属性都映射了

对于出站消息:

* `STRING`:仅映射`correlationIdString`属性

* `BYTES`:仅映射`correlationId`属性

* `BOTH`:这两个属性都被考虑,`String`属性优先。

同样从版本 1.6 开始,入站`deliveryMode`属性不再映射到`MessageProperties.deliveryMode`。它被映射到`MessageProperties.receivedDeliveryMode`。此外,入站`userId`属性不再映射到`MessageProperties.userId`。它被映射到`MessageProperties.receivedUserId`。如果出站消息使用相同的`MessageProperties`对象,则这些更改是为了避免这些属性的意外传播。

从版本 2.2 开始,`DefaultMessagePropertiesConverter`使用`getName()`而不是`toString()`转换类型为`Class<?>`的任何自定义标头;这避免了使用`toString()`表示来解析类名称的应用程序。对于滚动升级,你可能需要更改你的消费者来理解这两种格式,直到所有的生产者都升级了。

#### [](#post-processing)4.1.9。修改消息-压缩和更多

存在一些扩展点。它们允许你对消息执行一些处理,可以在消息发送到 RabbitMQ 之前,也可以在收到消息之后立即进行处理。

正如可以在[消息转换器](#message-converters)中看到的那样,这样的一个扩展点是在`AmqpTemplate``convertAndReceive`操作中,其中可以提供一个`MessagePostProcessor`。例如,在你的 POJO 被转换之后,`MessagePostProcessor`允许你在`Message`上设置自定义标题或属性。

从版本 1.4.2 开始,在`RabbitTemplate`-`setBeforePublishPostProcessors()``setAfterReceivePostProcessors()`中添加了额外的扩展点。第一个使后处理器能够在发送到 RabbitMQ 之前立即运行。在使用批处理时(参见[Batching](#template-batching)),这是在组装批处理之后和发送批处理之前调用的。第二个是在接收到消息后立即调用的。

这些扩展点用于诸如压缩的特征,并且为此目的,提供了几个`MessagePostProcessor`实现。`GZipPostProcessor``ZipPostProcessor``DeflaterPostProcessor`在发送消息之前压缩消息,并且`GUnzipPostProcessor``UnzipPostProcessor``InflaterPostProcessor`解压接收到的消息。

|   |从版本 2.1.5 开始,`GZipPostProcessor`可以配置`copyProperties = true`选项,以复制原始消息属性,默认情况下,由于性能原因,这些属性可以重用,并使用压缩内容编码和可选的`MessageProperties.SPRING_AUTO_DECOMPRESS`报头进行修改。<br/>如果保留对原始出站消息的引用,其属性也会发生变化。<br/>因此,如果你的应用程序使用这些消息后处理程序保留出站消息的副本,考虑打开`copyProperties`选项。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |从版本 2.2.12 开始,你可以配置压缩后置处理器在内容编码元素之间使用的分隔符。<br/>在版本 2.2.11 和之前,这是硬编码为`:`,现在默认设置为`, `<br/>解压程序将同时使用这两个分隔符。<br/>但是,如果你使用 2.3 或更高版本发布消息,而使用 2.2.11 或更高版本,则必须将压缩器上的`encodingDelimiter`属性设置为`:`<br/>当你的用户升级到 2.2.11 或更高版本时,你可以恢复到`, `的默认值。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

类似地,`SimpleMessageListenerContainer`也有一个`setAfterReceivePostProcessors()`方法,允许在容器接收到消息后执行解压缩。

从版本 2.1.4 开始,`addBeforePublishPostProcessors()``addAfterReceivePostProcessors()`已添加到`RabbitTemplate`中,以允许将新的后处理程序分别附加到发布前和接收后处理程序的列表中。还提供了删除后置处理器的方法。类似地,`AbstractMessageListenerContainer`还添加了`addAfterReceivePostProcessors()``removeAfterReceivePostProcessor()`方法。有关更多详细信息,请参见`RabbitTemplate``AbstractMessageListenerContainer`的 javadoc。

#### [](#request-reply)4.1.10。请求/回复消息

`AmqpTemplate`还提供了各种`sendAndReceive`方法,这些方法接受前面描述的用于单向发送操作的相同参数选项(`exchange``routingKey``Message`)。这些方法在请求-回复场景中非常有用,因为它们在发送之前处理必要的`reply-to`属性的配置,并且可以在内部为此目的创建的独占队列中侦听应答消息。

在将`MessageConverter`应用于请求和答复时,也可以使用类似的请求-答复方法。这些方法被命名为`convertSendAndReceive`。有关更多详细信息,请参见`AmqpTemplate`的[javadoc](https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframework/amqp/core/amqptemplate.html)。

从版本 1.5.0 开始,每个`sendAndReceive`方法变体都有一个重载版本,它接受`CorrelationData`。与正确配置的连接工厂一起,这将为发送端的操作启用发行者确认的接收。有关更多信息,请参见[相关发布者确认并返回](#template-confirms)和[javadoc for`RabbitOperations`](https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframework/amqp/rabbit/core/rabbitoperations.html)。

从版本 2.0 开始,这些方法有一些变体(`convertSendAndReceiveAsType`),它们接受一个额外的`ParameterizedTypeReference`参数来转换复杂的返回类型。模板必须配置为`SmartMessageConverter`。有关更多信息,请参见[从`Message`转换为`RabbitTemplate`]。

从版本 2.1 开始,你可以使用`noLocalReplyConsumer`选项配置`RabbitTemplate`,以控制用于回复消费者的`noLocal`标志。默认情况下,这是`false`

##### [](#reply-timeout)回复超时

默认情况下,发送和接收方法在 5 秒后超时并返回 null。你可以通过设置`replyTimeout`属性来修改此行为。从版本 1.5 开始,如果你将`mandatory`属性设置为`true`(或者对于特定的消息,`mandatory-expression`计算为`true`),如果无法将消息传递到队列,则将抛出`AmqpMessageReturnedException`。此异常具有`returnedMessage``replyCode``replyText`属性,以及用于发送的`exchange``routingKey`属性。

|   |此功能使用 Publisher Returns。<br/>你可以通过在`CachingConnectionFactory`上将`true`设置为`true`(参见[发布者确认并返回](#cf-pub-conf-ret))来启用它。<br/>此外,你还必须没有用`ReturnCallback`注册你自己的`ReturnCallback`。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 2.1.2 开始,添加了一个`replyTimedOut`方法,让子类被告知超时,以便它们可以清理任何保留的状态。

从版本 2.0.11 和 2.1.3 开始,当你使用默认的`DirectReplyToMessageListenerContainer`时,你可以通过设置模板的`replyErrorHandler`属性来添加错误处理程序。对于任何失败的交付,例如在没有相关标头的情况下收到的延迟回复和消息,都会调用此错误处理程序。传入的异常是`ListenerExecutionFailedException`,它具有`failedMessage`属性。

##### [](#direct-reply-to)RabbitMQ 直接回复-回复

|   |从 3.4.0 版本开始,RabbitMQ 服务器支持[直接回复](https://www.rabbitmq.com/direct-reply-to.html)<br/>这消除了固定回复队列的主要原因(以避免需要创建临时队列)对于每个请求)。<br/>以 Spring AMQP 版本 1.4.1 开始的直接回复默认情况下使用(如果服务器支持的话),而不是创建临时回复队列。<br/>当没有`replyQueue`被提供时(或者它的名称设置为`amq.rabbitmq.reply-to`),`RabbitTemplate`自动检测是否支持直接回复,并使用它或退回到使用临时回复队列。<br/>当使用直接回复时,不需要`reply-listener`,也不应进行配置。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

响应侦听器仍然支持命名队列(`amq.rabbitmq.reply-to`除外),允许控制响应并发等。

从版本 1.6 开始,如果你希望对每个回复使用一个临时的、排他的、自动删除队列,请将`useTemporaryReplyQueues`属性设置为`true`。如果设置`replyAddress`,则忽略此属性。

你可以通过子类化`RabbitTemplate`并重写`useDirectReplyTo()`来检查不同的条件,从而更改决定是否使用直接回复的条件。该方法仅在发送第一个请求时调用一次。

在版本 2.0 之前,`RabbitTemplate`为每个请求创建一个新的使用者,并在收到答复(或超时)时取消该使用者。现在,模板使用`DirectReplyToMessageListenerContainer`代替,让消费者被重用。该模板仍然负责将回复进行关联,因此不存在将延迟的回复发送给其他发件人的风险。如果要恢复到以前的行为,请将`useDirectReplyToContainer`(使用 XML 配置时`direct-reply-to-container`)属性设置为 false。

`AsyncRabbitTemplate`没有这样的选项。当使用直接回复时,它总是使用`DirectReplyToContainer`作为回复。

从版本 2.3.7 开始,模板有一个新的属性`useChannelForCorrelation`。当这是`true`时,服务器不必将相关 ID 从请求消息头复制到回复消息。相反,用于发送请求的通道用于将答复与请求关联起来。

##### [](#message-correlation-with-a-reply-queue)与回复队列的消息相关性

当使用固定的应答队列(`amq.rabbitmq.reply-to`除外)时,必须提供相关数据,以便能够将应答与请求关联起来。见[RabbitMQ 远程过程调用](https://www.rabbitmq.com/tutorials/tutorial-six-java.html)。默认情况下,标准`correlationId`属性用于保存相关数据。但是,如果希望使用自定义属性来保存相关数据,则可以在 \<rabbit-template/\>上设置`correlation-key`属性。显式地将属性设置为`correlationId`与省略该属性相同。对于相关数据,客户机和服务器必须使用相同的报头。

|   |Spring AMQP 版本 1.1 对此数据使用了一个名为`spring_reply_correlation`的自定义属性。<br/>如果你希望用当前版本恢复到此行为(也许是为了与使用 1.1 的另一个应用程序保持兼容性),则必须将该属性设置为`spring_reply_correlation`。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

默认情况下,模板会生成自己的相关 ID(忽略用户提供的任何值)。如果你希望使用自己的相关 ID,请将`RabbitTemplate`实例的`userCorrelationId`属性设置为`true`

|   |相关 ID 必须是唯一的,以避免对请求返回错误答复的可能性。|
|---|---------------------------------------------------------------------------------------------------------|

##### [](#reply-listener)回复侦听器容器

当使用 3.4.0 之前的 RabbitMQ 版本时,将为每个回复使用一个新的临时队列。但是,可以在模板上配置单个回复队列,这样效率更高,还可以让你在该队列上设置参数。但是,在这种情况下,你还必须提供一个 \<reply-listener/\>子元素。这个元素为应答队列提供了一个侦听器容器,模板就是侦听器。在 \<listener-container/\>上允许的所有[消息侦听器容器配置](#containerAttributes)属性在元素上都是允许的,但`connection-factory``message-converter`除外,它们是从模板的配置中继承而来的。

|   |如果你运行应用程序的多个实例或使用多个`RabbitTemplate`实例,则**MUST**对每个实例使用唯一的回复队列,<br/>RabbitMQ 无法从队列中选择消息,因此,如果它们都使用相同的队列,每个实例都会竞争回复,而不一定会收到自己的回复。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例定义了一个带有连接工厂的 Rabbit 模板:

```
<rabbit:template id="amqpTemplate"
        connection-factory="connectionFactory"
        reply-queue="replies"
        reply-address="replyEx/routeReply">
    <rabbit:reply-listener/>
</rabbit:template>
```

虽然容器和模板共享一个连接工厂,但它们不共享一个通道。因此,请求和响应不是在同一个事务(如果是事务的话)中执行的。

|   |在 1.5.0 版本之前,`reply-address`属性是不可用的,<br/>回复总是通过使用默认的 Exchange 和`reply-queue`名称作为路由密钥来路由的,<br/>仍然是默认的,但是你现在可以指定新的`reply-address`属性。<br/>`reply-address`中的`reply-address`可以包含一个具有`<exchange>/<routingKey>`表单的地址,并且将回复路由到指定的交换并被路由到与路由密钥绑定的队列。<br/>`reply-address`的优先权高于`reply-queue`<br/>当仅使用`reply-address`时,`<reply-listener>`必须配置为单独的`<listener-container>`组件。<br/>`reply-address``reply-queue`(或`queues`属性在`<listener-container>`上)必须在逻辑上引用相同的队列。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在这种配置下,使用`SimpleListenerContainer`来接收回复,而`RabbitTemplate``MessageListener`。当使用`<rabbit:template/>`名称空间元素定义模板时,如前面的示例所示,解析器将模板中的容器和线定义为侦听器。

|   |当模板不使用固定的`replyQueue`(或正在使用直接回复—参见[RabbitMQ 直接回复](#direct-reply-to))时,不需要侦听器容器。<br/>直接`reply-to`是使用 RabbitMQ3.4.0 或更高版本时的首选机制。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

如果你将`RabbitTemplate`定义为`<bean/>`,或者使用`@Configuration`类将其定义为`@Bean`,或者当你以编程方式创建模板时,你需要自己定义并连接应答侦听器容器。如果没有做到这一点,模板将永远不会收到回复,最终会超时并返回 null 作为对`sendAndReceive`方法的调用的回复。

从版本 1.5 开始,`RabbitTemplate`检测是否已将其配置为`MessageListener`以接收回复。如果不是,则尝试用`IllegalStateException`发送和接收带有回复地址的消息失败(因为这些回复永远不会收到)。

此外,如果使用了简单的`replyAddress`(队列名称),则应答侦听器容器将验证它正在侦听具有相同名称的队列。如果回复地址是 Exchange 和 Routing Key,并且写入了调试日志消息,则无法执行此检查。

|   |在连接应答侦听器和模板时,重要的是要确保模板的`replyAddress`和容器的`queues`(或`queueNames`)属性指向相同的队列。<br/>模板将应答地址插入到出站消息`replyTo`属性中。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的清单展示了如何手动连接 bean 的示例:

```
<bean id="amqpTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
    <constructor-arg ref="connectionFactory" />
    <property name="exchange" value="foo.exchange" />
    <property name="routingKey" value="foo" />
    <property name="replyQueue" ref="replyQ" />
    <property name="replyTimeout" value="600000" />
    <property name="useDirectReplyToContainer" value="false" />
</bean>

<bean class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
    <constructor-arg ref="connectionFactory" />
    <property name="queues" ref="replyQ" />
    <property name="messageListener" ref="amqpTemplate" />
</bean>

<rabbit:queue id="replyQ" name="my.reply.queue" />
```

```
    @Bean
    public RabbitTemplate amqpTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        rabbitTemplate.setMessageConverter(msgConv());
        rabbitTemplate.setReplyAddress(replyQueue().getName());
        rabbitTemplate.setReplyTimeout(60000);
        rabbitTemplate.setUseDirectReplyToContainer(false);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer replyListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory());
        container.setQueues(replyQueue());
        container.setMessageListener(amqpTemplate());
        return container;
    }

    @Bean
    public Queue replyQueue() {
        return new Queue("my.reply.queue");
    }
```

`RabbitTemplate`中显示了一个完整的[这个测试用例](https://github.com/spring-projects/spring-amqp/tree/main/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/JavaConfigFixedReplyQueueTests.java)示例,该示例连接了一个固定的应答队列,以及一个处理请求并返回应答的“远程”侦听器容器。

|   |当回复超时(`replyTimeout`)时,`sendAndReceive()`方法返回 null。|
|---|--------------------------------------------------------------------------------------|

在版本 1.3.6 之前,对超时邮件的延迟回复只会被记录。现在,如果收到了一个延迟的回复,它将被拒绝(模板抛出一个`AmqpRejectAndDontRequeueException`)。如果回复队列被配置为将被拒绝的消息发送到死信交换,则可以检索该回复以供以后进行分析。要做到这一点,需要将一个队列与配置的死信交换绑定,其路由密钥与回复队列的名称相等。

有关配置死字的更多信息,请参见[RabbitMQ 死信文档](https://www.rabbitmq.com/dlx.html)。你还可以查看`FixedReplyQueueDeadLetterTests`测试用例。

##### [](#async-template)异步兔子模板

1.6 版引入了`AsyncRabbitTemplate`。它具有与[`AmqpTemplate`](#amqp-template)上的方法类似的`sendAndReceive`(和`convertSendAndReceive`)方法。然而,它们返回的不是阻塞,而是`ListenableFuture`

`sendAndReceive`方法返回一个`RabbitMessageFuture``convertSendAndReceive`方法返回一个`RabbitConverterFuture`

你可以稍后通过在 Future 上调用`get()`来同步检索结果,也可以注册一个与结果异步调用的回调。下面的清单显示了这两种方法:

```
@Autowired
private AsyncRabbitTemplate template;

...

public void doSomeWorkAndGetResultLater() {

    ...

    ListenableFuture<String> future = this.template.convertSendAndReceive("foo");

    // do some more work

    String reply = null;
    try {
        reply = future.get();
    }
    catch (ExecutionException e) {
        ...
    }

    ...

}

public void doSomeWorkAndGetResultAsync() {

    ...

    RabbitConverterFuture<String> future = this.template.convertSendAndReceive("foo");
    future.addCallback(new ListenableFutureCallback<String>() {

        @Override
        public void onSuccess(String result) {
            ...
        }

        @Override
        public void onFailure(Throwable ex) {
            ...
        }

    });

    ...

}
```

如果设置了`mandatory`并且消息无法传递,则 Future 抛出一个`ExecutionException`,原因为`AmqpMessageReturnedException`,该原因封装了返回的消息和有关返回的信息。

如果设置了`enableConfirms`,则 Future 有一个名为`confirm`的属性,它本身是一个`ListenableFuture<Boolean>`,带有`true`,表示成功发布。如果确认的 future 是`false`,则`RabbitFuture`还具有一个名为`nackCause`的属性,如果可用,则该属性包含失败的原因。

|   |如果在回复之后收到了发布者确认,则该发布者确认将被丢弃,因为该回复意味着成功发布。|
|---|-------------------------------------------------------------------------------------------------------------------|

你可以将模板上的`receiveTimeout`属性设置为超时回复(默认设置为`30000`-30 秒)。如果发生超时,则使用`AmqpReplyTimeoutException`完成 future。

模板实现`SmartLifecycle`。在存在挂起的回复时停止模板,将导致挂起的`Future`实例被取消。

从版本 2.0 开始,异步模板现在支持[直接回复](https://www.rabbitmq.com/direct-reply-to.html),而不是配置的回复队列。要启用此功能,请使用以下构造函数之一:

```
public AsyncRabbitTemplate(ConnectionFactory connectionFactory, String exchange, String routingKey)

public AsyncRabbitTemplate(RabbitTemplate template)
```

参见[RabbitMQ 直接回复](#direct-reply-to),以使用直接回复与同步`RabbitTemplate`

版本 2.0 引入了这些方法的变体(`convertSendAndReceiveAsType`),这些方法接受一个额外的`ParameterizedTypeReference`参数来转换复杂的返回类型。你必须使用`SmartMessageConverter`配置底层`RabbitTemplate`。有关更多信息,请参见[从`Message`转换为`RabbitTemplate`]。

##### [](#remoting) Spring 带有 AMQP 的远程控制

|   |该功能已被弃用,将在 3.0 中删除。<br/>它已被[处理异常](#annotation-error-handling)取代了很长一段时间,`returnExceptions`被设置为 true,并在发送端配置了`RemoteInvocationAwareMessageConverterAdapter`<br/>有关更多信息,请参见[处理异常](#annotation-error-handling)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Spring 框架具有一般的远程处理能力,允许使用各种传输的[远程过程调用](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/remoting.html)。 Spring-AMQP 支持类似的机制,在客户端上是`AmqpProxyFactoryBean`,在服务器上是`AmqpInvokerServiceExporter`。这为 AMQP 提供了 RPC。在客户端,使用`RabbitTemplate`作为对[earlier](#reply-listener)的描述。在服务器端,调用者(配置为`MessageListener`)接收消息,调用配置的服务,并使用入站消息的`replyTo`信息返回回复。

你可以将客户端工厂 Bean 注入任何 Bean(通过使用其`serviceInterface`)。然后,客户机可以调用代理上的方法,从而在 AMQP 上进行远程执行。

|   |对于默认的`MessageConverter`实例,方法参数和返回的值必须是`Serializable`的实例。|
|---|----------------------------------------------------------------------------------------------------------------------------|

在服务器端,`AmqpInvokerServiceExporter`同时具有`AmqpTemplate``MessageConverter`属性。目前,没有使用模板的`MessageConverter`。如果需要提供自定义消息转换器,则应该通过设置`messageConverter`属性来提供它。在客户端,你可以向`AmqpTemplate`添加自定义消息转换器,该消息转换器通过使用其`amqpTemplate`属性提供给`AmqpProxyFactoryBean`

下面的清单显示了示例客户机和服务器配置:

```
<bean id="client"
    class="org.springframework.amqp.remoting.client.AmqpProxyFactoryBean">
    <property name="amqpTemplate" ref="template" />
    <property name="serviceInterface" value="foo.ServiceInterface" />
</bean>

<rabbit:connection-factory id="connectionFactory" />

<rabbit:template id="template" connection-factory="connectionFactory" reply-timeout="2000"
    routing-key="remoting.binding" exchange="remoting.exchange" />

<rabbit:admin connection-factory="connectionFactory" />

<rabbit:queue name="remoting.queue" />

<rabbit:direct-exchange name="remoting.exchange">
    <rabbit:bindings>
        <rabbit:binding queue="remoting.queue" key="remoting.binding" />
    </rabbit:bindings>
</rabbit:direct-exchange>
```

```
<bean id="listener"
    class="org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter">
    <property name="serviceInterface" value="foo.ServiceInterface" />
    <property name="service" ref="service" />
    <property name="amqpTemplate" ref="template" />
</bean>

<bean id="service" class="foo.ServiceImpl" />

<rabbit:connection-factory id="connectionFactory" />

<rabbit:template id="template" connection-factory="connectionFactory" />

<rabbit:queue name="remoting.queue" />

<rabbit:listener-container connection-factory="connectionFactory">
    <rabbit:listener ref="listener" queue-names="remoting.queue" />
</rabbit:listener-container>
```

|   |`AmqpInvokerServiceExporter`只能处理格式正确的消息,例如从`AmqpProxyFactoryBean`发送的消息。<br/>如果收到无法解释的消息,将发送序列化的`RuntimeException`作为回复。<br/>如果消息没有`replyToAddress`属性,如果没有配置死信交换,则消息将被拒绝并永久丢失。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |默认情况下,如果无法传递请求消息,则调用线程最终超时,并抛出一个`RemoteProxyFailureException`<br/>默认情况下,超时为 5 秒。<br/>你可以通过在`RabbitTemplate`上设置`replyTimeout`属性来修改持续时间,<br/>从 1.5 版本开始,通过将`mandatory`属性设置为`true`并在连接工厂上启用返回(参见[发布者确认并返回](#cf-pub-conf-ret)),调用线程抛出一个`AmqpMessageReturnedException`<br/>查看[回复超时](#reply-timeout)以获取更多信息。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### [](#broker-configuration)4.1.11。配置代理

AMQP 规范描述了如何使用该协议在代理上配置队列、交换和绑定。在`org.springframework.amqp.core`包中的`AmqpAdmin`接口中存在这些操作(可从 0.8 规范和更高版本移植)。该类的 RabbitMQ 实现`RabbitAdmin`位于`org.springframework.amqp.rabbit.core`包中。

`AmqpAdmin`接口基于使用 Spring AMQP 域抽象,如以下清单所示:

```
public interface AmqpAdmin {

    // Exchange Operations

    void declareExchange(Exchange exchange);

    void deleteExchange(String exchangeName);

    // Queue Operations

    Queue declareQueue();

    String declareQueue(Queue queue);

    void deleteQueue(String queueName);

    void deleteQueue(String queueName, boolean unused, boolean empty);

    void purgeQueue(String queueName, boolean noWait);

    // Binding Operations

    void declareBinding(Binding binding);

    void removeBinding(Binding binding);

    Properties getQueueProperties(String queueName);

}
```

另见[作用域操作](#scoped-operations)

`getQueueProperties()`方法返回一些关于队列的有限信息(消息计数和消费者计数)。返回的属性的键在`RabbitTemplate``QUEUE_NAME``QUEUE_MESSAGE_COUNT``QUEUE_CONSUMER_COUNT`)中作为常量可用。[RabbitMQ REST API](#management-rest-api)`QueueInfo`对象中提供了更多的信息。

no-arg`declareQueue()`方法在代理上定义了一个具有自动生成的名称的队列。这个自动生成队列的附加属性是`exclusive=true``autoDelete=true``durable=false`

`declareQueue(Queue queue)`方法接受一个`Queue`对象,并返回声明的队列的名称。如果所提供的`name``Queue`属性是空的`String`,则代理将使用生成的名称声明队列。该名称将返回给调用者。该名称也被添加到`Queue``actualName`属性中。你只能通过直接调用`RabbitAdmin`以编程方式使用此功能。当在应用程序上下文中以声明方式定义队列时,管理员使用自动声明时,可以将 name 属性设置为`""`(空字符串)。然后,代理创建名称。从版本 2.1 开始,侦听器容器可以使用这种类型的队列。有关更多信息,请参见[容器和以代理命名的队列](#containers-and-broker-named-queues)

这与`AnonymousQueue`相反,该框架生成唯一的(`UUID`)名称,并将`durable`设置为`false``exclusive``autoDelete`设置为`true`。带有空(或缺少)`<rabbit:queue/>`属性的`name`总是创建`AnonymousQueue`

参见[`AnonymousQueue`](#anonymous-queue)以了解为什么`AnonymousQueue`比代理生成的队列名称更受欢迎,以及如何控制名称的格式。从版本 2.1 开始,默认情况下,匿名队列的声明参数`Queue.X_QUEUE_LEADER_LOCATOR`设置为`client-local`。这确保了队列是在应用程序连接的节点上声明的。声明式队列必须具有固定的名称,因为它们可能在上下文的其他地方被引用——例如在以下示例中显示的侦听器中:

```
<rabbit:listener-container>
    <rabbit:listener ref="listener" queue-names="#{someQueue.name}" />
</rabbit:listener-container>
```

[交换、队列和绑定的自动声明](#automatic-declaration)

这个接口的 RabbitMQ 实现是`RabbitAdmin`,当使用 Spring XML 进行配置时,它类似于以下示例:

```
<rabbit:connection-factory id="connectionFactory"/>

<rabbit:admin id="amqpAdmin" connection-factory="connectionFactory"/>
```

`CachingConnectionFactory`缓存模式是`CHANNEL`(默认值)时,`RabbitAdmin`实现对在同一`ApplicationContext`中声明的队列、交换和绑定进行自动延迟声明。一旦向代理打开`Connection`,就会声明这些组件。有一些名称空间特性使其非常方便——例如,在 Stocks 示例应用程序中,我们有以下内容:

```
<rabbit:queue id="tradeQueue"/>

<rabbit:queue id="marketDataQueue"/>

<fanout-exchange name="broadcast.responses"
                 xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="tradeQueue"/>
    </bindings>
</fanout-exchange>

<topic-exchange name="app.stock.marketdata"
                xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="marketDataQueue" pattern="${stocks.quote.pattern}"/>
    </bindings>
</topic-exchange>
```

在前面的示例中,我们使用匿名队列(实际上,在内部,只使用由框架(而不是由代理)生成的名称的队列),并通过 ID 引用它们。我们还可以使用显式名称来声明队列,这些名称也可以作为上下文中其 Bean 定义的标识符。下面的示例使用显式名称配置队列:

```
<rabbit:queue name="stocks.trade.queue"/>
```

|   |你可以同时提供`id``name`属性。<br/>这允许你引用队列(例如,<br/>它还允许标准 Spring 特性(例如用于队列名称的属性占位符和 SPEL 表达式)。<br/>当你使用名称作为 Bean 标识符时,这些特性是不可用的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

队列可以配置额外的参数——例如,`x-message-ttl`。当你使用名称空间支持时,它们以参数-名称/参数-值对的`Map`形式提供,这是通过使用`<rabbit:queue-arguments>`元素定义的。下面的示例展示了如何做到这一点:

```
<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-dead-letter-exchange" value="myDLX"/>
        <entry key="x-dead-letter-routing-key" value="dlqRK"/>
    </rabbit:queue-arguments>
</rabbit:queue>
```

默认情况下,参数被假定为字符串。对于其他类型的参数,你必须提供该类型。下面的示例展示了如何指定类型:

```
<rabbit:queue name="withArguments">
    <rabbit:queue-arguments value-type="java.lang.Long">
        <entry key="x-message-ttl" value="100"/>
    </rabbit:queue-arguments>
</rabbit:queue>
```

当提供混合类型的参数时,你必须为每个条目元素提供类型。下面的示例展示了如何做到这一点:

```
<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-message-ttl">
            <value type="java.lang.Long">100</value>
        </entry>
        <entry key="x-dead-letter-exchange" value="myDLX"/>
        <entry key="x-dead-letter-routing-key" value="dlqRK"/>
    </rabbit:queue-arguments>
</rabbit:queue>
```

在 Spring Framework3.2 及更高版本中,可以更简洁地声明这一点,如下所示:

```
<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-message-ttl" value="100" value-type="java.lang.Long"/>
        <entry key="x-ha-policy" value="all"/>
    </rabbit:queue-arguments>
</rabbit:queue>
```

在使用 Java 配置时,`Queue.X_QUEUE_LEADER_LOCATOR`类上的`setLeaderLocator()`方法支持`Queue`参数作为第一类属性。从版本 2.1 开始,匿名队列的声明默认设置为`client-local`。这确保了队列是在应用程序连接到的节点上声明的。

|   |RabbitMQ 代理不允许声明具有不匹配参数的队列。<br/>例如,如果`queue`已经存在一个不带`time to live`参数的`queue`队列,并且尝试使用(例如)`key="x-message-ttl" value="100"`声明它,则抛出一个异常。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

默认情况下,当发生异常时,`RabbitAdmin`立即停止处理所有声明。这可能会导致下游问题,例如由于未声明另一个队列(在错误队列之后定义),侦听器容器无法初始化。

可以通过在`RabbitAdmin`实例上将`ignore-declaration-exceptions`属性设置为`true`来修改此行为。此选项指示`RabbitAdmin`记录异常并继续声明其他元素。当使用 Java 配置`RabbitAdmin`时,此属性称为`ignoreDeclarationExceptions`。这是一个适用于所有元素的全局设置。队列、交换和绑定具有类似的属性,仅适用于这些元素。

在版本 1.6 之前,此属性仅在通道上发生`IOException`时才生效,例如当前属性与期望属性之间存在不匹配时。现在,此属性对任何异常都生效,包括`TimeoutException`和其他异常。

此外,任何声明异常都会导致`DeclarationExceptionEvent`的发布,这是一个`ApplicationEvent`,可以由上下文中的任何`ApplicationListener`使用。该事件包含对管理、正在声明的元素和`Throwable`的引用。

##### [](#headers-exchange)headers exchange

从版本 1.3 开始,你可以将`HeadersExchange`配置为在多个头上匹配。你还可以指定是否必须匹配任何或所有标题。下面的示例展示了如何做到这一点:

```
<rabbit:headers-exchange name="headers-test">
    <rabbit:bindings>
        <rabbit:binding queue="bucket">
            <rabbit:binding-arguments>
                <entry key="foo" value="bar"/>
                <entry key="baz" value="qux"/>
                <entry key="x-match" value="all"/>
            </rabbit:binding-arguments>
        </rabbit:binding>
    </rabbit:bindings>
</rabbit:headers-exchange>
```

从版本 1.6 开始,你可以使用`internal`标志(默认为`false`)配置`Exchanges`,并且通过`RabbitAdmin`在代理上正确配置这样的`Exchange`(如果在应用程序上下文中存在一个)。如果用于交换的`internal`标志是`true`,则 RabbitMQ 不允许客户机使用该交换。这对于死信交换或交换到交换绑定非常有用,在这种情况下,你不希望发布者直接使用该交换。

要查看如何使用 Java 来配置 AMQP 基础架构,请查看股票示例应用程序,其中有`@Configuration``AbstractStockRabbitConfiguration`,它依次具有`RabbitClientConfiguration``RabbitServerConfiguration`子类。下面的清单显示了`AbstractStockRabbitConfiguration`的代码:

```
@Configuration
public abstract class AbstractStockAppRabbitConfiguration {

    @Bean
    public CachingConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory =
            new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory());
        template.setMessageConverter(jsonMessageConverter());
        configureRabbitTemplate(template);
        return template;
    }

    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public TopicExchange marketDataExchange() {
        return new TopicExchange("app.stock.marketdata");
    }

    // additional code omitted for brevity

}
```

在股票应用程序中,通过使用以下`@Configuration`类来配置服务器:

```
@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {

    @Bean
    public Queue stockRequestQueue() {
        return new Queue("app.stock.request");
    }
}
```

这是`@Configuration`类的整个继承链的结束。最终结果是在应用程序启动时向代理声明`TopicExchange``Queue`。在服务器配置中,没有将`TopicExchange`绑定到队列,这是在客户机应用程序中完成的。但是,股票请求队列会自动绑定到 AMQP 默认交换。此行为由规范定义。

客户机`@Configuration`类更有趣一些。其声明如下:

```
@Configuration
public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration {

    @Value("${stocks.quote.pattern}")
    private String marketDataRoutingKey;

    @Bean
    public Queue marketDataQueue() {
        return amqpAdmin().declareQueue();
    }

    /**
     * Binds to the market data exchange.
     * Interested in any stock quotes
     * that match its routing key.
     */
    @Bean
    public Binding marketDataBinding() {
        return BindingBuilder.bind(
                marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
    }

    // additional code omitted for brevity

}
```

客户机通过`AmqpAdmin`上的`declareQueue()`方法声明另一个队列。它使用一种路由模式将队列绑定到市场数据交换,该路由模式在属性文件中具体化。

##### [](#builder-api)用于队列和交换的 Builder API

版本 1.6 引入了一个方便的 Fluent API,用于在使用 Java 配置时配置`Queue``Exchange`对象。下面的示例展示了如何使用它:

```
@Bean
public Queue queue() {
    return QueueBuilder.nonDurable("foo")
        .autoDelete()
        .exclusive()
        .withArgument("foo", "bar")
        .build();
}

@Bean
public Exchange exchange() {
  return ExchangeBuilder.directExchange("foo")
      .autoDelete()
      .internal()
      .withArgument("foo", "bar")
      .build();
}
```

参见[`org.springframework.amqp.core.QueueBuilder`](https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframework/amqp/core/queuebuilder.html)和[`org.springframework.amqp.core.ExchangeBuilder`(https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframf/amqp/core/exchangebuilder.html)以获取更多信息。

从版本 2.0 开始,`ExchangeBuilder`现在默认情况下创建持久交换,以与单个`AbstractExchange`类上的简单构造函数保持一致。要与构建器进行非持久交换,在调用`.build()`之前使用`.durable(false)`。不再提供不带参数的`durable()`方法。

2.2 版引入了 Fluent API,以添加“众所周知的”交换和队列参数。

```
@Bean
public Queue allArgs1() {
    return QueueBuilder.nonDurable("all.args.1")
            .ttl(1000)
            .expires(200_000)
            .maxLength(42)
            .maxLengthBytes(10_000)
            .overflow(Overflow.rejectPublish)
            .deadLetterExchange("dlx")
            .deadLetterRoutingKey("dlrk")
            .maxPriority(4)
            .lazy()
            .leaderLocator(LeaderLocator.minLeaders)
            .singleActiveConsumer()
            .build();
}

@Bean
public DirectExchange ex() {
    return ExchangeBuilder.directExchange("ex.with.alternate")
            .durable(true)
            .alternate("alternate")
            .build();
}
```

##### [](#collection-declaration)声明交换、队列和绑定的集合

你可以在`Declarable`对象(`Queue``Exchange`,和`Binding`)的集合中包装`Declarables`对象。`RabbitAdmin`在应用程序上下文中检测此类 bean(以及离散`Declarable`bean),并在每次建立连接时(最初和连接失败后)在代理上声明所包含的对象。下面的示例展示了如何做到这一点:

```
@Configuration
public static class Config {

    @Bean
    public CachingConnectionFactory cf() {
        return new CachingConnectionFactory("localhost");
    }

    @Bean
    public RabbitAdmin admin(ConnectionFactory cf) {
        return new RabbitAdmin(cf);
    }

    @Bean
    public DirectExchange e1() {
        return new DirectExchange("e1", false, true);
    }

    @Bean
    public Queue q1() {
        return new Queue("q1", false, false, true);
    }

    @Bean
    public Binding b1() {
        return BindingBuilder.bind(q1()).to(e1()).with("k1");
    }

    @Bean
    public Declarables es() {
        return new Declarables(
                new DirectExchange("e2", false, true),
                new DirectExchange("e3", false, true));
    }

    @Bean
    public Declarables qs() {
        return new Declarables(
                new Queue("q2", false, false, true),
                new Queue("q3", false, false, true));
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Declarables prototypes() {
        return new Declarables(new Queue(this.prototypeQueueName, false, false, true));
    }

    @Bean
    public Declarables bs() {
        return new Declarables(
                new Binding("q2", DestinationType.QUEUE, "e2", "k2", null),
                new Binding("q3", DestinationType.QUEUE, "e3", "k3", null));
    }

    @Bean
    public Declarables ds() {
        return new Declarables(
                new DirectExchange("e4", false, true),
                new Queue("q4", false, false, true),
                new Binding("q4", DestinationType.QUEUE, "e4", "k4", null));
    }

}
```

|   |在 2.1 之前的版本中,你可以通过定义类型为`Collection<Declarable>`的 bean 来声明多个`Declarable`实例,<br/>在某些情况下,这可能会导致不良的副作用,因为管理员必须迭代所有`Collection<?>`bean。,<br/>现在禁用此功能,以支持`Declarables`,正如前面在这一节中所讨论的,<br/>你可以通过将`RabbitAdmin`属性设置为`declareCollections`来恢复到以前的行为。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

版本 2.2 将`getDeclarablesByType`方法添加到`Declarables`中;这可以作为一种方便,例如,在声明侦听器容器 Bean 时使用。

```
public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
        Declarables mixedDeclarables, MessageListener listener) {

    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    container.setQueues(mixedDeclarables.getDeclarablesByType(Queue.class).toArray(new Queue[0]));
    container.setMessageListener(listener);
    return container;
}
```

##### [](#conditional-declaration)条件声明

默认情况下,所有队列、交换和绑定都由应用程序上下文中的所有`RabbitAdmin`实例声明(假设它们具有`auto-startup="true"`)。

从版本 2.1.9 开始,`RabbitAdmin`有一个新的属性`explicitDeclarationsOnly`(默认情况下是`false`);当将其设置为`true`时,管理员将只声明显式配置为由该管理员声明的 bean。

|   |从 1.2 版本开始,你可以有条件地声明这些元素。<br/>当应用程序连接到多个代理并且需要指定应该用哪些代理声明特定元素时,这一点特别有用。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

表示这些元素的类实现`Declarable`,它有两个方法:`shouldDeclare()``getDeclaringAdmins()``RabbitAdmin`使用这些方法来确定特定实例是否应该实际处理其`Connection`上的声明。

这些属性可以作为名称空间中的属性使用,如以下示例所示:

```
<rabbit:admin id="admin1" connection-factory="CF1" />

<rabbit:admin id="admin2" connection-factory="CF2" />

<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />

<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />

<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />

<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />

<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />

<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
    <rabbit:bindings>
        <rabbit:binding key="foo" queue="bar"/>
    </rabbit:bindings>
</rabbit:direct-exchange>
```

|   |默认情况下,`auto-declare`属性是`true`,并且,如果`declared-by`没有提供(或者是空的),那么所有`RabbitAdmin`实例都会声明对象(只要管理员的`auto-startup`属性是`true`,默认的,并且管理员的`explicit-declarations-only`属性是假的)。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

类似地,你可以使用基于 Java 的`@Configuration`来实现相同的效果。在下面的示例中,组件由`admin1`声明,但不是由`admin2`声明:

```
@Bean
public RabbitAdmin admin1() {
    return new RabbitAdmin(cf1());
}

@Bean
public RabbitAdmin admin2() {
    return new RabbitAdmin(cf2());
}

@Bean
public Queue queue() {
    Queue queue = new Queue("foo");
    queue.setAdminsThatShouldDeclare(admin1());
    return queue;
}

@Bean
public Exchange exchange() {
    DirectExchange exchange = new DirectExchange("bar");
    exchange.setAdminsThatShouldDeclare(admin1());
    return exchange;
}

@Bean
public Binding binding() {
    Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null);
    binding.setAdminsThatShouldDeclare(admin1());
    return binding;
}
```

##### [](#note-id-name)关于`id`和`name`属性的注释

`<rabbit:queue/>``<rabbit:exchange/>`元素上的`name`属性反映了代理中实体的名称。对于队列,如果省略`name`,则创建一个匿名队列(参见[`AnonymousQueue`](#anonymous-queue))。

在 2.0 之前的版本中,`name`也被注册为 Bean 名称别名(类似于`name`上的`<bean/>`元素)。

这造成了两个问题:

* 它阻止了使用相同名称的队列和交换的声明。

* 如果别名包含 SPEL 表达式(`#{…​}`),则该别名将不会解析。

从版本 2.0 开始,如果你声明其中一个元素,同时带有`id`*和*一个`name`属性,则该名称将不再声明为 Bean name 别名。如果你希望声明一个队列并使用相同的`name`进行交换,则必须提供一个`id`

如果元素仅具有`name`属性,则不会发生更改。 Bean 仍然可以被`name`引用——例如,在绑定声明中。但是,如果名称包含 SPEL,则仍然不能引用它——你必须提供`id`以供引用。

##### [](#anonymous-queue)`AnonymousQueue`

通常,当你需要一个唯一命名的、排他的、自动删除的队列时,我们建议你使用`AnonymousQueue`而不是代理定义的队列名称(使用`""`作为`Queue`名称,使代理生成队列名称)。

这是因为:

1. 队列实际上是在建立到代理的连接时声明的。这是在豆子被创建并连接在一起之后很久的事情。使用队列的 bean 需要知道它的名称。实际上,在启动应用程序时,代理程序甚至可能不在运行。

2. 如果由于某种原因丢失了与代理的连接,则管理员将重新声明同名的`AnonymousQueue`。如果我们使用代理声明的队列,队列名称将会更改。

你可以控制`AnonymousQueue`实例使用的队列名称的格式。

默认情况下,队列名称的前缀是`spring.gen-`,后面跟着`UUID`的 base64 表示形式——例如:`spring.gen-MRBv9sqISkuCiPfOYfpo4g`

可以在构造函数参数中提供`AnonymousQueue.NamingStrategy`实现。下面的示例展示了如何做到这一点:

```
@Bean
public Queue anon1() {
    return new AnonymousQueue();
}

@Bean
public Queue anon2() {
    return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("something-"));
}

@Bean
public Queue anon3() {
    return new AnonymousQueue(AnonymousQueue.UUIDNamingStrategy.DEFAULT);
}
```

第一个 Bean 生成一个以`spring.gen-`为前缀的队列名称,后面跟着`UUID`的 base64 表示——例如:`spring.gen-MRBv9sqISkuCiPfOYfpo4g`。第二 Bean 生成以`something-`为前缀的队列名称,后跟`UUID`的 base64 表示。第三个 Bean 只使用 UUID(不使用 base64 转换)生成一个名称——例如,`f20c818a-006b-4416-bf91-643590fedb0e`

Base64 编码使用了 RFC4648 中的“URL 和文件名安全字母表”。删除尾随填充字符(`=`)。

你可以提供自己的命名策略,从而可以在队列名称中包括其他信息(例如应用程序名称或客户端主机)。

在使用 XML 配置时,可以指定命名策略。对于实现`AnonymousQueue.NamingStrategy`的 Bean 引用,在`<rabbit:queue>`元素上存在`naming-strategy`属性。以下示例展示了如何以各种方式指定命名策略:

```
<rabbit:queue id="uuidAnon" />

<rabbit:queue id="springAnon" naming-strategy="uuidNamer" />

<rabbit:queue id="customAnon" naming-strategy="customNamer" />

<bean id="uuidNamer" class="org.springframework.amqp.core.AnonymousQueue.UUIDNamingStrategy" />

<bean id="customNamer" class="org.springframework.amqp.core.AnonymousQueue.Base64UrlNamingStrategy">
    <constructor-arg value="custom.gen-" />
</bean>
```

第一个示例创建了`spring.gen-MRBv9sqISkuCiPfOYfpo4g`之类的名称。第二个示例使用 UUID 的字符串表示形式创建名称。第三个示例创建了`custom.gen-MRBv9sqISkuCiPfOYfpo4g`之类的名称。

你还可以提供自己的命名策略 Bean。

从版本 2.1 开始,默认情况下,匿名队列的声明参数`Queue.X_QUEUE_LEADER_LOCATOR`设置为`client-local`。这确保了队列是在应用程序连接的节点上声明的。在构造实例之后,你可以通过调用`queue.setLeaderLocator(null)`来恢复到以前的行为。

##### [](#declarable-recovery)恢复自动删除声明

通常,`RabbitAdmin`(s)只恢复在应用程序上下文中声明为 bean 的队列/交换/绑定;如果任何此类声明是自动删除的,则如果连接丢失,代理将删除它们。当重新建立连接时,管理员将重新声明这些实体。通常,通过调用`admin.declareQueue(…​)``admin.declareExchange(…​)``admin.declareBinding(…​)`创建的实体将不会被恢复。

从版本 2.4 开始,管理人员有一个新的属性`redeclareManualDeclarations`;如果为真,管理人员将恢复这些实体以及应用程序上下文中的 bean。

如果调用`deleteQueue(…​)``deleteExchange(…​)``removeBinding(…​)`,则不会执行单个声明的恢复。删除队列和交换时,将从可恢复实体中删除相关的绑定。

最后,调用`resetAllManualDeclarations()`将阻止恢复任何先前声明的实体。

#### [](#broker-events)4.1.12。代理事件监听器

当启用[事件交换插件 Name](https://www.rabbitmq.com/event-exchange.html)时,如果将类型`BrokerEventListener`的 Bean 添加到应用程序上下文中,则它将所选的代理事件发布为`BrokerEvent`实例,该实例可以通过正常的 Spring `ApplicationListener``@EventListener`方法来使用。事件由代理发布到主题交换`amq.rabbitmq.event`,每个事件类型都有不同的路由密钥。侦听器使用事件键,用于将`AnonymousQueue`绑定到交换,以便侦听器仅接收选定的事件。由于这是一个主题交换,所以可以使用通配符(以及显式地请求特定事件),如下例所示:

```
@Bean
public BrokerEventListener eventListener() {
    return new BrokerEventListener(connectionFactory(), "user.deleted", "channel.#", "queue.#");
}
```

通过使用普通 Spring 技术,可以进一步缩小单个事件侦听器中接收到的事件的范围,如下例所示:

```
@EventListener(condition = "event.eventType == 'queue.created'")
public void listener(BrokerEvent event) {
    ...
}
```

#### [](#delayed-message-exchange)4.1.13。延迟的消息交换

版本 1.6 引入了对[延迟消息交换插件 Name](https://www.rabbitmq.com/blog/2015/04/16/scheduling-messages-with-rabbitmq/)的支持

|   |该插件目前被标记为试验性的,但已经有一年多的时间可以使用了(在撰写本文时),<br/>如果需要对该插件进行更改,我们计划尽快添加对此类更改的支持,由于这个原因,<gt r=“2337”/, Spring AMQP 中的这种支持也应该被认为是实验性的。<br/>此功能已在 RabbitMQ3.6.0 和版本 0.0.1 的插件中进行了测试。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

要使用`RabbitAdmin`来声明一个 Exchange 为延迟,可以将 Exchange Bean 上的`delayed`属性设置为`true``RabbitAdmin`使用交换类型(`Direct``Fanout`,以此类推)来设置`x-delayed-type`参数,并用类型`x-delayed-message`声明交换。

当使用 XML 配置 Exchange bean 时,`delayed`属性(默认:`false`)也可用。下面的示例展示了如何使用它:

```
<rabbit:topic-exchange name="topic" delayed="true" />
```

要发送延迟消息,可以通过`MessageProperties`设置`x-delay`头,如下例所示:

```
MessageProperties properties = new MessageProperties();
properties.setDelay(15000);
template.send(exchange, routingKey,
        MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build());
```

```
rabbitTemplate.convertAndSend(exchange, routingKey, "foo", new MessagePostProcessor() {

    @Override
    public Message postProcessMessage(Message message) throws AmqpException {
        message.getMessageProperties().setDelay(15000);
        return message;
    }

});
```

要检查消息是否延迟,请在`MessageProperties`上使用`getReceivedDelay()`方法。它是一个单独的属性,以避免意外传播到由输入消息生成的输出消息。

#### [](#management-rest-api)4.1.14。RabbitMQ REST API

启用管理插件后,RabbitMQ 服务器公开一个 REST API 来监视和配置代理。a[API 的 Java 绑定](https://github.com/rabbitmq/hop)现已提供。`com.rabbitmq.http.client.Client`是一个标准的、直接的 API,因此是阻塞的 API。它是基于[Spring Web](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#spring-web)模块及其`RestTemplate`实现的。另一方面,`com.rabbitmq.http.client.ReactorNettyClient`是一个基于[反应堆网状结构](https://projectreactor.io/docs/netty/release/reference/docs/index.html)项目的反应式、非阻塞实现。

跳依赖项(`com.rabbitmq:http-client`)现在也是`optional`

有关更多信息,请访问他们的 Javadoc。

#### [](#exception-handling)4.1.15。异常处理

使用 RabbitMQ Java 客户机的许多操作都可以抛出检查过的异常。例如,在很多情况下`IOException`实例可能会被抛出。`RabbitTemplate``SimpleMessageListenerContainer`和其他 Spring AMQP 组件捕获这些异常,并将它们转换为`AmqpException`层次结构中的一个异常。这些在’org.springframework.amqp’包中定义,而`AmqpException`是层次结构的基础。

当侦听器抛出异常时,它被包装在`ListenerExecutionFailedException`中。通常情况下,代理会拒绝并重新请求消息。将`defaultRequeueRejected`设置为`false`会导致消息被丢弃(或路由到死信交换)。如[消息侦听器和异步情况](#async-listeners)中所讨论的,侦听器可以抛出一个`AmqpRejectAndDontRequeueException`(或`ImmediateRequeueAmqpException`)来有条件地控制此行为。

但是,有一类错误是侦听器无法控制行为的。当遇到无法转换的消息(例如,无效的`content_encoding`报头)时,在消息到达用户代码之前会抛出一些异常。将`defaultRequeueRejected`设置为`true`(默认)(或抛出`ImmediateRequeueAmqpException`),这样的消息将被一遍又一遍地重新传递。在版本 1.3.2 之前,用户需要编写一个自定义`ErrorHandler`,如[异常处理](#exception-handling)中所讨论的,以避免这种情况。

从版本 1.3.2 开始,默认的`ErrorHandler`现在是一个`ConditionalRejectingErrorHandler`,它拒绝(并且不请求)带有不可恢复错误的失败消息。具体地说,它拒绝具有以下错误的失败消息:

* `o.s.amqp…​MessageConversionException`:可以在使用`MessageConverter`转换传入消息有效负载时抛出。

* `o.s.messaging…​MessageConversionException`:如果在映射到`@RabbitListener`方法时需要额外的转换,则可以由转换服务抛出。

* `o.s.messaging…​MethodArgumentNotValidException`:如果在侦听器中使用验证(例如,`@Valid`)并且验证失败,则可以抛出。

* `o.s.messaging…​MethodArgumentTypeMismatchException`:如果入站消息被转换为针对目标方法不正确的类型,则可以抛出该消息。例如,参数被声明为`Message<Foo>`,但是`Message<Bar>`被接收。

* `java.lang.NoSuchMethodException`:在版本 1.6.3 中添加。

* `java.lang.ClassCastException`:在版本 1.6.3 中添加。

你可以使用`FatalExceptionStrategy`配置此错误处理程序的实例,以便用户可以为条件消息拒绝提供自己的规则——例如,来自 Spring Retry([消息侦听器和异步情况](#async-listeners))的`BinaryExceptionClassifier`的委托实现。此外,`ListenerExecutionFailedException`现在有一个`failedMessage`属性,你可以在决策中使用它。如果`FatalExceptionStrategy.isFatal()`方法返回`true`,则错误处理程序抛出一个`AmqpRejectAndDontRequeueException`。当异常被确定为致命异常时,默认`FatalExceptionStrategy`会记录一条警告消息。

自版本 1.6.3 以来,将用户异常添加到致命异常列表的一种方便的方法是子类`ConditionalRejectingErrorHandler.DefaultExceptionStrategy`并覆盖`isUserCauseFatal(Throwable cause)`方法,以返回`true`的致命异常。

处理 DLQ 消息的一种常见模式是在这些消息上设置`time-to-live`以及附加的 DLQ 配置,以便这些消息过期并路由回主队列进行重试。这种技术的问题在于,导致致命异常的消息会永远循环。从版本 2.1 开始,`ConditionalRejectingErrorHandler`检测消息上的`x-death`头,该头将导致抛出一个致命的异常。该消息已被记录并丢弃。通过将`ConditionalRejectingErrorHandler`上的`discardFatalsWithXDeath`属性设置为`false`,可以恢复到以前的行为。

|   |从版本 2.1.9 开始,具有这些致命异常的消息将被拒绝,并且默认情况下不会重新请求,即使容器确认模式是手动的。<br/>这些异常通常发生在调用侦听器之前,因此侦听器没有机会对消息进行 ACK 或 NACK,因此消息仍处于未 ACKED 状态。<br/>以恢复到先前的行为,将`ConditionalRejectingErrorHandler`上的`rejectManual`属性设置为`false`。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### [](#transactions)4.1.16。交易

Spring Rabbit Framework 具有对同步和异步用例中的自动事务管理的支持,其具有许多不同的语义,这些语义可以通过声明方式进行选择,这是 Spring 事务的现有用户所熟悉的。这使得许多即使不是最常见的消息传递模式也很容易实现。

有两种方法可以向框架发出所需的事务语义的信号。在`RabbitTemplate``SimpleMessageListenerContainer`中,都有一个标志`channelTransacted`,如果`true`,它告诉框架使用事务通道,并以提交或回滚(取决于结果)结束所有操作(发送或接收),并发出回滚的异常信号。另一个信号是提供具有 Spring 的`PlatformTransactionManager`实现之一的外部事务作为正在进行的操作的上下文。如果在框架发送或接收消息时已经有一个事务在进行中,并且`channelTransacted`标志是`true`,则消息事务的提交或回滚将推迟到当前事务结束时进行。如果`channelTransacted`标志是`false`,则消息传递操作不会应用事务语义(它是自动 ACKED 的)。

`channelTransacted`标志是一个配置时间设置。在创建 AMQP 组件时(通常是在应用程序启动时),对它进行一次声明和处理。外部事务在原则上是更动态的,因为系统在运行时响应当前线程状态。然而,在实践中,当事务以声明方式分层到应用程序上时,它通常也是一个配置设置。

对于使用`RabbitTemplate`的同步用例,外部事务由调用方提供,可以是声明式的,也可以是命令式的(通常的 Spring 事务模型)。下面的示例展示了一种声明式方法(通常更受欢迎,因为它是非侵入性的),其中模板已配置为`channelTransacted=true`:

```
@Transactional
public void doSomething() {
    String incoming = rabbitTemplate.receiveAndConvert();
    // do some more database processing...
    String outgoing = processInDatabaseAndExtractReply(incoming);
    rabbitTemplate.convertAndSend(outgoing);
}
```

在前面的示例中,在标记为`@Transactional`的方法中,作为消息体接收、转换和发送`String`有效负载。如果数据库处理出现异常而失败,则传入消息将返回给代理,而传出消息将不会发送。这适用于事务性方法链中带有`RabbitTemplate`的任何操作(例如,除非直接对`Channel`进行操作以尽早提交事务)。

对于带有`SimpleMessageListenerContainer`的异步用例,如果需要一个外部事务,则容器在设置侦听器时必须请求它。为了表示需要外部事务,用户在配置容器时向容器提供`PlatformTransactionManager`的实现。下面的示例展示了如何做到这一点:

```
@Configuration
public class ExampleExternalTransactionAmqpConfiguration {

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setTransactionManager(transactionManager());
        container.setChannelTransacted(true);
        container.setQueueName("some.queue");
        container.setMessageListener(exampleListener());
        return container;
    }

}
```

在前面的示例中,事务管理器被添加为从另一个 Bean 定义注入的依赖项(未示出),并且`channelTransacted`标志也被设置为`true`。其结果是,如果侦听器发生异常而失败,事务将被回滚,消息也将返回给代理。值得注意的是,如果事务未能提交(例如,由于数据库约束错误或连接问题),AMQP 事务也将回滚,并将消息返回给代理。这有时被称为“尽最大努力 1 阶段提交”,是可靠消息传递的一个非常强大的模式。如果在前面的示例中将`channelTransacted`标志设置为`false`(默认值),则仍将为侦听器提供外部事务,但是所有消息传递操作都将被自动 ACK,因此其效果是即使在业务操作的回滚时也提交消息传递操作。

##### [](#conditional-rollback)条件回滚

在 1.6.6 版本之前,在使用外部事务管理器(例如 JDBC)时,向容器的`transactionAttribute`添加回滚规则不会产生任何效果。异常总是回滚事务。

此外,当在容器的建议链中使用[交易建议](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/transaction.html#transaction-declarative)时,条件回滚不是很有用,因为所有侦听器异常都包装在`ListenerExecutionFailedException`中。

第一个问题已经得到纠正,规则现在得到了适当的应用。而且,现在提供了`ListenerFailedRuleBasedTransactionAttribute`。它是`RuleBasedTransactionAttribute`的一个子类,唯一的区别是它知道`ListenerExecutionFailedException`,并将这种异常的原因用于规则。这个事务属性可以直接在容器中使用,也可以通过事务通知使用。

下面的示例使用了这个规则:

```
@Bean
public AbstractMessageListenerContainer container() {
    ...
    container.setTransactionManager(transactionManager);
    RuleBasedTransactionAttribute transactionAttribute =
        new ListenerFailedRuleBasedTransactionAttribute();
    transactionAttribute.setRollbackRules(Collections.singletonList(
        new NoRollbackRuleAttribute(DontRollBackException.class)));
    container.setTransactionAttribute(transactionAttribute);
    ...
}
```

##### [](#transaction-rollback)关于回滚接收消息的说明

AMQP 事务只适用于发送给代理的消息和 ACK。因此,当对 Spring 事务进行回滚并且已经接收到消息时, Spring AMQP 不仅必须回滚该事务,而且还必须手动拒绝该消息(有点像 nack,但这不是规范所称的那样)。对消息拒绝所采取的操作独立于事务,并且依赖于`defaultRequeueRejected`属性(默认值:`true`)。有关拒绝失败消息的更多信息,请参见[消息侦听器和异步情况](#async-listeners)

有关 RabbitMQ 事务及其限制的更多信息,请参见[RabbitMQ 代理语义](https://www.rabbitmq.com/semantics.html)

|   |在 RabbitMQ2.7.0 之前,这样的消息(以及在通道关闭或中止时未加控制的消息)会被发送到 Rabbit Broker 上的队列的后面。<br/>自 2.7.0 以来,被拒绝的消息会被发送到队列的前面,其方式与 JMS 回滚消息类似。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |以前,在本地事务回滚和提供`TransactionManager`时,对事务回滚的消息请求是不一致的。<br/>在前一种情况下,应用正常的请求逻辑(`AmqpRejectAndDontRequeueException``defaultRequeueRejected=false`)(参见[消息侦听器和异步情况](#async-listeners))。<br/>与事务管理器,从版本 2.0 开始,该行为是一致的,并且在这两种情况下都应用了正常的请求逻辑。<br/>要恢复到以前的行为,你可以将容器的`alwaysRequeueWithTxManagerRollback`属性设置为`true`<br/>参见[消息侦听器容器配置](#containerAttributes)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#using-rabbittransactionmanager)使用`RabbitTransactionManager`

[RabbitTransactionManager](https://docs.spring.io/spring-amqp/docs/latest_ga/api/org/springframework/amqp/rabbit/transaction/RabbitTransactionManager.html)是在外部事务中执行 Rabbit 操作并与之同步的一种替代方法。此事务管理器是[`PlatformTransactionManager`](https://DOCS. Spring.io/ Spring/DOCS/current/javadoc-api/org/springframework/transactionmanager.html)接口的实现,应该与单个 Rabbit`ConnectionFactory`一起使用。

|   |这种策略不能提供 XA 事务——例如,为了在消息传递和数据库访问之间共享事务。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------|

需要应用程序代码来通过`ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean)`检索事务性 Rabbit 资源,而不是随后创建通道的标准`Connection.createChannel()`调用。当使用 Spring AMQP 的[RabbitTemplate](https://docs.spring.io/spring-amqp/docs/latest_ga/api/org/springframework/amqp/rabbit/core/RabbitTemplate.html)时,它将自动检测线程绑定通道并自动参与其事务。

使用 Java 配置,你可以通过使用以下 Bean 设置一个新的 RabbitTransActionManager:

```
@Bean
public RabbitTransactionManager rabbitTransactionManager() {
    return new RabbitTransactionManager(connectionFactory);
}
```

如果你更喜欢 XML 配置,那么可以在 XML 应用程序上下文文件中声明以下内容 Bean:

```
<bean id="rabbitTxManager"
      class="org.springframework.amqp.rabbit.transaction.RabbitTransactionManager">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>
```

##### [](#tx-sync)事务同步

将 RabbitMQ 事务与其他事务(例如 DBMS)同步提供了“Best Effort One Phase Commit”语义。在事务同步的完成后阶段,RabbitMQ 事务可能无法提交。这是由`spring-tx`基础架构作为错误记录的,但不会向调用代码抛出异常。从版本 2.3.10 开始,你可以在事务在处理该事务的同一线程上提交后调用`ConnectionUtils.checkAfterCompletion()`。如果没有发生异常,它将简单地返回;否则它将抛出一个`AfterCompletionFailedException`,该属性将具有表示完成的同步状态的属性。

通过调用`ConnectionFactoryUtils.enableAfterCompletionFailureCapture(true)`来启用此功能;这是一个全局标志,适用于所有线程。

#### [](#containerAttributes)4.1.17。消息侦听器容器配置

对于配置与事务和服务质量相关的`SimpleMessageListenerContainer``DirectMessageListenerContainer`有相当多的选项,其中一些选项相互交互。适用于 SMLC、DMLC 或`StreamListenerContainer`(见[使用 RabbitMQ 流插件](#stream-support))的属性由相应列中的复选标记指示。有关帮助你决定哪个容器适合你的应用程序的信息,请参见[选择容器](#choose-container)

下表显示了使用名称空间配置`<rabbit:listener-container/>`时的容器属性名称及其等效属性名称(在括号中)。该元素上的`type`属性可以是`simple`(默认)或`direct`,以分别指定`SMLC``DMLC`。名称空间不公开某些属性。这些由属性的`N/A`表示。

|                                                  Property<br/>(Attribute)                                                   |说明|              SMLC              |              DMLC              |              StLC              |
|-----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|--------------------------------|--------------------------------|
|                                         []()[`ackTimeout`](#ackTimeout)  <br/>(N/A)                                         |当`messagesPerAck`被设置时,这个超时被用作发送 ACK 的替代方法。<br/>当一个新的消息到达时,未加 ACK 的消息的计数与`messagesPerAck`进行比较,并且将自上次 ACK 的时间与该值进行比较。<br/>如果任一条件是`true`,当没有新的消息到达并且有未加锁的消息时,这个超时是近似的,因为每个条件只检查`monitorInterval`<br/>在这个表中还可以看到`messagesPerAck``monitorInterval`。|                                |![tickmark](images/tickmark.png)|                                |
|                                []()[`acknowledgeMode`](#acknowledgeMode)  <br/>(acknowledge)                                |*`NONE`:未发送 ACK(与`channelTransacted=true`不兼容),<br/>RabbitMQ 将此称为“自动 ACK”,因为代理假定所有消息都是 ACK 的,而不需要消费者的任何操作。<br/><br/>*`MANUAL`:侦听器必须通过调用`Channel.basicAck()`来确认所有消息。<br/><br/>*`AUTO`:容器自动确认消息,除非`MessageListener`抛出异常。<br/>注意,`acknowledgeMode``channelTransacted`是互补的——如果通道被处理,代理除了 ACK 之外还需要一个提交通知。<br/>这是默认模式。<br/>参见`batchSize`。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                   []()[`adviceChain`](#adviceChain)  <br/>(advice-chain)                                    |将 AOP 通知的数组应用到侦听器执行。<br/>这可以用于应用额外的横切关注点,例如在代理死亡的情况下自动重试。<br/>注意,在 AMQP 错误之后的简单重新连接由`CachingConnectionFactory`处理,只要经纪人还活着。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                         []()[`afterReceivePostProcessors`](#afterReceivePostProcessors)  <br/>(N/A)                         |在调用侦听器之前调用的`MessagePostProcessor`实例的数组。<br/>POST 处理器可以实现`PriorityOrdered``Ordered`<br/>该数组与上次调用的未排序成员进行排序。<br/>如果 POST 处理器返回`null`,则该消息将被丢弃(并在适当的情况下得到确认)。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                 []()[`alwaysRequeueWithTxManagerRollback`](#alwaysRequeueWithTxManagerRollback)  <br/>(N/A)                 |设置为`true`,以便在配置事务管理器时始终请求回滚消息。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                   []()[`autoDeclare`](#autoDeclare)  <br/>(auto-declare)                                    |当设置为`true`(默认)时,容器使用`RabbitAdmin`来重新声明所有 AMQP 对象(队列、交换、绑定),如果它在启动过程中检测到至少有一个队列丢失,可能是因为它是`auto-delete`或过期的队列,但是如果队列由于任何原因丢失,则重新声明继续进行。<br/>要禁用此行为,将此属性设置为`false`<br/>注意,如果缺少所有队列,则容器将无法启动。<br/><br/>|   |Prior to version 1.6, if there was more than one admin in the context, the container would randomly select one.<br/>If there were no admins, it would create one internally.<br/>In either case, this could cause unexpected results.<br/>Starting with version 1.6, for `autoDeclare` to work, there must be exactly one `RabbitAdmin` in the context, or a reference to a specific instance must be configured on the container using the `rabbitAdmin` property.|<br/>|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                                                                                                             |在版本 1.6 之前,如果上下文中有一个以上的管理员,容器将随机选择一个。<br/>如果没有管理员,它将在内部创建一个。<br/>在这两种情况下,这都可能导致意外的结果。<br/>从版本 1.6 开始,对于`autoDeclare`来说,上下文中必须正好有一个`RabbitAdmin`,或者必须使用`rabbitAdmin`属性在容器上配置对特定实例的引用。|                                |                                |                                |
|                                   []()[`autoStartup`](#autoStartup)  <br/>(auto-startup)                                    |表示容器应该在`ApplicationContext`开始时启动(作为`SmartLifecycle`回调的一部分,回调发生在初始化所有 bean 之后),<br/>默认为`true`,但是,如果你的代理在启动时可能不可用,你可以将其设置为`false`,并在知道代理已经准备好时,稍后手动调用`start()`。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|
|                           []()[`batchSize`](#batchSize)  <br/>(transaction-size)<br/>(batch-size)                           |当与`acknowledgeMode`一起使用时,将其设置为`AUTO`,容器在发送 ACK 之前尝试处理多达这个数量的消息(等待每个消息到接收超时设置),<br/>这也是提交事务通道时的情况,<br/>如果`prefetchCount`小于`batchSize`,它被增加以匹配`batchSize`。|![tickmark](images/tickmark.png)|                                |                                |
|                                   []()[`batchingStrategy`](#batchingStrategy)  <br/>(N/A)                                   |删除消息时使用的策略。<br/>默认`SimpleDebatchingStrategy`<br/>参见[Batching](#template-batching)[@RabbitListener 与批处理](#receiving-batch)。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                          []()[`channelTransacted`](#channelTransacted)  <br/>(channel-transacted)                           |布尔标志表示所有消息都应该在事务中得到确认(手动或自动)。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                        []()[`concurrency`](#concurrency)  <br/>(N/A)                                        |`m-n`每个侦听器的并发消费者的范围。<br/>如果只提供`n``n`是固定数量的消费者。<br/>参见[监听器并发](#listener-concurrency)。|![tickmark](images/tickmark.png)|                                |                                |
|                            []()[`concurrentConsumers`](#concurrentConsumers)  <br/>(concurrency)                            |每个侦听器最初要启动的并发消费者的数量。<br/>参见[监听器并发](#listener-concurrency)。|![tickmark](images/tickmark.png)|                                |                                |
|                          []()[`connectionFactory`](#connectionFactory)  <br/>(connection-factory)                           |对`ConnectionFactory`的引用。<br/>在配置 byusing XML 名称空间时,默认引用的 Bean 名称是`rabbitConnectionFactory`。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                 []()[`consecutiveActiveTrigger`](#consecutiveActiveTrigger)  <br/>(min-consecutive-active)                  |当考虑启动一个新的使用者时,一个使用者在不发生接收超时的情况下接收的连续消息的最小数量。<br/>也受到“batchsize”的影响。<br/>参见[监听器并发](#listener-concurrency)<br/>默认值:10。|![tickmark](images/tickmark.png)|                                |                                |
|                    []()[`consecutiveIdleTrigger`](#consecutiveIdleTrigger)  <br/>(min-consecutive-idle)                     |消费者在考虑停止一个消费者之前必须经历的接收超时的最小数量。<br/>也受到“batchsize”的影响。<br/>参见[监听器并发](#listener-concurrency)<br/>默认值:10。|![tickmark](images/tickmark.png)|                                |                                |
|                          []()[`consumerBatchEnabled`](#consumerBatchEnabled)  <br/>(batch-enabled)                          |如果`MessageListener`支持它,将其设置为 true 可以对离散消息进行批处理,最多`batchSize`;如果`receiveTimeout`中没有新消息到达,则将交付部分批处理。<br/>如果这是 false,则仅支持由生产者创建的批处理;请参见[Batching](#template-batching)。|![tickmark](images/tickmark.png)|                                |                                |
|                                 []()[`consumerCustomizer`](#consumerCustomizer)  <br/>(N/A)                                 |用于修改由容器创建的流消费者的`ConsumerCustomizer` Bean。|                                |                                |![tickmark](images/tickmark.png)|
|                               []()[`consumerStartTimeout`](#consumerStartTimeout)  <br/>(N/A)                               |等待消费线程启动的时间(以毫秒为单位)。<br/>如果这段时间过了,如果配置的`taskExecutor`没有足够的线程来支持容器`concurrentConsumers`<br/><br/>可能发生这种情况的一个例子是,<br/>默认情况:60000(一分钟)。|![tickmark](images/tickmark.png)|                                |                                |
|                       []()[`consumerTagStrategy`](#consumerTagStrategy)  <br/>(consumer-tag-strategy)                       |设置[消费者策略](#consumerTags)的实现,以便为每个消费者创建一个(唯一的)标记。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                          []()[`consumersPerQueue`](#consumersPerQueue)  <br/>(consumers-per-queue)                          |为每个配置好的队列创建的消费者的数量。<br/>参见[监听器并发](#listener-concurrency)。|                                |![tickmark](images/tickmark.png)|                                |
|                                       []()[`consumeDelay`](#consumeDelay)  <br/>(N/A)                                       |当使用[RabbitMQ 分片插件](https://github.com/rabbitmq/rabbitmq-sharding)`concurrentConsumers > 1`时,存在一种竞争条件,可以防止消费者在碎片之间的均匀分布。<br/>使用此属性在消费者开始之间添加一个小的延迟,以避免这种竞争条件。<br/>你应该对值进行实验,以确定适合你的环境的延迟。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                  []()[`debatchingEnabled`](#debatchingEnabled)  <br/>(N/A)                                  |当为真时,侦听器容器将删除批处理的消息,并用批处理中的每条消息调用侦听器。,<br/>从 2.2.7 版本开始,如果侦听器是`BatchMessageListener``ChannelAwareBatchMessageListener`,则[生产者创建了批](#template-batching)将被取消为[生产者创建了批](#template-batching)。否则,批处理中的消息将一次显示一次。<br/>默认为真。<br/>参见[Batching](#template-batching)[@RabbitListener 与批处理](#receiving-batch)。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                         []()[`declarationRetries`](#declarationRetries)  <br/>(declaration-retries)                         |被动队列声明失败时重试尝试的次数。<br/>被动队列声明发生在使用者启动或从多个队列消费时,当初始化期间不是所有的队列都可用时。<br/>当重试用完后不能被动地声明(出于任何原因)配置的队列时,容器行为由前面描述的“missingqueuesfatal”属性控制。<br/>默认情况:三次重试(总共四次尝试)。|![tickmark](images/tickmark.png)|                                |                                |
|                      []()[`defaultRequeueRejected`](#defaultRequeueRejected)  <br/>(requeue-rejected)                       |确定是否应重新请求因侦听器抛出异常而被拒绝的消息。<br/>默认:`true`。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                  []()[`errorHandler`](#errorHandler)  <br/>(error-handler)                                  |对`ErrorHandler`策略的引用,该策略用于处理在执行 MessageListener 期间可能发生的任何未捕获的异常。<br/>默认:`ConditionalRejectingErrorHandler`|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                       []()[`exclusive`](#exclusive)  <br/>(exclusive)                                       |确定此容器中的单个使用者是否对队列具有独占访问权限。<br/>当这是`true`时,容器的并发性必须为 1,<br/>如果另一个使用者具有独占访问权限,则容器将尝试恢复该使用者,根据`recovery-interval``recovery-back-off`.<br/>使用名称空间时,该属性将与队列名称一起出现在`<rabbit:listener/>`元素上。<br/>默认:`false`。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                   []()[`exclusiveConsumerExceptionLogger`](#exclusiveConsumerExceptionLogger)  <br/>(N/A)                   |当独占使用者无法访问队列时使用的异常记录器。<br/>默认情况下,这是在`WARN`级别上记录的。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|   []()[`failedDeclarationRetryInterval`](#failedDeclarationRetryInterval)  <br/>(failed-declaration<br/>-retry-interval)    |被动队列声明重试尝试之间的间隔。<br/>被动队列声明发生在使用者启动时,或者在从多个队列消费时,当初始化期间不是所有队列都可用时。<br/>默认值:5000(5 秒)。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                  []()[`forceCloseChannel`](#forceCloseChannel)  <br/>(N/A)                                  |如果消费者在`shutdownTimeout`内没有对关机做出响应,如果这是`true`,则通道将被关闭,从而导致任何未被 ACKed 的消息被重新请求。<br/>自 2.0 起默认为`true`<br/>你可以将其设置为`false`以恢复到以前的行为。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                      []()[`globalQos`](#globalQos)  <br/>(global-qos)                                       |当为真时,`prefetchCount`将全局地应用于通道,而不是应用于通道上的每个消费者。<br/>有关更多信息,请参见[`basicQos.global`](https://WWW.rabbitmq.com/amqp-0-9-1-reference.html#basic.qos.global)。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                                           (group)                                                           |这仅在使用名称空间时可用。<br/>当指定时,类型`Collection<MessageListenerContainer>`的 Bean 被注册为该名称,并且每个<br/>元素的<br/>容器被添加到集合中。<br/>例如,这允许,通过迭代集合来启动和停止容器组。<br/>如果多个`<listener-container/>`元素具有相同的组值,集合形式中的容器<br/>是如此指定的所有容器的集合。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                          []()[`idleEventInterval`](#idleEventInterval)  <br/>(idle-event-interval)                          |见[检测空闲异步消费者](#idle-containers)。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                               []()[`javaLangErrorHandler`](#javaLangErrorHandler)  <br/>(N/A)                               |当容器线程捕获`Error`时调用的`AbstractMessageListenerContainer.JavaLangErrorHandler`实现。<br/>默认实现调用`System.exit(99)`;要恢复到以前的行为(什么都不做),请添加一个 no-op 处理程序。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                       []()[`maxConcurrentConsumers`](#maxConcurrentConsumers)  <br/>(max-concurrency)                       |如果需要,按需启动的最大并发消费者数量。<br/>必须大于或等于“concurrentConsumers”。<br/>参见[监听器并发](#listener-concurrency)。|![tickmark](images/tickmark.png)|                                |                                |
|                                     []()[`messagesPerAck`](#messagesPerAck)  <br/>(N/A)                                     |ACK 之间要接收的消息的数量。使用此方法可以减少发送给代理的 ACK 的数量(以增加重新交付消息的可能性为代价)。通常,你应该仅在大容量的侦听器容器上设置此属性。<br/>如果设置了此属性,并且拒绝了一条消息(抛出了异常),则确认了挂起的 ACK,并且拒绝了失败的消息。<br/>不允许使用已处理的通道。<br/>如果`prefetchCount`小于`messagesPerAck`,它被增加以匹配`messagesPerAck`<br/>默认值:ACK 每条消息。<br/>参见`ackTimeout`在此表中。|                                |![tickmark](images/tickmark.png)|                                |
|                    []()[`mismatchedQueuesFatal`](#mismatchedQueuesFatal)  <br/>(mismatched-queues-fatal)                    |当容器启动时,如果此属性是`true`(默认值:`false`),则容器将检查上下文中声明的所有队列是否与代理上已经存在的队列兼容。<br/>如果存在不匹配的属性(例如`auto-delete`)或参数(skuch 为`x-message-ttl`),容器(和应用程序上下文)无法以致命的异常启动。<br/><br/>如果在恢复过程中检测到问题(例如,在丢失连接之后),容器被停止。<br/><br/>在应用程序上下文中必须有一个`RabbitAdmin`(或通过使用`rabbitAdmin`属性在容器上专门配置的一个)。<br/>否则,该属性必须是`false`。<br/><br/>|   |If the broker is not available during initial startup, the container starts and the conditions are checked when the connection is established.|<br/>|---|----------------------------------------------------------------------------------------------------------------------------------------------|<br/><br/>|   |The check is done against all queues in the context, not just the queues that a particular listener is configured to use.<br/>If you wish to limit the checks to just those queues used by a container, you should configure a separate `RabbitAdmin` for the container, and provide a reference to it using the `rabbitAdmin` property.<br/>See [Conditional Declaration](#conditional-declaration) for more information.|<br/>|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|<br/><br/>|   |Mismatched queue argument detection is disabled while starting a container for a `@RabbitListener` in a bean that is marked `@Lazy`.<br/>This is to avoid a potential deadlock which can delay the start of such containers for up to 60 seconds.<br/>Applications using lazy listener beans should check the queue arguments before getting a reference to the lazy bean.|<br/>|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------||![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                                                                                                             |如果在初始启动期间代理不可用,则容器将启动,并在建立连接时检查条件。|                                |                                |                                |
|                                                                                                                             |检查是针对上下文中的所有队列进行的,而不仅仅是特定侦听器被配置使用的队列,<br/>如果你希望将检查仅限于容器使用的那些队列,那么你应该为容器配置一个单独的`RabbitAdmin`,并使用`rabbitAdmin`属性提供对它的引用。<br/>有关更多信息,请参见[有条件声明](#conditional-declaration)。|                                |                                |                                |
|                                                                                                                             |在 Bean 中为`@RabbitListener`启动容器时,禁用不匹配的队列参数检测这被标记为`@Lazy`<br/>这是为了避免潜在的死锁,这可能会将此类容器的启动延迟长达 60 秒。<br/>使用 lazy Listener bean 的应用程序应该在获得对 lazy 的引用之前检查队列参数 Bean。|                                |                                |                                |
|                        []()[`missingQueuesFatal`](#missingQueuesFatal)  <br/>(missing-queues-fatal)                         |当设置为`true`(默认)时,如果代理上没有可用的配置队列,则认为这是致命的,<br/>这会导致应用程序上下文在启动期间无法初始化,<br/>同样,在默认情况下,当容器运行时删除队列时,消费者进行三次重试以连接到队列(每隔五秒),并在尝试失败时停止容器。<br/><br/>这在以前的版本中是不可配置的。<br/><br/>当设置为`false`时,在进行三次重试之后,容器进入恢复模式,与其他问题一样,例如代理被关闭。<br/>容器尝试根据`recoveryInterval`属性进行恢复。<br/>在每次尝试恢复期间,每个使用者再次尝试四次,以五秒的间隔被动地声明队列。<br/>此过程将无限期地继续。<br/><br/>你还可以使用一个属性 Bean 来为所有容器全局地设置属性,如下:<br/><br/>```<br/><util:properties<br/>        id="spring.amqp.global.properties"><br/>    <prop key="mlc.missing.queues.fatal"><br/>        false<br/>    </prop><br/></util:properties><br/>```<br/><br/>此全局属性不应用于具有显式`missingQueuesFatal`属性集的任何容器。<br/><br/>默认的重试属性(五秒间隔三次重试)可以通过设置下面的属性被重写。<br/><gt="2714"/>|   |Missing queue detection is disabled while starting a container for a `@RabbitListener` in a bean that is marked `@Lazy`.<br/>This is to avoid a potential deadlock which can delay the start of such containers for up to 60 seconds.<br/>Applications using lazy listener beans should check the queue(s) before getting a reference to the lazy bean.|<br/>|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|                                                                                                                                                                                                                                        |![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                                                                                                             |在 Bean 中为`@RabbitListener`启动容器时,将禁用丢失的队列检测这被标记为`@Lazy`<br/>这是为了避免潜在的死锁,这可能会将此类容器的启动延迟长达 60 秒。<br/>使用 Lazy Listener Bean 的应用程序应该在获得对 Lazy 的引用之前检查队列 Bean。|                                |                                |                                |
|                             []()[`monitorInterval`](#monitorInterval)  <br/>(monitor-interval)                              |对于 DMLC,计划在此间隔时间运行一个任务,以监视使用者的状态并恢复任何失败的任务。|                                |![tickmark](images/tickmark.png)|                                |
|                                            []()[`noLocal`](#noLocal)  <br/>(N/A)                                            |设置为`true`,以禁用在同一通道的连接上发布的从服务器到消费者的消息传递。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                             []()[`phase`](#phase)  <br/>(phase)                                             |当`autoStartup``true`时,此容器应在其中开始和停止的生命周期阶段。<br/>值越低,此容器开始的时间越早,停止的时间越晚。<br/>默认值是`Integer.MAX_VALUE`,这意味着容器开始得越晚,停止得越快。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|[]()[`possibleAuthenticationFailureFatal`](#possibleAuthenticationFailureFatal)  <br/>(possible-authentication-failure-fatal)|当设置为`true`(SMLC 的默认值)时,如果在连接过程中抛出了`PossibleAuthenticationFailureException`,它被认为是致命的。<br/>这会导致应用程序上下文在启动期间无法初始化(如果容器配置为自动启动)。<br/><br/>*2.0 版本*<br/><br/>**directmessageListenerContainer****<br/><br/>(默认),当设置为`false`时,每个使用者将尝试根据`monitorInterval`重新连接。<br/><br/>**SimpleMessageListenerContainer**<br/><br/>当设置为`false`时,在进行 3 次重试之后,容器将进入恢复模式,与其他问题一样,例如代理正在关闭。<br/>容器将根据`recoveryInterval`属性尝试恢复。<br/>在每次尝试恢复期间,每个使用者将再次尝试 4 次以启动。<br/>此过程将无限期地继续。<br/><br/>你还可以使用一个属性 Bean 来全局设置所有容器的属性,如下所示:<br/><br/>```<br/><util:properties<br/>    id="spring.amqp.global.properties"><br/>  <prop<br/>    key="mlc.possible.authentication.failure.fatal"><br/>     false<br/>  </prop><br/></util:properties><br/>```<br/>此全局属性将不会应用于任何具有显式`missingQueuesFatal`属性集的容器。<br/>默认的重试属性(5 秒间隔 3 次重试)可以在此属性之后使用属性重写。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                   []()[`prefetchCount`](#prefetchCount)  <br/>(prefetch)                                    |在每个消费者处可能未完成的未确认消息的数量。<br/>该值越高,消息可以交付得越快,但非顺序处理的风险越高。<br/>如果`acknowledgeMode``NONE`,则忽略不计。<br/>如果有必要,要匹配`batchSize``messagePerAck`<br/>自 2.0 起默认值为 250。<br/>你可以将其设置为 1 以恢复到以前的行为。<br/><br/>|   |There are scenarios where the prefetch value should<br/>be low — for example, with large messages, especially if the processing is slow (messages could add up<br/>to a large amount of memory in the client process), and if strict message ordering is necessary<br/>(the prefetch value should be set back to 1 in this case).<br/>Also, with low-volume messaging and multiple consumers (including concurrency within a single listener container instance), you may wish to reduce the prefetch to get a more even distribution of messages across consumers.|<br/>|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|<br/><br/>Also see `globalQos`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                                                                                                             |在某些情况下,预取取值应该<br/>较低,例如,对于较大的消息,尤其是在处理速度较慢的情况下(消息可能会将<br/>累加到客户端进程中的大量内存中),如果需要严格的消息排序<br/>(在这种情况下,预取值应该设置为 1),<br/>还可以使用低容量消息传递和多个消费者(包括单个侦听器容器实例中的并发性),你可能希望减少预取,以使消息在消费者之间的分布更加均匀。|                                |                                |                                |
|                                       []()[`rabbitAdmin`](#rabbitAdmin)  <br/>(admin)                                       |当侦听器容器侦听至少一个自动删除队列时,发现该队列在启动过程中丢失,则该容器使用`RabbitAdmin`声明队列以及任何相关的绑定和交换,<br/>如果这些元素被配置为使用条件声明(参见[有条件声明](#conditional-declaration)),容器必须使用配置来声明这些元素的管理。<br/>在这里指定该管理。<br/>只有在使用带有条件声明的自动删除队列时才需要。<br/>如果你不希望在容器启动之前声明自动删除队列,在管理员上将`auto-startup`设置为`false`<br/>默认设置为一个`RabbitAdmin`,该命令声明所有无条件元素。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                               []()[`receiveTimeout`](#receiveTimeout)  <br/>(receive-timeout)                               |等待每条消息的最长时间。<br/>如果`acknowledgeMode=NONE`,这几乎没有影响——容器旋转并请求另一条消息,<br/>对于带有`Channel`的事务性`batchSize > 1`,它的影响最大,因为它可能导致在超时到期之前不确认已经使用的消息。<br/>`consumerBatchEnabled`为真时,如果在批处理完成之前发生超时,则将交付部分批处理。|![tickmark](images/tickmark.png)|                                |                                |
|                             []()[`recoveryBackOff`](#recoveryBackOff)  <br/>(recovery-back-off)                             |指定`BackOff`如果使用者由于非致命原因而无法启动,则尝试启动使用者之间的间隔。<br/>默认值为`FixedBackOff`,每五秒进行无限次重试。<br/>`recoveryInterval`互斥。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                            []()[`recoveryInterval`](#recoveryInterval)  <br/>(recovery-interval)                            |如果使用者由于非致命原因而无法启动,则确定尝试启动使用者之间的时间(以毫秒为单位)。<br/>默认值:5000。<br/>`recoveryBackOff`互斥。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|            []()[`retryDeclarationInterval`](#retryDeclarationInterval)  <br/>(missing-queue-<br/>retry-interval)            |如果在使用者初始化期间,配置队列的一个子集可用,则使用者开始从这些队列消费。<br/>使用者试图通过使用此间隔被动地声明丢失的队列。<br/>当此间隔过后,将再次使用“declarnationRetries”和“faileddeclarationRetrygt Interval”。,如果仍然存在丢失的队列,<br/>,消费者在再次尝试之前再次等待这个间隔。<br/>此过程将无限期地继续,直到所有队列都可用。<br/>默认值:60000(一分钟)。|![tickmark](images/tickmark.png)|                                |                                |
|                                    []()[`shutdownTimeout`](#shutdownTimeout)  <br/>(N/A)                                    |当容器关闭时(例如,<br/>如果其封闭的`ApplicationContext`已关闭),它将等待飞行中的消息被处理到这个限制。<br/>默认为 5 秒。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                   []()[`startConsumerMinInterval`](#startConsumerMinInterval)  <br/>(min-start-interval)                    |在按需启动每个新用户之前必须经过的毫秒时间。<br/>参见[监听器并发](#listener-concurrency)<br/>默认值:10000(10 秒)。|![tickmark](images/tickmark.png)|                                |                                |
|                      []()[`statefulRetryFatal`](#statefulRetryFatal)  <br/>WithNullMessageId<br/>(N/A)                      |当使用有状态重试建议时,如果接收到缺少`messageId`属性的消息,则默认情况下认为<br/>对消费者是致命的(它被停止)。<br/>将此设置为`false`以丢弃(或路由到死信队列)此类消息。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                     []()[`stopConsumerMinInterval`](#stopConsumerMinInterval)  <br/>(min-stop-interval)                     |当检测到空闲消费者时,从最后一个消费者停止到消费者停止之前必须经过的毫秒时间。<br/>参见[监听器并发](#listener-concurrency)<br/>默认值:60000(一分钟)。|![tickmark](images/tickmark.png)|                                |                                |
|                                    []()[`streamConverter`](#streamConverter)  <br/>(N/A)                                    |将本机流消息转换为 Spring AMQP 消息的`StreamMessageConverter`。|                                |                                |![tickmark](images/tickmark.png)|
|                                  []()[`taskExecutor`](#taskExecutor)  <br/>(task-executor)                                  |对用于执行侦听器调用程序的 Spring `TaskExecutor`(或标准 JDK1.5+`Executor`)的引用。<br/>默认值是`SimpleAsyncTaskExecutor`,使用内部管理线程。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |
|                                []()[`taskScheduler`](#taskScheduler)  <br/>(task-scheduler)                                 |在 DMLC 中,调度程序用来在“monitorinterval”运行监视任务。|                                |![tickmark](images/tickmark.png)|                                |
|                         []()[`transactionManager`](#transactionManager)  <br/>(transaction-manager)                         |外部事务管理器用于侦听器的操作。<br/>还补充了`channelTransacted`—如果`Channel`被处理,则其事务与外部事务同步。|![tickmark](images/tickmark.png)|![tickmark](images/tickmark.png)|                                |

#### [](#listener-concurrency)4.1.18。监听器并发

##### [](#simplemessagelistenercontainer)SimpleMessageListenerContainer

默认情况下,侦听器容器启动一个从队列接收消息的使用者。

在检查上一节中的表时,你可以看到许多控制并发性的属性和属性。最简单的是`concurrentConsumers`,它创建了并发处理消息的(固定的)消费者数量。

在 1.3.0 版本之前,这是唯一可用的设置,容器必须停止并重新启动才能更改设置。

自版本 1.3.0 以来,你现在可以动态调整`concurrentConsumers`属性。如果在容器运行时对其进行了更改,则会根据需要添加或删除消费者,以适应新的设置。

此外,还添加了一个名为`maxConcurrentConsumers`的新属性,并且容器根据工作负载动态地调整并发。这与四个附加属性一起工作:`consecutiveActiveTrigger``startConsumerMinInterval``consecutiveIdleTrigger``stopConsumerMinInterval`。在默认设置下,增加消费者的算法工作如下:

如果`maxConcurrentConsumers`尚未到达,并且一个现有的使用者连续十个周期处于活动状态,并且自上一个使用者启动以来至少已经过了 10 秒,则启动一个新的使用者。如果使用者在`batchSize`\*`receiveTimeout`毫秒内至少接收到一条消息,则被认为是活动的。

在默认设置下,减少消费者的算法工作如下:

如果有超过`concurrentConsumers`的运行并且一个消费者检测到连续十个超时(空闲)并且最后一个消费者在至少 60 秒前被停止,则消费者被停止。超时取决于`receiveTimeout``batchSize`属性。如果使用者在`batchSize`\*`receiveTimeout`毫秒内没有收到消息,则被认为是空闲的。因此,使用默认的超时(一秒)和`batchSize`的四个超时,在 40 秒的空闲时间(四个超时对应一个空闲检测)后考虑停止消费者。

|   |实际上,只有当整个容器闲置一段时间时,才可以停止使用消费者。<br/>这是因为代理在所有活动消费者之间共享其工作。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

无论配置队列的数量如何,每个使用者都使用一个通道。

从版本 2.0 开始,`concurrentConsumers``maxConcurrentConsumers`属性可以设置为`concurrency`属性——例如,`2-4`

##### [](#using-directmessagelistenercontainer)使用`DirectMessageListenerContainer`

在这个容器中,并发是基于配置的队列和`consumersPerQueue`。每个队列的每个使用者都使用一个单独的通道,并且并发性由 Rabbit 客户端库控制。默认情况下,在编写时,它使用`DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2`线程池。

你可以配置`taskExecutor`以提供所需的最大并发性。

#### [](#exclusive-consumer)4.1.19。独家消费者

从版本 1.3 开始,你可以使用一个独占使用者来配置侦听器容器。这可以防止其他容器从队列中消费,直到当前使用者被取消为止。这样的容器的并发性必须是`1`

当使用独占消费者时,其他容器尝试根据`recoveryInterval`属性从队列中消费,如果尝试失败,则记录`WARN`消息。

#### [](#listener-queues)4.1.20。监听器容器队列

版本 1.3 为处理侦听器容器中的多个队列引入了许多改进。

容器必须被配置为至少监听一个队列。以前也是这样,但现在可以在运行时添加和删除队列。当处理了任何预取的消息时,容器回收(取消和重新创建)消费者。对于`addQueues``addQueueNames``removeQueues``removeQueueNames`方法,请参见[Javadoc](https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.html)。在删除队列时,必须保留至少一个队列。

现在,如果消费者的队列中有任何一个可用,他就会启动。以前,如果有任何队列不可用,容器就会停止。现在,只有在没有队列可用的情况下才会出现这种情况。如果不是所有的队列都是可用的,那么容器将尝试每隔 60 秒被动地声明(并消耗)丢失的队列。

此外,如果使用者从代理接收到取消(例如,如果队列被删除),则使用者将尝试恢复,并且恢复的使用者将继续处理来自任何其他配置队列的消息。以前,一个队列上的取消会取消整个消费者,最终,由于缺少队列,容器将停止。

如果希望永久删除队列,则应在删除到队列之前或之后更新容器,以避免将来尝试使用它。

#### [](#resilience-recovering-from-errors-and-broker-failures)4.1.21。弹性:从错误和代理失败中恢复

Spring AMQP 提供的一些关键(也是最流行的)高级特性与在协议错误或代理失败的情况下的恢复和自动重新连接有关。我们已经在本指南中看到了所有相关的组件,但是在这里将它们集合在一起并单独列出特性和恢复场景应该会有所帮助。

主要的重新连接功能由`CachingConnectionFactory`本身启用。使用`RabbitAdmin`自动声明功能通常也是有益的。此外,如果你关心保证的交付,你可能还需要在`RabbitTemplate``SimpleMessageListenerContainer`中使用`channelTransacted`标志,在`AcknowledgeMode.AUTO`中使用`AcknowledgeMode.AUTO`(如果你自己进行 ACK,则使用手动)标志。

##### [](#automatic-declaration)交换、队列和绑定的自动声明

`RabbitAdmin`组件可以在启动时声明交换、队列和绑定。它通过`ConnectionListener`懒洋洋地做到了这一点。因此,如果代理在启动时不存在,这并不重要。第一次使用`Connection`(例如,通过发送消息)时,侦听器将触发并应用管理功能。在侦听器中执行自动声明的另一个好处是,如果由于任何原因(例如,代理死亡,网络故障和其他原因)而丢失连接,则在重新建立连接时再次应用这些声明。

|   |以这种方式声明的队列必须具有固定的名称——要么是显式声明的,要么是由`AnonymousQueue`实例的框架生成的。<br/>匿名队列是不可持久的、排他的和自动删除的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |只有当`CachingConnectionFactory`缓存模式为`CHANNEL`(默认)时,才会执行自动声明。<br/>存在此限制,因为独占和自动删除队列绑定到连接。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 2.2.2 开始,`RabbitAdmin`将检测类型为`DeclarableCustomizer`的 bean,并在实际处理声明之前应用该函数。这是有用的,例如,设置一个新的参数(属性)之前,它在框架内有第一类支持。

```
@Bean
public DeclarableCustomizer customizer() {
    return dec -> {
        if (dec instanceof Queue && ((Queue) dec).getName().equals("my.queue")) {
            dec.addArgument("some.new.queue.argument", true);
        }
        return dec;
    };
}
```

对于不提供直接访问`Declarable` Bean 定义的项目,它也很有用。

另见[RabbitMQ 自动连接/拓扑恢复](#auto-recovery)

##### [](#retry)同步操作失败和重试选项

如果在使用`RabbitTemplate`(例如)时,在同步序列中丢失了与代理的连接, Spring AMQP 将抛出一个`AmqpException`(通常但并非总是`AmqpIOException`)。我们不会试图掩盖存在问题的事实,因此你必须能够捕捉并响应异常。如果你怀疑连接丢失(而且这不是你的错误),最简单的方法是再次尝试该操作。你可以手动完成此操作,也可以使用 Spring Retry 来处理重试(强制地或声明地)。

Spring 重试提供了两个 AOP 拦截器和很大的灵活性来指定重试的参数(尝试次数、异常类型、退避算法和其他)。 Spring AMQP 还提供了一些方便的工厂 bean,用于为 AMQP 用例以方便的形式创建 Spring 重试拦截器,其具有可用于实现自定义恢复逻辑的强类型回调接口。有关更多详细信息,请参见`StatefulRetryOperationsInterceptor``StatelessRetryOperationsInterceptor`的 Javadoc 和属性。如果没有事务或者在重试回调中启动了事务,则无状态重试是合适的。请注意,无状态重试比有状态重试更容易配置和分析,但是如果有一个正在进行的事务必须回滚或者肯定要回滚,那么它通常是不合适的。事务中间的断开连接应该具有与回滚相同的效果。因此,对于在堆栈更高的位置启动事务的重新连接,有状态重试通常是最佳选择。有状态重试需要一种机制来唯一地标识消息。最简单的方法是让发送方在`MessageId`消息属性中放置一个唯一的值。所提供的消息转换器提供了这样做的选项:你可以将`createMessageIds`设置为`true`。否则,可以将`MessageKeyGenerator`实现注入拦截器。密钥生成器必须为每条消息返回唯一的密钥。在版本 2.0 之前的版本中,提供了`MissingMessageIdAdvice`。它允许不带`messageId`属性的消息只重试一次(忽略重试设置)。不再提供此建议,因为与`spring-retry`版本 1.2 一起,其功能内置在拦截器和消息侦听器容器中。

|   |对于向后兼容性,缺省情况下(在一次重试之后),带有空消息 ID 的消息被认为对使用者是致命的(使用者被停止),<br/>以复制`MissingMessageIdAdvice`提供的功能,你可以在侦听器容器上将`statefulRetryFatalWithNullMessageId`属性设置为`false`<br/>通过该设置,使用者将继续运行并拒绝消息(在一次重试之后)。<br/>它将被丢弃或路由到死信队列(如果配置了死信队列)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 1.3 开始,提供了一个 Builder API,以通过使用 Java(在`@Configuration`类中)来帮助组装这些拦截器。下面的示例展示了如何做到这一点:

```
@Bean
public StatefulRetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateful()
            .maxAttempts(5)
            .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
            .build();
}
```

只能以这种方式配置重试功能的一个子集。更高级的功能将需要将`RetryTemplate`配置为 Spring  Bean。有关可用策略及其配置的完整信息,请参见[Spring Retry Javadoc](https://docs.spring.io/spring-retry/docs/api/current/)

##### [](#batch-retry)使用批处理侦听器重试

不建议使用批处理侦听器配置重试,除非批处理是由生成器在单个记录中创建的。有关消费者和生产者创建的批的信息,请参见[批处理消息](#de-batching)。对于消费者创建的批处理,框架不知道批处理中的哪条消息导致了故障,因此不可能在重试结束后进行恢复。使用生产者创建的批处理,由于只有一条消息实际失败,因此可以恢复整个消息。应用程序可能想要通知自定义恢复程序在批处理中发生故障的位置,可能是通过设置抛出异常的索引属性。

批处理侦听器的重试恢复程序必须实现`MessageBatchRecoverer`

##### [](#async-listeners)消息侦听器和异步情况

如果`MessageListener`由于业务异常而失败,则异常由消息侦听器容器处理,然后返回到侦听另一条消息。如果故障是由已删除的连接(而不是业务异常)引起的,则必须取消并重新启动为侦听器收集消息的使用者。`SimpleMessageListenerContainer`无缝地处理此问题,并留下一个日志来表示侦听器正在重新启动。事实上,它无休止地循环,试图重新启动消费者。只有当消费者表现得非常糟糕时,它才会放弃。一个副作用是,如果代理在容器启动时关闭,它会一直尝试,直到可以建立连接为止。

与协议错误和断开的连接相反,业务异常处理可能需要更多的考虑和一些自定义配置,尤其是在使用事务或容器 ACK 的情况下。在 2.8.x 之前,RabbitMQ 没有对死信行为的定义。因此,在默认情况下,由于业务异常而被拒绝或回滚的消息可以无休止地重新交付。要限制客户机的再交付次数,一个选择是侦听器的建议链中的`StatefulRetryOperationsInterceptor`。拦截器可以有一个实现自定义死信操作的恢复回调——任何适合你的特定环境的操作。

另一种选择是将容器的`defaultRequeueRejected`属性设置为`false`。这将导致丢弃所有失败的消息。当使用 RabbitMQ2.8.x 或更高版本时,这也有利于将消息传递给死信交换。

或者,你可以抛出`AmqpRejectAndDontRequeueException`。无论`defaultRequeueRejected`属性的设置如何,这样做都可以防止消息请求。

从版本 2.1 开始,引入了一个`ImmediateRequeueAmqpException`来执行完全相反的逻辑:无论`defaultRequeueRejected`属性的设置如何,消息都将被重新请求。

通常,这两种技术的组合被使用。你可以在建议链中使用`StatefulRetryOperationsInterceptor`,并使用`MessageRecoverer`抛出`AmqpRejectAndDontRequeueException`。当所有重试都已用尽时,将调用`MessageRecover``RejectAndDontRequeueRecoverer`就是这么做的。默认的`MessageRecoverer`消耗错误消息并发出`WARN`消息。

从版本 1.3 开始,提供了一个新的`RepublishMessageRecoverer`,允许在重试结束后发布失败的消息。

当回收者使用最后一个异常时,该消息将被 ACK’d,并且不会被发送到死信交换(如果有的话)。

|   |当`RepublishMessageRecoverer`在消费者侧使用时,接收到的消息在`receivedDeliveryMode`消息属性中具有`deliveryMode`<br/>在这种情况下,`deliveryMode``null`<br/>这意味着在代理上具有`NON_PERSISTENT`交付模式,<br/>从版本 2.0 开始,你可以将`RepublishMessageRecoverer``deliveryMode`配置为设置到消息中以重新发布,如果它是`null`<br/>默认情况下,它使用`MessageProperties`默认值-`MessageDeliveryMode.PERSISTENT`。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例展示了如何将`RepublishMessageRecoverer`设置为回收器:

```
@Bean
RetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateless()
            .maxAttempts(5)
            .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse"))
            .build();
}
```

`RepublishMessageRecoverer`在消息头中发布带有附加信息的消息,例如异常消息、堆栈跟踪、原始交换和路由密钥。可以通过创建一个子类并覆盖`additionalHeaders()`来添加额外的标题。`deliveryMode`(或任何其他属性)也可以在`additionalHeaders()`中进行更改,如下例所示:

```
RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(amqpTemplate, "error") {

    protected Map<? extends String, ? extends Object> additionalHeaders(Message message, Throwable cause) {
        message.getMessageProperties()
            .setDeliveryMode(message.getMessageProperties().getReceivedDeliveryMode());
        return null;
    }

};
```

从版本 2.0.5 开始,如果堆栈跟踪太大,它可能会被截断;这是因为所有的标头都必须适合于单个框架。默认情况下,如果堆栈跟踪将导致其他头的可用字节数少于 20,000(“headroom”),那么它将被截断。如果你需要更多或更少的空间来放置其他头文件,可以通过设置 recoverer 的`frameMaxHeadroom`属性来对此进行调整。从版本 2.1.13、2.2.3 开始,异常消息将包含在此计算中,并且使用以下算法将堆栈跟踪的数量最大化:

* 如果单独的堆栈跟踪将超过限制,则异常消息头将被截断为 97 字节加上`…​`,并且堆栈跟踪也将被截断。

* 如果堆栈跟踪很小,消息将被截断(加上`…​`)以适应可用字节(但堆栈跟踪本身中的消息被截断为 97 字节加上`…​`)。

每当发生任何类型的截断时,将记录原始异常以保留完整的信息。

从版本 2.3.3 开始,提供了一个新的子类`RepublishMessageRecovererWithConfirms`;这支持两种类型的 Publisher 确认,并将在返回之前等待确认(或者如果未确认或消息返回,则抛出异常)。

如果确认类型是`CORRELATED`,则子类还将检测是否返回了消息并抛出`AmqpMessageReturnedException`;如果发布是否定的,则将抛出`AmqpNackReceivedException`

如果确认类型是`SIMPLE`,则子类将调用通道上的`waitForConfirmsOrDie`方法。

有关确认和返回的更多信息,请参见[发布者确认并返回](#cf-pub-conf-ret)

从版本 2.1 开始,将添加`ImmediateRequeueMessageRecoverer`以抛出`ImmediateRequeueAmqpException`,该命令通知侦听器容器重新请求当前失败的消息。

##### Spring 重试的[](#exception-classification-for-spring-retry)异常分类

Spring 重试在确定哪些异常可以调用重试方面具有很大的灵活性。对于所有异常,默认配置都会重试。考虑到用户异常包装在`ListenerExecutionFailedException`中,我们需要确保分类检查异常原因。默认分类器只查看顶层异常。

由于 Spring 重试 1.0.3,`BinaryExceptionClassifier`具有一个名为`traverseCauses`的属性(默认:`false`)。当`true`时,它遍历异常原因,直到找到匹配的原因或没有原因为止。

要使用此分类器进行重试,你可以使用一个`SimpleRetryPolicy`,该构造函数创建了最大尝试次数,`Exception`实例的`Map`和布尔(`traverseCauses`),并将此策略注入`RetryTemplate`

#### [](#multi-rabbit)4.1.22。多个代理(或集群)支持

在单个应用程序与多个代理或代理集群之间进行通信时,版本 2.3 增加了更多的便利。在消费者方面,主要的好处是基础设施可以自动将自动声明的队列与适当的代理关联起来。

用一个例子最好地说明了这一点:

```
@SpringBootApplication(exclude = RabbitAutoConfiguration.class)
public class Application {

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

    @Bean
    CachingConnectionFactory cf1() {
        return new CachingConnectionFactory("localhost");
    }

    @Bean
    CachingConnectionFactory cf2() {
        return new CachingConnectionFactory("otherHost");
    }

    @Bean
    CachingConnectionFactory cf3() {
        return new CachingConnectionFactory("thirdHost");
    }

    @Bean
    SimpleRoutingConnectionFactory rcf(CachingConnectionFactory cf1,
            CachingConnectionFactory cf2, CachingConnectionFactory cf3) {

        SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory();
        rcf.setDefaultTargetConnectionFactory(cf1);
        rcf.setTargetConnectionFactories(Map.of("one", cf1, "two", cf2, "three", cf3));
        return rcf;
    }

    @Bean("factory1-admin")
    RabbitAdmin admin1(CachingConnectionFactory cf1) {
        return new RabbitAdmin(cf1);
    }

    @Bean("factory2-admin")
    RabbitAdmin admin2(CachingConnectionFactory cf2) {
        return new RabbitAdmin(cf2);
    }

    @Bean("factory3-admin")
    RabbitAdmin admin3(CachingConnectionFactory cf3) {
        return new RabbitAdmin(cf3);
    }

    @Bean
    public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() {
        return new RabbitListenerEndpointRegistry();
    }

    @Bean
    public RabbitListenerAnnotationBeanPostProcessor postProcessor(RabbitListenerEndpointRegistry registry) {
        MultiRabbitListenerAnnotationBeanPostProcessor postProcessor
                = new MultiRabbitListenerAnnotationBeanPostProcessor();
        postProcessor.setEndpointRegistry(registry);
        postProcessor.setContainerFactoryBeanName("defaultContainerFactory");
        return postProcessor;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory1(CachingConnectionFactory cf1) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf1);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory2(CachingConnectionFactory cf2) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf2);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory3(CachingConnectionFactory cf3) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf3);
        return factory;
    }

    @Bean
    RabbitTemplate template(RoutingConnectionFactory rcf) {
        return new RabbitTemplate(rcf);
    }

    @Bean
    ConnectionFactoryContextWrapper wrapper(SimpleRoutingConnectionFactory rcf) {
        return new ConnectionFactoryContextWrapper(rcf);
    }

}

@Component
class Listeners {

    @RabbitListener(queuesToDeclare = @Queue("q1"), containerFactory = "factory1")
    public void listen1(String in) {

    }

    @RabbitListener(queuesToDeclare = @Queue("q2"), containerFactory = "factory2")
    public void listen2(String in) {

    }

    @RabbitListener(queuesToDeclare = @Queue("q3"), containerFactory = "factory3")
    public void listen3(String in) {

    }

}
```

正如你所看到的,我们已经声明了 3 组基础设施(连接工厂、管理员、容器工厂)。如前所述,`@RabbitListener`可以定义使用哪个容器工厂;在这种情况下,它们还使用`queuesToDeclare`,如果队列不存在,则该队列将在代理上声明。通过使用约定`<container-factory-name>-admin`命名`RabbitAdmin`bean,基础结构能够确定哪个管理员应该声明队列。这也将与`bindings = @QueueBinding(…​)`一起工作,其中交换和绑定也将被声明。它将不能与`queues`一起工作,因为这期望队列已经存在。

在生产者方面,提供了一个方便的`ConnectionFactoryContextWrapper`类,以使使用`RoutingConnectionFactory`(参见[路由连接工厂](#routing-connection-factory))变得更简单。

正如你在上面看到的,一个`SimpleRoutingConnectionFactory` Bean 已经添加了路由密钥`one``two``three`。还有一个`RabbitTemplate`使用了那个工厂。下面是一个使用该模板和包装器路由到代理集群之一的示例。

```
@Bean
public ApplicationRunner runner(RabbitTemplate template, ConnectionFactoryContextWrapper wrapper) {
    return args -> {
        wrapper.run("one", () -> template.convertAndSend("q1", "toCluster1"));
        wrapper.run("two", () -> template.convertAndSend("q2", "toCluster2"));
        wrapper.run("three", () -> template.convertAndSend("q3", "toCluster3"));
    };
}
```

#### [](#debugging)4.1.23。调试

Spring AMQP 提供了广泛的日志记录,特别是在`DEBUG`级别。

如果希望监视应用程序和代理之间的 AMQP 协议,则可以使用 Wireshark 之类的工具,该工具具有一个插件来解码该协议。或者,RabbitMQ Java 客户机提供了一个非常有用的类`Tracer`。当作为`main`运行时,默认情况下,它会监听端口 5673 并连接到 LocalHost 上的端口 5672。你可以运行它并更改连接工厂配置以连接到 LocalHost 上的端口 5673。它在控制台上显示已解码的协议。有关更多信息,请参见`Tracer`Javadoc。

### [](#stream-support)4.2。使用 RabbitMQ 流插件

版本 2.4 为[RabbitMQ 流插件](https://rabbitmq.com/stream.html)引入了对[RabbitMQ 流插件 Java 客户端](https://github.com/rabbitmq/rabbitmq-stream-java-client)的初始支持。

* `RabbitStreamTemplate`

* `StreamListenerContainer`

#### [](#sending-messages-2)4.2.1。发送消息

`RabbitStreamTemplate`提供了`RabbitTemplate`功能的一个子集。

例 1。RabbitStreamOperations

```
public interface RabbitStreamOperations extends AutoCloseable {

	ListenableFuture<Boolean> send(Message message);

	ListenableFuture<Boolean> convertAndSend(Object message);

	ListenableFuture<Boolean> convertAndSend(Object message, @Nullable MessagePostProcessor mpp);

	ListenableFuture<Boolean> send(com.rabbitmq.stream.Message message);

	MessageBuilder messageBuilder();

	MessageConverter messageConverter();

	StreamMessageConverter streamMessageConverter();

	@Override
	void close() throws AmqpException;

}
```

`RabbitStreamTemplate`实现具有以下构造函数和属性:

例 2。RabbitStreamTemplate

```
public RabbitStreamTemplate(Environment environment, String streamName) {
}

public void setMessageConverter(MessageConverter messageConverter) {
}

public void setStreamConverter(StreamMessageConverter streamConverter) {
}

public synchronized void setProducerCustomizer(ProducerCustomizer producerCustomizer) {
}
```

`MessageConverter``convertAndSend`方法中用于将对象转换为 Spring amqp`Message`

`StreamMessageConverter`用于将 Spring AMQP`Message`转换为本机流`Message`

你还可以直接发送本机流`Message`s;使用`messageBuilder()`方法证明对`Producer`的消息生成器的访问。

`ProducerCustomizer`提供了一种机制,可以在生成生产者之前对其进行定制。

请参阅关于自定义[Java 客户端文档](https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmlsingle/)`Producer`[Java 客户端文档](https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmlsingle/)

#### [](#receiving-messages-2)4.2.2。接收消息

异步消息接收由`StreamListenerContainer`提供(当使用`@RabbitListener`时,`StreamRabbitListenerContainerFactory`)。

侦听器容器需要`Environment`以及一个流名。

你可以使用经典的`MessageListener`接收 Spring AMQP`Message`s,也可以使用新的接口接收本机流`Message`s:

```
public interface StreamMessageListener extends MessageListener {

	void onStreamMessage(Message message, Context context);

}
```

有关受支持的属性的信息,请参见[消息侦听器容器配置](#containerAttributes)

与模板类似,容器具有`ConsumerCustomizer`属性。

请参阅关于自定义[Java 客户端文档](https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmlsingle/)`Consumer`[Java 客户端文档](https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmlsingle/)

当使用`@RabbitListener`时,配置一个`StreamRabbitListenerContainerFactory`;此时,大多数`@RabbitListener`属性(`concurrency`等)被忽略。只支持`id``queues``autoStartup``containerFactory`。此外,`queues`只能包含一个流名。

#### [](#examples)4.2.3。例子

```
@Bean
RabbitStreamTemplate streamTemplate(Environment env) {
    RabbitStreamTemplate template = new RabbitStreamTemplate(env, "test.stream.queue1");
    template.setProducerCustomizer((name, builder) -> builder.name("test"));
    return template;
}

@Bean
RabbitListenerContainerFactory<StreamListenerContainer> rabbitListenerContainerFactory(Environment env) {
    return new StreamRabbitListenerContainerFactory(env);
}

@RabbitListener(queues = "test.stream.queue1")
void listen(String in) {
    ...
}

@Bean
RabbitListenerContainerFactory<StreamListenerContainer> nativeFactory(Environment env) {
    StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory(env);
    factory.setNativeListener(true);
    factory.setConsumerCustomizer((id, builder) -> {
        builder.name("myConsumer")
                .offset(OffsetSpecification.first())
                .manualTrackingStrategy();
    });
    return factory;
}

@RabbitListener(id = "test", queues = "test.stream.queue2", containerFactory = "nativeFactory")
void nativeMsg(Message in, Context context) {
    ...
    context.storeOffset();
}
```

### [](#logging)4.3。日志记录子系统 AMQP 附录

该框架为一些流行的日志记录子系统提供了日志附录:

* 注销(自 Spring AMQP 版本 1.4 起)

* log4j2(自 Spring AMQP 版本 1.6 起)

通过使用日志记录子系统的常规机制来配置附录,可用的属性在下面的部分中指定。

#### [](#common-properties)4.3.1。共同属性

以下属性可与所有附录一起使用:

|                 Property                  |                        Default                        |说明|
|-------------------------------------------|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|       ```<br/> exchangeName<br/>```       |                 ```<br/> logs<br/>```                 |要向其发布日志事件的交易所的名称。|
|       ```<br/> exchangeType<br/>```       |                ```<br/> topic<br/>```                 |将日志事件发布到其中的交换类型—仅当 Appender 声明交换时才需要。<br/>请参见`declareExchange`。|
|    ```<br/> routingKeyPattern<br/>```     |                ```<br/> %c.%p<br/>```                 |用于生成路由密钥的记录子系统模式格式.|
|      ```<br/> applicationId<br/>```       |                   ```<br/><br/>```                    |应用程序 ID——如果模式包含`%X{applicationId}`,则将其添加到路由键中。|
|      ```<br/> senderPoolSize<br/>```      |                  ```<br/> 2<br/>```                   |用于发布日志事件的线程数。|
|     ```<br/> maxSenderRetries<br/>```     |                  ```<br/> 30<br/>```                  |如果代理不可用或存在其他错误,则重试发送消息的次数。<br/>重试延迟如下:`N ^ log(N)`,其中`N`是重试编号。|
|        ```<br/> addresses<br/>```         |                   ```<br/><br/>```                    |以下列形式的以逗号分隔的代理地址列表:`host:port[,host:port]*`-覆盖`host``port`。|
|           ```<br/> host<br/>```           |              ```<br/> localhost<br/>```               |连接到哪个主机的 RabbitMQ。|
|           ```<br/> port<br/>```           |                 ```<br/> 5672<br/>```                 |连接到的 RabbitMQ 端口。|
|       ```<br/> virtualHost<br/>```        |                  ```<br/> /<br/>```                   |连接到的 RabbitMQ 虚拟主机。|
|         ```<br/> username<br/>```         |                ```<br/> guest<br/>```                 |RabbitMQ 用户连接时使用.|
|         ```<br/> password<br/>```         |                ```<br/> guest<br/>```                 |此用户的 RabbitMQ 密码。|
|          ```<br/> useSsl<br/>```          |                ```<br/> false<br/>```                 |是否将 SSL 用于 RabbitMQ 连接。<br/>参见[`RabbitConnectionFactoryBean`并配置 SSL]|
|      ```<br/> verifyHostname<br/>```      |                 ```<br/> true<br/>```                 |启用 TLS 连接的服务器主机名验证。<br/>参见[`RabbitConnectionFactoryBean`并配置 SSL]|
|       ```<br/> sslAlgorithm<br/>```       |                 ```<br/> null<br/>```                 |使用的 SSL 算法。|
|  ```<br/> sslPropertiesLocation<br/>```   |                 ```<br/> null<br/>```                 |SSL 属性文件的位置。|
|         ```<br/> keyStore<br/>```         |                 ```<br/> null<br/>```                 |密钥存储库的位置。|
|    ```<br/> keyStorePassphrase<br/>```    |                 ```<br/> null<br/>```                 |密钥库的密码。|
|       ```<br/> keyStoreType<br/>```       |                 ```<br/> JKS<br/>```                  |keystore 类型。|
|        ```<br/> trustStore<br/>```        |                 ```<br/> null<br/>```                 |信任库的位置。|
|   ```<br/> trustStorePassphrase<br/>```   |                 ```<br/> null<br/>```                 |信任存储库的密码。|
|      ```<br/> trustStoreType<br/>```      |                 ```<br/> JKS<br/>```                  |信任库类型。|
|        ```<br/> saslConfig<br/>```        |```<br/> null (RabbitMQ client default applies)<br/>```|`saslConfig`-关于有效值,请参见`RabbitUtils.stringToSaslConfig`的 Javadoc。|
|       ```<br/> ContentType<br/>```        |              ```<br/> text/plain<br/>```              |日志消息的`content-type`属性。|
|     ```<br/> contentEncoding<br/>```      |                   ```<br/><br/>```                    |`content-encoding`日志消息的属性。|
|     ```<br/> declareExchange<br/>```      |                ```<br/> false<br/>```                 |是否在此附录启动时声明已配置的交换。<br/>另请参见`durable``autoDelete`。|
|         ```<br/> durable<br/>```          |                 ```<br/> true<br/>```                 |当`declareExchange``true`时,持久标志被设置为该值。|
|        ```<br/> autoDelete<br/>```        |                ```<br/> false<br/>```                 |当`declareExchange``true`时,自动删除标志被设置为该值。|
|         ```<br/> charset<br/>```          |                 ```<br/> null<br/>```                 |将`String`转换为`byte[]`时使用的字符集。<br/>默认:null(使用的是系统默认字符集)。<br/>如果当前平台不支持字符集,我们将退回使用系统字符集。|
|       ```<br/> deliveryMode<br/>```       |              ```<br/> PERSISTENT<br/>```              |`PERSISTENT``NON_PERSISTENT`,以确定 RabbitMQ 是否应该持久化消息。|
|        ```<br/> generateId<br/>```        |                ```<br/> false<br/>```                 |用于确定`messageId`属性是否设置为唯一值。|
|```<br/> clientConnectionProperties<br/>```|                 ```<br/> null<br/>```                 |用于 RabbitMQ 连接的自定义客户端属性的`key:value`对的逗号分隔列表。|
|     ```<br/> addMdcAsHeaders<br/>```      |                 ```<br/> true<br/>```                 |在引入此属性之前,MDC 属性总是被添加到 RabbitMQ 消息头中。<br/>它可能会导致大 MDC 的问题,因为 RabbitMQ 对所有头都有有限的缓冲区大小而且这个缓冲区很小。<br/>引入这个属性是为了避免在大 MDC 的情况下出现问题。<br/>默认情况下,这个值设置为`true`,用于向后兼容。<br/>`false`将序列化 MDC 关闭到头中,<br/>请注意,默认情况下,`JsonLayout`将 MDC 添加到消息中。|

#### [](#log4j-2-appender)4.3.2。log4j2 附录

下面的示例展示了如何配置 log4j2Appender:

```
<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
        addresses="foo:5672,bar:5672" user="guest" password="guest" virtualHost="/"
        exchange="log4j2" exchangeType="topic" declareExchange="true" durable="true" autoDelete="false"
        applicationId="myAppId" routingKeyPattern="%X{applicationId}.%c.%p"
        contentType="text/plain" contentEncoding="UTF-8" generateId="true" deliveryMode="NON_PERSISTENT"
        charset="UTF-8"
        senderPoolSize="3" maxSenderRetries="5"
        addMdcAsHeaders="false">
    </RabbitMQ>
</Appenders>
```

|   |从 1.6.10 和 1.7.3 版本开始,默认情况下,log4j2Appender 将消息发布到调用线程上的 RabbitMQ。<br/>这是因为 log4j2 默认情况下不会创建线程安全事件。<br/>如果代理关闭,则使用`maxSenderRetries`进行重试,重试之间没有延迟。<br/>如果你希望恢复以前在单独的线程上发布消息的行为(`senderPoolSize`),则可以将`async`属性设置为`true`,但是,<br/>,你还需要配置 log4j2 来使用`DefaultLogEventFactory`而不是`ReusableLogEventFactory`<br/>这样做的一种方法是设置系统属性`-Dlog4j2.enable.threadlocals=false`<br/>如果你使用异步发布与`ReusableLogEventFactory`,由于相声,事件很有可能被破坏。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### [](#logback-appender)4.3.3。回录附录

下面的示例展示了如何配置一个 Logback Appender:

```
<appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender">
    <layout>
        <pattern><![CDATA[ %d %p %t [%c] - <%m>%n ]]></pattern>
    </layout>
    <addresses>foo:5672,bar:5672</addresses>
    <abbreviation>36</abbreviation>
    <includeCallerData>false</includeCallerData>
    <applicationId>myApplication</applicationId>
    <routingKeyPattern>%property{applicationId}.%c.%p</routingKeyPattern>
    <generateId>true</generateId>
    <charset>UTF-8</charset>
    <durable>false</durable>
    <deliveryMode>NON_PERSISTENT</deliveryMode>
    <declareExchange>true</declareExchange>
    <addMdcAsHeaders>false</addMdcAsHeaders>
</appender>
```

从版本 1.7.1 开始,logback`AmqpAppender`提供了一个`includeCallerData`选项,默认情况下是`false`。提取调用者数据可能会非常昂贵,因为日志事件必须创建一个可丢弃的数据,并对其进行检查以确定调用位置。因此,默认情况下,当事件被添加到事件队列时,不会提取与事件相关的调用方数据。通过将`includeCallerData`属性设置为`true`,可以将 Appender 配置为包括调用方数据。

从版本 2.0.0 开始,Logback`AmqpAppender`使用`encoder`选项支持[翻录编码器](https://logback.qos.ch/manual/encoders.html)`encoder``layout`选项是互斥的。

#### [](#customizing-the-messages)4.3.4。自定义消息

默认情况下,AMQP Appenders 填充以下消息属性:

* `deliveryMode`

* contentType

* `contentEncoding`,如果已配置

* `messageId`,如果`generateId`已配置

* 日志事件的`timestamp`

* `appId`,如果配置了 ApplicationID

此外,它们还用以下值填充标题:

* 日志事件的`categoryName`

* 日志事件的级别

* `thread`:发生日志事件的线程的名称

* 日志事件调用的堆栈跟踪的位置

* 所有 MDC 属性的副本(除非`addMdcAsHeaders`被设置为`false`

每个附录都可以进行子类,这样你就可以在发布之前修改消息了。下面的示例展示了如何自定义日志消息:

```
public class MyEnhancedAppender extends AmqpAppender {

    @Override
    public Message postProcessMessageBeforeSend(Message message, Event event) {
        message.getMessageProperties().setHeader("foo", "bar");
        return message;
    }

}
```

从 2.2.4 开始,log4j2`AmqpAppender`可以使用`@PluginBuilderFactory`进行扩展,也可以使用`AmqpAppender.Builder`进行扩展。

```
@Plugin(name = "MyEnhancedAppender", category = "Core", elementType = "appender", printObject = true)
public class MyEnhancedAppender extends AmqpAppender {

	public MyEnhancedAppender(String name, Filter filter, Layout<? extends Serializable> layout,
			boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue, String foo, String bar) {
		super(name, filter, layout, ignoreExceptions, manager, eventQueue);

	@Override
	public Message postProcessMessageBeforeSend(Message message, Event event) {
			message.getMessageProperties().setHeader("foo", "bar");
		return message;
	}

	@PluginBuilderFactory
	public static Builder newBuilder() {
		return new Builder();
	}

	protected static class Builder extends AmqpAppender.Builder {

		@Override
		protected AmqpAppender buildInstance(String name, Filter filter, Layout<? extends Serializable> layout,
				boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue) {
			return new MyEnhancedAppender(name, filter, layout, ignoreExceptions, manager, eventQueue);
		}
	}

}
```

#### [](#customizing-the-client-properties)4.3.5。自定义客户端属性

你可以通过添加字符串属性或更复杂的属性来添加自定义客户机属性。

##### [](#simple-string-properties)简单字符串属性

每个 Appender 都支持向 RabbitMQ 连接添加客户端属性。

下面的示例展示了如何为回登添加自定义客户机属性:

```
<appender name="AMQP" ...>
    ...
    <clientConnectionProperties>thing1:thing2,cat:hat</clientConnectionProperties>
    ...
</appender>
```

示例 3.log4j2

```
<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
        ...
        clientConnectionProperties="thing1:thing2,cat:hat"
        ...
    </RabbitMQ>
</Appenders>
```

这些属性是用逗号分隔的`key:value`对列表。键和值不能包含逗号或冒号。

当查看连接时,这些属性会出现在 RabbitMQ 管理 UI 上。

##### [](#advanced-technique-for-logback)回传的高级技术

你可以对 Logback Appender 进行子类。这样做可以让你在建立连接之前修改客户机连接属性。下面的示例展示了如何做到这一点:

```
public class MyEnhancedAppender extends AmqpAppender {

    private String thing1;

    @Override
    protected void updateConnectionClientProperties(Map<String, Object> clientProperties) {
        clientProperties.put("thing1", this.thing1);
    }

    public void setThing1(String thing1) {
        this.thing1 = thing1;
    }

}
```

然后可以将`<thing1>thing2</thing1>`添加到 logback.xml。

对于字符串属性(如前面示例中所示的那些),可以使用前面的技术。子类允许添加更丰富的属性(例如添加`Map`或数字属性)。

#### [](#providing-a-custom-queue-implementation)4.3.6。提供自定义队列实现

`AmqpAppenders`使用`BlockingQueue`将日志事件异步发布到 RabbitMQ。默认情况下,使用`LinkedBlockingQueue`。但是,你可以提供任何类型的自定义`BlockingQueue`实现。

下面的示例展示了如何对注销执行此操作:

```
public class MyEnhancedAppender extends AmqpAppender {

    @Override
    protected BlockingQueue<Event> createEventQueue() {
        return new ArrayBlockingQueue();
    }

}
```

log4j2Appender 支持使用[`BlockingQueueFactory`](https://logging. Apache.org/log4j/2.x/manual/appenders.html#BlockingQueueFactory),如下例所示:

```
<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
              bufferSize="10" ... >
        <ArrayBlockingQueue/>
    </RabbitMQ>
</Appenders>
```

### [](#sample-apps)4.4。示例应用程序

项目包括两个示例应用程序。第一个是一个简单的“Hello World”示例,演示了同步和异步消息接收。它为获得对基本组件的理解提供了一个很好的起点。第二个示例基于股票交易用例,以演示在现实世界的应用程序中常见的交互类型。在这一章中,我们提供了每个示例的快速演练,以便你能够关注最重要的组件。这些示例都是基于 Maven 的,因此你应该能够将它们直接导入到任何 Maven 可感知的 IDE 中(例如[SpringSource 工具套件](https://www.springsource.org/sts))。

#### [](#hello-world-sample)4.4.1。《Hello World》样本

“Hello World”示例演示了同步和异步消息接收。你可以将`spring-rabbit-helloworld`示例导入到 IDE 中,然后按照下面的讨论进行操作。

##### [](#hello-world-sync)同步示例

`src/main/java`目录中,导航到`org.springframework.amqp.helloworld`包。打开`HelloWorldConfiguration`类,注意它包含类级的`@Configuration`注释,并注意方法级的一些`@Bean`注释。这是 Spring 基于 Java 的配置的一个示例。你可以阅读有关[here](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-java)的更多信息。

下面的清单显示了如何创建连接工厂:

```
@Bean
public CachingConnectionFactory connectionFactory() {
    CachingConnectionFactory connectionFactory =
        new CachingConnectionFactory("localhost");
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    return connectionFactory;
}
```

配置还包含`RabbitAdmin`的实例,默认情况下,该实例查找类型为 exchange、queue 或 binding 的任何 bean,然后在代理上声明它们。实际上,在`HelloWorldConfiguration`中生成的`helloWorldQueue` Bean 是一个示例,因为它是`Queue`的一个实例。

下面的清单显示了`helloWorldQueue` Bean 的定义:

```
@Bean
public Queue helloWorldQueue() {
    return new Queue(this.helloWorldQueueName);
}
```

回顾`rabbitTemplate` Bean 配置,可以看到它的名称`helloWorldQueue`设置为其`queue`属性(用于接收消息)和其`routingKey`属性(用于发送消息)。

既然我们已经研究了配置,我们就可以查看实际使用这些组件的代码了。首先,从同一个包中打开`Producer`类。它包含一个`main()`方法,其中创建了 Spring `ApplicationContext`

下面的清单显示了`main`方法:

```
public static void main(String[] args) {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(RabbitConfiguration.class);
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    amqpTemplate.convertAndSend("Hello World");
    System.out.println("Sent: Hello World");
}
```

在前面的示例中,检索`AmqpTemplate` Bean 并用于发送`Message`。由于客户端代码应该尽可能依赖于接口,所以类型是`AmqpTemplate`而不是`RabbitTemplate`。尽管在`HelloWorldConfiguration`中创建的 Bean 是`RabbitTemplate`的一个实例,但依赖于接口意味着此代码更可移植(你可以独立于代码来更改配置)。由于调用了`convertAndSend()`方法,模板将委托给它的`MessageConverter`实例。在这种情况下,它使用默认的`SimpleMessageConverter`,但是可以向`rabbitTemplate` Bean 提供不同的实现,如`HelloWorldConfiguration`中所定义的。

现在打开`Consumer`类。它实际上共享相同的配置基类,这意味着它共享`rabbitTemplate` Bean。这就是为什么我们将模板配置为`routingKey`(用于发送)和`queue`(用于接收)。正如我们在[`AmqpTemplate`]中描述的那样,你可以将’RoutingKey’参数传递给 send 方法,将’Queue’参数传递给 receive 方法。`Consumer`代码基本上是生产者的镜像,调用`receiveAndConvert()`而不是`convertAndSend()`

下面的清单显示了`Consumer`的主要方法:

```
public static void main(String[] args) {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(RabbitConfiguration.class);
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    System.out.println("Received: " + amqpTemplate.receiveAndConvert());
}
```

如果运行`Producer`,然后运行`Consumer`,则应该在控制台输出中看到`Received: Hello World`

##### [](#hello-world-async)异步示例

[同步示例](#hello-world-sync)浏览了同步 Hello World 示例。这一部分描述了一个稍微更高级但功能更强大的选项。通过一些修改,Hello World 示例可以提供异步接收的示例,也称为消息驱动 POJO。实际上,有一个子包提供了以下内容:`org.springframework.amqp.samples.helloworld.async`

同样,我们从发送方开始。打开`ProducerConfiguration`类,注意它创建了一个`connectionFactory`和一个`rabbitTemplate` Bean。这一次,由于配置是专门用于消息发送端的,因此我们甚至不需要任何队列定义,并且`RabbitTemplate`仅具有’RoutingKey’属性集。记住,消息是发送到交易所的,而不是直接发送到队列的。AMQP 默认交换是一种没有名称的直接交换。所有队列都绑定到该默认交换,并以它们的名称作为路由密钥。这就是为什么我们只需要在这里提供路由密钥。

下面的清单显示了`rabbitTemplate`的定义:

```
public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    template.setRoutingKey(this.helloWorldQueueName);
    return template;
}
```

由于此示例演示了异步消息接收,所以生产端被设计为连续发送消息(如果它是一个类似于同步版本的每次执行消息的模型,那么它实际上就不是一个消息驱动的消费者了)。负责持续发送消息的组件被定义为`ProducerConfiguration`中的内部类。它被配置为每三秒运行一次。

下面的清单显示了该组件:

```
static class ScheduledProducer {

    @Autowired
    private volatile RabbitTemplate rabbitTemplate;

    private final AtomicInteger counter = new AtomicInteger();

    @Scheduled(fixedRate = 3000)
    public void sendMessage() {
        rabbitTemplate.convertAndSend("Hello World " + counter.incrementAndGet());
    }
}
```

你不需要了解所有的细节,因为真正的重点应该是接收端(我们接下来将讨论这一点)。但是,如果你还不熟悉 Spring 任务调度支持,则可以了解更多[here](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-annotation-support)。简而言之,`postProcessor` Bean 中的`ProducerConfiguration`使用调度程序注册任务。

现在我们可以转向接受方了。为了强调消息驱动的 POJO 行为,我们从对消息做出反应的组件开始。这个类被称为`HelloWorldHandler`,如以下清单所示:

```
public class HelloWorldHandler {

    public void handleMessage(String text) {
        System.out.println("Received: " + text);
    }

}
```

那门课很难上。它不扩展任何基类,不实现任何接口,甚至不包含任何导入。它正被 Spring AMQP`MessageListenerAdapter`接口“适配”到`MessageListener`接口。然后可以在`SimpleMessageListenerContainer`上配置该适配器。对于这个示例,容器是在`ConsumerConfiguration`类中创建的。你可以看到 POJO 包装在适配器那里。

下面的清单显示了`listenerContainer`是如何定义的:

```
@Bean
public SimpleMessageListenerContainer listenerContainer() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory());
    container.setQueueName(this.helloWorldQueueName);
    container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler()));
    return container;
}
```

`SimpleMessageListenerContainer`是一个 Spring 生命周期组件,默认情况下,它会自动启动。如果你在`Consumer`类中查找,你可以看到它的`main()`方法仅由一行引导程序组成,用于创建`ApplicationContext`。生产者的`main()`方法也是一行引导程序,因为其方法被注释为`@Scheduled`的组件也会自动启动。你可以以任何顺序启动`Producer``Consumer`,并且你应该每三秒钟就会看到正在发送和接收的消息。

#### [](#stock-trading)4.4.2。股票交易

股票交易示例演示了比[Hello World 样本](#hello-world-sample)更高级的消息传递场景。然而,配置是非常相似的,如果有一点更多的参与。由于我们详细介绍了 Hello World 配置,在此,我们将重点讨论使这个示例有所不同的原因。有一个服务器将市场数据(股票行情)推送到主题交换。然后,客户端可以通过绑定具有路由模式的队列来订阅市场数据提要(例如,`app.stock.quotes.nasdaq.*`)。这个演示的另一个主要功能是由客户端发起并由服务器处理的请求-回复“股票交易”交互。这涉及一个私有`replyTo`队列,该队列由客户端在订单请求消息本身内发送。

服务器的核心配置在`RabbitServerConfiguration`包中的`org.springframework.amqp.rabbit.stocks.config.server`类中。它扩展了`AbstractStockAppRabbitConfiguration`。这里定义了服务器和客户机的公共资源,包括市场数据主题交换(其名称为“app.stock.marketdata”)和服务器为股票交易公开的队列(其名称为“app.stock.request”)。在该公共配置文件中,你还可以看到在`Jackson2JsonMessageConverter`上配置了`RabbitTemplate`

特定于服务器的配置由两个部分组成。首先,它在`RabbitTemplate`上配置市场数据交换,这样它就不需要在每次调用发送`Message`时都提供该交换名称。它在基本配置类中定义的抽象回调方法中执行此操作。下面的列表显示了该方法:

```
public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) {
    rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME);
}
```

其次,声明股票请求队列。在这种情况下,它不需要任何显式绑定,因为它绑定到默认的无名称交换,并以自己的名称作为路由密钥。如前所述,AMQP 规范定义了这种行为。下面的清单显示了`stockRequestQueue` Bean 的定义:

```
@Bean
public Queue stockRequestQueue() {
    return new Queue(STOCK_REQUEST_QUEUE_NAME);
}
```

现在你已经看到了服务器的 AMQP 资源的配置,请导航到`org.springframework.amqp.rabbit.stocks`目录下的`src/test/java`包。在这里,你可以看到实际的`Server`类,它提供了`main()`方法。它基于`server-bootstrap.xml`配置文件创建`ApplicationContext`。在这里,你可以看到发布虚拟市场数据的计划任务。这种配置依赖于 Spring 的`task`名称空间支持。引导程序配置文件还导入了其他一些文件。最有趣的是`server-messaging.xml`,它直接位于`src/main/resources`之下。在这里,你可以看到负责处理股票交易请求的`messageListenerContainer` Bean。最后,看看`serverHandler` Bean 中定义的`server-handlers.xml`(也在“SRC/main/resources”中)。 Bean 是`ServerHandler`类的一个实例,并且是消息驱动 POJO 的一个很好的示例,该 POJO 也可以发送回复消息。请注意,它本身并不耦合到框架或任何 AMQP 概念。它接受`TradeRequest`并返回`TradeResponse`。下面的清单显示了`handleMessage`方法的定义:

```
public TradeResponse handleMessage(TradeRequest tradeRequest) { ...
}
```

现在,我们已经了解了服务器最重要的配置和代码,我们可以转到客户机。最好的起点可能是`RabbitClientConfiguration`包中的`org.springframework.amqp.rabbit.stocks.config.client`。注意,它声明了两个队列,但没有提供显式的名称。下面的清单显示了这两个队列的 Bean 定义:

```
@Bean
public Queue marketDataQueue() {
    return amqpAdmin().declareQueue();
}

@Bean
public Queue traderJoeQueue() {
    return amqpAdmin().declareQueue();
}
```

这些都是私有队列,并且会自动生成唯一的名称。客户端使用第一个生成的队列绑定到服务器公开的市场数据交换。回想一下,在 AMQP 中,消费者与队列交互,而生产者与交换器交互。队列与交易所的“绑定”是告诉代理将消息从给定的交易所传递(或路由)到队列的方法。由于市场数据交换是一个主题交换,因此绑定可以用路由模式表示。`RabbitClientConfiguration`使用`Binding`对象执行此操作,并且该对象是使用`BindingBuilder`Fluent API 生成的。下面的清单显示了`Binding`:

```
@Value("${stocks.quote.pattern}")
private String marketDataRoutingKey;

@Bean
public Binding marketDataBinding() {
    return BindingBuilder.bind(
        marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
}
```

请注意,实际值已在一个属性文件中外部化(`client.properties``src/main/resources`),并且我们使用 Spring 的`@Value`注释来注入该值。这通常是个好主意。否则,该值将在类中进行硬编码,并且在不重新编译的情况下不可修改。在这种情况下,在更改用于绑定的路由模式时,运行客户机的多个版本要容易得多。我们现在可以试试。

首先运行`org.springframework.amqp.rabbit.stocks.Server`,然后运行`org.springframework.amqp.rabbit.stocks.Client`。你应该会看到`NASDAQ`股票的虚拟报价,因为在 client.properties 中与“stocks.quote.pattern”键关联的当前值是“app.stock.quotes.nasdaq.**”。现在,在保持现有`Server`和`Client`运行的同时,将该属性值更改为’app.stock.quotes.nyse.**’,并启动第二个`Client`实例。你应该看到,第一个客户端仍然接收纳斯达克报价,而第二个客户端接收纽交所报价。相反,你可以改变模式,以获得所有的股票,甚至一个单独的股票代码。

我们探索的最后一个功能是从客户的角度进行请求-回复交互。回想一下,我们已经看到了接受`ServerHandler`对象并返回`TradeResponse`对象的`ServerHandler`对象。在`Client`包中,`RabbitStockServiceGateway`侧的对应代码是`RabbitStockServiceGateway`。它委托给`RabbitTemplate`以发送消息。下面的清单显示了`send`方法:

```
public void send(TradeRequest tradeRequest) {
    getRabbitTemplate().convertAndSend(tradeRequest, new MessagePostProcessor() {
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setReplyTo(new Address(defaultReplyToQueue));
            try {
                message.getMessageProperties().setCorrelationId(
                    UUID.randomUUID().toString().getBytes("UTF-8"));
            }
            catch (UnsupportedEncodingException e) {
                throw new AmqpException(e);
            }
            return message;
        }
    });
}
```

请注意,在发送消息之前,它设置了`replyTo`地址。它提供了由`traderJoeQueue` Bean 定义生成的队列(如前面所示)。下面的清单显示了`@Bean`类本身的`StockServiceGateway`定义:

```
@Bean
public StockServiceGateway stockServiceGateway() {
    RabbitStockServiceGateway gateway = new RabbitStockServiceGateway();
    gateway.setRabbitTemplate(rabbitTemplate());
    gateway.setDefaultReplyToQueue(traderJoeQueue());
    return gateway;
}
```

如果你不再运行服务器和客户机,请立即启动它们。尝试发送格式为“100tckr”的请求。在模拟“处理”请求的短暂人为延迟之后,你应该会看到一个确认消息出现在客户机上。

#### [](#spring-rabbit-json)4.4.3。从非 Spring 份申请中接收 JSON

Spring 应用程序,在发送 JSON 时,将`*TypeId*`头设置为完全限定的类名,以协助接收应用程序将 JSON 转换回 Java 对象。

`spring-rabbit-json`示例探索了几种从非 Spring 应用程序转换 JSON 的技术。

另见[Jackson2JSONMessageConverter](#json-message-converter)以及`DefaultClassMapper`的[javadoc](https://DOCS. Spring.io/ Spring-amqp/DOCS/current/api/index.html?org/springframework/amqp/support/converter/defaultclassmapper.html)。

### [](#testing)4.5。测试支持

为异步应用程序编写集成一定比测试更简单的应用程序更复杂。当`@RabbitListener`注释之类的抽象出现在图片中时,这就变得更加复杂了。问题是如何验证在发送消息后,侦听器是否如预期的那样接收到了消息。

框架本身有许多单元和集成测试。一些使用模拟,而另一些则使用与实时 RabbitMQ 代理的集成测试。你可以参考这些测试来获得测试场景的一些想法。

Spring AMQP 版本 1.6 引入了`spring-rabbit-test`JAR,它为测试这些更复杂的场景中的一些提供了支持。预计该项目将随着时间的推移而扩展,但我们需要社区反馈来为帮助测试所需的功能提供建议。请使用[JIRA](https://jira.spring.io/browse/AMQP)[GitHub 问题](https://github.com/spring-projects/spring-amqp/issues)提供此类反馈。

#### [](#spring-rabbit-test)4.5.1。@SpringRabbitTest

使用此注释将基础设施 bean 添加到 Spring test`ApplicationContext`。在使用`@SpringBootTest`时,这是不必要的,因为 Spring boot 的自动配置将添加 bean。

已注册的 bean 有:

* `CachingConnectionFactory``autoConnectionFactory`)。如果存在`@RabbitEnabled`,则使用其连接工厂。

* `RabbitTemplate`(`autoRabbitTemplate`)

* `RabbitAdmin`(`autoRabbitAdmin`)

* `RabbitListenerContainerFactory`(`autoContainerFactory`)

此外,还添加了与`@EnableRabbit`相关的 bean(以支持`@RabbitListener`)。

例 4。JUnit5 示例

```
@SpringJunitConfig
@SpringRabbitTest
public class MyRabbitTests {

	@Autowired
	private RabbitTemplate template;

	@Autowired
	private RabbitAdmin admin;

	@Autowired
	private RabbitListenerEndpointRegistry registry;

	@Test
	void test() {
        ...
	}

	@Configuration
	public static class Config {

        ...

	}

}
```

使用 JUnit4,将`@SpringJunitConfig`替换为`@RunWith(SpringRunnner.class)`

#### [](#mockito-answer)4.5.2。mockito`Answer<?>`实现

目前有两个`Answer<?>`实现来帮助测试。

第一个是`LatchCountDownAndCallRealMethodAnswer`,它提供一个`Answer<Void>`,返回`null`并对锁存器进行倒数。下面的示例展示了如何使用`LatchCountDownAndCallRealMethodAnswer`:

```
LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("myListener", 2);
doAnswer(answer)
    .when(listener).foo(anyString(), anyString());

...

assertThat(answer.await(10)).isTrue();
```

第二,`LambdaAnswer<T>`提供了一种可选地调用实际方法的机制,并提供了一种返回自定义结果的机会,该结果基于`InvocationOnMock`和结果(如果有的话)。

考虑以下 POJO:

```
public class Thing {

    public String thing(String thing) {
        return thing.toUpperCase();
    }

}
```

下面的类测试`Thing`POJO:

```
Thing thing = spy(new Thing());

doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + r))
    .when(thing).thing(anyString());
assertEquals("THINGTHING", thing.thing("thing"));

doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + i.getArguments()[0]))
    .when(thing).thing(anyString());
assertEquals("THINGthing", thing.thing("thing"));

doAnswer(new LambdaAnswer<String>(false, (i, r) ->
    "" + i.getArguments()[0] + i.getArguments()[0])).when(thing).thing(anyString());
assertEquals("thingthing", thing.thing("thing"));
```

从版本 2.2.3 开始,答案将捕获由 Test 下的方法引发的任何异常。使用`answer.getExceptions()`获取对它们的引用。

当与[`@RabbitListenerTest``RabbitListenerTestHarness`]结合使用时,使用`harness.getLambdaAnswerFor("listenerId", true, …​)`可以为侦听器获得正确构造的答案。

#### [](#test-harness)4.5.3。`@RabbitListenerTest`和`RabbitListenerTestHarness`

将一个`@Configuration`类注释为`@RabbitListenerTest`,会导致框架将标准`RabbitListenerAnnotationBeanPostProcessor`替换为一个名为`RabbitListenerTestHarness`的子类(它还通过`@EnableRabbit`启用`@RabbitListener`检测)。

`RabbitListenerTestHarness`通过两种方式增强了侦听器。首先,它将侦听器封装在`Mockito Spy`中,从而实现正常的`Mockito`存根和验证操作。它还可以向侦听器添加`Advice`,从而能够访问参数、结果和引发的任何异常。你可以通过`@RabbitListenerTest`上的属性来控制其中哪些(或两个)被启用。提供后者是为了访问关于调用的较低级别的数据。它还支持在调用异步监听器之前阻塞测试线程。

|   |`final``@RabbitListener`方法不能被监视或通知。<br/>此外,只有具有`id`属性的侦听器才能被监视或通知。|
|---|--------------------------------------------------------------------------------------------------------------------------------------|

举几个例子。

下面的示例使用 SPY:

```
@Configuration
@RabbitListenerTest
public class Config {

    @Bean
    public Listener listener() {
        return new Listener();
    }

    ...

}

public class Listener {

    @RabbitListener(id="foo", queues="#{queue1.name}")
    public String foo(String foo) {
        return foo.toUpperCase();
    }

    @RabbitListener(id="bar", queues="#{queue2.name}")
    public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
        ...
    }

}

public class MyTests {

    @Autowired
    private RabbitListenerTestHarness harness; (1)

    @Test
    public void testTwoWay() throws Exception {
        assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

        Listener listener = this.harness.getSpy("foo"); (2)
        assertNotNull(listener);
        verify(listener).foo("foo");
    }

    @Test
    public void testOneWay() throws Exception {
        Listener listener = this.harness.getSpy("bar");
        assertNotNull(listener);

        LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("bar", 2); (3)
        doAnswer(answer).when(listener).foo(anyString(), anyString()); (4)

        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");

        assertTrue(answer.await(10));
        verify(listener).foo("bar", this.queue2.getName());
        verify(listener).foo("baz", this.queue2.getName());
    }

}
```

|**1**|将线束注入到测试用例中,这样我们就可以访问间谍。|
|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|获取对 SPY 的引用,这样我们就可以验证它是否如预期的那样被调用了。<br/>由于这是一个发送和接收操作,因此不需要挂起测试线程,因为它已经<br/>挂起在`RabbitTemplate`中等待回复。|
|**3**|在这种情况下,我们只使用一个发送操作,所以我们需要一个锁存器来等待对容器线程上的侦听器<br/>的异步调用。<br/>我们使用[Answer\<?\>](#mockito-answer)实现之一来帮助实现这一点。<br/>重要:由于侦听器被监视的方式,使用`harness.getLatchAnswerFor()`为间谍获得正确配置的答案是很重要的。|
|**4**|将 SPY 配置为调用`Answer`。|

下面的示例使用了捕获建议:

```
@Configuration
@ComponentScan
@RabbitListenerTest(spy = false, capture = true)
public class Config {

}

@Service
public class Listener {

    private boolean failed;

    @RabbitListener(id="foo", queues="#{queue1.name}")
    public String foo(String foo) {
        return foo.toUpperCase();
    }

    @RabbitListener(id="bar", queues="#{queue2.name}")
    public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
        if (!failed && foo.equals("ex")) {
            failed = true;
            throw new RuntimeException(foo);
        }
        failed = false;
    }

}

public class MyTests {

    @Autowired
    private RabbitListenerTestHarness harness; (1)

    @Test
    public void testTwoWay() throws Exception {
        assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

        InvocationData invocationData =
            this.harness.getNextInvocationDataFor("foo", 0, TimeUnit.SECONDS); (2)
        assertThat(invocationData.getArguments()[0], equalTo("foo"));     (3)
        assertThat((String) invocationData.getResult(), equalTo("FOO"));
    }

    @Test
    public void testOneWay() throws Exception {
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "ex");

        InvocationData invocationData =
            this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS); (4)
        Object[] args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("bar"));
        assertThat((String) args[1], equalTo(queue2.getName()));

        invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
        args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("baz"));

        invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
        args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("ex"));
        assertEquals("ex", invocationData.getThrowable().getMessage()); (5)
    }

}
```

|**1**|将线束注入到测试用例中,这样我们就可以访问间谍。|
|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|使用`harness.getNextInvocationDataFor()`来检索调用数据-在这种情况下,因为这是一个请求/回复<br/>场景,因此不需要等待任何时间,因为测试线程被挂起在`RabbitTemplate`中等待<br/>结果。|
|**3**|然后,我们可以验证该论证和结果是否如预期的那样。|
|**4**|这一次我们需要一些时间来等待数据,因为这是容器线程上的异步操作,我们需要<br/>来挂起测试线程。|
|**5**|当侦听器抛出异常时,它在调用数据的`throwable`属性中可用。|

|   |当将自定义`Answer<?>`s 与线束一起使用时,为了正确地进行操作,这样的答案应该是子类`ForwardsInvocation`并从线束中获得实际的侦听器(而不是间谍)(`getDelegate("myListener")`)并调用<br/><br/>参见所提供的[mockito`Answer<?>`实现](#mockito-answer)源代码的示例。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### [](#test-template)4.5.4。使用`TestRabbitTemplate`

`TestRabbitTemplate`用于执行一些基本的集成测试,而不需要代理。在测试用例中将其添加为`@Bean`时,它会发现上下文中的所有侦听器容器,无论是声明为`@Bean`还是`<bean/>`还是使用`@RabbitListener`注释。它目前仅支持按队列名进行路由。模板从容器中提取消息侦听器,并直接在测试线程上调用它。返回回复的侦听器支持请求-回复消息(`sendAndReceive`methods)。

下面的测试用例使用了模板:

```
@RunWith(SpringRunner.class)
public class TestRabbitTemplateTests {

    @Autowired
    private TestRabbitTemplate template;

    @Autowired
    private Config config;

    @Test
    public void testSimpleSends() {
        this.template.convertAndSend("foo", "hello1");
        assertThat(this.config.fooIn, equalTo("foo:hello1"));
        this.template.convertAndSend("bar", "hello2");
        assertThat(this.config.barIn, equalTo("bar:hello2"));
        assertThat(this.config.smlc1In, equalTo("smlc1:"));
        this.template.convertAndSend("foo", "hello3");
        assertThat(this.config.fooIn, equalTo("foo:hello1"));
        this.template.convertAndSend("bar", "hello4");
        assertThat(this.config.barIn, equalTo("bar:hello2"));
        assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4"));

        this.template.setBroadcast(true);
        this.template.convertAndSend("foo", "hello5");
        assertThat(this.config.fooIn, equalTo("foo:hello1foo:hello5"));
        this.template.convertAndSend("bar", "hello6");
        assertThat(this.config.barIn, equalTo("bar:hello2bar:hello6"));
        assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4hello5hello6"));
    }

    @Test
    public void testSendAndReceive() {
        assertThat(this.template.convertSendAndReceive("baz", "hello"), equalTo("baz:hello"));
    }
```

```
    @Configuration
    @EnableRabbit
    public static class Config {

        public String fooIn = "";

        public String barIn = "";

        public String smlc1In = "smlc1:";

        @Bean
        public TestRabbitTemplate template() throws IOException {
            return new TestRabbitTemplate(connectionFactory());
        }

        @Bean
        public ConnectionFactory connectionFactory() throws IOException {
            ConnectionFactory factory = mock(ConnectionFactory.class);
            Connection connection = mock(Connection.class);
            Channel channel = mock(Channel.class);
            willReturn(connection).given(factory).createConnection();
            willReturn(channel).given(connection).createChannel(anyBoolean());
            given(channel.isOpen()).willReturn(true);
            return factory;
        }

        @Bean
        public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() throws IOException {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            factory.setConnectionFactory(connectionFactory());
            return factory;
        }

        @RabbitListener(queues = "foo")
        public void foo(String in) {
            this.fooIn += "foo:" + in;
        }

        @RabbitListener(queues = "bar")
        public void bar(String in) {
            this.barIn += "bar:" + in;
        }

        @RabbitListener(queues = "baz")
        public String baz(String in) {
            return "baz:" + in;
        }

        @Bean
        public SimpleMessageListenerContainer smlc1() throws IOException {
            SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
            container.setQueueNames("foo", "bar");
            container.setMessageListener(new MessageListenerAdapter(new Object() {

                @SuppressWarnings("unused")
                public void handleMessage(String in) {
                    smlc1In += in;
                }

            }));
            return container;
        }

    }

}
```

#### [](#junit-rules)4.5.5。JUnit4`@Rules`

Spring AMQP1.7 版及以后版本提供了一个名为`spring-rabbit-junit`的附加 JAR。此 JAR 包含两个实用程序`@Rule`实例,用于运行 JUnit4 测试时使用。关于 JUnit5 测试,请参见[JUnit5 条件](#junit5-conditions)

##### [](#using-brokerrunning)使用`BrokerRunning`

`BrokerRunning`提供了一种机制,当代理不运行时(默认情况下,在`localhost`上),让测试成功。

它还具有用于初始化和清空队列以及删除队列和交换的实用程序方法。

下面的示例展示了它的用法:

```
@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");

@AfterClass
public static void tearDown() {
    brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}
```

有几个`isRunning…​`静态方法,例如`isBrokerAndManagementRunning()`,它验证代理是否启用了管理插件。

###### [](#brokerRunning-configure)配置规则

有时,如果没有代理,你希望测试失败,比如夜间的 CI 构建。要在运行时禁用该规则,请将一个名为`RABBITMQ_SERVER_REQUIRED`的环境变量设置为`true`

你可以使用 setter 或环境变量重写代理属性,例如 hostname:

下面的示例展示了如何使用 setter 重写属性:

```
@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");

static {
    brokerRunning.setHostName("10.0.0.1")
}

@AfterClass
public static void tearDown() {
    brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}
```

你还可以通过设置以下环境变量来覆盖属性:

```
public static final String BROKER_ADMIN_URI = "RABBITMQ_TEST_ADMIN_URI";
public static final String BROKER_HOSTNAME = "RABBITMQ_TEST_HOSTNAME";
public static final String BROKER_PORT = "RABBITMQ_TEST_PORT";
public static final String BROKER_USER = "RABBITMQ_TEST_USER";
public static final String BROKER_PW = "RABBITMQ_TEST_PASSWORD";
public static final String BROKER_ADMIN_USER = "RABBITMQ_TEST_ADMIN_USER";
public static final String BROKER_ADMIN_PW = "RABBITMQ_TEST_ADMIN_PASSWORD";
```

这些环境变量覆盖了默认设置(对于 AMQP 是`localhost:5672`,对于 Management REST API 是`[localhost:15672/api/](http://localhost:15672/api/)`)。

更改主机名会同时影响`amqp``management`RESTAPI 连接(除非显式设置了管理 URI)。

`BrokerRunning`还提供了一个名为`static``static`方法,该方法允许你在包含这些变量的映射中进行传递。它们覆盖系统环境变量。如果你希望对多个测试套件中的测试使用不同的配置,这可能会很有用。重要事项:在调用创建规则实例的任何`isRunning()`静态方法之前,必须调用该方法。变量值应用于此调用后创建的所有实例。调用`clearEnvironmentVariableOverrides()`重置规则以使用默认值(包括任何实际的环境变量)。

在你的测试用例中,你可以在创建连接工厂时使用`brokerRunning``getConnectionFactory()`返回规则的 RabbitMQ`ConnectionFactory`。下面的示例展示了如何做到这一点:

```
@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
    return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
```

##### [](#using-longrunningintegrationtest)使用`LongRunningIntegrationTest`

`LongRunningIntegrationTest`是一条禁用长时间运行测试的规则。你可能希望在开发人员系统上使用该规则,但要确保在例如 Nightly CI 构建上禁用该规则。

下面的示例展示了它的用法:

```
@Rule
public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();
```

要在运行时禁用该规则,请将一个名为`RUN_LONG_INTEGRATION_TESTS`的环境变量设置为`true`

#### [](#junit5-conditions)4.5.6。JUnit5 条件

2.0.2 版引入了对 JUnit5 的支持。

##### [](#using-the-rabbitavailable-annotation)使用`@RabbitAvailable`注释

这个类级注释类似于在[JUnit4`@Rules`]中讨论的`BrokerRunning``@Rule`(#JUnit-rules)。它由`RabbitAvailableCondition`处理。

注释有三个属性:

* `queues`:在每次测试之前声明(和清除)并在所有测试完成后删除的队列数组。

* `management`:如果你的测试还需要在代理上安装管理插件,则将其设置为`true`

* `purgeAfterEach`:(自版本 2.2)当`true`(默认)时,`queues`将在测试之间被清除。

它用于检查代理是否可用,如果不可用,则跳过测试。正如[配置规则](#brokerRunning-configure)中所讨论的,如果`RABBITMQ_SERVER_REQUIRED`没有代理,则称为`true`的环境变量会导致测试快速失败。可以通过使用[配置规则](#brokerRunning-configure)中讨论的环境变量来配置条件。

此外,`RabbitAvailableCondition`支持参数化测试构造函数和方法的参数解析。支持两种参数类型:

* `BrokerRunningSupport`:实例(在 2.2 之前,这是一个 JUnit4`BrokerRunning`实例)

* `ConnectionFactory`:`BrokerRunningSupport`实例的 RabbitMQ 连接工厂

下面的示例展示了这两个方面:

```
@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {

    private final ConnectionFactory connectionFactory;

    public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
        this.connectionFactory = brokerRunning.getConnectionFactory();
    }

    @Test
    public void test(ConnectionFactory cf) throws Exception {
        assertSame(cf, this.connectionFactory);
        Connection conn = this.connectionFactory.newConnection();
        Channel channel = conn.createChannel();
        DeclareOk declareOk = channel.queueDeclarePassive("rabbitAvailableTests.queue");
        assertEquals(0, declareOk.getConsumerCount());
        channel.close();
        conn.close();
    }

}
```

前面的测试是在框架本身中进行的,并验证参数注入和条件是否正确地创建了队列。

一个实际的用户测试可能如下:

```
@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {

    private final CachingConnectionFactory connectionFactory;

    public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
        this.connectionFactory =
            new CachingConnectionFactory(brokerRunning.getConnectionFactory());
    }

    @Test
    public void test() throws Exception {
        RabbitTemplate template = new RabbitTemplate(this.connectionFactory);
        ...
    }
}
```

在测试类中使用 Spring 注释应用程序上下文时,可以通过一个名为`RabbitAvailableCondition.getBrokerRunning()`的静态方法获得对条件的连接工厂的引用。

|   |从版本 2.2 开始,`getBrokerRunning()`返回一个`BrokerRunningSupport`对象;以前,返回的是 JUnit4`BrokerRunnning`实例。<br/>新类的 API 与`BrokerRunning`相同。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的测试来自该框架,并演示了它的用法:

```
@RabbitAvailable(queues = {
        RabbitTemplateMPPIntegrationTests.QUEUE,
        RabbitTemplateMPPIntegrationTests.REPLIES })
@SpringJUnitConfig
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class RabbitTemplateMPPIntegrationTests {

    public static final String QUEUE = "mpp.tests";

    public static final String REPLIES = "mpp.tests.replies";

    @Autowired
    private RabbitTemplate template;

    @Autowired
    private Config config;

    @Test
    public void test() {

        ...

    }

    @Configuration
    @EnableRabbit
    public static class Config {

        @Bean
        public CachingConnectionFactory cf() {
            return new CachingConnectionFactory(RabbitAvailableCondition
                    .getBrokerRunning()
                    .getConnectionFactory());
        }

        @Bean
        public RabbitTemplate template() {

            ...

        }

        @Bean
        public SimpleRabbitListenerContainerFactory
                            rabbitListenerContainerFactory() {

            ...

        }

        @RabbitListener(queues = QUEUE)
        public byte[] foo(byte[] in) {
            return in;
        }

    }

}
```

##### [](#using-the-longrunning-annotation)使用`@LongRunning`注释

`LongRunningIntegrationTest`JUnit4`@Rule`类似,除非将环境变量(或系统属性)设置为`true`,否则此注释将导致跳过测试。下面的示例展示了如何使用它:

```
@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {

    public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";

...

}
```

默认情况下,变量是`RUN_LONG_INTEGRATION_TESTS`,但你可以在注释的`value`属性中指定变量名。

## [](#spring-integration-reference)5。 Spring 整合-参考

参考文档的这一部分快速介绍了 Spring 集成项目中的 AMQP 支持。

### [](#spring-integration-amqp)5.1。 Spring 集成 AMQP 支持

这一简短的章节涵盖了 Spring 集成和 Spring AMQP 项目之间的关系。

#### [](#spring-integration-amqp-introduction)5.1.1。导言

[Spring Integration](https://www.springsource.org/spring-integration)项目包括基于 Spring AMQP 项目的 AMQP 通道适配器和网关。这些适配器是在 Spring 集成项目中开发和发布的。在 Spring 集成中,“通道适配器”是单向的,而“网关”是双向的。我们提供了入站通道适配器、出站通道适配器、入站网关和出站网关。

由于 AMQP 适配器是 Spring 集成发行版的一部分,所以文档可以作为 Spring 集成发行版的一部分使用。我们在这里提供了主要功能的简要概述。有关更多详细信息,请参见[Spring Integration Reference Guide](https://docs.spring.io/spring-integration/reference/htmlsingle/)

#### [](#inbound-channel-adapter)5.1.2。入站通道适配器

要从队列接收 AMQP 消息,可以配置`<inbound-channel-adapter>`。下面的示例展示了如何配置入站通道适配器:

```
<amqp:inbound-channel-adapter channel="fromAMQP"
                              queue-names="some.queue"
                              connection-factory="rabbitConnectionFactory"/>
```

#### [](#outbound-channel-adapter)5.1.3。出站通道适配器

要将 AMQP 消息发送到 Exchange,可以配置`<outbound-channel-adapter>`。你可以在 Exchange 名称之外提供一个“路由密钥”。下面的示例展示了如何定义出站通道适配器:

```
<amqp:outbound-channel-adapter channel="toAMQP"
                               exchange-name="some.exchange"
                               routing-key="foo"
                               amqp-template="rabbitTemplate"/>
```

#### [](#inbound-gateway)5.1.4。入站网关

要从队列接收 AMQP 消息并响应其应答地址,可以配置`<inbound-gateway>`。下面的示例展示了如何定义入站网关:

```
<amqp:inbound-gateway request-channel="fromAMQP"
                      reply-channel="toAMQP"
                      queue-names="some.queue"
                      connection-factory="rabbitConnectionFactory"/>
```

#### [](#outbound-gateway)5.1.5。出站网关

要将 AMQP 消息发送到 Exchange 并从远程客户端接收回响应,可以配置`<outbound-gateway>`。你可以在 Exchange 名称之外提供一个“路由密钥”。下面的示例展示了如何定义出站网关:

```
<amqp:outbound-gateway request-channel="toAMQP"
                       reply-channel="fromAMQP"
                       exchange-name="some.exchange"
                       routing-key="foo"
                       amqp-template="rabbitTemplate"/>
```

## [](#resources)6。其他资源

除了这个参考文档,还有许多其他资源可以帮助你了解 AMQP。

### [](#further-reading)6.1。进一步阅读

对于那些不熟悉 AMQP 的人来说,[规格](https://www.amqp.org/resources/download)实际上是很有可读性的。当然,它是权威的信息来源, Spring AMQP 代码对于任何熟悉该规范的人都应该很容易理解。我们目前对 RabbitMQ 支持的实现基于他们的 2.8.x 版本,它正式支持 AMQP0.8 和 0.9.1。我们建议阅读 0.9.1 文档。

在 RabbitMQ[开始](https://www.rabbitmq.com/how.html)页面上有很多很棒的文章、演示文稿和博客。由于这是 Spring AMQP 当前唯一受支持的实现,因此我们还建议将其作为所有与代理相关的关注的一般起点。

## [](#change-history)附录 A:变更历史

这一节描述了在版本发生变化时所做的更改。

### [](#current-release)a.1。当前版本

[What’s New](#whats-new)

### [](#previous-whats-new)a.2。以前的版本

#### [](#changes-in-2-3-since-2-2)a.2.1。自 2.2 以来 2.3 的变化

本部分描述了版本 2.2 和版本 2.3 之间的更改。有关以前版本的更改,请参见[变更历史](#change-history)

##### [](#connection-factory-changes)连接工厂变更

现在提供了两个额外的连接工厂。有关更多信息,请参见[选择连接工厂](#choosing-factory)

##### [](#rabbitlistener-changes-2)`@RabbitListener`变化

现在可以指定回复内容类型。有关更多信息,请参见[回复 ContentType](#reply-content-type)

##### [](#message-converter-changes)消息转换器更改

如果`ObjectMapper`配置了自定义反序列化器,则`Jackson2JMessageConverter`s 现在可以反序列化抽象类(包括接口)。有关更多信息,请参见[反序列化抽象类](#jackson-abstract)

##### [](#testing-changes)测试更改

提供了一个新的注释`@SpringRabbitTest`,用于在不使用`SpringBootTest`时自动配置一些基础设施 bean。有关更多信息,请参见[@SpringRabbitTest](#spring-rabbit-test)

##### [](#rabbittemplate-changes)RabbitTemplate 更改

模板的`ReturnCallback`已被重构为`ReturnsCallback`,以便在 lambda 表达式中更简单地使用。有关更多信息,请参见[相关发布者确认并返回](#template-confirms)

当使用 Returns 和相关确认时,`CorrelationData`现在需要唯一的`id`属性。有关更多信息,请参见[相关发布者确认并返回](#template-confirms)

当使用直接回复时,你现在可以配置模板,这样服务器就不需要返回与回复相关的数据。有关更多信息,请参见[RabbitMQ 直接回复](#direct-reply-to)

##### [](#listener-container-changes)侦听器容器更改

现在可以使用一个新的侦听器容器属性`consumeDelay`;当使用[RabbitMQ 分片插件](https://github.com/rabbitmq/rabbitmq-sharding)时,该属性很有帮助。

默认的`JavaLangErrorHandler`现在调用`System.exit(99)`。要恢复到以前的行为(什么都不做),请添加一个 no-ophandler。

容器现在支持`globalQos`属性,以便在全局范围内为通道而不是为通道上的每个消费者应用`prefetchCount`

有关更多信息,请参见[消息侦听器容器配置](#containerAttributes)

##### [](#messagepostprocessor-changes)MessagePostProcessor 更改

压缩`MessagePostProcessor`s 现在使用逗号来分隔多个内容编码,而不是冒号。解压器可以处理这两种格式,但是,如果你使用这个版本生成的消息被 2.2.12 之前的版本使用,那么你应该将压缩器配置为使用旧的分隔符。有关更多信息,请参见[修改消息-压缩和更多](#post-processing)中的重要注释。

##### [](#multiple-broker-support-improvements)多个代理支持改进

有关更多信息,请参见[多个代理(或集群)支持](#multi-rabbit)

##### [](#republishmessagerecoverer-changes)RepublishMessageRecoverer 更改

不提供此 recoverer 的支持发布服务器确认的新子类。有关更多信息,请参见[消息侦听器和异步情况](#async-listeners)

#### [](#changes-in-2-2-since-2-1)a.2.2。自 2.1 以来 2.2 的变化

本部分描述了版本 2.1 和版本 2.2 之间的更改。

##### [](#package-changes)软件包更改

以下类/接口已从`org.springframework.amqp.rabbit.core.support`移至`org.springframework.amqp.rabbit.batch`:

* `BatchingStrategy`

* `MessageBatch`

* `SimpleBatchingStrategy`

此外,`ListenerExecutionFailedException`已从`org.springframework.amqp.rabbit.listener.exception`移至`org.springframework.amqp.rabbit.support`

##### [](#dependency-changes)依赖项更改

JUnit(4)现在是一个可选的依赖项,并且将不再以传递依赖项的形式出现。

`spring-rabbit-junit`模块现在是**编译**模块中的`spring-rabbit-test`依赖项,以便在仅使用单个`spring-rabbit-test`时获得更好的目标应用程序开发体验,我们获得了 AMQP 组件的完整测试实用程序堆栈。

##### [](#breaking-api-changes)“breaking”API 变更

JUnit(5)`RabbitAvailableCondition.getBrokerRunning()`现在返回一个`BrokerRunningSupport`实例,而不是一个`BrokerRunning`实例,后者取决于 JUnit4。它有相同的 API,所以只需要更改任何引用的类名。有关更多信息,请参见[JUnit5 条件](#junit5-conditions)

##### [](#listenercontainer-changes)ListenerContainer 更改

默认情况下,即使确认模式是手动的,具有致命异常的消息现在也会被拒绝,并且不会重新请求。有关更多信息,请参见[异常处理](#exception-handling)

监听器的性能现在可以使用微米计`Timer`s 进行监视。有关更多信息,请参见[监视监听器性能](#micrometer)

##### [](#rabbitlistener-changes-3)@RabbitListener 更改

现在可以在每个侦听器上配置`executor`,覆盖工厂配置,以更容易地识别与侦听器关联的线程。现在可以使用注释的`ackMode`属性覆盖容器工厂的`acknowledgeMode`属性。有关更多信息,请参见[覆盖集装箱工厂的属性](#listener-property-overrides)

当使用[batching](#receiving-batch)时,`@RabbitListener`方法现在可以在一个调用中接收一批完整的消息,而不是一次获得它们。

当一次只接收一条批处理消息时,最后一条消息的`isLastInBatch`消息属性设置为 true。

此外,收到的批处理消息现在包含`amqp_batchSize`头。

侦听器也可以使用在`SimpleMessageListenerContainer`中创建的批处理,即使该批处理不是由生成器创建的。有关更多信息,请参见[选择容器](#choose-container)

Spring 数据投影接口现在由`Jackson2JsonMessageConverter`支持。有关更多信息,请参见[Using Spring Data Projection Interfaces](#data-projection)

如果不存在`Jackson2JsonMessageConverter`属性,或者它是默认的(`application/octet-string`),则`Jackson2JsonMessageConverter`现在假定内容是 JSON。有关更多信息,请参见[converting from a`Message`](#Jackson2jsonMessageConverter-from-message)。

类似地,如果不存在`contentType`属性,或者它是默认的(`application/octet-string`),则`Jackson2XmlMessageConverter`现在假定内容是 XML。有关更多信息,请参见[`Jackson2XmlMessageConverter`](#Jackson2xml)。

`@RabbitListener`方法返回结果时, Bean 和`Method`现在在回复消息属性中可用。这允许`beforeSendReplyMessagePostProcessor`的配置,例如,在回复中设置一个头,以指示在服务器上调用了哪个方法。有关更多信息,请参见[回复管理](#async-annotation-driven-reply)

现在可以配置`ReplyPostProcessor`,以便在发送回复消息之前对其进行修改。有关更多信息,请参见[回复管理](#async-annotation-driven-reply)

##### [](#amqp-logging-appenders-changes)AMQP 日志附录更改

log4j 和 logback`AmqpAppender`s 现在支持`verifyHostname`SSL 选项。

现在还可以将这些附录配置为不将 MDC 条目添加为标题。引入了`addMdcAsHeaders`布尔选项来配置这样的行为。

Appenders 现在支持`SaslConfig`属性。

有关更多信息,请参见[日志记录子系统 AMQP 附录](#logging)

##### [](#messagelisteneradapter-changes)MessageListenerAdapter 更改

`MessageListenerAdapter`现在提供了一个新的`buildListenerArguments(Object, Channel, Message)`方法来构建一个参数数组,这些参数将被传递到目标侦听器中,而一个旧的参数将被弃用。有关更多信息,请参见[`MessageListenerAdapter`]。

##### [](#exchangequeue-declaration-changes)交换/队列声明更改

用于创建`ExchangeBuilder``QueueBuilder`用于由`Exchange`声明的`Queue`对象的 fluent API 现在支持“众所周知”的参数。有关更多信息,请参见[用于队列和交换的 Builder API](#builder-api)

`RabbitAdmin`有一个新的属性`explicitDeclarationsOnly`。有关更多信息,请参见[有条件声明](#conditional-declaration)

##### [](#connection-factory-changes-2)连接工厂变更

`CachingConnectionFactory`有一个新的属性`shuffleAddresses`。当提供代理节点地址列表时,将在创建连接之前对列表进行调整,以便尝试连接的顺序是随机的。有关更多信息,请参见[连接到集群](#cluster)

当使用 Publisher 确认和返回时,回调现在在连接工厂的`executor`上调用。如果你在回调中执行 Rabbit 操作,这就避免了`amqp-clients`库中可能出现的死锁。有关更多信息,请参见[相关发布者确认并返回](#template-confirms)

此外,现在使用`ConfirmType`枚举来指定发布服务器确认类型,而不是使用两个互斥的 setter 方法。

当启用 SSL 时,`RabbitConnectionFactoryBean`现在默认使用 TLS1.2。有关更多信息,请参见[`RabbitConnectionFactoryBean`和配置 SSL]。

##### [](#new-messagepostprocessor-classes)新的 MessagePostProcessor 类

当消息内容编码设置为`deflate`时,分别添加了类`DeflaterPostProcessor``InflaterPostProcessor`以支持压缩和解压。

##### [](#other-changes)其他更改

`Declarables`对象(用于声明多个队列、交换、绑定)现在为每个类型都有一个过滤 getter。有关更多信息,请参见[声明交换、队列和绑定的集合](#collection-declaration)

现在可以在`RabbitAdmin`处理其声明之前自定义每个`Declarable` Bean。有关更多信息,请参见[交换、队列和绑定的自动声明](#automatic-declaration)

`singleActiveConsumer()`已被添加到`QueueBuilder`以设置`x-single-active-consumer`队列参数。有关更多信息,请参见[用于队列和交换的 Builder API](#builder-api)

类型为`Class<?>`的出站标头现在使用`getName()`而不是`toString()`进行映射。有关更多信息,请参见[消息属性转换器](#message-properties-converters)

现在支持恢复失败的生产者创建的批处理。有关更多信息,请参见[使用批处理侦听器重试](#batch-retry)

#### [](#changes-in-2-1-since-2-0)a.2.3。自 2.0 以来 2.1 的变化

##### [](#amqp-client-library)AMQP 客户库

Spring AMQP 现在使用由 RabbitMQ 团队提供的`amqp-client`库的 5.4.x 版本。默认情况下,此客户端配置了自动恢复功能。见[RabbitMQ 自动连接/拓扑恢复](#auto-recovery)

|   |从版本 4.0 开始,客户端默认支持自动恢复。<br/>虽然与此功能兼容, Spring AMQP 有自己的恢复机制,并且客户端恢复功能通常不需要。<br/>我们建议禁用`amqp-client`自动恢复,为了避免在代理可用但连接尚未恢复时获得`AutoRecoverConnectionNotCurrentlyOpenException`实例。<br/>从 1.7.1 版本开始, Spring AMQP 禁用它,除非你显式地创建自己的 RabbitMQ 连接工厂并将其提供给`CachingConnectionFactory`<br/>RabbitMQ`ConnectionFactory``RabbitConnectionFactoryBean`创建的实例还具有默认禁用的选项。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#package-changes-2)软件包更改

某些类已转移到不同的包。大多数是内部类,不会影响用户应用程序。两个例外是`ChannelAwareMessageListener``RabbitListenerErrorHandler`。这些接口现在在`org.springframework.amqp.rabbit.listener.api`中。

##### [](#publisher-confirms-changes)Publisher 确认更改

当存在未完成的确认时,启用了发布者确认的通道不会返回到缓存中。有关更多信息,请参见[相关发布者确认并返回](#template-confirms)

##### [](#listener-container-factory-improvements)监听器容器工厂改进

现在,你可以使用侦听器容器工厂来创建任何侦听器容器,而不仅仅是用于`@RabbitListener`注释或`@RabbitListenerEndpointRegistry`注释的侦听器容器。有关更多信息,请参见[使用集装箱工厂](#using-container-factories)

`ChannelAwareMessageListener`现在继承自`MessageListener`

##### [](#broker-event-listener)Broker 事件侦听器

引入`BrokerEventListener`以将选定的代理事件发布为`ApplicationEvent`实例。有关更多信息,请参见[代理事件监听器](#broker-events)

##### [](#rabbitadmin-changes-2)RabbitAdmin 更改

`RabbitAdmin`发现类型为`Declarables`的 bean(这是`Declarable`-`Queue``Exchange``Binding`对象的容器)并在代理上声明所包含的对象。不鼓励用户使用声明`<Collection<Queue>>`(和其他)的旧机制,而应该使用`Declarables`bean。默认情况下,旧机制将被禁用。有关更多信息,请参见[声明交换、队列和绑定的集合](#collection-declaration)

`AnonymousQueue`实例现在声明为`x-queue-master-locator`,默认设置为`client-local`,以确保队列是在应用程序所连接的节点上创建的。有关更多信息,请参见[配置代理](#broker-configuration)

##### [](#rabbittemplate-changes-2)RabbitTemplate 更改

现在,你可以使用`noLocalReplyConsumer`选项配置`RabbitTemplate`,以便在`sendAndReceive()`操作中控制用于回复消费者的`noLocal`标志。有关更多信息,请参见[请求/回复消息](#request-reply)

`CorrelationData`对于发布者确认现在有一个`ListenableFuture`,你可以使用它来获得确认,而不是使用回调。当启用返回和确认时,相关数据(如果提供)将填充返回的消息。有关更多信息,请参见[相关发布者确认并返回](#template-confirms)

现在提供了一个名为`replyTimedOut`的方法,用于通知子类答复已超时,从而允许进行任何状态清理。有关更多信息,请参见[回复超时](#reply-timeout)

现在,你可以指定一个`ErrorHandler`,当发送回复时发生异常(例如,延迟回复)时,在使用`DirectReplyToMessageListenerContainer`(默认)请求/回复时要调用的`ErrorHandler`。参见`setReplyErrorHandler`上的`RabbitTemplate`。(也是从 2.0.11 开始)。

##### [](#message-conversion)消息转换

我们引入了一个新的`Jackson2XmlMessageConverter`,以支持将消息从 XML 格式转换为 XML 格式。有关更多信息,请参见[`Jackson2XmlMessageConverter`](#Jackson2xml)。

##### [](#management-rest-api-2)管理 REST API

现在不赞成`RabbitManagementTemplate`,而赞成直接使用`com.rabbitmq.http.client.Client`(或`com.rabbitmq.http.client.ReactorNettyClient`)。有关更多信息,请参见[RabbitMQ REST API](#management-rest-api)

##### [](#rabbitlistener-changes-4)`@RabbitListener`变化

现在可以将侦听器容器工厂配置为`RetryTemplate`,也可以在发送回复时使用`RecoveryCallback`。有关更多信息,请参见[启用监听器端点注释](#async-annotation-driven-enable)

##### [](#async-rabbitlistener-return)异步`@RabbitListener`返回

`@RabbitListener`方法现在可以返回`ListenableFuture<?>``Mono<?>`。有关更多信息,请参见[异步`@RabbitListener`返回类型]。

##### [](#connection-factory-bean-changes)连接工厂 Bean 变更

默认情况下,`RabbitConnectionFactoryBean`现在调用`enableHostnameVerification()`。要恢复到以前的行为,请将`enableHostnameVerification`属性设置为`false`

##### [](#connection-factory-changes-3)连接工厂变更

现在,`CachingConnectionFactory`无条件地禁用底层 RabbitMQ`ConnectionFactory`中的自动恢复,即使构造函数中提供了预先配置的实例。虽然已经采取措施使 Spring AMQP 与自动恢复兼容,但在仍然存在问题的情况下出现了某些情况。 Spring AMQP 自 1.0.0 以来已经有了自己的恢复机制,并且不需要使用由客户端提供的恢复。虽然仍然有可能在`CachingConnectionFactory`后启用该功能(使用`cachingConnectionFactory.getRabbitConnectionFactory()``.setAutomaticRecoveryEnabled()`),**我们强烈建议你不要这样做。**被构造。如果在直接使用客户端工厂(而不是使用 Spring AMQP 组件)时需要自动恢复连接,我们建议你使用单独的 RabbitMQ。

##### [](#listener-container-changes-2)侦听器容器更改

如果存在`x-death`报头,那么默认的`ConditionalRejectingErrorHandler`现在将完全丢弃导致致命错误的消息。有关更多信息,请参见[异常处理](#exception-handling)

##### [](#immediate-requeue)立即请求

引入了一个新的`ImmediateRequeueAmqpException`来通知侦听器容器消息必须重新排队。要使用此功能,需要添加一个新的`ImmediateRequeueMessageRecoverer`实现。

有关更多信息,请参见[消息侦听器和异步情况](#async-listeners)

#### [](#changes-in-2-0-since-1-7)a.2.4。自 1.7 以来 2.0 的变化

##### [](#using-cachingconnectionfactory)使用`CachingConnectionFactory`

从版本 2.0.2 开始,你可以将`RabbitTemplate`配置为使用与侦听器容器使用的不同的连接。这一变化避免了当生产商因任何原因而受阻时,消费者陷入僵局。有关更多信息,请参见[使用单独的连接](#separate-connection)

##### [](#amqp-client-library-2)AMQP 客户库

Spring AMQP 现在使用由 RabbitMQ 团队提供的`amqp-client`库的新的 5.0.x 版本。默认情况下,此客户端配置了自动恢复。见[RabbitMQ 自动连接/拓扑恢复](#auto-recovery)

|   |从版本 4.0 开始,客户端默认启用自动恢复。<br/>虽然兼容此功能, Spring AMQP 有自己的恢复机制,并且客户端恢复功能一般不需要。<br/>我们建议你禁用`amqp-client`自动恢复,为了避免在代理可用但连接尚未恢复时获得`AutoRecoverConnectionNotCurrentlyOpenException`实例。<br/>从 1.7.1 版本开始, Spring AMQP 禁用它,除非你显式地创建自己的 RabbitMQ 连接工厂并将其提供给`CachingConnectionFactory`<br/>RabbitMQ`ConnectionFactory``RabbitConnectionFactoryBean`创建的实例还具有默认禁用的选项。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#general-changes)一般变化

现在`ExchangeBuilder`默认情况下构建持久交换。在`@QeueueBinding`中使用的`@Exchange`注释也默认声明持久交换。在`@RabbitListener`中使用的`@Queue`注释默认情况下声明持久队列(如果命名)和非持久队列(如果匿名)。有关更多信息,请参见[用于队列和交换的 Builder API](#builder-api)[注释驱动的监听器端点](#async-annotation-driven)

##### [](#deleted-classes)删除类

`UniquelyNameQueue`不再提供。使用唯一的名称创建持久的非自动删除队列是不常见的。这个类已被删除。如果你需要它的功能,请使用`new Queue(UUID.randomUUID().toString())`

##### [](#new-listener-container)新建侦听器容器

已在现有的`SimpleMessageListenerContainer`旁边添加了`DirectMessageListenerContainer`。有关选择使用哪个容器以及如何配置它们的信息,请参见[选择容器](#choose-container)[消息侦听器容器配置](#containerAttributes)

##### [](#log4j-appender)log4j 附录

由于 log4j 的报废,此附录不再可用。有关可用日志附录的信息,请参见[日志记录子系统 AMQP 附录](#logging)

##### [](#rabbittemplate-changes-3)`RabbitTemplate`变更

|   |以前,如果一个非事务性事务`RabbitTemplate`运行在事务性侦听器容器线程上,那么它就参与了一个现有事务。,<br/>这是一个严重的错误,<br/>但是,用户可能依赖于这种行为,<br/>从 1.6.2 版本开始,你必须在模板上设置`channelTransacted`布尔,才能让它参与容器事务。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

`RabbitTemplate`现在使用`DirectReplyToMessageListenerContainer`(默认情况下),而不是为每个请求创建一个新的使用者。有关更多信息,请参见[RabbitMQ 直接回复](#direct-reply-to)

`AsyncRabbitTemplate`现在支持直接回复。有关更多信息,请参见[异步兔子模板](#async-template)

`RabbitTemplate``AsyncRabbitTemplate`现在有`receiveAndConvert``convertSendAndReceiveAsType`方法,它们接受一个`ParameterizedTypeReference<T>`参数,让调用者指定将结果转换为哪个类型。这对于复杂类型或在消息头中未传递类型信息的情况特别有用。它需要一个`SmartMessageConverter`,如`Jackson2JsonMessageConverter`。有关更多信息,请参见[接收消息](#receiving-messages)[请求/回复消息](#request-reply)[异步兔子模板](#async-template),以及[从`Message``RabbitTemplate`转换](#json-complex)。

现在可以使用`RabbitTemplate`在专用通道上执行多个操作。有关更多信息,请参见[作用域操作](#scoped-operations)

##### [](#listener-adapter)侦听器适配器

一个方便的`FunctionalInterface`可用于使用带有`MessageListenerAdapter`的 lambdas。有关更多信息,请参见[`MessageListenerAdapter`](#message-listener-adapter)。

##### [](#listener-container-changes-3)侦听器容器更改

###### [](#prefetch-default-value)预取默认值

预取默认值过去是 1,这可能导致有效消费者的利用率不足。默认的预取值现在是 250,这应该会使消费者在大多数常见的情况下都很忙,从而提高吞吐量。

|   |在某些情况下,预取取值应该<br/>较低,例如,对于较大的消息,尤其是在处理速度较慢的情况下(消息可能会将<br/>累加到客户端进程中的大量内存中),如果需要严格的消息排序<br/>(在这种情况下,预取值应该设置为 1),<br/>还可以使用低容量消息传递和多个消费者(包括单个侦听器容器实例中的并发性),你可能希望减少预取,以使消息在消费者之间的分布更加均匀。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

有关 Prefetch 的更多背景信息,请参见这篇关于[RabbitMQ 中的消费者利用](https://www.rabbitmq.com/blog/2014/04/14/finding-bottlenecks-with-rabbitmq-3-3/)的文章和这篇关于[排队论](https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/)的文章。

###### [](#message-count)消息计数

以前,对于容器发出的消息,`MessageProperties.getMessageCount()`返回`0`。此属性仅当你使用`basicGet`(例如,从`RabbitTemplate.receive()`方法)时才适用,并且现在对于容器消息初始化为`null`

###### [](#transaction-rollback-behavior)事务回滚行为

无论是否配置了事务管理器,事务回滚上的消息重新队列现在都是一致的。有关更多信息,请参见[关于回滚收到的消息的说明](#transaction-rollback)

###### [](#shutdown-behavior)关机行为

如果容器线程不响应`shutdownTimeout`内的关机,则默认情况下将强制关闭通道。有关更多信息,请参见[消息侦听器容器配置](#containerAttributes)

###### [](#after-receive-message-post-processors)接收消息后处理程序

如果`afterReceiveMessagePostProcessors`属性中的`MessagePostProcessor`返回`null`,则该消息将被丢弃(并在适当的情况下进行确认)。

##### [](#connection-factory-changes-4)连接工厂变更

连接和通道侦听器接口现在提供了一种机制来获取有关异常的信息。有关更多信息,请参见[连接和通道侦听器](#connection-channel-listeners)[发布是异步的——如何检测成功和失败](#publishing-is-async)

现在提供了一个新的`ConnectionNameStrategy`,用于从`AbstractConnectionFactory`填充目标 RabbitMQ 连接的特定于应用程序的标识。有关更多信息,请参见[连接和资源管理](#connections)

##### [](#retry-changes)重试更改

不再提供`MissingMessageIdAdvice`。它的功能现在是内置的。有关更多信息,请参见[同步操作中的故障和重试选项](#retry)

##### [](#anonymous-queue-naming)匿名队列命名

默认情况下,`AnonymousQueues`现在使用默认的`Base64UrlNamingStrategy`来命名,而不是简单的`UUID`字符串。有关更多信息,请参见[`AnonymousQueue`]。

##### [](#rabbitlistener-changes-5)`@RabbitListener`变更

现在可以在`@RabbitListener`注释中提供简单的队列声明(仅绑定到默认的交换)。有关更多信息,请参见[注释驱动的监听器端点](#async-annotation-driven)

你现在可以配置`@RabbitListener`注释,以便将任何异常返回给发送者。你还可以配置`RabbitListenerErrorHandler`来处理异常。有关更多信息,请参见[处理异常](#annotation-error-handling)

使用`@QueueBinding`注释时,现在可以将队列与多个路由键绑定。另外`@QueueBinding.exchange()`现在支持自定义交换类型,并默认声明持久交换。

现在,你可以在注释级设置侦听器容器的`concurrency`,而不必为不同的并发设置配置不同的容器工厂。

现在可以在注释级设置侦听器容器的`autoStartup`属性,从而覆盖容器工厂中的默认设置。

你现在可以在`RabbitListener`容器工厂中设置接收之后和发送之前的`MessagePostProcessor`实例。

有关更多信息,请参见[注释驱动的监听器端点](#async-annotation-driven)

从版本 2.0.3 开始,类级`@RabbitHandler`上的一个`@RabbitListener`注释可以被指定为默认值。有关更多信息,请参见[多方法侦听器](#annotation-method-selection)

##### [](#container-conditional-rollback)容器条件回滚

当使用外部事务管理器(例如 JDBC)时,当你为容器提供事务属性时,现在支持基于规则的回滚。现在,当你使用事务建议时,它也更加灵活。有关更多信息,请参见[条件回滚](#conditional-rollback)

##### [](#remove-jackson-1-x-support)删除 Jackson1.x 支持

在以前的版本中不推荐的 Jackson`1.x`转换器和相关组件现在已被删除。你可以使用基于 Jackson2.x 的类似组件。有关更多信息,请参见[Jackson2JSONMessageConverter](#json-message-converter)

##### [](#json-message-converter-2)JSON 消息转换器

当将入站 JSON 消息的`*TypeId*`设置为`Hashtable`时,现在默认的转换类型是`LinkedHashMap`。在此之前,它是`Hashtable`。要恢复到`Hashtable`,可以在`DefaultClassMapper`上使用`setDefaultMapType`

##### [](#xml-parsers)XML 解析器

在解析`Queue``Exchange`XML 组件时,如果存在`id`属性,则解析器不再将`name`属性值注册为 Bean 别名。有关更多信息,请参见[关于`id``name`属性的注释]。

##### [](#blocked-connection)阻塞连接

现在可以将`com.rabbitmq.client.BlockedListener`注入`org.springframework.amqp.rabbit.connection.Connection`对象。此外,当连接被代理阻塞或解除锁定时,`ConnectionBlockedEvent``ConnectionUnblockedEvent`事件由`ConnectionFactory`发出。

有关更多信息,请参见[连接和资源管理](#connections)

#### [](#changes-in-1-7-since-1-6)a.2.5。1.7 自 1.6 以来的变化

##### [](#amqp-client-library-3)AMQP 客户库

Spring AMQP 现在使用由 RabbitMQ 团队提供的`amqp-client`库的新的 4.0.x 版本。默认情况下,此客户端配置了自动恢复功能。见[RabbitMQ 自动连接/拓扑恢复](#auto-recovery)

|   |4.0.x 客户端默认支持自动恢复。<br/>虽然与此功能兼容, Spring AMQP 有自己的恢复机制,并且客户端恢复功能通常不需要。<br/>我们建议禁用`amqp-client`自动恢复,为了避免在代理可用但连接尚未恢复时获得`AutoRecoverConnectionNotCurrentlyOpenException`实例。<br/>从 1.7.1 版本开始, Spring AMQP 禁用它,除非你显式地创建自己的 RabbitMQ 连接工厂,并将其提供给`CachingConnectionFactory`<br/>RabbitMQ`ConnectionFactory``RabbitConnectionFactoryBean`创建的实例还具有默认禁用的选项。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### [](#log4j-2-upgrade)log4j2 升级

最小的 log4j2 版本(对于`AmqpAppender`)现在是`2.7`。该框架不再与以前的版本兼容。有关更多信息,请参见[日志记录子系统 AMQP 附录](#logging)

##### [](#logback-appender-2)翻录附录

默认情况下,此附录不再捕获调用方数据(方法、行号)。你可以通过设置`includeCallerData`配置选项重新启用它。有关可用日志附录的信息,请参见[日志记录子系统 AMQP 附录](#logging)

##### [](#spring-retry-upgrade) Spring 重试升级

重试的最低版本现在是`1.2`。该框架不再与以前的版本兼容。

###### [](#shutdown-behavior-2)关机行为

你现在可以将`forceCloseChannel`设置为`true`,这样,如果容器线程在`shutdownTimeout`内没有响应关机,则将强制关闭通道,从而导致任何未加锁的消息重新排队。有关更多信息,请参见[消息侦听器容器配置](#containerAttributes)

##### [](#fasterxml-jackson-upgrade)fasterxml Jackson 升级

Jackson 的最低版本现在是`2.8`。该框架不再与以前的版本兼容。

##### [](#junit-rules-2)JUnit`@Rules`

以前由框架内部使用的规则现在可以在一个名为`spring-rabbit-junit`的单独 JAR 中使用。有关更多信息,请参见[JUnit4`@Rules`]。

##### [](#container-conditional-rollback-2)容器条件回滚

当你使用外部事务管理器(例如 JDBC)时,当你为容器提供事务属性时,现在支持基于规则的回滚。现在,当你使用事务建议时,它也更加灵活。

##### [](#connection-naming-strategy)连接命名策略

现在提供了一个新的`ConnectionNameStrategy`,用于从`AbstractConnectionFactory`填充目标 RabbitMQ 连接的特定于应用程序的标识。有关更多信息,请参见[连接和资源管理](#connections)

##### [](#listener-container-changes-4)侦听器容器更改

###### [](#transaction-rollback-behavior-2)事务回滚行为

现在,无论是否配置了事务管理器,都可以将事务回滚上的消息重新排队配置为一致。有关更多信息,请参见[关于回滚收到的消息的说明](#transaction-rollback)

#### [](#earlier-releases)a.2.6。早期版本

有关以前版本的更改,请参见[以前的版本](#previous-whats-new)

#### [](#changes-in-1-6-since-1-5)a.2.7。自 1.5 以来 1.6 的变化

##### [](#testing-support)测试支持

现在提供了一个新的测试支持库。有关更多信息,请参见[测试支持](#testing)

##### [](#builder)建设者

现在可以使用为配置`Queue``Exchange`对象提供流畅 API 的构建器。有关更多信息,请参见[用于队列和交换的 Builder API](#builder-api)

##### [](#namespace-changes)名称空间变更

###### [](#connection-factory-2)连接工厂

现在可以将`thread-factory`添加到连接工厂 Bean 声明中——例如,用于命名由`amqp-client`库创建的线程。有关更多信息,请参见[连接和资源管理](#connections)

当你使用`CacheMode.CONNECTION`时,现在可以限制允许的连接总数。有关更多信息,请参见[连接和资源管理](#connections)

###### [](#queue-definitions)队列定义

现在可以为匿名队列提供命名策略。有关更多信息,请参见[`AnonymousQueue`]。

##### [](#listener-container-changes-5)侦听器容器更改

###### [](#idle-message-listener-detection)空闲消息侦听器检测

现在可以将侦听器容器配置为在空闲时发布`ApplicationEvent`实例。有关更多信息,请参见[检测空闲异步消费者](#idle-containers)

###### [](#mismatched-queue-detection)不匹配队列检测

默认情况下,当侦听器容器启动时,如果检测到具有不匹配属性或参数的队列,则容器会记录异常,但会继续侦听。容器现在具有一个名为`mismatchedQueuesFatal`的属性,如果在启动过程中检测到问题,该属性将阻止容器(和上下文)启动。如果稍后检测到问题,例如从连接失败中恢复后,它还会停止容器。有关更多信息,请参见[消息侦听器容器配置](#containerAttributes)

###### [](#listener-container-logging)侦听器容器日志记录

现在,侦听器容器将其`beanName`提供给内部`SimpleAsyncTaskExecutor`作为`threadNamePrefix`。它对日志分析很有用。

###### [](#default-error-handler)默认错误处理程序

默认的错误处理程序(`ConditionalRejectingErrorHandler`)现在认为不可恢复的`@RabbitListener`异常是致命的。有关更多信息,请参见[异常处理](#exception-handling)

##### [](#autodeclare-and-rabbitadmin-instances)`AutoDeclare`和`RabbitAdmin`实例

有关在应用程序上下文中使用`RabbitAdmin`实例对该选项语义的一些更改,请参见[消息侦听器容器配置](#containerAttributes)`autoDeclare`)。

##### [](#amqptemplate-receive-with-timeout)`AmqpTemplate`:超时接收

已经为`AmqpTemplate`及其`RabbitTemplate`实现引入了许多带有`timeout`的新`receive()`方法。有关更多信息,请参见[轮询消费者](#polling-consumer)

##### [](#using-asyncrabbittemplate)使用`AsyncRabbitTemplate`

引入了一个新的`AsyncRabbitTemplate`。此模板提供了许多发送和接收方法,其中返回值为`ListenableFuture`,以后可以使用它同步或异步地获得结果。有关更多信息,请参见[异步兔子模板](#async-template)

##### [](#rabbittemplate-changes-4)`RabbitTemplate`变化

1.4.1 引入了在代理支持[直接回复](https://www.rabbitmq.com/direct-reply-to.html)时使用[直接回复](https://www.rabbitmq.com/direct-reply-to.html)的能力。这比为每个回复使用临时队列更有效。此版本允许你通过将`useTemporaryReplyQueues`属性设置为`true`来覆盖此默认行为并使用临时队列。有关更多信息,请参见[RabbitMQ 直接回复](#direct-reply-to)

`RabbitTemplate`现在支持`user-id-expression`(在使用 Java 配置时`userIdExpression`)。有关更多信息,请参见[经过验证的用户 ID RabbitMQ 文档](https://www.rabbitmq.com/validated-user-id.html)[已验证的用户 ID](#template-user-id)

##### [](#message-properties)消息属性

###### [](#using-correlationid)使用`CorrelationId`

消息属性`correlationId`现在可以是`String`。有关更多信息,请参见[消息属性转换器](#message-properties-converters)

###### [](#long-string-headers)长字符串标题

以前,`DefaultMessagePropertiesConverter`将比长字符串限制(缺省 1024)更长的头转换为`DataInputStream`(实际上,它引用了`LongString`实例的`DataInputStream`)。在输出时,这个头没有被转换(除了转换为字符串——例如,通过在流上调用`toString()``[[email protected]](/cdn-cgi/l/email-protection)`)。

在此版本中,长`LongString`实例现在默认保留为`LongString`实例。你可以使用`getBytes[]``toString()``getStream()`方法访问内容。大量输入的`LongString`现在也可以在输出上正确地“转换”。

有关更多信息,请参见[消息属性转换器](#message-properties-converters)

###### [](#inbound-delivery-mode)入站交付模式

`deliveryMode`属性不再映射到`MessageProperties.deliveryMode`。如果使用相同的`MessageProperties`对象发送出站消息,则此更改将避免意外传播。相反,入站`deliveryMode`标头被映射到`MessageProperties.receivedDeliveryMode`

有关更多信息,请参见[消息属性转换器](#message-properties-converters)

当使用带注释的端点时,标题提供在名为`AmqpHeaders.RECEIVED_DELIVERY_MODE`的标题中。

有关更多信息,请参见[带注释的端点方法签名](#async-annotation-driven-enable-signature)

###### [](#inbound-user-id)入站用户 ID

`user_id`属性不再映射到`MessageProperties.userId`。如果使用相同的`MessageProperties`对象发送出站消息,则此更改将避免意外传播。相反,入站`userId`头被映射到`MessageProperties.receivedUserId`

有关更多信息,请参见[消息属性转换器](#message-properties-converters)

当你使用带注释的端点时,标题将在名为`AmqpHeaders.RECEIVED_USER_ID`的标题中提供。

有关更多信息,请参见[带注释的端点方法签名](#async-annotation-driven-enable-signature)

##### [](#rabbitadmin-changes-3)`RabbitAdmin`变化

###### [](#declaration-failures)宣告失败

以前,`ignoreDeclarationFailures`标志仅对通道上的`IOException`生效(例如不匹配的参数)。它现在对任何例外情况生效(例如`TimeoutException`)。此外,现在只要声明失败,就会发布`DeclarationExceptionEvent``RabbitAdmin`Last declaration 事件也可以作为属性`lastDeclarationExceptionEvent`使用。有关更多信息,请参见[配置代理](#broker-configuration)

##### [](#rabbitlistener-changes-6)`@RabbitListener`变更

###### [](#multiple-containers-for-each-bean)每个容器有多个 Bean

当你使用 Java8 或更高版本时,你现在可以将多个`@RabbitListener`注释添加到`@Bean`类或它们的方法中。当使用 Java7 或更早版本时,你可以使用`@RabbitListeners`容器注释来提供相同的功能。有关更多信息,请参见[`@Repeatable``@RabbitListener`]。

###### [](#sendto-spel-expressions)`@SendTo`spel 表达式

`@SendTo`用于路由不带`replyTo`属性的回复,现在可以根据请求/回复计算 SPEL 表达式。有关更多信息,请参见[回复管理](#async-annotation-driven-reply)

###### [](#queuebinding-improvements)`@QueueBinding`改进

现在可以在`@QueueBinding`注释中指定队列、交换和绑定的参数。现在`@QueueBinding`支持报头交换。有关更多信息,请参见[注释驱动的监听器端点](#async-annotation-driven)

##### [](#delayed-message-exchange-2)延迟消息交换

Spring AMQP 现在拥有对 RabbitMQ 延迟消息交换插件的一流支持。有关更多信息,请参见[延迟的消息交换](#delayed-message-exchange)

##### [](#exchange-internal-flag)交易所内部标志

任何`Exchange`定义现在都可以标记为`internal`,并且`RabbitAdmin`在声明交易所时将该值传递给代理。有关更多信息,请参见[配置代理](#broker-configuration)

##### [](#cachingconnectionfactory-changes)`CachingConnectionFactory`变化

###### [](#cachingconnectionfactory-cache-statistics)`CachingConnectionFactory`缓存统计信息

`CachingConnectionFactory`现在在运行时和 JMX 上提供缓存属性。有关更多信息,请参见[运行时缓存属性](#runtime-cache-properties)

###### [](#accessing-the-underlying-rabbitmq-connection-factory)访问底层 RabbitMQ 连接工厂

添加了一个新的 getter 以提供对底层工厂的访问。例如,你可以使用此 getter 添加自定义连接属性。有关更多信息,请参见[添加自定义客户端连接属性](#custom-client-props)

###### [](#channel-cache)通道缓存

默认通道缓存大小已从 1 增加到 25。有关更多信息,请参见[连接和资源管理](#connections)

此外,`SimpleMessageListenerContainer`不再调整缓存大小,使其至少与`concurrentConsumers`的数量一样大——这是多余的,因为容器使用者通道永远不会被缓存。

##### [](#using-rabbitconnectionfactorybean)使用`RabbitConnectionFactoryBean`

工厂 Bean 现在公开一个属性,以便将客户机连接属性添加到由结果工厂创建的连接中。

##### [](#java-deserialization-2)Java 反序列化

现在,你可以在使用 Java 反序列化时配置一个允许类的“允许列表”。如果你接受来自不受信任的源的带有序列化 Java 对象的消息,那么你应该考虑创建一个允许的列表。有关更多信息,请参见[Java 反序列化](#java-deserialization)

##### [](#json-messageconverter)JSON `MessageConverter`

对 JSON 消息转换器的改进现在允许使用消息头中没有类型信息的消息。有关更多信息,请参见[带注释方法的消息转换](#async-annotation-conversion)[Jackson2JSONMessageConverter](#json-message-converter)

##### [](#logging-appenders)日志附录

###### [](#log4j-2)log4j2

添加了一个 log4j2Appender,现在可以将 Appenders 配置为`addresses`属性,以连接到代理群集。

###### [](#client-connection-properties)客户端连接属性

现在可以将自定义客户端连接属性添加到 RabbitMQ 连接中。

有关更多信息,请参见[日志记录子系统 AMQP 附录](#logging)

#### [](#changes-in-1-5-since-1-4)a.2.8。1.5 自 1.4 以来的变化

##### [](#spring-erlang-is-no-longer-supported)`spring-erlang`不再支持

`spring-erlang`JAR 不再包含在分发版中。用[RabbitMQ REST API](#management-rest-api)代替。

##### [](#cachingconnectionfactory-changes-2)`CachingConnectionFactory`变化

###### [](#empty-addresses-property-in-cachingconnectionfactory)中的空地址属性`CachingConnectionFactory`

以前,如果连接工厂配置了主机和端口,但也为`addresses`提供了一个空字符串,则会忽略主机和端口。现在,一个空的`addresses`字符串被处理为与`null`相同的字符串,并且使用了主机和端口。

###### [](#uri-constructor)URI 构造函数

`CachingConnectionFactory`有一个附加的构造函数,带有`URI`参数,用于配置代理连接。

###### [](#connection-reset)连接重置

添加了一个名为`resetConnection()`的新方法,让用户重置连接(或连接)。例如,你可以使用它在失败转移到次要代理之后重新连接到主代理。这**是吗?**会影响进程内操作。现有的`destroy()`方法做的完全相同,但是新方法的名称不那么令人生畏。

##### [](#properties-to-control-container-queue-declaration-behavior)控制容器队列声明行为的属性

当监听器容器使用者启动时,他们尝试被动地声明队列,以确保它们在代理上可用。在此之前,如果这些声明失败(例如,因为队列不存在),或者当一个 HA 队列被移动时,重试逻辑被固定为以五秒为间隔的三次重试尝试。如果队列仍然不存在,则行为由`missingQueuesFatal`属性控制(默认:`true`)。此外,对于配置为监听多个队列的容器,如果只有一个队列子集可用,那么使用者将在 60 秒的固定时间间隔内重试丢失的队列。

`declarationRetries``failedDeclarationRetryInterval``retryDeclarationInterval`属性现在是可配置的。有关更多信息,请参见[消息侦听器容器配置](#containerAttributes)

##### [](#class-package-change)类包更改

`RabbitGatewaySupport`类从`o.s.amqp.rabbit.core.support`移动到`o.s.amqp.rabbit.core`

##### [](#defaultmessagepropertiesconverter-changes)`DefaultMessagePropertiesConverter`变化

你现在可以配置`DefaultMessagePropertiesConverter`以确定将`LongString`转换为`String`而不是转换为`DataInputStream`的最大长度。转换器有一个替代的构造函数,它将值作为一个限制。以前,这个限制是以`1024`字节进行硬编码的。(也可在 1.4.4 中获得)。

##### [](#rabbitlistener-improvements)`@RabbitListener`改进

###### [](#queuebinding-for-rabbitlistener)`@QueueBinding`表示`@RabbitListener`

`bindings`属性已被添加到`@RabbitListener`注释中,作为与`queues`属性的互斥,以允许`queue`的规范,其`exchange``binding`用于代理上的`RabbitAdmin`声明。

###### [](#spel-in-sendto)spel in`@SendTo`

`@RabbitListener`的默认回复地址(`@SendTo`)现在可以是 SPEL 表达式。

###### [](#multiple-queue-names-through-properties)通过属性获得多个队列名称

现在可以使用 SPEL 和属性占位符的组合来为侦听器指定多个队列。

有关更多信息,请参见[注释驱动的监听器端点](#async-annotation-driven)

##### [](#automatic-exchange-queue-and-binding-declaration)自动交换、队列和绑定声明

现在可以声明定义这些实体集合的 bean,并且`RabbitAdmin`将内容添加到它在建立连接时声明的实体列表中。有关更多信息,请参见[声明交换、队列和绑定的集合](#collection-declaration)

##### [](#rabbittemplate-changes-5)`RabbitTemplate`变更

###### [](#reply-address-added)`reply-address`已添加

已将`reply-address`属性添加到`<rabbit-template>`组件中,作为替代`reply-queue`。有关更多信息,请参见[请求/回复消息](#request-reply)。(也可以在 1.4.4 中作为`RabbitTemplate`上的 setter)。

###### [](#blocking-receive-methods)阻塞`receive`方法

`RabbitTemplate`现在支持`receive``convertAndReceive`方法中的阻塞。有关更多信息,请参见[轮询消费者](#polling-consumer)

###### [](#mandatory-with-sendandreceive-methods)强制使用`sendAndReceive`方法

当使用`mandatory``convertSendAndReceive`方法设置`mandatory`标志时,如果无法传递请求消息,则调用线程将抛出`AmqpMessageReturnedException`。有关更多信息,请参见[回复超时](#reply-timeout)

###### [](#improper-reply-listener-configuration)不正确的应答侦听器配置

当使用命名的应答队列时,框架将尝试验证应答侦听器容器的正确配置。

有关更多信息,请参见[回复监听器容器](#reply-listener)

##### [](#rabbitmanagementtemplate-added)`RabbitManagementTemplate`已添加

引入了`RabbitManagementTemplate`,通过使用其[管理插件 Name](https://www.rabbitmq.com/management.html)提供的 REST API 来监视和配置 RabbitMQ 代理。有关更多信息,请参见[RabbitMQ REST API](#management-rest-api)

##### [](#listener-container-bean-names-xml)侦听器容器 Bean 名称

|   |`<listener-container/>`元素上的`id`属性已被删除。<br/>从这个版本开始,单独使用`<listener/>`子元素上的`id`来命名为每个侦听器元素创建的侦听器容器 Bean。将应用<br/><br/>正常的 Spring  Bean 名称重载。<br/>如果稍后的`<listener/>`与现有的 Bean 相同的`id`进行解析,新的定义覆盖了现有的定义。<br/>以前, Bean 名称是由`id``<listener-container/>`元素的`<listener/>`属性组成的。<br/>当迁移到此版本时,如果你的`id`元素上有`id`属性,删除它们并在子元素`<listener/>`上设置`id`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

但是,为了支持作为一个组开始和停止容器,添加了一个新的`group`属性。定义此属性时,由此元素创建的容器将添加到具有此名称的 Bean,类型为`Collection<SimpleMessageListenerContainer>`。你可以迭代这个组来启动和停止容器。

##### [](#class-level-rabbitlistener)class-level`@RabbitListener`

现在可以在类级别应用`@RabbitListener`注释。与新的`@RabbitHandler`方法注释一起,这允许你基于有效负载类型选择处理程序方法。有关更多信息,请参见[多方法侦听器](#annotation-method-selection)

##### [](#simplemessagelistenercontainer-backoff-support)`SimpleMessageListenerContainer`:Backoff 支持

现在可以为`SimpleMessageListenerContainer`启动恢复提供`BackOff`实例。有关更多信息,请参见[消息侦听器容器配置](#containerAttributes)

##### [](#channel-close-logging-2)通道关闭日志记录

介绍了一种控制信道关闭日志级别的机制。见[记录通道关闭事件](#channel-close-logging)

##### [](#application-events)应用程序事件

现在,当使用者失败时,`SimpleMessageListenerContainer`将发出应用程序事件。有关更多信息,请参见[消费者活动](#consumer-events)

##### [](#consumer-tag-configuration)消费者标签配置

以前,异步消费者的消费者标记是由代理生成的。通过这个版本,现在可以向侦听器容器提供命名策略。见[消费者标签](#consumerTags)

##### [](#using-messagelisteneradapter)使用`MessageListenerAdapter`

`MessageListenerAdapter`现在支持队列名称(或使用者标记)到方法名称的映射,以根据接收消息的队列确定调用哪个委托方法。

##### [](#localizedqueueconnectionfactory-added)`LocalizedQueueConnectionFactory`已添加

`LocalizedQueueConnectionFactory`是一个新的连接工厂,它连接到集群中实际驻留镜像队列的节点。

参见[Queue Affinity 和`LocalizedQueueConnectionFactory`]。

##### [](#anonymous-queue-naming-2)匿名队列命名

从版本 1.5.3 开始,你现在可以控制`AnonymousQueue`名称的生成方式。有关更多信息,请参见[`AnonymousQueue`]。

#### [](#changes-in-1-4-since-1-3)a.2.9。自 1.3 以来 1.4 的变化

##### [](#rabbitlistener-annotation)`@RabbitListener`注释

POJO 侦听器可以使用`@RabbitListener`进行注释,也可以使用`@EnableRabbit``<rabbit:annotation-driven />`。 Spring 此功能需要框架 4.1。有关更多信息,请参见[注释驱动的监听器端点](#async-annotation-driven)

##### [](#rabbitmessagingtemplate-added)`RabbitMessagingTemplate`已添加

一个新的`RabbitMessagingTemplate`允许你通过使用`spring-messaging``Message`实例与 RabbitMQ 进行交互。在内部,它使用`RabbitTemplate`,你可以将其配置为常规配置。 Spring 此功能需要框架 4.1。有关更多信息,请参见[消息传递集成](#template-messaging)

##### [](#listener-container-missingqueuesfatal-attribute)侦听器容器`missingQueuesFatal`属性

1.3.5 在`SimpleMessageListenerContainer`上引入了`missingQueuesFatal`属性。这现在在侦听器容器名称空间元素上可用。见[消息侦听器容器配置](#containerAttributes)

##### [](#rabbittemplate-confirmcallback-interface)RabbitTemplate`ConfirmCallback`接口

这个接口上的`confirm`方法有一个额外的参数,称为`cause`。当可用时,此参数包含否定确认的原因。见[相关发布者确认并返回](#template-confirms)

##### [](#rabbitconnectionfactorybean-added)`RabbitConnectionFactoryBean`已添加

`RabbitConnectionFactoryBean`创建由`CachingConnectionFactory`使用的底层 RabbitMQ`ConnectionFactory`。这允许使用 Spring 的依赖注入配置 SSL 选项。见[配置底层客户机连接工厂](#connection-factory)

##### [](#using-cachingconnectionfactory-2)使用`CachingConnectionFactory`

现在,`CachingConnectionFactory`允许将`connectionTimeout`设置为名称空间中的一个属性或属性。它在底层 RabbitMQ`ConnectionFactory`上设置属性。见[配置底层客户机连接工厂](#connection-factory)

##### [](#log-appender)日志附录

已引入了注销`org.springframework.amqp.rabbit.logback.AmqpAppender`。它提供了类似于`org.springframework.amqp.rabbit.log4j.AmqpAppender`的选项。有关更多信息,请参见这些类的 Javadoc。

log4j`AmqpAppender`现在支持`deliveryMode`属性(`PERSISTENT``NON_PERSISTENT`,缺省:`PERSISTENT`)。以前,所有的 log4j 消息都是`PERSISTENT`

Appender 还支持在发送前修改`Message`——例如,允许添加自定义标头。子类应该覆盖`postProcessMessageBeforeSend()`

##### [](#listener-queues-2)侦听器队列

现在,默认情况下,侦听器容器会在启动过程中重新声明任何丢失的队列。已向`<rabbit:listener-container>`添加了一个新的`auto-declare`属性,以防止这些重新声明。参见[`auto-delete`队列]。

##### [](#rabbittemplate-mandatory-and-connectionfactoryselector-expressions)`RabbitTemplate`:`mandatory`和`connectionFactorySelector`表达式

`mandatoryExpression``sendConnectionFactorySelectorExpression``receiveConnectionFactorySelectorExpression`spel 表达式`s properties have been added to `RabbitTemplate`. The `mandatoryexpression` is used to evaluate a `强制` boolean value against each request message when a `returncall` is in use. See [Correlated Publisher Confirms and Returns](#template-confirms). The `sendConnectionygt=“factorexpression<4214"/>factortortoryr=“actorpression<actorpression4215"/>mentfactortortorypresslausion<yprespress 见[路由连接工厂](#routing-connection-factory)

##### [](#listeners-and-the-routing-connection-factory)侦听器和路由连接工厂

你可以配置带有路由连接工厂的`SimpleMessageListenerContainer`,以启用基于队列名称的连接选择。见[路由连接工厂](#routing-connection-factory)

##### [](#rabbittemplate-recoverycallback-option)`RabbitTemplate`:`RecoveryCallback`选项

已经添加了`recoveryCallback`属性,以便在`retryTemplate.execute()`中使用。见[添加重试功能](#template-retry)

##### [](#messageconversionexception-change)`MessageConversionException`变化

这个异常现在是`AmqpException`的子类。考虑以下代码:

```
try {
    template.convertAndSend("thing1", "thing2", "cat");
}
catch (AmqpException e) {
	...
}
catch (MessageConversionException e) {
	...
}
```

第二个捕获块不再可达,需要移动到捕获全部`AmqpException`的捕获块之上。

##### [](#rabbitmq-3-4-compatibility)RabbitMQ3.4 兼容性

Spring AMQP 现在与 RabbitMQ3.4 兼容,包括直接回复。有关更多信息,请参见[相容性](#compatibility)[RabbitMQ 直接回复](#direct-reply-to)

##### [](#contenttypedelegatingmessageconverter-added)`ContentTypeDelegatingMessageConverter`已添加

基于`contentType`中的`MessageProperties`属性,引入了`ContentTypeDelegatingMessageConverter`来选择要使用的`MessageConverter`。有关更多信息,请参见[消息转换器](#message-converters)

#### [](#changes-in-1-3-since-1-2)a.2.10。1.3 自 1.2 以来的变化

##### [](#listener-concurrency-2)侦听器并发

侦听器容器现在支持基于工作负载的消费者数量的动态伸缩,或者你可以在不停止容器的情况下以编程方式更改并发性。见[监听器并发](#listener-concurrency)

##### [](#listener-queues-3)侦听器队列

侦听器容器现在允许在运行时修改它侦听的队列。此外,如果至少有一个配置好的队列可供使用,容器现在就会启动。见[监听器容器队列](#listener-queues)

这个侦听器容器现在在启动期间重新声明任何自动删除队列。参见[`auto-delete`队列]。

##### [](#consumer-priority-2)消费者优先权

侦听器容器现在支持消费者参数,允许设置`x-priority`参数。见[消费者优先权](#consumer-priority)

##### [](#exclusive-consumer-2)独家消费者

你现在可以用一个`SimpleMessageListenerContainer`消费者配置`exclusive`,从而防止其他消费者监听队列。见[独家消费者](#exclusive-consumer)

##### [](#rabbit-admin)兔子管理员

现在可以让代理生成队列名,而不考虑`durable``autoDelete``exclusive`设置。见[配置代理](#broker-configuration)

##### [](#direct-exchange-binding)直接交换绑定

以前,从`direct-exchange`配置的`binding`元素中省略`key`属性会导致队列或交换被绑定为一个空字符串作为路由键。现在它与所提供的名称`Queue``Exchange`绑定。如果希望使用空字符串路由键绑定,则需要指定`key=""`

##### [](#amqptemplate-changes)`AmqpTemplate`变化

`AmqpTemplate`现在提供了几个同步的`receiveAndReply`方法。这些都是由`RabbitTemplate`实现的。有关更多信息,请参见[接收消息](#receiving-messages)

`RabbitTemplate`现在支持配置`RetryTemplate`,以便在代理不可用时尝试重试(使用可选的后退策略)。有关更多信息,请参见[添加重试功能](#template-retry)

##### [](#caching-connection-factory)缓存连接工厂

现在,你可以将缓存连接工厂配置为缓存`Connection`实例及其`Channel`实例,而不是使用单个连接并只缓存`Channel`实例。见[连接和资源管理](#connections)

##### [](#binding-arguments)约束参数

`<exchange>``<binding>`现在支持解析`<binding-arguments>`子元素。现在,你可以使用`key/value`属性对配置`<binding>`中的`<binding>`(以便在单个标头上进行匹配)或使用`<binding-arguments>`子元素(允许在多个标头上进行匹配)。这些选择是相互排斥的。见[头交换](#headers-exchange)

##### [](#routing-connection-factory-2)路由连接工厂

引入了一个新的`SimpleRoutingConnectionFactory`。它允许配置`ConnectionFactories`映射,以确定在运行时使用的目标`ConnectionFactory`。见[路由连接工厂](#routing-connection-factory)

##### [](#messagebuilder-and-messagepropertiesbuilder)`MessageBuilder`和`MessagePropertiesBuilder`

现在提供了用于构建消息或消息属性的“Fluent API”。见[Message Builder API](#message-builder)

##### [](#retryinterceptorbuilder-change)`RetryInterceptorBuilder`变化

现在提供了用于构建侦听器容器重试拦截器的“Fluent API”。见[同步操作中的故障和重试选项](#retry)

##### [](#republishmessagerecoverer-added)`RepublishMessageRecoverer`已添加

提供了这个新的`MessageRecoverer`,以允许在重试用完时将失败的消息发布到另一个队列(包括消息头中的堆栈跟踪信息)。见[消息侦听器和异步情况](#async-listeners)

##### [](#default-error-handler-since-1-3-2)默认错误处理程序(自 1.3.2 起)

已将默认的`ConditionalRejectingErrorHandler`添加到侦听器容器中。此错误处理程序检测到致命的消息转换问题,并指示容器拒绝该消息,以防止代理继续重新交付不可转换的消息。见[异常处理](#exception-handling)

##### [](#listener-container-missingqueuesfatal-property-since-1-3-5)侦听器容器“missingqueuesfatal”属性(自 1.3.5)

`SimpleMessageListenerContainer`现在有一个名为`missingQueuesFatal`的属性(默认值:`true`)。以前,排不上队总是致命的。见[消息侦听器容器配置](#containerAttributes)

#### [](#changes-to-1-2-since-1-1)a.2.11。自 1.1 以来对 1.2 的更改

##### [](#rabbitmq-version)RabbitMQ 版本

Spring AMQP 现在默认使用 RabbitMQ3.1.x(但保留了与早期版本的兼容性)。对于 RabbitMQ3.1.x——联邦交换和`RabbitTemplate`上的`immediate`属性不再支持的特性,已经添加了某些异议。

##### [](#rabbit-admin-2)兔子管理员

`RabbitAdmin`现在提供了一个选项,可以在声明失败时让 Exchange、Queue 和 binding 声明继续。以前,所有的声明都是在失败时停止的。通过设置`ignore-declaration-exceptions`,将记录此类异常(在`WARN`级别),但会继续进行进一步的声明。这可能有用的一个例子是,当队列声明失败时,原因是一个稍微不同的`ttl`设置,该设置通常会阻止其他声明继续进行。

`RabbitAdmin`现在提供了一个名为`getQueueProperties()`的附加方法。你可以使用它来确定代理上是否存在队列(对于不存在的队列,返回`null`)。此外,它返回队列中当前消息的数量以及当前消费者的数量。

##### [](#rabbit-template)兔子模板

以前,当`…​sendAndReceive()`方法与固定的应答队列一起使用时,将使用两个自定义标头来进行相关数据以及保留和恢复应答队列信息。在此版本中,默认情况下使用标准消息属性(`correlationId`),尽管你可以指定要使用的自定义属性。此外,嵌套的`replyTo`信息现在被保留在模板内部,而不是使用自定义标头。

`immediate`属性已弃用。在使用 RabbitMQ3.0.x 或更高版本时,不能设置此属性。

##### [](#json-message-converters)JSON 消息转换器

现在提供了一个 Jackson2.x`MessageConverter`,以及使用 Jackson1.x 的现有转换器。

##### [](#automatic-declaration-of-queues-and-other-items)队列和其他项的自动声明

以前,在声明队列、交换和绑定时,你无法定义声明使用哪个连接工厂。每个`RabbitAdmin`通过使用其连接声明所有组件。

从这个版本开始,你现在可以将声明限制为特定的`RabbitAdmin`实例。见[有条件声明](#conditional-declaration)

##### [](#amqp-remoting)AMQP Remoting

现在提供了用于使用 Spring 远程技术的设施,使用 AMQP 作为 RPC 调用的传输。有关更多信息,请参见[Spring Remoting with AMQP](#remoting)

##### [](#requested-heart-beats)请求心跳

一些用户要求在 Spring AMQP`CachingConnectionFactory`上公开底层客户机连接工厂的`requestedHeartBeats`属性。这是现在可用的。在此之前,有必要将 AMQP 客户端工厂配置为单独的 Bean,并在`CachingConnectionFactory`中提供对它的引用。

#### [](#changes-to-1-1-since-1-0)a.2.12。自 1.0 以来对 1.1 的更改

##### [](#general)一般

Spring-AMQP 现在是用 Gradle 构建的。

添加对 Publisher 确认和返回的支持。

添加对 HA 队列和代理故障转移的支持。

增加了对死信交换和死信队列的支持。

##### [](#amqp-log4j-appender)AMQP log4j Appender

添加一个选项,以支持将消息 ID 添加到已记录的消息中。

添加一个选项,允许在将`String`转换为`byte[]`时使用`Charset`名称的规范。

版本 2.4.2 最后更新时间:2022 年 01 月 16 日 15:18:50UTC

hljs.inithighlighting()if{{{i[’GoogleAnalyticsObject’]=r;i[r]=i[r]||function(){(i[r].q=i[r].q|[]).push(参数)},i[r].l=1\*new date();a=s.createelement(o),m.getelementsname(o)[0];a.asymentsname=1;a.SRC=g.node=getname;google(a.com=node=gleanalytnode,’,’’docume’,’’’document’,’