首先,我们来看看Ehcache的初始化时序图。
从时序图可以看出,Ehcache内部是使用SAX来解析配置文件的。
一般来说,XML文件的解析就是将配置文件解析成配置对象,下面是Ehcache的配置文件和配置类。
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true" monitoring="autodetect"
dynamicConfig="true">
<cacheManagerEventListenerFactory class="" properties=""/>
<cache name="userCache"
maxElementsInMemory="400"
overflowToDisk="false"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU">
</cache>
</ehcache>
public final class Configuration {
private String cacheManagerName;
private boolean updateCheck = true;
private int defaultTransactionTimeoutInSeconds = 15;
private Monitoring monitoring = Monitoring.AUTODETECT;
//缓存配置
private CacheConfiguration defaultCacheConfiguration;
private final Map<String, CacheConfiguration> cacheConfigurations;
//分布式缓存的一些配置
private FactoryConfiguration cacheManagerEventListenerFactoryConfiguration;
private TerracottaClientConfiguration terracottaConfigConfiguration;
private ManagementRESTServiceConfiguration managementRESTService;
private final List<FactoryConfiguration> cacheManagerPeerProviderFactoryConfiguration;
private final List<FactoryConfiguration> cacheManagerPeerListenerFactoryConfiguration;
private SizeOfPolicyConfiguration sizeOfPolicyConfiguration;
private FactoryConfiguration transactionManagerLookupConfiguration;
private ConfigurationSource configurationSource;
private boolean dynamicConfig = true;
private Long maxBytesLocalHeap;
private String maxBytesLocalHeapInput;
private Long maxBytesLocalOffHeap;
private String maxBytesLocalOffHeapInput;
private Long maxBytesLocalDisk;
private String maxBytesLocalDiskInput;
}
那么,ehcache是如何将ehcache.xml转换为Configuration对象的呢?
这个是通过BeanHandler来完成的,BeanHandler主要采用做了下面几件事情:
- 逐行解析标签,通过标签名称找到该标签在Configuration对象中的set或者add方法,然后根据方法的第一个参数使用反射实例化一个对象
- 通过反射,将标签的属性添加到具体的对象中
final class BeanHandler extends DefaultHandler {
private static final Logger LOG = LoggerFactory.getLogger(BeanHandler.class.getName());
private final Object bean;
private ElementInfo element;
private Locator locator;
public BeanHandler(final Object bean) {
this.bean = bean;
}
@Override
public final void startElement(final String uri,final String localName,final String qName,final Attributes attributes)throws SAXException {
if (extractingSubtree() || startExtractingSubtree(getTagPart(qName))) {
//……
} else {
//每次遇到新的起始标签时,创建新的ElementInfo对象,并关联它的父标签对象
if (element == null) {
element = new ElementInfo(qName, bean);
} else {
//通过标签名称,找到Configuration类中的set或者add方法,然后通过该方法的第一个参数实例化一个对象,最后调用set或者add方法。
final Object child = createChild(element, qName);
element = new ElementInfo(element, qName, child);
}
// 为新的ElementInfo对象添加属性
for (int i = 0; i < attributes.getLength(); i++) {
final String attrName = attributes.getQName(i);
final String attrValue = attributes.getValue(i);
setAttribute(element, attrName, attrValue);
}
}
}
@Override
public final void endElement(final String uri,final String localName,final String qName)throws SAXException {
if (element.parent != null) {
if (extractingSubtree()) {
//……
} else {
addChild(element.parent.bean, element.bean, qName);
}
}
element = element.parent;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if (extractingSubtree()) {
appendToSubtree(ch, start, length);
}
}
private Object createChild(final ElementInfo parent, final String name) throws SAXException {
try {
// Look for a create<name> method
final Class parentClass = parent.bean.getClass();
Method method = findCreateMethod(parentClass, name);
if (method != null) {
return method.invoke(parent.bean, new Object[] {});
}
// Look for an add<name> method
method = findSetMethod(parentClass, "add", name);
if (method != null) {
return createInstance(parent.bean, method.getParameterTypes()[0]);
}
} catch (final Exception e) {
throw new SAXException(getLocation() + ": Could not create nested element <" + name + ">.", e);
}
}
private static Object createInstance(Object parent, Class childClass)
throws Exception {
final Constructor[] constructors = childClass.getDeclaredConstructors();
ArrayList candidates = new ArrayList();
for (final Constructor constructor : constructors) {
final Class[] params = constructor.getParameterTypes();
if (params.length == 0) {
candidates.add(constructor);
} else if (params.length == 1 && params[0].isInstance(parent)) {
candidates.add(constructor);
}
}
switch (candidates.size()) {
case 0:
throw new Exception("No constructor for class " + childClass.getName());
case 1:
break;
default:
throw new Exception("Multiple constructors for class " + childClass.getName());
}
final Constructor constructor = (Constructor) candidates.remove(0);
constructor.setAccessible(true);
if (constructor.getParameterTypes().length == 0) {
return constructor.newInstance(new Object[] {});
} else {
return constructor.newInstance(new Object[]{parent});
}
}
private static Method findCreateMethod(Class objClass, String name) {
final String methodName = makeMethodName("create", name);
final Method[] methods = objClass.getMethods();
for (final Method method : methods) {
if (!method.getName().equals(methodName)) {
continue;
}
if (Modifier.isStatic(method.getModifiers())) {
continue;
}
if (method.getParameterTypes().length != 0) {
continue;
}
if (method.getReturnType().isPrimitive() || method.getReturnType().isArray()) {
continue;
}
return method;
}
return null;
}
private void setAttribute(final ElementInfo element,final String attrName,final String attrValue) throws SAXException {
try {
final Class objClass = element.bean.getClass();
final Method method = chooseSetMethod(objClass, "set", attrName, String.class);
if (method != null) {
final Object realValue = convert(attrName, method.getParameterTypes()[0], attrValue);
method.invoke(element.bean, new Object[]{realValue});
return;
} else {
if (element.elementName.equals("ehcache")) {
LOG.debug("Ignoring ehcache attribute {}", attrName);
return;
}
}
} catch (final InvocationTargetException e) {
//……
} catch (final Exception e) {
//……
}
//……
}
private Method findSetMethod(final Class objClass,final String prefix,final String name)throws Exception {
final String methodName = makeMethodName(prefix, name);
final Method[] methods = objClass.getMethods();
Method candidate = null;
for (final Method method : methods) {
if (!method.getName().equals(methodName)) {
continue;
}
if (Modifier.isStatic(method.getModifiers())) {
continue;
}
if (method.getParameterTypes().length != 1) {
continue;
}
if (!method.getReturnType().equals(Void.TYPE)) {
continue;
}
if (candidate != null) {
throw new Exception("Multiple " + methodName + "() methods in class " + objClass.getName() + ".");
}
candidate = method;
}
return candidate;
}
private void addChild(final Object parent,final Object child,final String name)throws SAXException {
try {
// Look for an add<name> method on the parent
final Method method = findSetMethod(parent.getClass(), "add", name);
if (method != null) {
method.invoke(parent, new Object[]{child});
}
} catch (final InvocationTargetException e) {
final SAXException exc = new SAXException(getLocation() + ": Could not finish element <" + name + ">." +
" Message was: " + e.getTargetException());
throw exc;
} catch (final Exception e) {
throw new SAXException(getLocation() + ": Could not finish element <" + name + ">.");
}
}
private static final class ElementInfo {
private final ElementInfo parent;
private final String elementName;
private final Object bean;
public ElementInfo(final String elementName, final Object bean) {
parent = null;
this.elementName = elementName;
this.bean = bean;
}
public ElementInfo(final ElementInfo parent, final String elementName, final Object bean) {
this.parent = parent;
this.elementName = elementName;
this.bean = bean;
}
}
}
BeanHandler有下面几点值得我们学习:
- 解析过程全部使用反射,无须关心具体的标签
- Configuration需要提供set或add方法