面试官问你MyBatis中有哪些设计模式,把这篇文章发给他


戳蓝字“CSDN云计算”关注我们哦!

640?wx_fmt=jpeg


作者 | 疯狂的蚂蚁

来源 | https://dwz.cn/KFgol1De


之前总结过一篇Spring中用到了哪些设计模式:《面试官:“谈谈Spring中都用到了那些设计模式?”》,昨晚看到了一篇很不错的一篇介绍MyBatis中都用到了那些设计模式的文章,今天分享给各位。

虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式。

Mybatis至少遇到了以下的设计模式的使用:

  1. Builder模式 :

    例如 SqlSessionFactoryBuilderXMLConfigBuilderXMLMapperBuilderXMLStatementBuilderCacheBuilder

  2. 工厂模式 :

    例如SqlSessionFactoryObjectFactoryMapperProxyFactory

  3. 单例模式 :例如ErrorContextLogFactory

  4. 代理模式 :Mybatis实现的核心,比如MapperProxyConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;

  5. 组合模式 :例如SqlNode和各个子类ChooseSqlNode等;

  6. 模板方法模式 : 例如BaseExecutorSimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler

  7. 适配器模式 : 例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;

  8. 装饰者模式 : 例如cache包中的cache.decorators子包中等各个装饰者的实现;

  9. 迭代器模式 : 例如迭代器模式PropertyTokenizer

接下来挨个模式进行解读,先介绍模式自身的知识,然后解读在Mybatis中怎样应用了该模式。

1、Builder 模式

Builder模式的定义是“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。”,它属于创建类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式和Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加复杂的对象的构建,甚至只会构建产品的一个部分。《effective-java》中第2条也提到:遇到多个构造器参数时,考虑用构建者(Builder)模式


640?wx_fmt=png
Builder模式


在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xml和所有的*Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。

其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取*.Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。

在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。

对于builder的具体类,方法都大都用build*开头,比如SqlSessionFactoryBuilder为例,它包含以下方法:


640?wx_fmt=png
SqlSessionFactoryBuilder


即根据不同的输入参数来构建SqlSessionFactory这个工厂对象。

2、工厂模式

在Mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。


640?wx_fmt=jpeg
简单工厂模式


SqlSession可以认为是一个Mybatis工作的核心的接口,通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似于连接MySQL的Connection对象。


640?wx_fmt=png
SqlSessionFactory


可以看到,该Factory的openSession()方法重载了很多个,分别支持autoCommitExecutorTransaction 等参数的输入,来构建核心的SqlSession对象。

DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品:

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,            boolean autoCommit) {        Transaction tx = null;        try {            final Environment environment = configuration.getEnvironment();            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);            final Executor executor = configuration.newExecutor(tx, execType);            return new DefaultSqlSession(configuration, executor, autoCommit);        } catch (Exception e) {            closeTransaction(tx); // may have fetched a connection so lets call                                    // close()            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);        } finally {            ErrorContext.instance().reset();        }    }
        Transaction tx = null;
        try {
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            final Executor executor = configuration.newExecutor(tx, execType);
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx); // may have fetched a connection so lets call
                                    // close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

这是一个openSession调用的底层方法,该方法先从configuration读取对应的环境配置,然后初始化TransactionFactory获得一个Transaction对象,然后通过Transaction获取一个Executor对象,最后通过configuration、Executor、是否autoCommit三个参数构建了SqlSession

在这里其实也可以看到端倪,SqlSession的执行,其实是委托给对应的Executor来进行的。

而对于LogFactory,它的实现代码:

public final class LogFactory {    private static Constructor<? extends Log> logConstructor;    private LogFactory() {        // disable construction    }    public static Log getLog(Class<?> aClass) {        return getLog(aClass.getName());    }final class LogFactory {
    private static Constructor<? extends Log> logConstructor;

    private LogFactory() {
        // disable construction
    }

    public static Log getLog(Class<?> aClass) {
        return getLog(aClass.getName());
    }

这里有个特别的地方,是Log变量的的类型是Constructor<? extendsLog>,也就是说该工厂生产的不只是一个产品,而是具有Log公共接口的一系列产品,比如Log4jImplSlf4jImpl等很多具体的Log。

3、单例模式

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。


640?wx_fmt=jpeg
单例模式


在Mybatis中有两个地方用到单例模式,ErrorContextLogFactory,其中ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息,而LogFactory则是提供给整个Mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。

ErrorContext的单例实现代码:

public class ErrorContext {    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();    private ErrorContext() {    }    public static ErrorContext instance() {        ErrorContext context = LOCAL.get();        if (context == null) {            context = new ErrorContext();            LOCAL.set(context);        }        return context;    }class ErrorContext {

    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

    private ErrorContext() {
    }

    public static ErrorContext instance() {
        ErrorContext context = LOCAL.get();
        if (context == null) {
            context = new ErrorContext();
            LOCAL.set(context);
        }
        return context;
    }

构造函数是private修饰,具有一个static的局部instance变量和一个获取instance变量的方法,在获取实例的方法中,先判断是否为空如果是的话就先创建,然后返回构造好的对象。

只是这里有个有趣的地方是,LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext

4、代理模式

代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper.java接口,不需要实现,由Mybatis后台帮我们完成具体SQL的执行。

代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。

代理模式包含如下角色:


640?wx_fmt=jpeg
代理模式


这里有两个步骤,第一个是提前创建一个Proxy,第二个是使用的时候会自动请求Proxy,然后由Proxy来执行具体事务;

当我们使用ConfigurationgetMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又会调用mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:

/** * @author Lasse Voss */public class MapperProxyFactory<T> {    private final Class<T> mapperInterface;    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();    public MapperProxyFactory(Class<T> mapperInterface) {        this.mapperInterface = mapperInterface;    }    public Class<T> getMapperInterface() {        return mapperInterface;    }    public Map<Method, MapperMethod> getMethodCache() {        return methodCache;    }    @SuppressWarnings("unchecked")    protected T newInstance(MapperProxy<T> mapperProxy) {        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },                mapperProxy);    }    public T newInstance(SqlSession sqlSession) {        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);        return newInstance(mapperProxy);    }}
public class MapperProxyFactory<T{

    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },
                mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

}

在这里,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用T newInstance(MapperProxy<T> mapperProxy)生成代理对象然后返回。

而查看MapperProxy的代码,可以看到如下内容:

public class MapperProxy<T> implements InvocationHandler, Serializable {    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        try {            if (Object.class.equals(method.getDeclaringClass())) {                return method.invoke(this, args);            } else if (isDefaultMethod(method)) {                return invokeDefaultMethod(proxy, method, args);            }        } catch (Throwable t) {            throw ExceptionUtil.unwrapThrowable(t);        }        final MapperMethod mapperMethod = cachedMapperMethod(method);        return mapperMethod.execute(sqlSession, args);    }class MapperProxy<Timplements InvocationHandlerSerializable {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }

非常典型的,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。

通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给MapperProxy.invoke方法,而该方法则会调用后续的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的执行和返回。

5、组合模式

组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。

组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关系。同时它也模糊了简单元素(叶子对象)和复杂元素(容器对象)的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客户程序能够与复杂元素的内部结构解耦。

在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。


640?wx_fmt=png
组合模式


Mybatis支持动态SQL的强大功能,比如下面的这个SQL:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">    UPDATE users    <trim prefix="SET" prefixOverrides=",">        <if test="name != null and name != ''">            name = #{name}        </if>        <if test="age != null and age != ''">            , age = #{age}        </if>        <if test="birthday != null and birthday != ''">            , birthday = #{birthday}        </if>    </trim>    where id = ${id}</update>
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

在这里面使用到了trim、if等动态元素,可以根据条件来生成不同情况下的SQL;

DynamicSqlSource.getBoundSql方法里,调用了rootSqlNode.apply(context)方法,apply方法是所有的动态节点都实现的接口:

public interface SqlNode {    boolean apply(DynamicContext context);}interface SqlNode {
    boolean apply(DynamicContext context);
}

对于实现该SqlSource接口的所有节点,就是整个组合模式树的各个节点:


640?wx_fmt=png
SqlNode


组合模式的简单之处在于,所有的子节点都是同一类节点,可以递归的向下执行,比如对于TextSqlNode,因为它是最底层的叶子节点,所以直接将对应的内容append到SQL语句中:

    @Override    public boolean apply(DynamicContext context) {        GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));        context.appendSql(parser.parse(text));        return true;    }
    public boolean apply(DynamicContext context) {
        GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
        context.appendSql(parser.parse(text));
        return true;
    }

但是对于IfSqlNode,就需要先做判断,如果判断通过,仍然会调用子元素的SqlNode,即contents.apply方法,实现递归的解析。

@Overridepublic boolean apply(DynamicContext context) {    if (evaluator.evaluateBoolean(test, context.getBindings())) {        contents.apply(context);        return true;    }    return false;} 
public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
        contents.apply(context);
        return true;
    }
    return false;

6、模板方法模式

模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。

模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。

模板类定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。


640?wx_fmt=png
模板方法模式


在Mybatis中,sqlSession的SQL执行,都是委托给Executor实现的,Executor包含以下结构:


640?wx_fmt=png
Executor接口


其中的BaseExecutor就采用了模板方法模式,它实现了大部分的SQL执行逻辑,然后把以下几个方法交给子类定制化完成:

    @Override    public boolean apply(DynamicContext context) {        if (evaluator.evaluateBoolean(test, context.getBindings())) {            contents.apply(context);            return true;        }        return false;    }
    public boolean apply(DynamicContext context) {
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);
            return true;
        }
        return false;
    }

该模板方法类有几个子类的具体实现,使用了不同的策略:

比如在SimpleExecutor中这样实现update方法:

    @Override    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {        Statement stmt = null;        try {            Configuration configuration = ms.getConfiguration();            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,                    null);            stmt = prepareStatement(handler, ms.getStatementLog());            return handler.update(stmt);        } finally {            closeStatement(stmt);        }    }
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,
                    null);
            stmt = prepareStatement(handler, ms.getStatementLog());
            return handler.update(stmt);
        } finally {
            closeStatement(stmt);
        }
    }

7、适配器模式

适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。


640?wx_fmt=jpeg
适配器模式


在Mybatsi的logging包中,有一个Log接口:

/** * @author Clinton Begin */public interface Log {    boolean isDebugEnabled();    boolean isTraceEnabled();    void error(String s, Throwable e);    void error(String s);    void debug(String s);    void trace(String s);    void warn(String s);}
public interface Log {

    boolean isDebugEnabled();

    boolean isTraceEnabled();

    void error(String s, Throwable e);

    void error(String s);

    void debug(String s);

    void trace(String s);

    void warn(String s);

}

该接口定义了Mybatis直接使用的日志方法,而Log接口具体由谁来实现呢?Mybatis提供了多种日志框架的实现,这些实现都匹配这个Log接口所定义的接口方法,最终实现了所有外部日志框架到Mybatis日志包的适配:


640?wx_fmt=png
Log


比如对于Log4jImpl的实现来说,该实现持有了org.apache.log4j.Logger的实例,然后所有的日志方法,均委托该实例来实现。

public class Log4jImpl implements Log {    private static final String FQCN = Log4jImpl.class.getName();    private Logger log;    public Log4jImpl(String clazz) {        log = Logger.getLogger(clazz);    }    @Override    public boolean isDebugEnabled() {        return log.isDebugEnabled();    }    @Override    public boolean isTraceEnabled() {        return log.isTraceEnabled();    }    @Override    public void error(String s, Throwable e) {        log.log(FQCN, Level.ERROR, s, e);    }    @Override    public void error(String s) {        log.log(FQCN, Level.ERROR, s, null);    }    @Override    public void debug(String s) {        log.log(FQCN, Level.DEBUG, s, null);    }    @Override    public void trace(String s) {        log.log(FQCN, Level.TRACE, s, null);    }    @Override    public void warn(String s) {        log.log(FQCN, Level.WARN, s, null);    }}class Log4jImpl implements Log {

    private static final String FQCN = Log4jImpl.class.getName();

    private Logger log;

    public Log4jImpl(String clazz) {
        log = Logger.getLogger(clazz);
    }

    @Override
    public boolean isDebugEnabled() {
        return log.isDebugEnabled();
    }

    @Override
    public boolean isTraceEnabled() {
        return log.isTraceEnabled();
    }

    @Override
    public void error(String s, Throwable e) {
        log.log(FQCN, Level.ERROR, s, e);
    }

    @Override
    public void error(String s) {
        log.log(FQCN, Level.ERROR, s, null);
    }

    @Override
    public void debug(String s) {
        log.log(FQCN, Level.DEBUG, s, null);
    }

    @Override
    public void trace(String s) {
        log.log(FQCN, Level.TRACE, s, null);
    }

    @Override
    public void warn(String s) {
        log.log(FQCN, Level.WARN, s, null);
    }

}

8、装饰者模式

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。


640?wx_fmt=jpeg
装饰者模式


在mybatis中,缓存的功能由根接口Cache(org.apache.ibatis.cache.Cache)定义。整个体系采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久缓存实现,然后通过一系列的装饰器来对PerpetualCache永久缓存进行缓存策略等方便的控制。如下图:


640?wx_fmt=jpeg
Cache


用于装饰PerpetualCache的标准装饰器共有8个(全部在org.apache.ibatis.cache.decorators包中):

  1. FifoCache:先进先出算法,缓存回收策略

  2. LoggingCache:输出缓存命中的日志信息

  3. LruCache:最近最少使用算法,缓存回收策略

  4. ScheduledCache:调度缓存,负责定时清空缓存

  5. SerializedCache:缓存序列化和反序列化存储

  6. SoftCache:基于软引用实现的缓存管理策略

  7. SynchronizedCache:同步的缓存装饰器,用于防止多线程并发访问

  8. WeakCache:基于弱引用实现的缓存管理策略

另外,还有一个特殊的装饰器TransactionalCache:事务性的缓存

正如大多数持久层框架一样,mybatis缓存同样分为一级缓存和二级缓存

二级缓存对象的默认类型为PerpetualCache,如果配置的缓存是默认类型,则mybatis会根据配置自动追加一系列装饰器。

Cache对象之间的引用顺序为:

SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

9、迭代器模式

迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。


640?wx_fmt=gif
迭代器模式


Java的Iterator就是迭代器模式的接口,只要实现了该接口,就相当于应用了迭代器模式:


640?wx_fmt=png
Iterator


比如Mybatis的PropertyTokenizer是property包中的重量级类,该类会被reflection包中其他的类频繁的引用到。这个类实现了Iterator接口,在使用时经常被用到的是Iterator接口中的hasNext这个函数。

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {    private String name;    private String indexedName;    private String index;    private String children;    public PropertyTokenizer(String fullname) {        int delim = fullname.indexOf('.');        if (delim > -1) {            name = fullname.substring(0, delim);            children = fullname.substring(delim + 1);        } else {            name = fullname;            children = null;        }        indexedName = name;        delim = name.indexOf('[');        if (delim > -1) {            index = name.substring(delim + 1, name.length() - 1);            name = name.substring(0, delim);        }    }    public String getName() {        return name;    }    public String getIndex() {        return index;    }    public String getIndexedName() {        return indexedName;    }    public String getChildren() {        return children;    }    @Override    public boolean hasNext() {        return children != null;    }    @Override    public PropertyTokenizer next() {        return new PropertyTokenizer(children);    }    @Override    public void remove() {        throw new UnsupportedOperationException(                "Remove is not supported, as it has no meaning in the context of properties.");    }}class PropertyTokenizer implements Iterator<PropertyTokenizer{
    private String name;
    private String indexedName;
    private String index;
    private String children;

    public PropertyTokenizer(String fullname) {
        int delim = fullname.indexOf('.');
        if (delim > -1) {
            name = fullname.substring(0, delim);
            children = fullname.substring(delim + 1);
        } else {
            name = fullname;
            children = null;
        }
        indexedName = name;
        delim = name.indexOf('[');
        if (delim > -1) {
            index = name.substring(delim + 1, name.length() - 1);
            name = name.substring(0, delim);
        }
    }

    public String getName() {
        return name;
    }

    public String getIndex() {
        return index;
    }

    public String getIndexedName() {
        return indexedName;
    }

    public String getChildren() {
        return children;
    }

    @Override
    public boolean hasNext() {
        return children != null;
    }

    @Override
    public PropertyTokenizer next() {
        return new PropertyTokenizer(children);
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException(
                "Remove is not supported, as it has no meaning in the context of properties.");
    }
}

可以看到,这个类传入一个字符串到构造函数,然后提供了iterator方法对解析后的子串进行遍历,是一个很常用的方法类。

参考资料

•图说设计模式:http://design-patterns.readthedocs.io/zh_CN/latest/index.html•深入浅出Mybatis系列(十)—SQL执行流程分析(源码篇):http://www.cnblogs.com/dongying/p/4142476.html•设计模式读书笔记—–组合模式 http://www.cnblogs.com/chenssy/p/3299719.html•Mybatis3.3.x技术内幕(四):五鼠闹东京之执行器Executor设计原本 http://blog.csdn.net/wagcy/article/details/32963235•mybatis缓存机制详解(一)——Cache https://my.oschina.net/lixin91/blog/620068

640?wx_fmt=png


640?wx_fmt=jpeg

福利

扫描添加小编微信,备注“姓名+公司职位”,加入【云计算学习交流群】,和志同道合的朋友们共同打卡学习!


640?wx_fmt=jpeg


推荐阅读:

  • 听说,私有云也出新一代了?

  • 搞不懂SDN?那是因为你没看这个小故事…

  • 华为最强自研 NPU 问世,麒麟 810 “抛弃”寒武纪

  • 北邮通信博士万字长文,带你秒懂 4G/5G 区别!

  • LinkedIn最新报告: 区块链成职位需求增长最快领域, 这些地区对区块链人才渴求度最高……

  • 中文NLP的分词真有必要吗?李纪为团队四项任务评测一探究竟 | ACL 2019

  • 6月技术福利限时免费领


640?wx_fmt=png真香,朕在看了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/523318.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

腾讯云首次公开边缘计算网络开源平台,拥抱5G与万物互联

6月25日&#xff0c;由Cloud Native Computing Foundation (CNCF) 主办的云原生技术大会在上海举办&#xff0c;腾讯云对外展示自身在边缘计算领域的最新进展&#xff0c;首次公开腾讯智能边缘计算网络平台TSEC&#xff08;Tencent Smart Edge Connector&#xff09;&#xff0…

解决SecureCRT与SecureFX中文乱码问题

文章目录一、SecureCRT中文乱码问题解决方法&#xff1a;二、SecureFX中文乱码问题解决方法&#xff1a;2.1. 找到SecureFX配置文件夹2.2. 在配置文件夹下的Sessions子目录中&#xff0c;找到SecureCRT连接对应的Session文件&#xff08;.ini扩展名&#xff09;&#xff0c;双击…

OpenStack精华问答 | OpenStack服务介绍

关于OpenStack的争议,从未停止&#xff0c;每每关于它的消息&#xff0c;都会一石激起千层浪。今天就让我们看看关于OpenStack的问答吧。1Q : OpenStack服务介绍A : MySQL为各个服务器提供数据存储RabbitMq:为各个服务之间提供通信认证和服务注册Keystone&#xff1a;为各个服务…

实战04_redis-cluster集群搭建

接上一篇:实战_03_Redis基础命令https://blog.csdn.net/weixin_40816738/article/details/99213524 #安装gcc yum install gcc-c #使用yum命令安装 ruby &#xff08;我们需要使用ruby脚本来实现集群搭建&#xff09; yum install ruby yum install rubygems #将redis源码包上…

linux网站渗透工具包,ubuntu下安装 kali linux 渗透工具包

相信用过linux系统的盆友都听说过kali linux 它是一个非常好的用于渗透测试的Linux发行版。但是如何在ubuntu下使用kali linux 的渗透工具嘞&#xff01;LionSec开发出了一个python工具&#xff0c;叫做Katoolin&#xff0c;它可以让你在其他Linux发行版上使用Kali的全部工具。…

OCP China Day“登陆”,最新技术、方案吸睛!

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 刘晶晶众所周知&#xff0c;OCP在2011年由Facebook发起成立&#xff0c;核心会员超过200家&#xff0c;其中更是包括Google、微软、Intel、IBM等企业&#xff0c;超过7000家企业参与了该社区的活动&#xff1b;2018年OCP非董…

SpringBoot集成Shiro前后端分离使用redis做缓存

文章目录一 、shiro介绍1、基础介绍2、基本功能点3、基本流程图二、 常用的权限管理表关系2.1. 表组成2.2. 表结构三、实战案例3.1. 案例介绍3.2. 依赖3.3. Shiro全局配置3.4. 自定义ShiroRealm3.5. ShiroUtils3.6. 自定义SessionManager3.7. 登录/出主方法3.8. 测试主方法四、…

边缘计算容器化是否有必要?

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | Steve来源 | 边缘计算中文社区简要由于容器有轻量级、安全性、秒级启动等优秀的特性&#xff0c;容器天然的轻量化和可移植性&#xff0c;非常适合边缘计算的场景&#xff0c;这一点边缘计算的厂家和开发者们都心知肚明。而且…

ipcp协议 Linux,Linux命令Man解释:PPPD(8) :点对点daemon协议

名称pppd - 点对点协定隐形程式(Point to Point Protocol daemon)语法pppd [ 选项 ] [ 终端设备名称(tty_name) ] [ 速率 ]描述这个点对点协定 (PPP) 提供一种在点对点串列线路上传输资料流(datagrams) 的方法。PPP 是由三个部份所组成的&#xff1a;一个在串列线路上封装(enca…

linux查看每个文件夹占空间大小

文章目录进入所在目录&#xff0c;执行以下命令&#xff1a;例&#xff1a;查看/app目录下面&#xff0c;每个目录的空间磁盘占比情况cd /app du -sh *

KubeCon 、 CloudNativeCon、Open Source Summit 2019三会交融,看点不断!

2019年6月24日&#xff0c;由CNCF和Linux基金会共同举办的KubeCon CloudNativeCon Open Source Summit 2019大会在上海世博中心盛大召开。来自全球各地的开源及云原生社区的采用者和技术专家齐聚于此&#xff0c;与参会者进一步探讨了云原生的教育及发展问题。 第一天大会以同…

c语言20152016真题及答案,2016年计算机二级《C语言》基础练习题及答案(15)

11[单选题]有以下程序程序运行后的输出结果是A.3B.9C.OD.-12参考答案&#xff1a;D参考解析&#xff1a;本题考查目的是运算符的结合性和优先级。首先计算a*a&#xff0c;结果为9&#xff0c;然后执行aa-9&#xff0c;即3-9&#xff0c;结果为-6&#xff0c;然后执行a(-6)(-6)&…

SpringBoot入门到精通_第2篇 _1分钟实战需求项目

接上一篇&#xff1a;SpringBoot入门到精通_第1篇 _核心概念 https://blog.csdn.net/weixin_40816738/article/details/94916051 文章目录一、实战SpringBoot项目1. 使用Spring Initializr快速创建Spring Boot应用2. 在线版本(任选其一即可)3. SpringBoot整合Spring MVC4. 创建…

云数据库精华问答 | 云数据库与其他数据库的关系

戳蓝字“CSDN云计算”关注我们哦&#xff01;云数据库是部署和虚拟化在云计算环境中的数据库。云数据库是在云计算的大背景下发展起来的一种新兴的共享基础架构的方法&#xff0c;它极大地增强了数据库的存储能力,今天我们就一起来看看云数据库的精华问答&#xff01;1Q&#x…

企业如何快速响应用户需求 且看云徙“数据+业务”双中台化简为繁

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 |刘丹“自然界生存下来的&#xff0c;既不是四肢最强壮的&#xff0c;也不是头脑最聪明的&#xff0c;而是最有能力适应变化的物种。”这句至理名言放在商业文明的今天依然得以适用&#xff0c;有能力适应技术快速发展、业务需…

微服务架构之「 下一代微服务 Service Mesh 」

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 奎哥来源 | 不止思考Service Mesh 被大家称为下一代的微服务&#xff0c;是微服务领域的一颗新星&#xff0c;被大家讨论的非常多。我在大家的讨论中&#xff0c;还看到有人说 “目前的微服务架构我都没学会呢&#xff0c;现…

C语言编程中线性表的顺序表示,数据结构C语言实现----线性表的顺序表示和实现...

线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素。在高级程序设计语言中&#xff0c;通常都用数组来描述数据结构中的顺序存储结构。同时&#xff0c;由于线性表的长度可变&#xff0c;且所需最大存储空间随问题不同而不同&#xff0c;在C语言中可用动…

SpringBoot入门到精通_第5篇 _SpringBoot Actuator监控

接上一篇&#xff1a;SpringBoot入门到精通_第4篇 _开发三板斧 https://blog.csdn.net/weixin_40816738/article/details/101097161 文章目录一、 SpringBoot Actuator 概念1. 是什么&#xff1f;2. 如何整合SpringBoot Actuator&#xff1f;二、 SpringBoot Actuator 实战2.1.…

反转!美光、英特尔等多家美企恢复对华为供货;首例云服务器存储侵权案改判,阿里云不担责;英国政府拟严格审查Libra……...

戳蓝字“CSDN云计算”关注我们哦&#xff01;嗨&#xff0c;大家好&#xff0c;重磅君带来的【云重磅】特别栏目&#xff0c;如期而至&#xff0c;每周五第一时间为大家带来重磅新闻。把握技术风向标&#xff0c;了解行业应用与实践&#xff0c;就交给我重磅君吧&#xff01;重…

从达标到卓越 —— API 设计之道

摘要&#xff1a; 新技术层出不穷&#xff0c;长江后浪推前浪。在浪潮褪去后&#xff0c;能留下来的&#xff0c;是一些经典的设计思想。 在前端界&#xff0c;以前有远近闻名的 jQuery&#xff0c;近来有声名鹊起的 Vue.js。这两者叫好又叫座的原因固然有很多&#xff0c;但是…