PageRenderTime 53ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://gitlab.com/metamorphiccode/guice
Java | 652 lines | 533 code | 65 blank | 54 comment | 4 complexity | c39affc8eced9154da4861d2d79ba845 MD5 | raw file
  1. /**
  2. * Copyright (C) 2006 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.Asserts.assertContains;
  18. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  19. import com.google.common.collect.Iterables;
  20. import com.google.common.collect.Maps;
  21. import junit.framework.TestCase;
  22. import java.lang.annotation.ElementType;
  23. import java.lang.annotation.Retention;
  24. import java.lang.annotation.Target;
  25. import java.util.ArrayList;
  26. import java.util.List;
  27. import java.util.Map;
  28. /**
  29. * @author crazybob@google.com (Bob Lee)
  30. * @author sameb@google.com (Sam Berlin)
  31. */
  32. public class CircularDependencyTest extends TestCase {
  33. @Override
  34. protected void setUp() throws Exception {
  35. AImpl.nextId = 0;
  36. BImpl.nextId = 0;
  37. }
  38. public void testCircularlyDependentConstructors()
  39. throws CreationException {
  40. Injector injector = Guice.createInjector(new AbstractModule() {
  41. @Override
  42. protected void configure() {
  43. bind(A.class).to(AImpl.class);
  44. bind(B.class).to(BImpl.class);
  45. }
  46. });
  47. assertCircularDependencies(injector);
  48. }
  49. public void testCircularlyDependentConstructorsWithProviderMethods()
  50. throws CreationException {
  51. Injector injector = Guice.createInjector(new AbstractModule() {
  52. @Override
  53. protected void configure() {}
  54. @Provides @Singleton A a(B b) { return new AImpl(b); }
  55. @Provides B b(A a) { return new BImpl(a); }
  56. });
  57. assertCircularDependencies(injector);
  58. }
  59. public void testCircularlyDependentConstructorsWithProviderInstances()
  60. throws CreationException {
  61. Injector injector = Guice.createInjector(new AbstractModule() {
  62. @Override
  63. protected void configure() {
  64. bind(A.class).toProvider(new Provider<A>() {
  65. @Inject Provider<B> bp;
  66. @Override
  67. public A get() {
  68. return new AImpl(bp.get());
  69. }
  70. }).in(Singleton.class);
  71. bind(B.class).toProvider(new Provider<B>() {
  72. @Inject Provider<A> ap;
  73. @Override
  74. public B get() {
  75. return new BImpl(ap.get());
  76. }
  77. });
  78. }
  79. });
  80. assertCircularDependencies(injector);
  81. }
  82. public void testCircularlyDependentConstructorsWithProviderKeys()
  83. throws CreationException {
  84. Injector injector = Guice.createInjector(new AbstractModule() {
  85. @Override
  86. protected void configure() {
  87. bind(A.class).toProvider(AP.class).in(Singleton.class);
  88. bind(B.class).toProvider(BP.class);
  89. }
  90. });
  91. assertCircularDependencies(injector);
  92. }
  93. public void testCircularlyDependentConstructorsWithProvidedBy()
  94. throws CreationException {
  95. Injector injector = Guice.createInjector();
  96. assertCircularDependencies(injector);
  97. }
  98. private void assertCircularDependencies(Injector injector) {
  99. A a = injector.getInstance(A.class);
  100. assertNotNull(a.getB().getA());
  101. assertEquals(0, a.id());
  102. assertEquals(a.id(), a.getB().getA().id());
  103. assertEquals(0, a.getB().id());
  104. assertEquals(1, AImpl.nextId);
  105. assertEquals(1, BImpl.nextId);
  106. assertSame(a, injector.getInstance(A.class));
  107. }
  108. @ProvidedBy(AutoAP.class)
  109. public interface A {
  110. B getB();
  111. int id();
  112. }
  113. @Singleton
  114. static class AImpl implements A {
  115. static int nextId;
  116. int id = nextId++;
  117. final B b;
  118. @Inject public AImpl(B b) {
  119. this.b = b;
  120. }
  121. @Override
  122. public int id() {
  123. return id;
  124. }
  125. @Override
  126. public B getB() {
  127. return b;
  128. }
  129. }
  130. static class AP implements Provider<A> {
  131. @Inject Provider<B> bp;
  132. @Override
  133. public A get() {
  134. return new AImpl(bp.get());
  135. }
  136. }
  137. @Singleton
  138. static class AutoAP implements Provider<A> {
  139. @Inject Provider<B> bp;
  140. A a;
  141. @Override
  142. public A get() {
  143. if (a == null) {
  144. a = new AImpl(bp.get());
  145. }
  146. return a;
  147. }
  148. }
  149. @ProvidedBy(BP.class)
  150. public interface B {
  151. A getA();
  152. int id();
  153. }
  154. static class BImpl implements B {
  155. static int nextId;
  156. int id = nextId++;
  157. final A a;
  158. @Inject public BImpl(A a) {
  159. this.a = a;
  160. }
  161. @Override
  162. public int id() {
  163. return id;
  164. }
  165. @Override
  166. public A getA() {
  167. return a;
  168. }
  169. }
  170. static class BP implements Provider<B> {
  171. Provider<A> ap;
  172. @Inject BP(Provider<A> ap) {
  173. this.ap = ap;
  174. }
  175. @Override
  176. public B get() {
  177. return new BImpl(ap.get());
  178. }
  179. }
  180. public void testUnresolvableCircularDependency() {
  181. try {
  182. Guice.createInjector().getInstance(C.class);
  183. fail();
  184. } catch (ProvisionException expected) {
  185. assertContains(expected.getMessage(),
  186. "Tried proxying " + C.class.getName() + " to support a circular dependency, ",
  187. "but it is not an interface.");
  188. }
  189. }
  190. public void testUnresolvableCircularDependenciesWithProviderInstances() {
  191. try {
  192. Guice.createInjector(new AbstractModule() {
  193. @Override protected void configure() {}
  194. @Provides C c(D d) { return null; }
  195. @Provides D d(C c) { return null; }
  196. }).getInstance(C.class);
  197. fail();
  198. } catch (ProvisionException expected) {
  199. assertContains(expected.getMessage(),
  200. "Tried proxying " + C.class.getName() + " to support a circular dependency, ",
  201. "but it is not an interface.");
  202. }
  203. }
  204. public void testUnresolvableCircularDependenciesWithProviderKeys() {
  205. try {
  206. Guice.createInjector(new AbstractModule() {
  207. @Override protected void configure() {
  208. bind(C2.class).toProvider(C2P.class);
  209. bind(D2.class).toProvider(D2P.class);
  210. }
  211. }).getInstance(C2.class);
  212. fail();
  213. } catch (ProvisionException expected) {
  214. assertContains(expected.getMessage(),
  215. "Tried proxying " + C2.class.getName() + " to support a circular dependency, ",
  216. "but it is not an interface.");
  217. }
  218. }
  219. public void testUnresolvableCircularDependenciesWithProvidedBy() {
  220. try {
  221. Guice.createInjector().getInstance(C2.class);
  222. fail();
  223. } catch (ProvisionException expected) {
  224. assertContains(expected.getMessage(),
  225. "Tried proxying " + C2.class.getName() + " to support a circular dependency, ",
  226. "but it is not an interface.");
  227. }
  228. }
  229. static class C {
  230. @Inject C(D d) {}
  231. }
  232. static class D {
  233. @Inject D(C c) {}
  234. }
  235. static class C2P implements Provider<C2> {
  236. @Inject Provider<D2> dp;
  237. @Override
  238. public C2 get() {
  239. dp.get();
  240. return null;
  241. }
  242. }
  243. static class D2P implements Provider<D2> {
  244. @Inject Provider<C2> cp;
  245. @Override
  246. public D2 get() {
  247. cp.get();
  248. return null;
  249. }
  250. }
  251. @ProvidedBy(C2P.class)
  252. static class C2 {
  253. @Inject C2(D2 d) {}
  254. }
  255. @ProvidedBy(D2P.class)
  256. static class D2 {
  257. @Inject D2(C2 c) {}
  258. }
  259. public void testDisabledCircularDependency() {
  260. try {
  261. Guice.createInjector(new AbstractModule() {
  262. @Override
  263. protected void configure() {
  264. binder().disableCircularProxies();
  265. }
  266. }).getInstance(C.class);
  267. fail();
  268. } catch (ProvisionException expected) {
  269. assertContains(expected.getMessage(),
  270. "Found a circular dependency involving " + C.class.getName() + ", and circular dependencies are disabled.");
  271. }
  272. }
  273. public void testDisabledCircularDependenciesWithProviderInstances() {
  274. try {
  275. Guice.createInjector(new AbstractModule() {
  276. @Override protected void configure() {
  277. binder().disableCircularProxies();
  278. }
  279. @Provides C c(D d) { return null; }
  280. @Provides D d(C c) { return null; }
  281. }).getInstance(C.class);
  282. fail();
  283. } catch (ProvisionException expected) {
  284. assertContains(expected.getMessage(),
  285. "Found a circular dependency involving " + C.class.getName() + ", and circular dependencies are disabled.");
  286. }
  287. }
  288. public void testDisabledCircularDependenciesWithProviderKeys() {
  289. try {
  290. Guice.createInjector(new AbstractModule() {
  291. @Override protected void configure() {
  292. binder().disableCircularProxies();
  293. bind(C2.class).toProvider(C2P.class);
  294. bind(D2.class).toProvider(D2P.class);
  295. }
  296. }).getInstance(C2.class);
  297. fail();
  298. } catch (ProvisionException expected) {
  299. assertContains(expected.getMessage(),
  300. "Found a circular dependency involving " + C2.class.getName() + ", and circular dependencies are disabled.");
  301. }
  302. }
  303. public void testDisabledCircularDependenciesWithProvidedBy() {
  304. try {
  305. Guice.createInjector(new AbstractModule() {
  306. @Override
  307. protected void configure() {
  308. binder().disableCircularProxies();
  309. }
  310. }).getInstance(C2.class);
  311. fail();
  312. } catch (ProvisionException expected) {
  313. assertContains(expected.getMessage(),
  314. "Found a circular dependency involving " + C2.class.getName() + ", and circular dependencies are disabled.");
  315. }
  316. }
  317. /**
  318. * As reported by issue 349, we give a lousy trace when a class is circularly
  319. * dependent on itself in multiple ways.
  320. */
  321. public void testCircularlyDependentMultipleWays() {
  322. Injector injector = Guice.createInjector(new AbstractModule() {
  323. @Override
  324. protected void configure() {
  325. binder.bind(A.class).to(E.class);
  326. binder.bind(B.class).to(E.class);
  327. }
  328. });
  329. injector.getInstance(A.class);
  330. }
  331. public void testDisablingCircularDependencies() {
  332. Injector injector = Guice.createInjector(new AbstractModule() {
  333. @Override
  334. protected void configure() {
  335. binder().disableCircularProxies();
  336. binder.bind(A.class).to(E.class);
  337. binder.bind(B.class).to(E.class);
  338. }
  339. });
  340. try {
  341. injector.getInstance(A.class);
  342. fail("expected exception");
  343. } catch(ProvisionException expected) {
  344. assertContains(expected.getMessage(),
  345. "Found a circular dependency involving " + A.class.getName() + ", and circular dependencies are disabled.",
  346. "Found a circular dependency involving " + B.class.getName() + ", and circular dependencies are disabled.");
  347. }
  348. }
  349. @Singleton
  350. static class E implements A, B {
  351. @Inject
  352. public E(A a, B b) {}
  353. @Override
  354. public B getB() {
  355. return this;
  356. }
  357. @Override
  358. public A getA() {
  359. return this;
  360. }
  361. @Override
  362. public int id() {
  363. return 0;
  364. }
  365. }
  366. public void testCircularDependencyProxyDelegateNeverInitialized() {
  367. Injector injector = Guice.createInjector(new AbstractModule() {
  368. @Override
  369. protected void configure() {
  370. bind(F.class).to(RealF.class);
  371. bind(G.class).to(RealG.class);
  372. }
  373. });
  374. F f = injector.getInstance(F.class);
  375. assertEquals("F", f.g().f().toString());
  376. assertEquals("G", f.g().f().g().toString());
  377. }
  378. public interface F {
  379. G g();
  380. }
  381. @Singleton
  382. public static class RealF implements F {
  383. private final G g;
  384. @Inject RealF(G g) {
  385. this.g = g;
  386. }
  387. @Override
  388. public G g() {
  389. return g;
  390. }
  391. @Override public String toString() {
  392. return "F";
  393. }
  394. }
  395. public interface G {
  396. F f();
  397. }
  398. @Singleton
  399. public static class RealG implements G {
  400. private final F f;
  401. @Inject RealG(F f) {
  402. this.f = f;
  403. }
  404. @Override
  405. public F f() {
  406. return f;
  407. }
  408. @Override public String toString() {
  409. return "G";
  410. }
  411. }
  412. /**
  413. * Tests that ProviderInternalFactory can detect circular dependencies
  414. * before it gets to Scopes.SINGLETON. This is especially important
  415. * because the failure in Scopes.SINGLETON doesn't have enough context to
  416. * provide a decent error message.
  417. */
  418. public void testCircularDependenciesDetectedEarlyWhenDependenciesHaveDifferentTypes() {
  419. Injector injector = Guice.createInjector(new AbstractModule() {
  420. @Override
  421. protected void configure() {
  422. bind(Number.class).to(Integer.class);
  423. }
  424. @Provides @Singleton Integer provideInteger(List list) {
  425. return new Integer(2);
  426. }
  427. @Provides List provideList(Integer integer) {
  428. return new ArrayList();
  429. }
  430. });
  431. try {
  432. injector.getInstance(Number.class);
  433. fail();
  434. } catch(ProvisionException expected) {
  435. assertContains(expected.getMessage(),
  436. "Tried proxying " + Integer.class.getName() + " to support a circular dependency, ",
  437. "but it is not an interface.");
  438. }
  439. }
  440. public void testPrivateModulesDontTriggerCircularErrorsInProviders() {
  441. Injector injector = Guice.createInjector(new AbstractModule() {
  442. @Override
  443. protected void configure() {
  444. install(new PrivateModule() {
  445. @Override
  446. protected void configure() {
  447. bind(Foo.class);
  448. expose(Foo.class);
  449. }
  450. @Provides String provideString(Bar bar) {
  451. return new String("private 1, " + bar.string);
  452. }
  453. });
  454. install(new PrivateModule() {
  455. @Override
  456. protected void configure() {
  457. bind(Bar.class);
  458. expose(Bar.class);
  459. }
  460. @Provides String provideString() {
  461. return new String("private 2");
  462. }
  463. });
  464. }
  465. });
  466. Foo foo = injector.getInstance(Foo.class);
  467. assertEquals("private 1, private 2", foo.string);
  468. }
  469. static class Foo {
  470. @Inject String string;
  471. }
  472. static class Bar {
  473. @Inject String string;
  474. }
  475. /**
  476. * When Scope Providers call their unscoped Provider's get() methods are
  477. * called, it's possible that the result is a circular proxy designed for one
  478. * specific parameter (not for all possible parameters). But custom scopes
  479. * typically cache the results without checking to see if the result is a
  480. * proxy. This leads to caching a result that is unsuitable for reuse for
  481. * other parameters.
  482. *
  483. * This means that custom proxies have to do an
  484. * {@code if(Scopes.isCircularProxy(..))}
  485. * in order to avoid exceptions.
  486. */
  487. public void testCustomScopeCircularProxies() {
  488. Injector injector = Guice.createInjector(new AbstractModule() {
  489. @Override
  490. protected void configure() {
  491. bindScope(SimpleSingleton.class, new BasicSingleton());
  492. bind(H.class).to(HImpl.class);
  493. bind(I.class).to(IImpl.class);
  494. bind(J.class).to(JImpl.class);
  495. }
  496. });
  497. // The reason this happens is because the Scope gets these requests, in order:
  498. // entry: Key<IImpl> (1 - from getInstance call)
  499. // entry: Key<HImpl>
  500. // entry: Key<IImpl> (2 - circular dependency from HImpl)
  501. // result of 2nd Key<IImpl> - a com.google.inject.$Proxy, because it's a circular proxy
  502. // result of Key<HImpl> - an HImpl
  503. // entry: Key<JImpl>
  504. // entry: Key<IImpl> (3 - another circular dependency, this time from JImpl)
  505. // At this point, if the first Key<Impl> result was cached, our cache would have
  506. // Key<IImpl> caching to an instanceof of I, but not an an instanceof of IImpl.
  507. // If returned this, it would result in cglib giving a ClassCastException or
  508. // java reflection giving an IllegalArgumentException when filling in parameters
  509. // for the constructor, because JImpl wants an IImpl, not an I.
  510. try {
  511. injector.getInstance(IImpl.class);
  512. fail();
  513. } catch(ProvisionException pe) {
  514. assertContains(Iterables.getOnlyElement(pe.getErrorMessages()).getMessage(),
  515. "Tried proxying " + IImpl.class.getName()
  516. + " to support a circular dependency, but it is not an interface.");
  517. }
  518. }
  519. interface H {}
  520. interface I {}
  521. interface J {}
  522. @SimpleSingleton
  523. static class HImpl implements H {
  524. @Inject HImpl(I i) {}
  525. }
  526. @SimpleSingleton
  527. static class IImpl implements I {
  528. @Inject IImpl(HImpl i, J j) {}
  529. }
  530. @SimpleSingleton
  531. static class JImpl implements J {
  532. @Inject JImpl(IImpl i) {}
  533. }
  534. @Target({ ElementType.TYPE, ElementType.METHOD })
  535. @Retention(RUNTIME)
  536. @ScopeAnnotation
  537. public @interface SimpleSingleton {}
  538. public static class BasicSingleton implements Scope {
  539. private static Map<Key<?>, Object> cache = Maps.newHashMap();
  540. @Override
  541. public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
  542. return new Provider<T>() {
  543. @Override
  544. @SuppressWarnings("unchecked")
  545. public T get() {
  546. if (!cache.containsKey(key)) {
  547. T t = unscoped.get();
  548. if (Scopes.isCircularProxy(t)) {
  549. return t;
  550. }
  551. cache.put(key, t);
  552. }
  553. return (T)cache.get(key);
  554. }
  555. };
  556. }
  557. }
  558. public void testDisabledNonConstructorCircularDependencies() {
  559. Injector injector = Guice.createInjector(new AbstractModule() {
  560. @Override
  561. protected void configure() {
  562. binder().disableCircularProxies();
  563. }
  564. });
  565. try {
  566. injector.getInstance(K.class);
  567. fail("expected exception");
  568. } catch(ProvisionException expected) {
  569. assertContains(expected.getMessage(),
  570. "Found a circular dependency involving " + K.class.getName() + ", and circular dependencies are disabled.");
  571. }
  572. try {
  573. injector.getInstance(L.class);
  574. fail("expected exception");
  575. } catch(ProvisionException expected) {
  576. assertContains(expected.getMessage(),
  577. "Found a circular dependency involving " + L.class.getName() + ", and circular dependencies are disabled.");
  578. }
  579. }
  580. static class K {
  581. @Inject L l;
  582. }
  583. static class L {
  584. @Inject void inject(K k) {
  585. }
  586. }
  587. }