├── README.textile ├── pom.xml ├── spring-rabbitmq.iml ├── spring-rabbitmq.ipr └── src ├── main └── java │ └── com │ └── rabbitmq │ └── spring │ ├── ExchangeType.java │ ├── InvalidRoutingKeyException.java │ ├── UnroutableException.java │ ├── channel │ └── RabbitChannelFactory.java │ ├── connection │ └── RabbitConnectionFactory.java │ ├── listener │ └── RabbitMessageListenerAdapter.java │ ├── remoting │ ├── RabbitInvokerClientInterceptor.java │ ├── RabbitInvokerProxyFactoryBean.java │ ├── RabbitInvokerServiceExporter.java │ └── RabbitRpcClient.java │ └── template │ ├── ASyncRabbitTemplate.java │ └── RabbitTemplate.java └── test ├── java └── com │ └── rabbitmq │ └── spring │ └── example │ ├── ExampleDelegate.java │ ├── ExampleObject.java │ ├── ExampleService.java │ └── ExampleServiceImpl.java └── resources ├── log4j.xml ├── rabbitmq.properties └── spring-examples.xml /README.textile: -------------------------------------------------------------------------------- 1 | Spring integration for RabbitMQ 2 | 3 | h2. What is it for? 4 | 5 | These components help using RabbitMQ with the Spring Framework. 6 | Initial goal was to provide an alternative for the spring jms components giving the posibility to use rabbitmq in a similar way. 7 | This initial version contains a simple connection and channel factory (currently only with single connection). 8 | 9 | Example spring configuration(s) is provided in src/main/test/spring-examples.xml 10 | 11 | h2. Descriptions 12 | 13 | |_. Component |_. Description | 14 | | RabbitTemplate | Send objects to injected exchange | 15 | | AsyncRabbitTemplate | Same as RabbitTempate, but uses internal queue and worker so calls to the send method are non-blocking | 16 | | RabbitMessageListenerAdapter | Listens for incoming object messages and delegate the handling to delegate object trying to find a handleMessage method taking the specific object as a parameter | 17 | | RabbitInvokerServerExporter | Exports spring service interface to a direct queue or and exchange, handling springs remote invocation calls | 18 | | RabbitInvokerProxyFactoryBean | Proxy bean to invoke a remote exported service by intercepting method calls on proxied interface and sending a remote invocation over rabbitmq | 19 | 20 | h2. Future plans 21 | 22 | * Merge/integrate with "amqp-spring":http://github.com/yawn/amqp-spring/tree from "yawn":http://github.com/yawn making it more spring-amqp generic with rabbitmq implementation 23 | * Better threading/pooling 24 | * Lazy connection/channel initalization (So connection doesn't block startup when rabbitmq is not running) 25 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.momania.spring 5 | spring-rabbitmq 6 | 0.1 7 | Spring RabbitMQ 8 | Spring integration for RabbitMQ 9 | jar 10 | 11 | 12 | 13 | 14 | org.apache.maven.plugins 15 | maven-compiler-plugin 16 | 2.0.2 17 | 18 | 1.5 19 | 1.5 20 | 21 | 22 | 23 | 24 | 25 | 26 | 1.5.4 27 | 2.5.6 28 | 2.4 29 | 1.4 30 | 1.2.15 31 | 5.7 32 | 33 | 34 | 35 | 36 | 37 | com.rabbitmq 38 | amqp-client 39 | ${rabbitmq.version} 40 | 41 | 42 | 43 | org.springframework 44 | spring-beans 45 | ${spring.version} 46 | 47 | 48 | org.springframework 49 | spring-aop 50 | ${spring.version} 51 | 52 | 53 | org.springframework 54 | spring-context 55 | ${spring.version} 56 | 57 | 58 | 59 | commons-lang 60 | commons-lang 61 | ${commons-lang.version} 62 | 63 | 64 | commons-io 65 | commons-io 66 | ${commons-io.version} 67 | 68 | 69 | 70 | log4j 71 | log4j 72 | ${log4j.version} 73 | test 74 | 75 | 76 | org.springframework 77 | spring-test 78 | ${spring.version} 79 | test 80 | 81 | 82 | org.testng 83 | testng 84 | ${testng.version} 85 | jdk15 86 | test 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /spring-rabbitmq.iml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /spring-rabbitmq.ipr: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | true 35 | ASK 36 | -4144960 37 | false 38 | true 39 | 0 40 | false 41 | -65536 42 | 0 43 | false 44 | 1.3 45 | 46 | 2000 47 | 5000 48 | true 49 | true 50 | true 51 | -3605816 52 | false 53 | false 54 | -12566464 55 | 56 | true 57 | true 58 | -32 59 | -341816 60 | false 61 | 62 | true 63 | GLOBAL 64 | TEST_CASES 65 | true 66 | 67 | -16711936 68 | true 69 | true 70 | true 71 | true 72 | -14336 73 | 74 | false 75 | false 76 | true 77 | true 78 | true 79 | 0 80 | -256 81 | APP_CLASSES_ONLY 82 | -3920 83 | true 84 | 85 | 86 | 89 | 90 | 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 | 312 | 313 | 315 | 316 | 322 | 323 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 351 | 352 | 360 | 361 | 362 | 370 | 371 | 372 | 381 | 382 | 404 | 409 | 414 | 416 | 417 | 423 | 424 | 440 | 441 | 448 | 449 | 452 | 453 | 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 | 583 | 584 | 585 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 601 | 602 | 603 | 609 | 610 | 611 | 612 | 613 | 623 | 624 | 626 | 627 | 628 | 629 | 630 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 676 | 695 | 696 | 697 | 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 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/ExchangeType.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring; 2 | 3 | public enum ExchangeType { 4 | 5 | DIRECT { 6 | @Override 7 | public void validateRoutingKey(String routingKey) { 8 | if (routingKey.contains("#") || routingKey.contains("*")) { 9 | throw new InvalidRoutingKeyException( 10 | String.format("Routing key for exchange type %s may not contain wildcards (* of #)", this)); 11 | } 12 | } 13 | }, 14 | FANOUT, 15 | TOPIC; 16 | 17 | public void validateRoutingKey(String routingKey) { 18 | // do nothing 19 | } 20 | 21 | 22 | @Override 23 | public String toString() { 24 | return super.toString().toLowerCase(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/InvalidRoutingKeyException.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring; 2 | 3 | public class InvalidRoutingKeyException extends RuntimeException { 4 | 5 | public InvalidRoutingKeyException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/UnroutableException.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring; 2 | 3 | public class UnroutableException extends RuntimeException { 4 | 5 | public UnroutableException() { 6 | } 7 | 8 | public UnroutableException(String message) { 9 | super(message); 10 | } 11 | 12 | public UnroutableException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public UnroutableException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/channel/RabbitChannelFactory.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.channel; 2 | 3 | import com.rabbitmq.spring.connection.RabbitConnectionFactory; 4 | import com.rabbitmq.client.*; 5 | import org.apache.commons.logging.Log; 6 | import org.apache.commons.logging.LogFactory; 7 | import org.springframework.beans.factory.DisposableBean; 8 | 9 | import java.io.IOException; 10 | import java.lang.ref.Reference; 11 | import java.lang.ref.WeakReference; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | public class RabbitChannelFactory implements DisposableBean, ShutdownListener { 16 | 17 | public static final int DEFAULT_CLOSE_CODE = AMQP.REPLY_SUCCESS; 18 | public static final String DEFAULT_CLOSE_MESSAGE = "Goodbye"; 19 | 20 | private static final Log log = LogFactory.getLog(RabbitChannelFactory.class); 21 | 22 | private RabbitConnectionFactory connectionFactory; 23 | private int closeCode = DEFAULT_CLOSE_CODE; 24 | private String closeMessage = DEFAULT_CLOSE_MESSAGE; 25 | 26 | private final Set> channelReferenceSet = new HashSet>(); 27 | 28 | public void setConnectionFactory(RabbitConnectionFactory connectionFactory) { 29 | this.connectionFactory = connectionFactory; 30 | } 31 | 32 | public void setCloseCode(int closeCode) { 33 | this.closeCode = closeCode; 34 | } 35 | 36 | public void setCloseMessage(String closeMessage) { 37 | this.closeMessage = closeMessage; 38 | } 39 | 40 | public Channel createChannel() throws IOException { 41 | 42 | if (log.isDebugEnabled()) { 43 | log.debug("Creating channel"); 44 | } 45 | 46 | Connection connection = connectionFactory.getConnection(); 47 | connection.addShutdownListener(this); 48 | Channel channel = connection.createChannel(); 49 | channelReferenceSet.add(new WeakReference(channel)); 50 | 51 | if (log.isInfoEnabled()) { 52 | 53 | log.info(String.format("Created channel nr. %d", channel.getChannelNumber())); 54 | } 55 | return channel; 56 | } 57 | 58 | @Override 59 | public void destroy() throws Exception { 60 | closeChannels(); 61 | } 62 | 63 | private void closeChannels() { 64 | if (log.isInfoEnabled()) { 65 | log.info(String.format("Closing '%d' channels", channelReferenceSet.size())); 66 | } 67 | 68 | for (Reference channelReference : channelReferenceSet) { 69 | 70 | try { 71 | Channel channel = channelReference.get(); 72 | if (channel != null && channel.isOpen()) { 73 | if (channel.getConnection().isOpen()) { 74 | channel.close(closeCode, closeMessage); 75 | } 76 | } 77 | } catch (NullPointerException e) { 78 | log.error("Error closing channel", e); 79 | } catch (IOException e) { 80 | log.error("Error closing channel", e); 81 | } 82 | } 83 | if (log.isInfoEnabled()) { 84 | log.info("All channels closed"); 85 | } 86 | 87 | channelReferenceSet.clear(); 88 | 89 | } 90 | 91 | @Override 92 | public void shutdownCompleted(ShutdownSignalException cause) { 93 | if (cause.isInitiatedByApplication()) { 94 | if (log.isInfoEnabled()) { 95 | log.info(String.format("Shutdown by application completed for reference [%s] - reason [%s]" 96 | , cause.getReference(), cause.getReason())); 97 | } 98 | 99 | } else if (cause.isHardError()) { 100 | log.error(String.format("Hard error shutdown completed for reference [%s] - reason [%s]" 101 | , cause.getReference(), cause.getReason())); 102 | } 103 | if (log.isInfoEnabled()) { 104 | log.info("Shutdown completed"); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/connection/RabbitConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.connection; 2 | 3 | import com.rabbitmq.client.*; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import org.springframework.beans.factory.DisposableBean; 7 | import org.springframework.beans.factory.annotation.Required; 8 | import org.springframework.util.ObjectUtils; 9 | 10 | import java.io.IOException; 11 | import java.util.*; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public class RabbitConnectionFactory implements DisposableBean { 15 | 16 | private static final Log log = LogFactory.getLog(RabbitConnectionFactory.class); 17 | 18 | // spring injected 19 | private ConnectionFactory connectionFactory; 20 | private String[] hosts; 21 | private ShutdownListener[] shutdownListeners; 22 | 23 | private Connection connection; 24 | private Address[] knownHosts; 25 | 26 | public synchronized Connection getConnection() throws IOException { 27 | 28 | if (knownHosts == null) { 29 | collectInitialKnownHosts(); 30 | } 31 | 32 | while (connection == null || !connection.isOpen()) { 33 | ConnectionParameters connectionParameters = connectionFactory.getParameters(); 34 | 35 | if (log.isInfoEnabled()) { 36 | log.info(String.format("Establishing connection to one of [%s] using virtualhost [%s]" 37 | , ObjectUtils.nullSafeToString(hosts), connectionParameters.getVirtualHost())); 38 | } 39 | 40 | try { 41 | connection = connectionFactory.newConnection(knownHosts); 42 | 43 | // always keep the original hosts 44 | Set
hosts = new HashSet
(Arrays.asList(knownHosts)); 45 | hosts.addAll(Arrays.asList(connection.getKnownHosts())); 46 | knownHosts = hosts.toArray(new Address[hosts.size()]); 47 | 48 | if (log.isDebugEnabled()) { 49 | log.debug(String.format("New known hosts list is [%s]", ObjectUtils.nullSafeToString(knownHosts))); 50 | } 51 | 52 | addShutdownListeners(); 53 | 54 | if (log.isInfoEnabled()) { 55 | log.info(String.format("Connected to [%s:%d]", connection.getHost(), connection.getPort())); 56 | } 57 | } catch (Exception e) { 58 | log.error("Error connecting, trying again in 5 seconds...", e); 59 | try { 60 | TimeUnit.SECONDS.sleep(5); 61 | } catch (InterruptedException e1) { 62 | log.warn("Interrupted while waiting"); 63 | } 64 | } 65 | 66 | } 67 | 68 | return connection; 69 | 70 | } 71 | 72 | private void collectInitialKnownHosts() { 73 | List
addresses = new ArrayList
(hosts.length); 74 | for (String host : hosts) { 75 | addresses.add(Address.parseAddress(host)); 76 | } 77 | knownHosts = addresses.toArray(new Address[hosts.length]); 78 | } 79 | 80 | private void addShutdownListeners() { 81 | if (shutdownListeners != null) { 82 | for (ShutdownListener shutdownListener : shutdownListeners) { 83 | connection.addShutdownListener(shutdownListener); 84 | } 85 | } 86 | } 87 | 88 | @Override 89 | public void destroy() throws Exception { 90 | if (connection != null && connection.isOpen()) { 91 | connection.close(); 92 | } 93 | } 94 | 95 | @Required 96 | public void setConnectionFactory(ConnectionFactory connectionFactory) { 97 | this.connectionFactory = connectionFactory; 98 | } 99 | 100 | @Required 101 | public void setHosts(String[] hosts) { 102 | this.hosts = hosts; 103 | } 104 | 105 | public void setShutdownListeners(ShutdownListener... shutdownListeners) { 106 | this.shutdownListeners = shutdownListeners; 107 | } 108 | } -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/listener/RabbitMessageListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.listener; 2 | 3 | import com.rabbitmq.spring.ExchangeType; 4 | import com.rabbitmq.spring.channel.RabbitChannelFactory; 5 | import com.rabbitmq.client.*; 6 | import org.apache.commons.lang.SerializationUtils; 7 | import org.apache.commons.logging.Log; 8 | import org.apache.commons.logging.LogFactory; 9 | import org.springframework.beans.factory.InitializingBean; 10 | import org.springframework.beans.factory.annotation.Required; 11 | import org.springframework.util.MethodInvoker; 12 | import org.springframework.util.ObjectUtils; 13 | 14 | import java.io.IOException; 15 | import java.io.Serializable; 16 | import java.lang.reflect.InvocationTargetException; 17 | 18 | public class RabbitMessageListenerAdapter implements Consumer, InitializingBean { 19 | 20 | public static final String DEFAULT_LISTENER_METHOD = "handleMessage"; 21 | public static final int DEFAULT_POOL_SIZE = 1; 22 | public static final String DEFAULT_ROUTING_KEY = "#"; 23 | public static final ExchangeType DEFAULT_EXCHANGE_TYPE = ExchangeType.DIRECT; 24 | 25 | private final Log log = LogFactory.getLog(RabbitMessageListenerAdapter.class); 26 | 27 | private Object delegate; 28 | 29 | private RabbitChannelFactory channelFactory; 30 | private String exchange; 31 | private ExchangeType exchangeType = DEFAULT_EXCHANGE_TYPE; 32 | private String queueName; 33 | private String routingKey = DEFAULT_ROUTING_KEY; 34 | private String listenerMethod = DEFAULT_LISTENER_METHOD; 35 | private int poolsize = DEFAULT_POOL_SIZE; 36 | 37 | private Channel channel; 38 | 39 | @Override 40 | public void afterPropertiesSet() throws Exception { 41 | 42 | exchangeType.validateRoutingKey(routingKey); 43 | 44 | startConsumer(); 45 | } 46 | 47 | private void startConsumer() { 48 | if (channel == null || !channel.isOpen()) { 49 | try { 50 | channel = channelFactory.createChannel(); 51 | String internalQueueName; 52 | if (queueName == null) { 53 | // declare anonymous queue and get name for binding and consuming 54 | internalQueueName = channel.queueDeclare().getQueue(); 55 | } else { 56 | internalQueueName = channel.queueDeclare(queueName, false, false, false, true, null).getQueue(); 57 | } 58 | channel.exchangeDeclare(exchange, exchangeType.toString()); 59 | channel.queueBind(internalQueueName, exchange, routingKey); 60 | 61 | for (int i=1; i<=poolsize; i++) { 62 | channel.basicConsume(internalQueueName, this); 63 | log.info(String.format("Started consumer %d on exchange [%s(%s)] - queue [%s] - routingKey [%s]" 64 | , i, exchange, exchangeType, queueName, routingKey)); 65 | } 66 | } catch (IOException e) { 67 | log.warn("Unable start consumer", e); 68 | } 69 | } 70 | } 71 | 72 | public void setExchangeType(ExchangeType exchangeType) { 73 | this.exchangeType = exchangeType; 74 | } 75 | 76 | public void setQueueName(String queueName) { 77 | this.queueName = queueName; 78 | } 79 | 80 | @Required 81 | public void setExchange(String exchange) { 82 | this.exchange = exchange; 83 | } 84 | 85 | @Required 86 | public void setChannelFactory(RabbitChannelFactory channelFactory) { 87 | this.channelFactory = channelFactory; 88 | } 89 | 90 | @Override 91 | public void handleConsumeOk(String consumerTag) { 92 | if (log.isTraceEnabled()) { 93 | log.trace(String.format("handleConsumeOk [%s]", consumerTag)); 94 | } 95 | } 96 | 97 | @Override 98 | public void handleCancelOk(String consumerTag) { 99 | if (log.isTraceEnabled()) { 100 | log.trace(String.format("handleCancelOk [%s]", consumerTag)); 101 | } 102 | } 103 | 104 | @Override 105 | public void handleShutdownSignal(String consumerTag, ShutdownSignalException cause) { 106 | if (log.isInfoEnabled()) { 107 | log.info(String.format("Channel connection lost for reason [%s]", cause.getReason())); 108 | log.info(String.format("Reference [%s]", cause.getReference())); 109 | } 110 | 111 | if (cause.isInitiatedByApplication()) { 112 | if (log.isInfoEnabled()) { 113 | log.info("Sutdown initiated by application"); 114 | } 115 | } else if (cause.isHardError()) { 116 | log.error("Shutdown is a hard error, trying to restart consumer(s)..."); 117 | startConsumer(); 118 | } 119 | } 120 | 121 | @Override 122 | public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { 123 | 124 | if (log.isDebugEnabled()) { 125 | log.debug(String.format("Handling message with tag [%s] on [%s]", consumerTag, envelope.getRoutingKey())); 126 | } 127 | try { 128 | 129 | Object message = SerializationUtils.deserialize(body); 130 | // Invoke the handler method with appropriate arguments. 131 | Object result = invokeListenerMethod(listenerMethod, new Object[]{message}); 132 | 133 | if (result != null && result instanceof Serializable) { 134 | handleResult((Serializable) result, envelope, properties); 135 | } else { 136 | log.trace("No result object given - no result to handle"); 137 | } 138 | } finally{ 139 | channel.basicAck(envelope.getDeliveryTag(), false); 140 | } 141 | 142 | } 143 | 144 | private void handleResult(Serializable result, Envelope envelope, AMQP.BasicProperties properties) throws IOException { 145 | if (properties.replyTo != null) { 146 | channel.basicPublish(envelope.getExchange(), properties.replyTo, null, SerializationUtils.serialize(result)); 147 | } 148 | } 149 | 150 | protected Object invokeListenerMethod(String methodName, Object[] arguments) { 151 | try { 152 | MethodInvoker methodInvoker = new MethodInvoker(); 153 | methodInvoker.setTargetObject(getDelegate()); 154 | methodInvoker.setTargetMethod(methodName); 155 | methodInvoker.setArguments(arguments); 156 | methodInvoker.prepare(); 157 | return methodInvoker.invoke(); 158 | } 159 | catch (InvocationTargetException ex) { 160 | Throwable targetEx = ex.getTargetException(); 161 | throw new ListenerExecutionFailedException( 162 | "Listener method '" + methodName + "' threw exception", targetEx); 163 | } 164 | catch (Throwable ex) { 165 | throw new ListenerExecutionFailedException("Failed to invoke target method '" + methodName + 166 | "' with arguments " + ObjectUtils.nullSafeToString(arguments), ex); 167 | } 168 | } 169 | 170 | @Required 171 | public void setDelegate(Object delegate) { 172 | this.delegate = delegate; 173 | } 174 | 175 | public Object getDelegate() { 176 | return delegate; 177 | } 178 | 179 | public void setRoutingKey(String routingKey) { 180 | this.routingKey = routingKey; 181 | } 182 | 183 | public void setListenerMethod(String listenerMethod) { 184 | this.listenerMethod = listenerMethod; 185 | } 186 | 187 | public void setPoolsize(int poolsize) { 188 | this.poolsize = poolsize; 189 | } 190 | 191 | private static class ListenerExecutionFailedException extends RuntimeException { 192 | public ListenerExecutionFailedException(String s, Throwable targetEx) { 193 | super(s, targetEx); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/remoting/RabbitInvokerClientInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.remoting; 2 | 3 | import com.rabbitmq.spring.ExchangeType; 4 | import com.rabbitmq.spring.UnroutableException; 5 | import com.rabbitmq.spring.channel.RabbitChannelFactory; 6 | import com.rabbitmq.client.*; 7 | import org.aopalliance.intercept.MethodInterceptor; 8 | import org.aopalliance.intercept.MethodInvocation; 9 | import org.apache.commons.lang.SerializationUtils; 10 | import org.apache.commons.logging.Log; 11 | import org.apache.commons.logging.LogFactory; 12 | import org.springframework.aop.support.AopUtils; 13 | import org.springframework.beans.factory.DisposableBean; 14 | import org.springframework.beans.factory.InitializingBean; 15 | import org.springframework.beans.factory.annotation.Required; 16 | import org.springframework.remoting.RemoteInvocationFailureException; 17 | import org.springframework.remoting.support.DefaultRemoteInvocationFactory; 18 | import org.springframework.remoting.support.RemoteInvocation; 19 | import org.springframework.remoting.support.RemoteInvocationFactory; 20 | import org.springframework.remoting.support.RemoteInvocationResult; 21 | 22 | import java.io.IOException; 23 | import static java.lang.String.format; 24 | import java.util.concurrent.BlockingQueue; 25 | import java.util.concurrent.LinkedBlockingQueue; 26 | import java.util.concurrent.TimeUnit; 27 | import java.util.concurrent.TimeoutException; 28 | 29 | public class RabbitInvokerClientInterceptor implements MethodInterceptor, InitializingBean, ShutdownListener, DisposableBean { 30 | 31 | private final Log log = LogFactory.getLog(RabbitInvokerClientInterceptor.class); 32 | 33 | private static final int DEFAULT_TIMEOUT_MS = 30000; 34 | private static final int DEFAULT_POOL_SIZE = 5; 35 | 36 | private final RemoteInvocationFactory remoteInvocationFactory = new DefaultRemoteInvocationFactory(); 37 | 38 | private RabbitChannelFactory channelFactory; 39 | private String exchange; 40 | private ExchangeType exchangeType; 41 | private String routingKey; 42 | private boolean mandatory; 43 | private boolean immediate; 44 | 45 | private int poolSize = DEFAULT_POOL_SIZE; 46 | 47 | private final BlockingQueue rpcClients = new LinkedBlockingQueue(); 48 | 49 | private int timeoutMs = DEFAULT_TIMEOUT_MS; 50 | 51 | @Override 52 | public void afterPropertiesSet() throws InterruptedException { 53 | 54 | if (routingKey.contains("#") || routingKey.contains("*")) { 55 | throw new IllegalArgumentException("Routing key may not contain wildcards."); 56 | } 57 | 58 | createRpcClients(); 59 | } 60 | 61 | private void createRpcClients() { 62 | 63 | try { 64 | Channel tmpChannel = channelFactory.createChannel(); 65 | tmpChannel.getConnection().addShutdownListener(this); 66 | tmpChannel.exchangeDeclare(exchange, exchangeType.toString()); 67 | for (int i = 0; i < poolSize; i++) { 68 | Channel channel = channelFactory.createChannel(); 69 | final RabbitRpcClient rpcClient = new RabbitRpcClient(channel, exchange, routingKey, timeoutMs, mandatory, immediate); 70 | channel.setReturnListener(new ReturnListener() { 71 | @SuppressWarnings({"ThrowableInstanceNeverThrown"}) 72 | @Override 73 | public void handleBasicReturn(int replyCode, String replyText, String exchange, String routingKey 74 | , AMQP.BasicProperties properties, byte[] body) throws IOException { 75 | 76 | // call handle result here, so uninterruptable cal will be interrupted 77 | Throwable resultException; 78 | switch (replyCode) { 79 | case AMQP.NO_CONSUMERS: 80 | resultException = new UnroutableException(String.format( 81 | "No consumers for message [%s] - [%s] - [%s]" 82 | , SerializationUtils.deserialize(body), exchange, routingKey)); 83 | break; 84 | case AMQP.NO_ROUTE: 85 | resultException = new UnroutableException(String.format( 86 | "Unroutable message [%s] - [%s] - [%s]" 87 | , SerializationUtils.deserialize(body), exchange, routingKey)); 88 | break; 89 | default: 90 | resultException = new UnroutableException(String.format( 91 | "Message returned [%s] - [%s] - [%s] - [%d] - [%s]" 92 | , SerializationUtils.deserialize(body), exchange, routingKey, replyCode, replyText)); 93 | 94 | } 95 | RemoteInvocationResult remoteInvocationResult = new RemoteInvocationResult(resultException); 96 | rpcClient.getConsumer().handleDelivery(null, null, properties 97 | , SerializationUtils.serialize(remoteInvocationResult)); 98 | } 99 | }); 100 | log.info(String.format("Started rpc client on exchange [%s(%s)] - routingKey [%s]" 101 | , exchange, exchangeType, routingKey)); 102 | rpcClients.add(rpcClient); 103 | 104 | } 105 | } catch (IOException e) { 106 | log.warn("Unable to create rpc client", e); 107 | } 108 | } 109 | 110 | 111 | public Object invoke(MethodInvocation methodInvocation) throws Throwable { 112 | if (AopUtils.isToStringMethod(methodInvocation.getMethod())) { 113 | return String.format("Rabbit invoker proxy for exchange [%s] - routingKey [%s]", exchange, routingKey); 114 | } 115 | 116 | RemoteInvocation invocation = createRemoteInvocation(methodInvocation); 117 | RemoteInvocationResult result = executeRequest(invocation); 118 | try { 119 | return recreateRemoteInvocationResult(result); 120 | } 121 | catch (Throwable ex) { 122 | if (result.hasInvocationTargetException()) { 123 | throw ex; 124 | } else { 125 | throw new RemoteInvocationFailureException(String.format("Invocation of method [%s] failed in " + 126 | "Rabbit invoker remote service at exchange [%s] - routingKey [%s]" 127 | , methodInvocation.getMethod(), exchange, routingKey), ex); 128 | } 129 | } 130 | } 131 | 132 | protected RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) { 133 | return remoteInvocationFactory.createRemoteInvocation(methodInvocation); 134 | } 135 | 136 | protected Object recreateRemoteInvocationResult(RemoteInvocationResult result) throws Throwable { 137 | return result.recreate(); 138 | } 139 | 140 | protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws IOException, TimeoutException, InterruptedException { 141 | byte[] message = SerializationUtils.serialize(invocation); 142 | 143 | RabbitRpcClient rpcClient = rpcClients.poll(timeoutMs, TimeUnit.MILLISECONDS); 144 | if (rpcClient != null) { 145 | 146 | byte[] response; 147 | try { 148 | response = rpcClient.primitiveCall(message); 149 | } finally { 150 | rpcClients.put(rpcClient); 151 | } 152 | return (RemoteInvocationResult) SerializationUtils.deserialize(response); 153 | } 154 | throw new TimeoutException("Timed out while waiting for available rpc client"); 155 | } 156 | 157 | @Required 158 | public void setChannelFactory(RabbitChannelFactory channelFactory) { 159 | this.channelFactory = channelFactory; 160 | } 161 | 162 | @Required 163 | public void setRoutingKey(String routingKey) { 164 | this.routingKey = routingKey; 165 | } 166 | 167 | @Required 168 | public void setExchange(String exchange) { 169 | this.exchange = exchange; 170 | } 171 | 172 | public void setTimeoutMs(int timeoutMs) { 173 | this.timeoutMs = timeoutMs; 174 | } 175 | 176 | public void setMandatory(boolean mandatory) { 177 | this.mandatory = mandatory; 178 | } 179 | 180 | public void setImmediate(boolean immediate) { 181 | this.immediate = immediate; 182 | } 183 | 184 | @Required 185 | public void setExchangeType(ExchangeType exchangeType) { 186 | this.exchangeType = exchangeType; 187 | } 188 | 189 | public void setPoolSize(int poolSize) { 190 | this.poolSize = poolSize; 191 | } 192 | 193 | @Override 194 | public void shutdownCompleted(ShutdownSignalException cause) { 195 | if (log.isInfoEnabled()) { 196 | log.info(String.format("Channel connection lost for reason [%s]", cause.getReason())); 197 | log.info(String.format("Reference [%s]", cause.getReference())); 198 | } 199 | 200 | if (cause.isInitiatedByApplication()) { 201 | if (log.isInfoEnabled()) { 202 | log.info("Sutdown initiated by application"); 203 | } 204 | } else if (cause.isHardError()) { 205 | log.error("Shutdown is a hard error, trying to restart the RPC clients..."); 206 | clearRpcClients(); 207 | createRpcClients(); 208 | } 209 | } 210 | 211 | private void clearRpcClients() { 212 | if (log.isInfoEnabled()) { 213 | log.info(format("Closing %d rpc clients", rpcClients.size())); 214 | } 215 | 216 | for (RabbitRpcClient rpcClient : rpcClients) { 217 | try { 218 | rpcClient.close(); 219 | } catch (Exception e) { 220 | log.warn("Error closing rpc client", e); 221 | } 222 | } 223 | rpcClients.clear(); 224 | 225 | if (log.isInfoEnabled()) { 226 | log.info("Rpc clients closed"); 227 | } 228 | 229 | } 230 | 231 | @Override 232 | public void destroy() throws Exception { 233 | clearRpcClients(); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/remoting/RabbitInvokerProxyFactoryBean.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.remoting; 2 | 3 | import org.springframework.aop.framework.ProxyFactory; 4 | import org.springframework.beans.factory.BeanClassLoaderAware; 5 | import org.springframework.beans.factory.FactoryBean; 6 | import org.springframework.util.ClassUtils; 7 | 8 | public class RabbitInvokerProxyFactoryBean extends RabbitInvokerClientInterceptor 9 | implements FactoryBean, BeanClassLoaderAware { 10 | 11 | private Class serviceInterface; 12 | 13 | private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 14 | 15 | private Object serviceProxy; 16 | 17 | 18 | public void setServiceInterface(Class serviceInterface) { 19 | if (serviceInterface == null || !serviceInterface.isInterface()) { 20 | throw new IllegalArgumentException("'serviceInterface' must be an interface"); 21 | } 22 | this.serviceInterface = serviceInterface; 23 | } 24 | 25 | public void setBeanClassLoader(ClassLoader classLoader) { 26 | beanClassLoader = classLoader; 27 | } 28 | 29 | @Override 30 | public void afterPropertiesSet() throws InterruptedException { 31 | super.afterPropertiesSet(); 32 | if (serviceInterface == null) { 33 | throw new IllegalArgumentException("Property 'serviceInterface' is required"); 34 | } 35 | serviceProxy = new ProxyFactory(serviceInterface, this).getProxy(beanClassLoader); 36 | } 37 | 38 | 39 | public Object getObject() { 40 | return serviceProxy; 41 | } 42 | 43 | public Class getObjectType() { 44 | return serviceInterface; 45 | } 46 | 47 | public boolean isSingleton() { 48 | return true; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/remoting/RabbitInvokerServiceExporter.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.remoting; 2 | 3 | import com.rabbitmq.spring.ExchangeType; 4 | import com.rabbitmq.spring.InvalidRoutingKeyException; 5 | import com.rabbitmq.spring.channel.RabbitChannelFactory; 6 | import com.rabbitmq.client.*; 7 | import org.apache.commons.lang.SerializationUtils; 8 | import org.apache.commons.logging.Log; 9 | import org.apache.commons.logging.LogFactory; 10 | import org.springframework.beans.factory.DisposableBean; 11 | import org.springframework.beans.factory.InitializingBean; 12 | import org.springframework.beans.factory.annotation.Required; 13 | import org.springframework.remoting.support.RemoteInvocation; 14 | import org.springframework.remoting.support.RemoteInvocationBasedExporter; 15 | import org.springframework.remoting.support.RemoteInvocationResult; 16 | 17 | import java.io.IOException; 18 | import static java.lang.String.format; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | public class RabbitInvokerServiceExporter extends RemoteInvocationBasedExporter implements InitializingBean, DisposableBean, ShutdownListener { 23 | 24 | private final Log log = LogFactory.getLog(RabbitInvokerServiceExporter.class); 25 | 26 | private RabbitChannelFactory channelFactory; 27 | private String exchange; 28 | private ExchangeType exchangeType; 29 | private String queueName; 30 | private String routingKey; 31 | 32 | private Object proxy; 33 | private List rpcServerPool; 34 | private int poolsize = 1; 35 | 36 | public void afterPropertiesSet() { 37 | 38 | if (exchangeType.equals(ExchangeType.FANOUT)) { 39 | throw new InvalidRoutingKeyException( 40 | String.format("Exchange type %s not allowed for service exporter", exchangeType)); 41 | } 42 | 43 | exchangeType.validateRoutingKey(routingKey); 44 | 45 | proxy = getProxyForService(); 46 | 47 | rpcServerPool = new ArrayList(poolsize); 48 | 49 | startRpcServer(); 50 | } 51 | 52 | private void startRpcServer() { 53 | try { 54 | log.info("Creating channel and rpc server"); 55 | Channel tmpChannel = channelFactory.createChannel(); 56 | tmpChannel.getConnection().addShutdownListener(this); 57 | tmpChannel.queueDeclare(queueName, false, false, false, true, null); 58 | if (exchange != null) { 59 | tmpChannel.exchangeDeclare(exchange, exchangeType.toString()); 60 | tmpChannel.queueBind(queueName, exchange, routingKey); 61 | } 62 | 63 | 64 | for (int i = 1; i <= poolsize; i++) { 65 | try { 66 | Channel channel = channelFactory.createChannel(); 67 | 68 | log.info(String.format( 69 | "Starting rpc server %d on exchange [%s(%s)] - queue [%s] - routingKey [%s]" 70 | , i, exchange, exchangeType, queueName, routingKey)); 71 | final RpcServer rpcServer = createRpcServer(channel); 72 | rpcServerPool.add(rpcServer); 73 | 74 | Runnable main = new Runnable() { 75 | @Override 76 | public void run() { 77 | try { 78 | throw rpcServer.mainloop(); 79 | } catch (IOException e) { 80 | throw new RuntimeException(e); 81 | } 82 | } 83 | }; 84 | new Thread(main).start(); 85 | } catch (IOException e) { 86 | log.warn("Unable to create rpc server", e); 87 | } 88 | } 89 | } catch (Exception e) { 90 | log.error("Unexpected error trying to start rpc servers", e); 91 | } 92 | } 93 | 94 | private RpcServer createRpcServer(Channel channel) throws IOException { 95 | return new RpcServer(channel, queueName) { 96 | 97 | @Override 98 | public byte[] handleCall(byte[] requestBody, AMQP.BasicProperties replyProperties) { 99 | 100 | RemoteInvocation invocation = (RemoteInvocation) SerializationUtils.deserialize(requestBody); 101 | RemoteInvocationResult result = invokeAndCreateResult(invocation, proxy); 102 | return SerializationUtils.serialize(result); 103 | 104 | } 105 | }; 106 | } 107 | 108 | public void setChannelFactory(RabbitChannelFactory channelFactory) { 109 | this.channelFactory = channelFactory; 110 | } 111 | 112 | @Required 113 | public void setQueueName(String queueName) { 114 | this.queueName = queueName; 115 | } 116 | 117 | public Object getProxy() { 118 | return proxy; 119 | } 120 | 121 | @Override 122 | public void destroy() throws Exception { 123 | clearRpcServers(); 124 | } 125 | 126 | private void clearRpcServers() { 127 | if (log.isInfoEnabled()) { 128 | log.info(format("Closing %d rpc servers", rpcServerPool.size())); 129 | } 130 | 131 | for (RpcServer rpcServer : rpcServerPool) { 132 | try { 133 | rpcServer.terminateMainloop(); 134 | rpcServer.close(); 135 | } catch (Exception e) { 136 | log.warn("Error termination rpcserver loop", e); 137 | } 138 | } 139 | rpcServerPool.clear(); 140 | if (log.isInfoEnabled()) { 141 | log.info("Rpc servers closed"); 142 | } 143 | 144 | } 145 | 146 | @Override 147 | public void shutdownCompleted(ShutdownSignalException cause) { 148 | if (log.isInfoEnabled()) { 149 | log.info(String.format("Channel connection lost for reason [%s]", cause.getReason())); 150 | log.info(String.format("Reference [%s]", cause.getReference())); 151 | } 152 | 153 | if (cause.isInitiatedByApplication()) { 154 | if (log.isInfoEnabled()) { 155 | log.info("Sutdown initiated by application"); 156 | } 157 | } else if (cause.isHardError()) { 158 | log.error("Shutdown is a hard error, trying to restart the RPC server..."); 159 | startRpcServer(); 160 | } 161 | } 162 | 163 | public void setExchange(String exchange) { 164 | this.exchange = exchange; 165 | } 166 | 167 | @Required 168 | public void setRoutingKey(String routingKey) { 169 | this.routingKey = routingKey; 170 | } 171 | 172 | public void setPoolsize(int poolsize) { 173 | this.poolsize = poolsize; 174 | } 175 | 176 | @Required 177 | public void setExchangeType(ExchangeType exchangeType) { 178 | this.exchangeType = exchangeType; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/remoting/RabbitRpcClient.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.remoting; 2 | 3 | import com.rabbitmq.client.*; 4 | import com.rabbitmq.client.impl.MethodArgumentReader; 5 | import com.rabbitmq.client.impl.MethodArgumentWriter; 6 | import com.rabbitmq.utility.BlockingCell; 7 | 8 | import java.io.*; 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.concurrent.TimeoutException; 13 | 14 | class RabbitRpcClient { 15 | 16 | private final Channel channel; 17 | private final String exchange; 18 | private final String routingKey; 19 | 20 | private final Map> continuationMap = new HashMap>(); 21 | private int correlationId; 22 | 23 | private final String replyQueue; 24 | private DefaultConsumer consumer; 25 | 26 | private final boolean mandatory; 27 | private final boolean immediate; 28 | private final int timeOutMs; 29 | 30 | public RabbitRpcClient(Channel channel, String exchange, String routingKey, int timeOutMs) throws IOException { 31 | this(channel, exchange, routingKey, timeOutMs, false, false); 32 | } 33 | 34 | @SuppressWarnings({"ConstructorWithTooManyParameters"}) 35 | public RabbitRpcClient(Channel channel, String exchange, String routingKey, int timeOutMs, boolean mandatory 36 | , boolean immediate) throws IOException { 37 | this.channel = channel; 38 | this.exchange = exchange; 39 | this.routingKey = routingKey; 40 | this.timeOutMs = timeOutMs; 41 | this.mandatory = mandatory; 42 | this.immediate = immediate; 43 | correlationId = 0; 44 | 45 | replyQueue = setupReplyQueue(); 46 | consumer = setupConsumer(); 47 | } 48 | 49 | void checkConsumer() throws IOException { 50 | if (consumer == null) { 51 | throw new EOFException("RpcClient is closed"); 52 | } 53 | } 54 | 55 | public void close() throws IOException { 56 | if (consumer != null) { 57 | channel.basicCancel(consumer.getConsumerTag()); 58 | consumer = null; 59 | } 60 | } 61 | 62 | private String setupReplyQueue() throws IOException { 63 | return channel.queueDeclare("", false, false, true, true, null).getQueue(); 64 | } 65 | 66 | private DefaultConsumer setupConsumer() throws IOException { 67 | DefaultConsumer consumer = new DefaultConsumer(channel) { 68 | 69 | @Override 70 | public void handleShutdownSignal(String consumerTag, 71 | ShutdownSignalException signal) { 72 | 73 | synchronized (continuationMap) { 74 | for (Map.Entry> entry : continuationMap.entrySet()) { 75 | entry.getValue().set(signal); 76 | } 77 | RabbitRpcClient.this.consumer = null; 78 | } 79 | } 80 | 81 | @Override 82 | public void handleDelivery(String consumerTag, 83 | Envelope envelope, 84 | AMQP.BasicProperties properties, 85 | byte[] body) 86 | throws IOException { 87 | synchronized (continuationMap) { 88 | String replyId = properties.correlationId; 89 | BlockingCell blocker = continuationMap.get(replyId); 90 | continuationMap.remove(replyId); 91 | blocker.set(body); 92 | } 93 | } 94 | }; 95 | channel.basicConsume(replyQueue, true, consumer); 96 | return consumer; 97 | } 98 | 99 | void publish(AMQP.BasicProperties props, byte[] message) 100 | throws IOException { 101 | channel.basicPublish(exchange, routingKey, mandatory, immediate, props, message); 102 | 103 | } 104 | 105 | public byte[] primitiveCall(AMQP.BasicProperties props, byte[] message) 106 | throws IOException, ShutdownSignalException, TimeoutException { 107 | AMQP.BasicProperties localProps = props; 108 | checkConsumer(); 109 | BlockingCell k = new BlockingCell(); 110 | synchronized (continuationMap) { 111 | correlationId++; 112 | String replyId = "" + correlationId; 113 | if (localProps != null) { 114 | localProps.correlationId = replyId; 115 | localProps.replyTo = replyQueue; 116 | } else { 117 | localProps = new AMQP.BasicProperties(null, null, null, null, 118 | null, replyId, 119 | replyQueue, null, null, null, 120 | null, null, null, null); 121 | } 122 | continuationMap.put(replyId, k); 123 | } 124 | publish(localProps, message); 125 | Object reply = k.uninterruptibleGet(timeOutMs); 126 | if (reply instanceof ShutdownSignalException) { 127 | ShutdownSignalException sig = (ShutdownSignalException) reply; 128 | ShutdownSignalException wrapper = 129 | new ShutdownSignalException(sig.isHardError(), 130 | sig.isInitiatedByApplication(), 131 | sig.getReason(), 132 | sig.getReference()); 133 | wrapper.initCause(sig); 134 | throw wrapper; 135 | } else { 136 | return (byte[]) reply; 137 | } 138 | } 139 | 140 | public byte[] primitiveCall(byte[] message) 141 | throws IOException, ShutdownSignalException, TimeoutException { 142 | return primitiveCall(null, message); 143 | } 144 | 145 | public String stringCall(String message) 146 | throws IOException, ShutdownSignalException, TimeoutException { 147 | return new String(primitiveCall(message.getBytes())); 148 | } 149 | 150 | @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"}) 151 | public Map mapCall(Map message) 152 | throws IOException, ShutdownSignalException, TimeoutException { 153 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 154 | MethodArgumentWriter writer = new MethodArgumentWriter(new DataOutputStream(buffer)); 155 | writer.writeTable(message); 156 | writer.flush(); 157 | byte[] reply = primitiveCall(buffer.toByteArray()); 158 | MethodArgumentReader reader = 159 | new MethodArgumentReader(new DataInputStream(new ByteArrayInputStream(reply))); 160 | return reader.readTable(); 161 | } 162 | 163 | public Map mapCall(Object[] keyValuePairs) 164 | throws IOException, ShutdownSignalException, TimeoutException { 165 | Map message = new HashMap(); 166 | for (int i = 0; i < keyValuePairs.length; i += 2) { 167 | message.put((String) keyValuePairs[i], keyValuePairs[i + 1]); 168 | } 169 | return mapCall(message); 170 | } 171 | 172 | public Channel getChannel() { 173 | return channel; 174 | } 175 | 176 | public String getExchange() { 177 | return exchange; 178 | } 179 | 180 | public String getRoutingKey() { 181 | return routingKey; 182 | } 183 | 184 | public Map> getContinuationMap() { 185 | return Collections.unmodifiableMap(continuationMap); 186 | } 187 | 188 | public int getCorrelationId() { 189 | return correlationId; 190 | } 191 | 192 | public String getReplyQueue() { 193 | return replyQueue; 194 | } 195 | 196 | public Consumer getConsumer() { 197 | return consumer; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/template/ASyncRabbitTemplate.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.template; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.beans.factory.DisposableBean; 6 | 7 | import java.io.Serializable; 8 | import java.util.concurrent.BlockingQueue; 9 | import java.util.concurrent.LinkedBlockingQueue; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | public class ASyncRabbitTemplate extends RabbitTemplate implements DisposableBean { 13 | 14 | private final Log log = LogFactory.getLog(ASyncRabbitTemplate.class); 15 | private final BlockingQueue queue = new LinkedBlockingQueue(); 16 | 17 | private volatile boolean running = true; 18 | 19 | @Override 20 | public void afterPropertiesSet() throws Exception { 21 | super.afterPropertiesSet(); 22 | new Thread(new Worker()).start(); 23 | } 24 | 25 | @Override 26 | public void destroy() throws Exception { 27 | running = false; 28 | queue.clear(); 29 | } 30 | 31 | @Override 32 | public void send(Serializable object, String routingKey, boolean mandatory, boolean direct) { 33 | queue.add(new RabbitMessage(object, routingKey, mandatory, direct)); 34 | } 35 | 36 | private void sendMessage(RabbitMessage message) { 37 | super.send(message.getObject(), message.routingKey, message.isMandatory(), message.isDirect()); 38 | } 39 | 40 | private final class Worker implements Runnable { 41 | 42 | @Override 43 | public void run() { 44 | while (running) { 45 | try { 46 | RabbitMessage message = queue.poll(1, TimeUnit.SECONDS); 47 | if (message != null) { 48 | sendMessage(message); 49 | } 50 | } catch (InterruptedException ie) { 51 | if (log.isDebugEnabled()) { 52 | log.debug("Interrupted while waiting for RabbitMessage in queue"); 53 | } 54 | } catch (Exception e) { 55 | log.error("Error sending message", e); 56 | } 57 | } 58 | } 59 | } 60 | private final class RabbitMessage { 61 | private final Serializable object; 62 | private final String routingKey; 63 | private final boolean mandatory; 64 | private final boolean direct; 65 | 66 | private RabbitMessage(Serializable object, String routingKey, boolean mandatory, boolean direct) { 67 | this.object = object; 68 | this.routingKey = routingKey; 69 | this.mandatory = mandatory; 70 | this.direct = direct; 71 | } 72 | 73 | public Serializable getObject() { 74 | return object; 75 | } 76 | 77 | public String getRoutingKey() { 78 | return routingKey; 79 | } 80 | 81 | public boolean isMandatory() { 82 | return mandatory; 83 | } 84 | 85 | public boolean isDirect() { 86 | return direct; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/spring/template/RabbitTemplate.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.template; 2 | 3 | import com.rabbitmq.spring.ExchangeType; 4 | import com.rabbitmq.spring.channel.RabbitChannelFactory; 5 | import com.rabbitmq.client.*; 6 | import org.apache.commons.lang.SerializationUtils; 7 | import org.apache.commons.logging.Log; 8 | import org.apache.commons.logging.LogFactory; 9 | import org.springframework.beans.factory.InitializingBean; 10 | import org.springframework.beans.factory.annotation.Required; 11 | 12 | import java.io.IOException; 13 | import java.io.Serializable; 14 | 15 | /** 16 | * N.B. Be careful: This class it NOT thread safe. 17 | * For thread save rabbit template, use: ASyncRabbitTemplate 18 | */ 19 | public class RabbitTemplate implements InitializingBean, ShutdownListener, ReturnListener { 20 | 21 | public static final ExchangeType DEFAULT_EXCHANGE_TYPE = ExchangeType.DIRECT; 22 | public static final String DEFAULT_ROUTING_KEY = "#"; 23 | 24 | private final Log log = LogFactory.getLog(RabbitTemplate.class); 25 | 26 | private RabbitChannelFactory channelFactory; 27 | private String exchange; 28 | private ExchangeType exchangeType = DEFAULT_EXCHANGE_TYPE; 29 | private String routingKey = DEFAULT_ROUTING_KEY; 30 | private boolean mandatory; 31 | private boolean immediate; 32 | 33 | 34 | private Channel channel; 35 | 36 | public void send(Serializable object) { 37 | send(object, routingKey, mandatory, immediate); 38 | } 39 | 40 | public void send(Serializable object, String routingKey) { 41 | send(object, routingKey, mandatory, immediate); 42 | } 43 | 44 | public void send(Serializable object, boolean mandatory, boolean immediate) { 45 | send(object, routingKey, mandatory, immediate); 46 | } 47 | 48 | public void send(Serializable object, String routingKey, boolean mandatory, boolean immediate) { 49 | if (log.isTraceEnabled()) { 50 | log.trace(String.format("Sending object [%s] with routingKey [%s] - mandatory [%s] - immediate [%s]" 51 | , object, routingKey, mandatory, immediate)); 52 | } 53 | try { 54 | channel.basicPublish(exchange, routingKey, mandatory, immediate, null, SerializationUtils.serialize(object)); 55 | 56 | } catch (IOException e) { 57 | throw new RuntimeException(e); 58 | } 59 | } 60 | 61 | @Override 62 | public void afterPropertiesSet() throws Exception { 63 | 64 | exchangeType.validateRoutingKey(routingKey); 65 | 66 | connectChannel(); 67 | } 68 | 69 | private void connectChannel() { 70 | if (channel == null || !channel.isOpen()) { 71 | try { 72 | channel = channelFactory.createChannel(); 73 | channel.getConnection().addShutdownListener(this); 74 | channel.setReturnListener(this); 75 | channel.exchangeDeclare(exchange, exchangeType.toString()); 76 | if (log.isInfoEnabled()) { 77 | log.info(String.format("Connected to exchange [%s(%s)] - routingKey [%s]" 78 | , exchange, exchangeType, routingKey)); 79 | } 80 | } catch (IOException e) { 81 | log.warn("Unable to connect channel", e); 82 | } 83 | } 84 | } 85 | 86 | @Required 87 | public void setExchange(String exchange) { 88 | this.exchange = exchange; 89 | } 90 | 91 | public void setRoutingKey(String routingKey) { 92 | this.routingKey = routingKey; 93 | } 94 | 95 | @Required 96 | public void setChannelFactory(RabbitChannelFactory channelFactory) { 97 | this.channelFactory = channelFactory; 98 | } 99 | 100 | @Override 101 | public void shutdownCompleted(ShutdownSignalException cause) { 102 | if (log.isInfoEnabled()) { 103 | log.info(String.format("Channel connection lost for reason [%s]", cause.getReason())); 104 | log.info(String.format("Reference [%s]", cause.getReference())); 105 | } 106 | 107 | if (cause.isInitiatedByApplication()) { 108 | if (log.isInfoEnabled()) { 109 | log.info("Sutdown initiated by application"); 110 | } 111 | } else if (cause.isHardError()) { 112 | log.error("Shutdown is a hard error, trying to reconnect the channel..."); 113 | connectChannel(); 114 | } 115 | } 116 | 117 | @Override 118 | public void handleBasicReturn(int replyCode, String replyText, String exchange, String routingKey 119 | , AMQP.BasicProperties properties, byte[] body) throws IOException { 120 | 121 | log.warn(String.format("Got message back from server [%d] - [%s]", replyCode, replyText)); 122 | handleReturn(replyCode, replyText, exchange, routingKey, properties, body); 123 | } 124 | 125 | 126 | /** 127 | * Callback hook for returned messages, overwrite where needed. 128 | * Will only be called when sending with 'immediate' or 'mandatory' set to true. 129 | */ 130 | @SuppressWarnings({"UnusedDeclaration"}) 131 | public void handleReturn(int replyCode, String replyText, String exchange, String routingKey 132 | , AMQP.BasicProperties properties, byte[] body) { 133 | 134 | } 135 | 136 | public void setMandatory(boolean mandatory) { 137 | this.mandatory = mandatory; 138 | } 139 | 140 | public void setImmediate(boolean immediate) { 141 | this.immediate = immediate; 142 | } 143 | 144 | public void setExchangeType(ExchangeType exchangeType) { 145 | this.exchangeType = exchangeType; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/spring/example/ExampleDelegate.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.example; 2 | 3 | public class ExampleDelegate { 4 | 5 | public void handleMessage(ExampleObject example) { 6 | // handle the incoming object.... 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/spring/example/ExampleObject.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.example; 2 | 3 | public class ExampleObject { 4 | 5 | private String example; 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/spring/example/ExampleService.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.example; 2 | 3 | public interface ExampleService { 4 | 5 | void someExampleMethod(ExampleObject example); 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/spring/example/ExampleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.spring.example; 2 | 3 | public class ExampleServiceImpl implements ExampleService { 4 | @Override 5 | public void someExampleMethod(ExampleObject example) { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/test/resources/rabbitmq.properties: -------------------------------------------------------------------------------- 1 | rabbit.hosts=localhost:5672 2 | rabbit.username=guest 3 | rabbit.password=guest 4 | rabbit.virtualHost=/ 5 | -------------------------------------------------------------------------------- /src/test/resources/spring-examples.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | --------------------------------------------------------------------------------