Author: musachy Date: Sun Jul 12 18:17:55 2009 New Revision: 793388 URL: http://svn.apache.org/viewvc?rev=793388&view=rev Log: WW-3183 Add class reloading to the Spring plugin
Added: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingBeanFactory.java (with props) struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingInstantiationStrategy.java (with props) struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingXMLWebApplicationContext.java (with props) Modified: struts/struts2/trunk/plugins/spring/pom.xml struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/StrutsSpringObjectFactory.java struts/struts2/trunk/plugins/spring/src/test/java/org/apache/struts2/spring/StrutsSpringObjectFactoryTest.java Modified: struts/struts2/trunk/plugins/spring/pom.xml URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/spring/pom.xml?rev=793388&r1=793387&r2=793388&view=diff ============================================================================== --- struts/struts2/trunk/plugins/spring/pom.xml (original) +++ struts/struts2/trunk/plugins/spring/pom.xml Sun Jul 12 18:17:55 2009 @@ -72,6 +72,13 @@ </dependency> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-jci-fam</artifactId> + <version>1.0</version> + <optional>true</optional> + </dependency> + + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> Added: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingBeanFactory.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingBeanFactory.java?rev=793388&view=auto ============================================================================== --- struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingBeanFactory.java (added) +++ struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingBeanFactory.java Sun Jul 12 18:17:55 2009 @@ -0,0 +1,96 @@ +/* + * $Id$ + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.spring; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.CannotLoadBeanClassException; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ClassUtils; +import org.springframework.core.DecoratingClassLoader; + +import java.lang.reflect.Constructor; + + +/** + * Same as DefaultListableBeanFactory, but it doesn't use the constructor and class cached in RootBeanDefinition + */ +public class ClassReloadingBeanFactory extends DefaultListableBeanFactory { + @Override + protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { + Class beanClass = resolveBeanClass(mbd, beanName); + + if (mbd.getFactoryMethodName() != null) { + return instantiateUsingFactoryMethod(beanName, mbd, args); + } + + //commented to cached constructor is not used + /* // Shortcut when re-creating the same bean... + if (mbd.resolvedConstructorOrFactoryMethod != null) { + if (mbd.constructorArgumentsResolved) { + return autowireConstructor(beanName, mbd, null, args); + } else { + return instantiateBean(beanName, mbd); + } + }*/ + + // Need to determine the constructor... + Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); + if (ctors != null || + mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || + mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { + return autowireConstructor(beanName, mbd, ctors, args); + } + + // No special handling: simply use no-arg constructor. + return instantiateBean(beanName, mbd); + } + + protected Class resolveBeanClass(RootBeanDefinition mbd, String beanName, Class[] typesToMatch) { + try { + //commented to cached class is not used + /* if (mbd.hasBeanClass()) { + return mbd.getBeanClass(); + }*/ + if (typesToMatch != null) { + ClassLoader tempClassLoader = getTempClassLoader(); + if (tempClassLoader != null) { + if (tempClassLoader instanceof DecoratingClassLoader) { + DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader; + for (int i = 0; i < typesToMatch.length; i++) { + dcl.excludeClass(typesToMatch[i].getName()); + } + } + String className = mbd.getBeanClassName(); + return (className != null ? ClassUtils.forName(className, tempClassLoader) : null); + } + } + return mbd.resolveBeanClass(getBeanClassLoader()); + } + catch (ClassNotFoundException ex) { + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex); + } + catch (LinkageError err) { + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err); + } + } +} Propchange: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingBeanFactory.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingBeanFactory.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Added: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingInstantiationStrategy.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingInstantiationStrategy.java?rev=793388&view=auto ============================================================================== --- struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingInstantiationStrategy.java (added) +++ struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingInstantiationStrategy.java Sun Jul 12 18:17:55 2009 @@ -0,0 +1,56 @@ +/* + * $Id$ + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.spring; + +import org.springframework.beans.factory.support.SimpleInstantiationStrategy; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.BeanInstantiationException; +import org.springframework.beans.BeanUtils; + +import java.lang.reflect.Constructor; + +/** + * Same as SimpleInstantiationStrategy, but constructor is not cached + */ +public class ClassReloadingInstantiationStrategy extends SimpleInstantiationStrategy { + public Object instantiate( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { + + // Don't override the class with CGLIB if no overrides. + if (beanDefinition.getMethodOverrides().isEmpty()) { + Class clazz = beanDefinition.getBeanClass(); + if (clazz.isInterface()) { + throw new BeanInstantiationException(clazz, "Specified class is an interface"); + } + try { + Constructor constructor = clazz.getDeclaredConstructor((Class[]) null); + return BeanUtils.instantiateClass(constructor, null); + } + catch (Exception ex) { + throw new BeanInstantiationException(clazz, "No default constructor found", ex); + } + } else { + // Must generate CGLIB subclass. + return instantiateWithMethodInjection(beanDefinition, beanName, owner); + } + } +} Propchange: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingInstantiationStrategy.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingInstantiationStrategy.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Added: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingXMLWebApplicationContext.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingXMLWebApplicationContext.java?rev=793388&view=auto ============================================================================== --- struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingXMLWebApplicationContext.java (added) +++ struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingXMLWebApplicationContext.java Sun Jul 12 18:17:55 2009 @@ -0,0 +1,181 @@ +/* + * $Id$ + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.spring; + +import com.opensymphony.xwork2.util.classloader.FileResourceStore; +import com.opensymphony.xwork2.util.classloader.JarResourceStore; +import com.opensymphony.xwork2.util.classloader.ReloadingClassLoader; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.apache.commons.jci.monitor.FilesystemAlterationListener; +import org.apache.commons.jci.monitor.FilesystemAlterationMonitor; +import org.apache.commons.jci.monitor.FilesystemAlterationObserver; +import org.springframework.web.context.support.XmlWebApplicationContext; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.BeansException; + +import java.io.File; +import java.util.List; +import java.util.ArrayList; + +/** + * This class can be used instead of XmlWebApplicationContext, and it will watch jar files and directories for changes + * and reload then changed classes. + * <br /> + * To use this class: + * <ul> + * <li>Set "struts.devMode" to "true" </li> + * <li>Set "struts.class.reloading.watchList" to a comma separated list of directories, or jar files (absolute paths)</p> + * <li>Add this to web.xml: + * <pre> + * <context-param> + * <param-name>contextClass</param-name> + * <param-value>org.apache.struts2.spring.ClassReloadingXMLWebApplicationContext</param-value> + * </context-param> + * </li> + * <li>Add Apache Commons JCI FAM to the classpath. If you are using maven, add this to pom.xml: + * <pre> + * <dependency> + * <groupId>org.apache.commons</groupId> + * <artifactId>commons-jci-fam</artifactId> + * <version>1.0</version> + * <optional>true</optional> + * </dependency> + * </pre> + * </li> + * </ul> + */ +public class ClassReloadingXMLWebApplicationContext extends XmlWebApplicationContext implements FilesystemAlterationListener { + private static final Logger LOG = LoggerFactory.getLogger(ClassReloadingXMLWebApplicationContext.class); + + private ReloadingClassLoader classLoader; + private FilesystemAlterationMonitor fam; + + private ClassReloadingBeanFactory beanFactory; + + public void setupReloading(String[] watchList) { + classLoader = new ReloadingClassLoader(ClassReloadingXMLWebApplicationContext.class.getClassLoader()); + fam = new FilesystemAlterationMonitor(); + + //setup stores + for (String watch : watchList) { + File file = new File(watch); + if (watch.endsWith(".jar")) { + classLoader.addResourceStore(new JarResourceStore(file)); + //register with the fam + fam.addListener(file, this); + LOG.debug("Watching [#0] for changes", file.getAbsolutePath()); + } else { + //get all subdirs + List<File> dirs = new ArrayList<File>(); + getAllPaths(file, dirs); + + for (File dir : dirs) { + classLoader.addResourceStore(new FileResourceStore(dir)); + //register with the fam + fam.addListener(dir, this); + LOG.debug("Watching [#0] for changes", dir.getAbsolutePath()); + } + } + } + //setup the bean factory + beanFactory = new ClassReloadingBeanFactory(); + beanFactory.setInstantiationStrategy(new ClassReloadingInstantiationStrategy()); + beanFactory.setBeanClassLoader(classLoader); + + //start watch thread + fam.start(); + } + + /** + * If root is a dir, find al the subdir paths + */ + private void getAllPaths(File root, List<File> dirs) { + dirs.add(root); + + if (root.isDirectory()) { + File[] files = root.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + getAllPaths(file, dirs); + } + } + } + } + } + + public void close() { + super.close(); + + if (fam != null) { + fam.removeListener(this); + fam.stop(); + } + } + + public void refresh() throws BeansException, IllegalStateException { + if (classLoader != null) { + classLoader.reload(); + } + + super.refresh(); + } + + protected DefaultListableBeanFactory createBeanFactory() { + return beanFactory != null ? beanFactory : super.createBeanFactory(); + } + + protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { + super.prepareBeanFactory(beanFactory); + + //overwrite the class loader in the bean factory + if (classLoader != null) + beanFactory.setBeanClassLoader(classLoader); + } + + public void onDirectoryChange(File file) { + } + + public void onDirectoryCreate(File file) { + } + + public void onDirectoryDelete(File file) { + } + + public void onFileChange(File file) { + if (classLoader != null) + classLoader.reload(); + } + + public void onFileCreate(File file) { + } + + public void onFileDelete(File file) { + } + + public void onStart(FilesystemAlterationObserver filesystemAlterationObserver) { + } + + public void onStop(FilesystemAlterationObserver filesystemAlterationObserver) { + } +} Propchange: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingXMLWebApplicationContext.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/ClassReloadingXMLWebApplicationContext.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Modified: struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/StrutsSpringObjectFactory.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/StrutsSpringObjectFactory.java?rev=793388&r1=793387&r2=793388&view=diff ============================================================================== --- struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/StrutsSpringObjectFactory.java (original) +++ struts/struts2/trunk/plugins/spring/src/main/java/org/apache/struts2/spring/StrutsSpringObjectFactory.java Sun Jul 12 18:17:55 2009 @@ -26,6 +26,7 @@ import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; import org.apache.struts2.StrutsConstants; +import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.web.context.WebApplicationContext; @@ -65,7 +66,9 @@ @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE,required=false) String autoWire, @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE_ALWAYS_RESPECT,required=false) String alwaysAutoWire, @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_USE_CLASS_CACHE,required=false) String useClassCacheStr, - @Inject ServletContext servletContext) { + @Inject ServletContext servletContext, + @Inject(StrutsConstants.STRUTS_DEVMODE) String devMode, + @Inject(value = "struts.class.reloading.watchList", required = false) String watchList) { super(); boolean useClassCache = "true".equals(useClassCacheStr); @@ -87,6 +90,17 @@ this.setApplicationContext(appContext); + if ("true".equals(devMode) + && StringUtils.isNotBlank(watchList) + && appContext instanceof ClassReloadingXMLWebApplicationContext) { + ClassReloadingXMLWebApplicationContext reloadingContext = (ClassReloadingXMLWebApplicationContext) appContext; + reloadingContext.setupReloading(watchList.split(",")); + LOG.info("Class reloading is enabled. Make sure this is not used on a production environment!", watchList); + + //we need to reload the context, so our isntance of the factory is picked up + reloadingContext.refresh(); + } + int type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; // default if ("name".equals(autoWire)) { type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; Modified: struts/struts2/trunk/plugins/spring/src/test/java/org/apache/struts2/spring/StrutsSpringObjectFactoryTest.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/spring/src/test/java/org/apache/struts2/spring/StrutsSpringObjectFactoryTest.java?rev=793388&r1=793387&r2=793388&view=diff ============================================================================== --- struts/struts2/trunk/plugins/spring/src/test/java/org/apache/struts2/spring/StrutsSpringObjectFactoryTest.java (original) +++ struts/struts2/trunk/plugins/spring/src/test/java/org/apache/struts2/spring/StrutsSpringObjectFactoryTest.java Sun Jul 12 18:17:55 2009 @@ -39,7 +39,7 @@ public void testNoSpringContext() throws Exception { // to cover situations where there will be logged an error - StrutsSpringObjectFactory fac = new StrutsSpringObjectFactory(null, null, null, new MockServletContext()); + StrutsSpringObjectFactory fac = new StrutsSpringObjectFactory(null, null, null, new MockServletContext(), null, "false"); assertEquals(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, fac.getAutowireStrategy()); } @@ -53,7 +53,7 @@ ac.setServletContext(msc); ac.setConfigLocations(new String[] {"org/apache/struts2/spring/StrutsSpringObjectFactoryTest-applicationContext.xml"}); ac.refresh(); - StrutsSpringObjectFactory fac = new StrutsSpringObjectFactory("constructor", null, null, msc); + StrutsSpringObjectFactory fac = new StrutsSpringObjectFactory("constructor", null, null, msc, null, "true"); assertEquals(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, fac.getAutowireStrategy()); }