You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2009/07/12 20:17:55 UTC

svn commit: r793388 - in /struts/struts2/trunk/plugins/spring: ./ src/main/java/org/apache/struts2/spring/ src/test/java/org/apache/struts2/spring/

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>
+ *  &lt;context-param&gt;
+ *       &lt;param-name&gt;contextClass&lt;/param-name&gt;
+ *       &lt;param-value&gt;org.apache.struts2.spring.ClassReloadingXMLWebApplicationContext&lt;/param-value&gt;
+ *   &lt;/context-param&gt;
+ *  </li>
+ * <li>Add Apache Commons JCI FAM to the classpath. If you are using maven, add this to pom.xml:
+ *  <pre>
+ *  &lt;dependency&gt;
+ *       &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
+ *       &lt;artifactId&gt;commons-jci-fam&lt;/artifactId&gt;
+ *       &lt;version&gt;1.0&lt;/version&gt;
+ *       &lt;optional&gt;true&lt;/optional&gt;
+ *  &lt;/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());
     }