core.md 848.6 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
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 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 6981 6982 6983 6984 6985 6986 6987 6988 6989 6990 6991 6992 6993 6994 6995 6996 6997 6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032 7033 7034 7035 7036 7037 7038 7039 7040 7041 7042 7043 7044 7045 7046 7047 7048 7049 7050 7051 7052 7053 7054 7055 7056 7057 7058 7059 7060 7061 7062 7063 7064 7065 7066 7067 7068 7069 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 7081 7082 7083 7084 7085 7086 7087 7088 7089 7090 7091 7092 7093 7094 7095 7096 7097 7098 7099 7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 7116 7117 7118 7119 7120 7121 7122 7123 7124 7125 7126 7127 7128 7129 7130 7131 7132 7133 7134 7135 7136 7137 7138 7139 7140 7141 7142 7143 7144 7145 7146 7147 7148 7149 7150 7151 7152 7153 7154 7155 7156 7157 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 7183 7184 7185 7186 7187 7188 7189 7190 7191 7192 7193 7194 7195 7196 7197 7198 7199 7200 7201 7202 7203 7204 7205 7206 7207 7208 7209 7210 7211 7212 7213 7214 7215 7216 7217 7218 7219 7220 7221 7222 7223 7224 7225 7226 7227 7228 7229 7230 7231 7232 7233 7234 7235 7236 7237 7238 7239 7240 7241 7242 7243 7244 7245 7246 7247 7248 7249 7250 7251 7252 7253 7254 7255 7256 7257 7258 7259 7260 7261 7262 7263 7264 7265 7266 7267 7268 7269 7270 7271 7272 7273 7274 7275 7276 7277 7278 7279 7280 7281 7282 7283 7284 7285 7286 7287 7288 7289 7290 7291 7292 7293 7294 7295 7296 7297 7298 7299 7300 7301 7302 7303 7304 7305 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340 7341 7342 7343 7344 7345 7346 7347 7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478 7479 7480 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 7492 7493 7494 7495 7496 7497 7498 7499 7500 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520 7521 7522 7523 7524 7525 7526 7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 7538 7539 7540 7541 7542 7543 7544 7545 7546 7547 7548 7549 7550 7551 7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 7572 7573 7574 7575 7576 7577 7578 7579 7580 7581 7582 7583 7584 7585 7586 7587 7588 7589 7590 7591 7592 7593 7594 7595 7596 7597 7598 7599 7600 7601 7602 7603 7604 7605 7606 7607 7608 7609 7610 7611 7612 7613 7614 7615 7616 7617 7618 7619 7620 7621 7622 7623 7624 7625 7626 7627 7628 7629 7630 7631 7632 7633 7634 7635 7636 7637 7638 7639 7640 7641 7642 7643 7644 7645 7646 7647 7648 7649 7650 7651 7652 7653 7654 7655 7656 7657 7658 7659 7660 7661 7662 7663 7664 7665 7666 7667 7668 7669 7670 7671 7672 7673 7674 7675 7676 7677 7678 7679 7680 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 7694 7695 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 7722 7723 7724 7725 7726 7727 7728 7729 7730 7731 7732 7733 7734 7735 7736 7737 7738 7739 7740 7741 7742 7743 7744 7745 7746 7747 7748 7749 7750 7751 7752 7753 7754 7755 7756 7757 7758 7759 7760 7761 7762 7763 7764 7765 7766 7767 7768 7769 7770 7771 7772 7773 7774 7775 7776 7777 7778 7779 7780 7781 7782 7783 7784 7785 7786 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 7815 7816 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 7829 7830 7831 7832 7833 7834 7835 7836 7837 7838 7839 7840 7841 7842 7843 7844 7845 7846 7847 7848 7849 7850 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 7864 7865 7866 7867 7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881 7882 7883 7884 7885 7886 7887 7888 7889 7890 7891 7892 7893 7894 7895 7896 7897 7898 7899 7900 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 7911 7912 7913 7914 7915 7916 7917 7918 7919 7920 7921 7922 7923 7924 7925 7926 7927 7928 7929 7930 7931 7932 7933 7934 7935 7936 7937 7938 7939 7940 7941 7942 7943 7944 7945 7946 7947 7948 7949 7950 7951 7952 7953 7954 7955 7956 7957 7958 7959 7960 7961 7962 7963 7964 7965 7966 7967 7968 7969 7970 7971 7972 7973 7974 7975 7976 7977 7978 7979 7980 7981 7982 7983 7984 7985 7986 7987 7988 7989 7990 7991 7992 7993 7994 7995 7996 7997 7998 7999 8000 8001 8002 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 8018 8019 8020 8021 8022 8023 8024 8025 8026 8027 8028 8029 8030 8031 8032 8033 8034 8035 8036 8037 8038 8039 8040 8041 8042 8043 8044 8045 8046 8047 8048 8049 8050 8051 8052 8053 8054 8055 8056 8057 8058 8059 8060 8061 8062 8063 8064 8065 8066 8067 8068 8069 8070 8071 8072 8073 8074 8075 8076 8077 8078 8079 8080 8081 8082 8083 8084 8085 8086 8087 8088 8089 8090 8091 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110 8111 8112 8113 8114 8115 8116 8117 8118 8119 8120 8121 8122 8123 8124 8125 8126 8127 8128 8129 8130 8131 8132 8133 8134 8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 8145 8146 8147 8148 8149 8150 8151 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 8166 8167 8168 8169 8170 8171 8172 8173 8174 8175 8176 8177 8178 8179 8180 8181 8182 8183 8184 8185 8186 8187 8188 8189 8190 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 8216 8217 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 8233 8234 8235 8236 8237 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256 8257 8258 8259 8260 8261 8262 8263 8264 8265 8266 8267 8268 8269 8270 8271 8272 8273 8274 8275 8276 8277 8278 8279 8280 8281 8282 8283 8284 8285 8286 8287 8288 8289 8290 8291 8292 8293 8294 8295 8296 8297 8298 8299 8300 8301 8302 8303 8304 8305 8306 8307 8308 8309 8310 8311 8312 8313 8314 8315 8316 8317 8318 8319 8320 8321 8322 8323 8324 8325 8326 8327 8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 8338 8339 8340 8341 8342 8343 8344 8345 8346 8347 8348 8349 8350 8351 8352 8353 8354 8355 8356 8357 8358 8359 8360 8361 8362 8363 8364 8365 8366 8367 8368 8369 8370 8371 8372 8373 8374 8375 8376 8377 8378 8379 8380 8381 8382 8383 8384 8385 8386 8387 8388 8389 8390 8391 8392 8393 8394 8395 8396 8397 8398 8399 8400 8401 8402 8403 8404 8405 8406 8407 8408 8409 8410 8411 8412 8413 8414 8415 8416 8417 8418 8419 8420 8421 8422 8423 8424 8425 8426 8427 8428 8429 8430 8431 8432 8433 8434 8435 8436 8437 8438 8439 8440 8441 8442 8443 8444 8445 8446 8447 8448 8449 8450 8451 8452 8453 8454 8455 8456 8457 8458 8459 8460 8461 8462 8463 8464 8465 8466 8467 8468 8469 8470 8471 8472 8473 8474 8475 8476 8477 8478 8479 8480 8481 8482 8483 8484 8485 8486 8487 8488 8489 8490 8491 8492 8493 8494 8495 8496 8497 8498 8499 8500 8501 8502 8503 8504 8505 8506 8507 8508 8509 8510 8511 8512 8513 8514 8515 8516 8517 8518 8519 8520 8521 8522 8523 8524 8525 8526 8527 8528 8529 8530 8531 8532 8533 8534 8535 8536 8537 8538 8539 8540 8541 8542 8543 8544 8545 8546 8547 8548 8549 8550 8551 8552 8553 8554 8555 8556 8557 8558 8559 8560 8561 8562 8563 8564 8565 8566 8567 8568 8569 8570 8571 8572 8573 8574 8575 8576 8577 8578 8579 8580 8581 8582 8583 8584 8585 8586 8587 8588 8589 8590 8591 8592 8593 8594 8595 8596 8597 8598 8599 8600 8601 8602 8603 8604 8605 8606 8607 8608 8609 8610 8611 8612 8613 8614 8615 8616 8617 8618 8619 8620 8621 8622 8623 8624 8625 8626 8627 8628 8629 8630 8631 8632 8633 8634 8635 8636 8637 8638 8639 8640 8641 8642 8643 8644 8645 8646 8647 8648 8649 8650 8651 8652 8653 8654 8655 8656 8657 8658 8659 8660 8661 8662 8663 8664 8665 8666 8667 8668 8669 8670 8671 8672 8673 8674 8675 8676 8677 8678 8679 8680 8681 8682 8683 8684 8685 8686 8687 8688 8689 8690 8691 8692 8693 8694 8695 8696 8697 8698 8699 8700 8701 8702 8703 8704 8705 8706 8707 8708 8709 8710 8711 8712 8713 8714 8715 8716 8717 8718 8719 8720 8721 8722 8723 8724 8725 8726 8727 8728 8729 8730 8731 8732 8733 8734 8735 8736 8737 8738 8739 8740 8741 8742 8743 8744 8745 8746 8747 8748 8749 8750 8751 8752 8753 8754 8755 8756 8757 8758 8759 8760 8761 8762 8763 8764 8765 8766 8767 8768 8769 8770 8771 8772 8773 8774 8775 8776 8777 8778 8779 8780 8781 8782 8783 8784 8785 8786 8787 8788 8789 8790 8791 8792 8793 8794 8795 8796 8797 8798 8799 8800 8801 8802 8803 8804 8805 8806 8807 8808 8809 8810 8811 8812 8813 8814 8815 8816 8817 8818 8819 8820 8821 8822 8823 8824 8825 8826 8827 8828 8829 8830 8831 8832 8833 8834 8835 8836 8837 8838 8839 8840 8841 8842 8843 8844 8845 8846 8847 8848 8849 8850 8851 8852 8853 8854 8855 8856 8857 8858 8859 8860 8861 8862 8863 8864 8865 8866 8867 8868 8869 8870 8871 8872 8873 8874 8875 8876 8877 8878 8879 8880 8881 8882 8883 8884 8885 8886 8887 8888 8889 8890 8891 8892 8893 8894 8895 8896 8897 8898 8899 8900 8901 8902 8903 8904 8905 8906 8907 8908 8909 8910 8911 8912 8913 8914 8915 8916 8917 8918 8919 8920 8921 8922 8923 8924 8925 8926 8927 8928 8929 8930 8931 8932 8933 8934 8935 8936 8937 8938 8939 8940 8941 8942 8943 8944 8945 8946 8947 8948 8949 8950 8951 8952 8953 8954 8955 8956 8957 8958 8959 8960 8961 8962 8963 8964 8965 8966 8967 8968 8969 8970 8971 8972 8973 8974 8975 8976 8977 8978 8979 8980 8981 8982 8983 8984 8985 8986 8987 8988 8989 8990 8991 8992 8993 8994 8995 8996 8997 8998 8999 9000 9001 9002 9003 9004 9005 9006 9007 9008 9009 9010 9011 9012 9013 9014 9015 9016 9017 9018 9019 9020 9021 9022 9023 9024 9025 9026 9027 9028 9029 9030 9031 9032 9033 9034 9035 9036 9037 9038 9039 9040 9041 9042 9043 9044 9045 9046 9047 9048 9049 9050 9051 9052 9053 9054 9055 9056 9057 9058 9059 9060 9061 9062 9063 9064 9065 9066 9067 9068 9069 9070 9071 9072 9073 9074 9075 9076 9077 9078 9079 9080 9081 9082 9083 9084 9085 9086 9087 9088 9089 9090 9091 9092 9093 9094 9095 9096 9097 9098 9099 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 9110 9111 9112 9113 9114 9115 9116 9117 9118 9119 9120 9121 9122 9123 9124 9125 9126 9127 9128 9129 9130 9131 9132 9133 9134 9135 9136 9137 9138 9139 9140 9141 9142 9143 9144 9145 9146 9147 9148 9149 9150 9151 9152 9153 9154 9155 9156 9157 9158 9159 9160 9161 9162 9163 9164 9165 9166 9167 9168 9169 9170 9171 9172 9173 9174 9175 9176 9177 9178 9179 9180 9181 9182 9183 9184 9185 9186 9187 9188 9189 9190 9191 9192 9193 9194 9195 9196 9197 9198 9199 9200 9201 9202 9203 9204 9205 9206 9207 9208 9209 9210 9211 9212 9213 9214 9215 9216 9217 9218 9219 9220 9221 9222 9223 9224 9225 9226 9227 9228 9229 9230 9231 9232 9233 9234 9235 9236 9237 9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 9249 9250 9251 9252 9253 9254 9255 9256 9257 9258 9259 9260 9261 9262 9263 9264 9265 9266 9267 9268 9269 9270 9271 9272 9273 9274 9275 9276 9277 9278 9279 9280 9281 9282 9283 9284 9285 9286 9287 9288 9289 9290 9291 9292 9293 9294 9295 9296 9297 9298 9299 9300 9301 9302 9303 9304 9305 9306 9307 9308 9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320 9321 9322 9323 9324 9325 9326 9327 9328 9329 9330 9331 9332 9333 9334 9335 9336 9337 9338 9339 9340 9341 9342 9343 9344 9345 9346 9347 9348 9349 9350 9351 9352 9353 9354 9355 9356 9357 9358 9359 9360 9361 9362 9363 9364 9365 9366 9367 9368 9369 9370 9371 9372 9373 9374 9375 9376 9377 9378 9379 9380 9381 9382 9383 9384 9385 9386 9387 9388 9389 9390 9391 9392 9393 9394 9395 9396 9397 9398 9399 9400 9401 9402 9403 9404 9405 9406 9407 9408 9409 9410 9411 9412 9413 9414 9415 9416 9417 9418 9419 9420 9421 9422 9423 9424 9425 9426 9427 9428 9429 9430 9431 9432 9433 9434 9435 9436 9437 9438 9439 9440 9441 9442 9443 9444 9445 9446 9447 9448 9449 9450 9451 9452 9453 9454 9455 9456 9457 9458 9459 9460 9461 9462 9463 9464 9465 9466 9467 9468 9469 9470 9471 9472 9473 9474 9475 9476 9477 9478 9479 9480 9481 9482 9483 9484 9485 9486 9487 9488 9489 9490 9491 9492 9493 9494 9495 9496 9497 9498 9499 9500 9501 9502 9503 9504 9505 9506 9507 9508 9509 9510 9511 9512 9513 9514 9515 9516 9517 9518 9519 9520 9521 9522 9523 9524 9525 9526 9527 9528 9529 9530 9531 9532 9533 9534 9535 9536 9537 9538 9539 9540 9541 9542 9543 9544 9545 9546 9547 9548 9549 9550 9551 9552 9553 9554 9555 9556 9557 9558 9559 9560 9561 9562 9563 9564 9565 9566 9567 9568 9569 9570 9571 9572 9573 9574 9575 9576 9577 9578 9579 9580 9581 9582 9583 9584 9585 9586 9587 9588 9589 9590 9591 9592 9593 9594 9595 9596 9597 9598 9599 9600 9601 9602 9603 9604 9605 9606 9607 9608 9609 9610 9611 9612 9613 9614 9615 9616 9617 9618 9619 9620 9621 9622 9623 9624 9625 9626 9627 9628 9629 9630 9631 9632 9633 9634 9635 9636 9637 9638 9639 9640 9641 9642 9643 9644 9645 9646 9647 9648 9649 9650 9651 9652 9653 9654 9655 9656 9657 9658 9659 9660 9661 9662 9663 9664 9665 9666 9667 9668 9669 9670 9671 9672 9673 9674 9675 9676 9677 9678 9679 9680 9681 9682 9683 9684 9685 9686 9687 9688 9689 9690 9691 9692 9693 9694 9695 9696 9697 9698 9699 9700 9701 9702 9703 9704 9705 9706 9707 9708 9709 9710 9711 9712 9713 9714 9715 9716 9717 9718 9719 9720 9721 9722 9723 9724 9725 9726 9727 9728 9729 9730 9731 9732 9733 9734 9735 9736 9737 9738 9739 9740 9741 9742 9743 9744 9745 9746 9747 9748 9749 9750 9751 9752 9753 9754 9755 9756 9757 9758 9759 9760 9761 9762 9763 9764 9765 9766 9767 9768 9769 9770 9771 9772 9773 9774 9775 9776 9777 9778 9779 9780 9781 9782 9783 9784 9785 9786 9787 9788 9789 9790 9791 9792 9793 9794 9795 9796 9797 9798 9799 9800 9801 9802 9803 9804 9805 9806 9807 9808 9809 9810 9811 9812 9813 9814 9815 9816 9817 9818 9819 9820 9821 9822 9823 9824 9825 9826 9827 9828 9829 9830 9831 9832 9833 9834 9835 9836 9837 9838 9839 9840 9841 9842 9843 9844 9845 9846 9847 9848 9849 9850 9851 9852 9853 9854 9855 9856 9857 9858 9859 9860 9861 9862 9863 9864 9865 9866 9867 9868 9869 9870 9871 9872 9873 9874 9875 9876 9877 9878 9879 9880 9881 9882 9883 9884 9885 9886 9887 9888 9889 9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 9900 9901 9902 9903 9904 9905 9906 9907 9908 9909 9910 9911 9912 9913 9914 9915 9916 9917 9918 9919 9920 9921 9922 9923 9924 9925 9926 9927 9928 9929 9930 9931 9932 9933 9934 9935 9936 9937 9938 9939 9940 9941 9942 9943 9944 9945 9946 9947 9948 9949 9950 9951 9952 9953 9954 9955 9956 9957 9958 9959 9960 9961 9962 9963 9964 9965 9966 9967 9968 9969 9970 9971 9972 9973 9974 9975 9976 9977 9978 9979 9980 9981 9982 9983 9984 9985 9986 9987 9988 9989 9990 9991 9992 9993 9994 9995 9996 9997 9998 9999 10000 10001 10002 10003 10004 10005 10006 10007 10008 10009 10010 10011 10012 10013 10014 10015 10016 10017 10018 10019 10020 10021 10022 10023 10024 10025 10026 10027 10028 10029 10030 10031 10032 10033 10034 10035 10036 10037 10038 10039 10040 10041 10042 10043 10044 10045 10046 10047 10048 10049 10050 10051 10052 10053 10054 10055 10056 10057 10058 10059 10060 10061 10062 10063 10064 10065 10066 10067 10068 10069 10070 10071 10072 10073 10074 10075 10076 10077 10078 10079 10080 10081 10082 10083 10084 10085 10086 10087 10088 10089 10090 10091 10092 10093 10094 10095 10096 10097 10098 10099 10100 10101 10102 10103 10104 10105 10106 10107 10108 10109 10110 10111 10112 10113 10114 10115 10116 10117 10118 10119 10120 10121 10122 10123 10124 10125 10126 10127 10128 10129 10130 10131 10132 10133 10134 10135 10136 10137 10138 10139 10140 10141 10142 10143 10144 10145 10146 10147 10148 10149 10150 10151 10152 10153 10154 10155 10156 10157 10158 10159 10160 10161 10162 10163 10164 10165 10166 10167 10168 10169 10170 10171 10172 10173 10174 10175 10176 10177 10178 10179 10180 10181 10182 10183 10184 10185 10186 10187 10188 10189 10190 10191 10192 10193 10194 10195 10196 10197 10198 10199 10200 10201 10202 10203 10204 10205 10206 10207 10208 10209 10210 10211 10212 10213 10214 10215 10216 10217 10218 10219 10220 10221 10222 10223 10224 10225 10226 10227 10228 10229 10230 10231 10232 10233 10234 10235 10236 10237 10238 10239 10240 10241 10242 10243 10244 10245 10246 10247 10248 10249 10250 10251 10252 10253 10254 10255 10256 10257 10258 10259 10260 10261 10262 10263 10264 10265 10266 10267 10268 10269 10270 10271 10272 10273 10274 10275 10276 10277 10278 10279 10280 10281 10282 10283 10284 10285 10286 10287 10288 10289 10290 10291 10292 10293 10294 10295 10296 10297 10298 10299 10300 10301 10302 10303 10304 10305 10306 10307 10308 10309 10310 10311 10312 10313 10314 10315 10316 10317 10318 10319 10320 10321 10322 10323 10324 10325 10326 10327 10328 10329 10330 10331 10332 10333 10334 10335 10336 10337 10338 10339 10340 10341 10342 10343 10344 10345 10346 10347 10348 10349 10350 10351 10352 10353 10354 10355 10356 10357 10358 10359 10360 10361 10362 10363 10364 10365 10366 10367 10368 10369 10370 10371 10372 10373 10374 10375 10376 10377 10378 10379 10380 10381 10382 10383 10384 10385 10386 10387 10388 10389 10390 10391 10392 10393 10394 10395 10396 10397 10398 10399 10400 10401 10402 10403 10404 10405 10406 10407 10408 10409 10410 10411 10412 10413 10414 10415 10416 10417 10418 10419 10420 10421 10422 10423 10424 10425 10426 10427 10428 10429 10430 10431 10432 10433 10434 10435 10436 10437 10438 10439 10440 10441 10442 10443 10444 10445 10446 10447 10448 10449 10450 10451 10452 10453 10454 10455 10456 10457 10458 10459 10460 10461 10462 10463 10464 10465 10466 10467 10468 10469 10470 10471 10472 10473 10474 10475 10476 10477 10478 10479 10480 10481 10482 10483 10484 10485 10486 10487 10488 10489 10490 10491 10492 10493 10494 10495 10496 10497 10498 10499 10500 10501 10502 10503 10504 10505 10506 10507 10508 10509 10510 10511 10512 10513 10514 10515 10516 10517 10518 10519 10520 10521 10522 10523 10524 10525 10526 10527 10528 10529 10530 10531 10532 10533 10534 10535 10536 10537 10538 10539 10540 10541 10542 10543 10544 10545 10546 10547 10548 10549 10550 10551 10552 10553 10554 10555 10556 10557 10558 10559 10560 10561 10562 10563 10564 10565 10566 10567 10568 10569 10570 10571 10572 10573 10574 10575 10576 10577 10578 10579 10580 10581 10582 10583 10584 10585 10586 10587 10588 10589 10590 10591 10592 10593 10594 10595 10596 10597 10598 10599 10600 10601 10602 10603 10604 10605 10606 10607 10608 10609 10610 10611 10612 10613 10614 10615 10616 10617 10618 10619 10620 10621 10622 10623 10624 10625 10626 10627 10628 10629 10630 10631 10632 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 10644 10645 10646 10647 10648 10649 10650 10651 10652 10653 10654 10655 10656 10657 10658 10659 10660 10661 10662 10663 10664 10665 10666 10667 10668 10669 10670 10671 10672 10673 10674 10675 10676 10677 10678 10679 10680 10681 10682 10683 10684 10685 10686 10687 10688 10689 10690 10691 10692 10693 10694 10695 10696 10697 10698 10699 10700 10701 10702 10703 10704 10705 10706 10707 10708 10709 10710 10711 10712 10713 10714 10715 10716 10717 10718 10719 10720 10721 10722 10723 10724 10725 10726 10727 10728 10729 10730 10731 10732 10733 10734 10735 10736 10737 10738 10739 10740 10741 10742 10743 10744 10745 10746 10747 10748 10749 10750 10751 10752 10753 10754 10755 10756 10757 10758 10759 10760 10761 10762 10763 10764 10765 10766 10767 10768 10769 10770 10771 10772 10773 10774 10775 10776 10777 10778 10779 10780 10781 10782 10783 10784 10785 10786 10787 10788 10789 10790 10791 10792 10793 10794 10795 10796 10797 10798 10799 10800 10801 10802 10803 10804 10805 10806 10807 10808 10809 10810 10811 10812 10813 10814 10815 10816 10817 10818 10819 10820 10821 10822 10823 10824 10825 10826 10827 10828 10829 10830 10831 10832 10833 10834 10835 10836 10837 10838 10839 10840 10841 10842 10843 10844 10845 10846 10847 10848 10849 10850 10851 10852 10853 10854 10855 10856 10857 10858 10859 10860 10861 10862 10863 10864 10865 10866 10867 10868 10869 10870 10871 10872 10873 10874 10875 10876 10877 10878 10879 10880 10881 10882 10883 10884 10885 10886 10887 10888 10889 10890 10891 10892 10893 10894 10895 10896 10897 10898 10899 10900 10901 10902 10903 10904 10905 10906 10907 10908 10909 10910 10911 10912 10913 10914 10915 10916 10917 10918 10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 10930 10931 10932 10933 10934 10935 10936 10937 10938 10939 10940 10941 10942 10943 10944 10945 10946 10947 10948 10949 10950 10951 10952 10953 10954 10955 10956 10957 10958 10959 10960 10961 10962 10963 10964 10965 10966 10967 10968 10969 10970 10971 10972 10973 10974 10975 10976 10977 10978 10979 10980 10981 10982 10983 10984 10985 10986 10987 10988 10989 10990 10991 10992 10993 10994 10995 10996 10997 10998 10999 11000 11001 11002 11003 11004 11005 11006 11007 11008 11009 11010 11011 11012 11013 11014 11015 11016 11017 11018 11019 11020 11021 11022 11023 11024 11025 11026 11027 11028 11029 11030 11031 11032 11033 11034 11035 11036 11037 11038 11039 11040 11041 11042 11043 11044 11045 11046 11047 11048 11049 11050 11051 11052 11053 11054 11055 11056 11057 11058 11059 11060 11061 11062 11063 11064 11065 11066 11067 11068 11069 11070 11071 11072 11073 11074 11075 11076 11077 11078 11079 11080 11081 11082 11083 11084 11085 11086 11087 11088 11089 11090 11091 11092 11093 11094 11095 11096 11097 11098 11099 11100 11101 11102 11103 11104 11105 11106 11107 11108 11109 11110 11111 11112 11113 11114 11115 11116 11117 11118 11119 11120 11121 11122 11123 11124 11125 11126 11127 11128 11129 11130 11131 11132 11133 11134 11135 11136 11137 11138 11139 11140 11141 11142 11143 11144 11145 11146 11147 11148 11149 11150 11151 11152 11153 11154 11155 11156 11157 11158 11159 11160 11161 11162 11163 11164 11165 11166 11167 11168 11169 11170 11171 11172 11173 11174 11175 11176 11177 11178 11179 11180 11181 11182 11183 11184 11185 11186 11187 11188 11189 11190 11191 11192 11193 11194 11195 11196 11197 11198 11199 11200 11201 11202 11203 11204 11205 11206 11207 11208 11209 11210 11211 11212 11213 11214 11215 11216 11217 11218 11219 11220 11221 11222 11223 11224 11225 11226 11227 11228 11229 11230 11231 11232 11233 11234 11235 11236 11237 11238 11239 11240 11241 11242 11243 11244 11245 11246 11247 11248 11249 11250 11251 11252 11253 11254 11255 11256 11257 11258 11259 11260 11261 11262 11263 11264 11265 11266 11267 11268 11269 11270 11271 11272 11273 11274 11275 11276 11277 11278 11279 11280 11281 11282 11283 11284 11285 11286 11287 11288 11289 11290 11291 11292 11293 11294 11295 11296 11297 11298 11299 11300 11301 11302 11303 11304 11305 11306 11307 11308 11309 11310 11311 11312 11313 11314 11315 11316 11317 11318 11319 11320 11321 11322 11323 11324 11325 11326 11327 11328 11329 11330 11331 11332 11333 11334 11335 11336 11337 11338 11339 11340 11341 11342 11343 11344 11345 11346 11347 11348 11349 11350 11351 11352 11353 11354 11355 11356 11357 11358 11359 11360 11361 11362 11363 11364 11365 11366 11367 11368 11369 11370 11371 11372 11373 11374 11375 11376 11377 11378 11379 11380 11381 11382 11383 11384 11385 11386 11387 11388 11389 11390 11391 11392 11393 11394 11395 11396 11397 11398 11399 11400 11401 11402 11403 11404 11405 11406 11407 11408 11409 11410 11411 11412 11413 11414 11415 11416 11417 11418 11419 11420 11421 11422 11423 11424 11425 11426 11427 11428 11429 11430 11431 11432 11433 11434 11435 11436 11437 11438 11439 11440 11441 11442 11443 11444 11445 11446 11447 11448 11449 11450 11451 11452 11453 11454 11455 11456 11457 11458 11459 11460 11461 11462 11463 11464 11465 11466 11467 11468 11469 11470 11471 11472 11473 11474 11475 11476 11477 11478 11479 11480 11481 11482 11483 11484 11485 11486 11487 11488 11489 11490 11491 11492 11493 11494 11495 11496 11497 11498 11499 11500 11501 11502 11503 11504 11505 11506 11507 11508 11509 11510 11511 11512 11513 11514 11515 11516 11517 11518 11519 11520 11521 11522 11523 11524 11525 11526 11527 11528 11529 11530 11531 11532 11533 11534 11535 11536 11537 11538 11539 11540 11541 11542 11543 11544 11545 11546 11547 11548 11549 11550 11551 11552 11553 11554 11555 11556 11557 11558 11559 11560 11561 11562 11563 11564 11565 11566 11567 11568 11569 11570 11571 11572 11573 11574 11575 11576 11577 11578 11579 11580 11581 11582 11583 11584 11585 11586 11587 11588 11589 11590 11591 11592 11593 11594 11595 11596 11597 11598 11599 11600 11601 11602 11603 11604 11605 11606 11607 11608 11609 11610 11611 11612 11613 11614 11615 11616 11617 11618 11619 11620 11621 11622 11623 11624 11625 11626 11627 11628 11629 11630 11631 11632 11633 11634 11635 11636 11637 11638 11639 11640 11641 11642 11643 11644 11645 11646 11647 11648 11649 11650 11651 11652 11653 11654 11655 11656 11657 11658 11659 11660 11661 11662 11663 11664 11665 11666 11667 11668 11669 11670 11671 11672 11673 11674 11675 11676 11677 11678 11679 11680 11681 11682 11683 11684 11685 11686 11687 11688 11689 11690 11691 11692 11693 11694 11695 11696 11697 11698 11699 11700 11701 11702 11703 11704 11705 11706 11707 11708 11709 11710 11711 11712 11713 11714 11715 11716 11717 11718 11719 11720 11721 11722 11723 11724 11725 11726 11727 11728 11729 11730 11731 11732 11733 11734 11735 11736 11737 11738 11739 11740 11741 11742 11743 11744 11745 11746 11747 11748 11749 11750 11751 11752 11753 11754 11755 11756 11757 11758 11759 11760 11761 11762 11763 11764 11765 11766 11767 11768 11769 11770 11771 11772 11773 11774 11775 11776 11777 11778 11779 11780 11781 11782 11783 11784 11785 11786 11787 11788 11789 11790 11791 11792 11793 11794 11795 11796 11797 11798 11799 11800 11801 11802 11803 11804 11805 11806 11807 11808 11809 11810 11811 11812 11813 11814 11815 11816 11817 11818 11819 11820 11821 11822 11823 11824 11825 11826 11827 11828 11829 11830 11831 11832 11833 11834 11835 11836 11837 11838 11839 11840 11841 11842 11843 11844 11845 11846 11847 11848 11849 11850 11851 11852 11853 11854 11855 11856 11857 11858 11859 11860 11861 11862 11863 11864 11865 11866 11867 11868 11869 11870 11871 11872 11873 11874 11875 11876 11877 11878 11879 11880 11881 11882 11883 11884 11885 11886 11887 11888 11889 11890 11891 11892 11893 11894 11895 11896 11897 11898 11899 11900 11901 11902 11903 11904 11905 11906 11907 11908 11909 11910 11911 11912 11913 11914 11915 11916 11917 11918 11919 11920 11921 11922 11923 11924 11925 11926 11927 11928 11929 11930 11931 11932 11933 11934 11935 11936 11937 11938 11939 11940 11941 11942 11943 11944 11945 11946 11947 11948 11949 11950 11951 11952 11953 11954 11955 11956 11957 11958 11959 11960 11961 11962 11963 11964 11965 11966 11967 11968 11969 11970 11971 11972 11973 11974 11975 11976 11977 11978 11979 11980 11981 11982 11983 11984 11985 11986 11987 11988 11989 11990 11991 11992 11993 11994 11995 11996 11997 11998 11999 12000 12001 12002 12003 12004 12005 12006 12007 12008 12009 12010 12011 12012 12013 12014 12015 12016 12017 12018 12019 12020 12021 12022 12023 12024 12025 12026 12027 12028 12029 12030 12031 12032 12033 12034 12035 12036 12037 12038 12039 12040 12041 12042 12043 12044 12045 12046 12047 12048 12049 12050 12051 12052 12053 12054 12055 12056 12057 12058 12059 12060 12061 12062 12063 12064 12065 12066 12067 12068 12069 12070 12071 12072 12073 12074 12075 12076 12077 12078 12079 12080 12081 12082 12083 12084 12085 12086 12087 12088 12089 12090 12091 12092 12093 12094 12095 12096 12097 12098 12099 12100 12101 12102 12103 12104 12105 12106 12107 12108 12109 12110 12111 12112 12113 12114 12115 12116 12117 12118 12119 12120 12121 12122 12123 12124 12125 12126 12127 12128 12129 12130 12131 12132 12133 12134 12135 12136 12137 12138 12139 12140 12141 12142 12143 12144 12145 12146 12147 12148 12149 12150 12151 12152 12153 12154 12155 12156 12157 12158 12159 12160 12161 12162 12163 12164 12165 12166 12167 12168 12169 12170 12171 12172 12173 12174 12175 12176 12177 12178 12179 12180 12181 12182 12183 12184 12185 12186 12187 12188 12189 12190 12191 12192 12193 12194 12195 12196 12197 12198 12199 12200 12201 12202 12203 12204 12205 12206 12207 12208 12209 12210 12211 12212 12213 12214 12215 12216 12217 12218 12219 12220 12221 12222 12223 12224 12225 12226 12227 12228 12229 12230 12231 12232 12233 12234 12235 12236 12237 12238 12239 12240 12241 12242 12243 12244 12245 12246 12247 12248 12249 12250 12251 12252 12253 12254 12255 12256 12257 12258 12259 12260 12261 12262 12263 12264 12265 12266 12267 12268 12269 12270 12271 12272 12273 12274 12275 12276 12277 12278 12279 12280 12281 12282 12283 12284 12285 12286 12287 12288 12289 12290 12291 12292 12293 12294 12295 12296 12297 12298 12299 12300 12301 12302 12303 12304 12305 12306 12307 12308 12309 12310 12311 12312 12313 12314 12315 12316 12317 12318 12319 12320 12321 12322 12323 12324 12325 12326 12327 12328 12329 12330 12331 12332 12333 12334 12335 12336 12337 12338 12339 12340 12341 12342 12343 12344 12345 12346 12347 12348 12349 12350 12351 12352 12353 12354 12355 12356 12357 12358 12359 12360 12361 12362 12363 12364 12365 12366 12367 12368 12369 12370 12371 12372 12373 12374 12375 12376 12377 12378 12379 12380 12381 12382 12383 12384 12385 12386 12387 12388 12389 12390 12391 12392 12393 12394 12395 12396 12397 12398 12399 12400 12401 12402 12403 12404 12405 12406 12407 12408 12409 12410 12411 12412 12413 12414 12415 12416 12417 12418 12419 12420 12421 12422 12423 12424 12425 12426 12427 12428 12429 12430 12431 12432 12433 12434 12435 12436 12437 12438 12439 12440 12441 12442 12443 12444 12445 12446 12447 12448 12449 12450 12451 12452 12453 12454 12455 12456 12457 12458 12459 12460 12461 12462 12463 12464 12465 12466 12467 12468 12469 12470 12471 12472 12473 12474 12475 12476 12477 12478 12479 12480 12481 12482 12483 12484 12485 12486 12487 12488 12489 12490 12491 12492 12493 12494 12495 12496 12497 12498 12499 12500 12501 12502 12503 12504 12505 12506 12507 12508 12509 12510 12511 12512 12513 12514 12515 12516 12517 12518 12519 12520 12521 12522 12523 12524 12525 12526 12527 12528 12529 12530 12531 12532 12533 12534 12535 12536 12537 12538 12539 12540 12541 12542 12543 12544 12545 12546 12547 12548 12549 12550 12551 12552 12553 12554 12555 12556 12557 12558 12559 12560 12561 12562 12563 12564 12565 12566 12567 12568 12569 12570 12571 12572 12573 12574 12575 12576 12577 12578 12579 12580 12581 12582 12583 12584 12585 12586 12587 12588 12589 12590 12591 12592 12593 12594 12595 12596 12597 12598 12599 12600 12601 12602 12603 12604 12605 12606 12607 12608 12609 12610 12611 12612 12613 12614 12615 12616 12617 12618 12619 12620 12621 12622 12623 12624 12625 12626 12627 12628 12629 12630 12631 12632 12633 12634 12635 12636 12637 12638 12639 12640 12641 12642 12643 12644 12645 12646 12647 12648 12649 12650 12651 12652 12653 12654 12655 12656 12657 12658 12659 12660 12661 12662 12663 12664 12665 12666 12667 12668 12669 12670 12671 12672 12673 12674 12675 12676 12677 12678 12679 12680 12681 12682 12683 12684 12685 12686 12687 12688 12689 12690 12691 12692 12693 12694 12695 12696 12697 12698 12699 12700 12701 12702 12703 12704 12705 12706 12707 12708 12709 12710 12711 12712 12713 12714 12715 12716 12717 12718 12719 12720 12721 12722 12723 12724 12725 12726 12727 12728 12729 12730 12731 12732 12733 12734 12735 12736 12737 12738 12739 12740 12741 12742 12743 12744 12745 12746 12747 12748 12749 12750 12751 12752 12753 12754 12755 12756 12757 12758 12759 12760 12761 12762 12763 12764 12765 12766 12767 12768 12769 12770 12771 12772 12773 12774 12775 12776 12777 12778 12779 12780 12781 12782 12783 12784 12785 12786 12787 12788 12789 12790 12791 12792 12793 12794 12795 12796 12797 12798 12799 12800 12801 12802 12803 12804 12805 12806 12807 12808 12809 12810 12811 12812 12813 12814 12815 12816 12817 12818 12819 12820 12821 12822 12823 12824 12825 12826 12827 12828 12829 12830 12831 12832 12833 12834 12835 12836 12837 12838 12839 12840 12841 12842 12843 12844 12845 12846 12847 12848 12849 12850 12851 12852 12853 12854 12855 12856 12857 12858 12859 12860 12861 12862 12863 12864 12865 12866 12867 12868 12869 12870 12871 12872 12873 12874 12875 12876 12877 12878 12879 12880 12881 12882 12883 12884 12885 12886 12887 12888 12889 12890 12891 12892 12893 12894 12895 12896 12897 12898 12899 12900 12901 12902 12903 12904 12905 12906 12907 12908 12909 12910 12911 12912 12913 12914 12915 12916 12917 12918 12919 12920 12921 12922 12923 12924 12925 12926 12927 12928 12929 12930 12931 12932 12933 12934 12935 12936 12937 12938 12939 12940 12941 12942 12943 12944 12945 12946 12947 12948 12949 12950 12951 12952 12953 12954 12955 12956 12957 12958 12959 12960 12961 12962 12963 12964 12965 12966 12967 12968 12969 12970 12971 12972 12973 12974 12975 12976 12977 12978 12979 12980 12981 12982 12983 12984 12985 12986 12987 12988 12989 12990 12991 12992 12993 12994 12995 12996 12997 12998 12999 13000 13001 13002 13003 13004 13005 13006 13007 13008 13009 13010 13011 13012 13013 13014 13015 13016 13017 13018 13019 13020 13021 13022 13023 13024 13025 13026 13027 13028 13029 13030 13031 13032 13033 13034 13035 13036 13037 13038 13039 13040 13041 13042 13043 13044 13045 13046 13047 13048 13049 13050 13051 13052 13053 13054 13055 13056 13057 13058 13059 13060 13061 13062 13063 13064 13065 13066 13067 13068 13069 13070 13071 13072 13073 13074 13075 13076 13077 13078 13079 13080 13081 13082 13083 13084 13085 13086 13087 13088 13089 13090 13091 13092 13093 13094 13095 13096 13097 13098 13099 13100 13101 13102 13103 13104 13105 13106 13107 13108 13109 13110 13111 13112 13113 13114 13115 13116 13117 13118 13119 13120 13121 13122 13123 13124 13125 13126 13127 13128 13129 13130 13131 13132 13133 13134 13135 13136 13137 13138 13139 13140 13141 13142 13143 13144 13145 13146 13147 13148 13149 13150 13151 13152 13153 13154 13155 13156 13157 13158 13159 13160 13161 13162 13163 13164 13165 13166 13167 13168 13169 13170 13171 13172 13173 13174 13175 13176 13177 13178 13179 13180 13181 13182 13183 13184 13185 13186 13187 13188 13189 13190 13191 13192 13193 13194 13195 13196 13197 13198 13199 13200 13201 13202 13203 13204 13205 13206 13207 13208 13209 13210 13211 13212 13213 13214 13215 13216 13217 13218 13219 13220 13221 13222 13223 13224 13225 13226 13227 13228 13229 13230 13231 13232 13233 13234 13235 13236 13237 13238 13239 13240 13241 13242 13243 13244 13245 13246 13247 13248 13249 13250 13251 13252 13253 13254 13255 13256 13257 13258 13259 13260 13261 13262 13263 13264 13265 13266 13267 13268 13269 13270 13271 13272 13273 13274 13275 13276 13277 13278 13279 13280 13281 13282 13283 13284 13285 13286 13287 13288 13289 13290 13291 13292 13293 13294 13295 13296 13297 13298 13299 13300 13301 13302 13303 13304 13305 13306 13307 13308 13309 13310 13311 13312 13313 13314 13315 13316 13317 13318 13319 13320 13321 13322 13323 13324 13325 13326 13327 13328 13329 13330 13331 13332 13333 13334 13335 13336 13337 13338 13339 13340 13341 13342 13343 13344 13345 13346 13347 13348 13349 13350 13351 13352 13353 13354 13355 13356 13357 13358 13359 13360 13361 13362 13363 13364 13365 13366 13367 13368 13369 13370 13371 13372 13373 13374 13375 13376 13377 13378 13379 13380 13381 13382 13383 13384 13385 13386 13387 13388 13389 13390 13391 13392 13393 13394 13395 13396 13397 13398 13399 13400 13401 13402 13403 13404 13405 13406 13407 13408 13409 13410 13411 13412 13413 13414 13415 13416 13417 13418 13419 13420 13421 13422 13423 13424 13425 13426 13427 13428 13429 13430 13431 13432 13433 13434 13435 13436 13437 13438 13439 13440 13441 13442 13443 13444 13445 13446 13447 13448 13449 13450 13451 13452 13453 13454 13455 13456 13457 13458 13459 13460 13461 13462 13463 13464 13465 13466 13467 13468 13469 13470 13471 13472 13473 13474 13475 13476 13477 13478 13479 13480 13481 13482 13483 13484 13485 13486 13487 13488 13489 13490 13491 13492 13493 13494 13495 13496 13497 13498 13499 13500 13501 13502 13503 13504 13505 13506 13507 13508 13509 13510 13511 13512 13513 13514 13515 13516 13517 13518 13519 13520 13521 13522 13523 13524 13525 13526 13527 13528 13529 13530 13531 13532 13533 13534 13535 13536 13537 13538 13539 13540 13541 13542 13543 13544 13545 13546 13547 13548 13549 13550 13551 13552 13553 13554 13555 13556 13557 13558 13559 13560 13561 13562 13563 13564 13565 13566 13567 13568 13569 13570 13571 13572 13573 13574 13575 13576 13577 13578 13579 13580 13581 13582 13583 13584 13585 13586 13587 13588 13589 13590 13591 13592 13593 13594 13595 13596 13597 13598 13599 13600 13601 13602 13603 13604 13605 13606 13607 13608 13609 13610 13611 13612 13613 13614 13615 13616 13617 13618 13619 13620 13621 13622 13623 13624 13625 13626 13627 13628 13629 13630 13631 13632 13633 13634 13635 13636 13637 13638 13639 13640 13641 13642 13643 13644 13645 13646 13647 13648 13649 13650 13651 13652 13653 13654 13655 13656 13657 13658 13659 13660 13661 13662 13663 13664 13665 13666 13667 13668 13669 13670 13671 13672 13673 13674 13675 13676 13677 13678 13679 13680 13681 13682 13683 13684 13685 13686 13687 13688 13689 13690 13691 13692 13693 13694 13695 13696 13697 13698 13699 13700 13701 13702 13703 13704 13705 13706 13707 13708 13709 13710 13711 13712 13713 13714 13715 13716 13717 13718 13719 13720 13721 13722 13723 13724 13725 13726 13727 13728 13729 13730 13731 13732 13733 13734 13735 13736 13737 13738 13739 13740 13741 13742 13743 13744 13745 13746 13747 13748 13749 13750 13751 13752 13753 13754 13755 13756 13757 13758 13759 13760 13761 13762 13763 13764 13765 13766 13767 13768 13769 13770 13771 13772 13773 13774 13775 13776 13777 13778 13779 13780 13781 13782 13783 13784 13785 13786 13787 13788 13789 13790 13791 13792 13793 13794 13795 13796 13797 13798 13799 13800 13801 13802 13803 13804 13805 13806 13807 13808 13809 13810 13811 13812 13813 13814 13815 13816 13817 13818 13819 13820 13821 13822 13823 13824 13825 13826 13827 13828 13829 13830 13831 13832 13833 13834 13835 13836 13837 13838 13839 13840 13841 13842 13843 13844 13845 13846 13847 13848 13849 13850 13851 13852 13853 13854 13855 13856 13857 13858 13859 13860 13861 13862 13863 13864 13865 13866 13867 13868 13869 13870 13871 13872 13873 13874 13875 13876 13877 13878 13879 13880 13881 13882 13883 13884 13885 13886 13887 13888 13889 13890 13891 13892 13893 13894 13895 13896 13897 13898 13899 13900 13901 13902 13903 13904 13905 13906 13907 13908 13909 13910 13911 13912 13913 13914 13915 13916 13917 13918 13919 13920 13921 13922 13923 13924 13925 13926 13927 13928 13929 13930 13931 13932 13933 13934 13935 13936 13937 13938 13939 13940 13941 13942 13943 13944 13945 13946 13947 13948 13949 13950 13951 13952 13953 13954 13955 13956 13957 13958 13959 13960 13961 13962 13963 13964 13965 13966 13967 13968 13969 13970 13971 13972 13973 13974 13975 13976 13977 13978 13979 13980 13981 13982 13983 13984 13985 13986 13987 13988 13989 13990 13991 13992 13993 13994 13995 13996 13997 13998 13999 14000 14001 14002 14003 14004 14005 14006 14007 14008 14009 14010 14011 14012 14013 14014 14015 14016 14017 14018 14019 14020 14021 14022 14023 14024 14025 14026 14027 14028 14029 14030 14031 14032 14033 14034 14035 14036 14037 14038 14039 14040 14041 14042 14043 14044 14045 14046 14047 14048 14049 14050 14051 14052 14053 14054 14055 14056 14057 14058 14059 14060 14061 14062 14063 14064 14065 14066 14067 14068 14069 14070 14071 14072 14073 14074 14075 14076 14077 14078 14079 14080 14081 14082 14083 14084 14085 14086 14087 14088 14089 14090 14091 14092 14093 14094 14095 14096 14097 14098 14099 14100 14101 14102 14103 14104 14105 14106 14107 14108 14109 14110 14111 14112 14113 14114 14115 14116 14117 14118 14119 14120 14121 14122 14123 14124 14125 14126 14127 14128 14129 14130 14131 14132 14133 14134 14135 14136 14137 14138 14139 14140 14141 14142 14143 14144 14145 14146 14147 14148 14149 14150 14151 14152 14153 14154 14155 14156 14157 14158 14159 14160 14161 14162 14163 14164 14165 14166 14167 14168 14169 14170 14171 14172 14173 14174 14175 14176 14177 14178 14179 14180 14181 14182 14183 14184 14185 14186 14187 14188 14189 14190 14191 14192 14193 14194 14195 14196 14197 14198 14199 14200 14201 14202 14203 14204 14205 14206 14207 14208 14209 14210 14211 14212 14213 14214 14215 14216 14217 14218 14219 14220 14221 14222 14223 14224 14225 14226 14227 14228 14229 14230 14231 14232 14233 14234 14235 14236 14237 14238 14239 14240 14241 14242 14243 14244 14245 14246 14247 14248 14249 14250 14251 14252 14253 14254 14255 14256 14257 14258 14259 14260 14261 14262 14263 14264 14265 14266 14267 14268 14269 14270 14271 14272 14273 14274 14275 14276 14277 14278 14279 14280 14281 14282 14283 14284 14285 14286 14287 14288 14289 14290 14291 14292 14293 14294 14295 14296 14297 14298 14299 14300 14301 14302 14303 14304 14305 14306 14307 14308 14309 14310 14311 14312 14313 14314 14315 14316 14317 14318 14319 14320 14321 14322 14323 14324 14325 14326 14327 14328 14329 14330 14331 14332 14333 14334 14335 14336 14337 14338 14339 14340 14341 14342 14343 14344 14345 14346 14347 14348 14349 14350 14351 14352 14353 14354 14355 14356 14357 14358 14359 14360 14361 14362 14363 14364 14365 14366 14367 14368 14369 14370 14371 14372 14373 14374 14375 14376 14377 14378 14379 14380 14381 14382 14383 14384 14385 14386 14387 14388 14389 14390 14391 14392 14393 14394 14395 14396 14397 14398 14399 14400 14401 14402 14403 14404 14405 14406 14407 14408 14409 14410 14411 14412 14413 14414 14415 14416 14417 14418 14419 14420 14421 14422 14423 14424 14425 14426 14427 14428 14429 14430 14431 14432 14433 14434 14435 14436 14437 14438 14439 14440 14441 14442 14443 14444 14445 14446 14447 14448 14449 14450 14451 14452 14453 14454 14455 14456 14457 14458 14459 14460 14461 14462 14463 14464 14465 14466 14467 14468 14469 14470 14471 14472 14473 14474 14475 14476 14477 14478 14479 14480 14481 14482 14483 14484 14485 14486 14487 14488 14489 14490 14491 14492 14493 14494 14495 14496 14497 14498 14499 14500 14501 14502 14503 14504 14505 14506 14507 14508 14509 14510 14511 14512 14513 14514 14515 14516 14517 14518 14519 14520 14521 14522 14523 14524 14525 14526 14527 14528 14529 14530 14531 14532 14533 14534 14535 14536 14537 14538 14539 14540 14541 14542 14543 14544 14545 14546 14547 14548 14549 14550 14551 14552 14553 14554 14555 14556 14557 14558 14559 14560 14561 14562 14563 14564 14565 14566 14567 14568 14569 14570 14571 14572 14573 14574 14575 14576 14577 14578 14579 14580 14581 14582 14583 14584 14585 14586 14587 14588 14589 14590 14591 14592 14593 14594 14595 14596 14597 14598 14599 14600 14601 14602 14603 14604 14605 14606 14607 14608 14609 14610 14611 14612 14613 14614 14615 14616 14617 14618 14619 14620 14621 14622 14623 14624 14625 14626 14627 14628 14629 14630 14631 14632 14633 14634 14635 14636 14637 14638 14639 14640 14641 14642 14643 14644 14645 14646 14647 14648 14649 14650 14651 14652 14653 14654 14655 14656 14657 14658 14659 14660 14661 14662 14663 14664 14665 14666 14667 14668 14669 14670 14671 14672 14673 14674 14675 14676 14677 14678 14679 14680 14681 14682 14683 14684 14685 14686 14687 14688 14689 14690 14691 14692 14693 14694 14695 14696 14697 14698 14699 14700 14701 14702 14703 14704 14705 14706 14707 14708 14709 14710 14711 14712 14713 14714 14715 14716 14717 14718 14719 14720 14721 14722 14723 14724 14725 14726 14727 14728 14729 14730 14731 14732 14733 14734 14735 14736 14737 14738 14739 14740 14741 14742 14743 14744 14745 14746 14747 14748 14749 14750 14751 14752 14753 14754 14755 14756 14757 14758 14759 14760 14761 14762 14763 14764 14765 14766 14767 14768 14769 14770 14771 14772 14773 14774 14775 14776 14777 14778 14779 14780 14781 14782 14783 14784 14785 14786 14787 14788 14789 14790 14791 14792 14793 14794 14795 14796 14797 14798 14799 14800 14801 14802 14803 14804 14805 14806 14807 14808 14809 14810 14811 14812 14813 14814 14815 14816 14817 14818 14819 14820 14821 14822 14823 14824 14825 14826 14827 14828 14829 14830 14831 14832 14833 14834 14835 14836 14837 14838 14839 14840 14841 14842 14843 14844 14845 14846 14847 14848 14849 14850 14851 14852 14853 14854 14855 14856 14857 14858 14859 14860 14861 14862 14863 14864 14865 14866 14867 14868 14869 14870 14871 14872 14873 14874 14875 14876 14877 14878 14879 14880 14881 14882 14883 14884 14885 14886 14887 14888 14889 14890 14891 14892 14893 14894 14895 14896 14897 14898 14899 14900 14901 14902 14903 14904 14905 14906 14907 14908 14909 14910 14911 14912 14913 14914 14915 14916 14917 14918 14919 14920 14921 14922 14923 14924 14925 14926 14927 14928 14929 14930 14931 14932 14933 14934 14935 14936 14937 14938 14939 14940 14941 14942 14943 14944 14945 14946 14947 14948 14949 14950 14951 14952 14953 14954 14955 14956 14957 14958 14959 14960 14961 14962 14963 14964 14965 14966 14967 14968 14969 14970 14971 14972 14973 14974 14975 14976 14977 14978 14979 14980 14981 14982 14983 14984 14985 14986 14987 14988 14989 14990 14991 14992 14993 14994 14995 14996 14997 14998 14999 15000 15001 15002 15003 15004 15005 15006 15007 15008 15009 15010 15011 15012 15013 15014 15015 15016 15017 15018 15019 15020 15021 15022 15023 15024 15025 15026 15027 15028 15029 15030 15031 15032 15033 15034 15035 15036 15037 15038 15039 15040 15041 15042 15043 15044 15045 15046 15047 15048 15049 15050 15051 15052 15053 15054 15055 15056 15057 15058 15059 15060 15061 15062 15063 15064 15065 15066 15067 15068 15069 15070 15071 15072 15073 15074 15075 15076 15077 15078 15079 15080 15081 15082 15083 15084 15085 15086 15087 15088 15089 15090 15091 15092 15093 15094 15095 15096 15097 15098 15099 15100 15101 15102 15103 15104 15105 15106 15107 15108 15109 15110 15111 15112 15113 15114 15115 15116 15117 15118 15119 15120 15121 15122 15123 15124 15125 15126 15127 15128 15129 15130 15131 15132 15133 15134 15135 15136 15137 15138 15139 15140 15141 15142 15143 15144 15145 15146 15147 15148 15149 15150 15151 15152 15153 15154 15155 15156 15157 15158 15159 15160 15161 15162 15163 15164 15165 15166 15167 15168 15169 15170 15171 15172 15173 15174 15175 15176 15177 15178 15179 15180 15181 15182 15183 15184 15185 15186 15187 15188 15189 15190 15191 15192 15193 15194 15195 15196 15197 15198 15199 15200 15201 15202 15203 15204 15205 15206 15207 15208 15209 15210 15211 15212 15213 15214 15215 15216 15217 15218 15219 15220 15221 15222 15223 15224 15225 15226 15227 15228 15229 15230 15231 15232 15233 15234 15235 15236 15237 15238 15239 15240 15241 15242 15243 15244 15245 15246 15247 15248 15249 15250 15251 15252 15253 15254 15255 15256 15257 15258 15259 15260 15261 15262 15263 15264 15265 15266 15267 15268 15269 15270 15271 15272 15273 15274 15275 15276 15277 15278 15279 15280 15281 15282 15283 15284 15285 15286 15287 15288 15289 15290 15291 15292 15293 15294 15295 15296 15297 15298 15299 15300 15301 15302 15303 15304 15305 15306 15307 15308 15309 15310 15311 15312 15313 15314 15315 15316 15317 15318 15319 15320 15321 15322 15323 15324 15325 15326 15327 15328 15329 15330 15331 15332 15333 15334 15335 15336 15337 15338 15339 15340 15341 15342 15343 15344 15345 15346 15347 15348 15349 15350 15351 15352 15353 15354 15355 15356 15357 15358 15359 15360 15361 15362 15363 15364 15365 15366 15367 15368 15369 15370 15371 15372 15373 15374 15375 15376 15377 15378 15379 15380 15381 15382 15383 15384 15385 15386 15387 15388 15389 15390 15391 15392 15393 15394 15395 15396 15397 15398 15399 15400 15401 15402 15403 15404 15405 15406 15407 15408 15409 15410 15411 15412 15413 15414 15415 15416 15417 15418 15419 15420 15421 15422 15423 15424 15425 15426 15427 15428 15429 15430 15431 15432 15433 15434 15435 15436 15437 15438 15439 15440 15441 15442 15443 15444 15445 15446 15447 15448 15449 15450 15451 15452 15453 15454 15455 15456 15457 15458 15459 15460 15461 15462 15463 15464 15465 15466 15467 15468 15469 15470 15471 15472 15473 15474 15475 15476 15477 15478 15479 15480 15481 15482 15483 15484 15485 15486 15487 15488 15489 15490 15491 15492 15493 15494 15495 15496 15497 15498 15499 15500 15501 15502 15503 15504 15505 15506 15507 15508 15509 15510 15511 15512 15513 15514 15515 15516 15517 15518 15519 15520 15521 15522 15523 15524 15525 15526 15527 15528 15529 15530 15531 15532 15533 15534 15535 15536 15537 15538 15539 15540 15541 15542 15543 15544 15545 15546 15547 15548 15549 15550 15551 15552 15553 15554 15555 15556 15557 15558 15559 15560 15561 15562 15563 15564 15565 15566 15567 15568 15569 15570 15571 15572 15573 15574 15575 15576 15577 15578 15579 15580 15581 15582 15583 15584 15585 15586 15587 15588 15589 15590 15591 15592 15593 15594 15595 15596 15597 15598 15599 15600 15601 15602 15603 15604 15605 15606 15607 15608 15609 15610 15611 15612 15613 15614 15615 15616 15617 15618 15619 15620 15621 15622 15623 15624 15625 15626 15627 15628 15629 15630 15631 15632 15633 15634 15635 15636 15637 15638 15639 15640 15641 15642 15643 15644 15645 15646 15647 15648 15649 15650 15651 15652 15653 15654 15655 15656 15657 15658 15659 15660 15661 15662 15663 15664 15665 15666 15667 15668 15669 15670 15671 15672 15673 15674 15675 15676 15677 15678 15679 15680 15681 15682 15683 15684 15685 15686 15687 15688 15689 15690 15691 15692 15693 15694 15695 15696 15697 15698 15699 15700 15701 15702 15703 15704 15705 15706 15707 15708 15709 15710 15711 15712 15713 15714 15715 15716 15717 15718 15719 15720 15721 15722 15723 15724 15725 15726 15727 15728 15729 15730 15731 15732 15733 15734 15735 15736 15737 15738 15739 15740 15741 15742 15743 15744 15745 15746 15747 15748 15749 15750 15751 15752 15753 15754 15755 15756 15757 15758 15759 15760 15761 15762 15763 15764 15765 15766 15767 15768 15769 15770 15771 15772 15773 15774 15775 15776 15777 15778 15779 15780 15781 15782 15783 15784 15785 15786 15787 15788 15789 15790 15791 15792 15793 15794 15795 15796 15797 15798 15799 15800 15801 15802 15803 15804 15805 15806 15807 15808 15809 15810 15811 15812 15813 15814 15815 15816 15817 15818 15819 15820 15821 15822 15823 15824 15825 15826 15827 15828 15829 15830 15831 15832 15833 15834 15835 15836 15837 15838 15839 15840 15841 15842 15843 15844 15845 15846 15847 15848 15849 15850 15851 15852 15853 15854 15855 15856 15857 15858 15859 15860 15861 15862 15863 15864 15865 15866 15867 15868 15869 15870 15871 15872 15873 15874 15875 15876 15877 15878 15879 15880 15881 15882 15883 15884 15885 15886 15887 15888 15889 15890 15891 15892 15893 15894 15895 15896 15897 15898 15899 15900 15901 15902 15903 15904 15905 15906 15907 15908 15909 15910 15911 15912 15913 15914 15915 15916 15917 15918 15919 15920 15921 15922 15923 15924 15925 15926 15927 15928 15929 15930 15931 15932 15933 15934 15935 15936 15937 15938 15939 15940 15941 15942 15943 15944 15945 15946 15947 15948 15949 15950 15951 15952 15953 15954 15955 15956 15957 15958 15959 15960 15961 15962 15963 15964 15965 15966 15967 15968 15969 15970 15971 15972 15973 15974 15975 15976 15977 15978 15979 15980 15981 15982 15983 15984 15985 15986 15987 15988 15989 15990 15991 15992 15993 15994 15995 15996 15997 15998 15999 16000 16001 16002 16003 16004 16005 16006 16007 16008 16009 16010 16011 16012 16013 16014 16015 16016 16017 16018 16019 16020 16021 16022 16023 16024 16025 16026 16027 16028 16029 16030 16031 16032 16033 16034 16035 16036 16037 16038 16039 16040 16041 16042 16043 16044 16045 16046 16047 16048 16049 16050 16051 16052 16053 16054 16055 16056 16057 16058 16059 16060 16061 16062 16063 16064 16065 16066 16067 16068 16069 16070 16071 16072 16073 16074 16075 16076 16077 16078 16079 16080 16081 16082 16083 16084 16085 16086 16087 16088 16089 16090 16091 16092 16093 16094 16095 16096 16097 16098 16099 16100 16101 16102 16103 16104 16105 16106 16107 16108 16109 16110 16111 16112 16113 16114 16115 16116 16117 16118 16119 16120 16121 16122 16123 16124 16125 16126 16127 16128 16129 16130 16131 16132 16133 16134 16135 16136 16137 16138 16139 16140 16141 16142 16143 16144 16145 16146 16147 16148 16149 16150 16151 16152 16153 16154 16155 16156 16157 16158 16159 16160 16161 16162 16163 16164 16165 16166 16167 16168 16169 16170 16171 16172 16173 16174 16175 16176 16177 16178 16179 16180 16181 16182 16183 16184 16185 16186 16187 16188 16189 16190 16191 16192 16193 16194 16195 16196 16197 16198 16199 16200 16201 16202 16203 16204 16205 16206 16207 16208 16209 16210 16211 16212 16213 16214 16215 16216 16217 16218 16219 16220 16221 16222 16223 16224 16225 16226 16227 16228 16229 16230 16231 16232 16233 16234 16235 16236 16237 16238 16239 16240 16241 16242 16243 16244 16245 16246 16247 16248 16249 16250 16251 16252 16253 16254 16255 16256 16257 16258 16259 16260 16261 16262 16263 16264 16265 16266 16267 16268 16269 16270 16271 16272 16273 16274 16275 16276 16277 16278 16279 16280 16281 16282 16283 16284 16285 16286 16287 16288 16289 16290 16291 16292 16293 16294 16295 16296 16297 16298 16299 16300 16301 16302 16303 16304 16305 16306 16307 16308 16309 16310 16311 16312 16313 16314 16315 16316 16317 16318 16319 16320 16321 16322 16323 16324 16325 16326 16327 16328 16329 16330 16331 16332 16333 16334 16335 16336 16337 16338 16339 16340 16341 16342 16343 16344 16345 16346 16347 16348 16349 16350 16351 16352 16353 16354 16355 16356 16357 16358 16359 16360 16361 16362 16363 16364 16365 16366 16367 16368 16369 16370 16371 16372 16373 16374 16375 16376 16377 16378 16379 16380 16381 16382 16383 16384 16385 16386 16387 16388 16389 16390 16391 16392 16393 16394 16395 16396 16397 16398 16399 16400 16401 16402 16403 16404 16405 16406 16407 16408 16409 16410 16411 16412 16413 16414 16415 16416 16417 16418 16419 16420 16421 16422 16423 16424 16425 16426 16427 16428 16429 16430 16431 16432 16433 16434 16435 16436 16437 16438 16439 16440 16441 16442 16443 16444 16445 16446 16447 16448 16449 16450 16451 16452 16453 16454 16455 16456 16457 16458 16459 16460 16461 16462 16463 16464 16465 16466 16467 16468 16469 16470 16471 16472 16473 16474 16475 16476 16477 16478 16479 16480 16481 16482 16483 16484 16485 16486 16487 16488 16489 16490 16491 16492 16493 16494 16495 16496 16497 16498 16499 16500 16501 16502 16503 16504 16505 16506 16507 16508 16509 16510 16511 16512 16513 16514 16515 16516 16517 16518 16519 16520 16521 16522 16523 16524 16525 16526 16527 16528 16529 16530 16531 16532 16533 16534 16535 16536 16537 16538 16539 16540 16541 16542 16543 16544 16545 16546 16547 16548 16549 16550 16551 16552 16553 16554 16555 16556 16557 16558 16559 16560 16561 16562 16563 16564 16565 16566 16567 16568 16569 16570 16571 16572 16573 16574 16575 16576 16577 16578 16579 16580 16581 16582 16583 16584 16585 16586 16587 16588 16589 16590 16591 16592 16593 16594 16595 16596 16597 16598 16599 16600 16601 16602 16603 16604 16605 16606 16607 16608 16609 16610 16611 16612 16613 16614 16615 16616 16617 16618 16619 16620 16621 16622 16623 16624 16625 16626 16627 16628 16629 16630 16631 16632 16633 16634 16635 16636 16637 16638 16639 16640 16641 16642 16643 16644 16645 16646 16647 16648 16649 16650 16651 16652 16653 16654 16655 16656 16657 16658 16659 16660 16661 16662 16663 16664 16665 16666 16667 16668 16669 16670 16671 16672 16673 16674 16675 16676 16677 16678 16679 16680 16681 16682 16683 16684 16685 16686 16687 16688 16689 16690 16691 16692 16693 16694 16695 16696 16697 16698 16699 16700 16701 16702 16703 16704 16705 16706 16707 16708 16709 16710 16711 16712 16713 16714 16715 16716 16717 16718 16719 16720 16721 16722 16723 16724 16725 16726 16727 16728 16729 16730 16731 16732 16733 16734 16735 16736 16737 16738 16739 16740 16741 16742 16743 16744 16745 16746 16747 16748 16749 16750 16751 16752 16753 16754 16755 16756 16757 16758 16759 16760 16761 16762 16763 16764 16765 16766 16767 16768 16769 16770 16771 16772 16773 16774 16775 16776 16777 16778 16779 16780 16781 16782 16783 16784 16785 16786 16787 16788 16789 16790 16791 16792 16793 16794 16795 16796 16797 16798 16799 16800 16801 16802 16803 16804 16805 16806 16807 16808 16809 16810 16811 16812 16813 16814 16815 16816 16817 16818 16819 16820 16821 16822 16823 16824 16825 16826 16827 16828 16829 16830 16831 16832 16833 16834 16835 16836 16837 16838 16839 16840 16841 16842 16843 16844 16845 16846 16847 16848 16849 16850 16851 16852 16853 16854 16855 16856 16857 16858 16859 16860 16861 16862 16863 16864 16865 16866 16867 16868 16869 16870 16871 16872 16873 16874 16875 16876 16877 16878 16879 16880 16881 16882 16883 16884 16885 16886 16887 16888 16889 16890 16891 16892 16893 16894 16895 16896 16897 16898 16899 16900 16901 16902 16903 16904 16905 16906 16907 16908 16909 16910 16911 16912 16913 16914 16915 16916 16917 16918 16919 16920 16921 16922 16923 16924 16925 16926 16927 16928 16929 16930 16931 16932 16933 16934 16935 16936 16937 16938 16939 16940 16941 16942 16943 16944 16945 16946 16947 16948 16949 16950 16951 16952 16953 16954 16955 16956 16957 16958 16959 16960 16961 16962 16963 16964 16965 16966 16967 16968 16969 16970 16971 16972 16973 16974 16975 16976 16977 16978 16979 16980 16981 16982 16983 16984 16985 16986 16987 16988 16989 16990 16991 16992 16993 16994 16995 16996 16997 16998 16999 17000 17001 17002 17003 17004 17005 17006 17007 17008 17009 17010 17011 17012 17013 17014 17015 17016 17017 17018 17019 17020 17021 17022 17023 17024 17025 17026 17027 17028 17029 17030 17031 17032 17033 17034 17035 17036 17037 17038 17039 17040 17041 17042 17043 17044 17045 17046 17047 17048 17049 17050 17051 17052 17053 17054 17055 17056 17057 17058 17059 17060 17061 17062 17063 17064 17065 17066 17067 17068 17069 17070 17071 17072 17073 17074 17075 17076 17077 17078 17079 17080 17081 17082 17083 17084 17085 17086 17087 17088 17089 17090 17091 17092 17093 17094 17095 17096 17097 17098 17099 17100 17101 17102 17103 17104 17105 17106 17107 17108 17109 17110 17111 17112 17113 17114 17115 17116 17117 17118 17119 17120 17121 17122 17123 17124 17125 17126 17127 17128 17129 17130 17131 17132 17133 17134 17135 17136 17137 17138 17139 17140 17141 17142 17143 17144 17145 17146 17147 17148 17149 17150 17151 17152 17153 17154 17155 17156 17157 17158 17159 17160 17161 17162 17163 17164 17165 17166 17167 17168 17169 17170 17171 17172 17173 17174 17175 17176 17177 17178 17179 17180 17181 17182 17183 17184 17185 17186 17187 17188 17189 17190 17191 17192 17193 17194 17195 17196 17197 17198 17199 17200 17201 17202 17203 17204 17205 17206 17207 17208 17209 17210 17211 17212 17213 17214 17215 17216 17217 17218 17219 17220 17221 17222 17223 17224 17225 17226 17227 17228 17229 17230 17231 17232 17233 17234 17235 17236 17237 17238 17239 17240 17241 17242 17243 17244 17245 17246 17247 17248 17249 17250 17251 17252 17253 17254 17255 17256 17257 17258 17259 17260 17261 17262 17263 17264 17265 17266 17267 17268 17269 17270 17271 17272 17273 17274 17275 17276 17277 17278 17279 17280 17281 17282 17283 17284 17285 17286 17287 17288 17289 17290 17291 17292 17293 17294 17295 17296 17297 17298 17299 17300 17301 17302 17303 17304 17305 17306 17307 17308 17309 17310 17311 17312 17313 17314 17315 17316 17317 17318 17319 17320 17321 17322 17323 17324 17325 17326 17327 17328 17329 17330 17331 17332 17333 17334 17335 17336 17337 17338 17339 17340 17341 17342 17343 17344 17345 17346 17347 17348 17349 17350 17351 17352 17353 17354 17355 17356 17357 17358 17359 17360 17361 17362 17363 17364 17365 17366 17367 17368 17369 17370 17371 17372 17373 17374 17375 17376 17377 17378 17379 17380 17381 17382 17383 17384 17385 17386 17387 17388 17389 17390 17391 17392 17393 17394 17395 17396 17397 17398 17399 17400 17401 17402 17403 17404 17405 17406 17407 17408 17409 17410 17411 17412 17413 17414 17415 17416 17417 17418 17419 17420 17421 17422 17423 17424 17425 17426 17427 17428 17429 17430 17431 17432 17433 17434 17435 17436 17437 17438 17439 17440 17441 17442 17443 17444 17445 17446 17447 17448 17449 17450 17451 17452 17453 17454 17455 17456 17457 17458 17459 17460 17461 17462 17463 17464 17465 17466 17467 17468 17469 17470 17471 17472 17473 17474 17475 17476 17477 17478 17479 17480 17481 17482 17483 17484 17485 17486 17487 17488 17489 17490 17491 17492 17493 17494 17495 17496 17497 17498 17499 17500 17501 17502 17503 17504 17505 17506 17507 17508 17509 17510 17511 17512 17513 17514 17515 17516 17517 17518 17519 17520 17521 17522 17523 17524 17525 17526 17527 17528 17529 17530 17531 17532 17533 17534 17535 17536 17537 17538 17539 17540 17541 17542 17543 17544 17545 17546 17547 17548 17549 17550 17551 17552 17553 17554 17555 17556 17557 17558 17559 17560 17561 17562 17563 17564 17565 17566 17567 17568 17569 17570 17571 17572 17573 17574 17575 17576 17577 17578 17579 17580 17581 17582 17583 17584 17585 17586 17587 17588 17589 17590 17591 17592 17593 17594 17595 17596 17597 17598 17599 17600 17601 17602 17603 17604 17605 17606 17607 17608 17609 17610 17611 17612 17613 17614 17615 17616 17617 17618 17619 17620 17621 17622 17623 17624 17625 17626 17627 17628 17629 17630 17631 17632 17633 17634 17635 17636 17637 17638 17639 17640 17641 17642 17643 17644 17645 17646 17647 17648 17649 17650 17651 17652 17653 17654 17655 17656 17657 17658 17659 17660 17661 17662 17663 17664 17665 17666 17667 17668 17669 17670 17671 17672 17673 17674 17675 17676 17677 17678 17679 17680 17681 17682 17683 17684 17685 17686 17687 17688 17689 17690 17691 17692 17693 17694 17695 17696 17697 17698 17699 17700 17701 17702 17703 17704 17705 17706 17707 17708 17709 17710 17711 17712 17713 17714 17715 17716 17717 17718 17719 17720 17721 17722 17723 17724 17725 17726 17727 17728 17729 17730 17731 17732 17733 17734 17735 17736 17737 17738 17739 17740 17741 17742 17743 17744 17745 17746 17747 17748 17749 17750 17751 17752 17753 17754 17755 17756 17757 17758 17759 17760 17761 17762 17763 17764 17765 17766 17767 17768 17769 17770 17771 17772 17773 17774 17775 17776 17777 17778 17779 17780 17781 17782 17783 17784 17785 17786 17787 17788 17789 17790 17791 17792 17793 17794 17795 17796 17797 17798 17799 17800 17801 17802 17803 17804 17805 17806 17807 17808 17809 17810 17811 17812 17813 17814 17815 17816 17817 17818 17819 17820 17821 17822 17823 17824 17825 17826 17827 17828 17829 17830 17831 17832 17833 17834 17835 17836 17837 17838 17839 17840 17841 17842 17843 17844 17845 17846 17847 17848 17849 17850 17851 17852 17853 17854 17855 17856 17857 17858 17859 17860 17861 17862 17863 17864 17865 17866 17867 17868 17869 17870 17871 17872 17873 17874 17875 17876 17877 17878 17879 17880 17881 17882 17883 17884 17885 17886 17887 17888 17889 17890 17891 17892 17893 17894 17895 17896 17897 17898 17899 17900 17901 17902 17903 17904 17905 17906 17907 17908 17909 17910 17911 17912 17913 17914 17915 17916 17917 17918 17919 17920 17921 17922 17923 17924 17925 17926 17927 17928 17929 17930 17931 17932 17933 17934 17935 17936 17937 17938 17939 17940 17941 17942 17943 17944 17945 17946 17947 17948 17949 17950 17951 17952 17953 17954 17955 17956 17957 17958 17959 17960 17961 17962 17963 17964 17965 17966 17967 17968 17969 17970 17971 17972 17973 17974 17975 17976 17977 17978 17979 17980 17981 17982 17983 17984 17985 17986 17987 17988 17989 17990 17991 17992 17993 17994 17995 17996 17997 17998 17999 18000 18001 18002 18003 18004 18005 18006 18007 18008 18009 18010 18011 18012 18013 18014 18015 18016 18017 18018 18019 18020 18021 18022 18023 18024 18025 18026 18027 18028 18029 18030 18031 18032 18033 18034 18035 18036 18037 18038 18039 18040 18041 18042 18043 18044 18045 18046 18047 18048 18049 18050 18051 18052 18053 18054 18055 18056 18057 18058 18059 18060 18061 18062 18063 18064 18065 18066 18067 18068 18069 18070 18071 18072 18073 18074 18075 18076 18077 18078 18079 18080 18081 18082 18083 18084 18085 18086 18087 18088 18089 18090 18091 18092 18093 18094 18095 18096 18097 18098 18099 18100 18101 18102 18103 18104 18105 18106 18107 18108 18109 18110 18111 18112 18113 18114 18115 18116 18117 18118 18119 18120 18121 18122 18123 18124 18125 18126 18127 18128 18129 18130 18131 18132 18133 18134 18135 18136 18137 18138 18139 18140 18141 18142 18143 18144 18145 18146 18147 18148 18149 18150 18151 18152 18153 18154 18155 18156 18157 18158 18159 18160 18161 18162 18163 18164 18165 18166 18167 18168 18169 18170 18171 18172 18173 18174 18175 18176 18177 18178 18179
# 核心技术

参考文档的这一部分涵盖了对 Spring 框架绝对必要的所有技术。

其中最重要的是 Spring 框架的控制反转容器。在对 Spring 框架的 IOC 容器进行了全面的介绍之后,还对 Spring 的面向方面编程( AOP)技术进行了全面的介绍。 Spring 框架有其自己的 AOP 框架,该框架在概念上易于理解,并且成功地解决了 爪哇 Enterprise 编程中 AOP 需求的 80% 的甜蜜点。

还提供了对 Spring 与 AspectJ 的集成的覆盖(就功能而言,目前是最丰富的——当然也是 爪哇 Enterprise 领域中最成熟的 AOP 实现)。

## 1. IOC 容器

本章介绍 Spring 的控制反转容器。

### 1.1. Spring IOC 容器和 bean 介绍

本章介绍了 Spring 倒置控制原则的框架实现。IoC 也被称为依赖注入。在这个过程中,对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖关系(即它们所使用的其他对象)。然后,容器在创建 Bean 时注入这些依赖项。这个过程从根本上讲是 Bean 本身的逆过程(因此称为控制的逆过程),通过使用类的直接构造或诸如服务定位器模式的机制来控制其依赖关系的实例化或位置。

`org.springframework.beans`和`org.springframework.context`包是 Spring Framework 的 IOC 容器的基础。[`BeanFactory`](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/爪哇doc-API/org/SpringFramework/Beans/Factory/BeanFactory.html)接口提供了一种能够管理任何类型对象的高级配置机制。[`ApplicationContext`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api-context/org/api/aptecontext/aptecontext/aptext/aptext.html)是<gtr="990"/>的子接口。它补充道:

* 与 Spring 的 AOP 特性更容易集成

* 消息资源处理(用于国际化)

* 事件发布

* 应用程序层特定的上下文,例如在 Web 应用程序中使用的`WebApplicationContext`。

简而言之,`BeanFactory`提供了配置框架和基本功能,而`ApplicationContext`增加了更多特定于 Enterprise 的功能。`ApplicationContext`是`BeanFactory`的完整超集,在本章 Spring 的 IOC 容器的描述中专门使用。有关使用`BeanFactory`而不是`ApplicationContext,`的更多信息,请参见[the`BeanFactory`](#beans-beanfactory)。

在 Spring 中,构成应用程序主干并由 Spring IOC 容器管理的对象称为 bean。 Bean 是由 Spring IOC 容器实例化、组装和管理的对象。否则, Bean 只是应用程序中的许多对象之一。bean 和它们之间的依赖关系反映在容器使用的配置元数据中。

### 1.2.集装箱概述

`org.springframework.context.ApplicationContext`接口表示 Spring IOC 容器,并负责实例化、配置和组装 bean。容器通过读取配置元数据获得有关要实例化、配置和组装哪些对象的指令。配置元数据用 XML、爪哇 注释或 爪哇 代码表示。它允许你表达组成应用程序的对象,以及这些对象之间丰富的相互依赖关系。

Spring 提供了`ApplicationContext`接口的几种实现方式。在独立应用程序中,通常创建[`ClassPathXmlApplicationContext`](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/爪哇doc-API/org/SpringFramework/Context/Support/ClassPathxmlApplicationContext.html)或[<`FileSystemXmlApplicationContext`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-org/context/context/supt/SpringsymmlContext.html)的实例。尽管 XML 一直是定义配置元数据的传统格式,但你可以通过提供少量的 XML 配置来指示容器使用 爪哇 注释或代码作为元数据格式,从而声明性地支持这些附加的元数据格式。

在大多数应用程序场景中,不需要显式的用户代码来实例化 Spring IOC 容器的一个或多个实例。例如,在一个 Web 应用程序场景中,应用程序的`web.xml`文件中的一个简单的 8 行样板 Web 描述符 XML 通常就足够了(参见[用于 Web 应用程序的方便的应用程序上下文实例化](#context-create))。如果使用[Spring Tools for Eclipse](https://spring.io/tools)(一种 Eclipse 驱动的开发环境),只需单击几下鼠标或击键,就可以轻松地创建这种样板配置。

下图显示了 Spring 如何工作的高级视图。你的应用程序类与配置元数据相结合,这样,在创建和初始化<br/>之后,你就拥有了一个完全配置和可执行的系统或应用程序。

![容器魔法](images/container-magic.png)

图 1。 Spring IOC 容器

#### 1.2.1.配置元数据

如上图所示, Spring IOC 容器使用一种形式的配置元数据。这个配置元数据表示你作为应用程序开发人员如何告诉 Spring 容器实例化、配置和组装应用程序中的对象。

传统上,配置元数据是以简单直观的 XML 格式提供的,本章的大部分内容都使用这种格式来传达 Spring IoC 容器的关键概念和特性。

|   |基于 XML 的元数据并不是配置元数据的唯一允许的形式。<br/> Spring IOC 容器本身与<br/>配置元数据实际使用的格式完全解耦。如今,许多开发人员为他们的 Spring 应用程序选择[基于 爪哇 的配置](#beans-java)。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

有关在 Spring 容器中使用其他形式的元数据的信息,请参见:

* [基于注释的配置](#beans-annotation-config): Spring 2.5 引入了对基于注释的配置元数据的支持。

* [基于 爪哇 的配置](#beans-java):从 Spring 3.0 开始, Spring 爪哇Config 项目提供的许多特性成为了核心 Spring 框架的一部分。因此,你可以使用 爪哇 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能,请参见[`@Configuration`](https://DOCS. Spring.io/ Spring-framework/current/javadoc-api/org/springframework/context/context/annotation/conventation.html),[`@Bean`(https://DOCS. Spring.io/ Spring.io/ Spring-framework/DOCS/curramework/current/javadoc-api/org/Springframework/context/context/annotation/ Bean.html].html),[<

Spring 配置由容器必须管理的至少一个且通常不止一个定义组成 Bean。基于 XML 的配置元数据将这些 bean 配置为顶级`<beans/>`元素中的`<bean/>`元素。爪哇 配置通常在`@Configuration`类中使用`@Bean`-带注释的方法。

Bean 这些定义对应于构成你的应用程序的实际对象。通常,你定义服务层对象、数据访问对象、表示对象(如 Struts实例)、基础设施对象(如 Hibernate )、JMS,等等。通常,人们不会在容器中配置细粒度的域对象,因为通常是 DAO 和业务逻辑负责创建和加载域对象。但是,你可以使用 Spring 与 AspectJ 的集成来配置在 IOC 容器控制范围之外创建的对象。见[Using AspectJ to dependency-inject domain objects with Spring](#aop-atconfigurable)。

下面的示例展示了基于 XML 的配置元数据的基本结构:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="..."> (1) (2)
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>
```

|**1**|`id`属性是一个字符串,用于标识单独的 Bean 定义。|
|-----|----------------------------------------------------------------------------------------------|
|**2**|`class`属性定义 Bean 的类型,并使用完全限定的<br/>类名。|

`id`属性的值是指协作对象。本例中没有显示用于引用协作对象的 XML。有关更多信息,请参见[依赖关系](#beans-dependencies)。

#### 1.2.2.实例化容器

提供给`ApplicationContext`构造函数的位置路径是资源字符串,这些资源字符串允许容器从各种外部资源加载配置元数据,例如本地文件系统、爪哇`CLASSPATH`,等等。

爪哇

```
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
```

Kotlin

```
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
```

|   |在了解了 Spring 的 IoC 容器之后,你可能想更多地了解 Spring 的`Resource`抽象(如`<map/>`中所述),它提供了一种方便的<br/>机制,用于从 URI 语法中定义的位置读取 InputStream。特别是,`Resource`路径用于构造应用程序上下文,如[应用程序上下文和资源路径](#resources-app-ctx)中所述。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例显示了服务层对象`(services.xml)`配置文件:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>
```

下面的示例显示了数据访问对象`daos.xml`文件:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>
```

在前面的示例中,服务层由`PetStoreServiceImpl`类和`JpaAccountDao`和`@Import`类型的两个数据访问对象组成(基于 JPA 对象关系映射标准)。`property name`元素指的是 爪哇Bean 属性的名称,而`ref`元素指的是另一个 Bean 定义的名称。`id`和`ref`元素之间的这种链接表达了协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参见[依赖关系](#beans-dependencies)。

#####  编写基于 XML 的配置元数据

Bean 定义跨越多个 XML 文件可能是有用的。通常,每个单独的 XML 配置文件都表示体系结构中的逻辑层或模块。

你可以使用应用程序上下文构造函数从所有这些 XML 片段加载 Bean 定义。这个构造函数接受多个`Resource`位置,如[上一节](#beans-factory-instantiation)中所示。或者,使用`<import/>`元素的一个或多个出现来从另一个或多个文件加载 Bean 定义。下面的示例展示了如何做到这一点:

```
<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>
```

在前面的示例中,外部 Bean 定义从三个文件加载:`services.xml`、`messageSource.xml`和`themeSource.xml`。所有的位置路径都是相对于执行导入的定义文件的,因此`services.xml`必须与执行导入的文件位于同一目录或 Classpath 位置,而`messageSource.xml`和`themeSource.xml`必须位于导入文件位置下方的`resources`位置。正如你所看到的,前导斜杠被忽略了。然而,鉴于这些路径是相对的,最好的形式是根本不使用斜杠。根据 Spring 模式,要导入的文件的内容,包括顶层`<beans/>`元素,必须是有效的 XML Bean 定义。

|   |使用<br/>relative“../”路径引用父目录中的文件是可能的,但不推荐。这样做会在当前<br/>应用程序之外的文件上创建一个依赖项。特别是,对于`classpath:`URL(对于[依赖关系](#beans-dependencies)示例,`classpath:../services.xml`),不建议使用该引用,在该示例中,运行时解析过程选择“最近的”根目录,然后查看其父目录。 Classpath <br/>配置的变化可能会导致选择不同的、不正确的目录。<br/>`getBean()`始终可以使用完全限定的资源位置,而不是相对路径:对于<br/>示例,`file:C:/config/services.xml`或`classpath:/config/services.xml`。但是,请注意,你正在将应用程序的配置耦合到特定的绝对位置<br/>。对于这样的绝对<br/>位置,通常最好是保持间接的——例如,通过在运行时针对 JVM<br/>系统属性解析的“${…}”占位符。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

名称空间本身提供了导入指令功能。除了普通的 Bean 定义之外,在 Spring 提供的一系列 XML 命名空间中还可以获得更多的配置特性——例如,`context`和`util`命名空间。

#####  Groovy Bean 定义 DSL

作为外部化配置元数据的另一个示例, Bean 定义也可以在 Spring 的 Groovy Bean 定义 DSL 中表示,正如 Grails 框架中所知的那样。通常,这样的配置存在于一个“.groovy”文件中,其结构如以下示例所示:

```
beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}
```

这种配置风格在很大程度上等同于 XML Bean 定义,甚至支持 Spring 的 XML 配置名称空间。它还允许通过`importBeans`指令导入 XML Bean 定义文件。

#### 1.2.3.使用容器

`ApplicationContext`是高级工厂的接口,该工厂能够维护不同 bean 及其依赖项的注册表。通过使用方法`T getBean(String name, Class<T> requiredType)`,你可以检索 bean 的实例。

`ApplicationContext`允许你读取 Bean 定义并访问它们,如下例所示:

爪哇

```
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();
```

Kotlin

```
import org.springframework.beans.factory.getBean

// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")

// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")

// use configured instance
var userList = service.getUsernameList()
```

在 Groovy 配置下,引导看起来非常相似。它有一个不同的上下文实现类,它是 Groovy 感知的(但也理解 XML Bean 定义)。下面的示例展示了 Groovy 配置:

爪哇

```
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
```

Kotlin

```
val context = GenericGroovyApplicationContext("services.groovy", "daos.groovy")
```

最灵活的变体是结合阅读器委托的`GenericApplicationContext`——例如,对于 XML 文件,使用`XmlBeanDefinitionReader`,如下例所示:

爪哇

```
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
```

Kotlin

```
val context = GenericApplicationContext()
XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml")
context.refresh()
```

对于 Groovy 文件,也可以使用`GroovyBeanDefinitionReader`,如下例所示:

爪哇

```
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
```

Kotlin

```
val context = GenericApplicationContext()
GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy")
context.refresh()
```

你可以在相同的`ApplicationContext`上混合和匹配这样的读取器委托,读取来自不同配置源的 Bean 定义。

然后,你可以使用`<import/>`来检索你的 bean 实例。`ApplicationContext`接口有一些其他方法来检索 bean,但是,理想情况下,应用程序代码不应该使用它们。实际上,你的应用程序代码根本不应该调用`getBean()`方法,因此根本不需要依赖 Spring API。例如, Spring 与 Web Frameworks 的集成为各种 Web Framework 组件(例如控制器和 JSF 管理的 Bean)提供了依赖注入,允许你通过元数据(例如自动连接注释)声明对特定 Bean 的依赖。

### 1.3. Bean 概述

Spring IOC 容器管理一个或多个 bean。这些 bean 是使用你提供给容器的配置元数据创建的(例如,以 XML`<bean/>`定义的形式)。

在容器本身中,这些 Bean 定义被表示为`BeanDefinition`对象,其中包含(除其他信息外)以下元数据:

* 包限定类名称:通常是正在定义的 Bean 的实际实现类。

* Bean 行为配置元素,它说明 Bean 在容器中应该如何表现(作用域、生命周期回调,等等)。

* 引用 Bean 完成其工作所需的其他 bean。这些引用也被称为协作者或依赖项。

* 在新创建的对象中设置的其他配置设置——例如,池的大小限制或在管理连接池的 Bean 中使用的连接数量。

该元数据转换为一组属性,这些属性构成了每个 Bean 定义。下表描述了这些属性:

|        Property        |解释在…|
|------------------------|---------------------------------------------------------------------|
|         Class          |[实例化豆类](#beans-factory-class)|
|          Name          |[命名 bean](#beans-beanname)|
|         Scope          |[Bean Scopes](#beans-factory-scopes)|
| Constructor arguments  |[依赖注入](#beans-factory-collaborators)|
|       Properties       |[依赖注入](#beans-factory-collaborators)|
|    Autowiring mode     |[自动布线合作者](#beans-factory-autowire)|
|Lazy initialization mode|[惰性初始化的 bean](#beans-factory-lazy-init)|
| Initialization method  |[初始化回调](#beans-factory-lifecycle-initializingbean)|
|   Destruction method   |[销毁回调](#beans-factory-lifecycle-disposablebean)|

Bean 定义包含关于如何创建特定 Bean 的信息,此外,`ApplicationContext`实现还允许注册在容器之外(由用户)创建的现有对象。这是通过通过`getBeanFactory()`方法访问应用程序上下文的 BeanFactory 来完成的,该方法返回 BeanFactory`DefaultListableBeanFactory`实现。`DefaultListableBeanFactory`通过`registerSingleton(..)`和`registerBeanDefinition(..)`方法支持此注册。然而,典型的应用程序仅使用通过常规 Bean 定义元数据定义的 bean。

|   |Bean 元数据和手动提供的单例实例需要尽可能早地<br/>进行注册,以便容器在<br/>和其他自检步骤期间对它们进行适当的推理。虽然在一定程度上支持重写现有元数据和现有的<br/>单例实例,但官方不支持在<br/>运行时注册新 bean(与工厂的实时访问同时进行),并且<br/>可能导致并发访问异常、 Bean 容器中的不一致状态或两者兼而有之。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.3.1.命名 bean

每个 Bean 具有一个或多个标识符。在承载 Bean 的容器中,这些标识符必须是唯一的。 Bean 通常只有一个标识符。但是,如果它需要多个,那么额外的一个可以被视为别名。

在基于 XML 的配置元数据中,使用`id`属性、`name`属性或两者来指定 Bean 标识符。`id`属性允许你精确地指定一个 ID。传统上,这些名称是字母数字(“MyBean”、“Someservice”等),但它们也可以包含特殊字符。如果要为 Bean 引入其他别名,还可以在`name`属性中指定它们,并用逗号(`,`)、分号(`;`)或空白分隔。作为一个历史记录,在 Spring 3.1 之前的版本中,`id`属性被定义为`id`类型,该类型限制了可能的字符。在 3.1 中,它被定义为`xsd:string`类型。注意 Bean `id`唯一性仍然由容器强制执行,尽管不再由 XML 解析器执行。

你不需要为 Bean 提供`name`或`id`。如果没有显式地提供`name`或`id`,则容器将为该 Bean 生成唯一的名称。但是,如果你希望通过使用`ApplicationContext`元素或服务定位器样式查找来按名称引用该 Bean,则必须提供一个名称。不提供名称的动机与使用[内豆](#beans-inner-beans)和[自动布线合作者](#beans-factory-autowire)有关。

Bean 命名约定

约定是在命名 bean 时对实例字段名称使用标准 爪哇 约定。也就是说, Bean 名称以小写字母开头,并从那里开始以驼峰式开头。这类名称的例子包括`accountManager`,`accountService`,`userDao`,`loginController`,等等。

始终如一地命名 bean 会使你的配置更易于阅读和理解。此外,如果你使用 Spring  AOP,那么在将建议应用到一组与名称相关的 bean 时,它会有很大帮助。

|   |通过在 Classpath 中扫描组件, Spring 为未命名的<br/>组件生成 Bean 名称,遵循前面描述的规则:本质上,采用简单的类名<br/>,并将其初始字符转换为小写字母。然而,在(不寻常的)特殊<br/>情况下,当有多个字符并且第一个和第二个字符<br/>都是大写时,原始的外壳得到保留。这些规则与<br/>定义的`java.beans.Introspector.decapitalize`( Spring 在此使用)相同。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  在 Bean 定义之外别名 Bean

在 Bean 定义本身中,通过使用由`id`属性指定的最多一个名称和`name`属性中的任意数量的其他名称的组合,可以为 Bean 提供多个名称。 Bean 这些名称可以是相同的别名,并且在某些情况下是有用的,例如通过使用特定于该组件本身的 Bean 名称,让应用程序中的每个组件引用公共依赖项。

然而,在 Bean 实际定义的地方指定所有别名并不总是足够的。有时需要为在别处定义的 Bean 引入别名。在大型系统中,配置通常在每个子系统之间进行分配,每个子系统都有自己的一组对象定义,这种情况很常见。在基于 XML 的配置元数据中,可以使用`<alias/>`元素来实现这一点。下面的示例展示了如何做到这一点:

```
<alias name="fromName" alias="toName"/>
```

在这种情况下, Bean(在相同的容器中)名为的还可以在使用这种别名定义之后,被称为。

例如,子系统 A 的配置元数据可以通过`subsystemA-dataSource`的名称引用数据源。子系统 B 的配置元数据可以以`subsystemB-dataSource`的名称引用数据源。在组成使用这两个子系统的主应用程序时,主应用程序以`myApp-dataSource`的名称引用数据源。要让这三个名称都引用同一个对象,可以向配置元数据添加以下别名定义:

```
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
```

现在,每个组件和主应用程序都可以通过一个唯一的名称来引用数据源,并保证不会与任何其他定义冲突(有效地创建一个名称空间),但是它们引用的是相同的 Bean。

爪哇 配置

如果使用 爪哇Configuration,可以使用`@Bean`注释来提供别名。有关详细信息,请参见[使用`@Bean`注释](#beans-java- Bean-注释)。

#### 1.3.2.实例化豆类

Bean 定义本质上是用于创建一个或多个对象的配方。容器在被请求时查看命名 Bean 的配方,并使用由该 Bean 定义封装的配置元数据来创建(或获取)实际对象。

如果使用基于 XML 的配置元数据,则可以在`<bean/>`元素的`class`属性中指定要实例化的对象类型(或类)。这个`class`属性(在内部,它是`Class`实例上的`Class`属性)通常是强制的。(有关异常,请参见[通过使用实例工厂方法实现实例化](#beans-factory-class-instance-factory-method)和[Bean Definition Inheritance](#beans-child-bean-definitions)。)你可以通过以下两种方式之一使用`Class`属性:

* 通常,在容器本身通过反射地调用其构造函数直接创建 Bean 的情况下,指定要构造的 Bean 类,这在某种程度上相当于使用`new`操作符的 爪哇 代码。

* 要指定包含用于创建对象的`static`工厂方法的实际类,在容器在类上调用`static`工厂方法以创建 Bean 的较不常见的情况下。调用`static`工厂方法返回的对象类型可以是同一个类,也可以完全是另一个类。

嵌套类名

如果你希望为嵌套类配置 Bean 定义,那么你可以使用嵌套类的二进制名称或源名。

例如,如果在`com.example`包中有一个名为`SomeThing`的类,而这个`SomeThing`类有一个名为`static`的嵌套类,它们可以用美元符号(`$`)或圆点(`.`)分隔。因此,在 Bean 定义中,`class`属性的值将是`com.example.SomeThing$OtherThing`或`com.example.SomeThing.OtherThing`。

#####  使用构造函数实例化

当通过构造函数方法创建 Bean 时,所有普通类都可由 Spring 使用并与 Spring 兼容。也就是说,正在开发的类不需要实现任何特定的接口,也不需要以特定的方式进行编码。只需指定 Bean 类就足够了。然而,根据你为该特定 Bean 使用的 IOC 类型,你可能需要一个默认(空)构造函数。

Spring IOC 容器实际上可以管理你希望它管理的任何类。它不仅限于管理真正的 爪哇Beans。 Spring 大多数用户更喜欢实际的 爪哇Bean,它只有一个默认的(无参数的)构造函数,以及按照容器中的属性建模的适当的 setter 和 getter。你还可以在容器中有更多奇异的非 Bean 样式类。例如,如果你需要使用一个绝对不遵守 爪哇Bean 规范的遗留连接池, Spring 也可以对其进行管理。

使用基于 XML 的配置元数据,你可以按以下方式指定你的 Bean 类:

```
<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
```

有关向构造函数提供参数(如果需要)和在构造对象后设置对象实例属性的机制的详细信息,请参见[注入依赖项](#beans-factory-collaborators)。

#####  使用静态工厂方法的实例化

在定义使用静态工厂方法创建的 Bean 时,使用`class`属性指定包含`static`工厂方法和一个名为`factory-method`的属性的类,以指定工厂方法本身的名称。你应该能够调用这个方法(使用可选参数,如后面所述)并返回一个活动对象,随后将其视为通过构造函数创建的对象。 Bean 这样的定义的一种用途是在遗留代码中调用`static`工厂。

下面的 Bean 定义指定通过调用工厂方法来创建 Bean。该定义没有指定返回对象的类型(类),只指定了包含工厂方法的类。在本例中,`createInstance()`方法必须是静态方法。下面的示例展示了如何指定工厂方法:

```
<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
```

下面的示例显示了一个将与前面的 Bean 定义一起工作的类:

爪哇

```
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}
```

Kotlin

```
class ClientService private constructor() {
    companion object {
        private val clientService = ClientService()
        fun createInstance() = clientService
    }
}
```

有关向工厂方法提供(可选的)参数并在从工厂返回对象后设置对象实例属性的机制的详细信息,请参见[详细介绍依赖关系和配置](#beans-factory-properties-detailed)。

#####  通过使用实例工厂方法实现实例化

与通过[静态工厂法](#beans-factory-class-static-factory-method)的实例化类似,使用实例工厂方法的实例化从容器调用现有 Bean 的非静态方法来创建新的 Bean。要使用此机制,将`class`属性保留为空,并在`factory-bean`属性中,指定当前(或父容器或祖先容器)中的 Bean 的名称,该容器包含将被调用以创建对象的实例方法。使用`factory-method`属性设置工厂方法本身的名称。下面的示例显示了如何配置这样的 Bean:

```
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
```

下面的示例展示了相应的类:

爪哇

```
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}
```

Kotlin

```
class DefaultServiceLocator {
    companion object {
        private val clientService = ClientServiceImpl()
    }
    fun createClientServiceInstance(): ClientService {
        return clientService
    }
}
```

一个工厂类也可以包含多个工厂方法,如下例所示:

```
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>
```

下面的示例展示了相应的类:

爪哇

```
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}
```

Kotlin

```
class DefaultServiceLocator {
    companion object {
        private val clientService = ClientServiceImpl()
        private val accountService = AccountServiceImpl()
    }

    fun createClientServiceInstance(): ClientService {
        return clientService
    }

    fun createAccountServiceInstance(): AccountService {
        return accountService
    }
}
```

这种方法表明, Bean 工厂本身可以通过依赖注入进行管理和配置。见[详细介绍依赖关系和配置](#beans-factory-properties-detailed)。

|   |在 Spring 文档中,“factory Bean”是指在<br/> Spring 容器中配置并通过[instance](#beans-factory-class-instance-factory-method)或[static](#beans-factory-class-static-factory-method)方法创建对象的 Bean 工厂。相比之下,`FactoryBean`(请注意大写)是指特定于 Spring 的[`FactoryBean`]实现类。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  确定 Bean 的运行时类型

Bean 特定的运行时类型是不可平凡地确定的。 Bean 元数据定义中的指定类只是一个初始类引用,可能与声明的工厂方法结合在一起,或者是`FactoryBean`类,这可能导致 Bean 的不同运行时类型,或者在实例级工厂方法的情况下根本没有设置(而是通过指定的`factory-bean`名称进行解析)。此外, AOP 代理可以将 Bean 实例与基于接口的代理包装在一起,而目标 Bean 的实际类型的暴露有限(仅包括其实现的接口)。

查找特定 Bean 的实际运行时类型的推荐方法是对指定的 Bean 名称进行`BeanFactory.getType`调用。这将考虑上述所有情况,并返回`BeanFactory.getBean`调用将为相同的 Bean 名称返回的对象类型。

### 1.4.依赖关系

典型的 Enterprise 应用程序不包括单个对象(或 Spring 所说的 Bean)。即使是最简单的应用程序也有几个对象一起工作,以呈现最终用户认为是一致的应用程序。下一节解释了如何从定义大量独立的 Bean 定义到一个完全实现的应用程序,在该应用程序中,对象协作以实现目标。

#### 1.4.1.依赖注入

依赖注入是一个过程,在这个过程中,对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖关系(即它们所使用的其他对象)。然后,容器在创建 Bean 时注入这些依赖项。这个过程从根本上讲是 Bean 本身的逆过程(因此称为控制的逆过程),通过使用类的直接构造或服务定位器模式来控制其依赖项的实例化或位置。

使用 DI 原则的代码更干净,当对象提供了它们的依赖关系时,解耦更有效。对象不会查找其依赖项,也不知道依赖项的位置或类。结果,类变得更容易测试,特别是当依赖于接口或抽象基类时,这允许在单元测试中使用存根或模拟实现。

DI 有两个主要的变体:[基于构造函数的依赖注入](#beans-constructor-injection)和[基于 setter 的依赖注入](#beans-setter-injection)。

#####  基于构造函数的依赖注入

基于构造函数的 DI 是由容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。调用带有特定参数的`static`工厂方法来构造 Bean 几乎是等效的,并且此讨论将参数处理为构造函数和[基于 setter 的依赖注入](#beans-setter-injection)工厂方法。下面的示例展示了一个只能通过构造函数注入进行依赖注入的类:

爪哇

```
public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private final MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}
```

Kotlin

```
// a constructor so that the Spring container can inject a MovieFinder
class SimpleMovieLister(private val movieFinder: MovieFinder) {
    // business logic that actually uses the injected MovieFinder is omitted...
}
```

请注意,这门课没有什么特别之处。它是一个 POJO,不依赖于特定于容器的接口、基类或注释。

###### 构造函数参数解析

构造函数的参数解析匹配是通过使用参数的类型来实现的。如果在 Bean 定义的构造函数参数中不存在潜在的歧义,则在 Bean 定义中定义构造函数参数的顺序是在 Bean 被实例化时将这些参数提供给适当的构造函数的顺序。考虑以下类:

爪哇

```
package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}
```

Kotlin

```
package x.y

class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)
```

假设`ThingTwo`和`ThingThree`类之间没有继承关系,则不存在潜在的歧义。因此,下面的配置工作正常,并且你不需要在[基于 setter 的依赖注入](#beans-setter-injection)元素中显式地指定构造函数参数索引或类型。

```
<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>
```

当引用另一个 Bean 时,类型是已知的,并且可以发生匹配(与前面的示例一样)。当使用简单的类型时,例如`<value>true</value>`, Spring 无法确定该值的类型,因此在没有帮助的情况下无法按类型进行匹配。考虑以下类:

爪哇

```
package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
```

Kotlin

```
package examples

class ExampleBean(
    private val years: Int, // Number of years to calculate the Ultimate Answer
    private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything
)
```

[]()构造函数参数类型匹配

在前面的场景中,如果你使用[基于 setter 的依赖注入](#beans-setter-injection)属性显式地指定构造函数参数的类型,则容器可以使用与简单类型匹配的类型,如下例所示:

```
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
```

[]()构造函数参数索引

可以使用`index`属性显式指定构造函数参数的索引,如下例所示:

```
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>
```

除了解决多个简单值的歧义之外,在构造函数具有两个相同类型的参数的情况下,指定索引还可以解决歧义。

|   |该索引是以 0 为基础的。|
|---|---------------------|

[]()构造函数参数 Name

还可以使用构造函数参数名称来消歧,如下例所示:

```
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>
```

请记住,要使这项工作开箱即用,你的代码必须使用启用的调试标志进行编译,以便 Spring 可以从构造函数中查找参数名称。如果不能或不想使用 Debug 标志编译代码,可以使用[@constructorProperties](https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.html)JDK 注释显式地命名构造函数参数。然后,示例类必须如下所示:

Java

```
package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
```

Kotlin

```
package examples

class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)
```

#####  基于 setter 的依赖注入

在调用一个无参数构造函数或一个无参数`static`工厂方法来实例化你的 Bean 之后,容器调用 bean 上的 setter 方法来完成基于 setter 的 DI。

下面的示例展示了一个只能通过使用纯 setter 注入进行依赖注入的类。这个类是传统的 Java。它是一个 POJO,不依赖于特定于容器的接口、基类或注释。

Java

```
public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}
```

Kotlin

```
class SimpleMovieLister {

    // a late-initialized property so that the Spring container can inject a MovieFinder
    lateinit var movieFinder: MovieFinder

    // business logic that actually uses the injected MovieFinder is omitted...
}
```

`ApplicationContext`为其管理的 bean 支持基于构造函数和基于 setter 的 DI。在通过构造函数方法注入了一些依赖项之后,它还支持基于 setter 的 DI。你以`BeanDefinition`的形式配置依赖项,该依赖项与`PropertyEditor`实例一起使用,以将属性从一种格式转换为另一种格式。然而,大多数 Spring 用户并不直接使用这些类(即编程),而是使用 XML`bean`定义、带注释的组件(即用`bean`、`@Controller`等注释的类),或基于 Java 的`@Bean`类中的`@Bean`方法。然后在内部将这些源转换为`BeanDefinition`的实例,并用于加载整个 Spring IoC 容器实例。

基于构造器还是基于设置器的 DI?

由于可以混合使用基于构造函数和基于 setter 的 DI,因此使用构造函数来实现强制依赖项,使用 setter 方法或配置方法来实现可选依赖项是一个很好的经验法则。请注意,在 setter 方法上使用[@Required](#beans-required-annotation)注释可以使该属性成为所需的依赖项;但是,最好使用带参数的编程验证的构造函数注入。

Spring 团队通常提倡构造函数注入,因为它允许将应用程序组件实现为不可变对象,并确保所需的依赖关系不是`null`。此外,构造函数注入的组件总是以完全初始化的状态返回给客户机(调用)代码。作为附带说明,大量的构造函数参数是一种糟糕的代码气味,这意味着类可能有太多的责任,应该进行重构以更好地解决关注的适当分离。

Setter 注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,在代码使用依赖项的所有地方都必须执行 not-null 检查。setter 注入的一个好处是,setter 方法使该类的对象可以在以后进行重新配置或重新注入。因此,通过[JMX MBeans](integration.html#jmx)进行管理是 Setter 注入的一个引人注目的用例。

使用对特定类最有意义的 DI 样式。有时,在处理你没有源代码的第三方类时,你需要做出选择。例如,如果第三方类不公开任何 setter 方法,那么构造函数注入可能是唯一可用的 DI 形式。

#####  依赖关系解决过程

容器执行 Bean 依赖项解析,如下所示:

* 使用描述所有 bean 的配置元数据创建和初始化`ApplicationContext`。配置元数据可以通过 XML、Java 代码或注释来指定。

* 对于每个 Bean,其依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示(如果你使用它而不是普通的构造函数)。这些依赖关系被提供给 Bean,当 Bean 实际被创建时。

* Bean 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个值的引用。

* 作为值的每个属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下, Spring 可以将以字符串格式提供的值转换为所有内置类型,例如`int`,`long`,`String`,`boolean`,等等。

Spring 容器在创建容器时验证每个 Bean 的配置。然而,在实际创建 Bean 之前不设置 Bean 属性本身。当创建容器时,将创建单实例范围并设置为预实例化(默认)的 bean。作用域在`BeanDefinition`中定义。否则, Bean 仅在被请求时才被创建。 Bean 的创建可能会导致创建 bean 的图形,因为 Bean 的依赖项及其依赖项的依赖项(等等)被创建和分配。请注意,这些依赖项之间的分辨率不匹配可能会出现在较晚的时候——也就是说,在第一次创建受影响的 Bean 时。

循环依赖

如果主要使用构造函数注入,则有可能创建一个不可解析的循环依赖场景。

例如:类 A 通过构造函数注入需要类 B 的实例,而类 B 通过构造函数注入需要类 A 的实例。如果将 bean 配置为类 A 和类 B 相互注入, Spring IOC 容器在运行时检测到此循环引用,并抛出`BeanCurrentlyInCreationException`。

一种可能的解决方案是编辑一些类的源代码,由 setter 而不是构造函数来配置。或者,避免构造函数注入,而只使用 setter 注入。换句话说,尽管不推荐使用它,但你可以使用 setter 注入配置循环依赖项。

与典型的情况(没有循环依赖关系)不同, Bean a 和 Bean b 之间的循环依赖关系迫使其中一个 bean 在完全初始化自身之前被注入到另一个 bean 中(典型的先有鸡后有蛋的场景)。

你通常可以相信 Spring 会做正确的事。它在容器加载时检测配置问题,例如对不存在的 bean 的引用和循环依赖关系。 Spring 设置属性并尽可能晚地解决依赖关系,当 Bean 实际被创建时。这意味着,已经正确加载的 Spring 容器以后可以在你请求对象时生成异常,如果在创建该对象或其依赖关系中存在问题的话——例如, Bean 抛出异常是由于缺少或无效的属性造成的。这可能延迟了某些配置问题的可见性,这就是`ApplicationContext`实现默认预实例化单例 bean 的原因。在实际需要这些 bean 之前创建这些 bean 需要一些前期时间和内存,但在创建`ApplicationContext`时(而不是以后),你会发现配置问题。你仍然可以重写此缺省行为,以便 Singleton Bean 可以懒洋洋地初始化,而不是急切地预先实例化。

如果不存在循环依赖关系,则当一个或多个协作 bean 被注入到依赖的 Bean 中时,每个协作的 Bean 在被注入到依赖的 Bean 中之前被完全配置。这意味着,如果 Bean a 对 Bean b 具有依赖性,则 Spring IOC 容器在 Bean a 上调用 setter 方法之前完全配置 Bean b。换句话说, Bean 被实例化(如果它不是预实例化的单例),它的依赖项被设置,并且相关的生命周期方法(例如[已配置的 init 方法](#beans-factory-lifecycle-initializingbean)或[初始化 bean 回调方法](#beans-factory-lifecycle-initializingbean))被调用。

#####  依赖注入示例

下面的示例为基于 setter 的 DI 使用基于 XML 的配置元数据。 Spring XML 配置文件的一小部分指定了如下一些 Bean 定义:

```
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
```

下面的示例显示了相应的`ExampleBean`类:

Java

```
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}
```

Kotlin

```
class ExampleBean {
    lateinit var beanOne: AnotherBean
    lateinit var beanTwo: YetAnotherBean
    var i: Int = 0
}
```

在前面的示例中,setter 被声明为与 XML 文件中指定的属性匹配。下面的示例使用基于构造函数的 DI:

```
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
```

下面的示例显示了相应的`ExampleBean`类:

Java

```
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}
```

Kotlin

```
class ExampleBean(
        private val beanOne: AnotherBean,
        private val beanTwo: YetAnotherBean,
        private val i: Int)
```

Bean 定义中指定的构造函数参数被用作`ExampleBean`构造函数的参数。

现在考虑这个示例的一个变体,其中, Spring 被告知调用`static`工厂方法来返回对象的实例,而不是使用构造函数:

```
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
```

下面的示例显示了相应的`ExampleBean`类:

Java

```
public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}
```

Kotlin

```
class ExampleBean private constructor() {
    companion object {
        // a static factory method; the arguments to this method can be
        // considered the dependencies of the bean that is returned,
        // regardless of how those arguments are actually used.
        fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean {
            val eb = ExampleBean (...)
            // some other operations...
            return eb
        }
    }
}
```

`<constructor-arg/>`工厂方法的参数由`<constructor-arg/>`元素提供,这与实际使用构造函数的情况完全相同。工厂方法返回的类的类型不必与包含`static`工厂方法的类的类型相同(尽管在本例中是这样)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用`factory-bean`属性而不是`class`属性),因此我们在这里不讨论这些细节。

#### 1.4.2.详细介绍依赖关系和配置

正如[上一节](#beans-factory-collaborators)中提到的,你可以将 Bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用,或者作为内联定义的值。 Spring 的基于 XML 的配置元数据为此支持其`<property/>`和`<constructor-arg/>`元素中的子元素类型。

#####  直值(原语、字符串等)

`<property/>`元素的`value`属性将一个属性或构造函数参数指定为人类可读的字符串表示形式。 Spring 的[转换服务](#core-convert-ConversionService-API)用于将这些值从`String`转换为属性或参数的实际类型。下面的示例显示了正在设置的各种值:

```
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>
```

下面的示例使用[P-命名空间](#beans-p-namespace)实现更简洁的 XML 配置:

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

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="misterkaoli"/>

</beans>
```

前面的 XML 更简洁。但是,在运行时而不是在设计时发现打字错误,除非你在创建 Bean 定义时使用支持自动属性完成的 IDE(例如[Intellij 思想](https://www.jetbrains.com/idea/)或[Spring Tools for Eclipse](https://spring.io/tools))。强烈推荐这种 IDE 协助。

你还可以配置`java.util.Properties`实例,如下所示:

```
<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>
```

Spring 容器通过使用 JavaBeans`PropertyEditor`机制,将`<value/>`元素内的文本转换为`Class`实例。这是一个很好的快捷方式,并且是 Spring 团队确实支持使用嵌套`<value/>`元素而不是`value`属性样式的少数几个地方之一。

###### `idref`元素

`idref`元素只是一种防错误的方式,可以将容器中另一个 Bean 的`id`(一个字符串值-不是引用)传递给`<constructor-arg/>`或`<property/>`元素。下面的示例展示了如何使用它:

```
<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>
```

Bean 前面的定义片段(在运行时)与下面的片段完全等效:

```
<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>
```

第一种形式比第二种形式更好,因为使用`idref`标记可以让容器在部署时验证所引用的名为 Bean 的容器实际存在。在第二个变化中,不对传递给`client` Bean 的`targetName`属性的值执行验证。只有当`client` Bean 被实际实例化时,才会发现错别字(最有可能导致致命的结果)。如果`client` Bean 是[prototype](#beans-factory-scopes) Bean,则只有在部署容器很长时间后才能发现该错别字和由此产生的异常。

|   |在 4.0bean<br/>XSD 中,`local`元素上的`local`属性不再受支持,因为它不再为常规的`bean`引用提供值。在升级到 4.0 模式时,将你现有的<br/>引用更改为`idref bean`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在`ProxyFactoryBean` Bean 定义中,`<idref/>`元素在[AOP interceptors](#aop-pfb-1)的配置中带来值的一个常见位置(至少在版本早于 Spring 2.0 的版本中)。在指定拦截器名称时使用`<idref/>`元素可以防止错误拼写拦截器 ID。

#####  对其他 bean 的引用(协作者)

`ref`元素是`<constructor-arg/>`或`<property/>`定义元素中的最后一个元素。在这里,你将 Bean 的指定属性的值设置为对由容器管理的另一个 Bean(协作者)的引用。引用的 Bean 是要设置其属性的 Bean 的依赖项,并且在设置该属性之前根据需要对其进行初始化。(如果协作者是单例 Bean,它可能已经被容器初始化了。)所有引用最终都是对另一个对象的引用。范围和验证取决于你是通过`bean`还是`parent`属性指定另一个对象的 ID 或名称。

通过`<ref/>`标记的`bean`属性指定目标 Bean 是最通用的形式,并且允许在相同的容器或父容器中创建对任何 Bean 的引用,无论它是否在相同的 XML 文件中。`bean`属性的值可以与目标 Bean 的`id`属性相同,或者与目标 Bean 的`name`属性中的一个值相同。下面的示例展示了如何使用`ref`元素:

```
<ref bean="someBean"/>
```

通过`parent`属性指定目标 Bean,将创建对当前容器的父容器中的 Bean 的引用。`parent`属性的值可以与目标 Bean 的`id`属性或目标 Bean 的`id`属性中的一个值相同。目标 Bean 必须位于当前容器的父容器中。当你拥有容器的层次结构并且希望用与父 Bean 具有相同名称的代理来包装父容器中的现有 Bean 时,你应该主要使用这个 Bean 引用变体。下面的一对清单展示了如何使用`parent`属性:

```
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required here -->
</bean>
```

```
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>
```

|   |在 4.0bean<br/>XSD 中,`local`元素上的`local`属性不再受支持,因为它不再为常规的`bean`引用提供值。在升级到 4.0 模式时,将你现有的<br/>引用更改为`ref bean`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  内豆

在`<property/>`或`<constructor-arg/>`元素内部的`<bean/>`元素定义了一个内部 Bean,如下例所示:

```
<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>
```

Bean 内部定义不需要定义的 ID 或名称。如果指定,则容器不使用这样的值作为标识符。容器还在创建时忽略`Properties`标志,因为内部 bean 总是匿名的,并且总是与外部 bean 一起创建 Bean。 Bean 不可能独立地访问内部 bean 或将它们注入到协作 bean 中,而不是插入到封闭的 bean 中。

作为一个角的情况,可以从自定义范围接收销毁回调——例如,对于包含在单例 Bean 中的请求范围的内部 Bean。内部 Bean 实例的创建与其包含的 Bean 绑定在一起,但是销毁回调允许它参与请求作用域的生命周期。这种情况并不常见。内部 bean 通常只共享其包含 Bean 的范围。

#####  收藏

`<list/>`、`<set/>`、`<map/>`和`<props/>`元素分别设置 Java`Collection`类型`List`、`Set`、`Map`和`Properties`的属性和参数。下面的示例展示了如何使用它们:

```
<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">[email protected]</prop>
            <prop key="support">[email protected]</prop>
            <prop key="development">[email protected]</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>
```

映射键或值或设定值的值也可以是以下任何元素:

```
bean | ref | idref | list | set | map | props | value | null
```

###### 集合合并

Spring 容器还支持合并集合。应用程序开发人员可以定义父`<list/>`、`<map/>`、`<set/>`或`<props/>`元素,并具有子元素`<list/>`、`<map/>`、`<set/>`或`<props/>`元素来继承和重写来自父集合的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,而子集合的元素重写了父集合中指定的值。

Bean 关于合并的这一节讨论了父-子机制。 Bean 不熟悉父和子定义的读者在继续之前可能希望阅读[相关部分](#beans-child-bean-definitions)。

下面的示例演示集合合并:

```
<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">[email protected]</prop>
                <prop key="support">[email protected]</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">[email protected]</prop>
                <prop key="support">[email protected]</prop>
            </props>
        </property>
    </bean>
<beans>
```

注意在`child` Bean 定义的`adminEmails`属性的`<props/>`元素上使用`merge=true`属性。当容器解析并实例化`child` Bean 时,生成的实例具有一个`adminEmails``Properties`集合,该集合包含将子集合的`adminEmails`与父集合的`<constructor-arg/>`合并的结果。下面的列表显示了结果:

```
[email protected]
[email protected]
[email protected]
```

子集`Properties`集合的值集继承了父集`<props/>`的所有属性元素,而子集`support`值的值覆盖了父集合中的值。

这种合并行为类似于`<list/>`、`<map/>`和`<set/>`集合类型。在`<list/>`元素的特定情况下,维护与`List`集合类型(即`ordered`值集合的概念)相关联的语义。父级的值在所有子列表的值之前。在`Map`、`Set`和`Properties`集合类型的情况下,不存在排序。因此,对于容器内部使用的关联`Map`、`Set`和`Properties`实现类型的集合类型,没有任何排序语义。

###### 集合合并的局限性

你不能合并不同的集合类型(例如`Map`和`List`)。如果你尝试这样做,将抛出一个适当的`Exception`。`merge`属性必须在较低的、继承的子定义上指定。在父集合定义上指定`merge`属性是多余的,并且不会导致所需的合并。

###### 强类型集合

通过在 Java5 中引入泛型类型,你可以使用强类型集合。也就是说,可以声明`Collection`类型,使得它只能包含(例如)`String`元素。如果使用 Spring 来将强类型的`Collection`注入到 Bean 中,则可以利用 Spring 的类型转换支持,使得强类型的`Collection`实例的元素在被添加到`Collection`之前被转换为适当的类型。下面的 Java 类和 Bean 定义展示了如何做到这一点:

Java

```
public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
```

Kotlin

```
class SomeClass {
    lateinit var accounts: Map<String, Float>
}
```

```
<beans>
    <bean id="something" class="x.y.SomeClass">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>
```

当`accounts`的`something`的属性被准备注入时,关于强类型的元素类型的泛型信息`Map<String, Float>`是通过反射获得的。因此, Spring 的类型转换基础结构将各种值元素识别为类型`Float`,并且将字符串值(`9.99`、`2.75`和`3.99`)转换为实际的`Float`类型。

#####  空字符串符值和空字符串符值

Spring 将属性等的空参数视为空`Strings`。以下基于 XML 的配置元数据片段将`email`属性设置为空的`String`值。

```
<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>
```

前面的示例相当于下面的 Java 代码:

Java

```
exampleBean.setEmail("");
```

Kotlin

```
exampleBean.email = ""
```

`<null/>`元素处理`null`值。下面的清单展示了一个示例:

```
<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>
```

前面的配置相当于下面的 Java 代码:

Java

```
exampleBean.setEmail(null);
```

Kotlin

```
exampleBean.email = null
```

#####  带有 P-namespace 的 XML 快捷方式

P-Namespace 允许你使用`bean`元素的属性(而不是嵌套的`<property/>`元素)来描述你的属性值协作 bean,或者两者兼而有之。

Spring 支持可扩展的配置格式[带有名称空间](#xsd-schemas),其基于 XML 模式定义。本章讨论的`beans`配置格式是在 XML 模式文档中定义的。然而,P-命名空间不是在 XSD 文件中定义的,并且仅存在于 Spring 的核心中。

下面的示例展示了两个解析为相同结果的 XML 片段(第一个使用标准 XML 格式,第二个使用 P 名称空间):

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

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="[email protected]"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="[email protected]"/>
</beans>
```

该示例在 Bean 定义中显示了 P-命名空间中的一个名为`email`的属性。这告诉 Spring 要包括一个属性声明。如前所述,P-Namespace 没有模式定义,因此你可以将属性的名称设置为属性名称。

下一个示例包括另外两个 Bean 定义,这两个定义都具有对另一个 Bean 的引用:

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

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>
```

这个示例不仅包括使用 P 名称空间的属性值,还使用一种特殊格式声明属性引用。鉴于第一个 Bean 定义使用`<property name="spouse" ref="jane"/>`来创建从 Bean `john`到 Bean `jane`的引用,第二个 Bean 定义使用`p:spouse-ref="jane"`作为属性来做完全相同的事情。在这种情况下,`spouse`是属性名,而`-ref`部分表明这不是一个直值,而是对另一个值的引用 Bean。

|   |P-namespace 不像标准 XML 格式那样灵活。例如,用于声明属性引用的<br/>格式与以`Ref`结尾的属性冲突,而<br/>标准 XML 格式则不冲突。我们建议你谨慎地选择你的方法,并将<br/>与你的团队成员进行沟通,以避免生成同时使用所有<br/>三种方法的 XML 文档。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  带有 C-Namespace 的 XML 快捷方式

与[带有 P-namespace 的 XML 快捷方式](#beans-p-namespace)类似, Spring 3.1 中引入的 C-namespace 允许内联属性用于配置构造函数参数,而不是嵌套`constructor-arg`元素。

下面的示例使用`c:`名称空间来执行与 from[基于构造函数的依赖注入](#beans-constructor-injection)相同的操作:

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

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="[email protected]"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="[email protected]"/>

</beans>
```

`c:`名称空间使用与`p:`1( Bean 引用的尾随`-ref`)相同的约定来根据构造函数参数的名称设置它们。类似地,它需要在 XML 文件中声明,即使它不是在 XSD 模式中定义的(它存在于 Spring 内核中)。

对于构造函数参数名称不可用的罕见情况(通常如果字节码是在没有调试信息的情况下编译的),可以使用回退到参数索引,如下所示:

```
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="[email protected]"/>
```

|   |由于 XML 语法的原因,由于 XML 属性名称不能以数字开头(即使某些 IDE 允许),因此索引符号需要存在前导数`_`,<br/>。<br/>也可以使用相应的索引符号。对于`<constructor-arg>`元素但<br/>不常用,因为声明的普通顺序通常在那里就足够了。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在实践中,构造函数分辨率[mechanism](#beans-factory-ctor-arguments-resolution)在匹配参数方面非常有效,因此,除非你确实需要,否则我们建议在整个配置中使用名称符号。

#####  复合属性名称

在设置 Bean 属性时,可以使用复合或嵌套的属性名称,只要路径的所有组件(除了最终的属性名称)不是`null`。考虑以下 Bean 定义:

```
<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>
```

`something` Bean 有一个`fred`属性,它有一个`bob`属性,它有一个`sammy`属性,最后的`sammy`属性被设置为一个值`123`。为了使此工作,`fred`的`something`属性和`bob`的`bob`属性在构造 Bean 之后一定不能是`null`。否则,将抛出`NullPointerException`。

#### 1.4.3.使用`depends-on`

如果一个 Bean 是另一个 Bean 的依赖项,这通常意味着一个 Bean 被设置为另一个 Bean 的属性。通常,你使用基于 XML 的配置元数据中的[`<ref/>`元素]来实现这一点。然而,有时 bean 之间的依赖关系并不那么直接。一个例子是当需要触发类中的静态初始化器时,例如用于数据库驱动程序注册。`depends-on`属性可以显式地强制一个或多个 bean 在使用该元素初始化 Bean 之前被初始化。下面的示例使用`depends-on`属性表示对单个 Bean 的依赖关系:

```
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
```

要表示对多个 bean 的依赖关系,请提供一个 Bean 名称列表,作为`depends-on`属性的值(逗号、空格和分号是有效的分隔符):

```
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
```

|   |`depends-on`属性既可以指定一个初始化-时间依赖项,也可以指定<br/>在[singleton](#beans-factory-scopes-singleton)beans 的情况下,对应的<br/>销毁-时间依赖项。定义了`depends-on`与给定 Bean 的关系<br/>的依赖 bean 首先被销毁,在给定 Bean 本身被销毁之前。<br/>因此,`depends-on`也可以控制关闭命令。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.4.4.惰性初始化的 bean

默认情况下,`ApplicationContext`实现急切地创建和配置所有[singleton](#beans-factory-scopes-singleton)bean,作为初始化过程的一部分。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是在几个小时甚至几天之后。当这种行为不可取时,可以通过将 Bean 定义标记为惰性初始化来防止单例 Bean 的预实例化。惰性初始化的 Bean 告诉 IoC 容器在首次请求时而不是在启动时创建 Bean 实例。

在 XML 中,这种行为由`<bean/>`元素上的`lazy-init`属性控制,如下例所示:

```
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
```

当前面的配置被`ApplicationContext`消耗时,`lazy` Bean 不会在`ApplicationContext`启动时急切地预先实例化,而`not.lazy` Bean 则急切地预先实例化。

然而,当惰性初始化的 Bean 是未惰性初始化的单例 Bean 的依赖项时,`ApplicationContext`在启动时创建惰性初始化的 Bean,因为它必须满足单例的依赖项。将惰性初始化的 Bean 注入到其他未惰性初始化的单例 Bean 中。

你还可以使用`<beans/>`元素上的`default-lazy-init`属性来控制容器级别的延迟初始化,如下例所示:

```
<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>
```

#### 1.4.5.自动布线合作者

Spring 容器可以自动连接协作 bean 之间的关系。你可以通过检查`ApplicationContext`的内容,让 Spring 为你的 Bean 自动解析协作者(其他 bean)。自动布线有以下优点:

* 自动布线可以大大减少指定属性或构造函数参数的需要。( Bean 模板[在本章的其他地方讨论过](#beans-child-bean-definitions)等其他机制在这方面也很有价值。)

* 随着对象的发展,自动布线可以更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而无需修改配置。因此,在开发过程中,自动布线特别有用,而无需否定当代码库变得更稳定时切换到显式布线的选项。

当使用基于 XML 的配置元数据(参见[依赖注入](#beans-factory-collaborators))时,可以使用`<bean/>`元素的`autowire`属性为 Bean 定义指定 AutoWire 模式。自动接线功能有四种模式。你可以根据 Bean 指定自动接线,因此可以选择要自动接线的接线方式。下表描述了四种自动布线模式:

|    Mode     |解释|
|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|    `no`     |(默认)没有自动接线。 Bean 引用必须由`ref`元素定义。对于较大的部署,不建议更改<br/>缺省设置,因为显式地指定<br/>协作者可获得更大的控制和清晰度。在某种程度上,它<br/>记录了一个系统的结构。|
|  `byName`   |按属性名称自动接线。 Spring 寻找与<br/>属性同名的需要自动连线的 Bean。例如,如果一个 Bean 定义通过名称设置为<br/>AutoWire,并且它包含一个`master`属性(即,它具有一个`setMaster(..)`方法), Spring 查找一个名为`master`的 Bean 定义,并使用<br/>它来设置该属性。|
|  `byType`   |如果在<br/>容器中正好存在一个属性类型 Bean,则让属性自动连线。如果存在多个异常,将抛出一个致命的异常,这表明<br/>你可能不会使用`byType`自动布线。如果没有匹配的<br/>bean,则不会发生任何事情(未设置该属性)。|
|`constructor`|类似于`byType`,但适用于构造函数参数。如果容器中没有一个 Bean 构造函数参数类型的<br/>,就会产生一个致命的错误。|

使用`byType`或`constructor`自动布线模式,可以连接数组和类型化集合。在这种情况下,将提供容器中匹配预期类型的所有 AutoWire 候选项,以满足依赖关系。如果期望的键类型是`String`,则可以自动连接强类型`Map`实例。自动连线`Map`实例的值由所有与预期类型匹配的 Bean 实例组成,而`Map`实例的键包含相应的 Bean 名称。

#####  自动布线的局限性和缺点

自动布线在项目中一致使用时效果最好。 Bean 如果一般不使用自动布线,那么只使用它来连接一个或两个定义可能会使开发人员感到困惑。

考虑一下自动布线的局限性和缺点:

* `property`和`constructor-arg`设置中的显式依赖总是覆盖自动布线。你不能自动连接简单的属性,例如原语、`Strings`和`Classes`(以及此类简单属性的数组)。这种限制是人为设计的。

* 自动布线不像显式布线那样精确。虽然,正如在前面的表中所指出的, Spring 小心地避免在可能产生意外结果的模棱两可的情况下进行猜测。你的 Spring-托管对象之间的关系不再显式地记录。

* 接线信息可能不能用于可能从 Spring 容器生成文档的工具。

* Bean 容器内的多个定义可以匹配由 setter 方法或构造函数参数指定的类型以进行自动连线。对于数组、集合或`Map`实例,这不一定是问题。然而,对于期望单个值的依赖关系,这种歧义不能任意解决。如果没有唯一的 Bean 定义可用,则抛出异常。

在后一种情况下,你有几种选择:

* 放弃自动布线,改用显式布线。

* 通过将其`autowire-candidate`属性设置为`false`,避免 Bean 定义的自动布线,如[下一节](#beans-factory-autowire-candidate)中所述。

* 通过将其`<bean/>`元素的`primary`属性设置为`true`,指定单个 Bean 定义作为主要候选项。

* 用基于注释的配置实现更细粒度的控件,如[基于注释的容器配置](#beans-annotation-config)中所述。

#####  从自动接线中排除 A Bean

在每 Bean 个基础上,可以将 Bean 个从自动布线中排除。在 Spring 的 XML 格式中,将`autowire-candidate`元素的`autowire-candidate`属性设置为`false`。该容器使得该特定的 Bean 定义对自动布线基础设施不可用(包括注释样式配置,例如[`@Autowired`](#beans-autowired-annotation))。

|   |`autowire-candidate`属性的设计目的是只影响基于类型的自动连接。<br/>它不影响通过名称的显式引用,即使指定的 Bean 未标记为自动连接候选项<br/>,也可以解决该问题。因此,如果名称匹配,则按名称自动布线<br/>仍然会注入 Bean。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你还可以根据模式匹配对 Bean 名称限制自动连接候选项。顶层`<beans/>`元素在其`default-autowire-candidates`属性中接受一个或多个模式。例如,要将 AutoWire 候选状态限制为名称以`Repository`结尾的任何 Bean,请提供一个值`*Repository`。要提供多个模式,请在逗号分隔的列表中定义它们。对于 Bean 定义的`autowire-candidate`属性,显式值`true`或`false`总是优先。对于这样的 bean,模式匹配规则不适用。

这些技术对于你永远不希望通过自动布线将 bean 注入到其他 bean 中的 bean 非常有用。这并不意味着排除 Bean 本身不能通过使用自动布线来进行配置。相反, Bean 本身并不是自动连接其他 bean 的候选者。

#### 1.4.6.方法注入

在大多数应用程序场景中,容器中的大多数 bean 都是[singletons](#beans-factory-scopes-singleton)。当单例 Bean 需要与另一个单例 Bean 协作时,或者非单例 Bean 需要与另一个非单例 Bean 协作时,通常通过将一个 Bean 定义为另一个的属性来处理依赖关系。当 Bean 生命周期不同时会出现问题。假设单例 Bean a 需要使用非单例(原型) Bean b,也许是在 a 上的每个方法调用上。容器只创建单例 Bean a 一次,因此只获得一次设置属性的机会。容器不能在每次需要一个 Bean b 的新实例时提供 Bean a。

一种解决办法是放弃某种程度的控制权倒置。通过实现`ApplicationContextAware`接口,并通过[对容器进行`getBean("B")`调用](#beans-factory-client)每次 Bean a 需要它时,都可以[make bean A aware of the container](#beans-factory-aware)请求 Bean b 实例。下面的示例展示了这种方法:

Java

```
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
```

Kotlin

```
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple

// Spring-API imports
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware

class CommandManager : ApplicationContextAware {

    private lateinit var applicationContext: ApplicationContext

    fun process(commandState: Map<*, *>): Any {
        // grab a new instance of the appropriate Command
        val command = createCommand()
        // set the state on the (hopefully brand new) Command instance
        command.state = commandState
        return command.execute()
    }

    // notice the Spring API dependency!
    protected fun createCommand() =
            applicationContext.getBean("command", Command::class.java)

    override fun setApplicationContext(applicationContext: ApplicationContext) {
        this.applicationContext = applicationContext
    }
}
```

上述方法是不可取的,因为业务代码是知道并耦合到 Spring 框架的。方法注入是 Spring IoC 容器的一个比较高级的特性,它允许你干净地处理这个用例。

你可以在[这个博客条目](https://spring.io/blog/2004/08/06/method-injection/)中阅读有关方法注入的动机的更多信息。

#####  查找方法注入

查找方法注入是容器重写容器管理的 bean 上的方法并返回容器中另一个名为 Bean 的查找结果的能力。查找通常涉及原型 Bean,如[上一节](#beans-factory-method-injection)中描述的场景中所述的那样。 Spring 框架通过使用来自 CGlib 库的字节码生成来动态地生成覆盖该方法的子类,从而实现了该方法注入。

|   |* 对于这种动态子类的工作, Spring  Bean 容器<br/>子类不能是`final`,并且要重写的方法不能是`final`,<br/><br/>* 单元-测试具有`abstract`方法的类需要你自己对类<br/>进行子类划分,并提供`abstract`方法的存根实现,<br/><br/>* 具体方法对于组件扫描也是必需的,这需要具体的<br/>类来拾取。<br/><br/>* 另一个关键限制是,查找方法不适用于工厂方法,而且<br/>尤其不适用于配置类中的`@Bean`方法,因为,在这种情况下,<br/>容器不负责创建实例,因此不能动态创建<br/>运行时生成的子类。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在前面的代码片段中的`CommandManager`类的情况下, Spring 容器动态地覆盖`createCommand()`方法的实现。正如重做的示例所示,`CommandManager`类没有任何 Spring 依赖关系:

Java

```
package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
```

Kotlin

```
package fiona.apple

// no more Spring imports!

abstract class CommandManager {

    fun process(commandState: Any): Any {
        // grab a new instance of the appropriate Command interface
        val command = createCommand()
        // set the state on the (hopefully brand new) Command instance
        command.state = commandState
        return command.execute()
    }

    // okay... but where is the implementation of this method?
    protected abstract fun createCommand(): Command
}
```

在包含要注入的方法的客户机类(在本例中为`CommandManager`)中,要注入的方法需要以下形式的签名:

```
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
```

如果方法是`abstract`,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖在原始类中定义的具体方法。考虑以下示例:

```
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>
```

Bean 标识为`commandManager`的方法在需要`myCommand` Bean 的新实例时调用它自己的`createCommand()`方法。如果确实需要将`myCommand` Bean 部署为原型,则必须小心。如果是[singleton](#beans-factory-scopes-singleton),则每次都返回相同的`myCommand` Bean 实例。

或者,在基于注释的组件模型中,可以通过`@Lookup`注释声明查找方法,如下例所示:

Java

```
public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}
```

Kotlin

```
abstract class CommandManager {

    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }

    @Lookup("myCommand")
    protected abstract fun createCommand(): Command
}
```

或者,更常见的是,你可以依赖于目标 Bean 根据查找方法的声明的返回类型进行解析:

Java

```
public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract Command createCommand();
}
```

Kotlin

```
abstract class CommandManager {

    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }

    @Lookup
    protected abstract fun createCommand(): Command
}
```

请注意,你通常应该使用一个具体的存根实现来声明这种带注释的查找方法,以便它们与 Spring 的组件扫描规则兼容,在组件扫描规则中,抽象类在默认情况下会被忽略。此限制不适用于显式注册或显式导入的 Bean 类。

|   |访问不同作用域的目标 bean 的另一种方法是`ObjectFactory`/`Provider`注入点。参见[作为依赖项的作用域 bean](#beans-factory-scopes-other-injection)。<br/><br/>你可能还会发现`ServiceLocatorFactoryBean`(在`org.springframework.beans.factory.config`包中)是有用的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  任意方法替换

Bean 与查找方法注入相比,方法注入的一种不那么有用的形式是能够用另一种方法实现来替换托管中的任意方法。你可以安全地跳过本节的其余部分,直到你真正需要此功能为止。

Bean 对于基于 XML 的配置元数据,可以使用`replaced-method`元素来替换已部署的另一个方法实现。考虑下面的类,它有一个名为`computeValue`的方法,我们想要覆盖它:

Java

```
public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}
```

Kotlin

```
class MyValueCalculator {

    fun computeValue(input: String): String {
        // some real code...
    }

    // some other methods...
}
```

实现`org.springframework.beans.factory.support.MethodReplacer`接口的类提供了新的方法定义,如下例所示:

Java

```
/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}
```

Kotlin

```
/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
class ReplacementComputeValue : MethodReplacer {

    override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any {
        // get the input value, work with it, and return a computed result
        val input = args[0] as String;
        ...
        return ...;
    }
}
```

Bean 用于部署原始类并指定方法覆盖的定义类似于以下示例:

```
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
```

可以在`<replaced-method/>`元素中使用一个或多个`<arg-type/>`元素来指示要重写的方法的方法签名。只有当方法重载并且类中存在多个变量时,参数的签名才是必需的。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,下面的 all match`java.lang.String`:

```
java.lang.String
String
Str
```

因为参数的数量通常足以区分每个可能的选择,所以通过允许你只键入与参数类型匹配的最短字符串,此快捷方式可以节省大量的输入。

### 1.5. Bean 范围

当你创建 Bean 定义时,你创建了用于创建由该 Bean 定义定义的类的实际实例的配方。 Bean 定义是一个配方的想法很重要,因为它意味着,与类一样,你可以从一个配方创建许多对象实例。

你不仅可以控制要插入到从特定的 Bean 定义创建的对象中的各种依赖关系和配置值,还可以控制从特定的 Bean 定义创建的对象的范围。这种方法功能强大且灵活,因为你可以选择通过配置创建的对象的范围,而不必在 Java 类级别上烘烤对象的范围。可以将 bean 定义为部署在多个作用域中的一个。 Spring 框架支持六个作用域,其中四个作用域只有在使用 Web 感知`ApplicationContext`时才可用。你也可以创建[自定义范围。](#beans-factory-scopes-custom)

下表描述了受支持的作用域:

|                        Scope                        |说明|
|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|    [singleton](#beans-factory-scopes-singleton)     |(缺省)将单个 Bean 定义作用于每个 Spring IOC<br/>容器的单个对象实例。|
|    [prototype](#beans-factory-scopes-prototype)     |Bean 将单个定义作用于任意数量的对象实例。|
|      [request](#beans-factory-scopes-request)       |Bean 将单个定义作用于单个 HTTP 请求的生命周期。即,<br/>每个 HTTP 请求都有其自己的实例,一个 Bean 创建于单个 Bean <br/>定义的后面。 Spring `ApplicationContext`仅在可感知 Web 的上下文中有效。|
|      [session](#beans-factory-scopes-session)       |将一个 Bean 定义的范围应用于 HTTP`Session`的生命周期。 Spring `ApplicationContext`仅在网络感知的上下文中有效。|
|  [application](#beans-factory-scopes-application)   |将一个 Bean 定义的范围应用于`ServletContext`的生命周期。 Spring `ApplicationContext`仅在网络感知的上下文中有效。|
|[websocket](web.html#websocket-stomp-websocket-scope)|将单个 Bean 定义作用于`WebSocket`的生命周期。 Spring `ApplicationContext`仅在网络感知的上下文中有效。|

|   |从 Spring 3.0 开始,线程作用域是可用的,但默认情况下不会注册。有关<br/>更多信息,请参见文档[`SimpleThreadScope`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/support/simplethreadscope.html)。<br/>有关如何注册此或任何其他自定义范围的说明,请参见[使用自定义作用域](#beans-factory-scopes-custom-using)。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.5.1.单例范围

仅管理单例 Bean 的一个共享实例,并且对具有与 Bean 定义相匹配的 ID 或 ID 的 bean 的所有请求导致由 Spring 容器返回一个特定的 Bean 实例。

换句话说,当你定义 Bean 定义并将其作为单例作用域时, Spring IoC 容器将创建由该 Bean 定义定义的对象的一个实例。这个单独的实例存储在这样的单例 bean 的缓存中,所有后续的请求和对名为 Bean 的引用都返回缓存的对象。下图显示了单例范围的工作原理:

![singleton](images/singleton.png)

Spring 的单例模式的概念 Bean 与四人帮模式书中定义的单例模式不同。GOF 单例硬编码对象的作用域,使得每个类装入器只创建一个特定类的实例。 Spring 单例的作用域最好描述为每个容器和每个- Bean。这意味着,如果在单个 Spring 容器中为特定类定义一个 Bean,则 Spring 容器创建由该 Bean 定义的类的一个且只有一个实例。单例作用域是 Spring 中的默认作用域。要将 Bean 定义为 XML 中的单例,可以定义 Bean,如以下示例所示:

```
<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
```

#### 1.5.2.原型范围

Bean 部署的非单例原型范围在每次提出对该特定 Bean 的请求时都会产生新的 Bean 实例。即,将 Bean 注入到另一个 Bean 中,或者通过容器上的`getBean()`方法调用来请求它。通常,你应该为所有有状态 bean 使用原型作用域,为无状态 bean 使用单例作用域。

下图说明了 Spring 原型范围:

![prototype](images/prototype.png)

(数据访问对象(DAO)通常不被配置为原型,因为典型的 DAO 不持有任何会话状态。对我们来说,重用单例图的核心更容易。

下面的示例将 Bean 定义为 XML 的原型:

```
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
```

与其他范围相反, Spring 不管理原型的完整生命周期 Bean。容器实例化、配置和以其他方式组装一个原型对象,并将其交给客户机,而不需要进一步记录该原型实例。因此,尽管在所有对象上调用初始化生命周期回调方法,而不考虑作用域,但在原型的情况下,不调用已配置的销毁生命周期回调。客户机代码必须清理原型范围内的对象,并释放原型 bean 所拥有的昂贵资源。要获得 Spring 容器以释放由原型范围的 bean 持有的资源,请尝试使用自定义[bean post-processor](#beans-factory-extension-bpp),它保存对需要清理的 bean 的引用。

在某些方面, Spring 容器对于原型范围 Bean 的作用是对 Java`new`操作符的替换。超过这一点的所有生命周期管理都必须由客户机处理。(有关 Spring 容器中 Bean 的生命周期的详细信息,请参见[生命周期回调](#beans-factory-lifecycle)。

#### 1.5.3.具有原型依赖关系的单例 bean- Bean

当你使用依赖于原型 bean 的单例作用域 bean 时,请注意,依赖关系是在实例化时解决的。因此,如果将依赖-注入原型范围的 Bean 到单例范围的 Bean 中,则实例化新的原型 Bean,然后将依赖-注入到单例范围的 Bean 中。原型实例是提供给单例范围的唯一实例 Bean。

然而,假设你希望单例作用域 Bean 在运行时反复获得原型作用域 Bean 的一个新实例。你不能将一个原型范围的 Bean 注入到你的单例 Bean 中,因为该注入只发生一次,当 Spring 容器实例化单例 Bean 并解析和注入其依赖项时。如果在运行时不止一次需要原型 Bean 的新实例,请参见[方法注入](#beans-factory-method-injection)。

#### 1.5.4.请求、会话、应用和 WebSocket 范围

`request`、`session`、`application`和`websocket`作用域只有在使用 Web-aware Spring `ApplicationContext`实现(例如`XmlWebApplicationContext`)时才可用。如果你将这些作用域与常规 Spring IOC 容器(例如`ClassPathXmlApplicationContext`)一起使用,则抛出一个抱怨未知 Bean 作用域的`IllegalStateException`。

#####  初始 Web 配置

要支持在`request`、`session`、`application`和`websocket`级别(Web 范围的 bean)对 bean 的范围进行配置,在定义 bean 之前需要进行一些较小的初始配置。(对于标准作用域:`singleton`和`prototype`,不需要这种初始设置。)

如何完成这个初始设置取决于你的特定环境 Servlet。

如果你在 Spring Web MVC 中访问作用域 bean,那么实际上,在由 Spring `DispatcherServlet`处理的请求中,就不需要进行特殊的设置。`DispatcherServlet`已经公开了所有相关的状态。

如果使用 Servlet 2.5Web 容器,并且在 Spring 的`DispatcherServlet`之外处理请求(例如,当使用 JSF 或 Struts 时),则需要注册`org.springframework.web.context.request.RequestContextListener``ServletRequestListener`。对于 Servlet 3.0+,这可以通过使用`WebApplicationInitializer`接口以编程方式完成。或者,对于较旧的容器,可以将以下声明添加到 Web 应用程序的`web.xml`文件中:

```
<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>
```

或者,如果你的侦听器设置存在问题,可以考虑使用 Spring 的`RequestContextFilter`。筛选器映射依赖于周围的 Web 应用程序配置,因此你必须对其进行适当的更改。下面的清单显示了 Web 应用程序的筛选器部分:

```
<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>
```

`DispatcherServlet`、`RequestContextListener`和`RequestContextFilter`都执行完全相同的操作,即将 HTTP 请求对象绑定到服务该请求的`Thread`。这使得请求范围和会话范围的 bean 在调用链的更下游可用。

#####  请求范围

考虑 Bean 定义的以下 XML 配置:

```
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
```

Spring 容器通过为每个 HTTP 请求使用 Bean 定义来创建 Bean 的新实例。也就是说,`loginAction` Bean 的作用域在 HTTP 请求级别。你可以随意更改所创建实例的内部状态,因为从相同的`loginAction` Bean 定义创建的其他实例在状态上看不到这些更改。它们是针对个人要求的。当请求完成处理时, Bean 被弃用到该请求的范围。

当使用注释驱动的组件或 Java 配置时,`@RequestScope`注释可用于将组件分配给`request`范围。下面的示例展示了如何做到这一点:

Java

```
@RequestScope
@Component
public class LoginAction {
    // ...
}
```

Kotlin

```
@RequestScope
@Component
class LoginAction {
    // ...
}
```

#####  会话范围

考虑 Bean 定义的以下 XML 配置:

```
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
```

Spring 容器通过使用`userPreferences` Bean 定义为单个 HTTP`Session`创建`UserPreferences` Bean 的新实例。换句话说,`userPreferences` Bean 在 http`Session`级别有效地起作用。与请求范围的 bean 一样,你可以更改所创建的实例的内部状态,只要你愿意,就可以知道其他 HTTP实例也在使用从相同的 Bean 定义创建的实例,它们在状态上看不到这些更改,因为它们是特定于单个 http`Session`的。当 http`Session`最终被丢弃时,作用域为特定 http`Session`的 Bean 也将被丢弃。

当使用注释驱动的组件或 Java 配置时,可以使用`@SessionScope`注释将组件分配给`session`范围。

Java

```
@SessionScope
@Component
public class UserPreferences {
    // ...
}
```

Kotlin

```
@SessionScope
@Component
class UserPreferences {
    // ...
}
```

#####  应用范围

考虑 Bean 定义的以下 XML 配置:

```
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
```

Spring 容器为整个 Web 应用程序使用一次`appPreferences` Bean 定义,从而创建`AppPreferences` Bean 的新实例。也就是说,`appPreferences` Bean 的作用域在`ServletContext`级别,并存储为常规的`ServletContext`属性。这在某种程度上类似于 Spring singleton Bean,但在两个重要方面有所不同:它是 singleton per`ServletContext`,而不是 per Spring `ApplicationContext`(在任何给定的 Web 应用程序中可能有几个),它实际上是公开的,因此以`ServletContext`属性可见。

当使用注释驱动的组件或 Java 配置时,可以使用`@ApplicationScope`注释将组件分配给`application`范围。下面的示例展示了如何做到这一点:

Java

```
@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
```

Kotlin

```
@ApplicationScope
@Component
class AppPreferences {
    // ...
}
```

#####  WebSocket 范围

WebSocket 范围与 WebSocket 会话的生命周期相关联,并应用于 WebSocket 应用程序上的 stomp,有关更多详细信息,请参见[WebSocket scope](web.html#websocket-stomp-websocket-scope)。

#####  作为依赖项的作用域 bean

Spring IoC 容器不仅管理对象的实例化,还管理协作者(或依赖项)的连接。如果你希望(例如)将一个 HTTP 请求范围 Bean 注入到另一个 Bean 的更长寿命范围中,那么你可以选择注入一个 AOP 代理来代替该范围 Bean。也就是说,你需要注入一个代理对象,该代理对象公开了与作用域对象相同的公共接口,但也可以从相关作用域(例如 HTTP 请求)检索真实目标对象,并将委托方法调用到真实对象上。

|   |你还可以在作用域为`<aop:scoped-proxy/>`的 bean 之间使用`singleton`,<br/>,然后通过一个中间代理,该代理是可序列化的<br/>,因此能够在反序列化时重新获得目标单例 Bean。当声明<br/>`<aop:scoped-proxy/>`的作用域`prototype`时,在共享代理上调用的每个方法<br/>都会导致创建一个新的目标实例,然后将<br/>调用转发到该实例。<br/><br/>此外,有作用域的代理并不是以<br/>生命周期安全方式从较短的作用域中访问 bean 的唯一方法。你也可以声明你的注射点(即,<br/>构造函数或 setter 参数或 autowired 字段)作为`ObjectFactory<MyTargetBean>`,<br/>允许一个`getObject()`调用,以便在需要的每一次<br/>时间按需检索当前实例——而无需抓牢实例或将其单独存储。<br/><br/>作为扩展变体,你可以声明`ObjectProvider<MyTargetBean>`,它提供了<br/>几个额外的访问变量,包括`getIfAvailable`和`getIfUnique`。<br/><br/>这个的 JSR-330 变体被称为`Provider`,并与`Provider<MyTargetBean>`声明和相应的`get()`调用一起使用。<br/>总体上查看[here](#beans-standard-annotations)有关 JSR-330 的更多详细信息。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面示例中的配置只有一行,但理解其背后的“为什么”和“如何”非常重要:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> (1)
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>
```

|**1**|定义代理的那条线。|
|-----|--------------------------------|

要创建这样的代理,你需要将一个子元素`<aop:scoped-proxy/>`插入到一个范围 Bean 定义中(参见[选择要创建的代理的类型](#beans-factory-scopes-other-injection-proxies)和[基于 XML 模式的配置](#xsd-schemas))。为什么在`request`、`session`和自定义范围级别范围内的 bean 定义需要`<aop:scoped-proxy/>`元素?考虑下面的单例定义 Bean,并将其与你需要为上述范围定义的内容进行对比(请注意,下面的`userPreferences` Bean 定义是不完整的):

```
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
```

在前面的示例中,注入单例 Bean(`userManager`)时引用了 http`Session`-scoped Bean(`userPreferences`)。这里的要点是,`userManager` Bean 是一个单例:每个容器只实例化一次,它的依赖项(在本例中只有一个,`userPreferences` Bean)也只注入一次。这意味着`userManager` Bean 仅对完全相同的`userPreferences`对象(即最初注入它的对象)进行操作。

这不是你在将较短的作用域 Bean 注入较长的作用域 Bean 时想要的行为(例如,将一个 HTTP作用域协作 Bean 作为一个依赖项注入到单例 Bean)。相反,你需要一个`userManager`对象,并且,对于一个 http`Session`的生命周期,你需要一个`userPreferences`对象,它是特定于 http`Session`的。因此,容器创建了一个对象,该对象公开与`UserPreferences`类完全相同的公共接口(理想情况下是一个`UserPreferences`实例的对象),它可以从范围机制(HTTP 请求,`Session`,等等)获取真正的`UserPreferences`对象。容器将该代理对象注入到`userManager` Bean 中,该对象不知道该`UserPreferences`引用是代理。在本例中,当`UserManager`实例调用依赖注入的`UserPreferences`对象上的方法时,它实际上是在调用代理上的方法。然后,代理从(在本例中)HTTP`Session`中获取 real`UserPreferences`对象,并将方法调用委托给检索到的 real`UserPreferences`对象。

因此,在将`request-`和`session-scoped`bean 注入协作对象时,需要进行以下(正确和完整的)配置,如下例所示:

```
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
```

###### 选择要创建的代理的类型

默认情况下,当 Spring 容器为用`<aop:scoped-proxy/>`元素标记的 Bean 创建代理时,将创建一个基于 CGLIB 的类代理。

|   |CGLIB 代理仅拦截公共方法调用!不要在这样的代理上调用非公共方法<br/>。它们不会被委托给实际作用域的目标对象。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------|

或者,你可以通过为`proxy-target-class`元素的`<aop:scoped-proxy/>`属性的值指定`false`,配置 Spring 容器来为此类作用域 bean 创建标准的基于 JDK 接口的代理。 Classpath 使用基于 JDK 接口的代理意味着你不需要应用程序中的额外库来影响这样的代理。然而,这也意味着 Bean 作用域的类必须实现至少一个接口,并且将 Bean 作用域注入其中的所有协作者必须通过其接口之一引用 Bean。下面的示例展示了一个基于接口的代理:

```
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.stuff.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
```

有关选择基于类或基于接口的代理的更详细信息,请参见[代理机制](#aop-proxying)。

#### 1.5.5.自定义范围

Bean 范围机制是可扩展的。你可以定义自己的作用域,甚至重新定义现有的作用域,尽管后者被认为是错误的实践,并且你无法覆盖内置的`singleton`和`prototype`作用域。

#####  创建自定义范围

要将你的自定义作用域集成到 Spring 容器中,你需要实现`org.springframework.beans.factory.config.Scope`接口,这将在本节中进行描述。有关如何实现自己的作用域的想法,请参见 Spring 框架本身和[`Scope`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/beans/factory/config/scope.html)Javadoc 的实现,它更详细地解释了你需要实现的方法。

`Scope`接口有四个方法来从作用域中获取对象,将它们从作用域中删除,然后销毁它们。

会话范围实现,例如,返回会话范围 Bean(如果它不存在,则方法返回 Bean 的新实例,在将其绑定到会话以供将来引用之后)。以下方法从底层作用域返回对象:

Java

```
Object get(String name, ObjectFactory<?> objectFactory)
```

Kotlin

```
fun get(name: String, objectFactory: ObjectFactory<*>): Any
```

例如,会话范围实现从基础会话中删除会话范围 Bean。应该返回该对象,但是如果没有找到指定名称的对象,则可以返回`null`。以下方法将该对象从底层作用域中删除:

Java

```
Object remove(String name)
```

Kotlin

```
fun remove(name: String): Any
```

以下方法注册了一个回调,当范围被销毁或范围中的指定对象被销毁时,该范围应调用该回调:

Java

```
void registerDestructionCallback(String name, Runnable destructionCallback)
```

Kotlin

```
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
```

有关销毁回调的更多信息,请参见[javadoc](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/beans/factory/config/Scope.html#registerDestructionCallback)或 Spring 范围实现。

以下方法获得底层作用域的会话标识符:

Java

```
String getConversationId()
```

Kotlin

```
fun getConversationId(): String
```

这个标识符对于每个作用域都是不同的。对于会话范围的实现,这个标识符可以是会话标识符。

#####  使用自定义作用域

在编写和测试一个或多个自定义`Scope`实现之后,你需要让 Spring 容器知道你的新作用域。下面的方法是在 Spring 容器中注册一个新的`Scope`的中心方法:

爪哇

```
void registerScope(String scopeName, Scope scope);
```

Kotlin

```
fun registerScope(scopeName: String, scope: Scope)
```

此方法在`ConfigurableBeanFactory`接口上声明,该接口可通过`BeanFactory`属性在 Spring 附带的大多数具体`ApplicationContext`实现上使用。

`registerScope(..)`方法的第一个参数是与作用域关联的唯一名称。 Spring 容器本身中的此类名称的示例是`singleton`和`prototype`。`registerScope(..)`方法的第二个参数是你希望注册和使用的自定义`Scope`实现的实际实例。

假设你编写了你的自定义`Scope`实现,然后将其注册为下一个示例所示。

|   |下一个示例使用`SimpleThreadScope`,它包含在 Spring 中,但不是默认注册的<br/>。对于你自己的自定义`Scope`实现,指令将是相同的。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

爪哇

```
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
```

Kotlin

```
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)
```

然后,你可以创建遵守自定义`Scope`的范围规则的 Bean 定义,如下所示:

```
<bean id="..." class="..." scope="thread">
```

使用自定义的`Scope`实现,你不仅限于编程注册的范围。你还可以使用`CustomScopeConfigurer`类来声明性地进行`Scope`注册,如下例所示:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>
```

|   |当将`<aop:scoped-proxy/>`置于`<bean>`实现的`FactoryBean`声明中时,作用域是工厂 Bean 本身,而不是从`getObject()`返回的对象<br/>。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 1.6.自定义 A 的性质 Bean

Spring 框架提供了许多接口,你可以使用这些接口来自定义 Bean 的性质。本节将其分类如下:

* [生命周期回调](#beans-factory-lifecycle)

* [`ApplicationContextAware`和`BeanNameAware`](#beans-factory-aware)

* [Other`Aware`接口](#aware-list)

#### 1.6.1.生命周期回调

要与容器的 Bean 生命周期管理交互,你可以实现 Spring `InitializingBean`和`DisposableBean`接口。容器为前者调用`afterPropertiesSet()`,为后者调用`destroy()`,以使 Bean 在初始化和销毁 bean 时执行某些操作。

|   |JSR-250`@PostConstruct`和`@PreDestroy`注释通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳<br/>实践。使用这些<br/>注释意味着你的 bean 没有耦合到 Spring 特定的接口。<br/>有关详细信息,请参见[使用`@PostConstruct`和`@PreDestroy`](#beans-postConstruct-and-predestroy-注释)。<br/><br/>如果你不想使用 JSR-250 注释,但仍然希望删除<br/>,则考虑元数据<gt="1728"/>和<gt=“定义”。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在内部, Spring 框架使用`BeanPostProcessor`实现来处理它能够找到并调用适当方法的任何回调接口。如果你需要自定义功能或其他默认情况下不提供的生命周期行为 Spring,那么你可以自己实现`BeanPostProcessor`。有关更多信息,请参见[容器延伸点](#beans-factory-extension)。

除了初始化和销毁回调, Spring 管理的对象还可以实现`Lifecycle`接口,以便这些对象可以参与启动和关闭过程,就像容器自身的生命周期所驱动的那样。

生命周期回调接口在本节中进行了描述。

#####  初始化回调

在容器在 Bean 上设置了所有必要的属性之后,`org.springframework.beans.factory.InitializingBean`接口允许 Bean 执行初始化工作。`InitializingBean`接口指定了一个方法:

```
void afterPropertiesSet() throws Exception;
```

我们建议你不要使用`InitializingBean`接口,因为它不必要地将代码耦合到 Spring。或者,我们建议使用[`@PostConstruct`](#beans-postconstruct-and-predestroy-注释)注释或指定 POJO 初始化方法。在基于 XML 的配置元数据的情况下,可以使用`init-method`属性指定具有无效无参数签名的方法的名称。使用 爪哇 配置,可以使用`@Bean`的`initMethod`属性。见[接收生命周期回调](#beans-java-lifecycle-callbacks)。考虑以下示例:

```
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
```

爪哇

```
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}
```

Kotlin

```
class ExampleBean {

    fun init() {
        // do some initialization work
    }
}
```

前面的示例与下面的示例(由两个列表组成)的效果几乎完全相同:

```
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
```

爪哇

```
public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}
```

Kotlin

```
class AnotherExampleBean : InitializingBean {

    override fun afterPropertiesSet() {
        // do some initialization work
    }
}
```

然而,前面的两个示例中的第一个不将代码与 Spring 耦合。

#####  销毁回调

实现`org.springframework.beans.factory.DisposableBean`接口可以让 Bean 在包含它的容器被销毁时获得回调。`DisposableBean`接口指定了一个方法:

```
void destroy() throws Exception;
```

我们建议你不要使用`DisposableBean`回调接口,因为它不必要地将代码耦合到 Spring。或者,我们建议使用[`@PreDestroy`](#beans-postconstruct-and-predestroy-注释)注释或指定 Bean 定义支持的通用方法。对于基于 XML 的配置元数据,你可以在`destroy-method`上使用`<bean/>`属性。使用 爪哇 配置,可以使用`@Bean`的`destroyMethod`属性。见[接收生命周期回调](#beans-java-lifecycle-callbacks)。考虑以下定义:

```
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
```

爪哇

```
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}
```

Kotlin

```
class ExampleBean {

    fun cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}
```

上述定义与以下定义的效果几乎完全相同:

```
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
```

爪哇

```
public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
```

Kotlin

```
class AnotherExampleBean : DisposableBean {

    override fun destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
```

然而,前面的两个定义中的第一个并不将代码与 Spring 耦合。

|   |可以为`<bean>`元素的`destroy-method`属性分配一个特殊的`(inferred)`值,该值指示 Spring 在特定的 Bean 类上自动检测一个公共`close`或`shutdown`方法。(因此,任何实现`java.lang.AutoCloseable`或`java.io.Closeable`的类都将匹配。)你还可以在<br/>元素的`default-destroy-method`属性上设置这个特殊的`(inferred)`值,以将此行为应用到整个 bean 集(参见[默认的初始化和销毁方法](#beans-factory-lifecycle-default-init-destroy-methods))。请注意,这是使用 爪哇 配置的<br/>默认行为。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  默认的初始化和销毁方法

当你编写初始化并销毁不使用 Spring 特定的`InitializingBean`和`DisposableBean`回调接口的方法回调时,通常使用`init()`、`initialize()`、`dispose()`等名称编写方法。理想情况下,这样的生命周期回调方法的名称在整个项目中是标准化的,这样所有开发人员都使用相同的方法名称并确保一致性。

你可以将 Spring 容器配置为“查找”命名的初始化,并在每个 Bean 上销毁回调方法名称。这意味着,作为应用程序开发人员,你可以编写应用程序类并使用一个名为`init()`的初始化回调,而不必为每个 Bean 定义配置一个`init-method="init"`属性。 Spring IOC 容器在创建 Bean 时调用该方法(并且根据标准的生命周期回调契约[先前描述过](#beans-factory-lifecycle))。此特性还强制执行一个用于初始化和销毁方法回调的一致的命名约定。

假设你的初始化回调方法名为`init()`,而销毁回调方法名为`destroy()`。在下面的示例中,你的类与类类似:

爪哇

```
public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}
```

Kotlin

```
class DefaultBlogService : BlogService {

    private var blogDao: BlogDao? = null

    // this is (unsurprisingly) the initialization callback method
    fun init() {
        if (blogDao == null) {
            throw IllegalStateException("The [blogDao] property must be set.")
        }
    }
}
```

然后,你可以在类似于以下内容的 Bean 中使用该类:

```
<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>
```

在顶层`<beans/>`元素属性上的`default-init-method`属性的存在导致 Spring IoC 容器将 Bean 类上的一个名为`init`的方法识别为初始化方法回调。当 Bean 被创建和组装时,如果 Bean 类具有这样的方法,则在适当的时间调用它。

通过在顶层`<beans/>`元素上使用`default-destroy-method`属性,可以类似地(在 XML 中)配置 destroy 方法回调。

如果现有的 Bean 类已经有了根据约定命名的回调方法,那么可以通过使用`init-method`本身的`<bean/>`属性指定(在 XML 中,即)方法名来覆盖缺省的方法名。

Spring 容器保证在 Bean 提供了所有依赖项之后立即调用已配置的初始化回调。因此,在 Raw Bean 引用上调用初始化回调,这意味着 AOP 拦截器等等尚未应用到 Bean。首先完全创建目标 Bean,然后应用具有拦截链的 AOP 代理(例如)。如果目标 Bean 和代理是分开定义的,那么你的代码甚至可以绕过代理与原始目标 Bean 进行交互。因此,将拦截器应用于`init`方法将是不一致的,因为这样做将把目标 Bean 的生命周期与其代理或拦截器耦合在一起,并在你的代码直接与原始目标 Bean 交互时留下奇怪的语义。

#####  结合生命周期机制

从 Spring 2.5 开始,你有三种选择来控制 Bean 生命周期行为:

* [`InitializingBean`](#beans-factory-lifecycle-initializingbean)和[`DisposableBean`](#beans-factory-lifecycle-disapablebean)回调接口

* 自定义`init()`和`destroy()`方法

* [`@PostConstruct`和`@PreDestroy`注释](#beans-postconstruct-and-predestroy-注释)。你可以结合这些机制来控制给定的 Bean。

|   |如果为 Bean 配置了多个生命周期机制,并且每个机制<br/>配置了不同的方法名,那么每个配置的方法都是在<br/>顺序中运行的,顺序在此注释之后列出。但是,如果为这些生命周期机制中的一个以上配置了相同的方法名(例如,对于初始化方法,`init()`),则<br/>该方法将运行一次,如[前一节](#beans-factory-lifecycle-default-init-destroy-methods)中所解释的那样。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Bean 为相同的 Bean 配置了多个生命周期机制,具有不同的初始化方法,调用如下:

1. 用`@PostConstruct`注释的方法

2. `afterPropertiesSet()`由`InitializingBean`回调接口定义

3. 自定义配置的`init()`方法

销毁方法的调用顺序相同:

1. 用`@PreDestroy`注释的方法

2. `destroy()`由`DisposableBean`回调接口定义

3. 自定义配置的`destroy()`方法

#####  启动和关闭回调

`Lifecycle`接口定义了任何对象的基本方法,这些对象具有自己的生命周期要求(例如启动和停止某些后台进程):

```
public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}
```

任何 Spring-托管对象都可以实现`Lifecycle`接口。然后,当`ApplicationContext`本身接收开始和停止信号(例如,对于运行时的停止/重新启动场景)时,它将这些调用级联到在该上下文中定义的所有`Lifecycle`实现。它通过委托给`LifecycleProcessor`来实现这一点,如下面的清单所示:

```
public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}
```

注意,`LifecycleProcessor`本身是`Lifecycle`接口的扩展。它还添加了另外两个方法,用于对正在刷新和关闭的上下文做出反应。

|   |请注意,常规的`org.springframework.context.Lifecycle`接口是用于显式启动和停止通知的普通<br/>契约,并不意味着在上下文<br/>刷新时间自动启动。对于特定 Bean 的自动启动(包括启动阶段)的细粒度控制,<br/>考虑实现`org.springframework.context.SmartLifecycle`,而不是。<br/><br/>此外,请注意,停止通知不能保证在销毁之前发出。<br/>在常规关闭时,所有`Lifecycle`bean 在<br/>一般销毁回调被传播之前首先收到停止通知。但是,在<br/>上下文的生命期内进行热刷新或停止刷新尝试时,只调用销毁方法。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

启动和关闭调用的顺序可能很重要。如果在任何两个对象之间存在“依赖”关系,则依赖侧在其依赖项之后开始,在其依赖项之前停止。然而,有时,直接的依赖关系是未知的。你可能只知道,某种类型的对象应该先于另一种类型的对象启动。在这些情况下,`SmartLifecycle`接口定义了另一个选项,即在其超级接口上定义的`getPhase()`方法,`Phased`。下面的清单显示了`Phased`接口的定义:

```
public interface Phased {

    int getPhase();
}
```

下面的清单显示了`SmartLifecycle`接口的定义:

```
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}
```

启动时,相位最低的对象首先启动。当停止时,将遵循相反的顺序。因此,一个实现`SmartLifecycle`并且其`getPhase()`方法返回`Integer.MIN_VALUE`的对象将是最先启动和最后停止的对象。在频谱的另一端,相位值`Integer.MAX_VALUE`将指示对象应该最后启动并首先停止(可能是因为它依赖于要运行的其他进程)。当考虑相位值时,同样重要的是要知道,对于任何不实现`SmartLifecycle`的“正常”`Lifecycle`对象,默认的相位是`0`。因此,任何负相位值都表示对象应该在这些标准组件之前启动(在它们之后停止)。对于任何正相位值,反之亦然。

由`SmartLifecycle`定义的停止方法接受回调。在该实现的关闭过程完成之后,任何实现都必须调用该回调的`run()`方法。这将在必要时支持异步关闭,因为`LifecycleProcessor`接口的默认实现`DefaultLifecycleProcessor`在每个阶段中等待一组对象的超时值以调用该回调。默认的每阶段超时是 30 秒。通过在上下文中定义一个名为`lifecycleProcessor`的 Bean,你可以覆盖默认的生命周期处理器实例。如果只想修改超时,定义以下内容就足够了:

```
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
```

如前所述,`LifecycleProcessor`接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关机过程,就好像`stop()`已被显式调用一样,但它发生在上下文关闭时。另一方面,“刷新”回调启用了`SmartLifecycle`bean 的另一个特性。当刷新上下文时(在所有对象都已实例化和初始化之后),将调用该回调。这时,默认的生命周期处理器会检查每个`SmartLifecycle`对象的`isAutoStartup()`方法返回的布尔值。如果`true`,则该对象在该点启动,而不是等待上下文或其自身的`start()`方法的显式调用(与上下文刷新不同,对于标准的上下文实现,上下文启动不会自动发生)。如前面所述,`phase`值和任何“依赖”关系决定启动顺序。

#####  在非 Web 应用程序中优雅地关闭 Spring IoC 容器

|   |本节仅适用于非 Web 应用程序。 Spring 的基于 Web 的`ApplicationContext`实现已经有了适当的代码,可以在相关的 Web 应用程序被关闭时优雅地关闭<br/> Spring IOC 容器。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

如果在非 Web 应用程序环境(例如,在富客户端桌面环境中)中使用 Spring 的 IoC 容器,请向 JVM 注册一个关闭钩子。这样做可以确保良好的关机,并在单例 bean 上调用相关的销毁方法,以便释放所有资源。你仍然必须正确地配置和实现这些 destroy 回调。

要注册关机钩子,请调用`registerShutdownHook()`接口上声明的`ConfigurableApplicationContext`方法,如下例所示:

爪哇

```
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}
```

Kotlin

```
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    val ctx = ClassPathXmlApplicationContext("beans.xml")

    // add a shutdown hook for the above context...
    ctx.registerShutdownHook()

    // app runs here...

    // main method exits, hook is called prior to the app shutting down...
}
```

#### 1.6.2.`ApplicationContextAware`和`BeanNameAware`

当`ApplicationContext`创建实现`org.springframework.context.ApplicationContextAware`接口的对象实例时,该实例将提供对`ApplicationContext`的引用。下面的清单显示了`ApplicationContextAware`接口的定义:

```
public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
```

因此,bean 可以通过`ApplicationContext`接口或通过对该接口的一个已知子类(例如`ConfigurableApplicationContext`,该接口公开了额外的功能)的引用,以编程方式操作创建它们的`ApplicationContext`。一种用途是对其他 bean 进行程序化检索。有时这种能力是有用的。但是,通常情况下,你应该避免它,因为它将代码与 Spring 耦合,并且不遵循控制风格的反转,在这种情况下,协作者被提供给 bean 作为属性。`ApplicationContext`的其他方法提供了对文件资源的访问、应用程序事件的发布以及对`MessageSource`的访问。这些附加功能在[`ApplicationContext`的附加功能](#context-introduction)中进行了描述。

自动布线是获得对`ApplicationContext`的引用的另一种选择。*传统的*`constructor`和`byType`自动布线模式(如[自动布线合作者](#beans-factory-autowire)中所述)可以分别为构造函数参数或 setter 方法参数提供类型`ApplicationContext`的依赖项。对于更多的灵活性,包括自动连接字段和多个参数的方法,使用基于注释的自动连接功能。如果你这样做了,`ApplicationContext`将自动连接到一个字段、构造函数参数或方法参数中,如果所讨论的字段、构造函数或方法带有`ApplicationContext`注释,则该参数将需要`@Autowired`类型。有关更多信息,请参见[使用`@Autowired`]。

当`ApplicationContext`创建一个实现`org.springframework.beans.factory.BeanNameAware`接口的类时,将为该类提供对在其关联的对象定义中定义的名称的引用。下面的清单显示了 BeannaMeAware 接口的定义:

```
public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}
```

回调是在正常 Bean 属性的填充之后,但在初始化回调(如`InitializingBean.afterPropertiesSet()`或自定义 init-method)之前调用的。

#### 1.6.3.其它`Aware`接口

除了`ApplicationContextAware`和`BeanNameAware`(已讨论过[earlier](#beans-factory-aware))之外, Spring 还提供了广泛的`Aware`回调接口,这些接口使 bean 向容器表明它们需要某种基础设施依赖关系。作为一般规则,名称指示依赖类型。下表总结了最重要的`Aware`接口:

|              Name              |注入依赖性|                               Explained in…​                               |
|--------------------------------|-----------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
|   `ApplicationContextAware`    |声明`ApplicationContext`。|   [`ApplicationContextAware` and `BeanNameAware`](#beans-factory-aware)    |
|`ApplicationEventPublisherAware`|附件`ApplicationContext`的事件发布者。|[Additional Capabilities of the `ApplicationContext`](#context-introduction)|
|     `BeanClassLoaderAware`     |类装入器用于装入 Bean 类。|                [Instantiating Beans](#beans-factory-class)                 |
|       `BeanFactoryAware`       |声明`BeanFactory`。|                  [The `BeanFactory`](#beans-beanfactory)                   |
|        `BeanNameAware`         |声明的名称 Bean。|   [`ApplicationContextAware` and `BeanNameAware`](#beans-factory-aware)    |
|     `LoadTimeWeaverAware`      |定义了用于在加载时处理类定义的 Weaver。|   [Load-time Weaving with AspectJ in the Spring Framework](#aop-aj-ltw)    |
|      `MessageSourceAware`      |用于解析消息的已配置策略(支持参数化和<br/>国际化)。|[Additional Capabilities of the `ApplicationContext`](#context-introduction)|
|  `NotificationPublisherAware`  |Spring JMX 通知发布者。|            [Notifications](integration.html#jmx-notifications)             |
|     `ResourceLoaderAware`      |为低级访问资源配置了加载器。|                          [Resources](#resources)                           |
|      `ServletConfigAware`      |current`ServletConfig`容器运行。仅在网络感知的 Spring `ApplicationContext`中有效。|                         [Spring MVC](web.html#mvc)                         |
|     `ServletContextAware`      |current`ServletContext`容器运行。仅在网络感知的 Spring `ApplicationContext`中有效。|                         [Spring MVC](web.html#mvc)                         |

再次注意,使用这些接口将你的代码绑定到 Spring API,并且不遵循控制样式的反转。因此,对于需要对容器进行编程访问的基础设施 bean,我们推荐使用它们。

### 1.7. Bean 定义继承

Bean 定义可以包含大量的配置信息,包括构造函数参数、属性值和特定于容器的信息,例如初始化方法、静态工厂方法名称等。 Bean 子定义从父定义继承配置数据。子定义可以根据需要覆盖某些值或添加其他值。 Bean 使用父类和子类定义可以节省大量的输入。实际上,这是模板化的一种形式。

如果以编程方式处理`ApplicationContext`接口,则子 Bean 定义由`ChildBeanDefinition`类表示。大多数用户不会在这个级别上与他们合作。相反,它们在一个类中以声明性的方式配置 Bean 定义,例如`ClassPathXmlApplicationContext`。当你使用基于 XML 的配置元数据时,你可以通过使用`parent`属性来指示一个子 Bean 定义,并指定父 Bean 作为该属性的值。下面的示例展示了如何做到这一点:

```
<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  (1)
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>
```

|**1**|注意`parent`属性。|
|-----|----------------------------|

子 Bean 定义使用来自父定义的 Bean 类,如果没有指定,但也可以覆盖它。在后一种情况下,子 Bean 类必须与父类兼容(也就是说,它必须接受父的属性值)。

Bean 子定义从父定义继承范围、构造函数参数值、属性值和方法重写,并带有添加新值的选项。指定覆盖相应父设置的任何作用域、初始化方法、销毁方法或`static`工厂方法设置。

其余的设置总是从子定义中获取:Dependent on、AutoWire 模式、依赖检查、单例和惰性 init。

前面的示例使用`abstract`属性显式地将父 Bean 定义标记为抽象。如果父定义没有指定类,则需要显式地将父 Bean 定义标记为`abstract`,如下例所示:

```
<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
```

父 Bean 不能单独实例化,因为它是不完整的,并且它也显式地标记为`abstract`。当一个定义是`abstract`时,它只能作为一个纯粹的模板 Bean 定义使用,作为子定义的父定义。试图单独使用这样的`abstract`父 Bean,通过将其称为另一个 Bean 的 ref 属性或使用父 Bean ID 执行显式的`getBean()`调用,返回一个错误。类似地,容器的内部`preInstantiateSingletons()`方法忽略了 Bean 定义为抽象的定义。

|   |`ApplicationContext`默认情况下预先实例化所有单例。因此,如果你有一个(父) Bean 定义<br/>,并且这个定义指定了一个类,那么<br/>是很重要的(至少对于单例 bean 来说是如此),你必须确保将*摘要*属性设置为*true*,否则,应用程序<br/>上下文实际上(试图)预先实例化`abstract` Bean。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 1.8.容器延伸点

通常,应用程序开发人员不需要子类`ApplicationContext`实现类。相反, Spring IOC 容器可以通过插入特殊集成接口的实现来扩展。接下来的几节将描述这些集成接口。

#### 1.8.1.使用`BeanPostProcessor`自定义 bean

`BeanPostProcessor`接口定义了回调方法,你可以实现这些方法来提供自己的(或覆盖容器的默认)实例化逻辑、依赖项解析逻辑,等等。如果希望在 Spring 容器完成实例、配置和初始化 Bean 之后实现一些自定义逻辑,则可以插入一个或多个自定义`BeanPostProcessor`实现。

你可以配置多个`BeanPostProcessor`实例,并且可以通过设置`order`属性来控制这些`BeanPostProcessor`实例的运行顺序。只有当`BeanPostProcessor`实现`Ordered`接口时,才可以设置此属性。如果你编写自己的`BeanPostProcessor`,那么你也应该考虑实现`Ordered`接口。有关更多详细信息,请参见[`BeanPostProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/config/Beanpostprocessor.html)和[`Ordered`](https://DOCS. Spring.io/ Spring/ Spring-framework/DOCS/5.3.16/javadoc-api/org/org/org.springframework/core/core.html)的 爪哇doc 另请参见关于[编程注册`BeanPostProcessor`实例](#beans-factory-programmatic-registing-beanpostprocessors)的说明。

|   |`BeanPostProcessor`实例对 Bean(或对象)实例进行操作。即,<br/> Spring IOC 容器实例化一个 Bean 实例,然后`BeanPostProcessor`实例执行它们的工作。<br/><br/>`BeanPostProcessor`实例是每个容器的作用域。只有当你<br/>使用容器层次结构时,这才是相关的。如果你在一个容器中定义了`BeanPostProcessor`,则<br/>只对该容器中的 bean 进行后处理。换句话说,在一个容器中定义<br/>的 bean 不会被在<br/>中定义的`BeanPostProcessor`另一个容器进行后处理,即使这两个容器都是同一层次结构的一部分。<br/><br/>来更改实际的 Bean 定义(即,定义 Bean)的蓝图,<br/>相反,你需要使用`BeanFactoryPostProcessor`,如[使用`BeanFactoryPostProcessor`定制配置元数据](#beans-factory-extension-factory-postprocessors)中所述。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

`org.springframework.beans.factory.config.BeanPostProcessor`接口正好由两个回调方法组成。当这样的类在容器中注册为后处理器时,对于由容器创建的每个 Bean 实例,后处理器在调用容器初始化方法(例如`InitializingBean.afterPropertiesSet()`或任何声明的`init`方法)之前都会从容器获得一个回调,并且在任何 Bean 初始化回调之后。后处理器可以对 Bean 实例采取任何操作,包括完全忽略回调。 Bean 后处理器通常检查回调接口,或者它可以用代理包装 Bean。一些 Spring  AOP 基础设施类被实现为 Bean 后处理器,以便提供代理包装逻辑。

`ApplicationContext`会自动检测在实现`BeanPostProcessor`接口的配置元数据中定义的任何 bean。`ApplicationContext`将这些 bean 注册为后处理器,以便在 Bean 创建时可以稍后调用它们。 Bean 后处理器可以以与任何其他 bean 相同的方式部署在容器中。

注意,当通过在配置类上使用`@Bean`工厂方法声明`BeanPostProcessor`时,工厂方法的返回类型应该是实现类本身,或者至少是`org.springframework.beans.factory.config.BeanPostProcessor`接口,这清楚地表明了该 Bean 的后处理器性质。否则,`ApplicationContext`在完全创建它之前无法通过类型自动检测它。由于`BeanPostProcessor`需要早期实例化,以便在上下文中应用于其他 bean 的初始化,因此这种早期类型检测是至关重要的。

|   |通过编程方式注册`BeanPostProcessor`实例<br/><br/>而推荐的`BeanPostProcessor`注册方法是通过`ApplicationContext`自动检测(如前所述),你可以通过使用<br/>方法对`ConfigurableBeanFactory`实例进行编程注册。当你需要在<br/>注册之前计算条件逻辑时,或者甚至在层次结构中跨上下文复制 Bean 后处理程序时,这可能是有用的。<br/>但是,以编程方式添加的`BeanPostProcessor`实例并不尊重`Ordered`接口。在这里,登记的顺序决定了执行的顺序<br/>。还请注意,无论是否有任何<br/>显式排序,以编程方式注册的<br/>实例总是在通过自动检测注册的实例之前进行处理。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |`BeanPostProcessor`实例和 AOP 自动代理<br/><br/>实现`BeanPostProcessor`接口的类是特殊的,并且被容器以不同的方式处理<br/>。它们<br/>直接引用的所有`BeanPostProcessor`实例和 bean 都在启动时实例化,作为`ApplicationContext`的特殊启动阶段<br/>的一部分。接下来,所有`BeanPostProcessor`实例都以排序的方式注册<br/>,并应用于容器中的所有其他 bean。因为 AOP <br/>自动代理被实现为`BeanPostProcessor`本身,所以`BeanPostProcessor`实例和它们直接引用的 bean 都不符合自动代理的条件,并且,<br/>因此,它们中没有交织的方面。对于任何这样的 Bean,<br/><gt r=“1994”/,你应该看到一个信息日志消息:`Bean someBean is not<br/>eligible for getting processed by all BeanPostProcessor interfaces (for example: not<br/>eligible for auto-proxying)`。<br/><br/>如果你的`BeanPostProcessor`中有通过使用自动连接或`@Resource`连接的 bean(它可能返回到自动连接), Spring 在搜索类型匹配候选项时可能会访问意外的 bean<br/>,因此,使它们<br/>没有资格进行自动代理或其他类型的 Bean 后处理。例如,如果你的<br/>有一个用`@Resource`注释的依赖项,其中字段或 setter 名称不<br/>直接对应于 Bean 的声明名称,并且没有使用 name 属性,则<br/> Spring 访问其他 bean,以便根据类型对其进行匹配。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例展示了如何在`ApplicationContext`中编写、注册和使用`BeanPostProcessor`实例。

#####  示例:Hello World,`BeanPostProcessor`-style

第一个例子说明了基本用法。该示例显示了一个自定义的`BeanPostProcessor`实现,该实现在容器创建每个 Bean 的时候调用`toString()`方法,并将生成的字符串打印到系统控制台。

下面的清单显示了自定义`BeanPostProcessor`实现类定义:

爪哇

```
package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
```

Kotlin

```
import org.springframework.beans.factory.config.BeanPostProcessor

class InstantiationTracingBeanPostProcessor : BeanPostProcessor {

    // simply return the instantiated bean as-is
    override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
        return bean // we could potentially return any object reference here...
    }

    override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
        println("Bean '$beanName' created : $bean")
        return bean
    }
}
```

下面的`beans`元素使用`InstantiationTracingBeanPostProcessor`:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>
```

注意`InstantiationTracingBeanPostProcessor`仅仅是如何定义的。它甚至没有名称,并且,因为它是 Bean,所以它可以像其他任何 Bean 一样被注入依赖项。(前面的配置还定义了由 Groovy 脚本支持的 Bean。 Spring 动态语言支持在标题为[动态语言支持](languages.html#dynamic-language)的章节中进行了详细介绍。)

下面的 爪哇 应用程序运行前面的代码和配置:

爪哇

```
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}
```

Kotlin

```
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
    val messenger = ctx.getBean<Messenger>("messenger")
    println(messenger)
}
```

上述应用程序的输出类似于以下内容:

```
Bean 'messenger' created : [email protected]
[email protected]
```

#####  示例:`AutowiredAnnotationBeanPostProcessor`

结合自定义`BeanPostProcessor`实现使用回调接口或注释是扩展 Spring IoC 容器的一种常见方法。一个例子是 Spring 的`AutowiredAnnotationBeanPostProcessor`—一个`BeanPostProcessor`实现,该实现附带 Spring 分发和 AutoWires 注释字段、setter 方法和任意配置方法。

#### 1.8.2.使用`BeanFactoryPostProcessor`自定义配置元数据 ###

我们看的下一个扩展点是`org.springframework.beans.factory.config.BeanFactoryPostProcessor`。该接口的语义与`BeanPostProcessor`相似,但有一个主要区别:`BeanFactoryPostProcessor`对 Bean 配置元数据进行操作。也就是说, Spring IOC 容器允许`BeanFactoryPostProcessor`读取配置元数据,并可能更改它*在此之前*容器实例化除`BeanFactoryPostProcessor`实例之外的任何 bean。

你可以配置多个`BeanFactoryPostProcessor`实例,并且可以通过设置`order`属性来控制这些`BeanFactoryPostProcessor`实例的运行顺序。但是,你只能在`BeanFactoryPostProcessor`实现`Ordered`接口的情况下设置此属性。如果你编写自己的`BeanFactoryPostProcessor`,那么也应该考虑实现`Ordered`接口。参见[`BeanFactoryPostProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/config/beanfactorypostprocessor.html)和[`Ordered`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api-api/org/coreframework/core/correed.html)接口的 爪哇doc-framewor

|   |如果你想要更改实际的 Bean 实例(即,从配置元数据中创建<br/>的对象),那么你需要使用`BeanPostProcessor`(前面在[通过使用`BeanPostProcessor`来定制 bean](#beans-factory-extension-bpp)中描述)。虽然从技术上讲,<br/>可以在`BeanFactoryPostProcessor`中使用 Bean 实例(例如,通过使用`BeanFactory.getBean()`),但这样做会导致过早的 Bean 实例化,违反<br/>标准容器生命周期。这可能会导致负面的副作用,例如绕过<br/> Bean 后处理。<br/><br/>此外,`BeanFactoryPostProcessor`实例是每个容器的作用域。如果使用容器层次结构,这仅与<br/>相关。如果在一个<br/>容器中定义`BeanFactoryPostProcessor`,则它仅应用于该容器中的 Bean 定义。 Bean 在一个容器中的定义<br/>不是由另一个`BeanFactoryPostProcessor`容器中的<br/>实例进行后处理的,即使这两个容器都是同一层次结构的一部分。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Bean 工厂后处理器在`ApplicationContext`内声明时自动运行,以便对定义容器的配置元数据应用更改。 Spring 包括许多预定义的工厂后处理器,例如和。你还可以使用自定义`BeanFactoryPostProcessor`——例如,注册自定义属性编辑器。

`ApplicationContext`会自动检测部署到其中的、实现`BeanFactoryPostProcessor`接口的任何 bean。它在适当的时候使用这些 bean 作为 Bean 工厂的后处理器。你可以部署这些后处理器 bean,就像部署任何其他的 Bean。

|   |与`BeanPostProcessor`s 一样,你通常不希望为惰性初始化配置`BeanFactoryPostProcessor`s。如果没有其他 Bean 引用`Bean(Factory)PostProcessor`,则该后处理器将根本不会实例化。因此,将其标记为惰性初始化将被忽略,并且即使在你的`default-lazy-init`元素的声明上将`<beans />`属性设置为`true`,也将急切地实例化`<beans />`属性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  示例:类名替换`PropertySourcesPlaceholderConfigurer`####

通过使用标准的 爪哇`Properties`格式,可以使用`PropertySourcesPlaceholderConfigurer`将 Bean 定义中的属性值外部化到单独的文件中。这样做可以使部署应用程序的人员定制特定于环境的属性,例如数据库 URL 和密码,而无需修改主 XML 定义文件或容器的文件的复杂性或风险。

考虑以下基于 XML 的配置元数据片段,其中定义了带有占位符的`DataSource`值:

```
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
```

该示例显示了从外部`Properties`文件配置的属性。在运行时,将`PropertySourcesPlaceholderConfigurer`应用于元数据,以替换数据源的某些属性。要替换的值被指定为`${property-name}`形式的占位符,它遵循 Ant 和 log4j 和 JSP EL 样式。

实际值来自另一个标准 爪哇`Properties`格式的文件:

```
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
```

因此,`${jdbc.username}`字符串在运行时被替换为值“sa”,这同样适用于匹配属性文件中的键的其他占位符。在 Bean 定义的大多数属性和属性中,`PropertySourcesPlaceholderConfigurer`检查占位符。此外,你还可以自定义占位符前缀和后缀。

使用 Spring 2.5 中引入的`context`名称空间,你可以使用一个专用的配置元素来配置属性占位符。可以在`location`属性中以逗号分隔的列表形式提供一个或多个位置,如下例所示:

```
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
```

`PropertySourcesPlaceholderConfigurer`不仅查找你指定的`Properties`文件中的属性。默认情况下,如果它无法在指定的属性文件中找到属性,则会检查 Spring `Environment`属性和常规的 爪哇`System`属性。

|   |你可以使用`PropertySourcesPlaceholderConfigurer`来替换类名,当你必须在运行时选择特定的实现类时,这<br/>有时是有用的,<br/>下面的示例展示了如何这样做:<br/>```<br/><bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer"><br/>    <property name="locations"><br/>        <value>classpath:com/something/strategy.properties</value><br/>    </property><br/>    <property name="properties"><br/>        <value>custom.strategy.class=com.something.DefaultStrategy</value><br/>    </property><br/></bean><br/><br/><bean id="serviceStrategy" class="${custom.strategy.class}"/><br/>```<br/>如果在运行时无法将类解析为有效的类,<br/>, Bean <br/>的解析在它即将被创建时失败,这是在`preInstantiateSingletons()`对于非惰性-init 的`ApplicationContext`阶段期间 Bean。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  示例:`PropertyOverrideConfigurer`

另一个 Bean 工厂后处理器`PropertyOverrideConfigurer`类似于`PropertySourcesPlaceholderConfigurer`,但与后者不同的是,对于 Bean 属性,原始定义可以有默认值,也可以完全没有值。如果覆盖的`Properties`文件没有特定 Bean 属性的条目,则使用默认的上下文定义。

请注意, Bean 定义并不知道是否被重写,因此从 XML 定义文件中不能立即看出是否正在使用重写配置器。在多个`PropertyOverrideConfigurer`实例为相同的 Bean 属性定义不同的值的情况下,由于覆盖机制,最后一个实例获胜。

属性文件配置行采用以下格式:

```
beanName.property=value
```

下面的清单展示了这种格式的一个示例:

```
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
```

这个示例文件可以与容器定义一起使用,该容器定义包含一个名为`dataSource`的 Bean,该 Bean 具有`driver`和`url`属性。

复合属性名称也是受支持的,只要路径的每个组件(除了被重写的最终属性)已经是非空的(大概是由构造函数初始化的)。在下面的示例中,将`tom` Bean 的`sammy`属性的`bob`属性的`fred`属性设置为标量值`123`:

```
tom.fred.bob.sammy=123
```

|   |指定的重写值总是文字值。它们没有被翻译成<br/> Bean 引用。当 XML Bean <br/>定义中的原始值指定 Bean 引用时,该约定也适用。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

通过 Spring 2.5 中引入的`context`名称空间,可以使用专用的配置元素配置属性重写,如下例所示:

```
<context:property-override location="classpath:override.properties"/>
```

#### 1.8.3.使用`FactoryBean`自定义实例化逻辑

你可以为对象本身是工厂的对象实现`org.springframework.beans.factory.FactoryBean`接口。

`FactoryBean`接口是可插入 Spring IoC 容器的实例化逻辑的一个点。如果你的复杂初始化代码更好地用 爪哇 来表示,而不是(可能)使用冗长的 XML,那么你可以创建自己的`FactoryBean`,在该类中编写复杂的初始化,然后将你的自定义`FactoryBean`插入到容器中。

`FactoryBean<T>`接口提供了三种方法:

* `T getObject()`:返回这个工厂创建的对象的实例。这个实例可以被共享,这取决于这个工厂返回的是单例还是原型。

* `boolean isSingleton()`:如果这个`FactoryBean`返回单例,或者`false`否则返回`true`。此方法的默认实现返回`true`。

* `Class<?> getObjectType()`:返回由`getObject()`方法返回的对象类型或`null`如果类型事先不知道的话。

`FactoryBean`概念和接口在 Spring 框架内的许多地方被使用。50 多个实现`FactoryBean`接口与 Spring 本身相关联。

当需要向容器请求一个实际的`FactoryBean`实例本身而不是它生成的 Bean 实例时,在调用`ApplicationContext`的`id`方法时,在 Bean 的`getBean()`前加上符号(`&`)。因此,对于给定的`FactoryBean`和`id`的`myBean`,在容器上调用`getBean("myBean")`将返回`FactoryBean`的乘积,而调用`getBean("&myBean")`将返回`FactoryBean`实例本身。

### 1.9.基于注释的容器配置

在配置 Spring 方面,注释是否比 XML 更好?

基于注释的配置的引入提出了这样一个问题:这种方法是否比 XML“更好”。简短的回答是“视情况而定。”长期的答案是,每种方法都有其优点和缺点,通常,由开发人员来决定哪种策略更适合他们。由于它们的定义方式,注释在其声明中提供了大量的上下文,从而导致更短和更简洁的配置。然而,XML 擅长将组件连接起来,而不会涉及它们的源代码或重新编译它们。一些开发人员更喜欢让连接靠近源代码,而另一些开发人员则认为,带注释的类不再是 POJO,此外,配置变得分散且更难控制。

无论如何选择, Spring 都可以容纳这两种风格,甚至可以将它们混合在一起。值得指出的是,通过其[爪哇Config](#beans-java)选项, Spring 允许以非侵入性的方式使用注释,而不涉及目标组件源代码,并且,就工具而言,[Spring Tools for Eclipse](https://spring.io/tools)支持所有配置样式。

基于注释的配置提供了 XML 设置的另一种选择,它依赖字节码元数据来连接组件,而不是角括号声明。开发人员不使用 XML 来描述 Bean 连接,而是通过使用相关类、方法或字段声明上的注释将配置移动到组件类本身。正如在[示例:the`AutowiredAnnotationBeanPostProcessor`](#beans-factory-extension-bpp-examples-aabpp)中提到的,结合注释使用`BeanPostProcessor`是扩展 Spring IOC 容器的一种常见方法。例如, Spring 2.0 引入了使用[`@Required`](#beans-required-annotation)注释强制执行所需属性的可能性。 Spring 2.5 使得有可能遵循相同的通用方法来驱动 Spring 的依赖注入。从本质上讲,`@Autowired`注释提供了与[自动布线合作者](#beans-factory-autowire)中描述的相同的功能,但具有更细粒度的控制和更广泛的适用性。 Spring 2.5 还增加了对 JSR-250 注释的支持,例如`@PostConstruct`和`@PreDestroy`。 Spring 3.0 增加了对`javax.inject`包中包含的 JSR-330(用于 爪哇 的依赖注入)注释的支持,例如`@Inject`和`@Named`。有关这些注释的详细信息可以在[相关部分](#beans-standard-annotations)中找到。

|   |注释注入是在 XML 注入之前执行的。因此,XML 配置<br/>覆盖了通过这两种方法连接的属性的注释。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------|

与往常一样,可以将后处理器注册为单独的 Bean 定义,但也可以通过在基于 XML 的 Spring 配置中包含以下标记来隐式地注册它们(请注意包含`context`名称空间):

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>
```

`<context:annotation-config/>`元素隐式地注册了以下后处理程序:

* [`ConfigurationClassPostProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/configurationclasspostprocessor.html)

* [`AutowiredAnnotationBeanPostProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/annotation/autowiredannotationbeanpostprocessor.html)

* [`CommonAnnotationBeanPostProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/commonannotationbeanpostprocessor.html)

* [`PersistenceAnnotationBeanPostProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/orm/ JPA/support/persistenceannotationbeanpostprocessor.html)

* [`EventListenerMethodProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/event/eventlistenermethodprocessor.html)

|   |`<context:annotation-config/>`只在与其定义相同的<br/>应用程序上下文中查找 bean 上的注释。这意味着,如果将`<context:annotation-config/>`放入`WebApplicationContext`中的`DispatcherServlet`,则<br/>只检查控制器中的`@Autowired`bean,而不是你的服务。有关更多信息,请参见[DispatcherServlet](web.html#mvc-servlet)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.9.1.@required

`@Required`注释适用于 Bean 属性设置器方法,如下例所示:

爪哇

```
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

Kotlin

```
class SimpleMovieLister {

    @Required
    lateinit var movieFinder: MovieFinder

    // ...
}
```

该注释表明,受影响的 Bean 属性必须在配置时通过 Bean 定义中的显式属性值或通过自动布线来填充。如果未填充受影响的 Bean 属性,则容器抛出一个异常。这允许急切和明确的失败,避免`NullPointerException`实例或类似的情况。我们仍然建议你将断言放入 Bean 类本身(例如,放入 init 方法)。即使在容器之外使用类,也要执行这些所需的引用和值。

|   |[`RequiredAnnotationBeanPostProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/annotation/requiredannotationbeanpostprocessor.html)必须注册为 Bean,才能支持`@Required`注释。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |在 Spring Framework5.1 中,`@Required`注释和<br/>正式地被<br/>弃用,这有利于使用构造函数注入<br/>所需的设置(或`InitializingBean.afterPropertiesSet()`的自定义实现,或带有 Bean 属性设定器方法的自定义`@PostConstruct`方法)。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.9.2.使用`@Autowired`

|   |JSR330 的`@Inject`注释可以用来代替 Spring 的`@Autowired`注释,在这一节包含的<br/>示例中。有关更多详细信息,请参见[here](#beans-standard-annotations)。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你可以将`@Autowired`注释应用于构造函数,如下例所示:

爪哇

```
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
```

Kotlin

```
class MovieRecommender @Autowired constructor(
    private val customerPreferenceDao: CustomerPreferenceDao)
```

|   |在 Spring Framework4.3 中,如果目标 Bean 只定义了一个要开始使用的构造函数,那么在这样的构造函数上的`@Autowired`注释就不再是<br/>所必需的了。但是,如果<br/>有几个构造函数可用,并且没有主/缺省构造函数,则至少<br/>其中一个构造函数必须用`@Autowired`进行注释,以便指示<br/>容器使用哪个。详见[构造器分辨率](#beans-autowired-annotation-constructor-resolution)讨论。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

还可以将`@Autowired`注释应用于*传统的*setter 方法,如下例所示:

爪哇

```
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

Kotlin

```
class SimpleMovieLister {

    @Autowired
    lateinit var movieFinder: MovieFinder

    // ...

}
```

你还可以将注释应用于具有任意名称和多个参数的方法,如下例所示:

爪哇

```
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
```

Kotlin

```
class MovieRecommender {

    private lateinit var movieCatalog: MovieCatalog

    private lateinit var customerPreferenceDao: CustomerPreferenceDao

    @Autowired
    fun prepare(movieCatalog: MovieCatalog,
                customerPreferenceDao: CustomerPreferenceDao) {
        this.movieCatalog = movieCatalog
        this.customerPreferenceDao = customerPreferenceDao
    }

    // ...
}
```

你也可以将`@Autowired`应用于字段,甚至可以将其与构造函数混合使用,如下例所示:

爪哇

```
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
```

Kotlin

```
class MovieRecommender @Autowired constructor(
    private val customerPreferenceDao: CustomerPreferenceDao) {

    @Autowired
    private lateinit var movieCatalog: MovieCatalog

    // ...
}
```

|   |确保你的目标组件(例如,`MovieCatalog`或`CustomerPreferenceDao`)<br/>由你为`@Autowired`使用的类型一致地声明-注释<br/>注入点。否则,注入可能会由于运行时的“未找到类型匹配”错误而失败。<br/><br/>对于通过 Classpath 扫描找到的 XML 定义的 bean 或组件类,容器<br/>通常预先知道具体的类型。但是,对于`@Bean`工厂方法,你需要<br/>来确保声明的返回类型具有足够的表达能力。对于实现多个接口的组件<br/>,或者对于其<br/>实现类型可能引用的组件,考虑在工厂<br/>方法上声明最特定的返回类型(至少与引用你的 Bean 的注入点所要求的特定)。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

还可以指示 Spring 从`ApplicationContext`提供特定类型的所有 bean,方法是将`@Autowired`注释添加到需要该类型数组的字段或方法,如下例所示:

爪哇

```
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}
```

Kotlin

```
class MovieRecommender {

    @Autowired
    private lateinit var movieCatalogs: Array<MovieCatalog>

    // ...
}
```

这同样适用于类型化集合,如下例所示:

爪哇

```
public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
```

Kotlin

```
class MovieRecommender {

    @Autowired
    lateinit var movieCatalogs: Set<MovieCatalog>

    // ...
}
```

|   |你的目标 bean 可以实现`org.springframework.core.Ordered`接口或使用<br/>`@Order`或标准`@Priority`注释,如果你希望将数组或列表中的项<br/>按特定顺序排序的话。否则,它们的顺序遵循在容器中对应的目标 Bean 定义的注册顺序<br/>。<br/><br/>可以在目标类级别和`@Order`方法上声明<br/>注释,<br/>可能用于单独的 Bean 定义(在多个定义<br/>使用相同的 Bean 类的情况下)。`@Order`值可能会影响注入点的优先级,<br/>但要注意,它们不会影响单例启动顺序,这是由依赖关系和`@DependsOn`声明确定的正交关系。<br/><br/>注意,标准`javax.annotation.Priority`注释在<gt="2213"/>级别不可用。因为它不能在方法上声明。它的语义可以通过<br/>值与`@Order`值结合,在单个 Bean 上为每个类型建模`@Primary`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

只要预期的键类型是`String`,即使是类型为`Map`的实例也可以自动连线。映射值包含预期类型的所有 bean,键包含相应的 Bean 名称,如下例所示:

爪哇

```
public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
```

Kotlin

```
class MovieRecommender {

    @Autowired
    lateinit var movieCatalogs: Map<String, MovieCatalog>

    // ...
}
```

默认情况下,当给定的注入点没有匹配的候选 bean 可用时,自动布线失败。在声明的数组、集合或映射的情况下,需要至少一个匹配元素。

默认的行为是将带注释的方法和字段视为指示所需的依赖项。你可以改变这种行为,如下例所示,通过将不可满足的注入点标记为非必需的(即,将`required`中的`required`属性设置为`false`),使框架能够跳过不可满足的注入点:

爪哇

```
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

Kotlin

```
class SimpleMovieLister {

    @Autowired(required = false)
    var movieFinder: MovieFinder? = null

    // ...
}
```

如果一个非必需的方法的依赖项(或者在多个参数的情况下,它的一个依赖项)是不可用的,那么它将不会被调用。在这种情况下,不需要的字段将根本不会被填充,从而保留其默认值。

注入的构造函数和工厂方法参数是一种特殊情况,因为`required`中的`required`属性具有某种不同的含义,这是由于 Spring 的构造函数解析算法可能处理多个构造函数。默认情况下,构造函数和工厂方法参数实际上是必需的,但在单个构造函数场景中需要一些特殊的规则,例如,如果没有匹配的 bean 可用,则将多元素注入点(数组、集合、映射)解析为空实例。这允许一种常见的实现模式,在这种模式中,所有依赖项都可以在唯一的多参数构造函数中声明——例如,在没有`@Autowired`注释的情况下,声明为单个公共构造函数。

|   |Bean 类中只有一个构造函数可以声明`@Autowired`,其`required`属性设置为`true`,表示*The*构造函数在用作 Spring <br/> Bean 时自动连接。因此,如果`required`属性保留在其默认值`true`处,<br/>只能用`@Autowired`注释单个构造函数。如果多个构造函数<br/>声明注释,它们都必须声明`required=false`,以便将<br/>视为自动布线的候选项(类似于`autowire=constructor`在 XML 中)。<br/>将选择通过在 Spring 容器中匹配<br/>bean 而能够满足最多依赖项的构造函数。如果不能满足任何一个候选项,<br/>则将使用一个主/缺省构造函数(如果存在)。类似地,如果类<br/>声明了多个构造函数,但其中没有一个被`@Autowired`注释,那么将使用<br/>主/缺省构造函数(如果存在)。如果一个类只声明一个<br/>构造函数,那么它将始终被使用,即使没有注释。注意,一个<br/>带注释的构造函数不一定是公共的。<br/><br/>`required`的`required`属性推荐使用在 setter 方法上不推荐的`@Required`注释。将`required`属性设置为`false`表示<br/>属性不需要自动布线,如果<br/>不能自动布线,则忽略该属性。另一方面,`@Required`更强,因为它强制使用容器支持的任何方式来设置<br/>属性,并且如果没有定义值,<br/>将引发相应的异常。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

或者,你可以通过 爪哇8 的`java.util.Optional`表示特定依赖项的非必需性质,如下例所示:

```
public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
```

在 Spring Framework5.0 中,你还可以使用`@Nullable`注释(在任何包中的任何类型——例如,来自 JSR-305 的`javax.annotation.Nullable`)或仅利用 Kotlin 内建空安全支持:

爪哇

```
public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}
```

Kotlin

```
class SimpleMovieLister {

    @Autowired
    var movieFinder: MovieFinder? = null

    // ...
}
```

对于众所周知的可解析依赖关系的接口,也可以使用`@Autowired`:`BeanFactory`,`ApplicationContext`,`Environment`,`ResourceLoader`,`ApplicationEventPublisher`,和`MessageSource`。这些接口及其扩展接口,例如`ConfigurableApplicationContext`或`ResourcePatternResolver`,是自动解析的,无需特殊设置。下面的示例自动连接`ApplicationContext`对象:

爪哇

```
public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}
```

Kotlin

```
class MovieRecommender {

    @Autowired
    lateinit var context: ApplicationContext

    // ...
}
```

|   |`@Autowired`、`@Inject`、`@Value`和`@Resource`注释由 Spring `BeanPostProcessor`实现来处理。这意味着你不能在你自己的`BeanPostProcessor`或`BeanFactoryPostProcessor`类型(如果有的话)中应用这些注释。<br/>这些类型必须通过使用 XML 或 Spring `@Bean`方法显式地“连接起来”。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.9.3.使用`@Primary`微调基于注释的自动连线

由于按类型自动布线可能会导致多个候选者,因此通常需要对选择过程有更多的控制。实现这一点的一种方法是使用 Spring 的`@Primary`注释。`@Primary`表示当多个 bean 是要自动连接到单值依赖项的候选 bean 时,应该给予特定的 Bean 优先权。如果在候选者之间正好存在一个主 Bean,则它成为自动连线值。

考虑以下配置,该配置将`firstMovieCatalog`定义为主要的`MovieCatalog`:

爪哇

```
@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}
```

Kotlin

```
@Configuration
class MovieConfiguration {

    @Bean
    @Primary
    fun firstMovieCatalog(): MovieCatalog { ... }

    @Bean
    fun secondMovieCatalog(): MovieCatalog { ... }

    // ...
}
```

通过上述配置,以下`MovieRecommender`与`firstMovieCatalog`自动连线:

爪哇

```
public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
```

Kotlin

```
class MovieRecommender {

    @Autowired
    private lateinit var movieCatalog: MovieCatalog

    // ...
}
```

相应的 Bean 定义如下:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
```

#### 1.9.4.使用限定符对基于注释的自动连线进行微调

`@Primary`是一种在可以确定一个主要候选者的情况下,通过多个实例使用自动布线的有效方法。当需要对选择过程进行更多控制时,可以使用 Spring 的`@Qualifier`注释。你可以将限定符值与特定的参数关联起来,从而缩小类型匹配的集合,以便为每个参数选择一个特定的 Bean。在最简单的情况下,这可以是一个简单的描述性值,如以下示例所示:

爪哇

```
public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}
```

Kotlin

```
class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private lateinit var movieCatalog: MovieCatalog

    // ...
}
```

你还可以对单个构造函数参数或方法参数指定`@Qualifier`注释,如以下示例所示:

爪哇

```
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
```

Kotlin

```
class MovieRecommender {

    private lateinit var movieCatalog: MovieCatalog

    private lateinit var customerPreferenceDao: CustomerPreferenceDao

    @Autowired
    fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
                customerPreferenceDao: CustomerPreferenceDao) {
        this.movieCatalog = movieCatalog
        this.customerPreferenceDao = customerPreferenceDao
    }

    // ...
}
```

下面的示例显示了相应的 Bean 定义。

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> (1)

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> (2)

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
```

|**1**|具有`main`限定符值的 Bean 与构造函数参数连线,该构造函数参数<br/>具有相同的限定符值。|
|-----|----------------------------------------------------------------------------------------------------------------------------|
|**2**|具有`action`限定符值的 Bean 与构造函数参数连线,该构造函数参数<br/>具有相同的限定符值。|

对于回退匹配, Bean 名称被认为是缺省限定符值。因此,可以使用`main`的`id`来定义 Bean,而不是使用嵌套的限定符元素,从而导致相同的匹配结果。然而,尽管可以使用此约定按名称引用特定的 bean,但`@Autowired`从根本上讲是关于使用可选语义限定符的类型驱动注入。这意味着限定符值,即使使用 Bean name fallback,在类型匹配集合中也始终具有缩小的语义。它们在语义上不表示对唯一 Bean `id`的引用。好的限定符值是`main`或`EMEA`或`persistent`,表示独立于 Bean `id`的特定组件的特征,在匿名 Bean 定义的情况下,例如前面示例中的定义,该特征可以自动生成。

限定符也适用于类型化集合,如前面所讨论的——例如,适用于`Set<MovieCatalog>`。在本例中,根据声明的限定符,所有匹配的 bean 都作为集合注入。这意味着限定词不必是唯一的。相反,它们构成了过滤标准。例如,你可以使用相同的限定符值“action”来定义多个`MovieCatalog`bean,所有这些 bean 都被注入到用`Set<MovieCatalog>`注释的`@Qualifier("action")`中。

|   |在类型匹配的<br/>候选项中,让限定符值相对于目标 Bean 名称进行选择,不需要在注入点进行`@Qualifier`注释。<br/>如果没有其他分辨率指示符(例如限定符或主标记),则对于非惟一依赖关系情况,<br/>, Spring 将注入点名称<br/>(即字段名称或参数名称)与目标 Bean 名称匹配,并选择<br/>相同名称的候选项,如果有的话。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

也就是说,如果打算通过名称来表示注释驱动的注入,则不要主要使用`@Autowired`,即使它能够通过 Bean 在类型匹配候选者中选择名称。相反,使用 JSR-250`@Resource`注释,该注释在语义上定义为通过其唯一的名称来标识特定的目标组件,声明的类型与匹配过程无关。`@Autowired`具有相当不同的语义:在按类型选择候选 bean 之后,指定的`String`限定符值仅被考虑在那些类型选择的候选值中(例如,将`account`限定符与标记有相同限定符标签的 bean 匹配)。

对于本身被定义为集合的 bean,`Map`或数组类型,`@Resource`是一个很好的解决方案,通过唯一的名称引用特定的集合或数组 Bean。也就是说,从 4.3 开始,也可以通过 Spring 的`@Autowired`类型匹配算法来匹配集合、`Map`和数组类型,只要元素类型信息保留在`@Bean`返回类型签名或集合继承层次结构中。在这种情况下,你可以使用限定符值在相同类型的集合中进行选择,如上一段所概述的那样。

截至 4.3,`@Autowired`还考虑用于注入的自引用(即,对当前注入的 Bean 的引用)。请注意,自我注入是一种回退。对其他组件的常规依赖总是具有优先权的。从这个意义上说,自我参照不参与常规的候选人选择,因此,特别是从来没有主要的。相反,它们最终总是排在最靠后的位置。在实践中,你应该仅将自引用作为最后的手段(例如,通过 Bean 的事务代理调用同一实例上的其他方法)。 Bean 在这样的场景中,考虑将受影响的方法分解为单独的委托。或者,可以使用`@Resource`,这可以获得返回当前 Bean 的代理的唯一名称。

|   |试图在相同的配置类上注入来自`@Bean`方法的结果实际上也是一个自引用场景。在实际需要的方法签名中(而不是配置类中的自动连线字段<br/>),懒惰地解析此类引用<br/>,或者将受影响的`@Bean`方法声明为`static`,<br/>将它们与包含的配置类实例及其生命周期分离。否则,这样的 bean 仅在后备阶段被考虑,在其他配置类上匹配的 bean<br/>被选为主要候选者(如果可用的话)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

`@Autowired`应用于字段、构造函数和多参数方法,允许在参数级别上通过限定符注释进行缩小。与此相反,`@Resource`仅支持具有单个参数的字段和 Bean 属性设置器方法。因此,如果注入目标是构造函数或多参数方法,则应该坚持使用限定符。

你可以创建自己的自定义限定符注释。为此,请定义一个注释,并在定义中提供`@Qualifier`注释,如下例所示:

爪哇

```
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}
```

Kotlin

```
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)
```

然后,你可以在 AutoWired 字段和参数上提供自定义限定符,如下例所示:

爪哇

```
public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}
```

Kotlin

```
class MovieRecommender {

    @Autowired
    @Genre("Action")
    private lateinit var actionCatalog: MovieCatalog

    private lateinit var comedyCatalog: MovieCatalog

    @Autowired
    fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
        this.comedyCatalog = comedyCatalog
    }

    // ...
}
```

接下来,可以为候选 Bean 定义提供信息。你可以添加`<qualifier/>`标记作为`<bean/>`标记的子元素,然后指定`type`和`value`以匹配自定义限定符注释。类型与注释的完全限定类名称匹配。另外,如果不存在名称冲突的风险,为了方便起见,你可以使用较短的类名。下面的示例演示了这两种方法:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
```

在[Classpath Scanning and Managed Components](#beans-classpath-scanning)中,你可以看到一种基于注释的替代方法,用于在 XML 中提供限定符元数据。具体见[提供带有注释的限定符元数据](#beans-scanning-qualifiers)。

在某些情况下,使用没有值的注释可能就足够了。当注释服务于更通用的目的并且可以跨几种不同类型的依赖关系应用时,这可能是有用的。例如,你可以提供一个脱机目录,当没有可用的 Internet 连接时可以对其进行搜索。首先,定义简单的注释,如下例所示:

爪哇

```
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}
```

Kotlin

```
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline
```

然后将注释添加到要自动连接的字段或属性中,如以下示例所示:

爪哇

```
public class MovieRecommender {

    @Autowired
    @Offline (1)
    private MovieCatalog offlineCatalog;

    // ...
}
```

|**1**|这一行添加了`@Offline`注释。|
|-----|-----------------------------------------|

Kotlin

```
class MovieRecommender {

    @Autowired
    @Offline (1)
    private lateinit var offlineCatalog: MovieCatalog

    // ...
}
```

|**1**|这一行添加了`@Offline`注释。|
|-----|-----------------------------------------|

现在 Bean 定义只需要一个限定符`type`,如下例所示:

```
<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> (1)
    <!-- inject any dependencies required by this bean -->
</bean>
```

|**1**|此元素指定限定符。|
|-----|-------------------------------------|

你还可以定义自定义限定符注释,这些注释除了接受简单的`value`属性之外,还接受命名属性。 Bean 如果随后在要自动连线的字段或参数上指定了多个属性值,则定义必须匹配所有这样的属性值才能被视为自动连线候选值。作为示例,请考虑以下注释定义:

爪哇

```
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}
```

Kotlin

```
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)
```

在这种情况下,`Format`是一个枚举,定义如下:

爪哇

```
public enum Format {
    VHS, DVD, BLURAY
}
```

Kotlin

```
enum class Format {
    VHS, DVD, BLURAY
}
```

要自动连接的字段将使用自定义限定符进行注释,并包括两个属性的值:`genre`和`format`,如下例所示:

爪哇

```
public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}
```

Kotlin

```
class MovieRecommender {

    @Autowired
    @MovieQualifier(format = Format.VHS, genre = "Action")
    private lateinit var actionVhsCatalog: MovieCatalog

    @Autowired
    @MovieQualifier(format = Format.VHS, genre = "Comedy")
    private lateinit var comedyVhsCatalog: MovieCatalog

    @Autowired
    @MovieQualifier(format = Format.DVD, genre = "Action")
    private lateinit var actionDvdCatalog: MovieCatalog

    @Autowired
    @MovieQualifier(format = Format.BLURAY, genre = "Comedy")
    private lateinit var comedyBluRayCatalog: MovieCatalog

    // ...
}
```

最后, Bean 定义应该包含匹配的限定符值。这个示例还演示了你可以使用 Bean 元属性而不是`<qualifier/>`元素。如果可用,则`<qualifier/>`元素及其属性优先,但是,如果不存在这样的限定符,则自动连接机制将落在`<meta/>`标记内提供的值上,如下例中的最后两个 Bean 定义所示:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>
```

#### 1.9.5.使用泛型作为自动连线限定符

除了`@Qualifier`注释之外,你还可以使用 爪哇 泛型类型作为一种隐含的限定形式。例如,假设你有以下配置:

爪哇

```
@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}
```

Kotlin

```
@Configuration
class MyConfiguration {

    @Bean
    fun stringStore() = StringStore()

    @Bean
    fun integerStore() = IntegerStore()
}
```

假设前面的 bean 实现了一个泛型接口(即`Store<String>`和`Store<Integer>`),则可以将`@Autowire`的`Store`接口和泛型用作限定符,如下例所示:

爪哇

```
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
```

Kotlin

```
@Autowired
private lateinit var s1: Store<String> // <String> qualifier, injects the stringStore bean

@Autowired
private lateinit var s2: Store<Integer> // <Integer> qualifier, injects the integerStore bean
```

当自动连接列表、`Map`实例和数组时,也会应用通用限定符。以下示例自动连接通用`List`:

爪哇

```
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
```

Kotlin

```
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private lateinit var s: List<Store<Integer>>
```

#### 1.9.6.使用`CustomAutowireConfigurer`

[`CustomAutowireConfigurer`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/annotation/customautowireconfigurer.html)是一个`BeanFactoryPostProcessor`,它允许你注册自己的自定义限定符注释类型,即使它们没有用 Spring 的`@Qualifier`注释。下面的示例展示了如何使用`CustomAutowireConfigurer`:

```
<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>
```

`AutowireCandidateResolver`通过以下方式确定自动连接候选项:

* 每个 Bean 定义的`autowire-candidate`值

* 在`<beans/>`元素上可用的任何`default-autowire-candidates`模式

* 存在`@Qualifier`注释和在`CustomAutowireConfigurer`中注册的任何自定义注释

当多个 bean 符合自动连接候选项的条件时,“主”的确定如下:如果在候选项中恰好有一个 Bean 定义将`primary`属性设置为`true`,则选中它。

#### 1.9.7.注入`@Resource`

Spring 还通过在字段或 Bean 属性设置器方法上使用 JSR-250注释()来支持注入。这是 爪哇 EE 中的一种常见模式:例如,在 JSF 管理的 Bean 和 JAX-WS 端点中。 Spring 对于 Spring 管理的对象也支持这种模式。

`@Resource`接受一个 name 属性。默认情况下, Spring 将该值解释为要注入的 Bean 名称。换句话说,它遵循副名语义,如下例所示:

爪哇

```
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") (1)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
```

|**1**|这一行注入一个`@Resource`。|
|-----|--------------------------------|

Kotlin

```
class SimpleMovieLister {

    @Resource(name="myMovieFinder") (1)
    private lateinit var movieFinder:MovieFinder
}
```

|**1**|这一行注入一个`@Resource`。|
|-----|--------------------------------|

如果没有显式指定名称,则缺省名称将从字段名称或 setter 方法派生。在字段的情况下,它采用字段名称。对于 setter 方法,它使用 Bean 属性名。下面的示例将把名为`movieFinder`的 Bean 注入到其 setter 方法中:

爪哇

```
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
```

Kotlin

```
class SimpleMovieLister {

    @Resource
    private lateinit var movieFinder: MovieFinder

}
```

|   |与注释一起提供的名称由`ApplicationContext`解析为 Bean 名称,其中`CommonAnnotationBeanPostProcessor`是知道的。<br/>如果配置 Spring 的[`SimpleJndiBeanFactory`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/jndi/support/support/simplejnfactory.html),则可以通过 JNDI 解析这些名称。但是,我们建议你依赖默认的行为,<br/>使用 Spring 的 JNDI 查找功能来保持间接的级别。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在不指定显式名称的`@Resource`用法的独占情况下,并且类似于`@Autowired`,`@Resource`找到一个主类型匹配,而不是一个特定的命名 Bean,并解决众所周知的可解析依赖项:`BeanFactory`,`ResourceLoader`,`ApplicationEventPublisher`,和`MessageSource`接口。

因此,在下面的示例中,`customerPreferenceDao`字段首先查找名为“CustomerPreferenceDAO”的 Bean,然后返回到类型`CustomerPreferenceDao`的主类型匹配:

爪哇

```
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; (1)

    public MovieRecommender() {
    }

    // ...
}
```

|**1**|基于已知的可解析依赖类型:`ApplicationContext`注入`context`字段。|
|-----|---------------------------------------------------------------------------------------------------|

Kotlin

```
class MovieRecommender {

    @Resource
    private lateinit var customerPreferenceDao: CustomerPreferenceDao

    @Resource
    private lateinit var context: ApplicationContext (1)

    // ...
}
```

|**1**|基于已知的可解析依赖类型:`ApplicationContext`注入`context`字段。|
|-----|---------------------------------------------------------------------------------------------------|

#### 1.9.8.使用`@Value`

`@Value`通常用于注入外部化属性:

爪哇

```
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}
```

Kotlin

```
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)
```

具有以下配置:

Java

```
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
```

Kotlin

```
@Configuration
@PropertySource("classpath:application.properties")
class AppConfig
```

以及下面的`application.properties`文件:

```
catalog.name=MovieCatalog
```

在这种情况下,`catalog`参数和字段将等于`MovieCatalog`值。

Spring 提供了一个默认的宽松内含价值解析器。它将尝试解析属性值,如果无法解析,则将注入属性名(例如`${catalog.name}`)作为该值。如果希望对不存在的值保持严格控制,则应该声明`PropertySourcesPlaceholderConfigurer` Bean,如下例所示:

Java

```
@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
```

|   |当使用 JavaConfig 配置`PropertySourcesPlaceholderConfigurer`时,`@Bean`方法必须是`static`。|
|---|---------------------------------------------------------------------------------------------------------------|

如果无法解决任何`${}`占位符,则使用上述配置可确保初始化失败。也可以使用`setPlaceholderPrefix`、`setPlaceholderSuffix`或`setValueSeparator`等方法来自定义占位符。

|   |Spring 在默认情况下,引导配置一个`PropertySourcesPlaceholderConfigurer` Bean,即<br/>将从`application.properties`和`application.yml`文件中获取属性。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Spring 提供的内置转换器支持允许自动处理简单的类型转换(例如到`Integer`或`int`)。多个逗号分隔的值可以自动转换为`String`数组,无需额外的工作。

可以提供如下默认值:

Java

```
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}
```

Kotlin

```
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)
```

Spring `BeanPostProcessor`在幕后使用`ConversionService`来处理将`String`中的`String`值转换为目标类型的过程。如果你想为自己的自定义类型提供转换支持,可以提供你自己的`ConversionService` Bean 实例,如下例所示:

Java

```
@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun conversionService(): ConversionService {
        return DefaultFormattingConversionService().apply {
            addConverter(MyCustomConverter())
        }
    }
}
```

当`@Value`包含一个[`SpEL`表达式]时,将在运行时动态计算值,如下例所示:

Java

```
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}
```

Kotlin

```
@Component
class MovieRecommender(
    @Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)
```

SPEL 还支持使用更复杂的数据结构:

Java

```
@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}
```

Kotlin

```
@Component
class MovieRecommender(
    @Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map<String, Int>)
```

#### 1.9.9.使用`@PostConstruct`和`@PreDestroy`

`CommonAnnotationBeanPostProcessor`不仅可以识别`@Resource`注释,还可以识别 JSR-250 生命周期注释:`javax.annotation.PostConstruct`和`javax.annotation.PreDestroy`。在 Spring 2.5 中引入的,对这些注释的支持为[初始化回调](#beans-factory-lifecycle-initializingbean)和[销毁回调](#beans-factory-lifecycle-disposablebean)中描述的生命周期回调机制提供了一种替代方案。如果`CommonAnnotationBeanPostProcessor`被注册在 Spring `ApplicationContext`内,则携带这些注释之一的方法在生命周期中的同一点被调用为相应的 Spring 生命周期接口方法或显式声明的回调方法。在下面的示例中,缓存在初始化时被预先填充,在销毁时被清除:

Java

```
public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}
```

Kotlin

```
class CachingMovieLister {

    @PostConstruct
    fun populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    fun clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}
```

有关结合各种生命周期机制的影响的详细信息,请参见[结合生命周期机制](#beans-factory-lifecycle-combined-effects)。

|   |和`@PostConstruct`一样,`@PostConstruct`和`@PreDestroy`注释类型是从 JDK6 到 8 的标准 Java 库中<br/>的一部分。然而,整个`javax.annotation`包在 JDK9 中与核心 Java 模块分离,并最终在<br/>JDK11 中被删除。如果需要,`javax.annotation-api`工件现在需要通过 Maven <br/>central 获得,只需像任何其他库一样添加到应用程序的 Classpath 中。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 1.10. Classpath 扫描和管理组件

本章中的大多数示例都使用 XML 来指定在 Spring 容器中产生每个`BeanDefinition`的配置元数据。上一节([基于注释的容器配置](#beans-annotation-config))演示了如何通过源级注释提供大量配置元数据。然而,即使在这些示例中,“基本” Bean 定义也是在 XML 文件中明确定义的,而注释仅驱动依赖注入。 Classpath 本节描述了用于通过扫描隐式地检测候选组件的选项。 Bean 候选组件是与筛选条件相匹配的类,并且具有与容器注册的相应定义。这消除了使用 XML 来执行 Bean 注册的需要。相反,你可以使用注释(例如,`@Component`)、AspectJ 类型表达式或你自己的自定义筛选条件来选择哪些类具有在容器中注册的 Bean 定义。

|   |从 Spring 3.0 开始, Spring JavaConfig 项目提供的许多特性都是<br/>核心 Spring 框架的一部分。这允许你使用 Java 来定义 bean,而不是使用传统的 XML 文件<br/>。看看`@Configuration`、`@Bean`、`@Import`和`@DependsOn`注释,以了解如何使用这些新特性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.10.1.`@Component`和进一步的原型注释

`@Repository`注释是满足存储库角色或原型(也称为数据访问对象或 DAO)的任何类的标记。此标记的用途之一是异常的自动转换,如[异常转换](data-access.html#orm-exception-translation)中所述。

Spring 提供了进一步的原型注释:`@Component`,`@Service`,和`@Controller`。`@Component`是任何 Spring-托管组件的通用原型。`@Repository`、`@Service`和`@Controller`是`@Component`对于更具体的用例(分别在持久性、服务层和表示层中)的专门化。因此,你可以使用`@Component`对组件类进行注释,但是,通过使用`@Repository`、`@Service`或`@Controller`对它们进行注释,你的类更适合于通过工具进行处理或与方面进行关联。例如,这些原型注释为切入点提供了理想的目标。`@Repository`、`@Service`和`@Controller`还可以在 Spring 框架的未来版本中携带额外的语义。因此,如果你在服务层使用`@Component`或`@Service`之间进行选择,`@Service`显然是更好的选择。类似地,如前所述,`@Repository`已经被支持作为持久性层中自动异常转换的标记。

#### 1.10.2.使用元注释和组合注释

Spring 提供的许多注释可以在你自己的代码中用作元注释。元注释是一种可以应用于另一种注释的注释。例如,所提到的`@Service`注释[earlier](#beans-stereotype-annotations)被元注释为`@Component`,如下例所示:

Java

```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {

    // ...
}
```

|**1**|`@Component`导致`@Service`被以与`@Component`相同的方式处理。|
|-----|---------------------------------------------------------------------------------|

Kotlin

```
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {

    // ...
}
```

|**1**|`@Component`导致`@Service`被以与`@Component`相同的方式处理。|
|-----|---------------------------------------------------------------------------------|

你还可以合并元注释来创建“组合注释”。例如,来自 Spring MVC 的`@RestController`注释由`@Controller`和`@ResponseBody`组成。

此外,合成注释可以选择从元注释中重新声明属性以允许定制。当你只想公开元注释属性的一个子集时,这可能特别有用。例如, Spring 的`@SessionScope`注释将作用域名称硬编码为`session`,但仍然允许自定义`proxyMode`。下面的清单显示了`SessionScope`注释的定义:

Java

```
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}
```

Kotlin

```
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
        @get:AliasFor(annotation = Scope::class)
        val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)
```

然后,你可以使用`@SessionScope`,而不必声明`proxyMode`,如下所示:

Java

```
@Service
@SessionScope
public class SessionScopedService {
    // ...
}
```

Kotlin

```
@Service
@SessionScope
class SessionScopedService {
    // ...
}
```

还可以重写`proxyMode`的值,如下例所示:

Java

```
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}
```

Kotlin

```
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
    // ...
}
```

有关更多详细信息,请参见[Spring Annotation Programming Model](https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model)维基页面。

#### 1.10.3.自动检测类并注册 Bean 定义

Spring 可以自动检测原型类并将与`BeanDefinition`实例相对应的`ApplicationContext`实例注册到`ApplicationContext`中。例如,以下两个类可以进行这种自动检测:

Java

```
@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
```

Kotlin

```
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
```

Java

```
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}
```

Kotlin

```
@Repository
class JpaMovieFinder : MovieFinder {
    // implementation elided for clarity
}
```

要自动检测这些类并注册相应的 bean,你需要将`@ComponentScan`添加到`@Configuration`类中,其中`basePackages`属性是这两个类的公共父包。(或者,你可以指定一个逗号或分号或空格分隔的列表,其中包括每个类的父包。

Java

```
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
```

Kotlin

```
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
    // ...
}
```

|   |为了简洁起见,前面的示例可以使用`value`注释(即`@ComponentScan("org.example")`)的`value`属性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------|

以下替代方案使用 XML:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>
```

|   |使用`<context:component-scan>`隐式启用了`<context:annotation-config>`的功能。在使用`<context:component-scan>`时,通常不需要包含`<context:annotation-config>`元素。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |Classpath 包的扫描需要在 Classpath 中存在对应的目录<br/>条目。当你使用 Ant 构建 JAR 时,请确保没有<br/>激活 jar 任务的仅文件开关。另外,在某些环境中,基于安全策略, Classpath 目录可能不会<br/>公开,例如,在<br/>jdk1.7.0\_45 或更高版本上的独立应用程序(这需要在你的清单中设置“信任库”——参见[https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources](https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources))。,<br/>在 JDK9 的模块路径(拼图)上<br/>, Spring 的 Classpath 扫描通常按预期工作。<br/>但是,请确保你的组件类在`module-info`描述符中导出。如果你希望 Spring 调用类的非公共成员,请确保<br/>它们是“打开的”(即它们在你的`opens`描述符中使用`exports`声明,而不是`module-info`声明)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

此外,当你使用组件扫描元素时,`AutowiredAnnotationBeanPostProcessor`和`CommonAnnotationBeanPostProcessor`都是隐式包含的。这意味着这两个组件是自动检测和连接在一起的——所有这些都没有 XML 提供的任何 Bean 配置元数据。

|   |你可以禁用`AutowiredAnnotationBeanPostProcessor`和`CommonAnnotationBeanPostProcessor`的注册,方法是使用`annotation-config`属性<br/>,其值为`false`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.10.4.使用筛选器自定义扫描

默认情况下,用`@Component`、`@Repository`、`@Service`、`@Controller`、`@Configuration`注释的类,或本身用`@Component`注释的自定义注释是唯一检测到的候选组件。但是,你可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为`includeFilters`或`excludeFilters`注释的属性(或作为`<context:include-filter />`或`<context:exclude-filter />`XML 配置中`<context:component-scan>`元素的子元素)。每个过滤器元素都需要`type`和`expression`属性。下表介绍了筛选选项:

|    Filter Type     |     Example Expression     |说明|
|--------------------|----------------------------|------------------------------------------------------------------------------------------|
|annotation (default)|`org.example.SomeAnnotation`|在目标组件的类型级别上,注释为*礼物*或*元存在*。|
|     assignable     |  `org.example.SomeClass`   |可将目标组件分配给(扩展或实现)的类(或接口)。|
|      aspectj       |  `org.example..*Service+`  |由目标组件匹配的 AspectJ 类型表达式。|
|       regex        | `org\.example\.Default.*`  |由目标组件的类名匹配的正则表达式。|
|       custom       | `org.example.MyTypeFilter` |`org.springframework.core.type.TypeFilter`接口的自定义实现。|

下面的示例显示了忽略所有`@Repository`注释并使用“存根”存储库的配置:

Java

```
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    // ...
}
```

Kotlin

```
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
        excludeFilters = [Filter(Repository::class)])
class AppConfig {
    // ...
}
```

下面的清单显示了等效的 XML:

```
<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>
```

|   |你还可以通过在<br/>注释上设置`useDefaultFilters=false`或通过提供`use-default-filters="false"`作为`<component-scan/>`元素的属性来禁用默认过滤器。这有效地禁用了对用<br/>注释或用`@Component`、`@Repository`、`@Service`、`@Controller`、`@RestController`注释或`@Configuration`注释的类的自动检测。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.10.5.在组件中定义 Bean 元数据

Spring 组件还可以向容器贡献 Bean 定义元数据。你可以使用与在`@Configuration`注释类中定义 Bean 元数据相同的`@Bean`注释来实现此目的。下面的示例展示了如何做到这一点:

爪哇

```
@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}
```

Kotlin

```
@Component
class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    fun publicInstance() = TestBean("publicInstance")

    fun doWork() {
        // Component method implementation omitted
    }
}
```

前面的类是一个 Spring 组件,在其`doWork()`方法中具有特定于应用程序的代码。然而,它还贡献了 Bean 定义,该定义具有引用方法`publicInstance()`的工厂方法。`@Bean`注释标识了工厂方法和其他 Bean 定义属性,例如通过`@Qualifier`注释的限定符值。可以指定的其他方法级注释是`@Scope`、`@Lazy`和自定义限定符注释。

|   |除了组件初始化的作用外,还可以将`@Lazy`注释放置在标记有`@Autowired`或`@Inject`的注入点上。在此上下文中,<br/>将导致注入一个延迟分辨率代理。然而,这样的代理方法<br/>是相当有限的。对于复杂的惰性交互,特别是结合<br/>和可选依赖项的情况,我们建议使用`ObjectProvider<MyTargetBean>`代替。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

正如前面所讨论的,支持自动连线字段和方法,同时还支持`@Bean`方法的自动连线。下面的示例展示了如何做到这一点:

爪哇

```
@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}
```

Kotlin

```
@Component
class FactoryMethodComponent {

    companion object {
        private var i: Int = 0
    }

    @Bean
    @Qualifier("public")
    fun publicInstance() = TestBean("publicInstance")

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected fun protectedInstance(
            @Qualifier("public") spouse: TestBean,
            @Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
        this.spouse = spouse
        this.country = country
    }

    @Bean
    private fun privateInstance() = TestBean("privateInstance", i++)

    @Bean
    @RequestScope
    fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}
```

该示例将`String`方法参数`country`自动连接到另一个名为`privateInstance`的 Bean 上的`age`属性的值。 Spring 表达式语言元素通过符号`#{ <expression> }`来定义该属性的值。对于`@Value`注释,表达式解析程序预先配置为在解析表达式文本时查找 Bean 名称。

在 Spring Framework4.3 中,还可以声明类型为的工厂方法参数(或其更具体的子类:),以访问触发当前 Bean 创建的请求注入点。请注意,这仅适用于 Bean 实例的实际创建,而不适用于现有实例的注入。因此,对于原型范围的 bean 来说,这个特性是最有意义的。对于其他作用域,Factory 方法只会看到在给定的作用域中触发创建新 Bean 实例的注入点(例如,触发创建惰性单例 Bean 的依赖项)。在这样的场景中,你可以使用提供的注入点元数据进行语义维护。下面的示例展示了如何使用`InjectionPoint`:

爪哇

```
@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}
```

Kotlin

```
@Component
class FactoryMethodComponent {

    @Bean
    @Scope("prototype")
    fun prototypeInstance(injectionPoint: InjectionPoint) =
            TestBean("prototypeInstance for ${injectionPoint.member}")
}
```

普通 Spring 组件中的`@Bean`方法的处理方式与 Spring `@Configuration`类中的方法的处理方式不同。不同之处在于,`@Component`类并没有用 CGlib 进行增强,以拦截方法和字段的调用。CGLIB 代理是在`@Configuration`中调用`@Bean`方法中的方法或字段创建 Bean 对协作对象的元数据引用的一种方法。这样的方法不是用普通的 爪哇 语义来调用的,而是通过容器来提供 Spring bean 的通常的生命周期管理和代理,即使是通过对`@Bean`方法的编程调用来引用其他 bean 时也是如此。相反,在普通的`@Component`类中调用`@Bean`方法中的方法或字段具有标准的 爪哇 语义,没有应用特殊的 CGLIB 处理或其他约束。

|   |你可以将`@Bean`方法声明为`static`,从而允许在不创建包含它们的配置类作为实例的情况下调用它们。在定义后处理器 bean(例如,类型`BeanFactoryPostProcessor`或`BeanPostProcessor`)时,这使得<br/>具有特殊意义,因为这样的 bean 在容器<br/>生命周期的早期就被初始化了,并且应该避免在那一点上触发配置的其他部分。,<br/><br/>调用静态`@Bean`方法永远不会被容器拦截,甚至在`@Configuration`类(如本节前面所述)中也不会,由于技术<br/>的限制:CGLIB 子类只能覆盖非静态方法。因此,<br/>直接调用另一个`@Bean`方法具有标准的 爪哇 语义,结果<br/>在一个独立的实例中被直接从工厂方法本身返回。<br/><br/>`@Bean`方法的 爪哇 语言可见性对<br/> Spring 容器中的结果 Bean 定义没有立即的影响。你可以自由地声明你的<br/>工厂方法,就像你在非-`@Configuration`类中看到的那样,也可以在任何地方声明静态<br/>方法。但是,`@Bean`类中的常规`@Configuration`方法需要<br/>才能被重写——也就是说,它们不能被声明为`private`或<br/>。<br/>方法也可以在给定组件的基类上发现,或者<br/>配置类,以及在 爪哇8 上由组件或配置类实现的接口<br/>中声明的默认方法。这允许在组成复杂的配置安排时有很多<br/>的灵活性,即使是多个<br/>继承也可以通过 爪哇8 的默认方法实现,截至 Spring 4.2,<br/>最后,对于相同的<br/> Bean,单个类可以容纳多个`@Bean`方法,根据运行时可用的<br/>依赖关系,作为使用的多个工厂方法的安排。这与在其他配置场景中选择“greediest”<br/>构造函数或工厂方法的算法相同:具有<br/>最大数量的可满足依赖项是在构造时选择的,<br/>类似于容器如何在多个`@Autowired`构造函数之间进行选择。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.10.6.命名自动检测到的组件

当组件作为扫描过程的一部分被自动检测时,其 Bean 名称由该扫描仪已知的`BeanNameGenerator`策略生成。默认情况下,任何 Spring 原型注释(`@Component`,`@Repository`,`@Service`,和`@Controller`)中包含一个名称`value`,从而为相应的 Bean 定义提供了该名称。

如果这样的注释不包含名称`value`或任何其他检测到的组件(例如由自定义过滤器发现的组件),则默认的 Bean Name Generator 返回未大写的非限定类名称。例如,如果检测到以下组件类,则名称将为`myMovieLister`和`movieFinderImpl`:

爪哇

```
@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
```

Kotlin

```
@Service("myMovieLister")
class SimpleMovieLister {
    // ...
}
```

爪哇

```
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
```

Kotlin

```
@Repository
class MovieFinderImpl : MovieFinder {
    // ...
}
```

如果你不想依赖默认的 Bean 命名策略,那么可以提供自定义的 Bean 命名策略。首先,实现[`BeanNameGenerator`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/support/beannamegenerator.html)接口,并确保包含一个默认的无 arg 构造函数。然后,在配置扫描仪时提供完全限定的类名,如下面的示例注释和 Bean 定义所示。

|   |如果由于多个具有<br/>相同的非限定类名(即名称相同但位于<br/>不同包中的类)的自动检测组件而导致命名冲突,则可能需要为生成的 Bean 名称配置一个`BeanNameGenerator`默认为<br/>完全限定类名的`BeanNameGenerator`类。截至 Spring 框架 5.2.3,位于包`FullyQualifiedAnnotationBeanNameGenerator`中的`org.springframework.context.annotation`可以用于这样的目的。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

爪哇

```
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}
```

Kotlin

```
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
    // ...
}
```

```
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>
```

作为一条一般规则,当其他组件可能对其进行显式引用时,可以考虑使用注释来指定名称。另一方面,只要容器负责布线,自动生成的名称就足够了。

#### 1.10.7.为自动检测的组件提供一个范围

与通常的 Spring-管理组件一样,自动检测组件的默认和最常见的作用域是`singleton`。然而,有时你需要使用`@Scope`注释来指定不同的作用域。你可以在注释中提供作用域的名称,如下例所示:

爪哇

```
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
```

Kotlin

```
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
    // ...
}
```

|   |`@Scope`注释仅在具体的 Bean 类(对于注释的<br/>组件)或工厂方法(对于`@Bean`方法)上进行内省。与 XML Bean <br/>定义相反,没有 Bean 定义继承的概念,并且在类级别上的继承<br/>层次结构对于元数据目的是无关的。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

有关特定于 Web 的作用域(例如 Spring 上下文中的“请求”或“会话”)的详细信息,请参见[Request, Session, Application, and WebSocket Scopes](#beans-factory-scopes-other)。与这些作用域的预构建注释一样,你也可以使用 Spring 的元注释方法来编写自己的范围注释:例如,使用`@Scope("prototype")`进行自定义注释的元注释,也可能声明自定义范围代理模式。

|   |要为范围解析提供自定义策略,而不是依赖于<br/>基于注释的方法,你可以实现[`ScopeMetadataResolver`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/scopemetadataresolver.html)接口。一定要包含一个默认的无参数构造函数。然后可以在配置扫描仪时提供<br/>完全限定的类名,如下面的<br/>注释和 Bean 定义的示例所示:|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

爪哇

```
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
```

Kotlin

```
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
    // ...
}
```

```
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
```

当使用某些非单例作用域时,可能需要为作用域对象生成代理。其推理在[作为依赖项的作用域 bean](#beans-factory-scopes-other-injection)中进行了描述。为此,在 Component-Scan 元素上提供了一个作用域-Proxy 属性。这三个可能的值是:`no`,`interfaces`,和`targetClass`。例如,以下配置将生成标准的 JDK 动态代理:

爪哇

```
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
```

Kotlin

```
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
    // ...
}
```

```
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
```

#### 1.10.8.提供带有注释的限定符元数据

`@Qualifier`注释在[使用限定符对基于注释的自动连线进行微调](#beans-autowired-annotation-qualifiers)中进行了讨论。该部分中的示例演示了在解析 AutoWire 候选项时使用`@Qualifier`注释和自定义限定符注释来提供细粒度的控制。因为这些示例是基于 XML Bean 定义的,所以通过使用 XML 中的`qualifier`或`meta`元素的子元素,在候选 Bean 定义上提供了限定符元数据。当依赖 Classpath 扫描来自动检测组件时,你可以在候选类上为限定符元数据提供类型级别的注释。以下三个示例演示了这种技术:

爪哇

```
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
```

Kotlin

```
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
```

爪哇

```
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
```

Kotlin

```
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
    // ...
}
```

爪哇

```
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}
```

Kotlin

```
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
    // ...
}
```

|   |与大多数基于注释的替代方法一样,请记住注释元数据是<br/>绑定到类定义本身的,而使用 XML 允许多个相同类型的 bean<br/>在其限定符元数据中提供变体,因为<br/>元数据是按实例而不是按类提供的。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.10.9.生成候选组件的索引

Classpath 尽管扫描速度非常快,但通过在编译时创建候选的静态列表,可以提高大型应用程序的启动性能。在这种模式下,所有组件扫描的目标模块都必须使用这种机制。

|   |你现有的`@ComponentScan`或`<context:component-scan/>`指令必须保持<br/>不变,以请求上下文扫描某些包中的候选项。当`ApplicationContext`检测到这样的索引时,它会自动使用它,而不是扫描<br/> Classpath。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

要生成索引,请向每个包含组件的模块添加一个附加依赖项,这些组件是组件扫描指令的目标。下面的示例展示了如何使用 Maven 来实现这一点:

```
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.3.16</version>
        <optional>true</optional>
    </dependency>
</dependencies>
```

对于 Gradle 4.5 及更早版本,应该在`compileOnly`配置中声明依赖项,如以下示例所示:

```
dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.3.16"
}
```

对于 Gradle 4.6 及更高版本,依赖项应该在`annotationProcessor`配置中声明,如以下示例所示:

```
dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:5.3.16"
}
```

`spring-context-indexer`工件生成一个`META-INF/spring.components`文件,该文件包含在 jar 文件中。

|   |在 IDE 中使用此模式时,`spring-context-indexer`必须将<br/>注册为注释处理器,以确保在更新<br/>候选组件时索引是最新的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |当在 Classpath 上找到`META-INF/spring.components`文件<br/>时,将自动启用索引。如果对于某些库(或用例)<br/>,索引部分可用,但无法为整个应用程序构建索引,则可以通过将<br/>设置为`true`,返回到常规的 Classpath <br/>安排(就好像根本没有索引),可以作为 JVM 系统属性,也可以通过[`SpringProperties`](acception.html#acception- Spring-properties)机制。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 1.11.使用 JSR330 标准注释

从 Spring 3.0 开始, Spring 提供了对 JSR-330 标准注释(依赖注入)的支持。以与 Spring 注释相同的方式扫描这些注释。要使用它们,你需要在你的 Classpath 中有相关的罐子。

|   |如果使用 Maven,则`javax.inject`工件在标准 Maven <br/>存储库([https://repo1.maven.org/maven2/javax/inject/javax.inject/1/](https://repo1.maven.org/maven2/javax/inject/javax.inject/1/))中可用。<br/>你可以将以下依赖项添加到你的文件 POM 中。xml:<br/><br/>```<br/><dependency><br/>    <groupId>javax.inject</groupId><br/>    <artifactId>javax.inject</artifactId><br/>    <version>1</version><br/></dependency><br/>```|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.11.1.依赖注入与`@Inject`和`@Named`

而不是`@Autowired`,你可以使用`@javax.inject.Inject`,如下所示:

爪哇

```
import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.findMovies(...);
        // ...
    }
}
```

Kotlin

```
import javax.inject.Inject

class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder

    fun listMovies() {
        movieFinder.findMovies(...)
        // ...
    }
}
```

与`@Autowired`一样,你可以在字段级、方法级和构造函数参数级使用`@Inject`。此外,你可以将注入点声明为`Provider`,从而允许按需访问较短范围的 bean,或者通过`Provider.get()`调用对其他 bean 进行惰性访问。下面的示例提供了前面示例的一个变体:

爪哇

```
import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        // ...
    }
}
```

Kotlin

```
import javax.inject.Inject

class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder

    fun listMovies() {
        movieFinder.findMovies(...)
        // ...
    }
}
```

如果你希望为应该注入的依赖项使用限定名称,那么应该使用`@Named`注释,如下例所示:

爪哇

```
import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

Kotlin

```
import javax.inject.Inject
import javax.inject.Named

class SimpleMovieLister {

    private lateinit var movieFinder: MovieFinder

    @Inject
    fun setMovieFinder(@Named("main") movieFinder: MovieFinder) {
        this.movieFinder = movieFinder
    }

    // ...
}
```

与`@Autowired`一样,`@Inject`也可以与`java.util.Optional`或`@Nullable`一起使用。这在这里甚至更适用,因为`@Inject`不具有`required`属性。以下两个示例展示了如何使用`@Inject`和`@Nullable`:

```
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        // ...
    }
}
```

爪哇

```
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        // ...
    }
}
```

Kotlin

```
class SimpleMovieLister {

    @Inject
    var movieFinder: MovieFinder? = null
}
```

#### 1.11.2.`@Named`和`@ManagedBean`:`@Component`注释的标准等价物

而不是`@Component`,你可以使用`@javax.inject.Named`或`javax.annotation.ManagedBean`,如下例所示:

爪哇

```
import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

Kotlin

```
import javax.inject.Inject
import javax.inject.Named

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder

    // ...
}
```

在不指定组件名称的情况下使用`@Component`是非常常见的。`@Named`可以以类似的方式使用,如下例所示:

爪哇

```
import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

Kotlin

```
import javax.inject.Inject
import javax.inject.Named

@Named
class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder

    // ...
}
```

当使用`@Named`或`@ManagedBean`时,可以使用组件扫描,就像使用 Spring 注释时一样,如下例所示:

爪哇

```
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
```

Kotlin

```
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
    // ...
}
```

|   |与`@Component`相反,JSR-330`@Named`和 JSR-250`@ManagedBean`注释是不可组合的。你应该使用 Spring 的原型模型来构建<br/>自定义组件注释。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.11.3.JSR-330 标准注释的局限性

当你使用标准注释时,你应该知道一些重要的特性是不可用的,如下表所示:

|      Spring       |   javax.inject.\*   |javax.inject 限制/注释|
|-------------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|    @Autowired     |       @Inject       |`@Inject`没有“required”属性。可以与 爪哇8 的`Optional`一起使用。|
|    @Component     |@Named / @ManagedBean|JSR-330 不提供可组合的模型,只提供一种识别命名组件的方法。|
|@Scope("singleton")|     @Singleton      |JSR-330 缺省作用域类似于 Spring 的`prototype`。然而,为了使其<br/>与 Spring 的一般默认值保持一致,在 Spring <br/>容器中声明的 JSR-330 Bean 默认为`singleton`。为了使用`singleton`、<br/>以外的作用域,你应该使用 Spring 的`@Scope`注释。`javax.inject`还提供了[@Scope](https://download.oracle.com/javaee/6/api/javax/inject/Scope.html)注释。<br/>不过,这个注释只用于创建你自己的注释。|
|    @Qualifier     | @Qualifier / @Named |`javax.inject.Qualifier`只是构建自定义限定符的元注释。<br/>具体`String`限定符(就像 Spring 的`@Qualifier`与一个值)可以关联<br/>到`javax.inject.Named`。|
|      @Value       |         \-          |没有等效的|
|     @Required     |         \-          |没有等效的|
|       @Lazy       |         \-          |没有等效的|
|   ObjectFactory   |      Provider       |`javax.inject.Provider`是 Spring 的`ObjectFactory`,<br/>的直接替代方法,只使用更短的`get()`方法名。它也可以与<br/> Spring 的`@Autowired`结合使用,或者与非注释的构造函数和 setter 方法结合使用。|

### 1.12.基于 爪哇 的容器配置

本节介绍如何在 爪哇 代码中使用注释来配置 Spring 容器。它包括以下主题:

* [基本概念:`@Bean`和`@Configuration`](#beans-java-basic-concepts)

* [通过使用`AnnotationConfigApplicationContext`实例化 Spring 容器]

* [使用`@Bean`注释](#beans-java- Bean-注释)

* [使用`@Configuration`注释](#beans-java-configuration-annotation)

* [编写基于 爪哇 的配置](#beans-java-composing-configuration-classes)

* [Bean Definition Profiles](#beans-definition-profiles)

* [`PropertySource`抽象]

* [using`@PropertySource`](#beans-using-propertysource)

* [语句中的占位符解析](#beans-placeholder-resolution-in-statements)

#### 1.12.1.基本概念:`@Bean`和`@Configuration`

Spring 新的 爪哇 配置支持中的核心构件是`@Configuration`-带注释的类和`@Bean`-带注释的方法。

`@Bean`注释用于指示方法实例化、配置和初始化一个新对象,该对象将由 Spring IOC 容器管理。对于那些熟悉 Spring 的`<beans/>`XML 配置的人来说,`@Bean`注释与`<bean/>`元素起着相同的作用。你可以在任意 Spring `@Component`中使用`@Bean`-带注释的方法。然而,它们最常用于`@Configuration`bean。

用`@Configuration`注释一个类表明,它的主要目的是作为 Bean 定义的来源。此外,`@Configuration`类通过调用同一类中的其他`@Bean`方法来定义 Bean 之间的依赖关系。最简单的`@Configuration`类如下:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun myService(): MyService {
        return MyServiceImpl()
    }
}
```

前面的`AppConfig`类等价于下面的 Spring `<beans/>`XML:

```
<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
```

完整的 @ 配置 VS“Lite”@ Bean 模式?

当`@Bean`方法在没有`@Configuration`注释的类中声明时,它们被称为正在以“精简”模式进行处理。 Bean 在`@Component`中声明的方法,甚至在一个普通的旧类中声明的方法,都被认为是“lite”,具有不同的包含类的主要目的,而`@Bean`方法是那里的一种奖励。例如,服务组件可以通过每个适用的组件类上的附加`@Bean`方法向容器公开管理视图。在这样的场景中,`@Bean`方法是一种通用的工厂方法机制。

与 full`@Configuration`不同,lite`@Bean`方法不能声明 Bean 之间的依赖关系。相反,它们对包含它们的组件的内部状态进行操作,并可选地对它们可能声明的参数进行操作。因此,这样的`@Bean`方法不应该调用其他`@Bean`方法。每个这样的方法实际上只是用于特定 Bean 引用的工厂方法,没有任何特殊的运行时语义。这里的积极的副作用是,在运行时不需要应用 CGLIB 子类,因此在类设计方面没有限制(即,包含的类可能是`final`等)。

在常见的场景中,`@Bean`方法要在`@Configuration`类中声明,以确保始终使用“完全”模式,并确保跨方法引用因此被重定向到容器的生命周期管理。这可以防止相同的`@Bean`方法通过常规的 爪哇 调用被意外调用,这有助于减少在“精简”模式下操作时很难跟踪的细微错误。

下面的部分将深入讨论`@Bean`和`@Configuration`注释。然而,首先,我们介绍了通过使用基于 爪哇 的配置来创建 Spring 容器的各种方法。

#### 1.12.2.通过使用`AnnotationConfigApplicationContext`### 来实例化 Spring 容器

下面的部分记录了 Spring 的`AnnotationConfigApplicationContext`,在 Spring 3.0 中介绍了它。这种通用的`ApplicationContext`实现不仅能够接受`@Configuration`类作为输入,而且还能够接受用 JSR-330 元数据注释的普通`@Component`类和类。

当`@Configuration`类被提供为输入时,`@Configuration`类本身被注册为 Bean 定义,并且类中所有声明的`@Bean`方法也被注册为 Bean 定义。

当`@Component`和 JSR-330 类被提供时,它们被注册为 Bean 定义,并且假定在必要的情况下,在那些类中使用诸如`@Autowired`或`@Inject`的 DI 元数据。

#####  简单的构造

与 Spring XML 文件在实例化`ClassPathXmlApplicationContext`时用作输入的方式大致相同,在实例化`@Configuration`时,可以使用`@Configuration`类作为输入。这允许完全无 XML 地使用 Spring 容器,如下例所示:

爪哇

```
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

Kotlin

```
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}
```

如前所述,`AnnotationConfigApplicationContext`并不限于仅与`@Configuration`类一起工作。任何`@Component`或 JSR-330 注释类都可以作为构造函数的输入提供,如下例所示:

爪哇

```
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

Kotlin

```
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}
```

前面的示例假设`MyServiceImpl`、`Dependency1`和`Dependency2`使用 Spring 依赖注入注释,例如`@Autowired`。

#####  通过使用`register(Class<?>…​)`#### 以编程方式构建容器

你可以使用 no-arg 构造函数实例化`AnnotationConfigApplicationContext`,然后使用`register()`方法对其进行配置。当以编程方式构建`AnnotationConfigApplicationContext`时,这种方法特别有用。下面的示例展示了如何做到这一点:

爪哇

```
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

Kotlin

```
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.register(AppConfig::class.java, OtherConfig::class.java)
    ctx.register(AdditionalConfig::class.java)
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}
```

#####  使用`scan(String…​)`启用组件扫描

要启用组件扫描,你可以对`@Configuration`类作如下注释:

爪哇

```
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig  {
    // ...
}
```

|**1**|此注释使组件扫描成为可能。|
|-----|-------------------------------------------|

Kotlin

```
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig  {
    // ...
}
```

|**1**|此注释使组件扫描成为可能。|
|-----|-------------------------------------------|

|   |有经验的 Spring 用户可能熟悉来自<br/> Spring 的`context:`名称空间的等效 XML 声明,如以下示例所示:<br/><br/>```<br/><beans><br/>    <context:component-scan base-package="com.acme"/><br/></beans><br/>```|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在前面的示例中,扫描`com.acme`包以查找任何`@Component`-注释的类,并且这些类被注册为容器内的 Spring  Bean 定义。`AnnotationConfigApplicationContext`公开了`scan(String…​)`方法,以允许相同的组件扫描功能,如下例所示:

爪哇

```
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
```

Kotlin

```
fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.scan("com.acme")
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
}
```

|   |请记住,`@Configuration`类是[Meta-Annotated](#beans-meta-annotations)与`@Component`,因此它们是组件扫描的候选对象。在前面的示例中,假设<br/>在`AppConfig`包(或下面的任何包<br/>)中声明了`AppConfig`,则在调用`scan()`时将其拾取。在`refresh()`时,它的所有`@Bean`方法都被处理并注册为容器内的 Bean 定义。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  对`AnnotationConfigWebApplicationContext`#### 的 Web 应用程序的支持

一个`WebApplicationContext`的`AnnotationConfigApplicationContext`变种可与`AnnotationConfigWebApplicationContext`一起使用。可以在配置 Spring `ContextLoaderListener` Servlet 侦听器、 Spring MVC`DispatcherServlet`等时使用该实现。下面的`web.xml`片段配置了典型的 Spring MVC Web 应用程序(请注意使用`contextClass`context-param 和 init-param):

```
<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>
```

|   |对于编程用例,`GenericWebApplicationContext`可以用作<br/>的`AnnotationConfigWebApplicationContext`的替代项。详情见[`GenericWebApplicationContext`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/context/support/genericwebapplicationcontext.html)爪哇doc。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.12.3.使用`@Bean`注释

`@Bean`是一个方法级的注释,是 XML`<bean/>`元素的直接模拟。该注释支持`<bean/>`提供的一些属性,例如:

* [初始化方法](#beans-factory-lifecycle-initializingbean)

* [销毁方法](#beans-factory-lifecycle-disposablebean)

* [autowiring](#beans-factory-autowire)

* `name`.

你可以在`@Configuration`注解类或`@Component`注解类中使用`@Bean`注解。

#####  宣布 A Bean

要声明 Bean,你可以使用`@Bean`注释对方法进行注释。你可以使用此方法在`ApplicationContext`中注册一个 Bean 定义,该定义的类型指定为该方法的返回值。默认情况下, Bean 名称与方法名称相同。下面的示例显示了`@Bean`方法声明:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun transferService() = TransferServiceImpl()
}
```

前面的配置与下面的 Spring XML 完全等价:

```
<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
```

这两个声明使得名为`transferService`的 Bean 在`ApplicationContext`中可用,并绑定到类型`TransferServiceImpl`的对象实例,如以下文本图像所示:

```
transferService -> com.acme.TransferServiceImpl
```

你也可以使用默认方法来定义 bean。这允许通过在默认方法上实现带有 Bean 定义的接口来组合 Bean 配置。

爪哇

```
public interface BaseConfig {

    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

@Configuration
public class AppConfig implements BaseConfig {

}
```

你还可以使用接口(或基类)返回类型声明你的`@Bean`方法,如下例所示:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl()
    }
}
```

然而,这将 Advance 类型预测的可见性限制为指定的接口类型(`TransferService`)。然后,只有在受影响的单例 Bean 被实例化之后,容器才知道完整类型(`TransferServiceImpl`)。非惰性单例 bean 会根据其声明顺序进行实例化,因此你可能会看到不同的类型匹配结果,这取决于另一个组件何时尝试使用未声明的类型进行匹配(例如`@Autowired TransferServiceImpl`,它仅在`transferService` Bean 被实例化之后进行解析)。

|   |如果你始终通过声明的服务接口引用你的类型,则你的`@Bean`返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件<br/>,或者对于由其<br/>实现类型可能引用的组件,更安全的做法是声明尽可能具体的返回类型<br/>(至少与引用你的 Bean 的注入点所要求的特定类型一样)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  Bean 相依性

带注释的`@Bean`方法可以具有任意数量的参数,这些参数描述了构建 Bean 所需的依赖关系。例如,如果我们的`TransferService`需要`AccountRepository`,则可以使用方法参数实现该依赖关系,如下例所示:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}
```

解析机制与基于构造函数的依赖注入几乎相同。有关更多详细信息,请参见[有关部分](#beans-constructor-injection)。

#####  接收生命周期回调

使用`@Bean`注释定义的任何类都支持常规的生命周期回调,并且可以使用 JSR-250 中的`@PostConstruct`和`@PreDestroy`注释。有关更多详细信息,请参见[JSR-250 注解](#beans-postconstruct-and-predestroy-annotations)。

常规的 Spring [lifecycle](#beans-factory-nature)回调也完全支持。如果 Bean 实现了`InitializingBean`、`DisposableBean`或`Lifecycle`,则容器调用它们各自的方法。

标准的`*Aware`接口集合(如[BeanFactoryAware](#beans-beanfactory),[Beannameaware](#beans-factory-aware),[MessagesourceAware](#context-functionality-messagesource),[应用情境软件](#beans-factory-aware),等等)也是完全支持的。

`@Bean`注释支持指定任意的初始化和销毁回调方法,很像 Spring XML 的`init-method`和`destroy-method`元素上的属性,如下例所示:

爪哇

```
public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
```

Kotlin

```
class BeanOne {

    fun init() {
        // initialization logic
    }
}

class BeanTwo {

    fun cleanup() {
        // destruction logic
    }
}

@Configuration
class AppConfig {

    @Bean(initMethod = "init")
    fun beanOne() = BeanOne()

    @Bean(destroyMethod = "cleanup")
    fun beanTwo() = BeanTwo()
}
```

|   |默认情况下,使用 爪哇 配置定义的具有公共`close`或`shutdown`方法的 bean 将自动加入销毁回调。如果你有一个公共的`close`或`shutdown`方法,并且不希望在容器<br/>关闭时调用它,你可以将`@Bean(destroyMethod="")`添加到你的 Bean 定义中,以禁用<br/>默认`(inferred)`模式。<br/><br/>对于你通过 JNDI 获得的资源,你可能希望在默认情况下这样做,因为其<br/>生命周期是在应用程序之外管理的。特别是,对于`DataSource`,要确保始终执行<br/>,下面的示例显示了如何防止`DataSource`的自动销毁回调:<br/>在 爪哇 EE 应用服务器上是有问题的:<br/>java<br/><br/>```<br/>@Bean(destroyMethod="")<br/>public DataSource dataSource() throws NamingException {<br/>    return (DataSource) jndiTemplate.lookup("MyDS");<br/>}<br/>```<br/><br/> Kotlin gt r=“/48”/>“><2917"/>><>>>>><<>>>>>>>>>>>>>>>>>>><<2946>>>>>>>>>>>><gt="2946>>>>>>>>>通过<br/>使用 Spring 的`JndiTemplate`或`JndiLocatorDelegate`助手或直接的 jndi`InitialContext`使用但不使用`JndiObjectFactoryBean`变体(这将强制<br/>你将返回类型声明为`FactoryBean`类型,而不是实际的目标<br/>类型,使得在其他`@Bean`方法中的交叉引用调用更难使用,这些方法<br/>打算在此引用所提供的资源)。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在上面的例子`BeanOne`的情况下,在构造过程中直接调用`init()`方法将同样有效,如下例所示:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun beanOne() = BeanOne().apply {
        init()
    }

    // ...
}
```

|   |当你直接在 爪哇 中工作时,你可以对对象执行任何你喜欢的操作,并且执行<br/>,并不总是需要依赖容器生命周期。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------|

#####  指定 Bean 范围

Spring 包括`@Scope`注释,以便可以指定 Bean 的作用域。

###### 使用`@Scope`注释

你可以指定使用`@Bean`注释定义的 bean 应该具有特定的作用域。你可以使用[Bean Scopes](#beans-factory-scopes)部分中指定的任何标准作用域。

默认的作用域是`singleton`,但是你可以使用`@Scope`注释来覆盖此范围,如下例所示:

爪哇

```
@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
```

Kotlin

```
@Configuration
class MyConfiguration {

    @Bean
    @Scope("prototype")
    fun encryptor(): Encryptor {
        // ...
    }
}
```

###### `@Scope`和`scoped-proxy`

Spring 通过[作用域代理](#beans-factory-scopes-other-injection)提供了一种处理作用域依赖关系的方便方法。在使用 XML 配置时,创建这样的代理的最简单的方法是`<aop:scoped-proxy/>`元素。使用`@Scope`注释在 爪哇 中配置 bean,可以提供与`proxyMode`属性相同的支持。默认值是`ScopedProxyMode.DEFAULT`,这通常表示除非在组件扫描指令级配置了不同的默认值,否则不应创建范围代理。你可以指定`ScopedProxyMode.TARGET_CLASS`、`ScopedProxyMode.INTERFACES`或`ScopedProxyMode.NO`。

如果你使用 爪哇 将范围代理示例从 XML 引用文档(参见[作用域代理](#beans-factory-scopes-other-injection))移植到我们的`@Bean`,它类似于以下内容:

爪哇

```
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}
```

Kotlin

```
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()

@Bean
fun userService(): Service {
    return SimpleUserService().apply {
        // a reference to the proxied userPreferences bean
        setUserPreferences(userPreferences())
    }
}
```

#####  自定义 Bean 命名

默认情况下,配置类使用`@Bean`方法的名称作为结果 Bean 的名称。但是,可以使用`name`属性重写此功能,如下例所示:

爪哇

```
@Configuration
public class AppConfig {

    @Bean("myThing")
    public Thing thing() {
        return new Thing();
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean("myThing")
    fun thing() = Thing()
}
```

#####  Bean 别名

如[命名 bean](#beans-beanname)中所讨论的,有时希望给出一个 Bean 多个名称,也称为 Bean 别名。为此目的,`@Bean`注释的`name`属性接受字符串数组。下面的示例展示了如何为 Bean 设置多个别名:

爪哇

```
@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
    fun dataSource(): DataSource {
        // instantiate, configure and return DataSource bean...
    }
}
```

#####  Bean 说明

有时,提供对 Bean 的更详细的文本描述是有帮助的。当公开 bean(可能通过 JMX)以进行监视时,这可能特别有用。

要向`@Bean`添加描述,可以使用[`@Description`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/description.html)注释,如下例所示:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    fun thing() = Thing()
}
```

#### 1.12.4.使用`@Configuration`注释

`@Configuration`是一个类级注释,指示对象是 Bean 定义的源。`@Configuration`类通过`@Bean`-带注释的方法声明 bean。对`@Configuration`类上的`@Bean`方法的调用也可用于定义 Bean 之间的依赖关系。有关一般介绍,请参见[基本概念:`@Bean`和`@Configuration`]。

#####  注入相互 Bean 的依赖关系

当 bean 彼此之间存在依赖关系时,表示这种依赖关系就像让一个 Bean 方法调用另一个方法一样简单,如下例所示:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun beanOne() = BeanOne(beanTwo())

    @Bean
    fun beanTwo() = BeanTwo()
}
```

在前面的示例中,`beanOne`通过构造函数注入接收对`beanTwo`的引用。

|   |这种声明 Bean 之间依赖关系的方法仅在`@Bean`方法<br/>在`@Configuration`类中声明时才起作用。不能使用普通的`@Component`类来声明 Bean 之间的依赖关系<br/>。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  查找方法注入

如前所述,[查找方法注入](#beans-factory-method-injection)是一个很少使用的高级特性。在单作用域 Bean 依赖于原型作用域 Bean 的情况下,它是有用的。对这种类型的配置使用 爪哇 为实现这种模式提供了一种自然的方法。下面的示例展示了如何使用查找方法注入:

爪哇

```
public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
```

Kotlin

```
abstract class CommandManager {
    fun process(commandState: Any): Any {
        // grab a new instance of the appropriate Command interface
        val command = createCommand()
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState)
        return command.execute()
    }

    // okay... but where is the implementation of this method?
    protected abstract fun createCommand(): Command
}
```

通过使用 爪哇 配置,你可以创建`CommandManager`的子类,其中抽象的`createCommand()`方法以这样一种方式被重写,即它会查找一个新的(原型)命令对象。下面的示例展示了如何做到这一点:

爪哇

```
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
```

Kotlin

```
@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
    val command = AsyncCommand()
    // inject dependencies here as required
    return command
}

@Bean
fun commandManager(): CommandManager {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return object : CommandManager() {
        override fun createCommand(): Command {
            return asyncCommand()
        }
    }
}
```

#####  有关基于 爪哇 的配置如何在内部工作的更多信息 #####

考虑以下示例,该示例显示了一个`@Bean`带注释的方法被调用了两次:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun clientService1(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }

    @Bean
    fun clientService2(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }

    @Bean
    fun clientDao(): ClientDao {
        return ClientDaoImpl()
    }
}
```

`clientDao()`在`clientService1()`中调用过一次,在`clientService2()`中调用过一次。由于此方法创建了`ClientDaoImpl`的一个新实例并返回它,因此你通常希望有两个实例(每个服务有一个实例)。这肯定会有问题:在 Spring 中,实例化的 bean 默认具有`singleton`作用域。这就是神奇之处:所有`@Configuration`类在启动时都用`CGLIB`子类。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存的(作用域)bean。

|   |根据你的 Bean 的范围,行为可能会有所不同。我们在这里讨论的是<br/>关于单身汉的问题。|
|---|--------------------------------------------------------------------------------------------------------------|

|   |从 Spring 3.2 开始,不再需要将 CGLIB 添加到你的 Classpath 中,因为 CGLIB类已经在下重新打包,并直接包含在 Spring-core jar 中。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |由于 CGlib 在<br/>启动时动态添加特性,因此有一些限制。特别是,配置类不能是最终的。但是,正如<br/>of4.3,在配置类上允许使用任何构造函数,包括使用`@Autowired`或为默认注入使用单个非默认构造函数声明。<br/><br/>如果你希望避免任何 CGLIB 施加的限制,请考虑在非-`@Bean`类上声明你的`@Bean`方法(例如,在普通的`@Component`类上)。<br/>方法之间的跨方法调用不会被截获,因此你有<br/>在构造函数或方法级别完全依赖于依赖注入。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.12.5.编写基于 爪哇 的配置

Spring 的基于 爪哇 的配置特性允许你编写注释,这可以降低配置的复杂性。

#####  使用`@Import`注释

正如在 Spring XML 文件中使用`<import/>`元素来帮助模块化配置一样,`@Import`注释允许从另一个配置类加载`@Bean`定义,如下例所示:

爪哇

```
@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}
```

Kotlin

```
@Configuration
class ConfigA {

    @Bean
    fun a() = A()
}

@Configuration
@Import(ConfigA::class)
class ConfigB {

    @Bean
    fun b() = B()
}
```

现在,在实例化上下文时,不需要同时指定`ConfigA.class`和`ConfigB.class`,只需要显式地提供`ConfigB`,如下例所示:

爪哇

```
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}
```

Kotlin

```
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)

    // now both beans A and B will be available...
    val a = ctx.getBean<A>()
    val b = ctx.getBean<B>()
}
```

这种方法简化了容器实例化,因为只需要处理一个类,而不是要求你在构建过程中记住大量的`@Configuration`类。

|   |在 Spring Framework4.2 中,`@Import`还支持对常规组件<br/>类的引用,类似于`AnnotationConfigApplicationContext.register`方法。<br/>如果你想要避免组件扫描,通过使用几个<br/>配置类作为切入点来显式地定义所有组件,这是特别有用的。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###### 对导入的`@Bean`定义注入依赖项

前面的例子是可行的,但过于简单化了。在大多数实际场景中,跨配置类的 bean 彼此具有依赖性。在使用 XML 时,这不是一个问题,因为不涉及编译器,并且你可以声明`ref="someBean"`并信任 Spring 在容器初始化期间将其计算出来。当使用`@Configuration`类时,Java 编译器对配置模型施加约束,因为对其他 bean 的引用必须是有效的 Java 语法。

幸运的是,解决这个问题很简单。作为[我们已经讨论过了](#beans-java-dependencies),`@Bean`方法可以具有任意数量的描述 Bean 依赖关系的参数。考虑以下更现实的场景,其中包含几个`@Configuration`类,每个类取决于在其他类中声明的 bean:

Java

```
@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

Kotlin

```
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

@Configuration
class RepositoryConfig {

    @Bean
    fun accountRepository(dataSource: DataSource): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return new DataSource
    }
}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // everything wires up across configuration classes...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}
```

还有另一种方法可以达到同样的效果。请记住,`@Configuration`类最终只是容器中的另一个 Bean:这意味着它们可以利用`@Autowired`和`@Value`注入和其它特征,与其它任何 Bean 相同。

|   |确保以这种方式注入的依赖关系仅是最简单的依赖关系。`@Configuration`类在上下文的初始化过程中很早就被处理了,并且强制以这种方式注入依赖项<br/>可能会导致意外的早期初始化。只要有可能,就求助于<br/>基于参数的注入,就像在前面的示例中一样。<br/><br/>此外,要特别小心`BeanPostProcessor`和`BeanFactoryPostProcessor`定义<br/>通过`@Bean`。这些方法通常应该声明为`static @Bean`方法,而不是触发其包含的配置类的<br/>实例化。否则,`@Autowired`和`@Value`可能不会在配置类本身上工作,因为有可能在[`AutowiredAnnotationBeanPostProcessor`]之前将其创建为 Bean 实例(https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/org/SpringFramework/beans/factory/factory/annotation/autoidannotationbeanpostpostchlor.html)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例显示了一个 Bean 如何能够自动连接到另一个 Bean:

Java

```
@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

Kotlin

```
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Autowired
    lateinit var accountRepository: AccountRepository

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

@Configuration
class RepositoryConfig(private val dataSource: DataSource) {

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return new DataSource
    }
}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // everything wires up across configuration classes...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}
```

|   |在`@Configuration`类中的构造函数注入仅在 Spring <br/>框架 4.3 支持。还请注意,如果目标<br/> Bean 只定义了一个构造函数,则不需要指定`@Autowired`。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

[]()完全合格的导入 bean,便于导航

在前面的场景中,使用`@Autowired`可以很好地工作并提供所需的模块化,但是确定 AutoWired Bean 定义的确切声明位置仍然有些模棱两可。例如,作为查看`ServiceConfig`的开发人员,你如何确切地知道在哪里声明了`@Autowired AccountRepository` Bean?它在代码中不是显式的,这可能就很好了。请记住,[Spring Tools for Eclipse](https://spring.io/tools)提供了工具,可以呈现显示所有事物如何连接的图形,这可能就是你所需要的。另外,你的 Java IDE 可以很容易地找到`AccountRepository`类型的所有声明和用法,并快速向你显示返回该类型的`@Bean`方法的位置。

如果这种歧义是不可接受的,并且你希望在 IDE 中从一个`@Configuration`类直接导航到另一个类,请考虑自动连接配置类本身。下面的示例展示了如何做到这一点:

Java

```
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}
```

Kotlin

```
@Configuration
class ServiceConfig {

    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig

    @Bean
    fun transferService(): TransferService {
        // navigate 'through' the config class to the @Bean method!
        return TransferServiceImpl(repositoryConfig.accountRepository())
    }
}
```

在前面的情况中,其中`AccountRepository`是完全显式定义的。然而,`ServiceConfig`现在与`RepositoryConfig`紧密耦合。这是一种权衡。通过使用基于接口或基于抽象类的`@Configuration`类,可以在一定程度上减轻这种紧密耦合。考虑以下示例:

Java

```
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

Kotlin

```
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(repositoryConfig.accountRepository())
    }
}

@Configuration
interface RepositoryConfig {

    @Bean
    fun accountRepository(): AccountRepository
}

@Configuration
class DefaultRepositoryConfig : RepositoryConfig {

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(...)
    }
}

@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class)  // import the concrete config!
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return DataSource
    }

}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}
```

现在`ServiceConfig`相对于具体的`DefaultRepositoryConfig`是松耦合的,并且内置的 IDE 工具仍然很有用:你可以轻松地获得`RepositoryConfig`实现的类型层次结构。通过这种方式,导航`@Configuration`类及其依赖关系与导航基于接口的代码的通常过程没有什么不同。

|   |如果你想要影响某些 bean 的启动创建顺序,请考虑<br/>将其中一些声明为`@Lazy`(用于在首次访问而不是在启动时创建)<br/>或将其声明为`@DependsOn`某些其他 bean(确保特定的其他 bean 是在当前 Bean 之前创建的<br/>,而后者的直接依赖所隐含的意义则更大)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  有条件地包括`@Configuration`类或`@Bean`方法

通常有条件地启用或禁用一个完整的`@Configuration`类,甚至基于某个任意系统状态的单个`@Bean`方法是有用的。一个常见的例子是,只有在 Spring `Environment`中启用了特定配置文件时,才使用`@Profile`注释来激活 bean(有关详细信息,请参见[Bean Definition Profiles](#beans-definition-profiles))。

`@Profile`注释实际上是通过使用一种更灵活的注释[`@Conditional`]来实现的(https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/conditional.html)。`@Conditional`注释表示在注册`@Bean`之前应该参考的特定`org.springframework.context.annotation.Condition`实现。

`Condition`接口的实现提供了一个`matches(…​)`方法,该方法返回`true`或`false`。例如,下面的清单显示了用于`Condition`的实际`@Profile`实现:

Java

```
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Read the @Profile annotation attributes
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
        for (Object value : attrs.get("value")) {
            if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                return true;
            }
        }
        return false;
    }
    return true;
}
```

Kotlin

```
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
    // Read the @Profile annotation attributes
    val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
    if (attrs != null) {
        for (value in attrs["value"]!!) {
            if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
                return true
            }
        }
        return false
    }
    return true
}
```

参见[`@Conditional`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/conditional.html)Javadoc 获取更多细节。

#####  结合 Java 和 XML 配置

Spring 的`@Configuration`类支持并不是为了 100% 地完全替代 Spring XML。一些工具,例如 Spring XML 名称空间,仍然是配置容器的理想方式。在 XML 方便或必要的情况下,你可以进行选择:以“以 XML 为中心”的方式实例化容器,例如使用`ClassPathXmlApplicationContext`,或者以“以 Java 为中心”的方式实例化容器,使用`AnnotationConfigApplicationContext`和`@ImportResource`注释来根据需要导入 XML。

###### 以 XML 为中心使用`@Configuration`类

最好是从 XML 引导 Spring 容器,并以一种特别的方式包括`@Configuration`类。例如,在使用 Spring XML 的大型现有代码库中,更容易根据需要创建`@Configuration`类,并从现有的 XML 文件中包含它们。在本节的后面,我们将介绍在这种“以 XML 为中心”的情况下使用`@Configuration`类的选项。

[]()将`@Configuration`类声明为普通 Spring `<bean/>`元素

请记住,`@Configuration`类最终是容器中的 Bean 定义。在本系列示例中,我们创建了一个名为`@Configuration`的`AppConfig`类,并将其包含在`system-test-config.xml`中,作为`<bean/>`的定义。因为打开了`<context:annotation-config/>`,容器识别`@Configuration`注释并正确处理`@Bean`中声明的`AppConfig`方法。

下面的示例展示了一个普通的 Java 配置类:

Java

```
@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Autowired
    private lateinit var dataSource: DataSource

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }

    @Bean
    fun transferService() = TransferService(accountRepository())
}
```

下面的示例显示了`system-test-config.xml`文件示例的一部分:

```
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
```

下面的示例显示了一个可能的`jdbc.properties`文件:

```
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
```

Java

```
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
```

Kotlin

```
fun main() {
    val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
    val transferService = ctx.getBean<TransferService>()
    // ...
}
```

|   |在`system-test-config.xml`文件中,`AppConfig``<bean/>`不声明`id`元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他 Bean <br/>引用过它,并且不太可能通过名称从容器中显式地获取它,同样,<br/> Bean 也只能通过类型自动连线,所以显式 Bean `id`并不是严格要求的。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

[]()使用 \<context:component-scan/\>获取`@Configuration`类

因为`@Configuration`是用`@Component`进行元注释的,所以`@Configuration`-注释的类自动成为组件扫描的候选类。使用与前面示例中描述的相同的场景,我们可以重新定义`system-test-config.xml`以利用组件扫描。注意,在这种情况下,我们不需要显式声明`<context:annotation-config/>`,因为`<context:component-scan/>`启用了相同的功能。

下面的示例显示了修改后的`system-test-config.xml`文件:

```
<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
```

###### `@Configuration`以类为中心使用`@ImportResource`######

在以`@Configuration`类为配置容器的主要机制的应用程序中,仍然可能需要至少使用一些 XML。在这些场景中,你可以使用`@ImportResource`并只定义所需的 XML。这样就实现了一种“以 Java 为中心”的方法来配置容器,并将 XML 保持在最低限度。下面的示例(包括一个配置类、一个定义 Bean 的 XML 文件、一个属性文件和`main`类)展示了如何使用`@ImportResource`注释来实现根据需要使用 XML 的“以 Java 为中心”的配置:

Java

```
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}
```

Kotlin

```
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {

    @Value("\${jdbc.url}")
    private lateinit var url: String

    @Value("\${jdbc.username}")
    private lateinit var username: String

    @Value("\${jdbc.password}")
    private lateinit var password: String

    @Bean
    fun dataSource(): DataSource {
        return DriverManagerDataSource(url, username, password)
    }
}
```

```
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
```

```
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
```

Java

```
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
```

Kotlin

```
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    // ...
}
```

### 1.13.环境抽象

[`Environment`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/ENV/environment.html)接口是集成在容器中的一种抽象,它为应用程序环境的两个关键方面建模:[profiles](#beans-definition-profiles)和[properties](#beans-property-source-abstraction)。

Bean 配置文件是由 Bean 个定义组成的一个命名的逻辑组,只有在给定的配置文件处于活动状态时才会向容器注册。可以将 bean 分配给一个配置文件,无论是用 XML 定义的还是用注释定义的。与配置文件相关的`Environment`对象的作用是确定哪些配置文件(如果有的话)当前处于活动状态,以及默认情况下哪些配置文件(如果有的话)应该处于活动状态。

属性在几乎所有的应用程序中都扮演着重要的角色,并且可能来自各种来源:属性文件、JVM 系统属性、系统环境变量、JNDI、 Servlet 上下文参数、ad-hoc`Properties`对象、`Map`对象,等等。与属性相关的`Environment`对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从它们解析属性。

#### 1.13.1. Bean 定义配置文件

Bean 定义配置文件在核心容器中提供了一种机制,该机制允许在不同的环境中注册不同的 bean。“环境”这个词对不同的用户来说可能意味着不同的东西,这个功能可以帮助解决许多用例,包括:

* 在开发中使用内存中的数据源,而不是在 QA 或生产中从 JNDI 中查找相同的数据源。

* 仅在将应用程序部署到性能环境时注册监视基础结构。

* 为客户 A 和客户 B 的部署注册定制的 bean 实现。

考虑实际应用程序中需要`DataSource`的第一个用例。在测试环境中,配置可能类似于以下内容:

Java

```
@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}
```

Kotlin

```
@Bean
fun dataSource(): DataSource {
    return EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("my-schema.sql")
            .addScript("my-test-data.sql")
            .build()
}
```

现在考虑如何将此应用程序部署到 QA 或生产环境中,假设应用程序的数据源已注册到生产应用程序服务器的 JNDI 目录中。我们的`dataSource` Bean 现在看起来像以下清单:

Java

```
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
```

Kotlin

```
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
    val ctx = InitialContext()
    return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
```

问题是如何根据当前环境在使用这两种变体之间进行切换。 Spring 随着时间的推移,用户已经设计了许多方法来完成这一工作,通常依赖于系统环境变量和 XML语句的组合,这些语句包含令牌,这些令牌根据环境变量的值解析为正确的配置文件路径。 Bean 定义配置文件是容器的核心特征,其提供了解决该问题的方法。

如果我们推广前面的例子中所示的特定于环境的定义 Bean 的用例,那么我们最终需要在某些上下文中注册某些 Bean 定义,而不是在其他上下文中注册。可以说,你希望在情况 A 中注册 Bean 定义的特定配置文件,在情况 B 中注册不同的配置文件。我们从更新配置开始,以反映这种需求。

#####  使用`@Profile`

[`@Profile`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/profile.html)注释允许你指示,当一个或多个指定的配置文件处于活动状态时,组件有资格进行注册。使用前面的示例,我们可以按以下方式重写`dataSource`配置:

Java

```
@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
```

Kotlin

```
@Configuration
@Profile("development")
class StandaloneDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }
}
```

Java

```
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
```

Kotlin

```
@Configuration
@Profile("production")
class JndiDataConfig {

    @Bean(destroyMethod = "")
    fun dataSource(): DataSource {
        val ctx = InitialContext()
        return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
    }
}
```

|   |如前所述,对于`@Bean`方法,你通常会选择使用程序化的<br/>JNDI 查找,方法是使用 Spring 的`JndiTemplate`/`JndiLocatorDelegate`helpers 或<br/>直接的 JNDI`InitialContext`用法,但不使用前面显示的`JndiObjectFactoryBean`变体,这将迫使你将返回类型声明为`FactoryBean`类型。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

配置文件字符串可以包含一个简单的配置文件名(例如,`production`)或一个配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑(例如,`production & us-east`)。配置文件表达式中支持以下操作符:

* `!`:配置文件的逻辑“not”

* `&`:配置文件的逻辑“和”

* `|`:配置文件的逻辑“或”

|   |You cannot mix the `&` and `|` operators without using parentheses. For example,`production & us-east |EU-Central` is not a valid expression. It must be expressed as`Production&(美国东部)| eu-central)`.|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你可以使用`@Profile`作为[元注释](#beans-meta-annotations)来创建自定义组合注释。下面的示例定义了一个自定义的`@Production`注释,你可以将其用作`@Profile("production")`的插入替换:

Java

```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
```

Kotlin

```
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
```

|   |如果一个`@Configuration`类被标记为`@Profile`,则所有与该类相关的`@Bean`方法和`@Import`注释都将被绕过,除非一个或多个<br/>指定的配置文件处于活动状态。如果一个`@Component`或`@Configuration`类被标记为<br/>并带有`@Profile({"p1", "p2"})`,则除非<br/>配置文件“p1”或“p2”已被激活,否则该类不会被注册或处理。如果给定概要文件的前缀是<br/>not 运算符(`!`),则只有在概要文件不是<br/>活动的情况下,才会注册带注释的元素。例如,给定`@Profile({"p1", "!p2"})`,如果配置文件<br/>’P1’是活动的,或者如果配置文件’P2’不是活动的,就会发生注册。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

`@Profile`还可以在方法级别上声明仅包括一个特定的 Bean 配置类(例如,对于特定的 Bean 的可选变体),如以下示例所示:

Java

```
@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") (1)
    public DataSource standaloneDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean("dataSource")
    @Profile("production") (2)
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
```

|**1**|`standaloneDataSource`方法仅在`development`配置文件中可用。|
|-----|---------------------------------------------------------------------------------|
|**2**|`jndiDataSource`方法仅在`production`配置文件中可用。|

Kotlin

```
@Configuration
class AppConfig {

    @Bean("dataSource")
    @Profile("development") (1)
    fun standaloneDataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }

    @Bean("dataSource")
    @Profile("production") (2)
    fun jndiDataSource() =
        InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
```

|**1**|`standaloneDataSource`方法仅在`development`配置文件中可用。|
|-----|---------------------------------------------------------------------------------|
|**2**|`jndiDataSource`方法仅在`production`配置文件中可用。|

|   |对于`@Profile`on`@Bean`方法,可能会应用一个特殊的场景:在<br/>重载`@Bean`相同 Java 方法名的方法的情况下(类似于构造函数<br/>重载),需要在所有`@Profile`重载方法上一致地声明`@Profile`条件。如果条件不一致,在重载的方法中,只有<br/>第一个声明上的条件才重要。因此,`@Profile`可以不使用<br/>在<br/>上选择具有特定参数签名的重载方法。相同 Bean 的所有工厂方法之间的解析遵循 Spring 的<br/>构造函数在创建时的解析算法。<br/><br/>如果你想定义具有不同配置文件条件的替代 bean,<br/>使用不同的 Java 方法名称,通过使用`@Bean`名称<br/>属性指向相同的 Bean 名称,如前面的例子所示。如果参数签名都是<br/>相同的(例如,所有变量都有 no-arg 工厂方法),这是唯一的<br/>在有效的 Java 类中表示这样的安排的方式,放在第一个位置<br/>(因为对于特定的名称和参数签名,只能有一种方法)。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  XML Bean 定义配置文件

XML 对应的是`profile`元素的`<beans>`属性。我们前面的示例配置可以在两个 XML 文件中重写,如下所示:

```
<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
```

```
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
```

也可以避免在同一个文件中分割和嵌套`<beans/>`元素,如下例所示:

```
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>
```

`spring-bean.xsd`已被限制为仅允许将此类元素作为文件中的最后一个元素。这应该有助于提供灵活性,而不会在 XML 文件中造成混乱。

|   |XML 对应方不支持前面描述的配置文件表达式。但是,<br/>可以使用`!`操作符来否定配置文件。也可以通过嵌套配置文件来应用逻辑<br/>“and”,如下例所示:<br/><br/>```<br/><beans xmlns="http://www.springframework.org/schema/beans"<br/>    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br/>    xmlns:jdbc="http://www.springframework.org/schema/jdbc"<br/>    xmlns:jee="http://www.springframework.org/schema/jee"<br/>    xsi:schemaLocation="..."><br/><br/>    <!-- other bean definitions --><br/><br/>    <beans profile="production"><br/>        <beans profile="us-east"><br/>            <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/><br/>        </beans><br/>    </beans><br/></beans><br/>```<br/><br/>在前面的示例中,如果`dataSource` Bean 和`us-east`配置文件都处于活动状态,则将暴露`dataSource`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  激活配置文件

既然我们已经更新了配置,我们仍然需要指示 Spring 哪个配置文件是活动的。如果我们现在开始示例应用程序,我们将看到一个`NoSuchBeanDefinitionException`抛出,因为容器找不到名为`dataSource`的 Spring  Bean。

激活配置文件可以通过几种方式完成,但最直接的方法是通过`Environment`API 以编程方式完成它,该 API 可通过`ApplicationContext`获得。下面的示例展示了如何做到这一点:

Java

```
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
```

Kotlin

```
val ctx = AnnotationConfigApplicationContext().apply {
    environment.setActiveProfiles("development")
    register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
    refresh()
}
```

此外,你还可以通过`spring.profiles.active`属性声明性地激活配置文件,该属性可以通过系统环境变量、JVM 系统属性、`web.xml`中的 Servlet 上下文参数、甚至作为 JNDI 中的条目来指定(参见[`PropertySource`Abstraction](#beans-property-source-abstraction))。在集成测试中,可以通过在`spring-test`模块中使用`@ActiveProfiles`注释来声明活动配置文件(参见[具有环境配置文件的上下文配置](testing.html#testcontext-ctx-management-env-profiles))。

请注意,配置文件不是一个“非此即彼”的命题。你可以一次激活多个配置文件。通过编程,你可以为`setActiveProfiles()`方法提供多个配置文件名,该方法接受`String…​`vargs。以下示例激活多个配置文件:

Java

```
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
```

Kotlin

```
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
```

声明地,`spring.profiles.active`可以接受以逗号分隔的配置文件名称列表,如下例所示:

```
    -Dspring.profiles.active="profile1,profile2"
```

#####  默认配置文件

默认配置文件表示默认启用的配置文件。考虑以下示例:

Java

```
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}
```

Kotlin

```
@Configuration
@Profile("default")
class DefaultDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .build()
    }
}
```

如果没有活动的配置文件,则创建`dataSource`。你可以将此视为为一个或多个 bean 提供默认定义的一种方式。如果启用了任何配置文件,则默认配置文件不适用。

你可以通过在`Environment`上使用`setDefaultProfiles()`来更改默认配置文件的名称,或者以声明性的方式使用`spring.profiles.default`属性来更改默认配置文件的名称。

#### 1.13.2.`PropertySource`抽象

Spring 的`Environment`抽象提供了对属性源的可配置层次结构的搜索操作。考虑一下以下清单:

Java

```
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
```

Kotlin

```
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
```

在前面的代码片段中,我们看到了一种高级的方式来询问 Spring 是否为当前环境定义了`my-property`属性。为了回答这个问题,`Environment`对象在一组[`PropertySource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/ENV/propertysource.html)对象上执行搜索。a`PropertySource`是对任何键值对的源的简单抽象, Spring 的[`StandardEnvironment`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/ENV/standardenvironment.html)配置了两个 PropertySource 对象——一个代表 JVM 系统属性集(`System.getProperties()`),一个代表系统环境变量集(<r="3267"/>)。

|   |这些默认属性源用于`StandardEnvironment`,用于独立的<br/>应用程序。[`StandardServletEnvironment`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/context/support/standardservletenvironment.html)填充了其他默认属性源,包括 Servlet config 和 Servlet <br/>context 参数。它可以任选地启用一个[`JndiPropertySource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/jndi/jndipropertysource.html)。<gtr="3273"/>详情请参见 javadoc。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

具体地说,当使用`StandardEnvironment`时,如果在运行时存在`my-property`系统属性或`my-property`环境变量,则对`env.containsProperty("my-property")`的调用将返回 true。

|   |执行的搜索是分层次的。默认情况下,系统属性优先于<br/>环境变量。因此,如果`my-property`属性在<br/>调用`env.getProperty("my-property")`的过程中恰好在这两个地方都设置了,则系统属性值“wins”并被返回。,<br/>注意,属性值不是合并<br/>,而是完全被前面的一个条目覆盖,<br/><br/>对于一个常见的`StandardServletEnvironment`,完整的层次结构如下,<br/>优先级最高的条目位于顶部:<br/><br/>1。ServletConfig 参数(如果适用——例如,在`DispatcherServlet`上下文的情况下)<br/><br/>2。ServletContext 参数<br/><br/>3。JNDI 环境变量(`java:comp/env/`条目)<br/><br/>4。JVM 系统属性(`-D`命令行参数)<br/><br/>5。JVM 系统环境(操作系统环境变量)|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

最重要的是,整个机制是可配置的。也许你有希望集成到此搜索中的自定义属性源。要做到这一点,实现并实例化你自己的`PropertySource`,并将其添加到当前`PropertySources`的`Environment`的集合中。下面的示例展示了如何做到这一点:

Java

```
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
```

Kotlin

```
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
```

在前面的代码中,`MyPropertySource`已被添加到搜索中具有最高优先级的位置。如果它包含一个`my-property`属性,则检测并返回该属性,以有利于任何其他`my-property`中的任何`PropertySource`属性。[`MutablePropertySources`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/ENV/mutablepropertysources.html)API 公开了许多允许精确操作属性源集合的方法。

#### 1.13.3.使用`@PropertySource`

[`@PropertySource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/propertysource.html)注释提供了一种方便的声明性机制,用于将`PropertySource`添加到 Spring 的`Environment`。

给定一个名为`app.properties`的文件,该文件包含键-值对`testbean.name=myTestBean`,下面的`@Configuration`类使用`@PropertySource`,使得对`testBean.getName()`的调用返回`myTestBean`:

Java

```
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
```

Kotlin

```
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {

    @Autowired
    private lateinit var env: Environment

    @Bean
    fun testBean() = TestBean().apply {
        name = env.getProperty("testbean.name")!!
    }
}
```

在`@PropertySource`资源位置中存在的任何`${…​}`占位符都将根据已经针对该环境注册的一组属性源进行解析,如下例所示:

Java

```
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
```

Kotlin

```
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {

    @Autowired
    private lateinit var env: Environment

    @Bean
    fun testBean() = TestBean().apply {
        name = env.getProperty("testbean.name")!!
    }
}
```

假设`my.placeholder`存在于已经注册的一个属性源中(例如,系统属性或环境变量),则将占位符解析为相应的值。如果不是,则将`default/path`作为默认值。如果没有指定默认值,并且无法解析某个属性,则抛出一个`IllegalArgumentException`。

|   |根据 Java8 约定,`@PropertySource`注释是可重复的。<br/>然而,所有此类`@PropertySource`注释都需要在相同的<br/>级别上声明,可以直接在配置类上声明,也可以在<br/>相同的自定义注释中声明元注释。混合直接注释和元注释不是<br/>推荐的,因为直接注释有效地覆盖了元注释。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.13.4.语句中的占位符解析

从历史上看,元素中占位符的值只能根据 JVM 系统属性或环境变量来解析。现在已经不是这样了。因为`Environment`抽象集成在整个容器中,所以很容易通过它来路由占位符的解析。这意味着你可以以你喜欢的任何方式配置解析过程。你可以更改通过系统属性和环境变量进行搜索的优先级,或者完全删除它们。你也可以将自己的属性源添加到该组合中,视情况而定。

具体地说,无论`customer`属性在哪里定义,只要它在`Environment`中可用,以下语句都可以工作:

```
<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>
```

### 1.14.注册`LoadTimeWeaver`

Spring 使用`LoadTimeWeaver`在类被加载到 Java 虚拟机时对其进行动态转换。

要启用加载时编织,可以将`@EnableLoadTimeWeaving`添加到一个`@Configuration`类中,如下例所示:

Java

```
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
```

Kotlin

```
@Configuration
@EnableLoadTimeWeaving
class AppConfig
```

或者,对于 XML 配置,你可以使用`context:load-time-weaver`元素:

```
<beans>
    <context:load-time-weaver/>
</beans>
```

一旦为`ApplicationContext`进行了配置,在该`ApplicationContext`内的任何 Bean 都可以实现`LoadTimeWeaverAware`,从而接收到对加载时 Weaver 实例的引用。这在与[Spring’s JPA support](data-access.html#orm-jpa)的结合中特别有用,在这种结合中, JPA 类转换可能需要进行加载时的编织。有关更多详细信息,请参见[`LocalContainerEntityManagerFactoryBean`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/orm/ JPA/localContainetyRentyManagerFactoryBean.html)Javadoc。有关 AspectJ 加载时编织的更多信息,请参见[Load-time Weaving with AspectJ in the Spring Framework](#aop-aj-ltw)。

### 1.15.`ApplicationContext`的附加功能

正如[章节介绍](#beans)中所讨论的,`org.springframework.beans.factory`包提供了用于管理和操作 bean 的基本功能,包括以编程方式。`org.springframework.context`包添加了[`ApplicationContext`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/applicationcontext.html)接口,该接口扩展了`BeanFactory`接口,此外还扩展了其他接口,以更加面向应用框架的风格提供额外的功能。许多人以完全声明式的方式使用`ApplicationContext`,甚至不是以编程的方式创建它,而是依靠诸如`ContextLoader`之类的支持类来自动实例化`ApplicationContext`,作为 Java EE Web 应用程序正常启动过程的一部分。

为了以更面向框架的风格增强`BeanFactory`功能,上下文包还提供了以下功能:

* 通过`MessageSource`接口访问 i18n 样式的消息。

* 通过`ResourceLoader`接口访问资源,例如 URL 和文件。

* 事件发布,即通过使用`ApplicationEventPublisher`接口来实现`ApplicationListener`接口的 bean。

* 通过`HierarchicalBeanFactory`接口,加载多个(分层次的)上下文,使每个上下文都集中在一个特定的层上,例如应用程序的 Web 层。

#### 1.15.1.使用`MessageSource`的国际化

`ApplicationContext`接口扩展了一个名为`MessageSource`的接口,因此提供了国际化(“i18n”)功能。 Spring 还提供了`HierarchicalMessageSource`接口,其能够按层次解析消息。这些接口一起提供了 Spring 影响消息解析的基础。在这些接口上定义的方法包括:

* `String getMessage(String code, Object[] args, String default, Locale loc)`:用于从`MessageSource`中检索消息的基本方法。当未找到指定区域设置的消息时,将使用默认消息。使用标准库提供的`MessageFormat`功能,传入的任何参数都将成为替换值。

* `String getMessage(String code, Object[] args, Locale loc)`:与以前的方法基本相同,但有一个区别:不能指定默认消息。如果找不到消息,将抛出`NoSuchMessageException`。

* `String getMessage(MessageSourceResolvable resolvable, Locale locale)`:前面的方法中使用的所有属性也包装在一个名为`MessageSourceResolvable`的类中,你可以在这个方法中使用它。

当加载`ApplicationContext`时,它会自动搜索上下文中定义的`MessageSource` Bean。 Bean 必须具有`messageSource`的名称。如果找到了这样的 Bean,那么对前面的方法的所有调用都被委托给消息源。如果找不到消息源,`ApplicationContext`将尝试查找包含同名 Bean 的父消息。如果是,则使用 Bean 作为`MessageSource`。如果`ApplicationContext`找不到任何消息源,则实例化一个空的`DelegatingMessageSource`,以便能够接受对上述定义的方法的调用。

Spring 提供了三个`MessageSource`实现方式,`ResourceBundleMessageSource`、`ReloadableResourceBundleMessageSource`和`StaticMessageSource`。它们都实现`HierarchicalMessageSource`以执行嵌套消息传递。`StaticMessageSource`很少使用,但提供了向源添加消息的编程方法。下面的示例显示`ResourceBundleMessageSource`:

```
<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>
```

该示例假定你在 Classpath 中定义了三个资源包,分别称为`format`、`exceptions`和`windows`。通过`ResourceBundle`对象以解析消息的 JDK 标准方式处理任何解析消息的请求。为了示例的目的,假设上述两个资源包文件的内容如下:

```
    # in format.properties
    message=Alligators rock!
```

```
    # in exceptions.properties
    argument.required=The {0} argument is required.
```

下一个示例显示了运行`MessageSource`功能的程序。请记住,所有`ApplicationContext`实现也是`MessageSource`实现,因此可以强制转换到`MessageSource`接口。

Java

```
public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}
```

Kotlin

```
fun main() {
    val resources = ClassPathXmlApplicationContext("beans.xml")
    val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
    println(message)
}
```

上述程序的输出结果如下:

```
Alligators rock!
```

总而言之,`MessageSource`是在一个名为`beans.xml`的文件中定义的,该文件存在于 Classpath 的根。`messageSource` Bean 定义通过其`basenames`属性引用了许多资源包。在列表中传递给`basenames`属性的三个文件作为文件存在于 Classpath 的根目录中,分别称为`format.properties`、`exceptions.properties`和`windows.properties`。

下一个示例显示传递给消息查找的参数。这些参数被转换为`String`对象,并插入到查找消息中的占位符中。

```
<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
```

爪哇

```
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", Locale.ENGLISH);
        System.out.println(message);
    }
}
```

Kotlin

```
    class Example {

    lateinit var messages: MessageSource

    fun execute() {
        val message = messages.getMessage("argument.required",
                arrayOf("userDao"), "Required", Locale.ENGLISH)
        println(message)
    }
}
```

调用`execute()`方法的结果如下:

```
The userDao argument is required.
```

关于国际化(“i18n”), Spring 的各种`MessageSource`实现遵循与标准 JDK`ResourceBundle`相同的语言环境解析和后备规则。简而言之,继续前面定义的示例`messageSource`,如果你希望针对英国(`en-GB`)语言环境解析消息,那么你将分别创建名为`format_en_GB.properties`、`exceptions_en_GB.properties`和`windows_en_GB.properties`的文件。

通常,区域设置解析由应用程序的周围环境管理。在下面的示例中,将手动指定用于解析(英式)消息的区域设置:

```
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
```

爪哇

```
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}
```

Kotlin

```
fun main() {
    val resources = ClassPathXmlApplicationContext("beans.xml")
    val message = resources.getMessage("argument.required",
            arrayOf("userDao"), "Required", Locale.UK)
    println(message)
}
```

运行上述程序的结果如下:

```
Ebagum lad, the 'userDao' argument is required, I say, required.
```

你还可以使用`MessageSourceAware`接口获取对已定义的任何`MessageSource`的引用。在实现`MessageSourceAware`接口的`ApplicationContext`中定义的任何 Bean,在创建和配置 Bean 时,都会被注入应用程序上下文的`MessageSource`。

|   |因为 Spring 的`MessageSource`是基于 爪哇 的`ResourceBundle`,所以它不会合并具有相同基名的<br/>bundle,而只会使用找到的第一个 bundle。<br/>具有相同基名的后续消息 bundle 将被忽略。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |作为`ResourceBundleMessageSource`的替代方案, Spring 提供了一个`ReloadableResourceBundleMessageSource`类。这个变体支持相同的 bundle<br/>文件格式,但比基于`ResourceBundleMessageSource`实现的标准 JDK 更灵活。特别是,它允许从任何 Spring 资源位置读取<br/>文件(不仅是从 Classpath),并支持 hot<br/>重新加载捆绑包属性文件(同时在两者之间有效地缓存它们)。<br/>参见[`ReloadableResourceBundleMessageSource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-context/org/javadoFramework/org/javadoFramework/org/reloadableResourceource.html)|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.15.2.标准和自定义事件

`ApplicationContext`中的事件处理是通过`ApplicationEvent`类和`ApplicationListener`接口提供的。如果将实现`ApplicationListener`接口的 Bean 部署到上下文中,则每次将`ApplicationEvent`发布到`ApplicationContext`时,都会通知 Bean 该接口。本质上,这是标准的观察者设计模式。

|   |截至 Spring 4.2,事件基础结构已经得到了显著的改进,并且提供了<br/>和[基于注释的模型](#context-functionality-events-annotation)以及<br/>发布任意事件的能力(即,不一定从<br/>扩展的对象`ApplicationEvent`)。当这样的对象被发布时,我们将它包装为<br/>事件。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下表描述了 Spring 提供的标准事件:

|           Event            |解释|
|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|  `ContextRefreshedEvent`   |当`ApplicationContext`被初始化或刷新时发布(例如,通过<br/>使用`refresh()`接口上的`ConfigurableApplicationContext`方法)。<br/>在这里,“初始化”意味着加载所有 bean,检测到后处理器 bean<br/>并激活,预先实例化单例,`ApplicationContext`对象<gt="3448"/>准备好使用。只要上下文尚未关闭,就可以多次触发<br/>刷新,前提是所选的`ApplicationContext`实际上支持这样的<br/>“热”刷新。例如,`XmlWebApplicationContext`支持热刷新,但`GenericApplicationContext`不支持。|
|   `ContextStartedEvent`    |当`ApplicationContext`通过在`ConfigurableApplicationContext`接口上使用`start()`方法启动`ApplicationContext`时发布。在这里,“started”表示所有`Lifecycle`bean 都接收到一个显式的开始信号。通常,此信号用于在显式停止后重新启动 bean<br/>,但也可以用于启动未被<br/>配置为自动启动的组件(例如,在<br/>初始化上尚未启动的组件)。|
|   `ContextStoppedEvent`    |当`ApplicationContext`通过在`ConfigurableApplicationContext`接口上使用`stop()`方法停止`ApplicationContext`时发布。这里,“stopped”表示所有`Lifecycle`bean 都接收到一个明确的停止信号。可以通过`start()`调用重新启动已停止的上下文。|
|    `ContextClosedEvent`    |当`ApplicationContext`通过`close()`接口上的<br/>方法<br/>关闭`ApplicationContext`时或通过 JVM 关机钩子关闭时发布。在这里,<br/>“closed”表示所有单例 bean 都将被销毁。一旦上下文被关闭,<br/>它就到达了生命周期的终点,不能刷新或重新启动。|
|   `RequestHandledEvent`    |一个特定于 Web 的事件,告诉所有 bean 一个 HTTP 请求已被服务。此<br/>事件将在请求完成后发布。此事件仅适用于使用 Spring 的<br/>的`DispatcherServlet`的 Web 应用程序。|
|`ServletRequestHandledEvent`|添加 Servlet 特定上下文信息的`RequestHandledEvent`子类。|

你还可以创建和发布自己的自定义事件。下面的示例展示了一个扩展 Spring 的`ApplicationEvent`基类的简单类:

爪哇

```
public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}
```

Kotlin

```
class BlockedListEvent(source: Any,
                    val address: String,
                    val content: String) : ApplicationEvent(source)
```

要发布自定义`ApplicationEvent`,请在`ApplicationEventPublisher`上调用`publishEvent()`方法。通常,这是通过创建一个实现`ApplicationEventPublisherAware`的类并将其注册为 Spring  Bean 来完成的。下面的示例展示了这样一个类:

爪哇

```
public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
}
```

Kotlin

```
class EmailService : ApplicationEventPublisherAware {

    private lateinit var blockedList: List<String>
    private lateinit var publisher: ApplicationEventPublisher

    fun setBlockedList(blockedList: List<String>) {
        this.blockedList = blockedList
    }

    override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) {
        this.publisher = publisher
    }

    fun sendEmail(address: String, content: String) {
        if (blockedList!!.contains(address)) {
            publisher!!.publishEvent(BlockedListEvent(this, address, content))
            return
        }
        // send email...
    }
}
```

在配置时, Spring 容器检测到`EmailService`实现了`ApplicationEventPublisherAware`并自动调用`setApplicationEventPublisher()`。实际上,传入的参数是 Spring 容器本身。你正在通过其`ApplicationEventPublisher`接口与应用程序上下文交互。

要接收自定义`ApplicationEvent`,你可以创建一个实现`ApplicationListener`的类,并将其注册为 Spring  Bean。下面的示例展示了这样一个类:

爪哇

```
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
```

Kotlin

```
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {

    lateinit var notificationAddres: String

    override fun onApplicationEvent(event: BlockedListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}
```

请注意,`ApplicationListener`通常是用自定义事件的类型参数化的(在前面的示例中是`BlockedListEvent`)。这意味着`onApplicationEvent()`方法可以保持类型安全,从而避免了任何向下转换的需要。你可以注册任意多的事件侦听器,但请注意,默认情况下,事件侦听器会同步接收事件。这意味着`publishEvent()`方法将阻塞,直到所有侦听器都完成了对事件的处理。这种同步和单线程方法的一个优点是,当侦听器接收到一个事件时,如果事务上下文可用,它将在发布者的事务上下文中进行操作。如果需要另一种事件发布策略,请参阅 爪哇doc 的[`ApplicationEventMulticaster`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/SpringFramework/context/event/ApplicationEventMulticaster.html)接口和[`SimpleApplicationEventMulticaster`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadog/springframework/eventapi/event/multicaster.html)配置选项的实现

下面的示例显示了用于注册和配置上述每个类的 Bean 定义:

```
<bean id="emailService" class="example.EmailService">
    <property name="blockedList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>

<bean id="blockedListNotifier" class="example.BlockedListNotifier">
    <property name="notificationAddress" value="[email protected]"/>
</bean>
```

将所有这些放在一起,当调用`emailService` Bean 的`sendEmail()`方法时,如果有任何应该被阻止的电子邮件消息,则会发布类型为`BlockedListEvent`的自定义事件。将`blockedListNotifier` Bean 注册为`ApplicationListener`并接收`BlockedListEvent`,此时它可以通知适当的当事人。

|   |Spring 的事件机制设计用于在相同的应用程序上下文中在 Spring bean<br/>之间进行简单的通信。然而,对于更复杂的 Enterprise<br/>集成需求,单独维护的[Spring Integration](https://projects.spring.io/spring-integration/)项目提供了<br/>构建轻量级、[以模式为导向](https://www.enterpriseintegrationpatterns.com)事件驱动的<br/>架构的完整支持,这些架构建立在著名的 Spring 编程模型上。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  基于注释的事件监听器

可以使用`@EventListener`注释在托管 Bean 的任何方法上注册事件侦听器。`BlockedListNotifier`可以重写如下:

爪哇

```
public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
```

Kotlin

```
class BlockedListNotifier {

    lateinit var notificationAddress: String

    @EventListener
    fun processBlockedListEvent(event: BlockedListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}
```

方法签名再次声明它监听的事件类型,但是,这一次,使用灵活的名称,并且不实现特定的监听器接口。只要实际的事件类型在其实现层次结构中解析你的泛型参数,就可以通过泛型来缩小事件类型的范围。

如果你的方法应该监听多个事件,或者如果你想在完全不使用参数的情况下定义它,那么也可以在注释本身上指定事件类型。下面的示例展示了如何做到这一点:

爪哇

```
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
```

Kotlin

```
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
    // ...
}
```

还可以通过使用注释的`condition`属性添加额外的运行时过滤,该注释定义了一个[`SpEL`表达式](# 表达式),它应该与实际调用特定事件的方法相匹配。

下面的示例显示了只有当事件的`content`属性等于`my-event`时,才可以重写我们的通知符:

爪哇

```
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}
```

Kotlin

```
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
    // notify appropriate parties via notificationAddress...
}
```

每个`SpEL`表达式都针对一个专用上下文进行计算。下表列出了对上下文可用的项,以便你可以将它们用于条件事件处理:

|     Name      |     Location     |说明|                                         Example                                         |
|---------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
|     Event     |   root object    |实际`ApplicationEvent`。|                                `#root.event` or `event`                                 |
|Arguments array|   root object    |用于调用方法的参数(作为对象数组)。|          `#root.args` or `args`; `args[0]` to access the first argument, etc.           |
|*Argument name*|evaluation context|任何方法参数的名称。如果由于某种原因,名称不可用<br/>(例如,因为编译的字节代码中没有调试信息),则单独的<br/>参数也可以使用`#a<#arg>`语法,其中`<#arg>`表示<br/>参数索引(从 0 开始)。|`#blEvent` or `#a0` (you can also use `#p0` or `#p<#arg>` parameter notation as an alias)|

请注意,`#root.event`允许你访问底层事件,即使你的方法签名实际上引用了已发布的任意对象。

如果需要发布一个事件作为处理另一个事件的结果,则可以更改方法签名以返回应该发布的事件,如下例所示:

爪哇

```
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
```

Kotlin

```
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
```

|   |[异步侦听器](#context-functionality-events-async)不支持此功能。|
|---|-----------------------------------------------------------------------------------------------|

`handleBlockedListEvent()`方法为它处理的每个`BlockedListEvent`发布一个新的`ListUpdateEvent`。如果需要发布多个事件,可以返回`Collection`或一个事件数组。

#####  异步侦听器

如果你希望一个特定的侦听器异步地处理事件,那么你可以重用[regular`@Async`support](integration.html#schooling-annotation-support-async)。下面的示例展示了如何做到这一点:

爪哇

```
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}
```

Kotlin

```
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
    // BlockedListEvent is processed in a separate thread
}
```

在使用异步事件时要注意以下限制:

* 如果异步事件侦听器抛出`Exception`,则不会将其传播给调用方。详见[`AsyncUncaughtExceptionHandler`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/ AOP/interceptor/asyncuncaughtexceptionhandler.html)。

* 异步事件侦听器方法不能通过返回值来发布后续事件。如果需要发布另一个事件作为处理的结果,则注入一个[`ApplicationEventPublisher`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/applicationeventpublisher.html)来手动发布事件。

#####  命令听众

如果需要在调用另一个侦听器之前调用一个侦听器,则可以将`@Order`注释添加到方法声明中,如下例所示:

爪哇

```
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}
```

Kotlin

```
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
    // notify appropriate parties via notificationAddress...
}
```

#####  一般事件

你还可以使用泛型来进一步定义事件的结构。考虑使用`EntityCreatedEvent<T>`,其中`T`是实际创建的实体的类型。例如,你可以创建以下监听器定义,以便只接收`EntityCreatedEvent`的`Person`:

爪哇

```
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
```

Kotlin

```
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
    // ...
}
```

由于类型擦除,只有当触发的事件解析了事件侦听器过滤的通用参数(即类似于`class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }`)时,这种方法才有效。

在某些情况下,如果所有事件都遵循相同的结构,这可能会变得非常乏味(前面示例中的事件就是这种情况)。在这种情况下,你可以实现`ResolvableTypeProvider`以指导超出运行时环境所提供的框架。以下事件展示了如何做到这一点:

爪哇

```
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}
```

Kotlin

```
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {

    override fun getResolvableType(): ResolvableType? {
        return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
    }
}
```

|   |这不仅适用于`ApplicationEvent`,还适用于作为<br/>事件发送的任意对象。|
|---|--------------------------------------------------------------------------------------------------|

#### 1.15.3.对低级资源的方便访问

为了优化应用程序上下文的使用和理解,你应该熟悉 Spring 的`Resource`抽象,如[资源](#resources)中所述。

一个应用程序上下文是`ResourceLoader`,它可以用来加载`Resource`对象。a`Resource`本质上是 JDK`java.net.URL`类的一个功能更丰富的版本。实际上,在`Resource`的实现方式中,在适当的情况下包装`java.net.URL`的实例。a`Resource`可以以透明的方式从几乎任何位置获得低级资源,包括从 Classpath、文件系统位置、标准 URL 可描述的任何地方以及其他一些变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合实际的应用程序上下文类型。

可以将 Bean 部署到应用程序上下文中以实现特殊的回调接口`ResourceLoaderAware`,以便在初始化时自动回调,并将应用程序上下文本身作为`ResourceLoader`传入。还可以公开类型`Resource`的属性,用于访问静态资源。它们像其他任何属性一样被注入其中。你可以将这些`Resource`属性指定为简单的`String`路径,并依赖于在部署 Bean 时将这些文本字符串自动转换为实际的`Resource`对象。

提供给`ApplicationContext`构造函数的位置路径实际上是资源字符串,并且以简单的形式根据特定的上下文实现适当地处理。例如,`ClassPathXmlApplicationContext`将简单的位置路径视为 Classpath 位置。你还可以使用带有特殊前缀的位置路径(资源字符串)来强制从 Classpath 或 URL 加载定义,而不管实际的上下文类型如何。

#### 1.15.4.应用程序启动跟踪

`ApplicationContext`管理 Spring 应用程序的生命周期,并提供围绕组件的丰富的编程模型。因此,复杂的应用程序可以具有同样复杂的组件图和启动阶段。

使用特定的指标跟踪应用程序的启动步骤可以帮助了解在启动阶段花费的时间,但也可以将其用作更好地理解整个上下文生命周期的一种方式。

`AbstractApplicationContext`(及其子类)用`ApplicationStartup`进行检测,它收集关于各种启动阶段的`StartupStep`数据:

* 应用程序上下文生命周期(基本包扫描、配置类管理)

* bean 生命周期(实例化、智能初始化、后处理)

* 应用程序事件处理

下面是`AnnotationConfigApplicationContext`中的一个插装示例:

爪哇

```
// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();
```

Kotlin

```
// create a startup step and start recording
val scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages)
// end the current step
scanPackages.end()
```

应用程序上下文已经通过多个步骤进行了检测。一旦记录了这些启动步骤,就可以用特定的工具收集、显示和分析这些步骤。有关现有启动步骤的完整列表,你可以查看[专用附录部分](#application-startup-steps)。

默认的`ApplicationStartup`实现是一个无操作的变体,以减少开销。这意味着默认情况下,在应用程序启动期间不会收集任何指标。 Spring Framework 附带了一种用于跟踪使用 爪哇 飞行记录器的启动步骤的实现:`FlightRecorderApplicationStartup`。要使用这个变体,你必须在创建它之后立即将它的实例配置为`ApplicationContext`。

如果开发人员提供自己的`AbstractApplicationContext`子类,或者如果他们希望收集更精确的数据,那么他们也可以使用`ApplicationStartup`基础架构。

|   |`ApplicationStartup`仅用于应用程序启动期间和<br/>核心容器;这绝不是对 爪哇 探查器或<br/>等度量类库的替代。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

要开始收集自定义`StartupStep`,组件可以直接从应用程序上下文获得`ApplicationStartup`实例,使其组件实现`ApplicationStartupAware`,或者在任何注入点上请求`ApplicationStartup`类型。

|   |开发人员在创建自定义启动步骤时不应使用`"spring.*"`名称空间。<br/>此名称空间是为内部 Spring 使用而保留的,并且可能会发生更改。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.15.5.用于 Web 应用程序的方便的应用程序上下文实例化

你可以通过使用(例如)`ContextLoader`来声明性地创建`ApplicationContext`实例。当然,你也可以通过使用`ApplicationContext`实现之一以编程方式创建`ApplicationContext`实例。

你可以使用`ContextLoaderListener`注册`ApplicationContext`,如下例所示:

```
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
```

侦听器检查`contextConfigLocation`参数。如果参数不存在,侦听器将使用`/WEB-INF/applicationContext.xml`作为默认值。当参数确实存在时,侦听器使用预定义的分隔符(逗号、分号和空格)分隔`String`,并将这些值用作搜索应用程序上下文的位置。 Ant-样式的路径模式也被支持。例如`/WEB-INF/*Context.xml`(对于所有名称以`Context.xml`结尾且位于`WEB-INF`目录中的文件)和`/WEB-INF/**/*Context.xml`(对于`WEB-INF`任意子目录中的所有此类文件)。

#### 1.15.6.将 Spring `ApplicationContext`部署为 爪哇 EE RAR 文件

可以将 Spring `ApplicationContext`部署为 RAR 文件,将上下文及其所需的所有 Bean 类和库 JAR 封装在 爪哇 EE RAR 部署单元中。这相当于引导一个独立的`ApplicationContext`(仅托管在 爪哇 EE 环境中)能够访问 爪哇 EE 服务器设施。RAR 部署是部署无头 WAR 文件的一种更自然的替代方案——实际上,这是一个没有任何 HTTP 入口点的 WAR 文件,仅用于在 爪哇 EE 环境中引导 Spring 。

对于不需要 HTTP 入口点,而只包含消息端点和计划作业的应用程序上下文,RAR 部署是理想的。这种上下文中的 bean 可以使用应用程序服务器资源,例如 JTA 事务管理器和绑定 JNDI 的 JDBC`DataSource`实例和 JMS`ConnectionFactory`实例,还可以通过 Spring 的标准事务管理以及 JNDI 和 JMX 支持设施向平台的 JMX 服务器注册。应用程序组件还可以通过 Spring 的`TaskExecutor`抽象与应用程序服务器的 JCA`WorkManager`进行交互。

关于 RAR 部署中涉及的配置细节,请参见[`SpringContextResourceAdapter`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/jca/context/springcontextresourceadapter.html)类的 爪哇doc。

将 Spring ApplicationContext 作为 爪哇 EE RAR 文件进行简单部署:

1. 将所有应用程序类打包到一个 RAR 文件中(这是一个标准的 jar 文件,具有不同的文件扩展名)。

2. 将所有必需的库 JAR 添加到 RAR 归档的根目录中。

3. 添加一个`META-INF/ra.xml`部署描述符(如[javadoc for`SpringContextResourceAdapter`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/jca/context/context/springcontextresourceadapter.html))和相应的 Spring xml Bean 定义文件(通常<gtr="3597"/>)。

4. 将生成的 RAR 文件放入应用程序服务器的部署目录中。

|   |这样的 RAR 部署单元通常是独立的。它们不向外部世界公开组件<br/>,甚至不向同一应用程序的其他模块公开组件。与基于<br/>RAR 的`ApplicationContext`的交互通常通过它与<br/>其他模块共享的 JMS 目的地进行。基于 RAR 的`ApplicationContext`也可以,例如,调度一些作业<br/>或对文件系统中的新文件做出反应(或类似)。如果它需要允许来自外部的同步<br/>访问,则它可以(例如)导出 RMI 端点,这可以由同一台机器上的其他应用程序模块使用<br/>。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 1.16.the`BeanFactory`

`BeanFactory`API 为 Spring 的 IOC 功能提供了底层基础。它的特定契约主要用于与 Spring 和相关的第三方框架的其他部分的集成,并且它的`DefaultListableBeanFactory`实现是更高级别`GenericApplicationContext`容器中的一个关键委托。

`BeanFactory`和相关接口(如`BeanFactoryAware`、`InitializingBean`、`DisposableBean`)是其他框架组件的重要集成点。通过不需要任何注释,甚至不需要反射,它们允许容器与其组件之间进行非常有效的交互。应用程序级 bean 可能使用相同的回调接口,但通常更喜欢声明性依赖注入,或者通过注释,或者通过编程配置。

请注意,核心`BeanFactory`API 级别及其`DefaultListableBeanFactory`实现不对要使用的配置格式或任何组件注释进行假设。所有这些风格都是通过扩展(例如`XmlBeanDefinitionReader`和`AutowiredAnnotationBeanPostProcessor`)来实现的,并在共享的`BeanDefinition`对象上作为核心元数据表示进行操作。这就是 Spring 的容器如此灵活和可扩展的本质。

#### 1.16.1.`BeanFactory`还是`ApplicationContext`?

本节解释`BeanFactory`和`ApplicationContext`容器级别之间的差异以及对引导的影响。

除非你有充分的理由不这样做,否则你应该使用`ApplicationContext`,将`GenericApplicationContext`及其子类`AnnotationConfigApplicationContext`作为自定义引导的公共实现。这些是 Spring 核心容器的主要入口点,用于所有常见目的:加载配置文件,触发 Classpath 扫描,以编程方式注册 Bean 定义和注释的类,以及(截至 5.0)注册功能 Bean 定义。

因为`ApplicationContext`包含了`BeanFactory`的所有功能,所以一般建议使用普通的`BeanFactory`,除非需要对 Bean 处理进行完全控制。在`ApplicationContext`(例如`GenericApplicationContext`实现)中,可以根据约定(即通过 Bean name 或 Bean type——特别是后处理器)检测几种 bean,而普通的`DefaultListableBeanFactory`对任何特殊的 bean 都是不可知的。

对于许多扩展容器特性,例如注释处理和 AOP 代理,[`BeanPostProcessor`扩展点]是必不可少的。如果只使用普通的`DefaultListableBeanFactory`,则默认情况下不会检测到并激活这样的后处理器。这种情况可能会令人困惑,因为你的 Bean 配置实际上没有任何问题。相反,在这种情况下,容器需要通过额外的设置进行完全引导。

下表列出了`BeanFactory`和`ApplicationContext`接口和实现提供的功能。

|特征|`BeanFactory`|`ApplicationContext`|
|------------------------------------------------------------|-------------|--------------------|
|Bean 实例化/布线|     Yes     |        Yes         |
|集成的生命周期管理|     No      |        Yes         |
|自动`BeanPostProcessor`注册|     No      |        Yes         |
|自动`BeanFactoryPostProcessor`注册|     No      |        Yes         |
|方便`MessageSource`访问(用于国际化)|     No      |        Yes         |
|内置`ApplicationEvent`发布机制|     No      |        Yes         |

要显式地用`DefaultListableBeanFactory`注册 Bean 后处理器,需要以编程方式调用`addBeanPostProcessor`,如下例所示:

爪哇

```
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory
```

Kotlin

```
val factory = DefaultListableBeanFactory()
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(AutowiredAnnotationBeanPostProcessor())
factory.addBeanPostProcessor(MyBeanPostProcessor())

// now start using the factory
```

要将`BeanFactoryPostProcessor`应用于普通的`DefaultListableBeanFactory`,需要调用其`postProcessBeanFactory`方法,如下例所示:

爪哇

```
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);
```

Kotlin

```
val factory = DefaultListableBeanFactory()
val reader = XmlBeanDefinitionReader(factory)
reader.loadBeanDefinitions(FileSystemResource("beans.xml"))

// bring in some property values from a Properties file
val cfg = PropertySourcesPlaceholderConfigurer()
cfg.setLocation(FileSystemResource("jdbc.properties"))

// now actually do the replacement
cfg.postProcessBeanFactory(factory)
```

在这两种情况下,显式的注册步骤都是不方便的,这就是为什么在 Spring 支持的应用程序中,各种`ApplicationContext`变体比普通的`DefaultListableBeanFactory`更受欢迎,特别是当在典型的 Enterprise 设置中依赖`BeanFactoryPostProcessor`和`BeanPostProcessor`实例用于扩展容器功能时。

|   |一个`AnnotationConfigApplicationContext`已经注册了所有常见的注解后处理程序<br/>,并且可以通过配置注解在<br/>覆盖下引入额外的处理器,例如`@EnableTransactionManagement`。<br/>在 Spring 的基于注解的配置模型的抽象级,<br/>后处理程序的概念变成了仅仅是内部容器的详细信息。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 2. Resources

本章介绍了 Spring 如何处理资源,以及如何使用 Spring 中的资源。它包括以下主题:

* [导言](#resources-introduction)

* [the`Resource`接口](#resources-resource)

* [内置`Resource`实现](#resources-implementations)

* [the`ResourceLoader`接口](#resources-ResourceLoader)

* [the`ResourcePatternResolver`接口](#resources-ResourcePatternResolver)

* [the`ResourceLoaderAware`接口](#resources-ResourceLoaderAware)

* [作为依赖关系的资源](#resources-as-dependencies)

* [应用程序上下文和资源路径](#resources-app-ctx)

### 2.1.导言

令人遗憾的是,爪哇 的标准`java.net.URL`类和各种 URL 前缀的标准处理程序对于所有对低级资源的访问都不够充分。例如,没有标准化的`URL`实现,其可用于访问需要从 Classpath 或相对于`ServletContext`获得的资源。虽然可以为专门的`URL`前缀注册新的处理程序(类似于现有的用于`http:`等前缀的处理程序),但这通常是相当复杂的,而且`URL`接口仍然缺乏一些理想的功能,例如检查所指向的资源是否存在的方法。

### 2.2.`Resource`接口

Spring 的`Resource`接口位于`org.springframework.core.io.`包中,这意味着它是一个更有能力的接口,用于抽象对低级资源的访问。下面的清单提供了`Resource`接口的概述。有关更多详细信息,请参见[`Resource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/io/resource.html)爪哇doc。

```
public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    boolean isFile();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    ReadableByteChannel readableChannel() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}
```

正如`Resource`接口的定义所示,它扩展了`InputStreamSource`接口。下面的清单显示了`InputStreamSource`接口的定义:

```
public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}
```

来自`Resource`接口的一些最重要的方法是:

* `getInputStream()`:定位并打开资源,返回一个`InputStream`用于从资源中读取。预计每个调用都会返回一个新的`InputStream`。这是呼叫者的责任,以关闭该流。

* `exists()`:返回一个`boolean`,指示此资源是否实际以物理形式存在。

* `isOpen()`:返回一个`boolean`,指示此资源是否表示具有开放流的句柄。如果`true`,则`InputStream`不能多次读取,必须只读一次,然后关闭,以避免资源泄漏。对于所有常见的资源实现,返回`false`,但`InputStreamResource`除外。

* `getDescription()`:返回此资源的描述,用于在使用该资源时进行错误输出。这通常是完全限定的文件名或资源的实际 URL。

其他方法允许你获得代表资源的实际`URL`或`File`对象(如果底层实现是兼容的并且支持该功能)。

`Resource`接口的一些实现还实现了扩展的[`WritableResource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/io/writableresource.html)接口,用于支持对其进行写入的资源。

Spring 本身广泛地使用`Resource`抽象,在需要资源时作为许多方法签名中的参数类型。在一些 Spring API 中的其他方法(例如各种`ApplicationContext`实现的构造函数)采用`String`,该方法以未修饰或简单的形式用于创建适合该上下文实现的`Resource`,或者通过`String`路径上的特殊前缀,让调用者指定必须创建和使用特定的`Resource`实现。

虽然`Resource`接口在 Spring 和 Spring 中被大量使用,但实际上,在你自己的代码中将其本身用作一个通用实用程序类来访问资源是非常方便的,即使你的代码不知道或不关心 Spring 的任何其他部分。虽然这将你的代码耦合到 Spring,但它实际上只将它耦合到这一小组实用程序类,它可以作为`URL`的更强大的替代,并且可以被认为与你将用于此目的的任何其他库等同。

|   |`Resource`抽象不会取代功能。它将它封装在<br/>可能的位置。例如,`UrlResource`包装一个 URL,并使用包装好的`URL`来完成其<br/>工作。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 2.3.内置`Resource`实现

Spring 包括几个内置`Resource`实现方式:

* [`UrlResource`]

* [`ClassPathResource`](#resources-implementations-classpathresource)

* [`FileSystemResource`]

* [`PathResource`]

* [`ServletContextResource`](#resources-implementations-servletContextResource)

* [`InputStreamResource`]

* [`ByteArrayResource`]

有关 Spring 中可用的`Resource`实现的完整列表,请参阅[`Resource`]中的“所有已知实现类”部分(https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/io/resource.html)爪哇doc。

#### 2.3.1.`UrlResource`

`UrlResource`封装了`java.net.URL`,并可用于访问通常通过 URL 可访问的任何对象,例如文件、HTTPS 目标、FTP 目标和其他对象。所有的 URL 都有一个标准化的`String`表示,使得适当的标准化前缀被用来从另一个 URL 类型指示一个 URL 类型。这包括用于访问文件系统路径的`file:`,用于通过 HTTPS 协议访问资源的`https:`,用于通过 FTP 访问资源的`ftp:`,以及其他。

`UrlResource`是由 爪哇 代码通过显式使用`UrlResource`构造函数创建的,但是当你调用一个 API 方法时,它通常是隐式创建的,该 API 方法接受一个旨在表示路径的`String`参数。对于后一种情况,爪哇Beans`PropertyEditor`最终决定创建哪种类型的`Resource`。如果路径字符串包含一个众所周知的前缀(对于属性编辑器来说,就是)(例如`classpath:`),那么它将为该前缀创建一个适当的专门的`Resource`。但是,如果它不能识别前缀,它将假设字符串是一个标准的 URL 字符串,并创建一个`UrlResource`。

#### 2.3.2.`ClassPathResource`

这个类表示应该从 Classpath 获得的资源。它使用线程上下文类装入器、给定类装入器或给定类来装载资源。

这个`Resource`实现支持解析为`java.io.File`,如果类路径资源驻留在文件系统中,而不是驻留在 Classpath 资源中且未(通过 Servlet 引擎或任何环境)扩展到文件系统的 Classpath 资源。为了解决这个问题,各种`Resource`实现总是支持解析为`java.net.URL`。

`ClassPathResource`是由 爪哇 代码通过显式使用`ClassPathResource`构造函数创建的,但是当你调用一个 API 方法时,它通常是隐式创建的,该 API 方法接受一个旨在表示路径的`String`参数。对于后一种情况,爪哇Beans`PropertyEditor`识别字符串路径上的特殊前缀`classpath:`,并在这种情况下创建`ClassPathResource`。

#### 2.3.3.`FileSystemResource`

这是`Resource`句柄的`java.io.File`实现。它还支持`java.nio.file.Path`句柄,应用 Spring 的标准基于字符串的路径转换,但通过`java.nio.file.Files`API 执行所有操作。对于纯粹的基于`java.nio.path.Path`的支持,使用`PathResource`代替。`FileSystemResource`支持解析为`File`和`URL`。

#### 2.3.4.`PathResource`

这是用于`java.nio.file.Path`句柄的`Resource`实现,通过`Path`API 执行所有操作和转换。它支持`File`和`URL`的解析,并实现了扩展的`WritableResource`接口。`PathResource`实际上是一种纯粹的基于`java.nio.path.Path`的替代方法,与`FileSystemResource`具有不同的`createRelative`行为。

#### 2.3.5.`ServletContextResource`

这是`Resource`资源的`ServletContext`实现,用于解释相关 Web 应用程序根目录中的相对路径。

它始终支持流访问和 URL 访问,但仅当 Web 应用程序归档被扩展并且资源在文件系统上时才允许`java.io.File`访问。它是否被扩展并在文件系统上或直接从 jar 或类似于数据库的其他地方(这是可以想象的)访问,实际上取决于 Servlet 容器。

#### 2.3.6.`InputStreamResource`

`InputStreamResource`是给定`InputStream`的`Resource`实现。只有在不适用特定的`Resource`实现的情况下,才应该使用它。特别是,在可能的情况下,优选`ByteArrayResource`或任何基于文件的`Resource`实现方式。

与其他`Resource`实现不同,这是对已经打开的资源的描述符。因此,它从`isOpen()`返回`true`。如果你需要将资源描述符保存在某个地方,或者如果你需要多次读取流,请不要使用它。

#### 2.3.7.`ByteArrayResource`

这是给定字节数组的`Resource`实现。它为给定字节数组创建一个`ByteArrayInputStream`。

对于从任何给定的字节数组加载内容,而不必使用一次性`InputStreamResource`,它是有用的。

### 2.4.`ResourceLoader`接口

`ResourceLoader`接口旨在通过可以返回(即加载)`Resource`实例的对象来实现。下面的清单显示了`ResourceLoader`接口定义:

```
public interface ResourceLoader {

    Resource getResource(String location);

    ClassLoader getClassLoader();
}
```

所有应用程序上下文都实现`ResourceLoader`接口。因此,可以使用所有应用程序上下文来获得`Resource`实例。

当你在特定的应用程序上下文中调用`getResource()`,而指定的位置路径没有特定的前缀时,你将返回一个适合该特定应用程序上下文的`Resource`类型。例如,假设对`ClassPathXmlApplicationContext`实例运行了以下代码片段:

爪哇

```
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
```

Kotlin

```
val template = ctx.getResource("some/resource/path/myTemplate.txt")
```

对于`ClassPathXmlApplicationContext`,该代码返回`ClassPathResource`。如果对`FileSystemXmlApplicationContext`实例运行相同的方法,它将返回`FileSystemResource`。对于`WebApplicationContext`,它将返回`ServletContextResource`。它将类似地为每个上下文返回适当的对象。

因此,你可以以适合特定应用程序上下文的方式加载资源。

另一方面,你也可以通过指定特殊的`classpath:`前缀来强制使用`ClassPathResource`,而不考虑应用程序的上下文类型,如下例所示:

爪哇

```
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
```

Kotlin

```
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
```

类似地,你可以通过指定任何标准的`java.net.URL`前缀来强制使用`UrlResource`。以下示例使用`file`和`https`前缀:

爪哇

```
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
```

Kotlin

```
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
```

爪哇

```
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
```

Kotlin

```
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")
```

下表总结了将`String`对象转换为`Resource`对象的策略:

|  Prefix  |            Example             |解释|
|----------|--------------------------------|----------------------------------------------------------------------------------------------------------------------|
|classpath:|`classpath:com/myapp/config.xml`|从 Classpath 加载。|
|  file:   |   `file:///data/config.xml`    |从文件系统加载为`URL`。另请参见[`FileSystemResource`caveats]。|
|  https:  |  `https://myserver/logo.png`   |加载为`URL`。|
|  (none)  |       `/data/config.xml`       |取决于底层`ApplicationContext`。|

### 2.5.`ResourcePatternResolver`接口

`ResourcePatternResolver`接口是`ResourceLoader`接口的扩展,它定义了将位置模式(例如, Ant 样式的路径模式)解析为`Resource`对象的策略。

```
public interface ResourcePatternResolver extends ResourceLoader {

    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String locationPattern) throws IOException;
}
```

如上所述,该接口还为来自类路径的所有匹配资源定义了一个特殊的`classpath*:`资源前缀。请注意,在这种情况下,资源位置应该是一个没有占位符的路径——例如,`classpath*:/config/beans.xml`。 jar 类路径中的文件或不同的目录可以包含具有相同路径和相同名称的多个文件。有关使用`classpath*:`资源前缀的通配符支持的更多详细信息,请参见[应用程序上下文构造函数资源路径中的通配符](#resources-app-ctx-wildcards-in-resource-paths)及其子节。

可以检查传入的`ResourceLoader`(例如,通过[`ResourceLoaderAware`](#resources-ResourceLoaderAware)语义提供的一个)是否也实现了这个扩展接口。

`PathMatchingResourcePatternResolver`是一个独立的实现,可以在`ApplicationContext`之外使用,并且`ResourceArrayPropertyEditor`也用于填充`Resource[]` Bean 属性。`PathMatchingResourcePatternResolver`能够将指定的资源定位路径解析为一个或多个匹配的`Resource`对象。源路径可以是具有到目标`Resource`的一对一映射的简单路径,或者可替换地包含特殊的`classpath*:`前缀和/或内部 Ant 式正则表达式(使用 Spring 的`org.springframework.util.AntPathMatcher`实用工具进行匹配)。后者实际上都是通配符。

|   |在任何标准`ApplicationContext`中,默认的`ResourceLoader`实际上是<br/>的一个实例`PathMatchingResourcePatternResolver`,它实现了`ResourcePatternResolver`接口。对于`ApplicationContext`实例本身也是如此,它也<br/>实现了`ResourcePatternResolver`接口并将其委托给默认的`PathMatchingResourcePatternResolver`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 2.6.`ResourceLoaderAware`接口

`ResourceLoaderAware`接口是一种特殊的回调接口,用于标识期望提供`ResourceLoader`引用的组件。下面的清单显示了`ResourceLoaderAware`接口的定义:

```
public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}
```

当一个类实现`ResourceLoaderAware`并被部署到应用程序上下文中(作为 Spring 管理的 Bean)时,应用程序上下文将其识别为`ResourceLoaderAware`。然后,应用程序上下文调用`setResourceLoader(ResourceLoader)`,将自身作为参数提供(请记住, Spring 中的所有应用程序上下文都实现`ResourceLoader`接口)。

由于`ApplicationContext`是`ResourceLoader`, Bean 还可以实现`ApplicationContextAware`接口,并直接使用提供的应用程序上下文来加载资源。然而,一般来说,如果这就是你所需要的,那么最好使用专门的`ResourceLoader`接口。代码将仅耦合到资源加载接口(可被视为实用程序接口),而不是整个 Spring `ApplicationContext`接口。

在应用程序组件中,还可以依赖`ResourceLoader`的自动布线作为实现`ResourceLoaderAware`接口的替代方案。*传统的*`constructor`和`byType`自动布线模式(如[自动布线合作者](#beans-factory-autowire)中所述)能够分别为构造函数参数或 setter 方法参数提供`ResourceLoader`。要获得更大的灵活性(包括自动连接字段和多个参数方法的能力),请考虑使用基于注释的自动连接功能。在这种情况下,`ResourceLoader`将自动连接到一个字段、构造函数参数或方法参数中,只要所讨论的字段、构造函数或方法带有`ResourceLoader`注释,这些参数就会期望`ResourceLoader`类型。有关更多信息,请参见[使用`@Autowired`]。

|   |要为包含通配符<br/>的资源路径加载一个或多个`Resource`对象,或者使用特殊的`classpath*:`资源前缀,请考虑将[`ResourcePatternResolver`](#resources-ResourcePatternResolver)的实例自动连接到你的<br/>应用程序组件中,而不是`ResourceLoader`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 2.7.作为依赖关系的资源

如果 Bean 本身将通过某种动态过程来确定和提供资源路径,则 Bean 使用`ResourceLoader`或`ResourcePatternResolver`接口来加载资源可能是有意义的。例如,考虑加载某种类型的模板,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么完全消除`ResourceLoader`接口(或`ResourcePatternResolver`接口)的使用是有意义的,让 Bean 公开它需要的`Resource`属性,并期望将它们注入其中。

然后注入这些属性的原因是,所有应用程序上下文都注册并使用一个特殊的 爪哇Beans`PropertyEditor`,它可以将`String`路径转换为`Resource`对象。例如,下面的`MyBean`类具有类型`template`的`Resource`属性。

爪哇

```
package example;

public class MyBean {

    private Resource template;

    public setTemplate(Resource template) {
        this.template = template;
    }

    // ...
}
```

Kotlin

```
class MyBean(var template: Resource)
```

在 XML 配置文件中,`template`属性可以为该资源配置一个简单的字符串,如下例所示:

```
<bean id="myBean" class="example.MyBean">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
```

请注意,资源路径没有前缀。因此,由于应用程序上下文本身将被用作`ResourceLoader`,资源将通过`ClassPathResource`、`FileSystemResource`或`ServletContextResource`加载,这取决于应用程序上下文的确切类型。

如果需要强制使用特定的`Resource`类型,则可以使用前缀。下面的两个示例展示了如何强制执行`ClassPathResource`和`UrlResource`(后者用于访问文件系统中的文件):

```
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
```

```
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
```

如果对`MyBean`类进行重构以用于注释驱动的配置,则`myTemplate.txt`的路径可以存储在一个名为`template.path`的键下——例如,在 Spring `Environment`提供的属性文件中(参见[环境抽象](#beans-environment))。然后可以通过使用属性占位符的`@Value`注释引用模板路径(参见[使用`@Value`](#beans-value-annotations))。 Spring 将检索模板路径的值作为字符串,而一个特殊的`PropertyEditor`将把字符串转换为一个`Resource`对象,以注入到`MyBean`构造函数中。下面的示例演示了如何实现这一点。

爪哇

```
@Component
public class MyBean {

    private final Resource template;

    public MyBean(@Value("${template.path}") Resource template) {
        this.template = template;
    }

    // ...
}
```

Kotlin

```
@Component
class MyBean(@Value("\${template.path}") private val template: Resource)
```

如果我们希望支持在 Classpath 中的多个位置的相同路径下发现的多个模板——例如,在 Classpath 中的多个 JAR 中——我们可以使用特殊的`classpath*:`前缀和通配符将`templates.path`键定义为`classpath*:/config/templates/*.txt`。如果我们按如下方式重新定义`MyBean`类, Spring 将把模板路径模式转换为`Resource`对象的数组,这些对象可以被注入到`MyBean`构造函数中。

爪哇

```
@Component
public class MyBean {

    private final Resource[] templates;

    public MyBean(@Value("${templates.path}") Resource[] templates) {
        this.templates = templates;
    }

    // ...
}
```

Kotlin

```
@Component
class MyBean(@Value("\${templates.path}") private val templates: Resource[])
```

### 2.8.应用程序上下文和资源路径

本节介绍如何使用资源创建应用程序上下文,包括使用 XML 的快捷方式、如何使用通配符以及其他详细信息。

#### 2.8.1.构造应用程序上下文

应用程序上下文构造函数(对于特定的应用程序上下文类型)通常使用字符串或字符串数组作为资源的位置路径,例如构成上下文定义的 XML 文件。

当这样的位置路径不具有前缀时,特定的`Resource`类型从该路径构建并用于加载 Bean 定义所依赖的并且适合于特定的应用程序上下文。例如,考虑以下示例,该示例创建了`ClassPathXmlApplicationContext`:

爪哇

```
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
```

Kotlin

```
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
```

Bean 定义是从 Classpath 加载的,因为使用了`ClassPathResource`。但是,请考虑以下示例,该示例创建了`FileSystemXmlApplicationContext`:

爪哇

```
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/appContext.xml");
```

Kotlin

```
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
```

现在 Bean 定义是从文件系统位置(在本例中,相对于当前工作目录)加载的。

请注意,在位置路径上使用特殊的`classpath`前缀或标准的 URL 前缀覆盖了为加载 Bean 定义而创建的默认类型`Resource`。考虑以下示例:

爪哇

```
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
```

Kotlin

```
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
```

使用`FileSystemXmlApplicationContext`从 Classpath 加载 Bean 定义。然而,它仍然是`FileSystemXmlApplicationContext`。如果随后将其用作`ResourceLoader`,则任何未加前缀的路径仍将被视为文件系统路径。

#####  构造`ClassPathXmlApplicationContext`实例——快捷方式

`ClassPathXmlApplicationContext`公开了许多构造函数,以实现方便的实例化。基本思想是,你可以只提供一个字符串数组,该字符串数组仅包含 XML 文件本身的文件名(不包含前导路径信息),还可以提供`Class`。然后,`ClassPathXmlApplicationContext`从提供的类派生路径信息。

考虑以下目录布局:

```
com/
  example/
    services.xml
    repositories.xml
    MessengerService.class
```

下面的示例显示了如何实例化由名为`services.xml`和`repositories.xml`(位于 Classpath 上)的文件中定义的 bean 组成的`ClassPathXmlApplicationContext`实例:

爪哇

```
ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
```

Kotlin

```
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "repositories.xml"), MessengerService::class.java)
```

有关各种构造函数的详细信息,请参见[`ClassPathXmlApplicationContext`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/support/classpathxmlapplicationcontext.html)爪哇doc。

#### 2.8.2.应用程序上下文构造函数资源路径中的通配符 ###

应用程序上下文构造函数值中的资源路径可以是简单的路径(如前面所示),其中每个路径都具有到目标`Resource`的一对一映射,或者,或者,也可以包含特殊的`classpath*:`前缀或内部 Ant 样式的模式(通过使用 Spring 的`PathMatcher`实用工具进行匹配)。后者实际上都是通配符。

这种机制的一种用途是当你需要执行组件样式的应用程序组装时。所有组件都可以*发布*上下文定义片段到一个众所周知的位置路径,并且,当最终的应用程序上下文是使用带有`classpath*:`前缀的相同路径创建的时,所有组件片段都会被自动拾取。

请注意,此通配符是特定于在应用程序上下文构造函数中使用资源路径的(或者当你直接使用`PathMatcher`实用程序类层次结构时),并在构建时解析。它与`Resource`类型本身无关。你不能使用`classpath*:`前缀来构造一个实际的`Resource`,因为一个资源一次只指向一个资源。

#####  Ant-样式模式

路径位置可以包含 Ant 样式的模式,如下例所示:

```
/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml
```

当路径位置包含 Ant 样式的模式时,解析器遵循一个更复杂的过程来尝试解析通配符。它为到最后一个非通配符段的路径生成`Resource`,并从中获得一个 URL。如果此 URL 不是`jar:`URL 或特定于容器的变体(例如 WebLogic 中的`zip:`,WebSphere 中的`wsjar`,以此类推),则从中获得一个`java.io.File`,并通过遍历文件系统来解析通配符。在 jar URL 的情况下,解析器或者从它获取`java.net.JarURLConnection`,或者手动解析 jar URL,然后遍历 jar 文件的内容以解析通配符。

###### 对可移植性的影响

如果指定的路径已经是`file`URL(因为基本`ResourceLoader`是一个文件系统,所以隐式地或显式地),则可以保证通配符以完全可移植的方式工作。

如果指定的路径是`classpath`位置,则解析器必须通过进行`Classloader.getResource()`调用来获得最后一个非通配符路径段 URL。由于这只是路径的一个节点(而不是末尾的文件),因此它实际上是未定义的(在`ClassLoader`Javadoc 中),在这种情况下返回的正是哪种类型的 URL。在实践中,它总是表示目录(其中 Classpath 资源解析为文件系统位置)或某种类型的 jar URL(其中 Classpath 资源解析为 jar 位置)的`java.io.File`。不过,这种操作仍然存在移植性方面的问题。

如果为最后一个非通配符段获得了 jar URL,则解析器必须能够从其获得`java.net.JarURLConnection`或手动解析 jar URL,以便能够遍历 jar 中的内容并解析通配符。这在大多数环境中确实有效,但在其他环境中失败,我们强烈建议在依赖它之前,在你的特定环境中对来自 JAR 的资源的通配符解析进行彻底测试。

#####  `classpath*:`前缀

在构建基于 XML 的应用程序上下文时,位置字符串可以使用特殊的`classpath*:`前缀,如下例所示:

Java

```
ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
```

Kotlin

```
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
```

这个特殊的前缀指定必须获得所有与给定名称匹配的 Classpath 资源(在内部,这基本上是通过调用`ClassLoader.getResources(…​)`实现的),然后合并以形成最终的应用程序上下文定义。

|   |通配符 Classpath 依赖于底层的`getResources()`方法`ClassLoader`。由于现在的大多数应用程序服务器都提供自己的`ClassLoader`实现,因此行为可能会有所不同,特别是在处理 jar 文件时。一个<br/>检查`classpath*`是否有效的简单测试是使用`ClassLoader`从<br/>在 Classpath 上的 jar 内加载一个文件:`getClass().getClassLoader().getResources("<someFileInsideTheJar>")`。使用具有相同名称但驻留在两个不同位置的<br/>文件尝试此测试——例如,在 Classpath 上具有相同名称和相同路径但位于不同 JAR 中的<br/>文件。如果返回了<br/>不合适的结果,请检查应用程序服务器文档中的设置<br/>,这些设置可能会影响`ClassLoader`行为。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你还可以在位置路径的其余部分(例如,`classpath*:META-INF/*-beans.xml`)中将`classpath*:`前缀与`PathMatcher`模式相结合。在这种情况下,解析策略相当简单:在最后一个非通配符路径段上使用`ClassLoader.getResources()`调用,以获取类装入器层次结构中的所有匹配资源,然后关闭每个资源,前面描述的`PathMatcher`分辨率策略也用于通配符的子路径。

#####  与通配符有关的其他注释

注意,`classpath*:`,当与 Ant 样式的模式相结合时,在模式开始之前仅与至少一个根目录可靠地工作,除非实际的目标文件驻留在文件系统中。这意味着,像`classpath*:*.xml`这样的模式可能不会从 jar 文件的根目录检索文件,而只会从扩展目录的根目录检索文件。

Spring 检索 Classpath 条目的能力源于 JDK 的`ClassLoader.getResources()`方法,该方法仅返回空字符串的文件系统位置(指示要搜索的潜在根)。 Spring 还在 jar 文件中评估`URLClassLoader`运行时配置和`java.class.path`清单,但这不能保证导致可移植行为。

|   |Classpath 包的扫描需要在 Classpath 中存在相应的目录<br/>条目。当使用 Ant 构建 JAR 时,不要激活 jar 任务的`files-only`开关。另外,在某些环境中, Classpath 目录可能不会基于安全性<br/>策略而被公开——例如,在 JDK1.7.0\_45<br/>上的独立应用程序以及更高的环境中(这要求在你的清单中设置“可信库”)。参见[https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources](https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources))。<br/><br/>在 JDK9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常可以按预期工作。<br/>将资源放入专用目录在这里也是非常值得推荐的,<br/>在搜索 jar 文件根级别时避免了前面提到的可移植性问题。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Ant-样式模式具有资源,如果要搜索的根包在多个 Classpath 位置可用,则不能保证找到匹配的资源。考虑以下资源位置示例:

```
com/mycompany/package1/service-context.xml
```

现在考虑一个 Ant 样式的路径,可能有人会使用它来尝试查找该文件:

```
classpath:com/mycompany/**/service-context.xml
```

这样的资源可以仅存在于 Classpath 中的一个位置,但是当使用诸如前面的示例的路径来尝试解析它时,解析器工作于由`getResource("com/mycompany");`返回的(第一个)URL。如果此基本包节点存在于多个`ClassLoader`位置中,则所需的资源可能不存在于第一个位置中。因此,在这种情况下,你应该更喜欢使用具有相同 Ant 样式模式的`classpath*:`,该模式搜索包含`com.mycompany`基本包的所有 Classpath 位置:`classpath*:com/mycompany/**/service-context.xml`。

#### 2.8.3.`FileSystemResource`注意事项

不附加到`FileSystemResource`的`FileSystemApplicationContext`(即当`FileSystemApplicationContext`不是实际的`ResourceLoader`时)按预期处理绝对路径和相对路径。相对路径是相对于当前工作目录的,而绝对路径是相对于文件系统的根的。

但是,由于向后兼容性(历史原因)的原因,当`FileSystemApplicationContext`是`ResourceLoader`时,这种情况会发生变化。`FileSystemApplicationContext`强制所有附加的`FileSystemResource`实例将所有位置路径视为相对的,无论它们是否以前导斜杠开始。在实践中,这意味着以下示例是等效的:

Java

```
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/context.xml");
```

Kotlin

```
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
```

Java

```
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("/conf/context.xml");
```

Kotlin

```
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
```

下面的例子也是等价的(尽管它们不同是有意义的,因为一种情况是相对的,另一种情况是绝对的):

Java

```
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
```

Kotlin

```
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
```

Java

```
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
```

Kotlin

```
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")
```

在实践中,如果需要真正的绝对文件系统路径,则应避免使用带有`FileSystemResource`或`FileSystemXmlApplicationContext`的绝对路径,并使用`UrlResource`URL 前缀强制使用`file:`。下面的例子说明了如何做到这一点:

Java

```
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
```

Kotlin

```
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
```

Java

```
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");
```

Kotlin

```
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")
```

## 3. 验证、数据绑定和类型转换 #

考虑将验证作为业务逻辑有其优点和缺点, Spring 提供了一种验证(和数据绑定)设计,该设计不排除其中的任何一种。具体地说,验证不应该绑定到 Web 层,并且应该易于本地化,并且应该可以插入任何可用的验证器。考虑到这些问题, Spring 提供了`Validator`契约,该契约在应用程序的每一层中都是基本的且非常可用的。

数据绑定对于让用户输入与应用程序的域模型(或用于处理用户输入的任何对象)动态绑定非常有用。 Spring 提供了恰当地命名为`DataBinder`的方法来做到这一点。`Validator`和`DataBinder`组成了`validation`包,该包主要用于但不限于 Web 层。

`BeanWrapper`是 Spring 框架中的一个基本概念,并在许多地方使用。但是,你可能不需要直接使用`BeanWrapper`。但是,由于这是参考文档,我们认为可能需要进行一些解释。我们将在本章中解释`BeanWrapper`,因为如果你打算使用它,那么在尝试将数据绑定到对象时,你很可能会使用它。

Spring 的`DataBinder`和较低级别的`BeanWrapper`都使用`PropertyEditorSupport`实现来解析和格式化属性值。`PropertyEditor`和`PropertyEditorSupport`类型是 JavaBeans 规范的一部分,也在本章中进行了说明。 Spring 3 引入了一个`core.convert`包,该包提供了一般的类型转换功能,以及用于格式化 UI 字段值的更高级别的“格式”包。你可以使用这些包作为`PropertyEditorSupport`实现的更简单的替代方案。本章还对这些问题进行了讨论。

Spring 通过设置基础设施和 Spring 自己的`Validator`合同的适配器支持 Java Bean 验证。应用程序可以在全局范围内一次启用 Bean 验证,如[Java Bean Validation](#validation-beanvalidation)中所述,并专门用于所有验证需求。在 Web 层中,应用程序可以进一步注册控制器-Local Spring 实例的每,如[配置](#validation-binder)中所述的那样,这对于插入自定义验证逻辑是有用的。

### 3.1.通过使用 Spring 的验证器接口进行验证

Spring 具有`Validator`接口,你可以使用该接口来验证对象。`Validator`接口通过使用`Errors`对象来工作,这样在验证时,验证器可以向`Errors`对象报告验证失败。

考虑以下一个小数据对象的示例:

Java

```
public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}
```

Kotlin

```
class Person(val name: String, val age: Int)
```

下一个示例通过实现`org.springframework.validation.Validator`接口的以下两种方法,为`Person`类提供了验证行为:

* `supports(Class)`:这个`Validator`可以验证所提供的`Class`的实例吗?

* `validate(Object, org.springframework.validation.Errors)`:验证给定的对象,在验证错误的情况下,用给定的`Errors`对象注册那些对象。

实现`Validator`相当简单,尤其是当你知道 Spring 框架还提供了`ValidationUtils`Helper 类时。下面的示例为`Person`实例实现`Validator`:

Java

```
public class PersonValidator implements Validator {

    /**
     * This Validator validates only Person instances
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }

    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too.darn.old");
        }
    }
}
```

Kotlin

```
class PersonValidator : Validator {

    /**
     * This Validator validates only Person instances
     */
    override fun supports(clazz: Class<*>): Boolean {
        return Person::class.java == clazz
    }

    override fun validate(obj: Any, e: Errors) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty")
        val p = obj as Person
        if (p.age < 0) {
            e.rejectValue("age", "negativevalue")
        } else if (p.age > 110) {
            e.rejectValue("age", "too.darn.old")
        }
    }
}
```

在`static`类上的`rejectIfEmpty(..)`方法用于拒绝`name`属性,如果它是`null`或空字符串。看看[`ValidationUtils`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/validation/validationutils.html)Javadoc,看看除了前面展示的示例之外,它还提供了什么功能。

虽然可以实现单个`Validator`类来验证富对象中的每个嵌套对象,但最好是将每个嵌套对象类的验证逻辑封装在其自己的`Validator`实现中。“rich”对象的一个简单示例是`Customer`,它由两个`String`属性(第一个和第二个名称)和一个复杂的`Address`对象组成。`Address`对象可以独立于`Customer`对象使用,因此已经实现了一个不同的`AddressValidator`对象。如果你希望你的`CustomerValidator`重用`AddressValidator`类中包含的逻辑而不使用复制和粘贴,则可以在你的`CustomerValidator`中使用依赖注入或实例化`AddressValidator`,如下例所示:

Java

```
public class CustomerValidator implements Validator {

    private final Validator addressValidator;

    public CustomerValidator(Validator addressValidator) {
        if (addressValidator == null) {
            throw new IllegalArgumentException("The supplied [Validator] is " +
                "required and must not be null.");
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException("The supplied [Validator] must " +
                "support the validation of [Address] instances.");
        }
        this.addressValidator = addressValidator;
    }

    /**
     * This Validator validates Customer instances, and any subclasses of Customer too
     */
    public boolean supports(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}
```

Kotlin

```
class CustomerValidator(private val addressValidator: Validator) : Validator {

    init {
        if (addressValidator == null) {
            throw IllegalArgumentException("The supplied [Validator] is required and must not be null.")
        }
        if (!addressValidator.supports(Address::class.java)) {
            throw IllegalArgumentException("The supplied [Validator] must support the validation of [Address] instances.")
        }
    }

    /*
    * This Validator validates Customer instances, and any subclasses of Customer too
    */
    override fun supports(clazz: Class<>): Boolean {
        return Customer::class.java.isAssignableFrom(clazz)
    }

    override fun validate(target: Any, errors: Errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required")
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required")
        val customer = target as Customer
        try {
            errors.pushNestedPath("address")
            ValidationUtils.invokeValidator(this.addressValidator, customer.address, errors)
        } finally {
            errors.popNestedPath()
        }
    }
}
```

将验证错误报告给传递给验证器的`Errors`对象。在 Spring Web MVC 的情况下,你可以使用`<spring:bind/>`标记来检查错误消息,但是你也可以自己检查`Errors`对象。有关其提供的方法的更多信息,请参见[javadoc](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/validation/Errors.html)。

### 3.2.将代码解析为错误消息

我们涵盖了数据库和验证。本节介绍输出与验证错误对应的消息。在[前一节](#validator)中显示的示例中,我们拒绝了`name`和`age`字段。如果我们希望通过使用`MessageSource`输出错误消息,那么我们可以使用在拒绝字段时提供的错误代码(本例中是“name”和“age”)来输出错误消息。当你从`Errors`接口调用(通过使用`ValidationUtils`类直接或间接调用)`rejectValue`或其他`reject`方法之一时,底层实现不仅注册了你传入的代码,而且还注册了许多额外的错误代码。`MessageCodesResolver`决定了哪个错误编码`Errors`接口寄存器。默认情况下,使用`DefaultMessageCodesResolver`,它(例如)不仅用给出的代码注册消息,还注册包含传递给拒绝方法的字段名称的消息。因此,如果通过使用`rejectValue("age", "too.darn.old")`拒绝字段,除了`too.darn.old`代码外, Spring 还注册了`too.darn.old.age`和`too.darn.old.age.int`(第一个包括字段名称,第二个包括字段的类型)。这样做是为了方便开发人员在定位错误消息时提供帮助。

关于`MessageCodesResolver`和默认策略的更多信息,可以在[`MessageCodesResolver`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/validation/messagecodesresolver.html)和[<`DefaultMessageCodesResolver`](https:/DOCS. Spring.io/ Spring-framework/[ Spring-framework/5.3.16/javadoc-api/javagecodeframework/deframework.html/

### 3.3. Bean 操纵和`BeanWrapper`

`org.springframework.beans`包遵循 JavaBeans 标准。JavaBean 是一个具有缺省无参数构造函数的类,它遵循一个命名约定,其中(例如)一个名为`bingoMadness`的属性将具有一个 setter 方法`setBingoMadness(..)`和一个 getter 方法`getBingoMadness()`。有关 JavaBeans 和规范的更多信息,请参见[javabeans](https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html)。

Bean 包中一个非常重要的类是`BeanWrapper`接口及其相应的实现(`BeanWrapperImpl`)。引用自 Javadoc,`BeanWrapper`提供了设置和获取属性值(单独或批量)、获取属性描述符和查询属性以确定它们是可读的还是可写的功能。此外,`BeanWrapper`还提供了对嵌套属性的支持,使子属性的属性设置达到无限的深度。`BeanWrapper`还支持添加标准 JavaBeans`PropertyChangeListeners`和`VetoableChangeListeners`的能力,而不需要在目标类中支持代码。最后但并非最不重要的是,`BeanWrapper`提供了对设置索引属性的支持。`BeanWrapper`通常不被应用程序代码直接使用,而是由`DataBinder`和`BeanFactory`使用。

`BeanWrapper`的工作方式在一定程度上由其名称来表示:它封装了一个 Bean 以在该 Bean 上执行操作,例如设置和检索属性。

#### 3.3.1.设置和获取基本和嵌套属性

设置和获取属性是通过`setPropertyValue`和`getPropertyValue`重载方法变量`BeanWrapper`完成的。有关详细信息,请访问他们的 Javadoc。下表显示了这些约定的一些示例:

|      Expression      |解释|
|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|        `name`        |指示与`getName()`或`isName()`和`setName(..)`方法对应的属性`name`。|
|    `account.name`    |指示属性`account`的嵌套属性`name`,它对应于<br/>(例如)`getAccount().setName()`或`getAccount().getName()`方法。|
|     `account[2]`     |指示索引属性`account`的*第三次*元素。索引属性<br/>可以是类型`array`,`list`,或其他自然有序的集合。|
|`account[COMPANYNAME]`|指示由`account``Map`属性的`COMPANYNAME`键索引的映射项的值。|

(如果你不打算直接使用`BeanWrapper`,那么下一节对你来说并不是至关重要的。如果只使用`DataBinder`和`BeanFactory`及其默认实现,则应跳过[关于`PropertyEditors`的部分](#beans-beans-conversion)。

以下两个示例类使用`BeanWrapper`来获取和设置属性:

Java

```
public class Company {

    private String name;
    private Employee managingDirector;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Employee getManagingDirector() {
        return this.managingDirector;
    }

    public void setManagingDirector(Employee managingDirector) {
        this.managingDirector = managingDirector;
    }
}
```

Kotlin

```
class Company {
    var name: String? = null
    var managingDirector: Employee? = null
}
```

Java

```
public class Employee {

    private String name;

    private float salary;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }
}
```

Kotlin

```
class Employee {
    var name: String? = null
    var salary: Float? = null
}
```

以下代码片段展示了如何检索和操作实例化`Company`s 和`Employee`s 的一些属性的示例:

Java

```
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
```

Kotlin

```
val company = BeanWrapperImpl(Company())
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.")
// ... can also be done like this:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)

// ok, let's create the director and tie it to the company:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)

// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?
```

#### 3.3.2.内置`PropertyEditor`实现

Spring 使用`PropertyEditor`的概念来实现`Object`和`String`之间的转换。以不同于对象本身的方式表示属性可能很方便。例如,`Date`可以以人类可读的方式表示(如`String`:`'2007-14-09'`),而我们仍然可以将人类可读形式转换回原始日期(或者,更好的是,将在人类可读形式中输入的任何日期转换回`Date`对象)。这种行为可以通过注册`java.beans.PropertyEditor`类型的自定义编辑器来实现。在`BeanWrapper`上注册自定义编辑器,或者在特定的 IoC 容器中注册自定义编辑器(如前一章中提到的),将为它提供如何将属性转换为所需类型的知识。有关`PropertyEditor`的更多信息,请参见[来自 Oracle 的`java.beans`包的 Javadoc](https://DOCS.oracle.com/javase/8/DOCS/api/java/beans/package-summary.html)。

Spring 中使用属性编辑的几个示例:

* 在 bean 上设置属性是通过使用`PropertyEditor`实现来完成的。当使用`String`作为在 XML 文件中声明的某个 Bean 属性的值时, Spring(如果相应属性的 setter 具有`Class`参数)使用`ClassEditor`来尝试将参数解析为`Class`对象。

* 在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种`PropertyEditor`实现完成的,你可以在`CommandController`的所有子类中手动绑定这些实现。

Spring 具有许多内置的`PropertyEditor`实现,以使生活变得容易。它们都位于`org.springframework.beans.propertyeditors`包中。默认情况下,大多数(但不是全部,如下表所示)是由`BeanWrapperImpl`注册的。在属性编辑器以某种方式可配置的情况下,你仍然可以注册自己的变体以覆盖默认的变体。下表描述了 Spring 提供的各种`PropertyEditor`实现方式:

|          Class          |解释|
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`ByteArrayPropertyEditor`|字节数组的编辑器。将字符串转换为其对应的字节<br/>表示形式。默认情况下由`BeanWrapperImpl`注册。|
|      `ClassEditor`      |解析将类表示为实际类的字符串,反之亦然。当未找到<br/>类时,将抛出`IllegalArgumentException`。默认情况下,由`BeanWrapperImpl`注册。|
|  `CustomBooleanEditor`  |`Boolean`属性的可自定义属性编辑器。默认情况下,由`BeanWrapperImpl`注册,但可以通过将其自定义实例注册为<br/>自定义编辑器来重写。|
|`CustomCollectionEditor` |集合的属性编辑器,将任何源`Collection`转换为给定的目标`Collection`类型。|
|   `CustomDateEditor`    |用于`java.util.Date`的可自定义属性编辑器,支持自定义`DateFormat`。不<br/>默认注册。必须根据需要以适当的格式进行用户注册。|
|  `CustomNumberEditor`   |任何`Number`子类的可定制属性编辑器,例如`Integer`,`Long`,`Float`,或`Double`。默认情况下,由`BeanWrapperImpl`注册,但可以由<br/>将其自定义实例注册为自定义编辑器来覆盖。|
|      `FileEditor`       |将字符串解析为`java.io.File`对象。默认情况下,由`BeanWrapperImpl`注册。|
|   `InputStreamEditor`   |一种单向属性编辑器,它可以获取一个字符串并产生(通过<br/>中间值`ResourceEditor`和`Resource`)一个`InputStream`,这样`InputStream`属性就可以直接设置为字符串。请注意,默认用法不会为你关闭<br/>的`InputStream`。默认情况下,由`BeanWrapperImpl`注册。|
|     `LocaleEditor`      |可以将字符串解析为`Locale`对象,反之亦然(字符串格式为`[language]_[country]_[variant]`,与`toString()`的`Locale`方法相同)。也接受空格作为分隔符,作为下划线的替代。<br/>默认情况下,由`BeanWrapperImpl`注册。|
|     `PatternEditor`     |可以将字符串解析为`java.util.regex.Pattern`对象,反之亦然。|
|   `PropertiesEditor`    |可以将字符串(格式为`java.util.Properties`类的 Javadoc 中定义的格式)转换为`Properties`对象。默认情况下,由`BeanWrapperImpl`注册<br/>。|
|  `StringTrimmerEditor`  |编辑字符串的属性编辑器。可选地允许将空字符串<br/>转换为`null`值。默认情况下未注册——必须是用户注册的。|
|       `URLEditor`       |可以将 URL 的字符串表示解析为实际的`URL`对象。<br/>默认情况下,由`BeanWrapperImpl`注册。|

Spring 使用`java.beans.PropertyEditorManager`设置可能需要的属性编辑器的搜索路径。搜索路径还包括`sun.bean.editors`,其中包括`PropertyEditor`类型的实现,例如`Font`、`Color`,以及大多数原始类型。还请注意,标准 JavaBeans 基础设施会自动发现`PropertyEditor`类(无需显式地注册它们),如果它们与它们处理的类在同一个包中,并且与该类具有相同的名称,并附加`Editor`。例如,可以有以下的类和包结构,这足以让`SomethingEditor`类被识别并用作`PropertyEditor`的`Something`类型属性。

```
com
  chank
    pop
      Something
      SomethingEditor // the PropertyEditor for the Something class
```

注意,这里也可以使用标准的`BeanInfo`JavaBeans 机制(在一定程度上描述了[here](https://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html))。下面的示例使用`BeanInfo`机制显式地用关联类的属性注册一个或多个`PropertyEditor`实例:

```
com
  chank
    pop
      Something
      SomethingBeanInfo // the BeanInfo for the Something class
```

下面引用的`SomethingBeanInfo`类的 Java 源代码将`CustomNumberEditor`与`age`类的`Something`属性关联起来:

Java

```
public class SomethingBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
                @Override
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                }
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}
```

Kotlin

```
class SomethingBeanInfo : SimpleBeanInfo() {

    override fun getPropertyDescriptors(): Array<PropertyDescriptor> {
        try {
            val numberPE = CustomNumberEditor(Int::class.java, true)
            val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) {
                override fun createPropertyEditor(bean: Any): PropertyEditor {
                    return numberPE
                }
            }
            return arrayOf(ageDescriptor)
        } catch (ex: IntrospectionException) {
            throw Error(ex.toString())
        }

    }
}
```

#####  注册额外的自定义`PropertyEditor`实现 #####

当将 Bean 属性设置为字符串值时, Spring IOC 容器最终使用标准 JavaBeans`PropertyEditor`实现将这些字符串转换为属性的复杂类型。 Spring 预先寄存器一些自定义的`PropertyEditor`实现(例如,将表示为字符串的类名转换为`Class`对象)。此外,Java 的标准 JavaBeans`PropertyEditor`查找机制允许对一个类的`PropertyEditor`进行适当的命名,并将其放置在与它所支持的类相同的包中,以便可以自动找到它。

如果需要注册其他自定义`PropertyEditors`,可以使用几种机制。最手动的方法(通常不方便或不推荐)是使用`registerCustomEditor()`接口的`ConfigurableBeanFactory`方法,假设你有`BeanFactory`引用。另一种(稍微更方便的)机制是使用一种特殊的 Bean 工厂后处理器,称为`CustomEditorConfigurer`。虽然可以使用带有`BeanFactory`实现的 Bean 工厂后处理器,但`CustomEditorConfigurer`具有嵌套的属性设置,因此我们强烈建议你将其用于`ApplicationContext`, Bean 可以以类似的方式部署它,并且可以自动检测和应用它。

请注意,所有 Bean 工厂和应用程序上下文通过使用`BeanWrapper`来处理属性转换,自动使用了许多内置的属性编辑器。在[上一节](#beans-beans-conversion)中列出了`BeanWrapper`寄存器的标准属性编辑器。此外,`ApplicationContext`s 还覆盖或添加额外的编辑器,以便以适合特定应用程序上下文类型的方式处理资源查找。

标准 JavaBeans`PropertyEditor`实例用于将以字符串表示的属性值转换为属性的实际复杂类型。你可以使用 Bean 工厂后处理器`CustomEditorConfigurer`来方便地向`ApplicationContext`实例添加对额外`PropertyEditor`实例的支持。

考虑以下示例,该示例定义了一个名为`ExoticType`的用户类和另一个名为`DependsOnExoticType`的类,该类需要将`ExoticType`设置为一个属性:

Java

```
package example;

public class ExoticType {

    private String name;

    public ExoticType(String name) {
        this.name = name;
    }
}

public class DependsOnExoticType {

    private ExoticType type;

    public void setType(ExoticType type) {
        this.type = type;
    }
}
```

Kotlin

```
package example

class ExoticType(val name: String)

class DependsOnExoticType {

    var type: ExoticType? = null
}
```

在正确设置之后,我们希望能够将 type 属性分配为字符串,而`PropertyEditor`将其转换为实际的`ExoticType`实例。 Bean 以下定义显示了如何设置这种关系:

```
<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>
```

`PropertyEditor`实现可能看起来类似于以下内容:

Java

```
// converts string representation to ExoticType object
package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}
```

Kotlin

```
// converts string representation to ExoticType object
package example

import java.beans.PropertyEditorSupport

class ExoticTypeEditor : PropertyEditorSupport() {

    override fun setAsText(text: String) {
        value = ExoticType(text.toUpperCase())
    }
}
```

最后,下面的示例展示了如何使用`CustomEditorConfigurer`将新的`PropertyEditor`注册为`ApplicationContext`,然后可以根据需要使用它:

```
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>
```

###### 使用`PropertyEditorRegistrar`

用 Spring 容器注册属性编辑器的另一种机制是创建和使用`PropertyEditorRegistrar`。当你需要在几种不同的情况下使用同一组属性编辑器时,此接口特别有用。你可以编写相应的注册表,并在每种情况下重用它。`PropertyEditorRegistrar`实例与一个名为`PropertyEditorRegistry`的接口一起工作,该接口由 Spring `BeanWrapper`(和`DataBinder`)实现。`PropertyEditorRegistrar`实例在与`CustomEditorConfigurer`(描述[here](#beans-beans-conversion-customeditor-registration))结合使用时特别方便,它公开了一个名为`setPropertyEditorRegistrars(..)`的属性。`PropertyEditorRegistrar`以这种方式添加到`CustomEditorConfigurer`的实例可以很容易地与`DataBinder`和 Spring MVC 控制器共享。此外,它避免了在自定义编辑器上进行同步的需要:一个`PropertyEditorRegistrar`预计将为每次 Bean 创建尝试创建新的`PropertyEditor`实例。

下面的示例展示了如何创建你自己的`PropertyEditorRegistrar`实现:

Java

```
package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        // it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

        // you could register as many custom property editors as are required here...
    }
}
```

Kotlin

```
package com.foo.editors.spring

import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry

class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {

    override fun registerCustomEditors(registry: PropertyEditorRegistry) {

        // it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor())

        // you could register as many custom property editors as are required here...
    }
}
```

另请参见`org.springframework.beans.support.ResourceEditorRegistrar`中的示例`PropertyEditorRegistrar`实现。请注意,在`registerCustomEditors(..)`方法的实现中,它如何为每个属性编辑器创建新的实例。

下一个示例展示了如何配置`CustomEditorConfigurer`,并将我们的`CustomPropertyEditorRegistrar`实例注入其中:

```
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <ref bean="customPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>

<bean id="customPropertyEditorRegistrar"
    class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
```

最后(对于那些使用[Spring’s MVC web framework](web.html#mvc)的人来说,有点偏离本章的重点),使用`PropertyEditorRegistrars`结合数据绑定`Controllers`(例如`SimpleFormController`)可以非常方便。下面的示例在`initBinder(..)`方法的实现中使用`PropertyEditorRegistrar`:

Java

```
public final class RegisterUserController extends SimpleFormController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    protected void initBinder(HttpServletRequest request,
            ServletRequestDataBinder binder) throws Exception {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods to do with registering a User
}
```

Kotlin

```
class RegisterUserController(
    private val customPropertyEditorRegistrar: PropertyEditorRegistrar) : SimpleFormController() {

    protected fun initBinder(request: HttpServletRequest,
                            binder: ServletRequestDataBinder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder)
    }

    // other methods to do with registering a User
}
```

这种`PropertyEditor`注册的样式可以导致简洁的代码(`initBinder(..)`的实现只有一行长),并让普通的`PropertyEditor`注册代码封装在一个类中,然后根据需要在尽可能多的`Controllers`之间共享。

### 3.4. Spring 类型转换

Spring 3 介绍了一种`core.convert`包,其提供了一种通用的类型转换系统。系统定义了实现类型转换逻辑的 SPI 和在运行时执行类型转换的 API。在 Spring 容器中,可以使用该系统作为`PropertyEditor`实现的替代方案,以将外部化的 Bean 属性值字符串转换为所需的属性类型。你还可以在应用程序中需要类型转换的任何地方使用公共 API。

#### 3.4.1.转换器 SPI

实现类型转换逻辑的 SPI 是简单且强类型的,如下接口定义所示:

```
package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);
}
```

要创建自己的转换器,请实现`Converter`接口,并将`S`参数化为要转换的类型,将`T`参数化为要转换的类型。如果需要将`S`的集合或数组转换为`T`的阵列或集合,那么也可以透明地应用这样的转换器,前提是已经注册了一个委托数组或集合转换器(默认情况下`DefaultConversionService`是这样做的)。

对于每个对`convert(S)`的调用,源参数保证不为空。如果转换失败,你的`Converter`可能抛出任何未经检查的异常。具体地说,它应该抛出`IllegalArgumentException`来报告无效的源值。注意确保你的`Converter`实现是线程安全的。

为了方便起见,在`core.convert.support`包中提供了几种转换器实现方式。这些包括从字符串到数字的转换器和其他常见类型的转换器。下面的清单显示了`StringToInteger`类,这是一个典型的`Converter`实现:

```
package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}
```

#### 3.4.2.使用`ConverterFactory`

当需要对整个类层次结构集中转换逻辑时(例如,从`String`转换为`Enum`对象时),可以实现`ConverterFactory`,如下例所示:

```
package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
```

参数化 S 是你要转换的类型,R 是定义你可以转换为的类的的基本类型。然后实现`getConverter(Class<T>)`,其中 t 是 r 的一个子类。

以`StringToEnumConverterFactory`为例:

```
package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnumConverter(targetType);
    }

    private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {

        private Class<T> enumType;

        public StringToEnumConverter(Class<T> enumType) {
            this.enumType = enumType;
        }

        public T convert(String source) {
            return (T) Enum.valueOf(this.enumType, source.trim());
        }
    }
}
```

#### 3.4.3.使用`GenericConverter`

当你需要复杂的`Converter`实现时,请考虑使用`GenericConverter`接口。使用比`Converter`更灵活但不那么强类型的签名,`GenericConverter`支持在多个源类型和目标类型之间进行转换。此外,`GenericConverter`提供了在实现转换逻辑时可以使用的源和目标字段上下文。这样的上下文允许类型转换由字段注释或在字段签名上声明的通用信息驱动。下面的清单显示了`GenericConverter`的接口定义:

```
package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
```

要实现`GenericConverter`,请让`getConvertibleTypes()`返回受支持的源目标类型对。然后实现`convert(Object, TypeDescriptor, TypeDescriptor)`以包含你的转换逻辑。源`TypeDescriptor`提供对保存被转换的值的源字段的访问。目标`TypeDescriptor`提供对要设置转换值的目标字段的访问。

`GenericConverter`的一个很好的例子是在 Java 数组和集合之间转换的转换器。这样的`ArrayToCollectionConverter`内省声明目标集合类型的字段,以解析集合的元素类型。这允许在目标字段上设置集合之前,将源数组中的每个元素转换为集合元素类型。

|   |因为`GenericConverter`是一个更复杂的 SPI 接口,所以只有在需要时才应该使用<br/>。赞成`Converter`或`ConverterFactory`对于基本类型<br/>转换需要。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  使用`ConditionalGenericConverter`

有时,只有当特定条件为真时,才希望运行`Converter`。例如,只有在目标字段上存在特定的注释时,你才可能希望运行`Converter`,或者,只有在目标类上定义了特定的方法(例如`static valueOf`方法)时,才希望运行`Converter`。`ConditionalGenericConverter`是`GenericConverter`和`ConditionalConverter`接口的联合,允许你定义这样的自定义匹配条件:

```
public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
```

`ConditionalGenericConverter`的一个很好的例子是`IdToEntityConverter`,它在持久实体标识符和实体引用之间转换。这样的`IdToEntityConverter`可能只有在目标实体类型声明了静态查找方法(例如,`findAccount(Long)`)时才匹配。你可以在`matches(TypeDescriptor, TypeDescriptor)`的实现中执行这样的查找方法检查。

#### 3.4.4.`ConversionService`api

`ConversionService`定义了用于在运行时执行类型转换逻辑的统一 API。转换器通常在以下 facade 接口后面运行:

```
package org.springframework.core.convert;

public interface ConversionService {

    boolean canConvert(Class<?> sourceType, Class<?> targetType);

    <T> T convert(Object source, Class<T> targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
```

大多数`ConversionService`实现还实现了`ConverterRegistry`,它提供了用于注册转换器的 SPI。在内部,`ConversionService`实现委托给其注册的转换器来执行类型转换逻辑。

在`core.convert.support`包中提供了一个健壮的`ConversionService`实现。`GenericConversionService`是适用于大多数环境的通用实现。`ConversionServiceFactory`为创建常见的`ConversionService`配置提供了一个方便的工厂。

#### 3.4.5.配置`ConversionService`

`ConversionService`是一种无状态对象,设计用于在应用程序启动时实例化,然后在多个线程之间共享。在 Spring 应用程序中,通常为每个 Spring 容器(或`ApplicationContext`)配置一个`ConversionService`实例。 Spring 获取`ConversionService`并在框架需要执行类型转换时使用它。你还可以将这个`ConversionService`注入到你的任何 bean 中,并直接调用它。

|   |如果没有`ConversionService`被注册到 Spring,则使用原来的基于`PropertyEditor`的<br/>系统。|
|---|------------------------------------------------------------------------------------------------------------|

要用 Spring 注册一个默认的`ConversionService`,请添加以下 Bean 定义,并使用`id`的`conversionService`:

```
<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean"/>
```

默认的`ConversionService`可以在字符串、数字、枚举、集合、映射和其他常见类型之间进行转换。要用你自己的定制转换器补充或覆盖默认转换器,请设置`converters`属性。属性值可以实现`Converter`、`ConverterFactory`或`GenericConverter`接口中的任意一个。

```
<bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>
```

在 Spring MVC 应用程序中使用`ConversionService`也是常见的。参见 Spring MVC 章节中的[转换和格式化](web.html#mvc-config-conversion)。

在某些情况下,你可能希望在转换过程中应用格式设置。有关使用`FormatterRegistry`SPI 的详细信息,请参见[the`FormatterRegistry`SPI]。

#### 3.4.6.以编程方式使用`ConversionService`

要以编程方式处理`ConversionService`实例,你可以像处理任何其他实例一样,对它注入一个引用 Bean。下面的示例展示了如何做到这一点:

Java

```
@Service
public class MyService {

    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doIt() {
        this.conversionService.convert(...)
    }
}
```

Kotlin

```
@Service
class MyService(private val conversionService: ConversionService) {

    fun doIt() {
        conversionService.convert(...)
    }
}
```

对于大多数用例,你可以使用`convert`方法来指定`targetType`,但是它不适用于更复杂的类型,例如参数化元素的集合。例如,如果要将`List`的`Integer`转换为`List`的`List`的`String`,则需要提供源类型和目标类型的正式定义。

幸运的是,`TypeDescriptor`提供了各种选项,以使这样做很简单,如下例所示:

Java

```
DefaultConversionService cs = new DefaultConversionService();

List<Integer> input = ...
cs.convert(input,
    TypeDescriptor.forObject(input), // List<Integer> type descriptor
    TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
```

Kotlin

```
val cs = DefaultConversionService()

val input: List<Integer> = ...
cs.convert(input,
        TypeDescriptor.forObject(input), // List<Integer> type descriptor
        TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))
```

请注意,`DefaultConversionService`会自动注册适合大多数环境的转换器。这包括集合转换器、标量转换器和基本`Object`-to-`String`转换器。通过在`DefaultConversionService`类上使用静态`addDefaultConverters`方法,可以用任何`ConverterRegistry`注册相同的转换器。

值类型的转换器可重用于数组和集合,因此不需要创建特定的转换器来将`Collection`的`S`转换为`Collection`的`T`的`T`,假设标准的集合处理是适当的。

### 3.5. Spring 字段格式

如上一节所讨论的,[`core.convert`]是一种通用的类型转换系统。它提供了一个统一的`ConversionService`API 以及一个强类型`Converter`SPI,用于实现从一种类型到另一种类型的转换逻辑。 Spring 容器使用此系统绑定 Bean 属性值。此外, Spring 表达式语言和`DataBinder`都使用此系统绑定字段值。例如,当 SPEL 需要强制`Short`到`Long`以完成`expression.setValue(Object bean, Object value)`尝试时,`core.convert`系统执行强制。

现在考虑典型的客户机环境(例如 Web 或桌面应用程序)的类型转换需求。在这样的环境中,通常从`String`转换为支持客户端回发过程,以及返回`String`以支持视图呈现过程。此外,你经常需要本地化`String`值。更一般的`core.convert``Converter`SPI 并不直接解决此类格式要求。为了直接解决这些问题, Spring 3 引入了一个方便的`Formatter`SPI,它为客户机环境提供了`PropertyEditor`实现的简单而健壮的替代方案。

通常,当需要实现通用类型转换逻辑时,可以使用`Converter`SPI——例如,用于在`java.util.Date`和`Long`之间进行转换。当你在客户机环境(例如 Web 应用程序)中工作并且需要解析和打印本地化字段值时,可以使用`Formatter`SPI。`ConversionService`为两个 SPI 提供了统一的类型转换 API。

#### 3.5.1.`Formatter`SPI

实现字段格式逻辑的`Formatter`SPI 是简单且强类型的。下面的清单显示了`Formatter`接口定义:

```
package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}
```

`Formatter`扩展自`Printer`和`Parser`构建块接口。下面的清单显示了这两个接口的定义:

```
public interface Printer<T> {

    String print(T fieldValue, Locale locale);
}
```

```
import java.text.ParseException;

public interface Parser<T> {

    T parse(String clientValue, Locale locale) throws ParseException;
}
```

要创建自己的`Formatter`,请实现前面显示的`Formatter`接口。将`T`参数化为你希望格式化的对象类型——例如,`java.util.Date`。实现`print()`操作来打印`T`的实例,以便在客户机语言环境中显示。实现`parse()`操作,从客户机区域设置返回的格式化表示中解析`T`的实例。如果解析尝试失败,你的`Formatter`应该抛出`ParseException`或`IllegalArgumentException`。注意确保你的`Formatter`实现是线程安全的。

`format`子包提供了几个`Formatter`实现作为一种便利。`number`包提供`NumberStyleFormatter`、`CurrencyStyleFormatter`和`PercentStyleFormatter`来格式化`Number`使用`java.text.NumberFormat`的对象。`datetime`包提供了`DateFormatter`,以将`java.util.Date`对象格式化为`java.text.DateFormat`。

下面的`DateFormatter`是`Formatter`实现的示例:

爪哇

```
package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {

    private String pattern;

    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }

    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}
```

Kotlin

```
class DateFormatter(private val pattern: String) : Formatter<Date> {

    override fun print(date: Date, locale: Locale)
            = getDateFormat(locale).format(date)

    @Throws(ParseException::class)
    override fun parse(formatted: String, locale: Locale)
            = getDateFormat(locale).parse(formatted)

    protected fun getDateFormat(locale: Locale): DateFormat {
        val dateFormat = SimpleDateFormat(this.pattern, locale)
        dateFormat.isLenient = false
        return dateFormat
    }
}
```

Spring 团队欢迎社区驱动的`Formatter`贡献。见[GitHub 问题](https://github.com/spring-projects/spring-framework/issues)供稿。

#### 3.5.2.注解驱动的格式

字段格式可以根据字段类型或注释进行配置。要将注释绑定到`Formatter`,请实现`AnnotationFormatterFactory`。下面的清单显示了`AnnotationFormatterFactory`接口的定义:

```
package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

    Set<Class<?>> getFieldTypes();

    Printer<?> getPrinter(A annotation, Class<?> fieldType);

    Parser<?> getParser(A annotation, Class<?> fieldType);
}
```

要创建一个实现:

1. 将 a 参数化为你希望与其关联的格式化逻辑的字段`annotationType`,例如`org.springframework.format.annotation.DateTimeFormat`。

2. 让`getFieldTypes()`返回可以使用注释的字段类型。

3. 有`getPrinter()`返回一个`Printer`来打印一个注释域的值。

4. 有`getParser()`返回一个`Parser`来解析带注释字段的`clientValue`。

下面的示例`AnnotationFormatterFactory`实现将`@NumberFormat`注释绑定到格式化程序,以便指定数字样式或模式:

爪哇

```
public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {

    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }

    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}
```

Kotlin

```
class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {

    override fun getFieldTypes(): Set<Class<*>> {
        return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
    }

    override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }

    override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }

    private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
        return if (annotation.pattern.isNotEmpty()) {
            NumberStyleFormatter(annotation.pattern)
        } else {
            val style = annotation.style
            when {
                style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
                style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
                else -> NumberStyleFormatter()
            }
        }
    }
}
```

要触发格式设置,你可以使用 @NumberFormat 对字段进行注释,如下例所示:

爪哇

```
public class MyModel {

    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;
}
```

Kotlin

```
class MyModel(
    @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
```

#####  格式注释 API

`org.springframework.format.annotation`包中存在一个可移植格式注释 API。可以使用`@NumberFormat`来格式化`Number`字段,例如`Double`和`Long`,以及`@DateTimeFormat`来格式化`java.util.Date`,`java.util.Calendar`,`Long`(用于毫秒时间戳)以及 jsr-310`java.time`。

下面的示例使用`@DateTimeFormat`将`java.util.Date`格式化为 ISO 日期:

爪哇

```
public class MyModel {

    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}
```

Kotlin

```
class MyModel(
    @DateTimeFormat(iso=ISO.DATE) private val date: Date
)
```

#### 3.5.3.`FormatterRegistry`SPI

`FormatterRegistry`是一个用于注册格式化程序和转换器的 SPI。`FormattingConversionService`是一个适用于大多数环境的`FormatterRegistry`的实现。你可以通过编程或声明性地将此变体配置为 Spring  Bean,例如通过使用`FormattingConversionServiceFactoryBean`。因为该实现还实现了`ConversionService`,所以你可以直接将其配置为与 Spring 的`DataBinder`和 Spring 表达式语言一起使用。

下面的清单显示了`FormatterRegistry`SPI:

```
package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addPrinter(Printer<?> printer);

    void addParser(Parser<?> parser);

    void addFormatter(Formatter<?> formatter);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}
```

如前面的清单所示,你可以通过字段类型或注释注册格式化程序。

`FormatterRegistry`SPI 允许你集中配置格式规则,而不是在你的控制器上重复这种配置。例如,你可能希望强制所有日期字段以特定的方式格式化,或者使用特定注释的字段以特定的方式格式化。使用共享的`FormatterRegistry`,你可以定义这些规则一次,并且在需要格式化时应用它们。

#### 3.5.4.`FormatterRegistrar`SPI

`FormatterRegistrar`是一个用于通过 FormatterRegistry 注册格式化程序和转换器的 SPI。下面的清单显示了它的接口定义:

```
package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}
```

当为给定的格式分类(例如日期格式)注册多个相关的转换器和格式化程序时,`FormatterRegistrar`是有用的。在声明性注册不足的情况下——例如,当格式化程序需要在与其本身的`<T>`不同的特定字段类型下进行索引时,或者当注册`Printer`/`Parser`对时,它也是有用的。下一节提供了有关转换器和格式化程序注册的更多信息。

#### 3.5.5.在 Spring MVC 中配置格式

参见 Spring MVC 章节中的[转换和格式化](web.html#mvc-config-conversion)。

### 3.6.配置全局日期和时间格式

默认情况下,不带`@DateTimeFormat`注释的日期和时间字段将使用`DateFormat.SHORT`样式从字符串转换。如果你愿意,你可以通过定义自己的全局格式来更改这一点。

要做到这一点,请确保 Spring 不注册默认格式化程序。相反,在以下帮助下手动注册格式化程序:

* `org.springframework.format.datetime.standard.DateTimeFormatterRegistrar`

* `org.springframework.format.datetime.DateFormatterRegistrar`

例如,下面的 爪哇 配置注册了一个全局`yyyyMMdd`格式:

爪哇

```
@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // Register JSR-310 date conversion with a specific global format
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}
```

Kotlin

```
@Configuration
class AppConfig {

    @Bean
    fun conversionService(): FormattingConversionService {
        // Use the DefaultFormattingConversionService but do not register defaults
        return DefaultFormattingConversionService(false).apply {

            // Ensure @NumberFormat is still supported
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())

            // Register JSR-310 date conversion with a specific global format
            val registrar = DateTimeFormatterRegistrar()
            registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            registrar.registerFormatters(this)

            // Register date conversion with a specific global format
            val registrar = DateFormatterRegistrar()
            registrar.setFormatter(DateFormatter("yyyyMMdd"))
            registrar.registerFormatters(this)
        }
    }
}
```

如果你更喜欢基于 XML 的配置,那么可以使用`FormattingConversionServiceFactoryBean`。下面的示例展示了如何做到这一点:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd>

    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="registerDefaultFormatters" value="false" />
        <property name="formatters">
            <set>
                <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
                    <property name="dateFormatter">
                        <bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
                            <property name="pattern" value="yyyyMMdd"/>
                        </bean>
                    </property>
                </bean>
            </set>
        </property>
    </bean>
</beans>
```

注意,在 Web 应用程序中配置日期和时间格式时还需要考虑其他因素。请参阅[WebMVC 转换和格式化](web.html#mvc-config-conversion)或[WebFlux 转换和格式化](web-reactive.html#webflux-config-conversion)。

### 3.7.爪哇 Bean 验证

Spring 框架为[爪哇 Bean Validation](https://beanvalidation.org/)API 提供支持。

#### 3.7.1. Bean 验证概述

Bean 验证通过约束声明和元数据为 爪哇 应用程序提供了一种通用的验证方式。要使用它,你需要使用声明性验证约束注释域模型属性,然后由运行时强制执行这些约束。有内置的约束,你也可以定义自己的自定义约束。

考虑以下示例,其中显示了一个简单的`PersonForm`模型,该模型具有两个属性:

爪哇

```
public class PersonForm {
    private String name;
    private int age;
}
```

Kotlin

```
class PersonForm(
        private val name: String,
        private val age: Int
)
```

Bean 验证允许你声明约束,如下例所示:

爪哇

```
public class PersonForm {

    @NotNull
    @Size(max=64)
    private String name;

    @Min(0)
    private int age;
}
```

Kotlin

```
class PersonForm(
    @get:NotNull @get:Size(max=64)
    private val name: String,
    @get:Min(0)
    private val age: Int
)
```

Bean 验证验证器然后基于声明的约束对该类的实例进行验证。有关 API 的一般信息,请参见[Bean Validation](https://beanvalidation.org/)。有关特定的约束,请参见[Hibernate Validator](https://hibernate.org/validator/)文档。要了解如何将 Bean 验证提供者设置为 Spring  Bean,请继续阅读。

#### 3.7.2.配置 Bean 验证提供程序

Spring 为 Bean 验证 API 提供了充分的支持,包括将 Bean 验证提供者引导为 Spring  Bean。这允许你在应用程序中需要验证的任何地方注入`javax.validation.ValidatorFactory`或`javax.validation.Validator`。

可以使用`LocalValidatorFactoryBean`将默认验证器配置为 Spring  Bean,如下例所示:

爪哇

```
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class AppConfig {

    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
}
```

XML

```
<bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
```

前面示例中的基本配置触发 Bean 验证,以通过使用其默认的引导程序机制来初始化。 Bean 验证提供程序,例如 Hibernate 验证器,预计将存在于 Classpath 中并被自动检测。

#####  注入验证器

`LocalValidatorFactoryBean`实现了`javax.validation.ValidatorFactory`和`javax.validation.Validator`,以及 Spring 的`org.springframework.validation.Validator`。你可以向需要调用验证逻辑的 bean 中注入对这两个接口中任一个的引用。

如果你更愿意直接使用 Bean 验证 API,那么可以插入对`javax.validation.Validator`的引用,如下例所示:

爪哇

```
import javax.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}
```

Kotlin

```
import javax.validation.Validator;

@Service
class MyService(@Autowired private val validator: Validator)
```

如果你的 Bean 需要 Spring 验证 API,则可以插入对`org.springframework.validation.Validator`的引用,如下例所示:

爪哇

```
import org.springframework.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}
```

Kotlin

```
import org.springframework.validation.Validator

@Service
class MyService(@Autowired private val validator: Validator)
```

#####  配置自定义约束

Bean 每个验证约束由两部分组成:

* 声明约束及其可配置属性的`@Constraint`注释。

* 实现约束行为的`javax.validation.ConstraintValidator`接口的实现。

要将声明与实现关联,每个`@Constraint`注释引用一个对应的`ConstraintValidator`实现类。在运行时,当域模型中遇到约束注释时,`ConstraintValidatorFactory`实例化引用的实现。

默认情况下,`LocalValidatorFactoryBean`配置一个`SpringConstraintValidatorFactory`,它使用 Spring 来创建`ConstraintValidator`实例。这使你的自定义`ConstraintValidators`像任何其他 Spring  Bean 一样受益于依赖注入。

下面的示例显示了一个自定义的`@Constraint`声明,后面跟着一个关联的`ConstraintValidator`实现,该实现将 Spring 用于依赖项注入:

爪哇

```
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
```

Kotlin

```
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator::class)
annotation class MyConstraint
```

爪哇

```
import javax.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

    @Autowired;
    private Foo aDependency;

    // ...
}
```

Kotlin

```
import javax.validation.ConstraintValidator

class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator {

    // ...
}
```

正如前面的示例所示,一个`ConstraintValidator`实现可以与任何其他 Spring  Bean 一样具有其依赖性`@Autowired`。

#####  Spring-驱动方法验证

你可以通过`MethodValidationPostProcessor` Bean 定义将 Bean 验证 1.1(以及 Hibernate 验证 4.3 作为自定义扩展)支持的方法验证功能集成到 Spring 上下文中:

爪哇

```
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class AppConfig {

    @Bean
    public MethodValidationPostProcessor validationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}
```

XML

```
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
```

为了有资格进行 Spring 驱动的方法验证,所有目标类都需要使用 Spring 的`@Validated`注释进行注释,该注释还可以选择性地声明要使用的验证组。参见[`MethodValidationPostProcessor`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/validation/beanvalidation/methodvalidationpostprocessor.html)以了解 Hibernate 验证器和 Bean 验证 1.1 提供程序的设置细节。

|   |方法验证依赖于[AOP Proxies](#aop-introduction-proxies)周围的<br/>目标类,或者是接口上方法的 JDK 动态代理,或者是 CGLIB 代理。<br/>使用代理有一定的限制,其中一些在[Understanding AOP Proxies](#aop-understanding-aop-proxies)中进行了描述。此外,请记住<br/>始终在代理类上使用方法和访问器;直接字段访问将不起作用。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  附加配置选项

对于大多数情况,默认的`LocalValidatorFactoryBean`配置就足够了。对于各种 Bean 验证构造,从消息插值到遍历解析,有许多配置选项。有关这些选项的更多信息,请参见[`LocalValidatorFactoryBean`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/validation/beanvalidation/localvalidatorfactorybean.html)爪哇doc。

#### 3.7.3.配置`DataBinder`

自 Spring 3 起,可以使用`Validator`配置`DataBinder`实例。一旦配置好,你就可以通过调用`binder.validate()`来调用`Validator`。任何验证`Errors`都会自动添加到活页夹的`BindingResult`中。

下面的示例展示了如何以编程方式使用`DataBinder`在绑定到目标对象后调用验证逻辑:

爪哇

```
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
```

Kotlin

```
val target = Foo()
val binder = DataBinder(target)
binder.validator = FooValidator()

// bind to the target object
binder.bind(propertyValues)

// validate the target object
binder.validate()

// get BindingResult that includes any validation errors
val results = binder.bindingResult
```

你还可以通过`dataBinder.addValidators`和`dataBinder.replaceValidators`配置带有多个`Validator`实例的`DataBinder`。当将全局配置的 Bean 验证与在 Databinder 实例上本地配置的 Spring `Validator`相结合时,这是有用的。见[Spring MVC Validation Configuration](web.html#mvc-config-validation)。

#### 3.7.4. Spring MVC3 验证

参见 Spring MVC 章节中的[Validation](web.html#mvc-config-validation)。

## 4. Spring 表达式语言

Spring 表达式语言(简称“SPEL”)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。该语言语法类似于 Unified EL,但提供了额外的功能,最明显的是方法调用和基本的字符串模板功能。

虽然还有其他几种可用的 爪哇 表达式语言——OGNL、MVEL 和 JBossEL,仅举几例——但 Spring 表达式语言的创建是为了向 Spring 社区提供一种受良好支持的表达式语言,这种语言可以在 Spring 产品组合中的所有产品中使用。它的语言特性是由 Spring 投资组合中的项目的需求驱动的,包括[Spring Tools for Eclipse](https://spring.io/tools)中对代码完成支持的工具需求。也就是说,SPEL 基于一种与技术无关的 API,该 API 允许在需要时集成其他表达式语言实现。

虽然 SPEL 是 Spring 投资组合中表达式求值的基础,但它不直接绑定到 Spring,可以独立使用。为了自成一体,本章中的许多示例使用 SPEL,就好像它是一种独立的表达语言。这需要创建一些引导基础设施类,比如解析器。 Spring 大多数用户不需要处理此基础结构,并且可以仅使用作者表达式字符串进行求值。这种典型用途的一个例子是将 SPEL 集成到创建 XML 或基于注释的 Bean 定义中,如[Expression support for defining bean definitions](#expressions-beandef)所示。

本章介绍了表达式语言的特点、API 和语言语法。在几个地方,`Inventor`和`Society`类被用作表达式求值的目标对象。这些类声明和用于填充它们的数据在本章的末尾列出。

表达式语言支持以下功能:

* 字面表达式

* 布尔运算符和关系运算符

* 正则表达式

* 类表达式

* 访问属性、数组、列表和映射

* 方法调用

* 关系运算符

* 任务分配

* 调用构造函数

* Bean 参考文献

* 阵列构造

* 内联列表

* 内联地图

* 三元算符

* 变量

* 用户定义的函数

* 集合投影

* 收藏选择

* 模板化表达式

### 4.1.评价

这一节介绍了 SPEL 接口的简单用法及其表达式语言。完整的语言引用可以在[语言参考](#expressions-language-ref)中找到。

下面的代码引入了 SPEL API 来计算字面字符串表达式`Hello World`。

爪哇

```
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
```

|**1**|消息变量的值是`'Hello World'`。|
|-----|-----------------------------------------------------|

Kotlin

```
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
```

|**1**|消息变量的值是`'Hello World'`。|
|-----|-----------------------------------------------------|

你最有可能使用的 SPEL 类和接口位于`org.springframework.expression`包及其子包中,例如`spel.support`。

`ExpressionParser`接口负责解析表达式字符串。在前面的示例中,表达式字符串是由周围的单引号表示的字符串文字。`Expression`接口负责计算先前定义的表达式字符串。当分别调用`parser.parseExpression`和`exp.getValue`时,可以抛出两个异常`ParseException`和`EvaluationException`。

SPEL 支持多种功能,例如调用方法、访问属性和调用构造函数。

在下面的方法调用示例中,我们在字符串字面上调用`concat`方法:

爪哇

```
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
```

|**1**|`message`的值现在是“Hello World!”。|
|-----|---------------------------------------------|

Kotlin

```
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
```

|**1**|`message`的值现在是“Hello World!”。|
|-----|---------------------------------------------|

下面调用 爪哇Bean 属性的示例调用`String`属性`Bytes`:

爪哇

```
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
```

|**1**|这一行将字面值转换为字节数组。|
|-----|-----------------------------------------------|

Kotlin

```
val parser = SpelExpressionParser()

// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
```

|**1**|这一行将字面值转换为字节数组。|
|-----|-----------------------------------------------|

SPEL 还通过使用标准的点标记(例如`prop1.prop2.prop3`)和相应的属性值设置来支持嵌套属性。也可以访问公共字段。

下面的示例展示了如何使用点记号来获取文字的长度:

爪哇

```
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();
```

|**1**|`'Hello World'.bytes.length`给出了字面的长度。|
|-----|-------------------------------------------------------------|

Kotlin

```
val parser = SpelExpressionParser()

// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
```

|**1**|`'Hello World'.bytes.length`给出了字面的长度。|
|-----|-------------------------------------------------------------|

可以调用字符串的构造函数,而不是使用字符串文字,如下例所示:

爪哇

```
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
```

|**1**|从字面上构造一个新的`String`,并使其成为大写。|
|-----|--------------------------------------------------------------------|

Kotlin

```
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()")  (1)
val message = exp.getValue(String::class.java)
```

|**1**|从字面上构造一个新的`String`,并使其成为大写。|
|-----|--------------------------------------------------------------------|

注意通用方法的使用:`public <T> T getValue(Class<T> desiredResultType)`。使用此方法就不需要将表达式的值强制转换为所需的结果类型。如果不能将该值强制转换为`T`类型或使用注册的类型转换器进行转换,则抛出`EvaluationException`。

SPEL 更常见的用法是提供一个表达式字符串,该表达式字符串根据特定的对象实例(称为根对象)进行求值。下面的示例展示了如何从`Inventor`类的实例检索`name`属性或创建布尔条件:

爪哇

```
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
```

Kotlin

```
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)

// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")

val parser = SpelExpressionParser()

var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true
```

#### 4.1.1.理解`EvaluationContext`

在计算表达式以解析属性、方法或字段并帮助执行类型转换时,使用`EvaluationContext`接口。 Spring 提供了两种实现方式。

* `SimpleEvaluationContext`:对于不需要完整的 SPEL 语言语法的表达式类别,公开了基本的 SPEL 语言特性和配置选项的子集,并且应该进行有意义的限制。示例包括但不限于数据绑定表达式和基于属性的过滤器。

* `StandardEvaluationContext`:公开了全套 SPEL 语言特性和配置选项。你可以使用它来指定一个默认的根对象,并配置所有可用的与评估相关的策略。

`SimpleEvaluationContext`旨在仅支持 SPEL 语言语法的一个子集。它排除了 爪哇 类型引用、构造函数和 Bean 引用。它还要求你显式地选择对表达式中的属性和方法的支持级别。默认情况下,`create()`静态工厂方法仅允许对属性的读访问。你还可以获得一个构建器,以配置所需的确切支持级别,目标是以下一种或几种组合:

* 仅自定义`PropertyAccessor`(无反射)

* 只读访问的数据绑定属性

* 用于读写的数据绑定属性

#####  类型转换

默认情况下,SPEL 使用 Spring core(`org.springframework.core.convert.ConversionService`)中可用的转换服务。这种转换服务带有许多用于公共转换的内置转换器,但也是完全可扩展的,因此你可以在类型之间添加自定义转换。此外,它具有泛型意识。这意味着,当你在表达式中处理泛型类型时,SPEL 会尝试转换,以维护它遇到的任何对象的类型正确性。

这在实践中意味着什么?假设使用`setValue()`的赋值用于设置`List`属性。属性的类型实际上是`List<Boolean>`。SPEL 认识到,在将列表中的元素放入其中之前,需要将其转换为`Boolean`。下面的示例展示了如何做到这一点:

爪哇

```
class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}

Simple simple = new Simple();
simple.booleanList.add(true);

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

// b is false
Boolean b = simple.booleanList.get(0);
```

Kotlin

```
class Simple {
    var booleanList: MutableList<Boolean> = ArrayList()
}

val simple = Simple()
simple.booleanList.add(true)

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")

// b is false
val b = simple.booleanList[0]
```

#### 4.1.2.解析器配置

可以通过使用解析器配置对象(`org.springframework.expression.spel.SpelParserConfiguration`)来配置 SPEL 表达式解析器。配置对象控制一些表达式组件的行为。例如,如果你索引到一个数组或集合中,并且指定索引处的元素是`null`,那么 SPEL 可以自动创建该元素。这在使用由一系列属性引用组成的表达式时很有用。如果你将索引放入一个数组或列表中,并指定一个超出该数组或列表当前大小的索引,那么 SPEL 可以自动增加数组或列表以适应该索引。为了在指定的索引处添加元素,在设置指定值之前,SPEL 将尝试使用元素类型的默认构造函数创建元素。如果元素类型没有默认的构造函数,`null`将被添加到数组或列表中。如果没有知道如何设置该值的内置或自定义转换器,`null`将保留在指定索引处的数组或列表中。下面的示例演示了如何自动增加列表:

爪哇

```
class Demo {
    public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true, true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
```

Kotlin

```
class Demo {
    var list: List<String>? = null
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)

val parser = SpelExpressionParser(config)

val expression = parser.parseExpression("list[3]")

val demo = Demo()

val o = expression.getValue(demo)

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
```

#### 4.1.3.SPEL 编译

Spring Framework4.1 包括基本表达式编译器。表达式通常被解释,这在评估期间提供了很大的动态灵活性,但并不提供最佳性能。对于偶尔使用表达式来说,这是很好的,但是,当被 Spring 集成之类的其他组件使用时,性能可能是非常重要的,并且不需要真正的动态性。

SPEL 编译器旨在解决这一需求。在求值过程中,编译器生成一个 爪哇 类,该类体现了运行时的表达式行为,并使用该类来实现更快的表达式求值。由于缺少表达式的类型,编译器在执行编译时使用在表达式的解释求值过程中收集的信息。例如,它并不完全从表达式中知道属性引用的类型,但是在第一次解释求值期间,它会找出它是什么。当然,如果各种表达式元素的类型随时间变化,基于这种派生信息的编译可能会在以后引起麻烦。由于这个原因,编译最适合于其类型信息不会在重复求值时发生变化的表达式。

考虑以下基本表达式:

```
someArray[0].someProperty.someOtherProperty < 0.1
```

由于前面的表达式涉及到数组访问、一些属性反引用和数字操作,因此性能增益可以非常明显。在运行 50000 次迭代的 MicroBenchmark 示例中,使用解释器进行计算需要 75ms,而使用表达式的编译版本只需要 3ms。

#####  编译器配置

默认情况下,编译器不会打开,但你可以通过两种不同的方式打开它。你可以通过使用解析器配置过程([前面讨论过](#expressions-parser-configuration))打开它,或者在将 SPEL 用法嵌入到另一个组件中时使用 Spring 属性打开它。本节讨论这两种选择。

编译器可以在`org.springframework.expression.spel.SpelCompilerMode`枚举中捕获的三种模式中的一种进行操作。模式如下:

* `OFF`(默认):编译器已关闭。

* `IMMEDIATE`:在即时模式下,表达式会尽快编译。这通常是在第一次解释评估之后。如果编译的表达式失败(通常是由于类型更改,如前面所述),表达式求值的调用者将收到一个异常。

* `MIXED`:在混合模式下,表达式会随着时间的推移在解释模式和编译模式之间默默地切换。经过一定数量的解释运行后,它们会切换到编译表单,如果编译表单出了问题(例如前面描述的类型更改),表达式会自动再次切换回解释表单。稍后,它可能会生成另一个已编译的表单并切换到它。基本上,用户在`IMMEDIATE`模式下获得的异常将在内部处理。

`IMMEDIATE`模式的存在是因为`MIXED`模式可能会导致具有副作用的表达式出现问题。如果编译的表达式在部分成功后爆炸,那么它可能已经做了一些影响系统状态的事情。如果发生了这种情况,调用者可能不希望它以解释模式静默地重新运行,因为表达式的一部分可能运行了两次。

选择模式后,使用`SpelParserConfiguration`配置解析器。下面的示例展示了如何做到这一点:

爪哇

```
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
        this.getClass().getClassLoader());

SpelExpressionParser parser = new SpelExpressionParser(config);

Expression expr = parser.parseExpression("payload");

MyMessage message = new MyMessage();

Object payload = expr.getValue(message);
```

Kotlin

```
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
        this.javaClass.classLoader)

val parser = SpelExpressionParser(config)

val expr = parser.parseExpression("payload")

val message = MyMessage()

val payload = expr.getValue(message)
```

在指定编译器模式时,还可以指定一个类装入器(允许传递空)。编译的表达式是在一个子类加载器中定义的,这个子类加载器是在所提供的任何类型下创建的。重要的是要确保,如果指定了类装入器,它就可以看到表达式求值过程中涉及的所有类型。如果没有指定类装入器,则使用默认的类装入器(通常是在表达式求值期间运行的线程的上下文类装入器)。

配置编译器的第二种方法是当 SPEL 嵌入到其他组件中时使用,并且可能无法通过配置对象对其进行配置。在这些情况下,可以通过 JVM 系统属性(或通过[`SpringProperties`](acception.html#acception- Spring-properties)机制)将`spring.expression.compiler.mode`属性设置为`SpelCompilerMode`enum 值(`off`,或`immediate`)中的一个值。

#####  编译器的限制

Spring 自框架 4.1 以来,基本的编译框架已经到位。然而,该框架还不支持编译所有类型的表达式。最初的重点是可能在性能关键上下文中使用的常见表达式。以下表达式目前无法编译:

* 涉及赋值的表达式

* 依赖于转换服务的表达式

* 使用自定义解析器或访问器的表达式

* 使用选择或投影的表达式

将来会有更多类型的表达式可以编译。

### 4.2. Bean 定义中的表达式

你可以使用带有基于 XML 或基于注释的配置元数据的 SPEL 表达式来定义`BeanDefinition`实例。在这两种情况下,定义表达式的语法都是`#{ <expression string> }`形式。

#### 4.2.1.XML 配置

可以通过使用表达式来设置属性或构造函数参数的值,如下例所示:

```
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>
```

应用程序上下文中的所有 bean 都可以作为预定义的变量使用它们的公共 Bean 名称。这包括用于访问运行时环境的标准上下文 bean,如`environment`(类型`org.springframework.core.env.Environment`)以及`systemProperties`和`systemEnvironment`(类型`Map<String, Object>`)。

下面的示例显示了对`systemProperties` Bean 作为 SPEL 变量的访问:

```
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

    <!-- other properties -->
</bean>
```

请注意,你不必在这里用`#`符号在预定义的变量前加上前缀。

还可以按名称引用其他 Bean 属性,如下例所示:

```
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>

    <!-- other properties -->
</bean>
```

#### 4.2.2.注释配置

要指定默认值,可以将`@Value`注释放置在字段、方法和方法或构造函数参数上。

下面的示例设置字段的默认值:

爪哇

```
public class FieldValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;

    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}
```

Kotlin

```
class FieldValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    var defaultLocale: String? = null
}
```

下面的示例展示了等价的但在属性 setter 方法上的方法:

爪哇

```
public class PropertyValueTestBean {

    private String defaultLocale;

    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}
```

Kotlin

```
class PropertyValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    var defaultLocale: String? = null
}
```

AutoWired 方法和构造函数也可以使用`@Value`注释,如下例所示:

爪哇

```
public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
```

Kotlin

```
class SimpleMovieLister {

    private lateinit var movieFinder: MovieFinder
    private lateinit var defaultLocale: String

    @Autowired
    fun configure(movieFinder: MovieFinder,
                @Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
        this.movieFinder = movieFinder
        this.defaultLocale = defaultLocale
    }

    // ...
}
```

爪哇

```
public class MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
            @Value("#{systemProperties['user.country']}") String defaultLocale) {
        this.customerPreferenceDao = customerPreferenceDao;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
```

Kotlin

```
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
            @Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
    // ...
}
```

### 4.3.语言参考

本节描述 Spring 表达式语言的工作方式。它涵盖以下主题:

* [字面表达式](#expressions-ref-literal)

* [属性、数组、列表、映射和索引器](#expressions-properties-arrays)

* [内联列表](#expressions-inline-lists)

* [内联地图](#expressions-inline-maps)

* [阵列构造](#expressions-array-construction)

* [Methods](#expressions-methods)

* [Operators](#expressions-operators)

* [Types](#expressions-types)

* [构造者](#expressions-constructors)

* [Variables](#expressions-ref-variables)

* [Functions](#expressions-ref-functions)

* [Bean References](#expressions-bean-references)

* [三元运算符(if-then-else)](#expressions-operator-ternary)

* [猫王操作员](#expressions-operator-elvis)

* [安全导航操作员](#expressions-operator-safe-navigation)

#### 4.3.1.字面表达式

所支持的文字表达式的类型包括字符串、数值(INT、实数、十六进制)、布尔表达式和空表达式。字符串用单引号分隔。要将单引号本身放入字符串中,请使用两个单引号字符。

下面的清单显示了文字的简单用法。通常,它们不会像这样孤立地使用,而是作为更复杂表达式的一部分使用——例如,在逻辑比较运算符的一侧使用文字。

爪哇

```
ExpressionParser parser = new SpelExpressionParser();

// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();
```

Kotlin

```
val parser = SpelExpressionParser()

// evals to "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String

val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double

// evals to 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int

val trueValue = parser.parseExpression("true").value as Boolean

val nullValue = parser.parseExpression("null").value
```

数字支持使用负号、指数记号和小数点。默认情况下,通过使用`Double.parseDouble()`解析实数。

#### 4.3.2.属性、数组、列表、映射和索引器

使用属性引用进行导航很容易。要做到这一点,请使用一个句号来指示嵌套的属性值。`Inventor`类的实例`pupin`和`tesla`的实例使用[示例中使用的类](#expressions-example-classes)部分中列出的数据填充。为了导航“向下”的对象图,并获得特斯拉的出生年份和普平的出生城市,我们使用以下表达式:

爪哇

```
// evals to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
```

Kotlin

```
// evals to 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int

val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
```

|   |允许对财产名称的第一个字母不区分大小写。因此,上述示例中的<br/>表达式可以分别写为`Birthdate.Year + 1900`和`PlaceOfBirth.City`。此外,可以选择通过<br/>方法调用访问属性——例如,`getPlaceOfBirth().getCity()`而不是`placeOfBirth.city`。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

数组和列表的内容是通过使用方括号表示法获得的,如下例所示:

爪哇

```
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// Inventions Array

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String.class);

// Members List

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String.class);
```

Kotlin

```
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

// Inventions Array

// evaluates to "Induction motor"
val invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String::class.java)

// Members List

// evaluates to "Nikola Tesla"
val name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String::class.java)

// List and Array navigation
// evaluates to "Wireless communication"
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String::class.java)
```

映射的内容是通过在括号中指定文字键的值来获得的。在下面的示例中,因为`officers`映射的键是字符串,所以我们可以指定字符串字面值:

爪哇

```
// Officer's Dictionary

Inventor pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor.class);

// evaluates to "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String.class);

// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia");
```

Kotlin

```
// Officer's Dictionary

val pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor::class.java)

// evaluates to "Idvor"
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String::class.java)

// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia")
```

#### 4.3.3.内联列表

你可以使用`{}`符号在表达式中直接表示列表。

爪哇

```
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
```

Kotlin

```
// evaluates to a Java list containing the four numbers
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>

val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>
```

`{}`本身意味着空列表。出于性能原因,如果列表本身完全由固定的文字组成,则创建一个常量列表来表示表达式(而不是在每个求值上构建一个新的列表)。

#### 4.3.4.内联地图

你也可以使用`{key:value}`符号在表达式中直接表示映射。下面的示例展示了如何做到这一点:

爪哇

```
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
```

Kotlin

```
// evaluates to a Java map containing the two entries
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, *>

val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<*, *>
```

`{:}`本身意味着一个空的映射。出于性能原因,如果映射本身由固定的文字或其他嵌套的常量结构(列表或映射)组成,则创建一个常量映射来表示表达式(而不是在每个求值上构建一个新的映射)。对映射键的引用是可选的(除非该键包含一个句号(`.`))。上面的示例不使用引号键。

#### 4.3.5.阵列构造

你可以使用熟悉的 爪哇 语法构建数组,也可以提供一个初始化器,以便在构建时填充数组。下面的示例展示了如何做到这一点:

爪哇

```
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
```

Kotlin

```
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray

// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray

// Multi dimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>
```

在构造多维数组时,当前不能提供初始化器。

#### 4.3.6.方法

你可以通过使用典型的 爪哇 编程语法调用方法。你还可以调用文字上的方法。还支持变量参数。以下示例展示了如何调用方法:

爪哇

```
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);
```

Kotlin

```
// string literal, evaluates to "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)

// evaluates to true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean::class.java)
```

#### 4.3.7.操作员

Spring 表达式语言支持以下几种运算符:

* [关系运算符](#expressions-operators-relational)

* [逻辑运算符](#expressions-operators-logical)

* [数学运算符](#expressions-operators-mathematical)

* [赋值运算符](#expressions-assignment)

#####  关系运算符

关系运算符(相等、不相等、小于、小于或等于、大于和大于或等于)通过使用标准运算符表示法来支持。下面的列表显示了几个操作符的示例:

爪哇

```
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
```

Kotlin

```
// evaluates to true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)

// evaluates to false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)

// evaluates to true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
```

|   |与`null`的大于或小于比较遵循一个简单的规则:`null`被视为<br/>nothing(即不为零)。因此,任何其他值总是大于<br/>`null`(`X > null`总是`true`),并且没有任何其他值总是小于 nothing<br/>(`X < null`总是<br/>)。<br/>如果你更喜欢数字比较,那么,<br/>避免基于数字的`null`比较<br/>,而倾向于与零进行比较(例如,`X > 0`或`X < 0`)。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

除了标准的关系运算符,SPEL 还支持`instanceof`和基于正则表达式的`matches`运算符。下面的清单展示了这两个方面的例子:

爪哇

```
// evaluates to false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
```

Kotlin

```
// evaluates to false
val falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean::class.java)

// evaluates to true
val trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)

// evaluates to false
val falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
```

|   |注意原始类型,因为它们会立即 Boxed 到它们的<br/>包装器类型。例如,`1 instanceof T(int)`的计算结果为`false`,而`1 instanceof T(Integer)`的计算结果为`true`,正如预期的那样。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

每个符号运算符也可以指定为纯字母等价的。这避免了所使用的符号对于嵌入表达式的文档类型(例如在 XML 文档中)具有特殊含义的问题。对应的文本是:

* `lt`(`<`)

* `gt`(`>`)

* `le`(`<=`)

* `ge`(`>=`)

* `eq`(`==`)

* `ne`(`!=`)

* `div`(`/`)

* `mod`(`%`)

* `not`(`!`).

所有的文本运算符都是不区分大小写的。

#####  逻辑运算符

SPEL 支持以下逻辑运算符:

* `and`(`&&`)

* `or`(`||`)

* `not`(`!`)

下面的示例展示了如何使用逻辑运算符:

爪哇

```
// -- AND --

// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- OR --

// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- NOT --

// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
```

Kotlin

```
// -- AND --

// evaluates to false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)

// evaluates to true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)

// -- OR --

// evaluates to true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)

// evaluates to true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)

// -- NOT --

// evaluates to false
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)

// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
```

#####  数学运算符

在数字和字符串上都可以使用加法运算符(`+`)。你可以只在数字上使用减法(`-`)、乘法(`*`)和除法(`/`)操作符。你还可以在数字上使用模(`%`)和指数功率(`^`)运算符。强制执行标准操作符优先级。下面的示例显示了正在使用的数学运算符:

爪哇

```
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class);  // 2

String testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String.class);  // 'test string'

// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class);  // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class);  // -9000

// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class);  // 6

double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class);  // 24.0

// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class);  // -2

double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class);  // 1.0

// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class);  // 3

int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class);  // 1

// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class);  // -21
```

Kotlin

```
// Addition
val two = parser.parseExpression("1 + 1").getValue(Int::class.java)  // 2

val testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String::class.java)  // 'test string'

// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java)  // 4

val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java)  // -9000

// Multiplication
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java)  // 6

val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java)  // 24.0

// Division
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java)  // -2

val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java)  // 1.0

// Modulus
val three = parser.parseExpression("7 % 4").getValue(Int::class.java)  // 3

val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java)  // 1

// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java)  // -21
```

#####  赋值运算符

要设置属性,请使用赋值运算符(`=`)。这通常在对`setValue`的调用中完成,但也可以在对`getValue`的调用中完成。下面的清单显示了使用赋值操作符的两种方法:

爪哇

```
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");

// alternatively
String aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
```

Kotlin

```
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()

parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic")

// alternatively
val aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)
```

#### 4.3.8.类型

你可以使用特殊的`T`操作符来指定`java.lang.Class`(类型)的实例。静态方法也可以通过使用此操作符来调用。`StandardEvaluationContext`使用`TypeLocator`来查找类型,而`StandardTypeLocator`(可以替换)是在理解`java.lang`包的情况下构建的。这意味着对`T()`包中的类型的引用不需要完全限定,但是所有其他类型的引用必须是完全限定的。下面的示例展示了如何使用`T`运算符:

爪哇

```
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);
```

Kotlin

```
val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java)

val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java)

val trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean::class.java)
```

#### 4.3.9.构造者

你可以使用`new`操作符调用构造函数。除了位于`java.lang`包(`Integer`,`Float`,`String`,以此类推)中的类型外,你应该为所有类型使用完全限定的类名。下面的示例展示了如何使用`new`操作符来调用构造函数:

爪哇

```
Inventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);

// create new Inventor instance within the add() method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);
```

Kotlin

```
val einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor::class.java)

// create new Inventor instance within the add() method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
        .getValue(societyContext)
```

#### 4.3.10.变量

你可以使用`#variableName`语法在表达式中引用变量。变量是通过在`EvaluationContext`实现上使用`setVariable`方法来设置的。

|   |有效的变量名必须由以下一个或多个受支持的<br/>字符组成。<br/><br/>* 字母:`A`到`Z`到`z`<br/>* 数字:`0`到<gt="gt="4711"/><gt="/r="><4712"/>>><<gt="gt="4712"/>>>>><<<4720">>>>>>>><gt=“4723:<gt=”gt="|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例展示了如何使用变量。

爪哇

```
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"
```

Kotlin

```
val tesla = Inventor("Nikola Tesla", "Serbian")

val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")

parser.parseExpression("name = #newName").getValue(context, tesla)
println(tesla.name)  // "Mike Tesla"
```

#####  `#this`和`#root`变量

总是定义`#this`变量,并引用当前的求值对象(针对该对象解析不合格的引用)。始终定义`#root`变量,并引用根上下文对象。虽然`#this`可以随着表达式的组件的求值而变化,但`#root`总是指根。以下示例展示了如何使用`#this`和`#root`变量:

爪哇

```
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);
```

Kotlin

```
// create an array of integers
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))

// create parser and set variable 'primes' as the array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
        "#primes.?[#this>10]").getValue(context) as List<Int>
```

#### 4.3.11.职能

你可以通过注册可以在表达式字符串中调用的用户定义函数来扩展 SPEL。函数是通过`EvaluationContext`注册的。下面的示例展示了如何注册用户定义的函数:

爪哇

```
Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
```

Kotlin

```
val method: Method = ...

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)
```

例如,考虑以下逆转字符串的实用工具方法:

爪哇

```
public abstract class StringUtils {

    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}
```

Kotlin

```
fun reverseString(input: String): String {
    val backwards = StringBuilder(input.length)
    for (i in 0 until input.length) {
        backwards.append(input[input.length - 1 - i])
    }
    return backwards.toString()
}
```

然后,你可以注册并使用前面的方法,如下例所示:

爪哇

```
ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));

String helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String.class);
```

Kotlin

```
val parser = SpelExpressionParser()

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)

val helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String::class.java)
```

#### 4.3.12. Bean 参考文献

如果已用 Bean 解析器配置了求值上下文,则可以使用`@`符号从表达式中查找 bean。下面的示例展示了如何做到这一点:

爪哇

```
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
```

Kotlin

```
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
val bean = parser.parseExpression("@something").getValue(context)
```

要访问工厂 Bean 本身,你应该在 Bean 名称前加上`&`符号。下面的示例展示了如何做到这一点:

Java

```
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
```

Kotlin

```
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
val bean = parser.parseExpression("&foo").getValue(context)
```

#### 4.3.13.三元运算符(if-then-else)

可以使用三值运算符在表达式中执行 if-then-else 条件逻辑。下面的清单展示了一个最小示例:

Java

```
String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
```

Kotlin

```
val falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String::class.java)
```

在这种情况下,布尔`false`将返回字符串值`'falseExp'`。下面是一个更现实的例子:

Java

```
parser.parseExpression("name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
        "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
```

Kotlin

```
parser.parseExpression("name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"

val queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
```

请参阅下一节关于 Elvis 操作符的内容,以获得更短的三元运算符语法。

#### 4.3.14.猫王操作员

Elvis 运算符是三元运算符语法的缩写,在[Groovy](http://www.groovy-lang.org/operators.html#_elvis_operator)语言中使用。使用三元运算符语法,你通常必须重复一个变量两次,如下例所示:

```
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
```

相反,你可以使用猫王操作符(该操作符的名称与猫王的发型相似)。下面的示例展示了如何使用 Elvis 操作符:

Java

```
ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name);  // 'Unknown'
```

Kotlin

```
val parser = SpelExpressionParser()

val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name)  // 'Unknown'
```

下面的清单展示了一个更复杂的示例:

Java

```
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla

tesla.setName(null);
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley
```

Kotlin

```
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Nikola Tesla

tesla.setName(null)
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Elvis Presley
```

|   |可以使用 Elvis 操作符在表达式中应用默认值。下面的<br/>示例展示了如何在`@Value`表达式中使用 Elvis 运算符:<br/><br/>`.?[selectionExpression]`<br/>如果不是,这将注入一个系统属性`pop3.port`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 4.3.15.安全导航操作员

安全导航操作符用于避免`NullPointerException`并来自[Groovy](http://www.groovy-lang.org/operators.html#_safe_navigation_operator)语言。通常,当你有一个对象的引用时,在访问对象的方法或属性之前,你可能需要验证它不是空的。为了避免这种情况,安全导航操作符将返回 null,而不是抛出异常。下面的示例展示了如何使用安全导航操作符:

Java

```
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // Smiljan

tesla.setPlaceOfBirth(null);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!
```

Kotlin

```
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))

var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city)  // Smiljan

tesla.setPlaceOfBirth(null)
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city)  // null - does not throw NullPointerException!!!
```

#### 4.3.16.收藏选择

选择是一种功能强大的表达式语言特性,它允许你通过从源集合的条目中进行选择来将其转换为另一个集合。

选择使用`.?[selectionExpression]`的语法。它对集合进行过滤,并返回一个新的集合,该集合包含原始元素的一个子集。例如,Selection 可以让我们很容易地获得一份塞尔维亚发明家的名单,如下例所示:

Java

```
List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext);
```

Kotlin

```
val list = parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>
```

对于实现`java.lang.Iterable`或`java.util.Map`的数组和任何东西,都支持选择。对于列表或数组,选择标准是根据每个单独的元素进行评估的。针对映射,根据每个映射条目(Java 类型`Map.Entry`的对象)评估选择条件。每个 map 条目都有其`key`和`value`可作为在选择中使用的属性访问。

下面的表达式返回一个新的映射,该映射由原始映射的那些元素组成,其中条目的值小于 27:

Java

```
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
```

Kotlin

```
val newMap = parser.parseExpression("map.?[value<27]").getValue()
```

除了返回所有选定的元素外,你还可以只检索第一个或最后一个元素。要获得与所选内容匹配的第一个元素,语法为`.^[selectionExpression]`。要获得最后一个匹配的选择,语法是`.$[selectionExpression]`。

#### 4.3.17.集合投影

投影让集合驱动子表达式的求值,其结果是一个新的集合。投影的语法是`.![projectionExpression]`。例如,假设我们有一个发明家名单,但想要他们出生的城市名单。实际上,我们希望对发明家列表中的每个条目进行“出生地.城市”评估。下面的示例使用投影来实现这一点:

Java

```
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
```

Kotlin

```
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
```

对于实现`java.lang.Iterable`或`java.util.Map`的数组和任何东西,都支持投影。当使用映射来驱动投影时,该投影表达式针对映射中的每个条目进行求值(表示为 Java`Map.Entry`)。在映射上进行投影的结果是一个列表,该列表由针对每个映射条目的投影表达式的求值组成。

#### 4.3.18.表达式模板化

表达式模板允许将文字与一个或多个求值块混合。每个求值块都用你可以定义的前缀和后缀字符分隔。一个常见的选择是使用`#{ }`作为分隔符,如下例所示:

Java

```
String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);

// evaluates to "random number is 0.7038186818312008"
```

Kotlin

```
val randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        TemplateParserContext()).getValue(String::class.java)

// evaluates to "random number is 0.7038186818312008"
```

字符串的计算方法是将文本`'random number is '`与在`#{ }`分隔符内计算表达式的结果连接起来(在这种情况下,调用`random()`方法的结果)。`parseExpression()`方法的第二个参数类型为`ParserContext`。`ParserContext`接口用于影响表达式的解析方式,以支持表达式模板功能。`TemplateParserContext`的定义如下:

Java

```
public class TemplateParserContext implements ParserContext {

    public String getExpressionPrefix() {
        return "#{";
    }

    public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}
```

Kotlin

```
class TemplateParserContext : ParserContext {

    override fun getExpressionPrefix(): String {
        return "#{"
    }

    override fun getExpressionSuffix(): String {
        return "}"
    }

    override fun isTemplate(): Boolean {
        return true
    }
}
```

### 4.4.示例中使用的类

这一节列出了在这一章的例子中使用的类。

Inventor.java

```
package org.spring.samples.spel.inventor;

import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

    private String name;
    private String nationality;
    private String[] inventions;
    private Date birthdate;
    private PlaceOfBirth placeOfBirth;

    public Inventor(String name, String nationality) {
        GregorianCalendar c= new GregorianCalendar();
        this.name = name;
        this.nationality = nationality;
        this.birthdate = c.getTime();
    }

    public Inventor(String name, Date birthdate, String nationality) {
        this.name = name;
        this.nationality = nationality;
        this.birthdate = birthdate;
    }

    public Inventor() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }

    public Date getBirthdate() {
        return birthdate;
    }

    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    public PlaceOfBirth getPlaceOfBirth() {
        return placeOfBirth;
    }

    public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
        this.placeOfBirth = placeOfBirth;
    }

    public void setInventions(String[] inventions) {
        this.inventions = inventions;
    }

    public String[] getInventions() {
        return inventions;
    }
}
```

Inventor.kt

```
class Inventor(
    var name: String,
    var nationality: String,
    var inventions: Array<String>? = null,
    var birthdate: Date =  GregorianCalendar().time,
    var placeOfBirth: PlaceOfBirth? = null)
```

placeofbirth.java

```
package org.spring.samples.spel.inventor;

public class PlaceOfBirth {

    private String city;
    private String country;

    public PlaceOfBirth(String city) {
        this.city=city;
    }

    public PlaceOfBirth(String city, String country) {
        this(city);
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String s) {
        this.city = s;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}
```

PlaceofBirth.kt

```
class PlaceOfBirth(var city: String, var country: String? = null) {
```

Society.java

```
package org.spring.samples.spel.inventor;

import java.util.*;

public class Society {

    private String name;

    public static String Advisors = "advisors";
    public static String President = "president";

    private List<Inventor> members = new ArrayList<Inventor>();
    private Map officers = new HashMap();

    public List getMembers() {
        return members;
    }

    public Map getOfficers() {
        return officers;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isMember(String name) {
        for (Inventor inventor : members) {
            if (inventor.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }
}
```

Society.kt

```
package org.spring.samples.spel.inventor

import java.util.*

class Society {

    val Advisors = "advisors"
    val President = "president"

    var name: String? = null

    val members = ArrayList<Inventor>()
    val officers = mapOf<Any, Any>()

    fun isMember(name: String): Boolean {
        for (inventor in members) {
            if (inventor.name == name) {
                return true
            }
        }
        return false
    }
}
```

## 5. Spring 面向方面的编程

AOP 面向方面编程(Aspect-oriented Programming,151)通过提供关于程序结构的另一种思考方式,补充了面向对象编程。在 OOP 中,模块化的关键单元是类,而在 AOP 中,模块化的单元是方面。方面支持跨多个类型和对象的关注(例如事务管理)的模块化。(在文献 AOP 中,这类担忧通常被称为“跨领域”担忧。

Spring 的关键组件之一是 AOP 框架。虽然 Spring IOC 容器不依赖于 AOP(这意味着如果你不想使用 AOP),但 AOP 补充了 Spring IOC 以提供非常有能力的中间件解决方案。

Spring  AOP 与 AspectJ 切入点

Spring 通过使用[基于模式的方法](#aop-schema)或[@AspectJ 注释样式](#aop-ataspectj)提供了编写自定义方面的简单而强大的方法。这两种样式都提供完全类型的建议和 AspectJ PointCut 语言的使用,同时仍然使用 Spring  AOP 进行编织。

本章讨论了基于模式和 @AspectJ 的支持 AOP。较低级别的 AOP 支持在[接下来的一章](#aop-api)中进行了讨论。

AOP 在 Spring 框架中用于:

* 提供声明式 Enterprise 服务。最重要的这类服务是[声明式事务管理](data-access.html#transaction-declarative)。

* 让用户实现自定义方面,用 AOP 补充他们对 OOP 的使用。

|   |如果你只对通用声明性服务或其他预打包的<br/>声明性中间件服务如池感兴趣,则不需要直接使用<br/> Spring  AOP,并且可以跳过本章的大部分内容。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 5.1. AOP 概念

让我们首先定义一些核心的概念和术语。这些术语不是 Spring 特定的。遗憾的是, AOP 术语并不是特别直观。然而,如果 Spring 使用自己的术语,那将更加令人困惑。

* 方面:跨多个类的关注的模块化。事务管理是 EnterpriseJava 应用程序中横切关注点的一个很好的示例。在 Spring  AOP 中,方面是通过使用普通类([基于模式的方法](#aop-schema))或使用`@Aspect`注释的普通类([@AspectJ Style](#aop-ataspectj))来实现的。

* 连接点:程序执行过程中的一个点,如方法的执行或异常的处理。在 Spring  AOP 中,连接点总是表示方法的执行。

* 建议:某一方面在某一特定连接点上采取的行动。不同类型的建议包括“周围”、“之前”和“之后”的建议。(通知类型将在后面讨论。)许多 AOP 框架(包括 Spring)将通知建模为拦截器,并在连接点周围维护拦截器的链。

* 切入点:匹配连接点的谓词。通知与切入点表达式关联,并在与切入点匹配的任何连接点上运行(例如,执行具有特定名称的方法)。由切入点表达式匹配的连接点的概念是 AOP 的核心,并且 Spring 默认情况下使用 AspectJ PointCut 表达式语言。

* 介绍:代表类型声明附加的方法或字段。 Spring  AOP 让你将新的接口(和相应的实现)引入到任何建议的对象。例如,你可以使用一个介绍来实现一个 Bean 实现`IsModified`接口,以简化缓存。(在 AspectJ 社区中,介绍称为类型间声明。

* 目标对象:由一个或多个方面提供建议的对象。也称为“已知对象”。由于 Spring  AOP 是通过使用运行时代理来实现的,因此该对象始终是代理对象。

* AOP 代理:由 AOP 框架创建的一个对象,以便实现方面契约(通知方法执行等)。在 Spring 框架中, AOP 代理是 JDK 动态代理或 CGLIB 代理。

* 编织:将方面与其他应用程序类型或对象连接起来,以创建一个建议的对象。这可以在编译时(例如使用 AspectJ 编译器)、加载时或运行时完成。 Spring  AOP 与其他纯 爪哇 AOP 框架一样,在运行时执行编织。

Spring  AOP 包括以下类型的建议:

* 建议之前:在连接点之前运行的建议,但不具有阻止执行流继续到连接点的能力(除非抛出异常)。

* 返回建议后:在连接点正常完成后运行的建议(例如,如果方法返回时没有抛出异常)。

* 抛出建议后:如果方法通过抛出异常退出,则要运行建议。

* 在(最后)建议之后:无论连接点以何种方式退出(正常或异常返回),都要运行建议。

* 围绕建议:围绕连接点(如方法调用)的建议。这是最有力的建议。建议可以在方法调用之前和之后执行自定义行为。它还负责选择是继续进行连接点,还是通过返回自己的返回值或抛出异常来快捷所建议的方法执行。

围绕建议的建议是最普遍的一种建议。由于 Spring  AOP 与 AspectJ 一样,提供了一系列完整的建议类型,因此我们建议你使用能够实现所需行为的功能最小的建议类型。例如,如果你只需要用一个方法的返回值来更新一个缓存,那么实现一个返回后的建议比一个环绕建议要好,尽管环绕建议可以完成同样的事情。使用最特定的建议类型可以提供一个更简单的编程模型,并且出错的可能性更小。例如,你不需要在用于 around advice 的`JoinPoint`上调用`proceed()`方法,因此,你不能失败地调用它。

所有的通知参数都是静态类型的,因此你可以使用适当类型的通知参数(例如,方法执行中返回值的类型),而不是`Object`数组。

由切入点匹配的连接点的概念是 AOP 的关键,它区别于仅提供截取的旧技术。切入点使建议能够独立于面向对象的层次结构而具有针对性。例如,你可以将一个提供声明性事务管理的建议应用于一组跨越多个对象的方法(例如服务层中的所有业务操作)。

### 5.2. Spring  AOP 能力和目标

Spring  AOP 是用纯 爪哇 实现的。不需要特殊的编译过程。 Spring  AOP 不需要控制类装入器层次结构,因此适合在 Servlet 容器或应用服务器中使用。

Spring  AOP 目前仅支持方法执行连接点(建议在 Spring bean 上执行方法)。未实现字段截取,尽管可以在不破坏核心 Spring  AOP API 的情况下添加对字段截取的支持。如果需要建议字段访问和更新连接点,请考虑使用 AspectJ 之类的语言。

Spring  AOP 对 AOP 的方法与大多数其他 AOP 框架的方法不同。目的不是提供最完整的 AOP 实现(尽管 Spring  AOP 相当有能力)。相反,目的是提供 AOP 实现和 Spring IOC 之间的紧密集成,以帮助解决 Enterprise 应用程序中的常见问题。

因此,例如, Spring 框架的 AOP 功能通常与 Spring IOC 容器一起使用。方面是通过使用正常的 Bean 定义语法来配置的(尽管这允许强大的“自动代理”功能)。这是与其他 AOP 实现方式的一个关键区别。对于 Spring  AOP,你无法轻松或有效地完成某些事情,例如建议非常细粒度的对象(通常是域对象)。AspectJ 是这种情况下的最佳选择。然而,我们的经验是 Spring  AOP 为 Enterprise爪哇 应用程序中的大多数问题提供了极好的解决方案,而这些问题是 AOP 所允许的。

Spring  AOP 从不努力与 AspectJ 竞争以提供全面的 AOP 解决方案。我们认为, Spring  AOP 这样的基于代理的框架和 AspectJ 这样的成熟框架都是有价值的,它们是互补的,而不是竞争的。 Spring 将 Spring  AOP 和 IoC 与 AspectJ 无缝集成,以使 AOP 在一致的基于 Spring 的应用程序体系结构中的所有使用成为可能。这种集成不会影响 Spring  AOP API 或 AOP Alliance API。 Spring  AOP 保持向后兼容。有关 Spring  AOP API 的讨论,请参见[接下来的一章](#aop-api)。

|   |Spring 框架的核心原则之一是非侵犯性。这<br/>是这样一种想法,即你不应该被迫将特定于框架的类和<br/>接口引入到你的业务或域模型中。然而,在某些地方, Spring Framework<br/>确实为你提供了在<br/>代码库中引入 Spring 特定于 Framework 的依赖项的选项。为你提供此类选项的理由是,在某些情况下,<br/>可能只是更容易阅读或以<br/>的方式编码某些特定的功能。然而, Spring 框架(几乎)总是为你提供选择:你有<br/>做出明智决定的自由,即哪个选项最适合你的特定使用<br/>情况或场景。<br/><br/>与本章相关的一个这样的选择是选择哪种 AOP 框架(和<br/>哪种 AOP 样式)。你可以选择 AspectJ、 Spring  AOP 或两者。你<br/>还可以选择 @AspectJ 注释样式方法或 Spring XML<br/>配置样式方法。本章选择首先介绍<br/>@AspectJ-style 方法,这一事实不应被视为表明 Spring 团队<br/>更喜欢 @AspectJ 注释式方法,而不是 Spring XML 配置风格。<br/><br/>关于[Choosing which AOP Declaration Style to Use](#aop-choosing)每种风格的“为什么和原因”的更完整讨论。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 5.3. AOP 代理

Spring  AOP 对于 AOP 代理,默认使用标准的 JDK 动态代理。这使得可以代理任何接口(或一组接口)。

Spring  AOP 还可以使用 CGLIB 代理。这对于代理类而不是接口是必需的。默认情况下,如果业务对象不实现接口,则使用 CGLIB。由于按接口而不是按类编程是一种很好的做法,所以业务类通常实现一个或多个业务接口。在以下情况下,[强制使用 CGlib](#aop-proxying)是可能的:你需要建议一个未在接口上声明的方法,或者你需要将代理对象作为具体类型传递给方法。

理解 Spring  AOP 是基于代理的这一事实是很重要的。请参阅[Understanding AOP Proxies](#aop-understanding-aop-proxies),以全面了解此实现细节的实际含义。

### 5.4.@AspectJ 支持

@AspectJ 引用了一种将方面声明为带有注释的常规 爪哇 类的风格。@AspectJ 样式是由[AspectJ 项目](https://www.eclipse.org/aspectj)作为 AspectJ5 版本的一部分引入的。 Spring 使用 AspectJ 提供的用于切入点解析和匹配的库来解释与 AspectJ5 相同的注释。然而, AOP 运行时仍然是纯的 Spring  AOP,并且对 AspectJ 编译器或 Weaver 没有依赖关系。

|   |使用 AspectJ 编译器和 Weaver 可以使用完整的 AspectJ 语言,<br/>在[Using AspectJ with Spring Applications](#aop-using-aspectj)中进行了讨论。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 5.4.1.启用 @AspectJ 支持

要在 Spring 配置中使用 @AspectJ 方面,你需要启用 Spring 支持,以便基于 @AspectJ 方面配置 Spring  AOP,并根据这些方面是否建议使用自动代理 bean。通过自动代理,我们的意思是,如果 Spring 确定 Bean 是由一个或多个方面建议的,则它会自动为该 Bean 生成代理,以拦截方法调用并确保根据需要运行建议。

可以通过 XML 或 爪哇 风格的配置来启用 @AspectJ 支持。在这两种情况下,你都需要确保 AspectJ 的`aspectjweaver.jar`库位于应用程序的 Classpath(版本 1.8 或更高版本)上。这个库可以在 AspectJ 发行版的`lib`目录中使用,也可以从 Maven 中央存储库中使用。

#####  通过 爪哇 配置启用 @AspectJ 支持

要使用 爪哇`@Configuration`启用 @AspectJ 支持,请添加`@EnableAspectJAutoProxy`注释,如下例所示:

爪哇

```
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
```

Kotlin

```
@Configuration
@EnableAspectJAutoProxy
class AppConfig
```

#####  通过 XML 配置启用 @AspectJ 支持

要通过基于 XML 的配置启用 @AspectJ 支持,请使用`aop:aspectj-autoproxy`元素,如下例所示:

```
<aop:aspectj-autoproxy/>
```

这假定你使用[基于 XML 模式的配置](#xsd-schemas)中描述的模式支持。有关如何导入`aop`命名空间中的标记,请参见[the AOP schema](#xsd-schemas-aop)。

#### 5.4.2.声明一个方面

启用了 @AspectJ 支持后, Spring 将自动检测 Bean 在应用程序上下文中定义的具有 @AspectJ 方面的类(具有`@Aspect`注释)的任何 Bean,并将其用于配置 Spring  AOP。接下来的两个示例展示了一个不太有用的方面所需的最小定义。

这两个示例中的第一个示例显示了应用程序上下文中的常规 Bean 定义,该定义指向具有`@Aspect`注释的 Bean 类:

```
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of the aspect here -->
</bean>
```

两个示例中的第二个示例显示了`NotVeryUsefulAspect`类定义,该定义使用`org.aspectj.lang.annotation.Aspect`注释;

爪哇

```
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}
```

Kotlin

```
package org.xyz

import org.aspectj.lang.annotation.Aspect;

@Aspect
class NotVeryUsefulAspect
```

方面(用`@Aspect`注释的类)可以具有方法和字段,与任何其他类相同。它们还可以包含切入点、建议和 Introduction(类型间)声明。

|   |通过组件扫描自动检测方面<br/><br/>你可以在 Spring XML 配置中将方面类注册为常规 bean,<br/>通过`@Bean`类中的`@Configuration`方法,或者通过<br/> Classpath 扫描使 Spring 自动检测它们——与任何其他 Spring 管理的 Bean 相同。然而,注意`@Aspect`注释对于 Classpath 中的自动检测是不够的。为了达到<br/>的目的,你需要添加一个单独的`@Component`注释(或者,根据 Spring 的组件扫描仪的规则,添加一个自定义的<br/>原型注释)。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |与其他方面的建议方面?<br/><br/>在 Spring  AOP 中,方面本身不能成为来自其他方面的建议的目标。类上的`@Aspect`注释将其标记为一个方面,因此将<br/>从自动代理中排除。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 5.4.3.声明切入点

切入点确定感兴趣的连接点,从而使我们能够控制通知何时运行。 Spring  AOP 仅支持 Spring bean 的方法执行连接点,因此可以将切入点视为匹配 Spring bean 上方法的执行。切入点声明有两个部分:一个包含名称和任何参数的签名,以及一个切入点表达式,该表达式确定我们感兴趣的确切方法执行。在 AOP 的 @AspectJ 注释样式中,切入点签名是由一个常规方法定义提供的,切入点表达式是通过使用`@Pointcut`注释来指示的(充当切入点签名的方法必须具有`void`返回类型)。

示例可能有助于明确切入点签名和切入点表达式之间的区别。下面的示例定义了一个名为`anyOldTransfer`的切入点,该切入点与任何名为`transfer`的方法的执行相匹配:

爪哇

```
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
```

Kotlin

```
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature
```

形成`@Pointcut`注释的值的 pointcut 表达式是一个正则 AspectJ pointcut 表达式。有关 AspectJ 的切入点语言的完整讨论,请参见[AspectJ 编程指南](https://www.eclipse.org/aspectj/doc/released/progguide/index.html)(以及,对于扩展,[AspectJ5 开发者笔记本](https://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html))或 AspectJ 上的一本书(例如 Colyer 等人的*Eclipse AspectJ*,或 Ramnivas Laddad 的*AspectJ 在行动*)。

#####  支持的切入点指示符

Spring  AOP 支持在切入点表达式中使用以下 AspectJ 切入点指示器:

* `execution`:用于匹配方法执行连接点。这是在使用 Spring  AOP 时要使用的主要切入点指示器。

* `within`:对某些类型内的连接点的匹配进行限制(在使用 Spring  AOP 时在匹配类型内声明的方法的执行)。

* `this`:限制与连接点的匹配(在使用 Spring  AOP 时执行方法),其中 Bean 引用( Spring  AOP 代理)是给定类型的实例。

* `target`:限制与连接点的匹配(在使用 Spring  AOP 时执行方法),其中目标对象(正在代理的应用程序对象)是给定类型的实例。

* `args`:限制与连接点的匹配(在使用 Spring  AOP 时方法的执行),其中参数是给定类型的实例。

* `@target`:限制与连接点的匹配(在使用 Spring  AOP 时方法的执行),其中执行对象的类具有给定类型的注释。

* `@args`:限制与连接点的匹配(在使用 Spring  AOP 时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。

* `@within`:限制与具有给定注释的类型内的连接点的匹配(在使用 Spring  AOP 时,在具有给定注释的类型中声明的方法的执行)。

* `@annotation`:将匹配限制为连接点的主题(在 Spring  AOP 中运行的方法)具有给定注释的连接点。

其他切入点类型

完整的 AspectJ PointCut 语言支持在 Spring 中不支持的额外的 PointCut 指示器:,,,,,,,,,“4862”gt=“/>,”4862“gt=”/>,“4864”R=“,”<gt="R="/>,“和”4864“R=”>。“<gt=">在由 Spring  AOP 解释的切入点表达式中使用这些切入点指示符将导致抛出`IllegalArgumentException`。

Spring  AOP 支持的一组切入点指示器可以在未来的版本中进行扩展,以支持更多的 AspectJ 切入点指示器。

因为 Spring  AOP 将匹配限制为仅与方法执行连接点匹配,所以前面对切入点指示器的讨论给出了比 AspectJ 编程指南中所能找到的更窄的定义。此外,AspectJ 本身具有基于类型的语义,并且在执行连接点,`this`和`target`都指向相同的对象:执行方法的对象。 Spring  AOP 是一种基于代理的系统,并在代理对象本身(其被绑定到)和代理背后的目标对象(其被绑定到)之间进行区分。

|   |由于 Spring 的 AOP 框架的基于代理的性质,根据定义,目标对象<br/>内的调用不会被拦截。对于 JDK 代理,只能拦截代理上的公共接口方法<br/>调用。使用 CGlib,对<br/>代理的公共和受保护方法调用将被截获(如果需要,甚至还会截获包可见的方法)。但是,<br/>通过代理进行的公共交互应该始终通过公共签名来设计。<br/>注意,切入点定义通常与任何截获的方法相匹配。<br/>如果切入点严格地说是仅用于公共的,即使在 CGLIB 代理场景中,<br/>通过代理进行的潜在的非公共交互,<br/><br/>如果你的拦截需要在目标<br/>类中包括方法调用甚至构造函数,请考虑使用 Spring 驱动的[原生 AspectJ 编织](#aop-aj-ltw)而不是 Spring 的基于代理的 AOP 框架的<br/>。这构成了具有不同特征的 AOP 使用<br/>的不同模式,因此在做出决定之前,一定要让自己熟悉编织<br/>。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Spring  AOP 还支持名为`bean`的附加 PCD。此 PCD 允许你将连接点的匹配限制为特定的命名 Spring  Bean 或命名的 Spring bean 集合(当使用通配符时)。`bean`PCD 具有以下形式:

爪哇

```
bean(idOrNameOfBean)
```

Kotlin

```
bean(idOrNameOfBean)
```

`idOrNameOfBean`令牌可以是任何 Spring  Bean 的名称。提供了使用`*`字符的有限通配符支持,因此,如果你为 Spring bean 建立了一些命名约定,则可以编写`bean`PCD 表达式来选择它们。与其他切入点指示符的情况一样,`bean`PCD 也可以与`&&`(和)、`||`(或)和`!`(否定)操作符一起使用。

|   |`bean`PCD 仅在 Spring  AOP 中支持,而在<br/>本地 AspectJ 编织中不支持。它是标准 PCD 的 Spring 特定扩展,<br/>AspectJ 定义了该扩展,因此,不可用对于在`@Aspect`模型中声明的方面。<br/><br/>`bean`PCD 在实例级运行(构建在 Spring  Bean 名称<br/>概念上)<br/>基于实例的切入点指示器是 Spring 的<br/>基于代理的 AOP 框架及其与 Spring  Bean 工厂(其中<br/>工厂)的紧密集成的一种特殊功能。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  结合切入点表达式

可以使用`&&,``||`和`!`合并切入点表达式。你也可以按名称引用切入点表达式。下面的示例显示了三个切入点表达式:

爪哇

```
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} (1)

@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} (2)

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} (3)
```

|**1**|如果方法执行连接点表示任何公共方法的执行<br/>,则`anyPublicOperation`匹配。|
|-----|----------------------------------------------------------------------------------------------------------------|
|**2**|如果方法执行在交易模块中,则`inTrading`匹配。|
|**3**|如果方法执行表示<br/>交易模块中的任何公共方法,则`tradingOperation`匹配。|

Kotlin

```
@Pointcut("execution(public * *(..))")
private fun anyPublicOperation() {} (1)

@Pointcut("within(com.xyz.myapp.trading..*)")
private fun inTrading() {} (2)

@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {} (3)
```

|**1**|如果方法执行连接点表示任何公共方法的执行<br/>,则`anyPublicOperation`匹配。|
|-----|----------------------------------------------------------------------------------------------------------------|
|**2**|如果方法执行在交易模块中,则`inTrading`匹配。|
|**3**|如果方法执行表示<br/>交易模块中的任何公共方法,则`tradingOperation`匹配。|

正如前面所示,从较小的命名组件中构建更复杂的切入点表达式是一种最佳实践。当按名称引用切入点时,将应用普通的 爪哇 可见性规则(你可以看到相同类型的私有切入点,层次结构中的受保护切入点,任何地方的公共切入点,等等)。可见性不影响切入点匹配。

#####  共享公共切入点定义

在使用 Enterprise 应用程序时,开发人员通常希望从几个方面引用应用程序的模块和特定的操作集。我们建议为此目的定义一个`CommonPointcuts`方面来捕获公共切入点表达式。这样的方面通常类似于以下示例:

爪哇

```
package com.xyz.myapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CommonPointcuts {

    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.xyz.myapp.web package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.web..*)")
    public void inWebLayer() {}

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.xyz.myapp.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.service..*)")
    public void inServiceLayer() {}

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.xyz.myapp.dao package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     *
     * If you group service interfaces by functional area (for example,
     * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
     * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
     * could be used instead.
     *
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    public void businessService() {}

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}
```

Kotlin

```
package com.xyz.myapp

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut

@Aspect
class CommonPointcuts {

    /**
    * A join point is in the web layer if the method is defined
    * in a type in the com.xyz.myapp.web package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.web..*)")
    fun inWebLayer() {
    }

    /**
    * A join point is in the service layer if the method is defined
    * in a type in the com.xyz.myapp.service package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.service..*)")
    fun inServiceLayer() {
    }

    /**
    * A join point is in the data access layer if the method is defined
    * in a type in the com.xyz.myapp.dao package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    fun inDataAccessLayer() {
    }

    /**
    * A business service is the execution of any method defined on a service
    * interface. This definition assumes that interfaces are placed in the
    * "service" package, and that implementation types are in sub-packages.
    *
    * If you group service interfaces by functional area (for example,
    * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
    * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
    * could be used instead.
    *
    * Alternatively, you can write the expression using the 'bean'
    * PCD, like so "bean(*Service)". (This assumes that you have
    * named your Spring service beans in a consistent fashion.)
    */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    fun businessService() {
    }

    /**
    * A data access operation is the execution of any method defined on a
    * dao interface. This definition assumes that interfaces are placed in the
    * "dao" package, and that implementation types are in sub-packages.
    */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    fun dataAccessOperation() {
    }

}
```

你可以在需要切入点表达式的任何地方引用在这样的方面中定义的切入点。例如,要使服务层具有事务性,你可以编写以下内容:

```
<aop:config>
    <aop:advisor
        pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
        advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
```

`<aop:config>`和`<aop:advisor>`元素在[Schema-based AOP Support](#aop-schema)中讨论。事务元素在[事务管理](data-access.html#transaction)中讨论。

#####  例子

Spring  AOP 用户很可能最常使用`execution`切入点指示符。执行表达式的格式如下:

```
    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                throws-pattern?)
```

除了返回类型模式(`ret-type-pattern`在前面的代码片段中)、名称模式和参数模式之外的所有部分都是可选的。返回类型模式决定了方法的返回类型必须是什么,才能匹配连接点。`*`是最常用的返回类型模式。它匹配任何返回类型。只有当方法返回给定的类型时,完全限定的类型名才匹配。名称模式与方法名匹配。可以使用`*`通配符作为名称模式的全部或部分。如果指定了声明类型模式,请包含一个尾随`.`,以将其连接到名称模式组件。参数模式稍微复杂一些:`()`匹配不接受参数的方法,而`(..)`匹配任意数量(零或更多)的参数。`(*)`模式匹配一个接受任何类型的参数的方法。`(*,String)`匹配一个接受两个参数的方法。第一个可以是任何类型,而第二个必须是`String`。有关更多信息,请参阅 AspectJ 编程指南的[语言语义学](https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html)部分。

以下示例展示了一些常见的切入点表达式:

* 任何公开方式的执行:

  ```
      execution(public * *(..))
  ```

* 执行名称以`set`开头的任何方法:

  ```
      execution(* set*(..))
  ```

* 由`AccountService`接口定义的任何方法的执行:

  ```
      execution(* com.xyz.service.AccountService.*(..))
  ```

* 在`service`包中定义的任何方法的执行:

  ```
      execution(* com.xyz.service.*.*(..))
  ```

* 在服务包或其子包中定义的任何方法的执行:

  ```
      execution(* com.xyz.service..*.*(..))
  ```

* 服务包中的任何连接点(仅在 Spring  AOP 中执行方法):

  ```
      within(com.xyz.service.*)
  ```

* 服务包或其子包中的任何连接点(方法仅在 Spring  AOP 中执行):

  ```
      within(com.xyz.service..*)
  ```

* 代理实现`AccountService`接口的任何连接点(仅在 Spring  AOP 中执行方法):

  ```
      this(com.xyz.service.AccountService)
  ```

  |   |`this`更常用的是绑定形式。有关如何使代理对象在建议主体中可用,请参见[声明建议](#aop-advice)一节。|
  |---|----------------------------------------------------------------------------------------------------------------------------------------------------------------|

* 目标对象实现`AccountService`接口的任何连接点(仅在 Spring  AOP 中执行方法):

  ```
      target(com.xyz.service.AccountService)
  ```

  |   |`target`更常用的是绑定形式。关于如何使目标对象在建议主体中可用,请参见[声明建议](#aop-advice)节<br/>。|
  |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|

* 获取单个参数并且在运行时传递的参数是`Serializable`的任何连接点(仅在 Spring  AOP 中执行方法):

  ```
      args(java.io.Serializable)
  ```

  |   |`args`更常用的是绑定形式。有关如何使方法参数在建议主体中可用,请参见[声明建议](#aop-advice)节<br/>。|
  |---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|

  请注意,本例中给出的切入点与`execution(* *(java.io.Serializable))`不同。如果在运行时传递的参数是`Serializable`,则 ARGS 版本匹配,如果方法签名声明一个类型为`Serializable`的参数,则执行版本匹配。

* 任何连接点(仅在 Spring  AOP 中执行方法),其中目标对象具有`@Transactional`注释:

  ```
      @target(org.springframework.transaction.annotation.Transactional)
  ```

  |   |也可以在绑定形式中使用`@target`。关于<br/>如何使注释对象在建议正文中可用,请参见[声明建议](#aop-advice)小节。|
  |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|

* 任何连接点(仅在 Spring  AOP 中执行方法),其中目标对象的声明类型具有`@Transactional`注释:

  ```
      @within(org.springframework.transaction.annotation.Transactional)
  ```

  |   |也可以在绑定形式中使用`@within`。关于<br/>如何使注释对象在建议正文中可用,请参见[声明建议](#aop-advice)小节。|
  |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|

* 任何连接点(仅在 Spring  AOP 中执行方法),其中执行方法具有`@Transactional`注释:

  ```
      @annotation(org.springframework.transaction.annotation.Transactional)
  ```

  |   |也可以在绑定形式中使用`@annotation`。请参阅[声明建议](#aop-advice)节<br/>,以了解如何使注释对象在建议主体中可用。|
  |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

* 任何接受单个参数的连接点(仅在 Spring  AOP 中执行方法),其中传递的参数的运行时类型具有`@Classified`注释:

  ```
      @args(com.xyz.security.Classified)
  ```

  |   |也可以在绑定形式中使用`@args`。参见[声明建议](#aop-advice)部分<br/>如何在建议主体中提供注释对象。|
  |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------|

* 名为`tradeService`的 Spring  Bean 上的任何连接点(方法仅在 Spring  AOP 中执行):

  ```
      bean(tradeService)
  ```

* 具有与通配符表达式`*Service`匹配的名称的 Spring bean 上的任何连接点(方法仅在 Spring  AOP 中执行):

  ```
      bean(*Service)
  ```

#####  写出好的切入点

在编译过程中,AspectJ 处理切入点以优化匹配性能。检查代码并确定每个连接点是否(静态或动态地)匹配给定的切入点是一个昂贵的过程。(动态匹配意味着不能从静态分析中完全确定匹配,并且在代码中进行测试以确定在代码运行时是否存在实际匹配)。在第一次遇到切入点声明时,AspectJ 将其重写为匹配过程的最佳形式。这是什么意思?基本上,切入点被重写为 DNF(析取范式),切入点的组件被排序,以便首先检查那些更便宜的评估组件。这意味着你不必担心理解各种切入点指示器的性能,并且可以在切入点声明中以任何顺序提供它们。

然而,AspectJ 只能根据所告知的内容工作。为了获得最佳的匹配性能,你应该考虑他们试图实现的目标,并在定义中尽可能地缩小匹配的搜索空间。现有的指示器自然分为以下三类:Kinded、范围界定和上下文关联:

* Kinded 指示器选择特定类型的连接点:`execution`,`get`,`set`,`call`,和`handler`。

* 范围指示器选择一组感兴趣的连接点(可能有多种):`within`和`withincode`

* 上下文指示符基于上下文匹配(并可选地绑定):`this`,`target`和`@annotation`

写得好的切入点至少应该包括前两种类型(Kinded 和 Scoping)。你可以包括上下文指示符,以便根据连接点上下文进行匹配,也可以将该上下文绑定,以便在建议中使用。仅提供 Kinded 指示器或仅提供上下文指示器可以工作,但由于额外的处理和分析,可能会影响编织性能(使用的时间和内存)。范围指示器的匹配速度非常快,使用它们意味着 AspectJ 可以非常快地删除不应进一步处理的连接点组。一个好的切入点应该总是包括一个,如果可能的话。

#### 5.4.4.声明建议

通知与切入点表达式相关联,并在切入点匹配的方法执行之前、之后或周围运行。PointCut 表达式可以是对已命名的 PointCut 的简单引用,也可以是已声明的 PointCut 表达式。

#####  建议之前

你可以使用`@Before`注释在一个方面的建议之前声明:

爪哇

```
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
```

Kotlin

```
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before

@Aspect
class BeforeExample {

    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}
```

如果我们使用一个就地切入点表达式,我们可以将前面的示例重写为下面的示例:

爪哇

```
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }
}
```

Kotlin

```
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before

@Aspect
class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    fun doAccessCheck() {
        // ...
    }
}
```

#####  在返回建议后

返回通知后,当匹配的方法执行正常返回时运行。你可以使用`@AfterReturning`注释来声明它:

爪哇

```
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
```

Kotlin

```
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning

@Aspect
class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}
```

|   |你可以在同一个方面中拥有多个通知声明(以及其他成员)<br/>。在这些<br/>示例中,我们只显示了一个通知声明,以集中显示每个通知声明的效果。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

有时,你需要在建议主体中访问返回的实际值。可以使用绑定返回值的`@AfterReturning`形式来获得访问权限,如下例所示:

爪哇

```
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }
}
```

Kotlin

```
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning

@Aspect
class AfterReturningExample {

    @AfterReturning(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning = "retVal")
    fun doAccessCheck(retVal: Any) {
        // ...
    }
}
```

`returning`属性中使用的名称必须与通知方法中的参数名称对应。当方法执行返回时,返回值将作为相应的参数值传递给通知方法。`returning`子句还限制只匹配那些返回指定类型的值的方法执行(在本例中,`Object`,它匹配任何返回值)。

请注意,在返回建议后使用时,不可能返回完全不同的参考。

#####  在提出建议之后

抛出建议后,当匹配的方法执行通过抛出异常退出时,将运行该建议。你可以使用`@AfterThrowing`注释来声明它,如下例所示:

爪哇

```
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }
}
```

Kotlin

```
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing

@Aspect
class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doRecoveryActions() {
        // ...
    }
}
```

通常,你希望仅在抛出给定类型的异常时才运行该建议,并且你还经常需要访问建议主体中抛出的异常。你可以使用`throwing`属性来限制匹配(如果需要的话,使用`Throwable`作为异常类型),并将抛出的异常绑定到一个通知参数。下面的示例展示了如何做到这一点:

爪哇

```
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
}
```

Kotlin

```
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing

@Aspect
class AfterThrowingExample {

    @AfterThrowing(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing = "ex")
    fun doRecoveryActions(ex: DataAccessException) {
        // ...
    }
}
```

`throwing`属性中使用的名称必须与通知方法中的参数名称对应。当一个方法通过抛出异常而退出执行时,该异常将作为相应的参数值传递给通知方法。`throwing`子句还限制只匹配那些抛出指定类型异常的方法执行(在本例中为`DataAccessException`)。

|   |注意,`@AfterThrowing`并不表示一般的异常处理回调。<br/>具体地说,一个`@AfterThrowing`通知方法只应该从联结点(用户声明的目标方法)本身接收异常<br/>,而不是从伴随的`@After`方法接收异常。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####   建议

当匹配的方法执行退出时,After(最终)通知将运行。它是通过使用`@After`注释声明的。建议后必须准备好处理正常和异常返回条件。它通常用于释放资源和类似的目的。下面的示例展示了如何在“最终建议”之后使用它:

爪哇

```
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }
}
```

Kotlin

```
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After

@Aspect
class AfterFinallyExample {

    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doReleaseLock() {
        // ...
    }
}
```

|   |请注意,AspectJ 中的`@After`通知被定义为“after finally advice”,类似于<br/>try-catch 语句中的 finally 块。它将针对任何结果被调用,<br/>正常返回或从连接点(用户声明的目标方法)抛出的异常,<br/>与`@AfterReturning`相反,后者仅适用于成功的正常返回。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  围绕建议

最后一种建议是*周围*建议。围绕建议运行“围绕”一个匹配的方法的执行。它有机会在方法运行之前和之后都进行工作,并确定何时、如何以及即使方法实际运行了。如果你需要以线程安全的方式共享方法执行前后的状态(例如,启动和停止计时器),通常会使用“周围建议”。

|   |始终使用最不强大的通知形式来满足你的要求。<br/><br/>例如,如果*周围*通知足以满足你的需求,则不要使用*在此之前*通知。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

通过对带有`@Around`注释的方法进行注释来声明“建议周围”。方法应该声明`Object`作为其返回类型,并且方法的第一个参数必须是`ProceedingJoinPoint`类型。在 advice 方法的主体中,你必须在`ProceedingJoinPoint`上调用`proceed()`才能运行底层方法。在没有参数的情况下调用`proceed()`将导致调用者的原始参数在调用时被提供给底层方法。对于高级用例,有一个重载的`proceed()`方法的变体,它接受一个参数数组(`Object[]`)。当调用底层方法时,数组中的值将被用作该方法的参数。

|   |当使用`Object[]`调用`proceed`时,<br/>的行为与 AspectJ 编译器编译的关于 around 建议的`proceed`的行为略有不同。对于使用传统 AspectJ 语言编写的关于<br/>的通知,传递到`proceed`的参数的数量必须与传递到 around 通知的参数的数量相匹配(而不是底层连接点的参数的数量<br/>),并且在<br/>给定的参数位置中传递以继续进行的值取代了<br/>值绑定到的实体的连接点上的原始值(不要担心)<br/><br/> Spring 采取的方法更简单,并且更好地匹配其基于代理的,<br/>仅执行的语义。你只需要在编译为 Spring 编写的`@AspectJ`方面并使用`proceed`与 AspectJ<br/>编译器和 Weaver 的参数时,才需要注意到这种差异。有一种方法可以在<br/> Spring  AOP 和 AspectJ 之间编写 100% 兼容的这样的方面,这在[以下关于建议参数的一节](#aop-ataspectj-advice-proceeding-with-the-call)中进行了讨论。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

around 通知返回的值是该方法的调用者看到的返回值。例如,一个简单的缓存方面可以从缓存返回一个值,如果它有一个值,或者调用`proceed()`(如果没有,则返回该值)。请注意,`proceed`可以在 around 建议的主体内被调用一次、多次或根本不调用。所有这些都是合法的。

|   |如果将 around advice 方法的返回类型声明为`void`,则`null`将始终返回给调用方,实际上忽略了<br/>of`proceed()`的任何调用的结果。因此,建议使用一个 around advice 方法声明一个<br/>类型的`Object`。通知方法通常应该返回从<br/>调用`proceed()`返回的值,即使底层方法具有`void`返回类型。<br/>但是,通知可以根据用例选择返回一个缓存的值、一个包装的值或其他<br/>值。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例展示了如何使用“周围建议”:

爪哇

```
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }
}
```

Kotlin

```
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint

@Aspect
class AroundExample {

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
        // start stopwatch
        val retVal = pjp.proceed()
        // stop stopwatch
        return retVal
    }
}
```

#####  建议参数

Spring 提供完全类型的建议,这意味着你在建议签名中声明所需的参数(正如我们在前面的返回和抛出示例中看到的那样),而不是始终使用`Object[]`数组。在这一节的后面部分,我们将看到如何使参数和其他上下文值可用于建议主体。首先,我们来看看如何编写通用的建议,以了解该建议目前建议的方法。

###### 访问当前`JoinPoint`

任何通知方法都可以声明类型`org.aspectj.lang.JoinPoint`的参数作为其第一个参数。注意,around advice 需要声明类型`ProceedingJoinPoint`的第一个参数,这是`JoinPoint`的子类。

`JoinPoint`接口提供了许多有用的方法:

* `getArgs()`:返回方法参数。

* `getThis()`:返回代理对象。

* `getTarget()`:返回目标对象。

* `getSignature()`:返回所建议方法的描述。

* `toString()`:打印所建议的方法的有用描述。

有关更多详细信息,请参见[javadoc](https://www.eclipse.org/aspectj/doc/released/runtime-api/org/aspectj/lang/JoinPoint.html)。

###### 将参数传递给建议

我们已经了解了如何绑定返回值或异常值(在返回后和抛出建议后使用)。要使参数值对建议主体可用,可以使用`args`的绑定形式。如果在`args`表达式中使用参数名代替类型名,则在调用通知时将相应参数的值作为参数值传递。举个例子应该能更清楚地说明这一点。假设你想建议以`Account`对象作为第一个参数的 DAO 操作的执行,并且你需要访问建议主体中的帐户。你可以写下:

爪哇

```
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}
```

Kotlin

```
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
fun validateAccount(account: Account) {
    // ...
}
```

切入点表达式的`args(account,..)`部分有两个目的。首先,它只限制匹配那些方法执行,其中方法至少接受一个参数,并且传递给该参数的参数是`Account`的实例。其次,它通过`account`参数使实际的`Account`对象可用于通知。

另一种编写方法是声明一个切入点,该切入点在匹配连接点时“提供”`Account`对象值,然后从通知中引用已命名的切入点。其内容如下:

爪哇

```
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}
```

Kotlin

```
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}

@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
    // ...
}
```

有关更多详细信息,请参见 AspectJ 编程指南。

代理对象(`this`)、目标对象(`target`)和注释(`@within`、`@target`、`@annotation`和`@args`)都可以以类似的方式绑定。接下来的两个示例展示了如何匹配带有`@Auditable`注释的方法的执行并提取审计代码:

这两个示例中的第一个示例显示了`@Auditable`注释的定义:

爪哇

```
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}
```

Kotlin

```
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)
```

这两个示例中的第二个示例显示了与`@Auditable`方法的执行相匹配的建议:

爪哇

```
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}
```

Kotlin

```
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
fun audit(auditable: Auditable) {
    val code = auditable.value()
    // ...
}
```

###### 建议参数和泛型

Spring  AOP 可以处理在类声明和方法参数中使用的泛型。假设你有一个通用类型,如下所示:

爪哇

```
public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}
```

Kotlin

```
interface Sample<T> {
    fun sampleGenericMethod(param: T)
    fun sampleGenericCollectionMethod(param: Collection<T>)
}
```

可以通过将通知参数与要截取方法的参数类型绑定,将方法类型的截取限制为某些参数类型:

爪哇

```
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}
```

Kotlin

```
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
    // Advice implementation
}
```

这种方法不适用于泛型集合。因此,你不能如下定义切入点:

爪哇

```
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice implementation
}
```

Kotlin

```
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
    // Advice implementation
}
```

要实现此工作,我们必须检查集合的每个元素,这是不合理的,因为我们也无法决定如何一般地处理`null`值。要实现类似的功能,你必须将参数键入`Collection<?>`,并手动检查元素的类型。

###### 确定参数名称

通知调用中的参数绑定依赖于将切入点表达式中使用的名称与通知和切入点方法签名中声明的参数名称进行匹配。参数名不能通过 爪哇 Reflection 使用,因此 Spring  AOP 使用以下策略来确定参数名:

* 如果参数名称是由用户显式指定的,则使用指定的参数名称。通知和切入点注释都有一个可选的`argNames`属性,你可以使用该属性指定带注释方法的参数名称。这些参数名称在运行时可用。下面的示例展示了如何使用`argNames`属性:

爪哇

```
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}
```

Kotlin

```
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(bean: Any, auditable: Auditable) {
    val code = auditable.value()
    // ... use code and bean
}
```

如果第一个参数是`JoinPoint`、`ProceedingJoinPoint`或`JoinPoint.StaticPart`类型的参数,则可以从`argNames`属性的值中省略参数的名称。例如,如果修改前面的通知以接收连接点对象,则`argNames`属性不需要包括它:

爪哇

```
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code, bean, and jp
}
```

Kotlin

```
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
    val code = auditable.value()
    // ... use code, bean, and jp
}
```

对`JoinPoint`、`ProceedingJoinPoint`和`JoinPoint.StaticPart`类型的第一个参数的特殊处理对于不收集任何其他连接点上下文的通知实例特别方便。在这种情况下,你可以省略`argNames`属性。例如,以下建议不需要声明`argNames`属性:

爪哇

```
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
    // ... use jp
}
```

Kotlin

```
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
fun audit(jp: JoinPoint) {
    // ... use jp
}
```

* 使用`argNames`属性有点笨拙,因此,如果尚未指定`argNames`属性, Spring  AOP 将查看该类的调试信息,并尝试从局部变量表中确定参数名称。只要类已经用调试信息编译(`-g:vars`至少),就存在此信息。使用此标志进行编译的结果是:(1)你的代码稍微容易理解(逆向工程),(2)类文件的大小稍微大一些(通常不重要),(3)你的编译器不应用删除未使用的局部变量的优化。换句话说,你应该不会遇到任何困难,通过建设与这一标志。

  |   |如果一个 @AspectJ 方面已经由 AspectJ 编译器编译(`ajc`)甚至<br/>而没有调试信息,则不需要添加`argNames`属性,因为编译器<br/>保留了所需的信息。|
  |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

* Spring  AOP 如果在没有必要的调试信息的情况下编译了代码,则尝试推断绑定变量到参数的配对(例如,如果在切入点表达式中只绑定了一个变量,并且通知方法只使用了一个参数,则该配对是显而易见的)。如果给定可用信息,变量的绑定是模棱两可的,则抛出一个`AmbiguousBindingException`。

* 如果上述所有策略都失败,则抛出一个`IllegalArgumentException`。

###### 继续进行辩论

我们在前面提到,我们将描述如何使用在 Spring  AOP 和 AspectJ 上一致工作的参数编写`proceed`调用。解决方案是确保通知签名按顺序绑定每个方法参数。下面的示例展示了如何做到这一点:

爪哇

```
@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
        String accountHolderNamePattern) throws Throwable {
    String newPattern = preProcess(accountHolderNamePattern);
    return pjp.proceed(new Object[] {newPattern});
}
```

Kotlin

```
@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
                        accountHolderNamePattern: String): Any {
    val newPattern = preProcess(accountHolderNamePattern)
    return pjp.proceed(arrayOf<Any>(newPattern))
}
```

在许多情况下,无论如何都要进行这种绑定(如前面的示例)。

#####  建议订购

当多个建议都希望在同一个连接点运行时会发生什么情况? Spring  AOP 遵循与 AspectJ 相同的优先规则来确定通知执行的顺序。优先级最高的建议先“在途中”运行(因此,给定两条前建议,优先级最高的建议先运行)。在从联结点“退出”时,最高优先级的通知最后运行(因此,给定两条后通知,具有最高优先级的通知将运行第二条)。

当在不同方面中定义的两个通知都需要在相同的连接点运行时,除非你另有指定,否则执行顺序是未定义的。你可以通过指定优先级来控制执行的顺序。这是通过在 Aspect 类中实现`org.springframework.core.Ordered`接口或使用`@Order`注释来以正常的 Spring 方式完成的。给定两个方面,从`Ordered.getOrder()`返回较低值的方面(或注释值)具有较高的优先权。

|   |在概念上,特定方面的每一个不同的通知类型都意味着将<br/>直接应用到连接点。因此,`@AfterThrowing`通知方法不是<br/>应该从随附的`@After`/`@AfterReturning`方法接收异常。<br/>在 Spring 框架 5.2.7 中,在<br/>中定义的相同`@Aspect`类中需要在相同连接点上运行的通知方法,根据其在<br/>中的通知类型,按照以下顺序,从最高优先级到最低优先级:`@Around`,`@After`,`@AfterReturning`,`@AfterThrowing`。但是,请注意,在相同的方面中,当使用任何`@AfterReturning`或`@AfterThrowing`通知方法`@After`时,将有效地调用<br/>通知方法`@AfterThrowing`。在 AspectJ 的<gt r=“finally advice”语义之后,对于。<br/>当使用两个相同类型的通知(例如,<br/>时,两个`@After`通知方法)<br/>在同一个`@Aspect`类中定义的<br/>类都需要在相同的连接点上运行,其排序<br/>是未定义的(因为没有办法通过<br/>反射获得源代码的声明顺序用于 爪哇C 编译的类)。考虑在每个`@Aspect`类中的每个连接点将这样的通知方法折叠成一个<br/>通知方法,或者将这些通知片段重构为<br/>单独的`@Aspect`类,你可以通过`Ordered`或`@Order`在方面级别订购这些类。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 5.4.5.介绍

引入(在 AspectJ 中称为类型间声明)使方面能够声明建议的对象实现了给定的接口,并代表这些对象提供了该接口的实现。

你可以使用`@DeclareParents`注释进行介绍。此注释用于声明匹配的类型有一个新的父类型(因此命名)。例如,给定一个名为`UsageTracked`的接口和一个名为`DefaultUsageTracked`的接口的实现,以下方面声明服务接口的所有实现程序也实现`UsageTracked`接口(例如,通过 JMX 进行统计):

爪哇

```
@Aspect
public class UsageTracking {

    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}
```

Kotlin

```
@Aspect
class UsageTracking {

    companion object {
        @DeclareParents(value = "com.xzy.myapp.service.*+", defaultImpl = DefaultUsageTracked::class)
        lateinit var mixin: UsageTracked
    }

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    fun recordUsage(usageTracked: UsageTracked) {
        usageTracked.incrementUseCount()
    }
}
```

要实现的接口由注释字段的类型决定。`@DeclareParents`注释的`value`属性是 AspectJ 类型模式。匹配类型的任何 Bean 实现`UsageTracked`接口。注意,在前面示例的 before 通知中,服务 bean 可以直接用作`UsageTracked`接口的实现。如果以编程方式访问 Bean,你将编写以下内容:

爪哇

```
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
```

Kotlin

```
val usageTracked = context.getBean("myService") as UsageTracked
```

#### 5.4.6.方面实例化模型

|   |这是一个高级话题。如果你刚开始使用 AOP,则可以安全地跳过<br/>它,直到稍后。|
|---|---------------------------------------------------------------------------------------------------------|

默认情况下,在应用程序上下文中,每个方面都有一个实例。AspectJ 将其称为单例实例化模型。可以用替代的生命周期来定义方面。 Spring 目前不支持 AspectJ 的`perthis`和`pertarget`实例化模型;`percflow`、`percflowbelow`和`pertypewithin`。

通过在`@Aspect`注释中指定`perthis`子句,可以声明`perthis`方面。考虑以下示例:

爪哇

```
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {

    private int someState;

    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    public void recordServiceUsage() {
        // ...
    }
}
```

Kotlin

```
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
class MyAspect {

    private val someState: Int = 0

    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    fun recordServiceUsage() {
        // ...
    }
}
```

在前面的示例中,`perthis`子句的效果是,为执行业务服务的每个唯一服务对象创建一个方面实例(每个在切入点表达式匹配的连接点绑定到`this`的唯一对象)。第一次在服务对象上调用方法时,将创建方面实例。当服务对象超出范围时,方面就超出了范围。在创建方面实例之前,它中的任何建议都不会运行。一旦创建了方面实例,其中声明的通知就会在匹配的连接点上运行,但仅当服务对象是与该方面相关联的对象时才会运行。有关`per`子句的更多信息,请参见 AspectJ 编程指南。

`pertarget`实例化模型的工作方式与`perthis`完全相同,但它在匹配的连接点上为每个唯一的目标对象创建一个方面实例。

#### 5.4.7. AOP 例

既然你已经了解了所有组成部分是如何工作的,那么我们可以将它们组合在一起来做一些有用的事情。

业务服务的执行有时会由于并发性问题(例如,死锁失败者)而失败。如果该操作被重试,则很可能在下一次尝试中成功。对于在这种情况下(幂等运算不需要返回给用户以解决冲突)适合重试的业务服务,我们希望透明地重试该操作,以避免客户端看到`PessimisticLockingFailureException`。这是一个明显跨越服务层中多个服务的需求,因此非常适合通过一个方面来实现。

因为我们想要重试操作,所以我们需要使用 around advice,这样我们就可以多次调用`proceed`。下面的清单展示了基本方面的实现:

爪哇

```
@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
```

Kotlin

```
@Aspect
class ConcurrentOperationExecutor : Ordered {

    private val DEFAULT_MAX_RETRIES = 2
    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1

    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }

    override fun getOrder(): Int {
        return this.order
    }

    fun setOrder(order: Int) {
        this.order = order
    }

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }

        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}
```

注意,方面实现了`Ordered`接口,这样我们就可以将方面的优先级设置为高于事务通知的优先级(每次重试时,我们都希望有一个新的事务)。`maxRetries`和`order`属性都是由 Spring 配置的。主要的操作发生在`doConcurrentOperation`周围的建议中。请注意,目前,我们将重试逻辑应用于每个`businessService()`。我们尝试继续,如果`PessimisticLockingFailureException`失败,我们会再试一次,除非我们已经用尽了所有的重试尝试。

相应的 Spring 配置如下:

```
<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>
```

为了细化方面,使其仅重试幂等运算,我们可以定义以下`Idempotent`注释:

爪哇

```
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
```

Kotlin

```
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation
```

然后,我们可以使用注释来注释服务操作的实现。对只重试幂等运算的方面的更改涉及细化切入点表达式,使只有`@Idempotent`运算匹配,如下所示:

爪哇

```
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}
```

Kotlin

```
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
    // ...
}
```

### 5.5.基于模式的 AOP 支持

如果你更喜欢基于 XML 的格式, Spring 还提供了使用`aop`名称空间标记来定义方面的支持。支持与使用 @AspectJ 样式时完全相同的切入点表达式和通知类型。因此,在本节中,我们将重点讨论该语法,并请读者参考上一节([@AspectJ 支持](#aop-ataspectj))中的讨论,以了解如何编写切入点表达式和绑定建议参数。

要使用本节中描述的 AOP 名称空间标记,你需要导入`spring-aop`模式,如[基于 XML 模式的配置](#xsd-schemas)中所述。有关如何导入`aop`命名空间中的标记,请参见[the AOP schema](#xsd-schemas-aop)。

在你的 Spring 配置中,所有方面和顾问元素都必须放置在`<aop:config>`元素中(在应用程序上下文配置中,可以有多个`<aop:config>`元素)。`<aop:config>`元素可以包含 pointcut、advisor 和方面元素(请注意,这些元素必须按顺序声明)。

|   |配置的`<aop:config>`样式大量使用了 Spring 的[自动代理](#aop-autoproxy)机制。如果你已经通过使用`BeanNameAutoProxyCreator`或类似的方式使用了显式自动代理,则可能会导致问题(例如建议<br/>未被编织)。推荐的使用模式是<br/>只使用`<aop:config>`样式,或者只使用`AutoProxyCreator`样式和<br/>样式,永远不要混合它们。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 5.5.1.声明一个方面

当你使用模式支持时,一个方面是在你的 Spring 应用程序上下文中定义为 Bean 的常规 爪哇 对象。在对象的字段和方法中捕获状态和行为,在 XML 中捕获切入点和通知信息。

你可以通过使用`<aop:aspect>`元素来声明一个方面,并通过使用`ref`属性来引用 backing Bean,如下例所示:

```
<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>
```

支持方面的 Bean(在这种情况下)当然可以像任何其他 Spring  Bean 一样被配置和注入依赖关系。

#### 5.5.2.声明切入点

你可以在`<aop:config>`元素中声明一个命名的切入点,让切入点定义在多个方面和顾问之间共享。

表示服务层中任何业务服务的执行的切入点可以定义如下:

```
<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

</aop:config>
```

请注意,PointCut 表达式本身使用与[@AspectJ 支持](#aop-ataspectj)中描述的 AspectJ PointCut 表达式语言相同的语言。如果使用基于模式的声明样式,则可以引用在切入点表达式中的类型(@Aspects)中定义的命名切入点。定义上述切入点的另一种方法如下:

```
<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.CommonPointcuts.businessService()"/>

</aop:config>
```

假设你有`CommonPointcuts`中描述的[共享公共切入点定义](#aop-common-pointcuts)方面。

然后在一个方面中声明一个切入点与声明一个顶级切入点非常相似,如下例所示:

```
<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        ...
    </aop:aspect>

</aop:config>
```

与 @AspectJ 方面几乎相同,使用基于模式的定义样式声明的切入点可以收集连接点上下文。例如,下面的切入点收集`this`对象作为连接点上下文,并将其传递给通知:

```
<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) &amp;&amp; this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>

</aop:config>
```

必须声明通知,以通过包括匹配名称的参数来接收收集的连接点上下文,如下所示:

爪哇

```
public void monitor(Object service) {
    // ...
}
```

Kotlin

```
fun monitor(service: Any) {
    // ...
}
```

在组合 PointCut 子表达式时,`&amp;&amp;`在 XML 文档中很难处理,因此可以分别使用`and`、`or`和`not`关键字来代替`&amp;&amp;`、`||`和`!`。例如,前面的切入点可以更好地编写如下:

```
<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>
</aop:config>
```

请注意,以这种方式定义的切入点由其 XML`id`引用,并且不能用作命名的切入点来形成复合切入点。因此,基于模式的定义样式中的命名切入点支持比 @AspectJ 样式提供的支持更有限。

#### 5.5.3.声明建议

基于模式的 AOP 支持使用与 @AspectJ 样式相同的五种建议,并且它们具有完全相同的语义。

#####  建议之前

在匹配的方法执行之前运行建议之前。通过使用`<aop:before>`元素在`<aop:aspect>`内声明它,如下例所示:

```
<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>
```

这里,`dataAccessOperation`是在顶部(`<aop:config>`)级别定义的切入点的`id`。要定义 PointCut 内联,将`pointcut-ref`属性替换为`pointcut`属性,如下所示:

```
<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>

    ...
</aop:aspect>
```

正如我们在讨论 @AspectJ 风格时所指出的,使用命名切入点可以显著提高代码的可读性。

`method`属性标识了提供建议正文的方法(`doAccessCheck`)。该方法必须为包含该建议的方面元素所引用的 Bean 定义。在执行数据访问操作(由切入点表达式匹配的方法执行连接点)之前, Bean 方面的`doAccessCheck`方法被调用。

#####  在返回建议后

返回通知后,当匹配的方法执行正常完成时运行。它是在`<aop:aspect>`中声明的,与通知之前的方式相同。下面的示例展示了如何声明它:

```
<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...
</aop:aspect>
```

正如在 @AspectJ 样式中一样,你可以在建议主体中获得返回值。为此,使用`returning`属性指定返回值应传递到的参数的名称,如下例所示:

```
<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...
</aop:aspect>
```

`doAccessCheck`方法必须声明一个名为`retVal`的参数。该参数的类型以与`@AfterReturning`相同的方式限制匹配。例如,你可以如下声明方法签名:

爪哇

```
public void doAccessCheck(Object retVal) {...
```

Kotlin

```
fun doAccessCheck(retVal: Any) {...
```

#####  在提出建议之后

抛出建议后,当匹配的方法执行通过抛出异常退出时,将运行该建议。它是在`<aop:aspect>`中通过使用`after-throwing`元素声明的,如下例所示:

```
<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        method="doRecoveryActions"/>

    ...
</aop:aspect>
```

正如在 @AspectJ 样式中一样,你可以在建议主体中获得抛出的异常。要做到这一点,请使用`throwing`属性指定应向其传递异常的参数的名称,如下例所示:

```
<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...
</aop:aspect>
```

`doRecoveryActions`方法必须声明一个名为`dataAccessEx`的参数。该参数的类型以与`@AfterThrowing`相同的方式限制匹配。例如,方法签名可以声明如下:

爪哇

```
public void doRecoveryActions(DataAccessException dataAccessEx) {...
```

Kotlin

```
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
```

#####  在(最终)建议之后

无论匹配的方法执行如何退出,在(最终)通知之后都会运行。你可以使用`after`元素来声明它,如下例所示:

```
<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...
</aop:aspect>
```

#####  围绕建议

最后一种建议是*周围*建议。围绕建议运行“围绕”一个匹配的方法的执行。它有机会在方法运行之前和之后都进行工作,并确定何时、如何以及即使方法实际运行了。如果你需要以线程安全的方式共享方法执行之前和之后的状态,通常会使用“周围建议”——例如,启动和停止计时器。

|   |始终使用功能最小的通知形式来满足你的要求。<br/><br/>例如,如果*周围*通知足以满足你的需求,则不要使用*在此之前*通知。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你可以使用`aop:around`元素声明通知周围。通知方法应该声明`Object`作为其返回类型,并且方法的第一个参数必须是`ProceedingJoinPoint`类型。在 advice 方法的主体中,你必须在`ProceedingJoinPoint`上调用`proceed()`才能运行底层方法。在没有参数的情况下调用`proceed()`将导致调用者的原始参数在调用时被提供给底层方法。对于高级用例,有一个重载的`proceed()`方法的变体,它接受一个参数数组(`Object[]`)。当调用底层方法时,数组中的值将被用作该方法的参数。有关使用`Object[]`调用`proceed`的注释,请参见[围绕建议](#aop-ataspectj-around-advice)。

下面的示例展示了如何在 XML 中声明有关建议的内容:

```
<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...
</aop:aspect>
```

`doBasicProfiling`通知的实现可以与 @AspectJ 示例中的实现完全相同(当然要减去注释),如下例所示:

爪哇

```
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
}
```

Kotlin

```
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
    // start stopwatch
    val retVal = pjp.proceed()
    // stop stopwatch
    return pjp.proceed()
}
```

#####  建议参数

基于模式的声明风格支持完全类型的通知,其方式与 @AspectJ 支持中所描述的相同——通过将切入点参数按名称与通知方法参数进行匹配。详见[建议参数](#aop-ataspectj-advice-params)。如果你希望显式地指定建议方法的参数名称(而不是依赖于前面描述的检测策略),那么你可以通过使用建议元素的`arg-names`属性来这样做,其处理方式与通知注释中的`argNames`属性相同(如[确定参数名称](#aop-ataspectj-advice-params-names)中所述)。下面的示例展示了如何在 XML 中指定参数名称:

```
<aop:before
    pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
    method="audit"
    arg-names="auditable"/>
```

`arg-names`属性接受以逗号分隔的参数名列表。

下面稍微更详细的基于 XSD 的方法的示例展示了一些与多个强类型参数一起使用的建议:

爪哇

```
package x.y.service;

public interface PersonService {

    Person getPerson(String personName, int age);
}

public class DefaultPersonService implements PersonService {

    public Person getPerson(String name, int age) {
        return new Person(name, age);
    }
}
```

Kotlin

```
package x.y.service

interface PersonService {

    fun getPerson(personName: String, age: Int): Person
}

class DefaultPersonService : PersonService {

    fun getPerson(name: String, age: Int): Person {
        return Person(name, age)
    }
}
```

接下来是方面。注意,`profile(..)`方法接受许多强类型参数,其中第一个参数恰好是用于继续方法调用的连接点。该参数的存在表明`profile(..)`将用作`around`通知,如下例所示:

爪哇

```
package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {

    public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
        StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}
```

Kotlin

```
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch

class SimpleProfiler {

    fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
        val clock = StopWatch("Profiling for '$name' and '$age'")
        try {
            clock.start(call.toShortString())
            return call.proceed()
        } finally {
            clock.stop()
            println(clock.prettyPrint())
        }
    }
}
```

最后,下面的示例 XML 配置会影响针对特定连接点的上述建议的执行:

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

    <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
    <bean id="personService" class="x.y.service.DefaultPersonService"/>

    <!-- this is the actual advice itself -->
    <bean id="profiler" class="x.y.SimpleProfiler"/>

    <aop:config>
        <aop:aspect ref="profiler">

            <aop:pointcut id="theExecutionOfSomePersonServiceMethod"
                expression="execution(* x.y.service.PersonService.getPerson(String,int))
                and args(name, age)"/>

            <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
                method="profile"/>

        </aop:aspect>
    </aop:config>

</beans>
```

考虑以下驱动程序脚本:

爪哇

```
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
        PersonService person = (PersonService) ctx.getBean("personService");
        person.getPerson("Pengo", 12);
    }
}
```

Kotlin

```
fun main() {
    val ctx = ClassPathXmlApplicationContext("x/y/plain.xml")
    val person = ctx.getBean("personService") as PersonService
    person.getPerson("Pengo", 12)
}
```

有了这样的引导类,我们将获得类似于以下标准输出的输出:

```
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)
```

#####  建议订购

当多条通知需要在相同的连接点(执行方法)上运行时,排序规则如[建议订购](#aop-ataspectj-advice-ordering)中所述。通过`<aop:aspect>`元素中的`order`属性,或者通过向支持方面的 Bean 添加`@Order`注释,或者通过使 Bean 实现`Ordered`接口,确定方面之间的优先级。

|   |与在相同的`@Aspect`类中定义的通知方法的优先规则相反,当在相同的`<aop:aspect>`元素中定义的两条通知都需要<br/>在相同的连接点上运行时,优先级由在附件`<aop:aspect>`元素中声明通知<br/>元素的顺序决定,从最高到最低<br/>优先级。<br/><br/>例如,给定一个`around`通知和一个`before`通知,该通知在同一个`<aop:aspect>`元素中定义,该元素适用于相同的连接点,以确保`around`通知比`before`通知具有更高的优先级,`<aop:around>`元素必须在`<aop:before>`元素之前声明<br/>元素。<br/><br/>作为一般的经验法则,如果你发现在同一个`<aop:aspect>`元素中有多个定义<br/>的通知适用于相同的连接点,考虑在每个`<aop:aspect>`元素<br/>中的每个连接点将<br/>这样的通知方法折叠成一个通知方法,或者将这些通知片段重构为单独的`<aop:aspect>`元素,你可以在方面级别订购<br/>元素。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 5.5.4.介绍

介绍(在 AspectJ 中称为类型间声明)让一个方面声明被建议的对象实现一个给定的接口,并代表这些对象提供该接口的实现。

你可以通过在`aop:aspect`中使用`aop:declare-parents`元素来进行介绍。你可以使用`aop:declare-parents`元素来声明匹配的类型有一个新的父类型(因此命名)。例如,给定一个名为`UsageTracked`的接口和一个名为`DefaultUsageTracked`的接口的实现,以下方面声明服务接口的所有实现者也实现`UsageTracked`接口。(例如,为了通过 JMX 公开统计信息。

```
<aop:aspect id="usageTrackerAspect" ref="usageTracking">

    <aop:declare-parents
        types-matching="com.xzy.myapp.service.*+"
        implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
        default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="com.xyz.myapp.CommonPointcuts.businessService()
            and this(usageTracked)"
            method="recordUsage"/>

</aop:aspect>
```

然后,支持`usageTracking` Bean 的类将包含以下方法:

爪哇

```
public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}
```

Kotlin

```
fun recordUsage(usageTracked: UsageTracked) {
    usageTracked.incrementUseCount()
}
```

要实现的接口由`implement-interface`属性决定。`types-matching`属性的值是 AspectJ 类型模式。 Bean 匹配类型的任何一个实现`UsageTracked`接口。注意,在前面示例的 before 通知中,服务 bean 可以直接用作`UsageTracked`接口的实现。要以编程方式访问 Bean,你可以编写以下内容:

Java

```
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
```

Kotlin

```
val usageTracked = context.getBean("myService") as UsageTracked
```

#### 5.5.5.方面实例化模型

对于模式定义的方面,唯一支持的实例化模型是单例模型。其他实例化模型可能会在未来的版本中得到支持。

#### 5.5.6.顾问

“顾问”的概念来自 Spring 中定义的 AOP 支持,在 AspectJ 中没有直接的等价物。顾问就像是一个独立的小方面,只有一条建议。通知本身由 Bean 表示,并且必须实现[Advice Types in Spring](#aop-api-advice-types)中描述的通知接口之一。顾问可以利用 AspectJ 切入点表达式。

Spring 支持带有`<aop:advisor>`元素的 advisor 概念。你最常看到它与事务性建议一起使用,事务性建议在 Spring 中也有自己的名称空间支持。下面的示例展示了一个顾问:

```
<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
```

除了前面示例中使用的`pointcut-ref`属性外,还可以使用`pointcut`属性来内联定义切入点表达式。

要定义 advisor 的优先级,以便建议可以参与排序,请使用`order`属性来定义 advisor 的`Ordered`值。

#### 5.5.7. AOP 模式示例

本节展示了在使用模式支持重写时,来自[An AOP Example](#aop-ataspectj-example)的并发锁定失败重试示例的外观。

业务服务的执行有时会由于并发性问题(例如,死锁失败者)而失败。如果该操作被重试,则很可能在下一次尝试中成功。对于在这种情况下(幂等运算不需要返回给用户以解决冲突)适合重试的业务服务,我们希望透明地重试该操作,以避免客户端看到`PessimisticLockingFailureException`。这是一个明显跨越服务层中多个服务的需求,因此非常适合通过一个方面来实现。

因为我们想要重试操作,所以我们需要使用 around advice,这样我们就可以多次调用`proceed`。下面的清单展示了基本的方面实现(这是一个使用模式支持的常规 Java 类):

Java

```
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
```

Kotlin

```
class ConcurrentOperationExecutor : Ordered {

    private val DEFAULT_MAX_RETRIES = 2

    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1

    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }

    override fun getOrder(): Int {
        return this.order
    }

    fun setOrder(order: Int) {
        this.order = order
    }

    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }

        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}
```

注意,方面实现了`Ordered`接口,这样我们就可以将方面的优先级设置为高于事务通知的优先级(每次重试时,我们都希望有一个新的事务)。`maxRetries`和`order`属性都是由 Spring 配置的。主要动作发生在`doConcurrentOperation`around advice 方法中。我们试图继续。如果我们使用`PessimisticLockingFailureException`失败,我们会再试一次,除非我们已经用尽了所有的重试尝试。

|   |这个类与 @AspectJ 示例中使用的类相同,但删除了<br/>注释。|
|---|------------------------------------------------------------------------------------------------------|

Spring 相应的配置如下:

```
<aop:config>

    <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">

        <aop:pointcut id="idempotentOperation"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        <aop:around
            pointcut-ref="idempotentOperation"
            method="doConcurrentOperation"/>

    </aop:aspect>

</aop:config>

<bean id="concurrentOperationExecutor"
    class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
        <property name="maxRetries" value="3"/>
        <property name="order" value="100"/>
</bean>
```

请注意,目前我们假设所有业务服务都是幂等的。如果不是这样的话,我们可以通过引入`Idempotent`注释并使用该注释来注释服务操作的实现,从而细化方面,使其仅重试真正的幂等运算,如下例所示:

Java

```
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
```

Kotlin

```
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
    // marker annotation
}
```

对只重试幂等运算的方面的更改涉及细化切入点表达式,使只有`@Idempotent`运算匹配,如下所示:

```
<aop:pointcut id="idempotentOperation"
        expression="execution(* com.xyz.myapp.service.*.*(..)) and
        @annotation(com.xyz.myapp.service.Idempotent)"/>
```

### 5.6.选择使用哪种 AOP 声明样式

一旦你确定一个方面是实现给定需求的最佳方法,你如何在使用 Spring  AOP 或 AspectJ 以及使用方面语言(代码)样式、@AspectJ 注释样式或 Spring XML 样式之间做出决定?这些决策受到许多因素的影响,包括应用程序需求、开发工具和团队对 AOP 的熟悉程度。

#### 5.6.1. Spring  AOP 还是全方面?

用最简单的能起作用的东西。 Spring  AOP 比使用完整的 AspectJ 更简单,因为不需要将 AspectJ 编译器/Weaver 引入到你的开发和构建过程中。如果只需要建议在 Spring bean 上执行操作,则 Spring  AOP 是正确的选择。如果你需要建议不是由 Spring 容器管理的对象(例如,通常是域对象),则需要使用 AspectJ。如果你希望建议连接点而不是简单的方法执行(例如,字段 get 或设置连接点等等),还需要使用 AspectJ。

在使用 AspectJ 时,你可以选择 AspectJ 语言语法(也称为“代码样式”)或 @AspectJ 注释样式。显然,如果你不使用 Java5+,那么已经为你做出了这样的选择:使用代码样式。如果方面在你的设计中起着很大的作用,并且你能够为 Eclipse 使用[AspectJ 开发工具](https://www.eclipse.org/ajdt/)插件,那么 AspectJ 语言语法是首选选项。它更简洁,更简单,因为该语言是专门为写作方面而设计的。如果你不使用 Eclipse,或者只有几个方面在你的应用程序中不起主要作用,那么你可能想要考虑使用 @AspectJ 样式,在 IDE 中坚持常规的 Java 编译,并在构建脚本中添加一个方面编织阶段。

#### 5.6.2. Spring  AOP 的 @AspectJ 或 XML?

如果你选择使用 Spring  AOP,那么你可以选择 @AspectJ 或 XML 样式。需要考虑的权衡因素有很多。

Spring 现有用户可能最熟悉 XML 风格,并且它得到了真正的 POJO 的支持。当使用 AOP 作为配置 Enterprise 服务的工具时,XML 可以是一个很好的选择(一个很好的测试是,你是否认为切入点表达式是你可能希望独立更改的配置的一部分)。使用 XML 风格,可以从配置中更清楚地看出系统中存在哪些方面。

XML 样式有两个缺点。首先,它没有完全封装它在一个地方处理的需求的实现。干原理认为,系统中的任何知识都应该有一个单一的、明确的、权威的表示。在使用 XML 样式时,关于需求是如何实现的知识在配置文件中的 Backing Bean 类和 XML 的声明中进行了分割。当你使用 @AspectJ 样式时,此信息被封装在一个模块中:Aspect。其次,与 @AspectJ 风格相比,XML 风格在表达的内容上略有限制:只支持“单例”方面实例化模型,并且不可能合并 XML 中声明的命名切入点。例如,在 @AspectJ 样式中,你可以编写如下内容:

Java

```
@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
```

Kotlin

```
@Pointcut("execution(* get*())")
fun propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
fun operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
fun accountPropertyAccess() {}
```

在 XML 样式中,你可以声明前两个切入点:

```
<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>
```

XML 方法的缺点是,你无法通过合并这些定义来定义`accountPropertyAccess`切入点。

@AspectJ 样式支持额外的实例化模型和更丰富的切入点组合。它具有保持方面为模块化单元的优点。它还具有这样的优点: Spring  AOP 和 AspectJ 都可以理解(并因此使用)@AspectJ 方面。因此,如果你后来决定需要 AspectJ 的功能来实现额外的需求,则可以轻松地迁移到经典的 AspectJ 设置。总的来说, Spring 团队对于自定义方面更喜欢 @AspectJ 风格,而不是简单地配置 Enterprise 服务。

### 5.7.混合方面类型

通过使用自动代理支持、模式定义的`<aop:aspect>`方面、`<aop:advisor>`声明的顾问,甚至在相同配置中使用其他样式的代理和拦截器,完全可以混合 @AspectJ 样式的方面。所有这些都是通过使用相同的底层支持机制来实现的,并且可以毫无困难地共存。

### 5.8.代理机制

Spring  AOP 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。JDK 动态代理是内置在 JDK 中的,而 CGlib 是一种常见的开源类定义库(重新打包为`spring-core`)。

如果要代理的目标对象实现了至少一个接口,则使用 JDK 动态代理。由目标类型实现的所有接口都是代理的。如果目标对象不实现任何接口,则创建一个 CGLIB 代理。

如果你想强制使用 CGlib 代理(例如,代理为目标对象定义的每个方法,而不仅仅是那些由其接口实现的方法),你可以这样做。但是,你应该考虑以下问题:

* 对于 CGlib,不能通知`final`方法,因为它们不能在运行时生成的子类中被重写。

* 从 Spring 4.0 开始,Proxied 对象的构造函数不再被调用两次,因为 CGlib 代理实例是通过 ObjeNesis 创建的。只有当你的 JVM 不允许绕过构造函数时,你才可能在 Spring 的 AOP 支持中看到双重调用和相应的调试日志条目。

要强制使用 CGLIB 代理,请将`<aop:config>`元素的`proxy-target-class`属性的值设置为 true,如下所示:

```
<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>
```

要在使用 @AspectJ 自动代理支持时强制进行 CGLIB 代理,请将`<aop:aspectj-autoproxy>`元素的`proxy-target-class`属性设置为`true`,如下所示:

```
<aop:aspectj-autoproxy proxy-target-class="true"/>
```

|   |在运行时,将多个`<aop:config/>`节折叠成一个统一的自动代理创建器<br/>,该创建器应用*最强*代理设置,该设置是由`<aop:config/>`节(通常来自不同的 XML Bean 定义文件)指定的。<br/>这也适用于`<tx:annotation-driven/>`和`<aop:aspectj-autoproxy/>`元素。要清楚,<br/>元素在`<tx:annotation-driven/>`、`<aop:aspectj-autoproxy/>`或`<aop:config/>`元素上使用`proxy-target-class="true"`强制使用 CGLIB<br/>代理*对于他们三个人来说*。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 5.8.1.理解 AOP 代理

Spring  AOP 是基于代理的。在编写自己的方面或使用 Spring 框架中提供的基于 Spring  AOP 的方面之前,掌握最后一条语句的实际含义的语义是非常重要的。

首先考虑这样一个场景:你有一个简单的、未被代理的、没有什么特别之处的、直接的对象引用,如下面的代码片段所示:

Java

```
public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}
```

Kotlin

```
class SimplePojo : Pojo {

    fun foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar()
    }

    fun bar() {
        // some logic...
    }
}
```

如果你在对象引用上调用一个方法,那么该方法将直接在该对象引用上调用,如下面的图像和列表所示:

![aop proxy plain pojo call](images/aop-proxy-plain-pojo-call.png)

Java

```
public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}
```

Kotlin

```
fun main() {
    val pojo = SimplePojo()
    // this is a direct method call on the 'pojo' reference
    pojo.foo()
}
```

当客户机代码的引用是代理时,情况会略有变化。考虑以下图表和代码片段:

![aop proxy call](images/aop-proxy-call.png)

Java

```
public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}
```

Kotlin

```
fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface(Pojo::class.java)
    factory.addAdvice(RetryAdvice())

    val pojo = factory.proxy as Pojo
    // this is a method call on the proxy!
    pojo.foo()
}
```

这里需要理解的关键是,`main(..)`类的`Main`方法中的客户端代码引用了代理。这意味着对该对象引用的方法调用是对代理的调用。因此,代理可以委托给与该特定方法调用相关的所有拦截器(通知)。然而,一旦调用最终到达目标对象(在这种情况下是`SimplePojo`引用),它可能对自身进行的任何方法调用,例如`this.bar()`或`this.foo()`,都将针对`this`引用而不是代理调用。这具有重要的意义。这意味着,自我调用不会导致与方法调用相关的建议有机会运行。

好吧,那么我们该怎么做呢?最好的方法(此处不严格使用术语“best”)是重构代码,以使自我调用不会发生。这确实需要你做一些工作,但这是最好的、侵入性最小的方法。下一种做法绝对是可怕的,我们不愿指出这一点,恰恰是因为它太可怕了。你可以(尽管这对我们来说是痛苦的)将你的类中的逻辑完全绑定到 Spring  AOP,如下例所示:

Java

```
public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}
```

Kotlin

```
class SimplePojo : Pojo {

    fun foo() {
        // this works, but... gah!
        (AopContext.currentProxy() as Pojo).bar()
    }

    fun bar() {
        // some logic...
    }
}
```

这将你的代码完全耦合到 Spring  AOP,并且它使类本身意识到这样一个事实,即它是在 AOP 上下文中使用的,这与 AOP 完全相反。在创建代理时,它还需要一些额外的配置,如下例所示:

Java

```
public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}
```

Kotlin

```
fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface(Pojo::class.java)
    factory.addAdvice(RetryAdvice())
    factory.isExposeProxy = true

    val pojo = factory.proxy as Pojo
    // this is a method call on the proxy!
    pojo.foo()
}
```

最后,必须指出的是,AspectJ 不存在这种自我调用问题,因为它不是基于代理的框架 AOP。

### 5.9.程序化地创建 @AspectJ 代理

除了在配置中使用`<aop:config>`或`<aop:aspectj-autoproxy>`声明方面之外,还可以通过编程方式创建代理来通知目标对象。有关 Spring 的 AOP API 的全部详细信息,请参见[下一章](#aop-api)。在这里,我们希望重点关注通过使用 @AspectJ Aspects 自动创建代理的能力。

你可以使用`org.springframework.aop.aspectj.annotation.AspectJProxyFactory`类为一个或多个 @AspectJ 方面建议的目标对象创建代理。这个类的基本用法非常简单,如下例所示:

Java

```
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
```

Kotlin

```
// create a factory that can generate a proxy for the given target object
val factory = AspectJProxyFactory(targetObject)

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager::class.java)

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker)

// now get the proxy object...
val proxy = factory.getProxy<Any>()
```

有关更多信息,请参见[javadoc](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/aop/aspectj/annotation/AspectJProxyFactory.html)。

### 5.10.在 Spring 应用程序中使用 AspectJ

到目前为止,我们在本章中所讨论的一切都是纯粹的 Spring  AOP。在本节中,如果你的需求超出了 Spring  AOP 单独提供的功能,我们将研究如何使用 AspectJ 编译器或 Weaver,而不是 Spring  AOP。

Spring 附带了一个小的 AspectJ 方面库,它可以在你的发行版中作为`spring-aspects.jar`单独使用。你需要将此添加到你的 Classpath 中,以便使用其中的方面。[Using AspectJ to Dependency Inject Domain Objects with Spring](#aop-atconfigurable)和[Other Spring aspects for AspectJ](#aop-ajlib-other)讨论这个库的内容以及如何使用它。[Configuring AspectJ Aspects by Using Spring IoC](#aop-aj-configure)讨论了如何通过依赖注入使用 AspectJ 编译器编织的 AspectJ 方面。最后,[Load-time Weaving with AspectJ in the Spring Framework](#aop-aj-ltw)为使用 AspectJ 的 Spring 应用程序提供了加载时编织的介绍。

#### 5.10.1.使用 AspectJ 到依赖注入域对象 Spring

Spring 容器实例化并配置在应用程序上下文中定义的 bean。还可以要求 Bean 工厂配置预先存在的对象,给定 Bean 定义的名称,该定义包含要应用的配置。`spring-aspects.jar`包含一个注释驱动的方面,该方面利用该能力来允许依赖注入任何对象。该支持旨在用于在任何容器的控制范围之外创建的对象。域对象通常属于这一类,因为它们通常是通过`new`操作符以编程方式创建的,或者是通过数据库查询的结果由 ORM 工具创建的。

`@Configurable`注释将一个类标记为符合 Spring 驱动配置的条件。在最简单的情况下,你可以将其纯粹用作标记注释,如下例所示:

Java

```
package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
    // ...
}
```

Kotlin

```
package com.xyz.myapp.domain

import org.springframework.beans.factory.annotation.Configurable

@Configurable
class Account {
    // ...
}
```

当以这种方式用作标记接口时, Spring 通过使用与完全限定类型名(`com.xyz.myapp.domain.Account`)同名的 Bean 定义(通常是原型范围)来配置带注释类型(`Account`,在这种情况下)的新实例。由于 Bean 的默认名称是其类型的完全限定名称,因此声明原型定义的一种方便方法是省略`id`属性,如下例所示:

```
<bean class="com.xyz.myapp.domain.Account" scope="prototype">
    <property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
```

如果要显式地指定要使用的原型 Bean 定义的名称,可以直接在注释中这样做,如下例所示:

爪哇

```
package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
    // ...
}
```

Kotlin

```
package com.xyz.myapp.domain

import org.springframework.beans.factory.annotation.Configurable

@Configurable("account")
class Account {
    // ...
}
```

Spring 现在查找一个名为`account`的 Bean 定义,并使用该定义来配置新的`Account`实例。

你还可以使用自动布线来避免指定专门的 Bean 定义。要使 Spring 应用自动布线,请使用`autowire`注释的`@Configurable`属性。可以分别通过类型或名称指定`@Configurable(autowire=Autowire.BY_TYPE)`或`@Configurable(autowire=Autowire.BY_NAME)`用于自动布线。作为一种替代方法,最好是在字段或方法级别上通过`@Autowired`或`@Inject`为你的`@Configurable`bean 指定显式的、注释驱动的依赖注入(有关更多详细信息,请参见[基于注释的容器配置](#beans-annotation-config))。

最后,你可以通过使用`dependencyCheck`属性(例如,`@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)`)来启用 Spring 对新创建和配置的对象中的对象引用的依赖项检查。如果将此属性设置为`true`,则 Spring 在配置后验证已设置了所有属性(不是原语或集合)。

请注意,使用注释本身并不会产生任何效果。正是`AnnotationBeanConfigurerAspect`中的`spring-aspects.jar`作用于注释的存在。实质上,该方面表示,“在从使用`@Configurable`注释的类型的新对象的初始化返回后,根据注释的属性使用 Spring 配置新创建的对象”。在这种情况下,“初始化”指的是新实例化的对象(例如,用`new`操作符实例化的对象)以及正在进行反序列化的`Serializable`对象(例如,通过[readresolve()](https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html))。

|   |上面一段中的关键短语之一是“本质上”。在大多数情况下,“从新对象的初始化返回后”的<br/>精确语义是<br/>。在这种上下文中,“在初始化之后”意味着依赖项是在对象构造完成之后注入的<br/>。这意味着依赖项<br/>在类的构造函数主体中不可用。如果希望在构造函数主体运行之前注入<br/>依赖项,从而使<br/>在构造函数主体中可用,则需要在`@Configurable`声明中定义此项,如下:<br/><br/>java<br/><br/>```<br/>@Configurable(preConstruction = true)<br/>```<br/><br/><br/><br/><br/>你可以在“the”stapectcut“中找到更多关于各种类型语义的信息<5399"GT r=">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

要做到这一点,必须将带注释的类型与 AspectJ Weaver 结合起来。你可以使用构建时 Ant 或 Maven 任务来完成此操作(例如,参见[AspectJ 开发环境指南](https://www.eclipse.org/aspectj/doc/released/devguide/antTasks.html))或加载时编织(参见[Load-time Weaving with AspectJ in the Spring Framework](#aop-aj-ltw))。`AnnotationBeanConfigurerAspect`本身需要由 Spring 进行配置(以便获得对 Bean 工厂的引用,该工厂将用于配置新对象)。如果使用基于 爪哇 的配置,可以将`@EnableSpringConfigured`添加到任何`@Configuration`类,如下所示:

爪哇

```
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
```

Kotlin

```
@Configuration
@EnableSpringConfigured
class AppConfig {
}
```

如果你更喜欢基于 XML 的配置, Spring[`context`名称空间](#XSD-schemas-context)定义了一个方便的`context:spring-configured`元素,你可以按以下方式使用它:

```
<context:spring-configured/>
```

在配置方面之前创建的`@Configurable`对象的实例将导致向调试日志发出消息,并且不会发生对象的配置。一个示例可能是 Spring 配置中的 Bean,该配置在 Spring 初始化域对象时创建域对象。在这种情况下,可以使用`depends-on` Bean 属性手动指定 Bean 取决于配置方面。下面的示例展示了如何使用`depends-on`属性:

```
<bean id="myService"
        class="com.xzy.myapp.service.MyService"
        depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

    <!-- ... -->

</bean>
```

|   |不要通过 Bean 配置器方面激活`@Configurable`处理,除非你<br/>确实意味着在运行时依赖其语义。特别是,要确保在 Bean 类上使用<br/>而不是在容器中注册为常规 Spring bean<br/>的类上使用`@Configurable`。这样做会导致双重初始化,一次通过<br/>容器,一次通过方面。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  单元测试`@Configurable`对象

`@Configurable`支持的目标之一是实现域对象的独立单元测试,而不会遇到与硬编码查找相关的困难。如果`@Configurable`类型没有被 AspectJ 编织,则注释在单元测试期间不受影响。你可以在测试对象中设置 mock 或 stub 属性引用,并按常规进行操作。如果`@Configurable`类型已由 AspectJ 编织,则仍然可以正常地在容器之外进行单元测试,但是每次构造`@Configurable`对象时,都会看到一条警告消息,表明该对象尚未由 Spring 进行配置。

#####  处理多个应用程序上下文

用于实现`@Configurable`支持的`AnnotationBeanConfigurerAspect`是 AspectJ Singleton 方面。单例方面的作用域与`static`成员的作用域相同:每个类装入器只有一个方面实例定义类型。这意味着,如果在相同的类装入器层次结构中定义多个应用程序上下文,则需要考虑在哪里定义`@EnableSpringConfigured` Bean,在哪里将`spring-aspects.jar`放置在 Classpath 上。

考虑一个典型的 Spring Web 应用程序配置,该配置具有一个共享的父应用程序上下文,该上下文定义了公共业务服务、支持这些服务所需的一切,以及每个 Servlet 的一个子应用程序上下文(其中包含专门针对该 Servlet 的定义)。所有这些上下文都在相同的类装入器层次结构中共存,因此`AnnotationBeanConfigurerAspect`只能保存对其中之一的引用。在这种情况下,我们建议在共享(父)应用程序上下文中定义`@EnableSpringConfigured` Bean。这定义了你可能希望注入到域对象中的服务。结果是,你无法通过使用 @configurable 机制(这可能不是你想要做的事情)来配置具有对子( Servlet 特定)上下文中定义的 bean 的引用的域对象。

当在同一个容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序通过使用自己的类装入器(例如,将`spring-aspects.jar`放置在`WEB-INF/lib`中)加载`spring-aspects.jar`中的类型。如果`spring-aspects.jar`只添加到容器范围的 Classpath(因此由共享的父类加载器加载),则所有 Web 应用程序都共享相同的方面实例(这可能不是你想要的)。

#### 5.10.2.AspectJ 的其他 Spring 方面

除了`@Configurable`方面,`spring-aspects.jar`还包含一个 AspectJ 方面,你可以使用该方面来驱动 Spring 的事务管理,用于使用`@Transactional`注释的类型和方法。这主要用于希望在 Spring 容器之外使用 Spring 框架的事务支持的用户。

解释`@Transactional`注释的方面是`AnnotationTransactionAspect`。当你使用这个方面时,你必须注释实现类(或该类中的方法或两者中的方法),而不是类实现的接口(如果有的话)。AspectJ 遵循 爪哇 的规则,即接口上的注释不会被继承。

类上的`@Transactional`注释指定了类中任何公共操作的执行的默认事务语义。

类中方法上的`@Transactional`注释重写了类注释给出的默认事务语义(如果存在的话)。任何可见性的方法都可以被注释,包括私有方法。直接对非公共方法进行注释是获得用于执行此类方法的事务划分的唯一方法。

|   |Spring Framework4.2 以来,`spring-aspects`提供了一个类似的方面,该方面为标准<br/>注释提供了完全相同的功能。查看`JtaAnnotationTransactionAspect`以获取更多详细信息。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

对于希望使用 Spring 配置和事务管理支持但不想(或不能)使用注释的 AspectJ 程序员,`spring-aspects.jar`还包含`abstract`方面,可以扩展以提供自己的切入点定义。有关更多信息,请参见`AbstractBeanConfigurerAspect`和`AbstractTransactionAspect`方面的来源。作为示例,下面的摘录展示了如何编写一个方面,通过使用匹配完全限定类名称的原型 Bean 定义来配置在域模型中定义的对象的所有实例:

```
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

    public DomainObjectConfiguration() {
        setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
    }

    // the creation of a new bean (any object in the domain model)
    protected pointcut beanCreation(Object beanInstance) :
        initialization(new(..)) &&
        CommonPointcuts.inDomainModel() &&
        this(beanInstance);
}
```

#### 5.10.3.使用 Spring IOC 配置 AspectJ 方面

当你在 Spring 应用程序中使用 AspectJ 方面时,很自然地,既希望也希望能够使用 Spring 配置这样的方面。AspectJ 运行时本身负责方面的创建,通过 Spring 配置 AspectJ 创建的方面的方法取决于方面使用的 AspectJ 实例化模型(`per-xxx`子句)。

大多数 AspectJ 方面都是单例方面。这些方面的配置很容易。你可以创建一个 Bean 定义,将方面类型作为常规引用,并包括`factory-method="aspectOf"` Bean 属性。这确保了 Spring 通过向 AspectJ 询问来获得方面实例,而不是试图创建实例本身。下面的示例展示了如何使用`factory-method="aspectOf"`属性:

```
<bean id="profiler" class="com.xyz.profiler.Profiler"
        factory-method="aspectOf"> (1)

    <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
```

|**1**|注意`factory-method="aspectOf"`属性|
|-----|----------------------------------------------|

非单例方面更难配置。然而,通过创建原型 Bean 定义并使用`@Configurable`来自`spring-aspects.jar`的`@Configurable`支持来配置 AspectJ 运行时所创建的 Bean 方面实例,就可以做到这一点。

如果你有一些要与 AspectJ 一起使用的 @AspectJ 方面(例如,对域模型类型使用加载时编织)和其他要与 Spring  AOP 一起使用的 @AspectJ 方面,并且这些方面都是在 Spring 中配置的,你需要告诉 Spring  AOP @AspectJ 自动代理支持程序,配置中定义的 @AspectJ 方面的哪些确切子集应该用于自动代理。你可以通过在`<aop:aspectj-autoproxy/>`声明中使用一个或多个`<include/>`元素来实现此目的。每个`<include/>`元素都指定了一个名称模式,并且在 Spring  AOP 自动代理配置中仅使用名称与至少一个模式匹配的 bean。下面的示例展示了如何使用`<include/>`元素:

```
<aop:aspectj-autoproxy>
    <aop:include name="thisBean"/>
    <aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
```

|   |不要被`<aop:aspectj-autoproxy/>`元素的名称所误导。使用它<br/>将导致创建 Spring  AOP 代理。此处使用了 Aspect<br/>声明的 @AspectJ 样式,但不涉及 AspectJ 运行时。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 5.10.4. Spring 框架中的 AspectJ 加载时编织

加载时编织(LOAD-TIME WEAWING,LTW)是指将 AspectJ 方面编织到应用程序的类文件中的过程,这些类文件正被加载到 爪哇 虚拟机中。本节的重点是在 Spring 框架的特定上下文中配置和使用 LTW。本节不是对 LTW 的一般介绍。有关 LTW 和仅使用 AspectJ 配置 LTW 的详细信息(完全不涉及 Spring),请参见[AspectJ 开发环境指南的 LTW 部分](https://www.eclipse.org/aspectj/doc/released/devguide/ltw.html)。

Spring 框架给 AspectJ LTW 带来的价值在于能够对编织过程进行更细粒度的控制。“Vanilla”AspectJ LTW 是通过使用 爪哇(5+)代理来实现的,在启动 JVM 时,通过指定 VM 参数来打开该代理。因此,它是一个 JVM 范围内的设置,在某些情况下可能很好,但通常有点太粗糙了。 Spring-enabled LTW 允许你在 per-`ClassLoader`的基础上切换 LTW,这是更细粒度的,并且可以在“单 JVM 多应用程序”环境(例如在典型的应用程序服务器环境中发现的)中更有意义。

此外,[在某些环境中](#aop-aj-ltw-environments),这种支持使加载时编织成为可能,而无需对应用程序服务器的启动脚本进行任何修改,这是添加`-javaagent:path/to/aspectjweaver.jar`或(如我们在本节后面描述的)`-javaagent:path/to/spring-instrument.jar`所需的。开发人员将应用程序上下文配置为支持加载时编织,而不是依赖通常负责部署配置(例如启动脚本)的管理员。

既然销售介绍已经结束,让我们先来看一个使用 Spring 的 AspectJ LTW 的快速示例,然后是关于示例中引入的元素的详细细节。有关完整的示例,请参见[PetClinic 样本应用程序](https://github.com/spring-projects/spring-petclinic)。

#####  第一个例子

假设你是一个应用程序开发人员,负责诊断系统中某些性能问题的原因。我们将切换到一个简单的分析方面,让我们快速获得一些性能指标,而不是推出一个分析工具。然后,我们可以立即将更细粒度的分析工具应用到该特定区域。

|   |这里展示的示例使用 XML 配置。你还可以配置<br/>并使用 @AspectJ 和[爪哇 配置](#beans-java)。具体地说,你可以使用`@EnableLoadTimeWeaving`注释作为`<context:load-time-weaver/>`的替代方法(有关详细信息,请参见[below](#aop-aj-ltw-spring))。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例展示了分析方面,这并不新奇。这是一个基于时间的探查器,它使用了方面声明的 @AspectJ 风格:

爪哇

```
package foo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;

@Aspect
public class ProfilingAspect {

    @Around("methodsToBeProfiled()")
    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch sw = new StopWatch(getClass().getSimpleName());
        try {
            sw.start(pjp.getSignature().getName());
            return pjp.proceed();
        } finally {
            sw.stop();
            System.out.println(sw.prettyPrint());
        }
    }

    @Pointcut("execution(public * foo..*.*(..))")
    public void methodsToBeProfiled(){}
}
```

Kotlin

```
package foo

import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Pointcut
import org.springframework.util.StopWatch
import org.springframework.core.annotation.Order

@Aspect
class ProfilingAspect {

    @Around("methodsToBeProfiled()")
    fun profile(pjp: ProceedingJoinPoint): Any {
        val sw = StopWatch(javaClass.simpleName)
        try {
            sw.start(pjp.getSignature().getName())
            return pjp.proceed()
        } finally {
            sw.stop()
            println(sw.prettyPrint())
        }
    }

    @Pointcut("execution(public * foo..*.*(..))")
    fun methodsToBeProfiled() {
    }
}
```

我们还需要创建一个`META-INF/aop.xml`文件,以通知 AspectJ Weaver 我们要将`ProfilingAspect`编织到类中。这种文件约定,即在 爪哇 Classpath 上存在一个名为`META-INF/aop.xml`的文件(或多个文件)是标准的 AspectJ。下面的示例显示了`aop.xml`文件:

```
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

    <weaver>
        <!-- only weave classes in our application-specific packages -->
        <include within="foo.*"/>
    </weaver>

    <aspects>
        <!-- weave in just this aspect -->
        <aspect name="foo.ProfilingAspect"/>
    </aspects>

</aspectj>
```

现在我们可以转到配置的 Spring 特定部分。我们需要配置`LoadTimeWeaver`(稍后会进行说明)。这个加载时 Weaver 是负责将一个或多个`META-INF/aop.xml`文件中的方面配置编织到应用程序中的类中的基本组件。好的方面是,它不需要大量的配置(你可以指定一些更多的选项,但这些选项将在后面详细说明),这一点可以在下面的示例中看到:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- a service object; we will be profiling its methods -->
    <bean id="entitlementCalculationService"
            class="foo.StubEntitlementCalculationService"/>

    <!-- this switches on the load-time weaving -->
    <context:load-time-weaver/>
</beans>
```

现在,所有必需的工件(方面、`META-INF/aop.xml`文件和 Spring 配置)都已就绪,我们可以使用`main(..)`方法创建以下驱动程序类,以演示 LTW 的实际操作:

爪哇

```
package foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);

        EntitlementCalculationService entitlementCalculationService =
                (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");

        // the profiling aspect is 'woven' around this method execution
        entitlementCalculationService.calculateEntitlement();
    }
}
```

Kotlin

```
package foo

import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    val ctx = ClassPathXmlApplicationContext("beans.xml")

    val entitlementCalculationService = ctx.getBean("entitlementCalculationService") as EntitlementCalculationService

    // the profiling aspect is 'woven' around this method execution
    entitlementCalculationService.calculateEntitlement()
}
```

我们还有最后一件事要做。这一部分的介绍确实说,人们可以在 Spring 的 per-`ClassLoader`的基础上有选择地切换 LTW,这是事实。然而,对于这个示例,我们使用一个 爪哇 代理(由 Spring 提供)来切换 LTW。我们使用以下命令运行前面显示的`Main`类:

```
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
```

`-javaagent`是用于指定和启用[测试在 JVM 上运行的程序的代理](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html)的标志。 Spring 框架附带了这样的代理,`InstrumentationSavingAgent`,该代理被打包在`spring-instrument.jar`中,该代理在前面的示例中作为`-javaagent`参数的值提供。

执行`Main`程序的输出与下一个示例类似。(我在`Thread.sleep(..)`实现中引入了`calculateEntitlement()`语句,这样探查器实际上捕获了 0 毫秒以外的内容(`01234`毫秒不是 AOP 引入的开销)。下面的清单显示了我们运行探查器时得到的输出:

```
Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement
```

由于这个 LTW 是通过使用完全成熟的 AspectJ 来实现的,因此我们不仅限于为 Spring bean 提供建议。以下对`Main`程序的微小更改产生了相同的结果:

爪哇

```
package foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Main {

    public static void main(String[] args) {
        new ClassPathXmlApplicationContext("beans.xml", Main.class);

        EntitlementCalculationService entitlementCalculationService =
                new StubEntitlementCalculationService();

        // the profiling aspect will be 'woven' around this method execution
        entitlementCalculationService.calculateEntitlement();
    }
}
```

Kotlin

```
package foo

import org.springframework.context.support.ClassPathXmlApplicationContext

fun main(args: Array<String>) {
    ClassPathXmlApplicationContext("beans.xml")

    val entitlementCalculationService = StubEntitlementCalculationService()

    // the profiling aspect will be 'woven' around this method execution
    entitlementCalculationService.calculateEntitlement()
}
```

请注意,在前面的程序中,我们如何引导 Spring 容器,然后完全在 Spring 的上下文之外创建`StubEntitlementCalculationService`的新实例。剖析建议仍被广泛使用。

诚然,这个例子过于简单化了。然而, Spring 中 LTW 支持的基础已经在前面的示例中介绍了,本节的其余部分详细解释了每一位配置和用法背后的“为什么”。

|   |本例中使用的`ProfilingAspect`可能是基本的,但它非常有用。这是开发时方面的一个很好的示例,开发人员可以在开发过程中使用<br/>,然后很容易地将<br/>从正在部署的应用程序的构建中排除到 UAT 或生产中。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  方面

你在 LTW 中使用的方面必须是 AspectJ 方面。你可以用 AspectJ 语言本身编写它们,也可以用 @AspectJ-style 编写方面。那么你的方面就是有效的 AspectJ 和 Spring  AOP 方面。此外,所编译的方面类需要在 Classpath 上可用。

#####  META-INF/ AOP.xml

通过使用 爪哇 Classpath 上的一个或多个`META-INF/aop.xml`文件(直接或更典型地在 jar 文件中)来配置 AspectJ LTW 基础设施。

该文件的结构和内容在[AspectJ 参考文档](https://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html)的 LTW 部分中有详细说明。因为`aop.xml`文件是 100%AspectJ,所以我们在这里不再进一步描述它。

#####  所需的库(JAR)

至少,你需要以下库来使用 Spring 框架对 AspectJ LTW 的支持:

* `spring-aop.jar`

* `aspectjweaver.jar`

如果使用[Spring-provided agent to enable instrumentation](#aop-aj-ltw-environments-generic),还需要:

* `spring-instrument.jar`

#####  Spring 配置

Spring LTW 支持中的关键组件是`LoadTimeWeaver`接口(在`org.springframework.instrument.classloading`包中),以及 Spring 发行版附带的众多实现。`LoadTimeWeaver`负责在运行时将一个或多个`java.lang.instrument.ClassFileTransformers`添加到`ClassLoader`中,这为各种有趣的应用程序打开了大门,其中之一恰好是方面的 LTW。

|   |如果你不熟悉运行时类文件转换的概念,请在继续之前查看`java.lang.instrument`包的<br/>爪哇doc API 文档。<br/>尽管该文档并不全面,但至少你可以看到关键接口<br/>和类(供你阅读本节时参考)。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

为特定的`LoadTimeWeaver`配置`ApplicationContext`就像添加一行一样简单。(注意,你几乎肯定需要使用`ApplicationContext`作为 Spring 容器——通常,`BeanFactory`是不够的,因为 LTW 支持使用`BeanFactoryPostProcessors`。

要启用 Spring 框架的 LTW 支持,你需要配置`LoadTimeWeaver`,这通常是通过使用`@EnableLoadTimeWeaving`注释完成的,如下所示:

爪哇

```
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
```

Kotlin

```
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}
```

或者,如果你更喜欢基于 XML 的配置,可以使用`<context:load-time-weaver/>`元素。注意,元素是在`context`命名空间中定义的。下面的示例展示了如何使用`<context:load-time-weaver/>`:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:load-time-weaver/>

</beans>
```

前面的配置为你自动定义并注册了许多 LTW 特定的基础架构 bean,例如`LoadTimeWeaver`和`AspectJWeavingEnabler`。默认的`LoadTimeWeaver`是`DefaultContextLoadTimeWeaver`类,它试图修饰自动检测到的`LoadTimeWeaver`。“自动检测”的`LoadTimeWeaver`的确切类型取决于你的运行时环境。下表总结了各种`LoadTimeWeaver`实现:

|运行时环境|`LoadTimeWeaver` implementation|
|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|
|运行在[Apache Tomcat](https://tomcat.apache.org/)中|    `TomcatLoadTimeWeaver`     |
|在[GlassFish](https://eclipse-ee4j.github.io/glassfish/)中运行(仅限于 EAR 部署)|   `GlassFishLoadTimeWeaver`   |
|在 Red Hat 的[JBoss AS](https://www.jboss.org/jbossas/)或[WildFly](https://www.wildfly.org/)中运行|     `JBossLoadTimeWeaver`     |
|运行在 IBM 的[WebSphere](https://www-01.ibm.com/software/webservers/appserv/was/)中|   `WebSphereLoadTimeWeaver`   |
|运行在 Oracle 的[WebLogic](https://www.oracle.com/technetwork/middleware/weblogic/overview/index-085209.html)中|   `WebLogicLoadTimeWeaver`    |
|JVM 以 Spring `InstrumentationSavingAgent`(`java -javaagent:path/to/spring-instrument.jar`)开始|`InstrumentationLoadTimeWeaver`|
|fallback,期望底层类装入器遵循常见的约定<br/>(即`addTransformer`和可选的`getThrowawayClassLoader`方法)|  `ReflectiveLoadTimeWeaver`   |

请注意,该表仅列出了使用`DefaultContextLoadTimeWeaver`时自动检测到的`LoadTimeWeavers`。你可以精确地指定要使用的`LoadTimeWeaver`实现。

要使用 爪哇 配置指定特定的`LoadTimeWeaver`,请实现`LoadTimeWeavingConfigurer`接口并覆盖`getLoadTimeWeaver()`方法。下面的示例指定了`ReflectiveLoadTimeWeaver`:

爪哇

```
@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new ReflectiveLoadTimeWeaver();
    }
}
```

Kotlin

```
@Configuration
@EnableLoadTimeWeaving
class AppConfig : LoadTimeWeavingConfigurer {

    override fun getLoadTimeWeaver(): LoadTimeWeaver {
        return ReflectiveLoadTimeWeaver()
    }
}
```

如果使用基于 XML 的配置,则可以将完全限定的类名指定为`weaver-class`元素上的`<context:load-time-weaver/>`属性的值。下面的示例再次指定了`ReflectiveLoadTimeWeaver`:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:load-time-weaver
            weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>
```

由配置定义和注册的`LoadTimeWeaver`可以在以后通过使用众所周知的名称`loadTimeWeaver`从 Spring 容器中检索。请记住,`LoadTimeWeaver`仅作为 Spring 的 LTW 基础结构的一种机制存在,用于添加一个或多个`ClassFileTransformers`。执行 LTW 的实际`ClassFileTransformer`是`ClassPreProcessorAgentAdapter`(来自`org.aspectj.weaver.loadtime`包)类。有关更多详细信息,请参见`ClassPreProcessorAgentAdapter`类的类级 爪哇doc,因为实际实现编织的细节超出了本文档的范围。

配置的最后一个属性还有待讨论:`aspectjWeaving`属性(如果使用 XML,则为`aspectj-weaving`属性)。此属性控制是否启用 LTW。它接受三个可能值中的一个,如果属性不存在,默认值为`autodetect`。下表总结了三个可能的值:

|Annotation Value| XML Value  |解释|
|----------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|   `ENABLED`    |    `on`    |AspectJ 编织是在,和方面是编织在加载时,视情况而定。|
|   `DISABLED`   |   `off`    |LTW 关闭。任何方面都不是在加载时编织的。|
|  `AUTODETECT`  |`autodetect`|如果 Spring LTW 基础设施能够找到至少一个`META-INF/aop.xml`文件,<br/>,那么 AspectJ Weaving 就处于打开状态。否则,它就关闭了。这是默认值。|

#####  特定于环境的配置

最后一节包含在应用程序服务器和 Web 容器等环境中使用 Spring 的 LTW 支持时所需的任何附加设置和配置。

###### Tomcat、JBoss、WebSphere、WebLogic

Tomcat、JBoss/Wildfly、IBMWebSphere Application Server 和 Oracle WebLogic Server 都提供了一个通用的应用`ClassLoader`,该应用程序能够进行本地检测。 Spring 的本机 LTW 可以利用那些类装入器实现来提供 AspectJ 编织。你可以简单地启用加载时编织,如[前面描述的](#aop-using-aspectj)。具体地说,你不需要修改 JVM 启动脚本来添加`-javaagent:path/to/spring-instrument.jar`。

请注意,在 JBoss 上,你可能需要禁用应用程序服务器扫描,以防止它在应用程序实际启动之前加载类。一种快速的解决方法是将名为`WEB-INF/jboss-scanning.xml`的文件添加到工件中,该文件具有以下内容:

```
<scanning xmlns="urn:jboss:scanning:1.0"/>
```

###### 通用 爪哇 应用程序

当在特定`LoadTimeWeaver`实现不支持的环境中需要类插装时,JVM 代理是通用的解决方案。对于这样的情况, Spring 提供了`InstrumentationLoadTimeWeaver`,它需要一个 Spring 特定的(但非常通用的)JVM 代理,`spring-instrument.jar`,通过常见的`@EnableLoadTimeWeaving`和`<context:load-time-weaver/>`设置进行自动检测。

要使用它,你必须使用 Spring 代理通过提供以下 JVM 选项来启动虚拟机:

```
-javaagent:/path/to/spring-instrument.jar
```

请注意,这需要修改 JVM 启动脚本,这可能会阻止你在应用程序服务器环境中使用该脚本(取决于你的服务器和操作策略)。也就是说,对于每个 JVM 部署一个应用程序,例如独立的启动应用程序,通常在任何情况下都可以控制整个 JVM 设置。

### 5.11.更多资源

有关 AspectJ 的更多信息,请访问[AspectJ 网站](https://www.eclipse.org/aspectj)。

*Eclipse AspectJ*由 Adrian Colyer 等人(Addison-Wesley,2005)为 AspectJ 语言提供了全面的介绍和参考。

*AspectJ 在行动*,第二版由 Ramnivas Laddad(曼宁,2009 年)强烈推荐。这本书的重点是 AspectJ,但(在一定程度上)探讨了许多一般性的 AOP 主题。

## 6. Spring  AOP API

上一章描述了 Spring 对 @AspectJ 和基于模式的方面定义的 AOP 的支持。在这一章中,我们讨论了较低级别的 Spring  AOP API。对于常见的应用程序,我们建议使用 Spring  AOP 和 AspectJ 切入点,如前一章所述。

### 6.1. Spring 中的切入点 API

本节描述 Spring 如何处理关键的切入点概念。

#### 6.1.1.概念

Spring 的切入点模型能够独立于建议类型实现切入点重用。你可以用相同的切入点针对不同的建议。

`org.springframework.aop.Pointcut`接口是中心接口,用于针对特定类和方法的建议。完整的界面如下:

```
public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}
```

将`Pointcut`接口拆分成两部分,可以重用类和方法匹配部分以及细粒度的组合操作(例如与另一个方法匹配程序执行“合并”)。

`ClassFilter`接口用于将切入点限制为给定的一组目标类。如果`matches()`方法总是返回 true,那么所有目标类都是匹配的。下面的清单显示了`ClassFilter`接口定义:

```
public interface ClassFilter {

    boolean matches(Class clazz);
}
```

`MethodMatcher`接口通常更重要。完整的界面如下:

```
public interface MethodMatcher {

    boolean matches(Method m, Class<?> targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class<?> targetClass, Object... args);
}
```

`matches(Method, Class)`方法用于测试此切入点是否与目标类上的给定方法匹配。当 AOP 代理被创建以避免需要对每个方法调用进行测试时,可以执行此评估。如果给定方法的双参数`matches`方法返回`true`,而 MethodMatcher 的`isRuntime()`方法返回`true`,则在每个方法调用时都会调用三参数匹配方法。这使得切入点可以查看在目标通知开始之前立即传递给方法调用的参数。

大多数`MethodMatcher`实现都是静态的,这意味着它们的`isRuntime()`方法返回`false`。在这种情况下,永远不会调用三参数`matches`方法。

|   |如果可能的话,尝试使切入点是静态的,允许 AOP 框架在创建 AOP 代理时缓存切入点求值的<br/>结果。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------|

#### 6.1.2.切入点上的操作

Spring 支持在切入点上的操作(特别是,联合和交叉)。

UNION 是指任何一个切入点都匹配的方法。交集表示两个切入点匹配的方法。联合通常更有用。你可以通过在`org.springframework.aop.support.Pointcuts`类中使用静态方法或在同一个包中使用`ComposablePointcut`类来编写切入点。然而,使用 AspectJ 切入点表达式通常是一种更简单的方法。

#### 6.1.3.AspectJ 表达式切入点

自 2.0 以来, Spring 使用的最重要的切入点类型是`org.springframework.aop.aspectj.AspectJExpressionPointcut`。这是一个切入点,它使用 AspectJ 提供的库来解析 AspectJ PointCut 表达式字符串。

有关受支持的 AspectJ 切入点原语的讨论,请参见[上一章](#aop)。

#### 6.1.4.方便的切入点实现

Spring 提供了几种方便的切入点实现方式。你可以直接使用其中的一些;其他的则打算在特定于应用程序的切入点中进行子类。

#####  静态切入点

静态切入点基于方法和目标类,不能考虑方法的参数。静态切入点对于大多数用途来说已经足够了,也是最好的。 Spring 可以仅在第一次调用方法时对静态切入点进行一次求值。在那之后,就不需要在每次方法调用时再次计算切入点了。

本节的其余部分描述了 Spring 中包含的一些静态切入点实现。

###### 正则表达式切入点

指定静态切入点的一个明显方法是正则表达式。除了 Spring 之外的几个 AOP 框架使这成为可能。`org.springframework.aop.support.JdkRegexpMethodPointcut`是一种通用的正则表达式切入点,它使用了 JDK 中对正则表达式的支持。

使用`JdkRegexpMethodPointcut`类,你可以提供模式字符串的列表。如果其中任何一个是匹配的,那么切入点的计算结果是`true`。(因此,得到的切入点实际上是指定模式的合并。

下面的示例展示了如何使用`JdkRegexpMethodPointcut`:

```
<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>
```

Spring 提供了一个名为`RegexpMethodPointcutAdvisor`的方便类,它还允许我们引用`Advice`(请记住,`Advice`可以是拦截器,在通知、抛出通知和其他通知之前)。在幕后, Spring 使用`JdkRegexpMethodPointcut`。使用`RegexpMethodPointcutAdvisor`简化了连接,因为 Bean 封装了切入点和建议,如下例所示:

```
<bean id="settersAndAbsquatulateAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref bean="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>
```

你可以使用`RegexpMethodPointcutAdvisor`与任何`Advice`类型。

###### 属性驱动的切入点

静态切入点的一种重要类型是元数据驱动的切入点。这使用了元数据属性的值(通常是源级元数据)。

#####  动态切入点

动态切入点比静态切入点的评估成本更高。它们考虑了方法参数和静态信息。这意味着每次方法调用都必须对它们进行求值,并且不能缓存结果,因为参数会发生变化。

主要的例子是`control flow`切入点。

###### 控制流切入点

Spring 控制流切点在概念上类似于 AspectJ切点,尽管不那么强大。(目前无法指定切入点运行在与另一个切入点匹配的连接点之下。)控制流切入点匹配当前调用堆栈。例如,如果连接点是由`com.mycompany.web`包中的方法调用的,或者是由`SomeCaller`类调用的,那么它可能会触发。控制流的切入点是通过使用`org.springframework.aop.support.ControlFlowPointcut`类来指定的。

|   |与<br/>其他动态切入点相比,在运行时计算控制流切入点的成本要高得多。在 爪哇1.4 中,成本大约是其他动态<br/>切入点的五倍。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 6.1.5.切入点超类

Spring 提供了有用的切入点超类,以帮助你实现自己的切入点。

因为静态切入点是最有用的,所以你可能应该使用子类`StaticMethodMatcherPointcut`。这只需要实现一个抽象方法(尽管你可以重写其他方法来定制行为)。下面的示例展示了如何子类`StaticMethodMatcherPointcut`:

爪哇

```
class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}
```

Kotlin

```
class TestStaticPointcut : StaticMethodMatcherPointcut() {

    override fun matches(method: Method, targetClass: Class<*>): Boolean {
        // return true if custom criteria match
    }
}
```

还有用于动态切入点的超类。你可以对任何通知类型使用自定义切入点。

#### 6.1.6.自定义切入点

因为 Spring  AOP 中的切入点是 爪哇 类,而不是语言特性(如 AspectJ),所以你可以声明自定义的切入点,无论是静态的还是动态的。 Spring 中的自定义切入点可以是任意复杂的。但是,如果可以的话,我们建议使用 AspectJ PointCut 表达式语言。

|   |Spring 的后续版本可能会提供对 JAC 提供的“语义切入点”的支持——例如,“所有改变目标对象中实例变量的方法”。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 6.2. Spring 中的建议 API

现在我们可以研究 Spring  AOP 如何处理建议。

#### 6.2.1.建议生命周期

每个建议都是 Spring  Bean。一个建议实例可以在所有被建议的对象之间共享,或者对于每个被建议的对象是唯一的。这对应于每个类或每个实例的建议。

每堂课的建议是最常用的。它适用于一般的建议,例如事务顾问。这些不依赖于代理对象的状态或添加新的状态。他们只是根据方法和论据行事。

每个实例的建议适合于介绍,以支持 mixin。在这种情况下,建议将状态添加到代理对象。

你可以在同一个 AOP 代理中混合使用共享和每个实例的建议。

#### 6.2.2. Spring 中的建议类型

Spring 提供了几种建议类型,并且是可扩展的,以支持任意的建议类型。本节介绍基本概念和标准建议类型。

#####  围绕建议的拦截

Spring 中最基本的建议类型是围绕建议的拦截。

Spring 与 AOP `Alliance`接口兼容,用于使用方法拦截的周围建议。实现`MethodInterceptor`并围绕建议实现的类也应该实现以下接口:

```
public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}
```

`MethodInvocation`方法的`invoke()`参数公开了被调用的方法、目标连接点、 AOP 代理以及方法的参数。`invoke()`方法应该返回调用的结果:连接点的返回值。

下面的示例展示了一个简单的`MethodInterceptor`实现:

爪哇

```
public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}
```

Kotlin

```
class DebugInterceptor : MethodInterceptor {

    override fun invoke(invocation: MethodInvocation): Any {
        println("Before: invocation=[$invocation]")
        val rval = invocation.proceed()
        println("Invocation returned")
        return rval
    }
}
```

请注意调用`proceed()`的`MethodInvocation`方法。这会沿着拦截器链朝向连接点。大多数拦截器调用这个方法并返回它的返回值。但是,`MethodInterceptor`与任何 around advice 一样,可以返回不同的值或抛出异常,而不是调用 proceed 方法。然而,你不想在没有充分理由的情况下这样做。

|   |`MethodInterceptor`实现提供了与其他 AOP 联盟兼容的 AOP <br/>实现的互操作性。在本节的剩余部分<br/>中讨论的其他通知类型以 Spring 特定的方式实现公共 AOP 概念。虽然在使用最特定的建议类型时有一个优点<br/>,但如果<br/>你可能希望在另一个 AOP 框架中运行方面,则坚持使用`MethodInterceptor`周围的建议。请注意,PointCuts<br/>目前在框架之间不具有互操作性,并且 AOP 联盟目前没有<br/>定义 PointCut 接口。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  建议之前

一种更简单的建议类型是在给出建议之前给出的。这不需要`MethodInvocation`对象,因为只有在输入方法之前才调用它。

before 通知的主要优点是,不需要调用`proceed()`方法,因此,不可能在无意中无法沿着拦截器链继续前进。

下面的清单显示了`MethodBeforeAdvice`接口:

```
public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}
```

( Spring 的 API 设计将允许先字段后通知,尽管通常的对象适用于字段截取,并且 Spring 不太可能实现它。)

请注意,返回类型是`void`。before advice 可以在连接点运行之前插入自定义行为,但不能更改返回值。如果 before 通知抛出异常,它将停止拦截器链的进一步执行。异常向拦截器链传播备份。如果它未被选中,或者在被调用方法的签名上,它将直接传递给客户机。否则,它将被 AOP 代理包装在一个未经检查的异常中。

下面的示例显示了 Spring 中的 before 建议,该建议计算了所有方法调用:

爪哇

```
public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

    public void before(Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}
```

Kotlin

```
class CountingBeforeAdvice : MethodBeforeAdvice {

    var count: Int = 0

    override fun before(m: Method, args: Array<Any>, target: Any?) {
        ++count
    }
}
```

|   |在建议可以与任何切入点一起使用之前。|
|---|--------------------------------------------|

#####  抛出建议

如果连接点引发异常,则在连接点返回后调用 Throws 通知。 Spring 提供打印抛出建议。请注意,这意味着`org.springframework.aop.ThrowsAdvice`接口不包含任何方法。它是一个标记接口,标识给定对象实现了一个或多个类型抛出建议方法。这些措施应采取以下形式:

```
afterThrowing([Method, args, target], subclassOfThrowable)
```

只有最后一个论点是必需的。方法签名可以有一个或四个参数,这取决于通知方法是否对方法和参数感兴趣。接下来的两个列表展示了抛出建议的示例类。

如果抛出`RemoteException`(包括从子类),则调用以下通知:

Java

```
public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}
```

Kotlin

```
class RemoteThrowsAdvice : ThrowsAdvice {

    fun afterThrowing(ex: RemoteException) {
        // Do something with remote exception
    }
}
```

与前面的建议不同,下一个示例声明了四个参数,这样它就可以访问被调用的方法、方法参数和目标对象。如果抛出`ServletException`,将调用以下通知:

Java

```
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
```

Kotlin

```
class ServletThrowsAdviceWithArguments : ThrowsAdvice {

    fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
        // Do something with all arguments
    }
}
```

最后一个示例说明了如何在一个同时处理`RemoteException`和`ServletException`的类中使用这两个方法。任何数量的抛出建议方法都可以合并到一个类中。下面的清单展示了最后一个示例:

Java

```
public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
```

Kotlin

```
class CombinedThrowsAdvice : ThrowsAdvice {

    fun afterThrowing(ex: RemoteException) {
        // Do something with remote exception
    }

    fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
        // Do something with all arguments
    }
}
```

|   |如果一个 throws-advice 方法本身抛出一个异常,它将重写<br/>原始异常(也就是说,它将更改抛出给用户的异常)。覆盖的<br/>异常通常是一个 runtimeException,它与任何方法<br/>签名兼容。但是,如果一个 throws-advice 方法抛出一个选中的异常,它必须<br/>匹配目标方法的声明的异常,因此,在某种程度上,<br/>耦合到特定的目标方法签名。*Do not throw an undeclared checked<br/>exception that is incompatible with the target method’s signature!*|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |抛出建议可以与任何切入点一起使用。|
|---|--------------------------------------------|

#####  在返回建议后

Spring 中的一个返回后通知必须实现`org.springframework.aop.AfterReturningAdvice`接口,下面的清单显示了该接口:

```
public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}
```

返回后通知可以访问返回值(它不能对其进行修改)、调用的方法、方法的参数和目标。

返回建议后,将计算所有未抛出异常的成功方法调用:

Java

```
public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}
```

Kotlin

```
class CountingAfterReturningAdvice : AfterReturningAdvice {

    var count: Int = 0
        private set

    override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
        ++count
    }
}
```

此建议不会更改执行路径。如果它抛出一个异常,它将被抛出到拦截器链中,而不是返回值。

|   |返回后,建议可以与任何切入点一起使用。|
|---|-----------------------------------------------------|

#####  介绍建议

Spring 将介绍建议视为一种特殊的拦截建议。

Introduction 需要一个`IntroductionAdvisor`和一个`IntroductionInterceptor`来实现以下接口:

```
public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}
```

继承自 AOP Alliance`MethodInterceptor`接口的`invoke()`方法必须实现介绍。也就是说,如果被调用的方法位于一个引入的接口上,则引入拦截器负责处理方法调用——它不能调用`proceed()`。

介绍建议不能与任何切入点一起使用,因为它只适用于类,而不是方法级别。你只能使用带有`IntroductionAdvisor`的介绍建议,该方法具有以下方法:

```
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}
```

没有`MethodMatcher`,因此,没有`Pointcut`与介绍建议相关联。只有类过滤是合乎逻辑的。

`getInterfaces()`方法返回此顾问引入的接口。

在内部使用`validateInterfaces()`方法来查看是否可以通过配置的`IntroductionInterceptor`实现引入的接口。

考虑 Spring 测试套件中的一个示例,并假设我们希望将以下接口引入一个或多个对象:

Java

```
public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}
```

Kotlin

```
interface Lockable {
    fun lock()
    fun unlock()
    fun locked(): Boolean
}
```

这说明了一个 mixin。我们希望能够将建议对象强制转换为`Lockable`,无论它们的类型如何,并调用锁定和解锁方法。如果我们调用`lock()`方法,我们希望所有 setter 方法都抛出一个`LockedException`。因此,我们可以添加一个方面,该方面提供了使对象不可变的能力,而不需要它们对此有任何了解: AOP 的一个很好的示例。

首先,我们需要一个`IntroductionInterceptor`来完成繁重的工作。在这种情况下,我们扩展`org.springframework.aop.support.DelegatingIntroductionInterceptor`便利类。我们可以直接实现`IntroductionInterceptor`,但是在大多数情况下使用`DelegatingIntroductionInterceptor`是最好的。

`DelegatingIntroductionInterceptor`的设计目的是将介绍委派给所介绍的接口的实际实现,从而隐藏了拦截的使用。可以使用构造函数参数将委托设置为任何对象。默认的委托(当使用无参数构造函数时)是`this`。因此,在下一个示例中,委托是`LockMixin`的`DelegatingIntroductionInterceptor`子类。给定一个委托(默认情况下是委托本身),`DelegatingIntroductionInterceptor`实例将查找委托实现的所有接口(`IntroductionInterceptor`除外),并支持针对其中任何一个的介绍。像`LockMixin`这样的子类可以调用`suppressInterface(Class intf)`方法来抑制不应该公开的接口。然而,无论`IntroductionInterceptor`准备支持多少接口,`IntroductionAdvisor`都使用控制哪些接口实际上是公开的。引入的接口掩盖了目标对同一接口的任何实现。

因此,`LockMixin`扩展了`DelegatingIntroductionInterceptor`并实现了`Lockable`本身。超类自动获取`Lockable`可以支持的引言,因此我们不需要指定它。我们可以以这种方式引入任意数量的接口。

注意使用`locked`实例变量。这实际上为目标对象中的状态添加了额外的状态。

下面的示例显示了`LockMixin`类的示例:

Java

```
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}
```

Kotlin

```
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {

    private var locked: Boolean = false

    fun lock() {
        this.locked = true
    }

    fun unlock() {
        this.locked = false
    }

    fun locked(): Boolean {
        return this.locked
    }

    override fun invoke(invocation: MethodInvocation): Any? {
        if (locked() && invocation.method.name.indexOf("set") == 0) {
            throw LockedException()
        }
        return super.invoke(invocation)
    }

}
```

通常,你不需要重写`invoke()`方法。通常,`DelegatingIntroductionInterceptor`实现(如果引入了方法,则调用`delegate`方法,否则将继续进行连接)就足够了。在本例中,我们需要添加一个检查:如果处于锁定模式,则不能调用 setter 方法。

所需的介绍只需要持有一个不同的`LockMixin`实例并指定引入的接口(在这种情况下,只需要`Lockable`)。一个更复杂的示例可能会引用介绍拦截器(它将被定义为原型)。在这种情况下,不存在与`LockMixin`相关的配置,因此我们使用`new`创建它。下面的示例显示了我们的`LockMixinAdvisor`类:

Java

```
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}
```

Kotlin

```
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
```

我们可以非常简单地应用此 advisor,因为它不需要配置。(然而,在没有`IntroductionAdvisor`的情况下,不可能使用`IntroductionInterceptor`。)与通常的介绍一样,advisor 必须是每个实例,因为它是有状态的。对于每个被建议的对象,我们需要一个不同的`LockMixinAdvisor`实例,因此也需要一个`LockMixin`实例。顾问是被建议对象状态的一部分。

我们可以通过使用`Advised.addAdvisor()`方法或 XML 配置中的(推荐的方式),以编程方式应用此顾问,就像任何其他顾问一样。下面讨论的所有代理创建选择,包括“自动代理创建”,正确处理介绍和有状态的混合。

### 6.3. Spring 中的顾问 API

在 Spring 中,Advisor 是一个方面,它仅包含与切入点表达式相关联的单个通知对象。

除了介绍的特殊情况外,任何 advisor 都可以与任何建议一起使用。`org.springframework.aop.support.DefaultPointcutAdvisor`是最常用的 advisor 类。它可以与`MethodInterceptor`、`BeforeAdvice`或`ThrowsAdvice`一起使用。

在相同的 AOP 代理中可以混合 Spring 中的顾问和建议类型。例如,你可以在一个代理配置中使用围绕建议、抛出建议和在建议之前的拦截。 Spring 自动创建必要的拦截链。

### 6.4.使用`ProxyFactoryBean`创建 AOP 代理

如果你为你的业务对象使用 Spring IOC 容器(`ApplicationContext`或`BeanFactory`)(而且你应该是!),那么你希望使用 Spring 的 AOP `FactoryBean`实现中的一个。(请记住,工厂 Bean 引入了间接层,允许其创建不同类型的对象。)

|   |Spring  AOP 支持还在覆盖件下使用工厂 bean。|
|---|----------------------------------------------------------------|

在 Spring 中创建 AOP 代理的基本方法是使用`org.springframework.aop.framework.ProxyFactoryBean`。这样就可以完全控制切入点、应用的任何建议以及它们的排序。然而,如果你不需要这种控制,那么有一些更简单的选项是更好的。

#### 6.4.1.基础知识

与其他 Spring `FactoryBean`实现方式一样,`ProxyFactoryBean`引入了间接的级别。如果你定义了名为`ProxyFactoryBean`的`foo`,那么引用`foo`的对象不会看到`ProxyFactoryBean`实例本身,而是由`getObject()`方法的实现在`ProxyFactoryBean`中创建的对象。该方法创建一个 AOP 代理来包装目标对象。

使用`ProxyFactoryBean`或另一个 IoC-aware 类来创建 AOP 代理的最重要的好处之一是,IoC 也可以管理建议和切入点。这是一个强大的特性,它支持某些方法,而这些方法是用其他 AOP 框架很难实现的。例如,建议本身可以引用应用程序对象(除了目标,这应该在任何 AOP 框架中可用),从而受益于依赖注入提供的所有可插拔性。

#### 6.4.2.Javabean 属性

与 Spring 提供的大多数`FactoryBean`实现一样,`ProxyFactoryBean`类本身是一个 JavaBean。它的特性用于:

* 指定要代理的目标。

* 指定是否使用 CGLIB(稍后将进行说明,还请参见[基于 JDK 和 CGLIB 的代理](#aop-pfb-proxy-types))。

一些键属性继承自`org.springframework.aop.framework.ProxyConfig`( Spring 中所有 AOP 代理工厂的超类)。这些关键属性包括以下内容:

* `proxyTargetClass`:`true`如果要代理目标类,而不是目标类的接口。如果将此属性值设置为`true`,则创建 CGLIB 代理(但也请参见[基于 JDK 和 CGLIB 的代理](#aop-pfb-proxy-types))。

* `optimize`:控制是否对通过 CGLIB 创建的代理应用积极的优化。除非完全了解相关的代理如何处理优化,否则不应随意使用此设置。这目前仅用于 CGlib 代理。它对 JDK 动态代理没有任何影响。

* `frozen`:如果代理配置是`frozen`,则不再允许对配置进行更改。这既是一种轻微的优化,也适用于在创建代理后不希望调用者能够操作代理(通过`Advised`接口)的情况。此属性的默认值是`false`,因此允许进行更改(例如添加额外的建议)。

* `exposeProxy`:确定当前代理是否应该在`ThreadLocal`中公开,以便目标可以访问它。如果目标需要获得代理,并且`exposeProxy`属性设置为`true`,则目标可以使用`AopContext.currentProxy()`方法。

特定于`ProxyFactoryBean`的其他属性包括以下内容:

* `proxyInterfaces`:由`String`接口名称组成的数组。如果不提供此选项,则使用目标类的 CGLIB 代理(但也请参见[基于 JDK 和 CGLIB 的代理](#aop-pfb-proxy-types))。

* `interceptorNames`:一个`String`的数组`Advisor`、拦截器或其他要应用的通知名称。订购是重要的,在先到先得的基础上。也就是说,列表中的第一个拦截器是第一个能够拦截调用的拦截器。

  这些名称是当前工厂中的 Bean 名称,包括 Bean 来自祖先工厂的名称。在此不能提及 Bean 引用,因为这样做会导致`ProxyFactoryBean`忽略建议的单例设置。

  可以用星号(`*`)追加拦截器名称。这样做会导致所有的 advisor bean 的应用程序的名称都以要应用的星号之前的部分开始。你可以在[使用“全球”顾问](#aop-global-advisors)中找到使用此功能的示例。

* 单例:无论工厂是否应该返回单个对象,无论调用`getObject()`方法的频率如何。几个`FactoryBean`实现提供了这样的方法。默认值为`true`。如果你想使用有状态的建议--例如,对于有状态的 mixin--使用原型建议以及`false`的单例值。

#### 6.4.3.基于 JDK 和 CGLIB 的代理

这一节是关于`ProxyFactoryBean`如何选择为特定目标对象创建基于 JDK 的代理或基于 CGLIB 的代理的权威文档。

|   |`ProxyFactoryBean`关于创建基于 JDK-或 CGLIB 的<br/>代理的行为在 Spring 的 1.2.x 和 2.0 版本之间发生了变化。`ProxyFactoryBean`现在<br/>在自动检测接口方面表现出与`TransactionProxyFactoryBean`类类似的语义。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

如果要代理的目标对象的类(以下简称为目标类)不实现任何接口,则创建一个基于 CGLIB 的代理。这是最简单的场景,因为 JDK 代理是基于接口的,没有接口意味着甚至不可能代理 JDK。你可以通过设置`interceptorNames`属性来插入目标 Bean 并指定拦截器列表。请注意,即使`ProxyFactoryBean`的`proxyTargetClass`属性已设置为`false`,也会创建基于 CGLIB 的代理。(这样做没有意义,最好从 Bean 定义中删除,因为它最好是多余的,最坏的情况是令人困惑。

如果目标类实现了一个(或多个)接口,那么创建的代理类型取决于`ProxyFactoryBean`的配置。

如果`ProxyFactoryBean`的`proxyTargetClass`属性已设置为`true`,则创建一个基于 CGLIB 的代理。这是有道理的,也符合最少令人惊讶的原则。即使`ProxyFactoryBean`的`proxyInterfaces`属性已被设置为一个或多个完全限定的接口名称,但`proxyTargetClass`属性被设置为`true`的事实将导致基于 CGLIB 的代理生效。

如果`proxyInterfaces`的`ProxyFactoryBean`属性已被设置为一个或多个完全限定的接口名,则将创建一个基于 JDK 的代理。创建的代理实现了在`proxyInterfaces`属性中指定的所有接口。如果目标类实现了比`proxyInterfaces`属性中指定的接口多得多的接口,那就很好了,但是这些额外的接口并不是由返回的代理实现的。

如果`proxyInterfaces`的`ProxyFactoryBean`属性尚未设置,但是目标类确实实现了一个(或多个)接口,则`ProxyFactoryBean`自动检测目标类确实实现了至少一个接口,并创建了一个基于 JDK 的代理。实际代理的接口是目标类实现的所有接口。实际上,这与向`proxyInterfaces`属性提供目标类实现的每个接口的列表是一样的。然而,它的工作量要少得多,而且不太容易出现印刷错误。

#### 6.4.4.代理接口

以`ProxyFactoryBean`的一个简单示例为例。这个例子涉及到:

* Bean 被代理的目标。这是示例中的`personTarget` Bean 定义。

* 用于提供建议的`Advisor`和`Interceptor`。

* AOP 代理 Bean 定义来指定目标对象(`personTarget` Bean)、代理的接口以及应用的建议。

下面的清单展示了这个示例:

```
<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>

    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>
```

请注意,`interceptorNames`属性接受`String`的列表,该列表保存当前工厂中拦截器或顾问的 Bean 名称。你可以在返回之前、之后使用 Advisors、Interceptors,并抛出建议对象。对顾问的排序意义重大。

|   |你可能想知道为什么该列表不包含 Bean 引用。其原因是<br/>,如果`ProxyFactoryBean`的单例属性设置为`false`,则它必须能够<br/>返回独立的代理实例。如果任何顾问本身是一个原型,则需要返回一个<br/>独立实例,因此必须能够从工厂获得<br/>原型的实例。仅有引用是不够的。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

前面显示的`person` Bean 定义可以用来代替`Person`实现,如下所示:

Java

```
Person person = (Person) factory.getBean("person");
```

Kotlin

```
val person = factory.getBean("person") as Person;
```

在同一 IoC 上下文中的其他 bean 可以表示对它的强类型依赖,就像普通的 Java 对象一样。下面的示例展示了如何做到这一点:

```
<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person"/></property>
</bean>
```

本例中的`PersonUser`类公开了类型`Person`的属性。就其而言, AOP 代理可以透明地代替“真实的”人实现。然而,它的类将是一个动态代理类。可以将其强制转换到`Advised`接口(将在后面讨论)。

你可以通过使用匿名内部 Bean 来隐藏目标和代理之间的区别。只有`ProxyFactoryBean`的定义是不同的。仅为完整起见,才列入该建议。下面的示例展示了如何使用匿名内部 Bean:

```
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>
```

Bean 使用匿名内部具有这样的优点,即只有一个类型`Person`的对象。如果我们希望防止应用程序上下文的用户获得对未建议对象的引用,或者需要避免使用 Spring IOC 自动布线的任何歧义,那么这是有用的。可以说,`ProxyFactoryBean`的定义是自包含的,这也有一个优点。然而,有时能够从工厂获得不建议的目标实际上可能是一种优势(例如,在某些测试场景中)。

#### 6.4.5.代理类

如果你需要代理一个类,而不是一个或多个接口,该怎么办?

想象一下,在我们早期的示例中,没有`Person`接口。我们需要建议一个名为`Person`的类,它没有实现任何业务接口。在这种情况下,可以将 Spring 配置为使用 CGlib 代理,而不是动态代理。为此,将前面显示的`ProxyFactoryBean`上的`proxyTargetClass`属性设置为`true`。虽然最好使用接口编程,而不是类编程,但在使用遗留代码时,向不实现接口的类提供建议的能力可能是有用的。(一般来说, Spring 不是规定性的。尽管它使应用好的实践变得容易,但它避免了强制使用特定的方法。

如果你愿意,你可以在任何情况下强制使用 CGlib,即使你确实有接口。

CGLIB 代理的工作原理是在运行时生成目标类的一个子类。 Spring 将此生成的子类配置为将方法调用委托给原始目标。子类用于实现 decorator 模式,并在建议中进行编织。

CGLIB 代理通常应该对用户透明。然而,有一些问题需要考虑:

* 不能通知`Final`方法,因为它们不能被重写。

* 没有必要将 CGlib 添加到你的 Classpath 中。截至 Spring 3.2,CGlib 被重新打包并包含在 Spring-core jar 中。换句话说,基于 CGLIB 的 AOP 工作是“开箱即用”的,JDK 动态代理也是如此。

CGlib 代理和动态代理之间的性能差别不大。在这种情况下,业绩不应是决定性的考虑因素。

#### 6.4.6.使用“全球”顾问

通过将星号附加到拦截器名称中,所有具有 Bean 名称的、与星号之前的部分匹配的顾问都将添加到顾问链中。如果你需要添加一组标准的“全球”顾问,这可能会派上用场。以下示例定义了两个全球顾问:

```
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
```

### 6.5.简明代理定义

特别是在定义事务代理时,你可能会使用许多类似的代理定义。 Bean 父定义和子定义以及内部 Bean 定义的使用可以导致更干净和更简洁的代理定义。

首先,我们为代理创建一个父模板 Bean 定义,如下所示:

```
<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>
```

这本身从来不是实例化的,因此它实际上可能是不完整的。然后,需要创建的每个代理都是一个子 Bean 定义,该定义将代理的目标包装为内部 Bean 定义,因为该目标本身永远不会被使用。下面的示例显示了这样的孩子 Bean:

```
<bean id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>
```

你可以从父模板重写属性。在下面的示例中,我们重写事务传播设置:

```
<bean id="mySpecialService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MySpecialServiceImpl">
        </bean>
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>
```

请注意,在父 Bean 示例中,我们通过将`abstract`属性设置为`true`,显式地将父 Bean 定义标记为抽象,如所描述的[previously](#beans-child-bean-definitions),以便它实际上可能永远不会被实例化。默认情况下,应用程序上下文(但不是简单的 Bean 工厂)预先实例化所有单例。因此,重要的是(至少对于单例 bean 而言),如果你有一个(父) Bean 定义,只打算用作模板,并且该定义指定了一个类,那么你必须确保将`abstract`属性设置为`true`。否则,应用程序上下文实际上会尝试预先实例化它。

### 6.6.用`ProxyFactory`以编程方式创建 AOP 代理

使用 Spring 以编程方式创建 AOP 代理是很容易的。这允许你使用 Spring  AOP 而不依赖 Spring IOC。

由目标对象实现的接口是自动代理的。下面的清单展示了为目标对象创建代理的过程,其中包括一个拦截器和一个顾问:

Java

```
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
```

Kotlin

```
val factory = ProxyFactory(myBusinessInterfaceImpl)
factory.addAdvice(myMethodInterceptor)
factory.addAdvisor(myAdvisor)
val tb = factory.proxy as MyBusinessInterface
```

第一步是构造类型`org.springframework.aop.framework.ProxyFactory`的对象。你可以使用目标对象来创建这个,就像前面的示例一样,或者指定要在替代构造函数中代理的接口。

你可以添加建议(将拦截器作为一种专门的建议)、顾问或两者,并在`ProxyFactory`的生命周期中对它们进行操作。如果添加`IntroductionInterceptionAroundAdvisor`,则可以使代理实现其他接口。

在`ProxyFactory`(继承自`AdvisedSupport`)上也有方便的方法,允许你添加其他通知类型,例如 before 和 throws 通知。`AdvisedSupport`是`ProxyFactory`和`ProxyFactoryBean`的超类。

|   |AOP 在大多数<br/>应用程序中,将代理创建与 IoC 框架集成是最佳实践。我们建议你使用 AOP,<br/>将配置从 Java 代码中外部化,就像你通常应该做的那样。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 6.7.操作被建议的对象

无论你如何创建 AOP 代理,你都可以通过使用`org.springframework.aop.framework.Advised`接口来操作它们。 AOP 任何代理都可以被强制转换到这个接口,无论它实现了哪个其他接口。该接口包括以下方法:

Java

```
Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();
```

Kotlin

```
fun getAdvisors(): Array<Advisor>

@Throws(AopConfigException::class)
fun addAdvice(advice: Advice)

@Throws(AopConfigException::class)
fun addAdvice(pos: Int, advice: Advice)

@Throws(AopConfigException::class)
fun addAdvisor(advisor: Advisor)

@Throws(AopConfigException::class)
fun addAdvisor(pos: Int, advisor: Advisor)

fun indexOf(advisor: Advisor): Int

@Throws(AopConfigException::class)
fun removeAdvisor(advisor: Advisor): Boolean

@Throws(AopConfigException::class)
fun removeAdvisor(index: Int)

@Throws(AopConfigException::class)
fun replaceAdvisor(a: Advisor, b: Advisor): Boolean

fun isFrozen(): Boolean
```

对于已添加到工厂的每个 Advisor、Interceptor 或其他通知类型,`getAdvisors()`方法返回一个`Advisor`。如果你添加了`Advisor`,则此索引处返回的顾问就是你添加的对象。如果你添加了一个拦截器或其他通知类型, Spring 将其包装在一个具有始终返回`true`的切入点的 Advisor 中。因此,如果你添加了一个`MethodInterceptor`,则为该索引返回的 advisor 是一个`DefaultPointcutAdvisor`,它返回你的`MethodInterceptor`和一个匹配所有类和方法的切入点。

`addAdvisor()`方法可用于添加任何`Advisor`。通常,持有切入点和建议的顾问是通用的`DefaultPointcutAdvisor`,你可以将其用于任何建议或切入点(但不用于介绍)。

默认情况下,即使创建了代理,也可以添加或删除顾问或拦截器。唯一的限制是,不可能添加或删除 IntroductionAdvisor,因为来自工厂的现有代理不会显示接口更改。(你可以从工厂获得一个新的代理,以避免此问题。

下面的示例显示了将 AOP 代理强制转换到`Advised`接口并检查和操作其建议:

Java

```
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
```

Kotlin

```
val advised = myObject as Advised
val advisors = advised.advisors
val oldAdvisorCount = advisors.size
println("$oldAdvisorCount advisors")

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(DebugInterceptor())

// Add selective advice using a pointcut
advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice))

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size)
```

|   |在生产中修改对<br/>业务对象的建议是否可取(没有双关语意思)是值得怀疑的,尽管毫无疑问存在合法的使用情况。<br/>但是,它在开发(例如,在测试中)中可能非常有用。我们有时发现<br/>能够以拦截器或其他<br/>建议的形式添加测试代码非常有用,从而进入我们想要测试的方法调用。(例如,建议可以<br/>获取为该方法创建的事务的内部,也许可以运行 SQL 来检查<br/>数据库是否已正确更新,然后标记该事务以进行回滚。)|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

根据创建代理的方式,通常可以设置`frozen`标志。在这种情况下,`Advised``isFrozen()`方法返回`true`,任何通过添加或删除修改建议的尝试都会导致`AopConfigException`。在某些情况下,冻结被建议对象的状态的能力是有用的(例如,可以防止调用代码删除安全拦截器)。

### 6.8.使用“自动代理”功能

到目前为止,我们已经考虑了通过使用`ProxyFactoryBean`或类似的工厂 Bean 显式地创建 AOP 代理。

Spring 还允许我们使用“自动代理” Bean 定义,其可以自动代理所选择的 Bean 定义。这是建立在 Spring 的“ Bean 后处理器”基础架构上的,该基础架构允许在容器加载时修改任何 Bean 定义。

在这个模型中,你在 XML Bean 定义文件中设置了一些特殊的 Bean 定义,以配置自动代理基础设施。这允许你声明有资格进行自动代理的目标。你不需要使用`ProxyFactoryBean`。

有两种方法可以做到这一点:

* 通过使用在当前上下文中引用特定 bean 的自动代理创建器。

* 一个值得单独考虑的自动代理创建的特殊情况:由源级元数据属性驱动的自动代理创建。

#### 6.8.1.自动代理 Bean 定义

本节介绍由`org.springframework.aop.framework.autoproxy`包提供的自动代理创建器。

#####  `BeanNameAutoProxyCreator`

`BeanNameAutoProxyCreator`类是一个`BeanPostProcessor`类,它为名称与文字值或通配符匹配的 bean 自动创建 AOP 代理。下面的示例展示了如何创建`BeanNameAutoProxyCreator` Bean:

```
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="jdk*,onlyJdk"/>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>
```

与`ProxyFactoryBean`一样,有一个`interceptorNames`属性,而不是拦截器列表,以允许原型顾问的正确行为。被命名的“拦截器”可以是顾问,也可以是任何类型的建议。

与一般的自动代理一样,使用`BeanNameAutoProxyCreator`的主要目的是以最小的配置量将相同的配置一致地应用于多个对象。它是将声明式事务应用于多个对象的流行选择。

Bean 名称匹配的定义,例如前面示例中的`jdkMyBean`和`onlyJdk`,是与目标类完全一致的旧定义 Bean。 AOP 代理由`BeanNameAutoProxyCreator`自动创建。同样的建议也适用于所有匹配的 bean。请注意,如果使用了 Advisors(而不是前面示例中的拦截器),那么切入点可能会以不同的方式应用于不同的 bean。

#####  `DefaultAdvisorAutoProxyCreator`

一个更通用且功能极其强大的自动代理创建器是`DefaultAdvisorAutoProxyCreator`。这自动地在当前上下文中应用合格的顾问,而不需要在自动代理顾问的 Bean 定义中包括特定的 Bean 名称。它提供了与`BeanNameAutoProxyCreator`相同的优点,即配置一致和避免重复。

使用这一机制涉及:

* 指定`DefaultAdvisorAutoProxyCreator` Bean 定义。

* 在相同或相关的上下文中指定任意数量的顾问。请注意,这些必须是顾问,而不是拦截器或其他建议。这是必要的,因为必须有一个切入点来进行评估,以检查每个建议对候选者 Bean 定义的资格。

`DefaultAdvisorAutoProxyCreator`会自动计算每个 Advisor 中包含的切入点,以查看它应该对每个业务对象应用什么(如果有的话)建议(例如示例中的`businessObject1`和`businessObject2`)。

这意味着任何数量的顾问都可以自动应用到每个业务对象。如果任何顾问中没有切入点与业务对象中的任何方法匹配,则不代理该对象。 Bean 在为新的业务对象添加定义时,如果有必要,会自动代理它们。

通常,自动代理的优点是使调用者或依赖项不可能获得未通知的对象。在此`getBean("businessObject1")`上调用`ApplicationContext`返回一个 AOP 代理,而不是目标业务对象。(前面展示的“内 Bean”成语也提供了这一好处。

下面的示例创建了`DefaultAdvisorAutoProxyCreator` Bean 和本节讨论的其他元素:

```
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>

<bean id="businessObject1" class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
```

如果你希望将相同的建议一致地应用于许多业务对象,那么`DefaultAdvisorAutoProxyCreator`非常有用。一旦基础设施定义到位,你就可以添加新的业务对象,而不需要包括特定的代理配置。你还可以很容易地删除其他方面(例如,跟踪或性能监视方面),而只需对配置进行最小的更改。

`DefaultAdvisorAutoProxyCreator`提供了对过滤和排序的支持(通过使用命名约定,以便只对某些顾问进行评估,这允许在同一工厂中使用多个配置不同的顾问或自动代理创建者)。如果出现问题,Advisors 可以实现`org.springframework.core.Ordered`接口,以确保正确的排序。前面示例中使用的`TransactionAttributeSourceAdvisor`具有可配置的订单值。默认设置是无序的。

### 6.9.使用`TargetSource`实现

Spring 提供了一个`TargetSource`的概念,表示在`org.springframework.aop.TargetSource`接口中。这个接口负责返回实现连接点的“目标对象”。 AOP 代理每次处理方法调用时,都会对`TargetSource`实现请求一个目标实例。

使用 Spring  AOP 的开发人员通常不需要直接使用`TargetSource`实现,但是这提供了一种支持池、热插拔和其他复杂目标的强大手段。例如,池`TargetSource`可以通过使用池来管理实例,为每次调用返回不同的目标实例。

如果没有指定`TargetSource`,则使用默认实现来包装本地对象。每次调用都返回相同的目标(如你所料)。

本节的其余部分描述了 Spring 提供的标准目标源,以及如何使用它们。

|   |当使用自定义目标源时,你的目标通常需要是一个原型<br/>,而不是一个单例 Bean 定义。这允许 Spring 在需要时创建新的目标<br/>实例。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 6.9.1.热插拔目标源

存在`org.springframework.aop.target.HotSwappableTargetSource`是为了让 AOP 代理的目标被切换,同时让调用者保留对它的引用。

更改目标源的目标将立即生效。`HotSwappableTargetSource`是线程安全的。

你可以使用 HotswappleTargetSource 上的`swap()`方法来更改目标,如下例所示:

Java

```
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
```

Kotlin

```
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val oldTarget = swapper.swap(newTarget)
```

下面的示例展示了所需的 XML 定义:

```
<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="swapper"/>
</bean>
```

前面的`swap()`调用更改了可交换对象的目标 Bean。客户持有的参考 Bean 是不知道的变化,但立即开始击中新的目标。

虽然此示例不添加任何建议(使用`TargetSource`不需要添加建议),但任何`TargetSource`都可以与任意建议一起使用。

#### 6.9.2.汇集目标源

使用池目标源提供了类似于无状态会话 EJB 的编程模型,在这种模型中,将维护一个由相同实例组成的池,方法调用将释放池中的对象。

Spring 池和 SLSB 池之间的一个关键区别是 Spring 池可以应用于任何 POJO。与 Spring 一般情况下一样,该服务可以以非侵入性的方式应用。

Spring 提供了对 Commons Pool2.2 的支持,其提供了相当有效的池实现。你需要应用程序 Classpath 上的`commons-pool` jar 才能使用此功能。你还可以子类`org.springframework.aop.target.AbstractPoolingTargetSource`来支持任何其他池 API。

|   |Commons Pool1.5+ 也被支持,但在 Spring Framework4.2 中已被弃用。|
|---|---------------------------------------------------------------------------------|

下面的清单展示了一个配置示例:

```
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
        scope="prototype">
    ... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
    <property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="poolTargetSource"/>
    <property name="interceptorNames" value="myInterceptor"/>
</bean>
```

请注意,目标对象(在前面的示例中为`businessObjectTarget`)必须是原型。这允许`PoolingTargetSource`实现创建目标的新实例,以便在必要时增加池。参见[javadoc of`AbstractPoolingTargetSource`](https://DOCS. Spring.io/ Spring-framework/5.3.16/javadoc-api/org/springframework/ AOP/target/abstractpoolingtargetsource.html)和你希望用于获取其属性信息的具体子类。`maxSize`是最基本的,并且总是保证存在。

在这种情况下,`myInterceptor`是需要在相同的 IOC 上下文中定义的拦截器的名称。但是,你不需要指定拦截器来使用池。如果你只想要池而不想要其他建议,那么根本不要设置`interceptorNames`属性。

你可以将 Spring 配置为能够将任何池对象强制转换到`org.springframework.aop.target.PoolingConfig`接口,该接口通过介绍公开有关池的配置和当前大小的信息。你需要定义类似于以下内容的顾问:

```
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
```

这个顾问是通过调用`AbstractPoolingTargetSource`类上的一个方便方法获得的,因此使用`MethodInvokingFactoryBean`。此顾问的名称(`poolConfigAdvisor`,此处)必须位于公开池对象的`ProxyFactoryBean`中的拦截器名称列表中。

强制转换的定义如下:

Java

```
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
```

Kotlin

```
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
```

|   |通常不需要池无状态的服务对象。我们不认为它应该是<br/>的默认选择,因为大多数无状态对象自然是线程安全的,并且如果资源被缓存,实例<br/>池是有问题的。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

通过使用自动代理,可以获得更简单的池。你可以设置任何自动代理创建者使用的`TargetSource`实现。

#### 6.9.3.原型目标源

设置“原型”目标源类似于设置池`TargetSource`。在这种情况下,每个方法调用都会创建一个新的目标实例。尽管在现代 JVM 中创建新对象的成本并不高,但连接新对象(满足其 IoC 依赖关系)的成本可能更高。因此,如果没有很好的理由,就不应该使用这种方法。

为此,你可以修改前面显示的`poolTargetSource`定义,如下所示(为了清楚起见,我们还更改了名称):

```
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
```

唯一的属性是目标的名称 Bean。继承在`TargetSource`实现中使用,以确保一致的命名。与池目标源一样,目标 Bean 必须是原型 Bean 定义。

#### 6.9.4.`ThreadLocal`目标来源

如果需要为每个传入请求(每个线程)创建一个对象,`ThreadLocal`目标源是有用的。`ThreadLocal`的概念提供了一个 JDK 范围内的功能,可以在线程旁边透明地存储资源。设置`ThreadLocalTargetSource`与为其他类型的目标源所解释的几乎相同,如下例所示:

```
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>
```

|   |当<br/>实例在多线程和多类加载器环境中不正确地使用它们时,会出现严重的问题(可能导致内存泄漏)。你<br/>应该始终考虑在其他类中包装 ThreadLocal,并且永远不要直接使用<br/>`ThreadLocal`本身(包装类中除外)。另外,你应该始终记住正确地设置和取消设置(后者只涉及对`ThreadLocal.set(null)`的调用)线程本地的资源。在<br/>的任何情况下都应该进行重置,因为不进行重置可能会导致有问题的行为。 Spring 的`ThreadLocal`支持为你实现了这一点,并且应该始终考虑使用`ThreadLocal`实例,而不使用其他适当的处理代码。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 6.10.定义新的建议类型

Spring  AOP 被设计为可扩展的。虽然拦截实现策略目前在内部使用,但除了围绕建议、抛出建议之前、返回建议之后的拦截之外,还可能支持任意的建议类型。

`org.springframework.aop.framework.adapter`包是一个 SPI 包,它允许在不改变核心框架的情况下添加对新的自定义建议类型的支持。对自定义`Advice`类型的唯一限制是,它必须实现`org.aopalliance.aop.Advice`标记接口。

有关更多信息,请参见[`org.springframework.aop.framework.adapter`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/ AOP/framework/adapter/package-frame.html)Javadoc。

## 7. 零安全

虽然 Java 不允许你用其类型系统来表示空安全,但是 Spring 框架现在在`org.springframework.lang`包中提供了以下注释,允许你声明 API 和字段的空性:

* [`@Nullable`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/lang/nullable.html):表示特定参数、返回值或字段可以`null`的注释。

* [`@NonNull`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/lang/nonnull.html):表示特定参数、返回值或字段不能`null`(对于参数/返回值和字段`@NonNullApi`和`@NonNullFields`分别不需要)的注释。

* [`@NonNullApi`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/lang/nonnullapi.html):包级别的注释,声明非 Null 作为参数和返回值的默认语义。

* [`@NonNullFields`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/lang/nonnullfields.html):在包级别的注释,声明 non-null 为字段的默认语义。

Spring 框架本身利用了这些注释,但它们也可以在任何基于 Spring 的 Java 项目中用于声明空安全的 API 和可选的空安全字段。目前还不支持泛型类型参数、varargs 和数组元素的可空性,但应该在即将发布的版本中支持,有关最新信息,请参见[SPR-15942](https://jira.spring.io/browse/SPR-15942)。预期在 Spring 框架版本(包括较小的版本)之间对可否定性声明进行微调。在方法体中使用的类型的可空性不在此特性的范围内。

|   |诸如 Reactor 和 Spring Data 之类的其他公共库提供了空安全 API,其<br/>使用了类似的空性安排,为<br/> Spring 应用程序开发人员提供了一致的整体体验。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 7.1.用例

除了为 Spring Framework API nullability 提供显式声明外,IDE 还可以使用这些注释(例如 Idea 或 Eclipse)来提供与空安全相关的有用的警告,以便在运行时避免`NullPointerException`。

它们还用于在 Kotlin 项目中使 Spring API 为空安全的,因为 Kotlin 原生地支持[零安全](https://kotlinlang.org/docs/reference/null-safety.html)。更多详细信息请参见[Kotlin support documentation](languages.html#kotlin-null-safety)。

### 7.2.JSR-305 元注释

Spring 注释是用[JSR 305](https://jcp.org/en/jsr/detail?id=305)注释(一种休眠但广泛传播的 JSR)进行元注释的。JSR-305 元注释允许 Idea 或 Kotlin 之类的工具供应商以通用的方式提供空安全支持,而无需对 Spring 注释进行硬编码支持。

为了利用 Spring 空安全 API,没有必要也不建议向项目 Classpath 添加 JSR-305 依赖项。只有基于 Spring 的库等在其代码库中使用空安全注释的项目才应该添加`com.google.code.findbugs:jsr305:3.0.2`带有`compileOnly` Gradle 配置或 Maven `provided`作用域,以避免编译警告。

## 8. 数据缓冲区和编解码器

Java 蔚来提供`ByteBuffer`,但是许多库在上面构建自己的字节缓冲区 API,特别是在网络操作中,重用缓冲区和/或使用直接缓冲区有利于性能。例如,Netty 具有`ByteBuf`层次结构, Undertow 使用 XNIO, Jetty 使用带有要释放的回调的池字节缓冲区,以此类推。`spring-core`模块提供了一组抽象来处理各种字节缓冲区 API,如下所示:

* [`DataBufferFactory`]抽象数据缓冲区的创建。

* [`DataBuffer`]表示一个字节缓冲区,它可能是[pooled](#databuffers-buffer-pooled)。

* [`DataBufferUtils`]提供了用于数据缓冲区的实用方法。

* [Codecs](#codecs)将数据缓冲流解码或编码到更高级别的对象中。

### 8.1.`DataBufferFactory`

`DataBufferFactory`用于以以下两种方式之一创建数据缓冲区:

1. 分配一个新的数据缓冲区,如果已知,可以选择预先指定容量,这是更有效的,即使`DataBuffer`的实现可以按需增长和收缩。

2. 包装现有的`byte[]`或`java.nio.ByteBuffer`,它使用`DataBuffer`实现来装饰给定数据,并且不涉及分配。

注意,WebFlux 应用程序不会直接创建`DataBufferFactory`,而是通过客户端的`ServerHttpResponse`或`ClientHttpRequest`访问它。工厂的类型取决于底层客户机或服务器,例如,对于反应堆网络,`NettyDataBufferFactory`,对于其他网络,`DefaultDataBufferFactory`。

### 8.2.`DataBuffer`

`DataBuffer`接口提供了与`java.nio.ByteBuffer`类似的操作,但也带来了一些额外的好处,其中一些是受 netty`ByteBuf`的启发。以下是部分福利清单:

* 以独立的位置读写,即不需要调用`flip()`来交替读写。

* 容量随需求增加,如`java.lang.StringBuilder`。

* 通过[`PooledDataBuffer`](#databuffers-buffer-pool)进行池缓冲区和引用计数。

* 将缓冲区查看为`java.nio.ByteBuffer`,`InputStream`,或`OutputStream`。

* 确定给定字节的索引或最后一个索引。

### 8.3.`PooledDataBuffer`

正如在[ByteBuffer](https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html)的 Javadoc 中所解释的,字节缓冲区可以是直接的,也可以是非直接的。直接缓冲区可以驻留在 Java 堆之外,从而消除了对本机 I/O 操作的复制需求。这使得直接缓冲区在通过套接字接收和发送数据时特别有用,但它们的创建和发布也更昂贵,这就产生了池缓冲区的想法。

`PooledDataBuffer`是`DataBuffer`的扩展,它有助于引用计数,这对于字节缓冲池是必不可少的。它是如何工作的?当分配了`PooledDataBuffer`时,引用计数为 1。调用`retain()`来增加计数,而调用`release()`来减少计数。只要计数大于 0,缓冲区就保证不会被释放。当计数减少到 0 时,可以释放池中的缓冲区,这实际上可能意味着将为缓冲区保留的内存返回到内存池。

请注意,在大多数情况下,与其直接在`PooledDataBuffer`上进行操作,不如使用`DataBufferUtils`中的便利方法,这些方法仅当`DataBuffer`是`PooledDataBuffer`的实例时才将释放或保留应用于`DataBuffer`。

### 8.4.`DataBufferUtils`

`DataBufferUtils`提供了许多对数据缓冲区进行操作的实用方法:

* 如果底层的 Byte Buffer API 支持,那么将数据缓冲流连接到一个可能没有复制的缓冲区中,例如通过复合缓冲区。

* 将`InputStream`或蔚来`Channel`转换为`Flux<DataBuffer>`,反之亦然,将`Publisher<DataBuffer>`转换为`OutputStream`或蔚来`Channel`。

* 如果缓冲区是`PooledDataBuffer`的实例,则释放或保留`DataBuffer`的方法。

* 从一个字节流中跳过或获取,直到一个特定的字节计数。

### 8.5.编解码器

`org.springframework.core.codec`包提供了以下策略接口:

* `Encoder`将`Publisher<T>`编码到数据缓冲流中。

* `Decoder`将`Publisher<DataBuffer>`解码为更高级别的对象流。

`spring-core`模块提供`byte[]`、`ByteBuffer`、`DataBuffer`、`Resource`以及`String`编码器和解码器实现。`spring-web`模块添加了 JacksonJSON、JacksonSmile、JAXB2、协议缓冲区和其他编码器和解码器。参见 WebFlux 部分中的[Codecs](web-reactive.html#webflux-codecs)。

### 8.6.使用`DataBuffer`

在使用数据缓冲区时,必须特别注意确保缓冲区被释放,因为它们可能是[pooled](#databuffers-buffer-pooled)。我们将使用编解码器来说明这是如何工作的,但这些概念更普遍地适用。让我们来看看编解码器内部必须做什么来管理数据缓冲区。

a`Decoder`是在创建更高级别的对象之前最后读取输入数据缓冲区的方法,因此它必须按以下方式释放它们:

1. 如果`Decoder`只读取每个输入缓冲区并准备立即释放它,则可以通过`DataBufferUtils.release(dataBuffer)`执行。

2. 如果`Decoder`正在使用`Flux`或`Mono`运算符,例如`flatMap`,`reduce`,以及其他在内部预取和缓存数据项的运算符,或者正在使用`filter`、`skip`以及其他省略项的运算符,然后`doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)`必须被添加到合成链中,以确保这样的缓冲区在被丢弃之前被释放,这也可能是错误或取消信号的结果。

3. 如果`Decoder`以任何其他方式保持一个或多个数据缓冲区,则必须确保它们在完全读取时被释放,或者在缓存的数据缓冲区已被读取和释放之前发生错误或取消信号的情况下被释放。

请注意,`DataBufferUtils#join`提供了一种安全有效的方法,可以将数据缓冲流聚合到单个数据缓冲区中。同样,`skipUntilByteCount`和`takeUntilByteCount`也是供解码器使用的附加安全方法。

`Encoder`分配其他人必须读取(并释放)的数据缓冲区。所以`Encoder`不需要做太多的事情。但是,如果在用数据填充缓冲区时出现序列化错误,`Encoder`必须注意释放数据缓冲区。例如:

Java

```
DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
    // serialize and populate buffer..
    release = false;
}
finally {
    if (release) {
        DataBufferUtils.release(buffer);
    }
}
return buffer;
```

Kotlin

```
val buffer = factory.allocateBuffer()
var release = true
try {
    // serialize and populate buffer..
    release = false
} finally {
    if (release) {
        DataBufferUtils.release(buffer)
    }
}
return buffer
```

`Encoder`的使用者负责释放它接收到的数据缓冲区。在 WebFlux 应用程序中,`Encoder`的输出用于写到 HTTP 服务器的响应,或写到客户端的 HTTP 请求,在这种情况下,释放数据缓冲区是负责将代码写到服务器响应,或写到客户端请求。

请注意,在 Netty 上运行时,有[缓冲区泄漏故障排除](https://github.com/netty/netty/wiki/Reference-counted-objects#troubleshooting-buffer-leaks)的调试选项。

## 9. 伐木

由于 Spring Framework5.0, Spring 自带了在`spring-jcl`模块中实现的 Commons 日志记录桥。该实现检查 Classpath 中是否存在日志 4j2.x API 和 SLF4j1.7API,并使用其中发现的第一个作为日志实现,如果 log4j2.x 和 SLF4j 都不可用,则返回到 Java 平台的核心日志记录功能(也称为*JUL*或`java.util.logging`)。

在 Classpath 中放置 log4j2.x 或 logback(或另一个 SLF4j 提供程序),不需要任何额外的桥接器,并让框架自动适应你的选择。有关更多信息,请参见[Spring Boot Logging Reference Documentation](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-logging)。

|   |Spring 的 Commons 日志记录变体仅用于核心框架和扩展中的基础设施日志记录<br/>目的。<br/><br/>对于应用程序代码中的日志记录需求,请直接使用 log4j2.x、SLF4j 或 jul。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

一个`Log`实现可以通过`org.apache.commons.logging.LogFactory`检索,如下面的示例中所示。

爪哇

```
public class MyBean {
    private final Log log = LogFactory.getLog(getClass());
    // ...
}
```

Kotlin

```
class MyBean {
  private val log = LogFactory.getLog(javaClass)
  // ...
}
```

## 10. 附录

### 10.1.XML 模式

附录的这一部分列出了与核心容器相关的 XML 模式。

#### 10.1.1.`util`模式

顾名思义,`util`标记处理常见的实用程序配置问题,例如配置集合、引用常量等等。要在`util`模式中使用标记,你需要在 Spring XML 配置文件的顶部具有以下前导符(代码片段中的文本引用了正确的模式,以便你可以使用`util`命名空间中的标记):

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">

        <!-- bean definitions here -->

</beans>
```

#####  使用`<util:constant/>`

考虑以下 Bean 定义:

```
<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>
```

前面的配置使用 Spring `FactoryBean`实现(`FieldRetrievingFactoryBean`)将 Bean 上`isolation`属性的值设置为`java.sql.Connection.TRANSACTION_SERIALIZABLE`常量的值。这一切都很好,但它很冗长,(不必要地)向最终用户暴露了 Spring 的内部管道。

以下基于 XML 模式的版本更简洁,清楚地表达了开发人员的意图(“注入这个常量值”),并且读起来更好:

```
<bean id="..." class="...">
    <property name="isolation">
        <util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
    </property>
</bean>
```

###### 从字段值设置 Bean 属性或构造函数参数

[`FieldRetrievingFactoryBean`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/config/fieldretrievingfactorybean.html)是一个`FactoryBean`,用于检索`static`或非静态字段的值。它通常用于检索`public``static``final`常数,然后可用于为另一个 Bean 设置属性值或构造函数参数。

下面的示例显示了如何通过使用[`staticField`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/config/fieldretrievingfactorybean.html#setstaticfield)属性来公开<gtr="6045"/>字段:

```
<bean id="myField"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
    <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>
```

还有一种方便使用表单,其中`static`字段被指定为 Bean 名称,如下例所示:

```
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
```

这确实意味着,在 Bean `id`中不再有任何选择(因此,任何其他 Bean 引用它的名称也必须使用这个更长的名称),但是这种形式的定义非常简洁,并且非常方便地用作内部 Bean,因为`id`不必为 Bean 引用指定,如下面的示例所示:

```
<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>
```

你还可以访问另一个 Bean 的非静态(实例)字段,如[`FieldRetrievingFactoryBean`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/beans/Factory/config/fieldretrievingfactorybean.html)类的 API 文档中所述。

在 Spring 中,很容易将枚举值作为属性或构造函数参数注入到 bean 中。实际上,你不需要做任何事情,也不需要知道任何关于 Spring 内部(或者甚至关于类,例如`FieldRetrievingFactoryBean`)的事情。下面的例举枚举显示了注入一个枚举值是多么容易:

爪哇

```
package javax.persistence;

public enum PersistenceContextType {

    TRANSACTION,
    EXTENDED
}
```

Kotlin

```
package javax.persistence

enum class PersistenceContextType {

    TRANSACTION,
    EXTENDED
}
```

现在考虑以下类型`PersistenceContextType`的设置器和相应的 Bean 定义:

爪哇

```
package example;

public class Client {

    private PersistenceContextType persistenceContextType;

    public void setPersistenceContextType(PersistenceContextType type) {
        this.persistenceContextType = type;
    }
}
```

Kotlin

```
package example

class Client {

    lateinit var persistenceContextType: PersistenceContextType
}
```

```
<bean class="example.Client">
    <property name="persistenceContextType" value="TRANSACTION"/>
</bean>
```

#####  使用`<util:property-path/>`

考虑以下示例:

```
<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<bean id="testBean.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
```

前面的配置使用 Spring `FactoryBean`实现(`PropertyPathFactoryBean`)来创建 Bean(类型为`int`)称为`testBean.age`,其值等于`age`属性的`testBean` Bean。

现在考虑下面的示例,它添加了一个`<util:property-path/>`元素:

```
<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<util:property-path id="name" path="testBean.age"/>
```

`<property-path/>`元素的`path`属性的值遵循`beanName.beanProperty`的形式。在这种情况下,它获取名为`testBean`的 Bean 的`age`属性。`age`属性的值是`10`。

###### 使用`<util:property-path/>`设置 Bean 属性或构造函数参数 #####

`PropertyPathFactoryBean`是一个`FactoryBean`,它计算给定目标对象上的属性路径。目标对象可以直接指定,也可以通过 Bean 名称指定。然后,你可以在另一个 Bean 定义中使用该值作为属性值或构造函数参数。

下面的示例按名称显示了针对另一个 Bean 使用的路径:

```
<!-- target bean to be referenced by name -->
<bean id="person" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 11, which is the value of property 'spouse.age' of bean 'person' -->
<bean id="theAge"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetBeanName" value="person"/>
    <property name="propertyPath" value="spouse.age"/>
</bean>
```

在下面的示例中,根据内部 Bean 对路径进行求值:

```
<!-- results in 12, which is the value of property 'age' of the inner bean -->
<bean id="theAge"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetObject">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="12"/>
        </bean>
    </property>
    <property name="propertyPath" value="age"/>
</bean>
```

还有一种快捷方式,其中 Bean 名称是属性路径。下面的示例显示了快捷方式:

```
<!-- results in 10, which is the value of property 'age' of bean 'person' -->
<bean id="person.age"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
```

这种形式确实意味着在 Bean 的名称中没有选择。对它的任何引用也必须使用相同的`id`,这是路径。如果用作内部 Bean,则根本不需要引用它,如下例所示:

```
<bean id="..." class="...">
    <property name="age">
        <bean id="person.age"
                class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
    </property>
</bean>
```

你可以在实际定义中专门设置结果类型。对于大多数用例来说,这并不是必需的,但它有时是有用的。有关此功能的更多信息,请参见 爪哇doc。

#####  使用`<util:properties/>`

考虑以下示例:

```
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="location" value="classpath:com/foo/jdbc-production.properties"/>
</bean>
```

前面的配置使用 Spring `FactoryBean`实现(`PropertiesFactoryBean`)实例化一个`java.util.Properties`实例,该实例的值来自所提供的[`Resource`](#resources)位置)。

下面的示例使用`util:properties`元素来进行更简洁的表示:

```
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
```

#####  使用`<util:list/>`

考虑以下示例:

```
<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' -->
<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">
    <property name="sourceList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>
```

前面的配置使用 Spring `FactoryBean`实现(`ListFactoryBean`)来创建`java.util.List`实例,并使用来自提供的`sourceList`的值对其进行初始化。

下面的示例使用`<util:list/>`元素来进行更简洁的表示:

```
<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
</util:list>
```

还可以使用`<util:list/>`元素上的`list-class`属性显式地控制实例化和填充的`List`的确切类型。例如,如果我们确实需要实例化`java.util.LinkedList`,则可以使用以下配置:

```
<util:list id="emails" list-class="java.util.LinkedList">
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>d'[email protected]</value>
</util:list>
```

如果没有提供`list-class`属性,则容器选择一个`List`实现。

#####  使用`<util:map/>`

考虑以下示例:

```
<!-- creates a java.util.Map instance with values loaded from the supplied 'sourceMap' -->
<bean id="emails" class="org.springframework.beans.factory.config.MapFactoryBean">
    <property name="sourceMap">
        <map>
            <entry key="pechorin" value="[email protected]"/>
            <entry key="raskolnikov" value="[email protected]"/>
            <entry key="stavrogin" value="[email protected]"/>
            <entry key="porfiry" value="[email protected]"/>
        </map>
    </property>
</bean>
```

前面的配置使用 Spring `FactoryBean`实现(`MapFactoryBean`)来创建`java.util.Map`实例,该实例使用从提供的`'sourceMap'`中获取的键值对进行初始化。

下面的示例使用`<util:map/>`元素来进行更简洁的表示:

```
<!-- creates a java.util.Map instance with the supplied key-value pairs -->
<util:map id="emails">
    <entry key="pechorin" value="[email protected]"/>
    <entry key="raskolnikov" value="[email protected]"/>
    <entry key="stavrogin" value="[email protected]"/>
    <entry key="porfiry" value="[email protected]"/>
</util:map>
```

还可以使用`<util:map/>`元素上的`'map-class'`属性显式地控制实例化和填充的`Map`的确切类型。例如,如果我们确实需要实例化`java.util.TreeMap`,则可以使用以下配置:

```
<util:map id="emails" map-class="java.util.TreeMap">
    <entry key="pechorin" value="[email protected]"/>
    <entry key="raskolnikov" value="[email protected]"/>
    <entry key="stavrogin" value="[email protected]"/>
    <entry key="porfiry" value="[email protected]"/>
</util:map>
```

如果没有提供`'map-class'`属性,则容器选择一个`Map`实现。

#####  使用`<util:set/>`

考虑以下示例:

```
<!-- creates a java.util.Set instance with values loaded from the supplied 'sourceSet' -->
<bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean">
    <property name="sourceSet">
        <set>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </set>
    </property>
</bean>
```

前面的配置使用 Spring `FactoryBean`实现(`SetFactoryBean`)来创建`java.util.Set`实例,该实例初始化后的值取自所提供的`sourceSet`。

下面的示例使用`<util:set/>`元素来进行更简洁的表示:

```
<!-- creates a java.util.Set instance with the supplied values -->
<util:set id="emails">
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
</util:set>
```

还可以使用`<util:set/>`元素上的`set-class`属性显式地控制实例化和填充的`Set`的确切类型。例如,如果我们确实需要实例化`java.util.TreeSet`,我们可以使用以下配置:

```
<util:set id="emails" set-class="java.util.TreeSet">
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
</util:set>
```

如果不提供`set-class`属性,则容器选择一个`Set`实现。

#### 10.1.2.`aop`模式

`aop`标记用于配置 Spring 中的所有内容,包括 Spring 自己的基于代理的 AOP 框架和 Spring 与 AspectJ AOP 框架的集成。这些标记在标题为[Aspect Oriented Programming with Spring](#aop)的一章中得到了全面的介绍。

为了完整起见,要使用`aop`模式中的标记,你需要在 Spring XML 配置文件的顶部具有以下前导符(代码片段中的文本引用了正确的模式,以便`aop`名称空间中的标记对你可用):

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean definitions here -->

</beans>
```

#### 10.1.3.`context`模式

`context`标记处理的是与管道相关的`ApplicationContext`配置——也就是说,通常不是对最终用户很重要的 bean,而是在 Spring 中执行许多“grunt”工作的 bean,例如`BeanfactoryPostProcessors`。下面的代码片段引用了正确的模式,因此`context`名称空间中的元素对你是可用的:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- bean definitions here -->

</beans>
```

#####  使用`<property-placeholder/>`

此元素激活替换`${…​}`占位符,这些占位符是根据指定的属性文件解析的(如[Spring resource location](#resources))。这个元素是一个方便的机制,它为你设置了一个[`PropertySourcesPlaceholderConfigurer`](#beans-factory-placeholderconfigurer)。如果你需要对特定的`PropertySourcesPlaceholderConfigurer`设置进行更多的控制,那么你可以自己将其显式地定义为 Bean。

#####  使用`<annotation-config/>`

此元素激活 Spring 基础结构以检测 Bean 类中的注释:

* Spring 的[`@Configuration`]模型

* [`@Autowired`/`@Inject`],`@Value`,和`@Lookup`

* JSR-250 的`@Resource`,`@PostConstruct`,和`@PreDestroy`(如果可用)

* JAX-WS 的`@WebServiceRef`和 EJB3 的`@EJB`(如果可用)

* JPA 的`@PersistenceContext`和`@PersistenceUnit`(如果有)

* Spring 的[`@EventListener`](#context-funicity-events-annotation)

或者,你可以选择显式地为这些注释激活单独的`BeanPostProcessors`。

|   |此元素不激活 Spring 的[`@Transactional`](data-access.html#transaction-declarative-annotations)注释的处理;<br/>你可以为此目的使用[`<tx:annotation-driven/>`](data-access.html#tx-decl-explained)元素。类似地, Spring 的[缓存注释](integration.html#cache-annotations)也需要显式地[enabled](integration.html#cache-annotation-enable)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#####  使用`<component-scan/>`

这个元素在[基于注释的容器配置](#beans-annotation-config)一节中有详细介绍。

#####  使用`<load-time-weaver/>`

这个元素在[load-time weaving with AspectJ in the Spring Framework](#aop-aj-ltw)一节中有详细介绍。

#####  使用`<spring-configured/>`

这个元素在[using AspectJ to dependency inject domain objects with Spring](#aop-atconfigurable)一节中有详细介绍。

#####  使用`<mbean-export/>`

这个元素在[配置基于注释的 MBean 导出](integration.html#jmx-context-mbeanexport)一节中有详细介绍。

#### 10.1.4.Beans 模式

最后但并非最不重要的是,我们拥有`beans`模式中的元素。自该框架问世以来,这些要素就一直在 Spring 中。这里没有显示`beans`模式中的各种元素的示例,因为它们在[详细介绍依赖关系和配置](#beans-factory-properties-detailed)(实际上,在整个[chapter](#beans)中)中得到了相当全面的覆盖。

请注意,你可以向`<bean/>`XML 定义添加零个或多个键值对。使用这个额外的元数据所做的事情完全取决于你自己的自定义逻辑(因此,通常只有在你编写自己的自定义元素时才会使用,该自定义元素在标题为[XML 模式创作](#xml-custom)的附录中进行了描述)。

下面的示例在周围的`<bean/>`的上下文中显示了`<meta/>`元素(请注意,如果没有任何逻辑来解释它,元数据实际上是无用的)。

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="foo" class="x.y.Foo">
        <meta key="cacheName" value="foo"/> (1)
        <property name="name" value="Rick"/>
    </bean>

</beans>
```

|**1**|这就是`meta`元素的示例|
|-----|----------------------------------|

在前面的示例中,你可以假设有一些逻辑使用 Bean 定义,并设置了一些使用所提供的元数据的缓存基础设施。

### 10.2.XML 模式创作

自版本 2.0 以来, Spring 已经提供了一种机制,用于在用于定义和配置 bean 的基本 Spring XML 格式中添加基于模式的扩展。本节介绍如何编写你自己的定制 XML Bean 定义解析器,并将这些解析器集成到 Spring IOC 容器中。

为了便于编写使用模式感知 XML 编辑器的配置文件, Spring 的可扩展 XML 配置机制是基于 XML 模式的。如果你不熟悉 Spring 标准 Spring 发行版附带的当前 XML 配置扩展,那么你应该首先阅读关于[XML 模式](#xsd-schemas)的上一节。

要创建新的 XML 配置扩展:

1. [Author](#xsd-custom-schema)描述自定义元素的 XML 模式。

2. [Code](#xsd-custom-namespacehandler)一个自定义的`NamespaceHandler`实现。

3. [Code](#xsd-custom-parser)一个或多个`BeanDefinitionParser`实现(这是完成实际工作的地方)。

4. [Register](#xsd-custom-registration)带有 Spring 的新工件。

对于一个统一的示例,我们创建了一个 XML 扩展(一个自定义 XML 元素),它允许我们配置`SimpleDateFormat`类型的对象(来自`java.text`包)。完成后,我们将能够定义`SimpleDateFormat`类型的 Bean 定义,如下所示:

```
<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>
```

(我们将在本附录后面列出更详细的例子。第一个简单示例的目的是让你了解制作自定义扩展的基本步骤。)

#### 10.2.1.创建模式

创建用于 Spring 的 IOC 容器的 XML 配置扩展,首先要创建一个 XML 模式来描述该扩展。对于我们的示例,我们使用以下模式来配置`SimpleDateFormat`对象:

```
<!-- myns.xsd (inside package org/springframework/samples/xml) -->

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:beans="http://www.springframework.org/schema/beans"
        targetNamespace="http://www.mycompany.example/schema/myns"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">

    <xsd:import namespace="http://www.springframework.org/schema/beans"/>

    <xsd:element name="dateformat">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType"> (1)
                    <xsd:attribute name="lenient" type="xsd:boolean"/>
                    <xsd:attribute name="pattern" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>
```

|**1**|所指示的行包含所有可识别标记<br/>的扩展基础(这意味着它们具有`id`属性,我们可以将其用作<br/>容器中的 Bean 标识符)。我们可以使用这个属性,因为我们导入了 Spring 提供的`beans`名称空间。|
|-----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

前面的模式允许我们使用`<myns:dateformat/>`元素在 XML 应用程序上下文文件中直接配置`SimpleDateFormat`对象,如下例所示:

```
<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>
```

请注意,在我们创建了基础结构类之后,前面的 XML 片段与下面的 XML 片段本质上是相同的:

```
<bean id="dateFormat" class="java.text.SimpleDateFormat">
    <constructor-arg value="yyyy-MM-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>
```

前面两个片段中的第二个片段在容器中创建了一个 Bean(由`dateFormat`类型的名称`SimpleDateFormat`标识),其中设置了几个属性。

|   |基于模式的创建配置格式的方法允许与具有模式感知 XML 编辑器的 IDE 紧密集成<br/>。通过使用适当编写的模式,<br/>可以使用自动补全功能,让用户在枚举中定义的几个配置选项<br/>之间进行选择。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 10.2.2.编码 a`NamespaceHandler`

除了模式之外,我们还需要一个`NamespaceHandler`来解析 Spring 在解析配置文件时遇到的这个特定名称空间的所有元素。对于这个示例,`NamespaceHandler`应该负责解析`myns:dateformat`元素。

`NamespaceHandler`接口具有三种方法:

* `init()`:允许初始化`NamespaceHandler`,并在使用处理程序之前由 Spring 调用。

* `BeanDefinition parse(Element, ParserContext)`:当 Spring 遇到顶级元素(不嵌套在 Bean 定义或不同的名称空间中)时调用。该方法本身可以注册 Bean 定义,返回 Bean 定义,或者两者兼而有之。

* `BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)`:当 Spring 遇到不同名称空间的属性或嵌套元素时调用。 Bean 定义的一个或多个装饰(例如)与[scopes that Spring supports](#beans-factory-scopes)一起使用。我们首先突出显示一个简单的示例,而不使用装饰,然后我们在一个更高级的示例中显示装饰。

尽管你可以为整个命名空间编写自己的`NamespaceHandler`代码(因此提供了解析命名空间中每个元素的代码),通常情况下, Spring XML 配置文件中的每个顶级 XML 元素都会导致一个 Bean 定义(就像我们的情况一样,单个`<myns:dateformat/>`元素会导致一个`SimpleDateFormat` Bean 定义)。 Spring 支持此场景的许多方便类的特征。在下面的示例中,我们使用`NamespaceHandlerSupport`类:

爪哇

```
package org.springframework.samples.xml;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MyNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
    }
}
```

Kotlin

```
package org.springframework.samples.xml

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class MyNamespaceHandler : NamespaceHandlerSupport {

    override fun init() {
        registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
    }
}
```

你可能会注意到,在这个类中实际上并没有大量的解析逻辑。的确,`NamespaceHandlerSupport`类有一个内置的委托概念。它支持注册任意数量的`BeanDefinitionParser`实例,当需要解析名称空间中的元素时,它将委托给这些实例。这种对关注点的清晰分离使`NamespaceHandler`能够处理其名称空间中所有自定义元素的解析的编排,同时将任务委托给`BeanDefinitionParsers`来执行 XML 解析的繁重工作。这意味着每个`BeanDefinitionParser`只包含用于解析单个自定义元素的逻辑,正如我们在下一步中所看到的那样。

#### 10.2.3.使用`BeanDefinitionParser`

如果`BeanDefinitionParser`遇到已映射到特定 Bean 定义解析器(本例中`dateformat`)的类型的 XML 元素,则使用`BeanDefinitionParser`。换句话说,`BeanDefinitionParser`负责解析模式中定义的一个不同的顶级 XML 元素。在解析器中,我们可以访问 XML 元素(因此也可以访问它的子元素),这样我们就可以解析自定义的 XML 内容,正如你在下面的示例中所看到的那样:

爪哇

```
package org.springframework.samples.xml;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

import java.text.SimpleDateFormat;

public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { (1)

    protected Class getBeanClass(Element element) {
        return SimpleDateFormat.class; (2)
    }

    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        // this will never be null since the schema explicitly requires that a value be supplied
        String pattern = element.getAttribute("pattern");
        bean.addConstructorArgValue(pattern);

        // this however is an optional property
        String lenient = element.getAttribute("lenient");
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
        }
    }

}
```

|**1**|我们使用 Spring-提供的`AbstractSingleBeanDefinitionParser`来处理大量的<br/>创建单个`BeanDefinition`的基本繁重工作。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们为`AbstractSingleBeanDefinitionParser`超类提供了我们的<br/>单变量`BeanDefinition`所表示的类型。|

Kotlin

```
package org.springframework.samples.xml

import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
import org.springframework.util.StringUtils
import org.w3c.dom.Element

import java.text.SimpleDateFormat

class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() { (1)

    override fun getBeanClass(element: Element): Class<*>? { (2)
        return SimpleDateFormat::class.java
    }

    override fun doParse(element: Element, bean: BeanDefinitionBuilder) {
        // this will never be null since the schema explicitly requires that a value be supplied
        val pattern = element.getAttribute("pattern")
        bean.addConstructorArgValue(pattern)

        // this however is an optional property
        val lenient = element.getAttribute("lenient")
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient))
        }
    }
}
```

|**1**|我们使用 Spring-提供的`AbstractSingleBeanDefinitionParser`来处理大量<br/>创建单个`BeanDefinition`的基本繁重工作。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们用我们的<br/>表示的类型提供`AbstractSingleBeanDefinitionParser`超类。|

在这个简单的例子中,这就是我们需要做的所有事情。我们的单个`BeanDefinition`的创建由`AbstractSingleBeanDefinitionParser`超类处理, Bean 定义的唯一标识符的提取和设置也是如此。

#### 10.2.4.注册处理程序和模式

编码完成了。剩下要做的就是让 Spring XML 解析基础结构了解我们的自定义元素。我们通过在两个特殊用途的属性文件中注册自定义`namespaceHandler`和自定义 XSD 文件来实现这一目的。这些属性文件都放置在应用程序中的`META-INF`目录中,并且可以与 jar 文件中的二进制类一起分发。 Spring XML 解析基础结构通过使用这些特殊的属性文件自动获取你的新扩展名,其格式将在接下来的两节中详细介绍。

#####  写作`META-INF/spring.handlers`

名为`spring.handlers`的属性文件包含 XML 模式 URI 到名称空间处理程序类的映射。对于我们的示例,我们需要编写以下内容:

```
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
```

(`:`字符在 爪哇 Properties 格式中是一个有效的分隔符,因此需要用反斜杠转义 URI 中的`:`字符。

键-值对的第一部分(键)是与自定义名称空间扩展关联的 URI,并且需要完全匹配`targetNamespace`属性的值,如在自定义 XSD 模式中所指定的那样。

#####  撰写“meta-inf/ Spring.schemas”

名为`spring.schemas`的属性文件包含 XML 模式位置(连同模式声明一起,在 XML 文件中使用模式作为`xsi:schemaLocation`属性的一部分)到 Classpath 资源的映射。这个文件是必需的,以防止 Spring 绝对必须使用默认的`EntityResolver`,该默认`EntityResolver`需要互联网访问来检索模式文件。如果在此属性文件中指定映射, Spring 将在 Classpath 上搜索模式(在本例中,`org.springframework.samples.xml`包中的`myns.xsd`)。下面的代码片段显示了我们需要为自定义模式添加的行:

```
http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
```

(请记住,`:`字符必须转义。

鼓励你在 Classpath 上的`NamespaceHandler`和`BeanDefinitionParser`类旁边部署你的 XSD 文件(或多个文件)。

#### 10.2.5.在 Spring XML 配置中使用自定义扩展

使用你自己实现的自定义扩展与使用 Spring 提供的“自定义”扩展之一没有什么不同。下面的示例在 Spring XML 配置文件中使用了在前面的步骤中开发的自定义`<dateformat/>`元素:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:myns="http://www.mycompany.example/schema/myns"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">

    <!-- as a top-level bean -->
    <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> (1)

    <bean id="jobDetailTemplate" abstract="true">
        <property name="dateFormat">
            <!-- as an inner bean -->
            <myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
        </property>
    </bean>

</beans>
```

|**1**|我们的习俗 Bean。|
|-----|----------------|

#### 10.2.6.更详细的例子

本节介绍了定制 XML 扩展的一些更详细的示例。

#####  在自定义元素中嵌套自定义元素

本节中展示的示例展示了如何编写满足以下配置的目标所需的各种工件:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:foo="http://www.foo.example/schema/component"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">

    <foo:component id="bionic-family" name="Bionic-1">
        <foo:component name="Mother-1">
            <foo:component name="Karate-1"/>
            <foo:component name="Sport-1"/>
        </foo:component>
        <foo:component name="Rock-1"/>
    </foo:component>

</beans>
```

前面的配置相互嵌套自定义扩展。由`<foo:component/>`元素实际配置的类是`Component`类(如下一个示例所示)。请注意`Component`类并不公开`components`属性的 setter 方法。这使得很难(或者说不可能)通过使用 setter 注入来为`Component`类配置 Bean 定义。下面的清单显示了`Component`类:

爪哇

```
package com.foo;

import java.util.ArrayList;
import java.util.List;

public class Component {

    private String name;
    private List<Component> components = new ArrayList<Component> ();

    // mmm, there is no setter method for the 'components'
    public void addComponent(Component component) {
        this.components.add(component);
    }

    public List<Component> getComponents() {
        return components;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
```

Kotlin

```
package com.foo

import java.util.ArrayList

class Component {

    var name: String? = null
    private val components = ArrayList<Component>()

    // mmm, there is no setter method for the 'components'
    fun addComponent(component: Component) {
        this.components.add(component)
    }

    fun getComponents(): List<Component> {
        return components
    }
}
```

这个问题的典型解决方案是创建一个自定义`FactoryBean`,它为`components`属性公开一个 setter 属性。下面的清单显示了这样的自定义`FactoryBean`:

爪哇

```
package com.foo;

import org.springframework.beans.factory.FactoryBean;

import java.util.List;

public class ComponentFactoryBean implements FactoryBean<Component> {

    private Component parent;
    private List<Component> children;

    public void setParent(Component parent) {
        this.parent = parent;
    }

    public void setChildren(List<Component> children) {
        this.children = children;
    }

    public Component getObject() throws Exception {
        if (this.children != null && this.children.size() > 0) {
            for (Component child : children) {
                this.parent.addComponent(child);
            }
        }
        return this.parent;
    }

    public Class<Component> getObjectType() {
        return Component.class;
    }

    public boolean isSingleton() {
        return true;
    }
}
```

Kotlin

```
package com.foo

import org.springframework.beans.factory.FactoryBean
import org.springframework.stereotype.Component

class ComponentFactoryBean : FactoryBean<Component> {

    private var parent: Component? = null
    private var children: List<Component>? = null

    fun setParent(parent: Component) {
        this.parent = parent
    }

    fun setChildren(children: List<Component>) {
        this.children = children
    }

    override fun getObject(): Component? {
        if (this.children != null && this.children!!.isNotEmpty()) {
            for (child in children!!) {
                this.parent!!.addComponent(child)
            }
        }
        return this.parent
    }

    override fun getObjectType(): Class<Component>? {
        return Component::class.java
    }

    override fun isSingleton(): Boolean {
        return true
    }
}
```

这很好地工作,但它向最终用户暴露了大量的管道系统。我们要做的是编写一个自定义扩展,以隐藏所有这些管道。如果我们坚持使用[前面描述的步骤](#xsd-custom-introduction),那么我们首先创建 XSD 模式来定义自定义标记的结构,如下面的清单所示:

```
<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.foo.example/schema/component"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/component"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">

    <xsd:element name="component">
        <xsd:complexType>
            <xsd:choice minOccurs="0" maxOccurs="unbounded">
                <xsd:element ref="component"/>
            </xsd:choice>
            <xsd:attribute name="id" type="xsd:ID"/>
            <xsd:attribute name="name" use="required" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>
```

同样在[前面描述的过程](#xsd-custom-introduction)之后,我们将创建一个自定义`NamespaceHandler`:

爪哇

```
package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class ComponentNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
    }
}
```

Kotlin

```
package com.foo

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class ComponentNamespaceHandler : NamespaceHandlerSupport() {

    override fun init() {
        registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
    }
}
```

接下来是自定义`BeanDefinitionParser`。请记住,我们正在创建一个`BeanDefinition`来描述`ComponentFactoryBean`。下面的清单显示了我们的自定义`BeanDefinitionParser`实现:

爪哇

```
package com.foo;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import java.util.List;

public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {

    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        return parseComponentElement(element);
    }

    private static AbstractBeanDefinition parseComponentElement(Element element) {
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
        factory.addPropertyValue("parent", parseComponent(element));

        List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
        if (childElements != null && childElements.size() > 0) {
            parseChildComponents(childElements, factory);
        }

        return factory.getBeanDefinition();
    }

    private static BeanDefinition parseComponent(Element element) {
        BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
        component.addPropertyValue("name", element.getAttribute("name"));
        return component.getBeanDefinition();
    }

    private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
        ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
        for (Element element : childElements) {
            children.add(parseComponentElement(element));
        }
        factory.addPropertyValue("children", children);
    }
}
```

Kotlin

```
package com.foo

import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.support.ManagedList
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser
import org.springframework.beans.factory.xml.ParserContext
import org.springframework.util.xml.DomUtils
import org.w3c.dom.Element

import java.util.List

class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() {

    override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? {
        return parseComponentElement(element)
    }

    private fun parseComponentElement(element: Element): AbstractBeanDefinition {
        val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java)
        factory.addPropertyValue("parent", parseComponent(element))

        val childElements = DomUtils.getChildElementsByTagName(element, "component")
        if (childElements != null && childElements.size > 0) {
            parseChildComponents(childElements, factory)
        }

        return factory.getBeanDefinition()
    }

    private fun parseComponent(element: Element): BeanDefinition {
        val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java)
        component.addPropertyValue("name", element.getAttribute("name"))
        return component.beanDefinition
    }

    private fun parseChildComponents(childElements: List<Element>, factory: BeanDefinitionBuilder) {
        val children = ManagedList<BeanDefinition>(childElements.size)
        for (element in childElements) {
            children.add(parseComponentElement(element))
        }
        factory.addPropertyValue("children", children)
    }
}
```

最后,需要通过修改`META-INF/spring.handlers`和`META-INF/spring.schemas`文件,将各种工件注册到 Spring XML 基础结构中,如下所示:

```
# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
```

```
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
```

#####  “正常”元素上的自定义属性

编写自己的自定义解析器和相关的工件并不难。然而,这有时并不是正确的做法。考虑这样一个场景,你需要将元数据添加到已经存在的 Bean 定义中。在这种情况下,你当然不希望不得不编写自己的整个自定义扩展。相反,你只是希望向现有的 Bean 定义元素添加一个额外的属性。

通过另一个示例,假设你为访问集群[JCache](https://jcp.org/en/jsr/detail?id=107)的服务对象定义了 Bean 定义,并且你希望确保命名的 JCache 实例在周围的集群中急切地启动。下面的清单显示了这样的定义:

```
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
        jcache:cache-name="checking.account">
    <!-- other dependencies here... -->
</bean>
```

然后,我们可以在解析`'jcache:cache-name'`属性时创建另一个`BeanDefinition`。这`BeanDefinition`然后为我们初始化命名的 JCache。我们还可以修改`BeanDefinition`的`'checkingAccountService'`的现有`BeanDefinition`,使其对这个新的 JCache 具有依赖性-初始化`BeanDefinition`。下面的清单显示了我们的`JCacheInitializer`:

爪哇

```
package com.foo;

public class JCacheInitializer {

    private String name;

    public JCacheInitializer(String name) {
        this.name = name;
    }

    public void initialize() {
        // lots of JCache API calls to initialize the named cache...
    }
}
```

Kotlin

```
package com.foo

class JCacheInitializer(private val name: String) {

    fun initialize() {
        // lots of JCache API calls to initialize the named cache...
    }
}
```

现在,我们可以进入自定义扩展。首先,我们需要编写描述自定义属性的 XSD 模式,如下所示:

```
<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.foo.example/schema/jcache"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/jcache"
        elementFormDefault="qualified">

    <xsd:attribute name="cache-name" type="xsd:string"/>

</xsd:schema>
```

接下来,我们需要创建关联的`NamespaceHandler`,如下所示:

Java

```
package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class JCacheNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
            new JCacheInitializingBeanDefinitionDecorator());
    }

}
```

Kotlin

```
package com.foo

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class JCacheNamespaceHandler : NamespaceHandlerSupport() {

    override fun init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
                JCacheInitializingBeanDefinitionDecorator())
    }

}
```

接下来,我们需要创建解析器。请注意,在本例中,因为我们要解析一个 XML 属性,所以我们写一个`BeanDefinitionDecorator`,而不是`BeanDefinitionParser`。下面的清单显示了我们的`BeanDefinitionDecorator`实现:

Java

```
package com.foo;

import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {

    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
            ParserContext ctx) {
        String initializerBeanName = registerJCacheInitializer(source, ctx);
        createDependencyOnJCacheInitializer(holder, initializerBeanName);
        return holder;
    }

    private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
            String initializerBeanName) {
        AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
        String[] dependsOn = definition.getDependsOn();
        if (dependsOn == null) {
            dependsOn = new String[]{initializerBeanName};
        } else {
            List dependencies = new ArrayList(Arrays.asList(dependsOn));
            dependencies.add(initializerBeanName);
            dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
        }
        definition.setDependsOn(dependsOn);
    }

    private String registerJCacheInitializer(Node source, ParserContext ctx) {
        String cacheName = ((Attr) source).getValue();
        String beanName = cacheName + "-initializer";
        if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
            BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
            initializer.addConstructorArg(cacheName);
            ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
        }
        return beanName;
    }
}
```

Kotlin

```
package com.foo

import org.springframework.beans.factory.config.BeanDefinitionHolder
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.BeanDefinitionDecorator
import org.springframework.beans.factory.xml.ParserContext
import org.w3c.dom.Attr
import org.w3c.dom.Node

import java.util.ArrayList

class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator {

    override fun decorate(source: Node, holder: BeanDefinitionHolder,
                        ctx: ParserContext): BeanDefinitionHolder {
        val initializerBeanName = registerJCacheInitializer(source, ctx)
        createDependencyOnJCacheInitializer(holder, initializerBeanName)
        return holder
    }

    private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder,
                                                    initializerBeanName: String) {
        val definition = holder.beanDefinition as AbstractBeanDefinition
        var dependsOn = definition.dependsOn
        dependsOn = if (dependsOn == null) {
            arrayOf(initializerBeanName)
        } else {
            val dependencies = ArrayList(listOf(*dependsOn))
            dependencies.add(initializerBeanName)
            dependencies.toTypedArray()
        }
        definition.setDependsOn(*dependsOn)
    }

    private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String {
        val cacheName = (source as Attr).value
        val beanName = "$cacheName-initializer"
        if (!ctx.registry.containsBeanDefinition(beanName)) {
            val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java)
            initializer.addConstructorArg(cacheName)
            ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition())
        }
        return beanName
    }
}
```

最后,我们需要通过修改`META-INF/spring.handlers`和`META-INF/spring.schemas`文件,在 Spring XML 基础结构中注册各种工件,如下所示:

```
# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
```

```
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd
```

### 10.3.应用程序启动步骤

附录的这一部分列出了核心容器所使用的现有`StartupSteps`。

|   |关于每个启动步骤的名称和详细信息不是公共契约的一部分,<br/>可能会发生更改;这被认为是核心容器的实现细节,并且将随着<br/>其行为的变化而变化。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|                     Name                     |说明|                                        Tags                                         |
|----------------------------------------------|----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
|          `spring.beans.instantiate`          |Bean 及其依赖关系的实例化。|`beanName` the name of the bean, `beanType` the type required at the injection point.|
|       `spring.beans.smart-initialize`        |初始化`SmartInitializingSingleton`bean。|                          `beanName` the name of the bean.                           |
|`spring.context.annotated-bean-reader.create` |创建`AnnotatedBeanDefinitionReader`。|                                                                                     |
|     `spring.context.base-packages.scan`      |扫描基本包装。|                   `packages` array of base packages for scanning.                   |
|     `spring.context.beans.post-process`      |beans 后处理阶段。|                                                                                     |
|  `spring.context.bean-factory.post-process`  |调用`BeanFactoryPostProcessor`bean。|                     `postProcessor` the current post-processor.                     |
|`spring.context.beandef-registry.post-process`|调用`BeanDefinitionRegistryPostProcessor`bean。|                     `postProcessor` the current post-processor.                     |
| `spring.context.component-classes.register`  |通过`AnnotationConfigApplicationContext#register`注册组件类。|                 `classes` array of given classes for registration.                  |
|   `spring.context.config-classes.enhance`    |使用 CGlib 代理增强配置类。|                       `classCount` count of enhanced classes.                       |
|    `spring.context.config-classes.parse`     |配置类解析阶段使用`ConfigurationClassPostProcessor`。|                      `classCount` count of processed classes.                       |
|           `spring.context.refresh`           |应用程序上下文刷新阶段。|                                                                                     |