mirror of
https://github.com/apache/struts.git
synced 2026-08-11 01:27:14 +00:00
WW-3183 Add class reloading to the Spring plugin
git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@793388 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
@@ -71,6 +71,13 @@
|
||||
<scope>test</scope>
|
||||
</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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+181
@@ -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) {
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -26,6 +26,7 @@ import com.opensymphony.xwork2.spring.SpringObjectFactory;
|
||||
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 @@ public class StrutsSpringObjectFactory extends SpringObjectFactory {
|
||||
@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 @@ public class StrutsSpringObjectFactory extends SpringObjectFactory {
|
||||
|
||||
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;
|
||||
|
||||
+2
-2
@@ -39,7 +39,7 @@ public class StrutsSpringObjectFactoryTest extends TestCase {
|
||||
|
||||
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 @@ public class StrutsSpringObjectFactoryTest extends TestCase {
|
||||
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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user