Spring使用groovy作为bean,官方用了lang标签,但都是一个个文件。Groovy本身编译成class文件后当然可以和Java完全一样可以被component-scan。
但是我想实现能够扫描groovy文件,并且groovy文件发生修改时候能够重新load(方便开发环境中提高效率),网上查查了,然后自己摸索了下,简单实现了。
思路:
1. 通过NamespaceHandlerSupport自己写一个parser,parser和已有的component-scan的区别就是修改了ClassPathBeanDefinitionScanner的reourceLoader,classloader用GroovyClassLoader,metadataReaderFactory重写了一个支持groovy class的,spring的是用asm去解析class文件(所以他也只支持编译后的)。
2. 写个timer监听groovy文件的最后修改时间,发生变化的,就让application context reload。
代码献上
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ComponentScanBeanDefinitionParser;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;
import org.w3c.dom.Element;
public class ComponentGroovyScanParser extends ComponentScanBeanDefinitionParser {
@Override
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), true);
scanner.setEnvironment(parserContext.getReaderContext().getEnvironment());
scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
scanner.addIncludeFilter(new AnnotationTypeFilter(Component.class));
scanner.setResourceLoader(new PathMatchingResourcePatternResolver(ClassLoaderHolder.gcl));
scanner.setResourcePattern("**/*.groovy");
PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(
ClassLoaderHolder.gcl);
scanner.setMetadataReaderFactory(new CachingMetadataReaderFactory2(resourcePatternResolver));
return scanner;
}
}
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.asm.Opcodes;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
public class CachingMetadataReaderFactory2 extends SimpleMetadataReaderFactory {
/** Default maximum number of entries for the MetadataReader cache: 256 */
public static final int DEFAULT_CACHE_LIMIT = 256;
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
@SuppressWarnings("serial")
private final Map<Resource, MetadataReader> metadataReaderCache = new LinkedHashMap<Resource, MetadataReader>(
DEFAULT_CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Resource, MetadataReader> eldest) {
return size() > getCacheLimit();
}
};
/**
* Create a new CachingMetadataReaderFactory for the default class loader.
*/
public CachingMetadataReaderFactory2() {
super();
}
/**
* Create a new CachingMetadataReaderFactory for the given resource loader.
*
* @param resourceLoader
* the Spring ResourceLoader to use (also determines the
* ClassLoader to use)
*/
public CachingMetadataReaderFactory2(ResourceLoader resourceLoader) {
super(resourceLoader);
}
/**
* Create a new CachingMetadataReaderFactory for the given class loader.
*
* @param classLoader
* the ClassLoader to use
*/
public CachingMetadataReaderFactory2(ClassLoader classLoader) {
super(classLoader);
}
/**
* Specify the maximum number of entries for the MetadataReader cache.
* Default is 256.
*/
public void setCacheLimit(int cacheLimit) {
this.cacheLimit = cacheLimit;
}
/**
* Return the maximum number of entries for the MetadataReader cache.
*/
public int getCacheLimit() {
return this.cacheLimit;
}
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
if (getCacheLimit() <= 0) {
return super.getMetadataReader(resource);
}
synchronized (this.metadataReaderCache) {
MetadataReader metadataReader = this.metadataReaderCache.get(resource);
if (metadataReader == null) {
metadataReader = genReader(resource);
this.metadataReaderCache.put(resource, metadataReader);
}
return metadataReader;
}
}
@SuppressWarnings("rawtypes")
private MetadataReader genReader(final Resource resource) throws IOException {
Class clz = ClassLoaderHolder.gcl.parseClass(resource.getFile());
final AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(
super.getResourceLoader().getClassLoader());
Class[] ll = clz.getInterfaces();
// must has GroovyObject
String[] interfaces = new String[ll.length];
for (int i = 0; i < interfaces.length; i++) {
interfaces[i] = ll[i].getName();
}
visitor.visit(0, Opcodes.ACC_PUBLIC, clz.getName(), null, clz.getSuperclass().getName(), interfaces);
Annotation[] ll2 = clz.getAnnotations();
if (ll2.length > 0) {
for (Annotation ano : ll2) {
visitor.getAnnotationTypes().add(ano.annotationType().getName());
}
}
MetadataReader reader = new MetadataReader() {
@Override
public Resource getResource() {
return resource;
}
@Override
public ClassMetadata getClassMetadata() {
return visitor;
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return visitor;
}
};
return reader;
}
/**
* Clear the entire MetadataReader cache, removing all cached class
* metadata.
*/
public void clearCache() {
synchronized (this.metadataReaderCache) {
this.metadataReaderCache.clear();
}
}
}
import groovy.lang.GroovyClassLoader;
public class ClassLoaderHolder {
public static GroovyClassLoader gcl = new GroovyClassLoader();
static {
gcl.addClasspath("src");
}
}