diff --git a/src/play/classloading/RebelClassloader.java b/src/play/classloading/RebelClassloader.java index d7752dc..20509b4 100644 --- a/src/play/classloading/RebelClassloader.java +++ b/src/play/classloading/RebelClassloader.java @@ -4,10 +4,29 @@ import play.Play; import play.exceptions.RestartNeededException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + public class RebelClassloader extends ApplicationClassloader { + private static final Map> cache = new ConcurrentHashMap<>(); + private static final Map unexistingClasses = new ConcurrentHashMap<>(); + @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - return super.loadClass(name, resolve); + Class aClass = cache.get(name); + if (aClass == null) { + if (unexistingClasses.get(name) != null) return null; + + try { + aClass = super.loadClass(name, resolve); + cache.put(name, aClass); + } + catch (ClassNotFoundException e) { + unexistingClasses.put(name, true); + } + } + return aClass; } @Override public Class loadApplicationClass(String name) { diff --git a/test/play/classloading/RebelClassloaderTest.java b/test/play/classloading/RebelClassloaderTest.java new file mode 100644 index 0000000..9d76660 --- /dev/null +++ b/test/play/classloading/RebelClassloaderTest.java @@ -0,0 +1,68 @@ +package play.classloading; + +import org.junit.Before; +import org.junit.Test; +import play.Play; +import play.classloading.ApplicationClasses.ApplicationClass; +import play.exceptions.RestartNeededException; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +public class RebelClassloaderTest { + RebelClassloader cl; + + @Before + public void setUp() { + Play.classes = new ApplicationClasses(); + cl = new RebelClassloader(); + } + + @Test + public void returnsNullForUnexistingClass() throws ClassNotFoundException { + assertThat(cl.loadClass("Script1BeanInfo", true), equalTo(null)); + } + + @Test + public void doesNotTryToLoadUnexistingClassMultipleTimes() throws ClassNotFoundException { + cl.loadClass("Script1BeanInfo", true); + cl = spy(cl); + + assertThat(cl.loadClass("Script1BeanInfo", true), equalTo(null)); + verify(cl, never()).loadApplicationClass(anyString()); + } + + @Test + public void loadsClassForApplicationClasses() throws ClassNotFoundException { + ApplicationClass applicationClass = new ApplicationClass("com.my.User"); + applicationClass.javaClass = User.class; + Play.classes.add(applicationClass); + + assertThat(cl.loadClass("com.my.User", true), equalTo(User.class)); + } + + @Test + public void cachesLoadedClasses() throws ClassNotFoundException { + ApplicationClass applicationClass = new ApplicationClass("com.my.User"); + applicationClass.javaClass = User.class; + Play.classes.add(applicationClass); + cl.loadClass("com.my.User", true); + + cl = spy(cl); + assertThat(cl.loadClass("com.my.User", true), equalTo(User.class)); + verify(cl, never()).loadApplicationClass(anyString()); + } + + @Test + public void doesNotDetectChanges() throws RestartNeededException { + Play.classes = mock(ApplicationClasses.class); + + cl.detectChanges(); + + verify(Play.classes, never()).all(); + } + + private static class User {} +} \ No newline at end of file