Ehcache源码解析——配置文件解析

本文介绍了Ehcache如何通过BeanHandler将ehcache.xml配置文件解析为Configuration对象的过程。BeanHandler利用反射机制,根据XML标签名查找对应的set或add方法,并实例化对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先,我们来看看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主要采用做了下面几件事情:

  1. 逐行解析标签,通过标签名称找到该标签在Configuration对象中的set或者add方法,然后根据方法的第一个参数使用反射实例化一个对象
  2. 通过反射,将标签的属性添加到具体的对象中
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有下面几点值得我们学习:

  1. 解析过程全部使用反射,无须关心具体的标签
  2. Configuration需要提供set或add方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值