PageRenderTime 103ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/core/test/com/google/inject/MethodInterceptionTest.java

https://gitlab.com/metamorphiccode/guice
Java | 369 lines | 300 code | 50 blank | 19 comment | 8 complexity | 3775ef0913292979a29b2e95030e917b MD5 | raw file
  1. /**
  2. * Copyright (C) 2008 Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.google.inject;
  17. import static com.google.inject.matcher.Matchers.only;
  18. import com.google.common.collect.ImmutableList;
  19. import com.google.common.collect.ImmutableMap;
  20. import com.google.common.collect.Iterables;
  21. import com.google.common.collect.Lists;
  22. import com.google.inject.matcher.AbstractMatcher;
  23. import com.google.inject.matcher.Matchers;
  24. import com.google.inject.spi.ConstructorBinding;
  25. import junit.framework.TestCase;
  26. import org.aopalliance.intercept.MethodInterceptor;
  27. import org.aopalliance.intercept.MethodInvocation;
  28. import java.lang.reflect.Method;
  29. import java.util.Arrays;
  30. import java.util.List;
  31. import java.util.Queue;
  32. import java.util.concurrent.atomic.AtomicInteger;
  33. import java.util.concurrent.atomic.AtomicReference;
  34. /**
  35. * @author jessewilson@google.com (Jesse Wilson)
  36. */
  37. public class MethodInterceptionTest extends TestCase {
  38. private AtomicInteger count = new AtomicInteger();
  39. private final class CountingInterceptor implements MethodInterceptor {
  40. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  41. count.incrementAndGet();
  42. return methodInvocation.proceed();
  43. }
  44. }
  45. private final class ReturnNullInterceptor implements MethodInterceptor {
  46. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  47. return null;
  48. }
  49. }
  50. private final class NoOpInterceptor implements MethodInterceptor {
  51. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  52. return methodInvocation.proceed();
  53. }
  54. }
  55. public void testSharedProxyClasses() {
  56. Injector injector = Guice.createInjector(new AbstractModule() {
  57. protected void configure() {
  58. bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
  59. new ReturnNullInterceptor());
  60. }
  61. });
  62. Injector childOne = injector.createChildInjector(new AbstractModule() {
  63. protected void configure() {
  64. bind(Interceptable.class);
  65. }
  66. });
  67. Interceptable nullFoosOne = childOne.getInstance(Interceptable.class);
  68. assertNotNull(nullFoosOne.bar());
  69. assertNull(nullFoosOne.foo()); // confirm it's being intercepted
  70. Injector childTwo = injector.createChildInjector(new AbstractModule() {
  71. protected void configure() {
  72. bind(Interceptable.class);
  73. }
  74. });
  75. Interceptable nullFoosTwo = childTwo.getInstance(Interceptable.class);
  76. assertNull(nullFoosTwo.foo()); // confirm it's being intercepted
  77. assertSame("Child injectors should share proxy classes, otherwise memory leaks!",
  78. nullFoosOne.getClass(), nullFoosTwo.getClass());
  79. Injector injector2 = Guice.createInjector(new AbstractModule() {
  80. protected void configure() {
  81. bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
  82. new ReturnNullInterceptor());
  83. }
  84. });
  85. Interceptable separateNullFoos = injector2.getInstance(Interceptable.class);
  86. assertNull(separateNullFoos.foo()); // confirm it's being intercepted
  87. assertSame("different injectors should share proxy classes, otherwise memory leaks!",
  88. nullFoosOne.getClass(), separateNullFoos.getClass());
  89. }
  90. public void testGetThis() {
  91. final AtomicReference<Object> lastTarget = new AtomicReference<Object>();
  92. Injector injector = Guice.createInjector(new AbstractModule() {
  93. protected void configure() {
  94. bindInterceptor(Matchers.any(), Matchers.any(), new MethodInterceptor() {
  95. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  96. lastTarget.set(methodInvocation.getThis());
  97. return methodInvocation.proceed();
  98. }
  99. });
  100. }
  101. });
  102. Interceptable interceptable = injector.getInstance(Interceptable.class);
  103. interceptable.foo();
  104. assertSame(interceptable, lastTarget.get());
  105. }
  106. public void testInterceptingFinalClass() {
  107. Injector injector = Guice.createInjector(new AbstractModule() {
  108. protected void configure() {
  109. bindInterceptor(Matchers.any(), Matchers.any(), new MethodInterceptor() {
  110. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  111. return methodInvocation.proceed();
  112. }
  113. });
  114. }
  115. });
  116. try {
  117. injector.getInstance(NotInterceptable.class);
  118. fail();
  119. } catch(ConfigurationException ce) {
  120. assertEquals("Unable to method intercept: " + NotInterceptable.class.getName(),
  121. Iterables.getOnlyElement(ce.getErrorMessages()).getMessage().toString());
  122. assertEquals("Cannot subclass final class " + NotInterceptable.class.getName(),
  123. ce.getCause().getMessage());
  124. }
  125. }
  126. public void testSpiAccessToInterceptors() throws NoSuchMethodException {
  127. final MethodInterceptor countingInterceptor = new CountingInterceptor();
  128. final MethodInterceptor returnNullInterceptor = new ReturnNullInterceptor();
  129. Injector injector = Guice.createInjector(new AbstractModule() {
  130. protected void configure() {
  131. bindInterceptor(Matchers.any(),Matchers.returns(only(Foo.class)),
  132. countingInterceptor);
  133. bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class).or(only(Bar.class))),
  134. returnNullInterceptor);
  135. }
  136. });
  137. ConstructorBinding<?> interceptedBinding
  138. = (ConstructorBinding<?>) injector.getBinding(Interceptable.class);
  139. Method barMethod = Interceptable.class.getMethod("bar");
  140. Method fooMethod = Interceptable.class.getMethod("foo");
  141. assertEquals(ImmutableMap.<Method, List<MethodInterceptor>>of(
  142. fooMethod, ImmutableList.of(countingInterceptor, returnNullInterceptor),
  143. barMethod, ImmutableList.of(returnNullInterceptor)),
  144. interceptedBinding.getMethodInterceptors());
  145. ConstructorBinding<?> nonInterceptedBinding
  146. = (ConstructorBinding<?>) injector.getBinding(Foo.class);
  147. assertEquals(ImmutableMap.<Method, List<MethodInterceptor>>of(),
  148. nonInterceptedBinding.getMethodInterceptors());
  149. injector.getInstance(Interceptable.class).foo();
  150. assertEquals("expected counting interceptor to be invoked first", 1, count.get());
  151. }
  152. public void testInterceptedMethodThrows() throws Exception {
  153. Injector injector = Guice.createInjector(new AbstractModule() {
  154. protected void configure() {
  155. bindInterceptor(Matchers.any(), Matchers.any(), new CountingInterceptor());
  156. bindInterceptor(Matchers.any(), Matchers.any(), new CountingInterceptor());
  157. }
  158. });
  159. Interceptable interceptable = injector.getInstance(Interceptable.class);
  160. try {
  161. interceptable.explode();
  162. fail();
  163. } catch (Exception e) {
  164. // validate all causes.
  165. for (Throwable t = e; t != null; t = t.getCause()) {
  166. StackTraceElement[] stackTraceElement = t.getStackTrace();
  167. assertEquals("explode", stackTraceElement[0].getMethodName());
  168. assertEquals("invoke", stackTraceElement[1].getMethodName());
  169. assertEquals("invoke", stackTraceElement[2].getMethodName());
  170. assertEquals("testInterceptedMethodThrows", stackTraceElement[3].getMethodName());
  171. }
  172. }
  173. }
  174. public void testNotInterceptedMethodsInInterceptedClassDontAddFrames() {
  175. Injector injector = Guice.createInjector(new AbstractModule() {
  176. protected void configure() {
  177. bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
  178. new NoOpInterceptor());
  179. }
  180. });
  181. Interceptable interceptable = injector.getInstance(Interceptable.class);
  182. assertNull(interceptable.lastElements);
  183. interceptable.foo();
  184. boolean cglibFound = false;
  185. for (int i = 0; i < interceptable.lastElements.length; i++) {
  186. if (interceptable.lastElements[i].toString().contains("cglib")) {
  187. cglibFound = true;
  188. break;
  189. }
  190. }
  191. assertTrue(Arrays.toString(interceptable.lastElements), cglibFound);
  192. cglibFound = false;
  193. interceptable.bar();
  194. for (int i = 0; i < interceptable.lastElements.length; i++) {
  195. if (interceptable.lastElements[i].toString().contains("cglib")) {
  196. cglibFound = true;
  197. break;
  198. }
  199. }
  200. assertFalse(Arrays.toString(interceptable.lastElements), cglibFound);
  201. }
  202. static class Foo {}
  203. static class Bar {}
  204. public static class Interceptable {
  205. StackTraceElement[] lastElements;
  206. public Foo foo() {
  207. lastElements = Thread.currentThread().getStackTrace();
  208. return new Foo() {};
  209. }
  210. public Bar bar() {
  211. lastElements = Thread.currentThread().getStackTrace();
  212. return new Bar() {};
  213. }
  214. public String explode() throws Exception {
  215. lastElements = Thread.currentThread().getStackTrace();
  216. throw new Exception("kaboom!", new RuntimeException("boom!"));
  217. }
  218. }
  219. public static final class NotInterceptable {}
  220. public void testInterceptingNonBridgeWorks() {
  221. Injector injector = Guice.createInjector(new AbstractModule() {
  222. @Override
  223. protected void configure() {
  224. bind(Interface.class).to(Impl.class);
  225. bindInterceptor(Matchers.any(), new AbstractMatcher<Method>() {
  226. public boolean matches(Method t) {
  227. return !t.isBridge() && t.getDeclaringClass() != Object.class;
  228. }
  229. }, new CountingInterceptor());
  230. }
  231. });
  232. Interface intf = injector.getInstance(Interface.class);
  233. assertEquals(0, count.get());
  234. intf.aMethod(null);
  235. assertEquals(1, count.get());
  236. }
  237. static class ErasedType {}
  238. static class RetType extends ErasedType {}
  239. static abstract class Superclass<T extends ErasedType> {
  240. public T aMethod(T t) { return null; }
  241. }
  242. public interface Interface {
  243. RetType aMethod(RetType obj);
  244. }
  245. public static class Impl extends Superclass<RetType> implements Interface {
  246. }
  247. public void testInterceptionOrder() {
  248. final List<String> callList = Lists.newArrayList();
  249. Injector injector = Guice.createInjector(new AbstractModule() {
  250. protected void configure() {
  251. bindInterceptor(Matchers.any(), Matchers.any(),
  252. new NamedInterceptor("a", callList),
  253. new NamedInterceptor("b", callList),
  254. new NamedInterceptor("c", callList));
  255. }
  256. });
  257. Interceptable interceptable = injector.getInstance(Interceptable.class);
  258. assertEquals(0, callList.size());
  259. interceptable.foo();
  260. assertEquals(Arrays.asList("a", "b", "c"), callList);
  261. }
  262. private final class NamedInterceptor implements MethodInterceptor {
  263. private final String name;
  264. final List<String> called;
  265. NamedInterceptor(String name, List<String> callList) {
  266. this.name = name;
  267. this.called = callList;
  268. }
  269. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  270. called.add(name);
  271. return methodInvocation.proceed();
  272. }
  273. }
  274. public void testDeDuplicateInterceptors() throws Exception {
  275. Injector injector = Guice.createInjector(new AbstractModule() {
  276. @Override protected void configure() {
  277. CountingInterceptor interceptor = new CountingInterceptor();
  278. bindInterceptor(Matchers.any(), Matchers.any(), interceptor);
  279. bindInterceptor(Matchers.any(), Matchers.any(), interceptor);
  280. }
  281. });
  282. Interceptable interceptable = injector.getInstance(Interceptable.class);
  283. interceptable.foo();
  284. assertEquals(1, count.get());
  285. }
  286. public void testCallLater() {
  287. final Queue<Runnable> queue = Lists.newLinkedList();
  288. Injector injector = Guice.createInjector(new AbstractModule() {
  289. protected void configure() {
  290. bindInterceptor(Matchers.any(), Matchers.any(), new CallLaterInterceptor(queue));
  291. }
  292. });
  293. Interceptable interceptable = injector.getInstance(Interceptable.class);
  294. interceptable.foo();
  295. assertNull(interceptable.lastElements);
  296. assertEquals(1, queue.size());
  297. queue.remove().run();
  298. assertNotNull(interceptable.lastElements);
  299. }
  300. private final class CallLaterInterceptor implements MethodInterceptor {
  301. private final Queue<Runnable> queue;
  302. public CallLaterInterceptor(Queue<Runnable> queue) {
  303. this.queue = queue;
  304. }
  305. public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
  306. queue.add(new Runnable() {
  307. @Override
  308. public void run() {
  309. try {
  310. methodInvocation.proceed();
  311. } catch (Throwable t) {
  312. throw new RuntimeException(t);
  313. }
  314. }
  315. });
  316. return null;
  317. }
  318. }
  319. }