package org.hotswap.agent.plugin.spring; import org.hotswap.agent.plugin.hotswapper.HotSwapper; import org.hotswap.agent.plugin.spring.scanner.ClassPathBeanDefinitionScannerAgent; import org.hotswap.agent.plugin.spring.testBeans.BeanPrototype; import org.hotswap.agent.plugin.spring.testBeans.BeanRepository; import org.hotswap.agent.plugin.spring.testBeans.BeanService; import org.hotswap.agent.plugin.spring.testBeans.BeanServiceImpl; import org.hotswap.agent.plugin.spring.testBeansHotswap.BeanPrototype2; import org.hotswap.agent.plugin.spring.testBeansHotswap.BeanRepository2; import org.hotswap.agent.plugin.spring.testBeansHotswap.BeanServiceImpl2; import org.hotswap.agent.util.ReflectionHelper; import org.hotswap.agent.util.test.WaitHelper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Hotswap class files of spring beans. * * See maven setup for javaagent and autohotswap settings. * * @author Jiri Bubnik */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class SpringPluginTest { @Autowired AutowireCapableBeanFactory factory; /** * Check correct setup. */ @Test public void basicTest() { assertEquals("Hello from Repository ServiceWithAspect", factory.getBean(BeanService.class).hello()); assertEquals("Hello from Repository ServiceWithAspect Prototype", factory.getBean(BeanPrototype.class).hello()); } /** * Switch method implementation (using bean definition or interface). */ @Test public void hotswapSeviceTest() throws Exception { BeanServiceImpl bean = factory.getBean(BeanServiceImpl.class); assertEquals("Hello from Repository ServiceWithAspect", bean.hello()); swapClasses(BeanServiceImpl.class, BeanServiceImpl2.class.getName()); assertEquals("Hello from ChangedRepository Service2WithAspect", bean.hello()); // ensure that using interface is Ok as well assertEquals("Hello from ChangedRepository Service2WithAspect", factory.getBean(BeanService.class).hello()); // return configuration swapClasses(BeanServiceImpl.class, BeanServiceImpl.class.getName()); assertEquals("Hello from Repository ServiceWithAspect", bean.hello()); } /** * Add new method - invoke via reflection (not available at compilation time). */ @Test public void hotswapSeviceAddMethodTest() throws Exception { swapClasses(BeanServiceImpl.class, BeanServiceImpl2.class.getName()); String helloNewMethodIfaceVal = (String) ReflectionHelper.invoke(factory.getBean(BeanService.class), BeanServiceImpl.class, "helloNewMethod", new Class[] {}); assertEquals("Hello from helloNewMethod Service2", helloNewMethodIfaceVal); String helloNewMethodImplVal = (String) ReflectionHelper.invoke(factory.getBean(BeanServiceImpl.class), BeanServiceImpl.class, "helloNewMethod", new Class[] {}); assertEquals("Hello from helloNewMethod Service2", helloNewMethodImplVal); // return configuration swapClasses(BeanServiceImpl.class, BeanServiceImpl.class.getName()); assertEquals("Hello from Repository ServiceWithAspect", factory.getBean(BeanServiceImpl.class).hello()); } @Test public void hotswapRepositoryTest() throws Exception { BeanServiceImpl bean = factory.getBean(BeanServiceImpl.class); assertEquals("Hello from Repository ServiceWithAspect", bean.hello()); swapClasses(BeanRepository.class, BeanRepository2.class.getName()); assertEquals("Hello from ChangedRepository2 ServiceWithAspect", bean.hello()); // return configuration swapClasses(BeanRepository.class, BeanRepository.class.getName()); assertEquals("Hello from Repository ServiceWithAspect", bean.hello()); } @Test public void hotswapRepositoryNewMethodTest() throws Exception { assertEquals("Hello from Repository ServiceWithAspect", factory.getBean(BeanServiceImpl.class).hello()); swapClasses(BeanRepository.class, BeanRepository2.class.getName()); String helloNewMethodImplVal = (String) ReflectionHelper.invoke(factory.getBean(BeanRepository.class), BeanRepository.class, "helloNewMethod", new Class[] {}); assertEquals("Repository new method", helloNewMethodImplVal); // return configuration swapClasses(BeanRepository.class, BeanRepository.class.getName()); assertEquals("Hello from Repository ServiceWithAspect", factory.getBean(BeanServiceImpl.class).hello()); } @Test public void hotswapPrototypeTest() throws Exception { assertEquals("Hello from Repository ServiceWithAspect Prototype", factory.getBean(BeanPrototype.class).hello()); // swap service this prototype is dependent to swapClasses(BeanServiceImpl.class, BeanServiceImpl2.class.getName()); assertEquals("Hello from ChangedRepository Service2WithAspect Prototype", factory.getBean(BeanPrototype.class).hello()); // swap autowired field swapClasses(BeanPrototype.class, BeanPrototype2.class.getName()); assertEquals("Hello from Repository Prototype2", factory.getBean(BeanPrototype.class).hello()); // return configuration swapClasses(BeanServiceImpl.class, BeanServiceImpl.class.getName()); swapClasses(BeanPrototype.class, BeanPrototype.class.getName()); assertEquals("Hello from Repository ServiceWithAspect Prototype", factory.getBean(BeanPrototype.class).hello()); } /** * Plugin is currently unable to reload prototype bean instance. */ @Test public void hotswapPrototypeTestFailWhenHoldingInstance() throws Exception { BeanPrototype beanPrototypeInstance = factory.getBean(BeanPrototype.class); assertEquals("Hello from Repository ServiceWithAspect Prototype", beanPrototypeInstance.hello()); // swap service this prototype is dependent to try { swapClasses(BeanServiceImpl.class, BeanServiceImpl2.class.getName()); assertEquals("Hello from ChangedRepository Service2WithAspect Prototype", beanPrototypeInstance.hello()); throw new IllegalStateException("Reload prototype bean should not be correctly initialized."); } catch (NullPointerException e) { // BeanServiceImpl2 contains reference to different repository. Because existing reference // is not changed, this reference is null } // return configuration swapClasses(BeanServiceImpl.class, BeanServiceImpl.class.getName()); assertEquals("Hello from Repository ServiceWithAspect Prototype", factory.getBean(BeanPrototype.class).hello()); } private void swapClasses(Class original, String swap) throws Exception { ClassPathBeanDefinitionScannerAgent.reloadFlag = true; HotSwapper.swapClasses(original, swap); assertTrue(WaitHelper.waitForCommand(new WaitHelper.Command() { @Override public boolean result() throws Exception { return !ClassPathBeanDefinitionScannerAgent.reloadFlag; } })); // TODO do not know why sleep is needed, maybe a separate thread in Spring refresh? Thread.sleep(100); } }