30分钟教你写一个mybatis框架

XMLConfigBuilder类解析mybatis配置文件,创建一个Configuration对象,该对象是mybatis的核心配置类。对配置文件中的<environments>标签解析,<environments>包含多个<environment>每个包含<dataSource>根据<environments>标签的default属性选择一个environment,读取对应的<dataSource>配置信息。根据<dataSource>的type属性,确定要使用的连接池。使用<dataSource>中配置的数据库信息进而创建数据源DataSource,将DataSource设置到Configuration中。

<configuration>
    <!-- mybatis 数据源环境配置 -->
    <environments default="dev">
        <environment id="dev">
            <!-- 配置数据源信息 -->
            <dataSource type="DBCP">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/petstore?serverTimezone=GMT%2B8&amp;characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 映射文件加载 -->
    <mappers>
        <!-- resource指定映射文件的类路径 -->
        <mapper resource="mapper/manageUser.xml"></mapper>
        <!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
        <!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
        <!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
        <!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
    </mappers>
</configuration>

package builder;

import mapping.Configuration;
import org.apache.commons.dbcp.BasicDataSource;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-08 10:42
 * @notify 解析mybatisConfig的类,当前解析 environments  mappers
 * 创建Configuration 设置DataSource
 *  mappers 则获取输入流,交给XMLMapperBuilder解析每一个mapper文件
 * @version 1.0
 */
public class XMLConfigBuilder {

    private Configuration configuration = new Configuration();

    public Configuration parse(Element rootElement) throws Exception {
        //解析<environments>
        Element environments = rootElement.element("environments");
        parseEnvironments(environments);
        //解析<mappers>
        Element mappers = rootElement.element("mappers");
        parseMappers(mappers);
        return configuration;
    }

    //解析<environments>
    private void parseEnvironments(Element environments) {
        //查询environments default="dev"
        String aDefault = environments.attributeValue("default");
        //获取全部的 environment
        List<Element> environment = environments.elements("environment");
        //循环所有的 environment
        for (Element env : environment) {
            //如果当前 environment 的id和默认的id相同则继续向下解析
            if (env.attributeValue("id").equals(aDefault)) {
                parseEnvironment(env);
            }
        }
    }

    //解析environment
    private void parseEnvironment(Element environment) {
        //解析<dataSource type="DBCP">
        Element dataSource = environment.element("dataSource");
        parseDataSource(dataSource);
    }

    //解析dataSource
    private void parseDataSource(Element dataSource) {
        //获取连接池类型
        String type = dataSource.attributeValue("type");
        //设置 <dataSource type="DBCP"> 连接池
        if (type.equals("DBCP")) {
            //创建 DBCP连接池
            BasicDataSource dataSource1 = new BasicDataSource();
            //创建配置类
            Properties properties = new Properties();
            //获取全部的property
            List<Element> propertys = dataSource.elements("property");
            //循环拿到<property name="driver" value="com.mysql.jdbc.Driver"/>
            for (Element prop : propertys) {
                //获取标签name属性值
                String name = prop.attributeValue("name");
                //获取标签value属性值
                String value = prop.attributeValue("value");
                //设置到配置类
                properties.put(name, value);
            }
            //设置连接池属性
            dataSource1.setDriverClassName(properties.get("driver").toString());
            dataSource1.setUrl(properties.get("url").toString());
            dataSource1.setUsername(properties.get("username").toString());
            dataSource1.setPassword(properties.get("password").toString());
            //给Configuration设置数据源信息
            configuration.setDataSource(dataSource1);
        }
    }

    //解析<mappers>
    private void parseMappers(Element mappers) throws Exception {
        //拿到所有的<mapper resource="mapper/UserMapper.xml"></mapper>
        List<Element> mapperElements = mappers.elements("mapper");
        //遍历解析每一个 mapper.xml
        for (Element mapperElement : mapperElements) {
            parseMapper(mapperElement);
        }
    }

    //解析每一个mapper标签
    private void parseMapper(Element mapperElement) throws Exception {
        //TODO 此处还有url等方式
        String resource = mapperElement.attributeValue("resource");
        //根据文件名获取输入流
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resource);
        //dom4j解析
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        //获取跟标签
        Element rootElement = document.getRootElement();
        XMLMapperBuilder mapperBuilder = new XMLMapperBuilder(configuration);
        mapperBuilder.parse(rootElement);
    }

}

循环<mappers>标签获取多个<mapper>循环解析mapper配置文件,使用XMLMapperBuilder类获取每个mapper配置文件的namespace,在解析不同的sql标签(<insert><select>)。

<mapper namespace="manageUser">
    <!-- select标签,封装了SQL语句信息、入参类型、结果映射类型 -->
    <select id="getManageUserById"
            parameterType="pojo.ManageUser"
            resultType="pojo.ManageUser" statementType="prepared">
        SELECT * FROM manage_user WHERE id = #{id}
    </select>

    <insert  id="insertManage"
             parameterType="pojo.ManageUser"
             statementType="prepared">
        insert into manage_user values(#{id},#{username},#{password},#{create_date});
    </insert>

    <select id="getManageUserByUserName"
            parameterType="pojo.ManageUser"
            resultType="pojo.ManageUser" statementType="prepared">
        SELECT * FROM manage_user WHERE id = #{id}
        <if test="username!=null and username!=‘‘">
            and username = ${username}
        </if>
    </select>
</mapper>

package builder;

import mapping.Configuration;
import org.dom4j.Element;

import java.util.List;

/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-08 11:13
 * @notify 解析mapper文件获取namespace,读取到<insert>和<select>标签,交给XMLStatementBuilder
 * @version 1.0
 */
public class XMLMapperBuilder {

    private String namespace = "";

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    //解析每一个mapper文件
    public void parse(Element rootElement) throws Exception {
        //查询namespace
        namespace = rootElement.attributeValue("namespace");
        //获取select标签
        List<Element> selectElements = rootElement.elements("select");
        parse(selectElements, "select");

        List<Element> insertElements = rootElement.elements("insert");
        parse(insertElements, "insert");

    }

    public void parse(List<Element> selectElements, String sqlType) throws Exception {
        for (Element selectElement : selectElements) {
            XMLStatementBuilder xmlStatementBuilder = new XMLStatementBuilder(configuration);
            xmlStatementBuilder.parseStatementElement(selectElement, namespace, sqlType);
        }
    }

}

XMLStatementBuilder类解析具体的sql标签。每一个sql标签都是一个MappedStatement对象,而每一个mapper文件中有N个sql标签,一个项目又有M个mapper文件。所以一个Configuration中有一个map,key是statementid,(由mapper文件的namespace和sql标签的id组成)value是MappedStatement。一个MappedStatement对象由statementid,入参类型,返回值类型,sqlType属于<select>还是<insert>等等 statementType的值分别对应:statement不进行预编译,prepared预编译,callable执行存储过程。最后还有一个专门用来存储sql语句的对象SqlSource,SqlSource是一个接口。

package builder;

import mapping.Configuration;
import mapping.MappedStatement;
import org.dom4j.Element;
import sqlnode.impl.MixedSqlNode;
import sqlsource.SqlSource;
import sqlsource.impl.DynamicSqlSource;
import sqlsource.impl.RawSqlSource;
import utils.ResolveType;

/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-08 11:24
 * @notify 解析<insert><select>标签,读取入参,返回值,id等信息,标签内容交给XMLScriptBuilder
 * @version 1.0
 */
public class XMLStatementBuilder {
    private Configuration configuration;

    public XMLStatementBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    //解析标签
    public void parseStatementElement(Element selectElement, String namespace, String sqlType) throws Exception {
        //读取id
        String statementId = selectElement.attributeValue("id");
        //如果id不存在则返回
        if (statementId == null || selectElement.equals("")) {
            return;
        }
        //拼接namespace
        statementId = namespace + "." + statementId;
        //查询 parameterType 属性
        String parameterType = selectElement.attributeValue("parameterType");
        //通过类名获取Class
        Class<?> parameterClass = ResolveType.resolveType(parameterType);
        //查询 resultType 属性
        String resultType = selectElement.attributeValue("resultType");
        Class<?> resultClass = null;
        if (resultType != null && !resultType.equals("")) {
            //通过类名获取Class
            resultClass = ResolveType.resolveType(resultType);
        }

        //获取statementType属性
        String statementType = selectElement.attributeValue("statementType");
        //设置默认的statementType属性
        statementType = statementType == null || statementType == "" ? "prepared" : statementType;
        // 解析SQL信息
        SqlSource sqlSource = createSqlSource(selectElement);

        // TODO 建议使用构建者模式去优化
        MappedStatement mappedStatement = new MappedStatement(statementId, parameterClass, resultClass, statementType,
                sqlSource, sqlType);
        //设置Configuration参数
        configuration.addMappedStatement(statementId, mappedStatement);

    }

    //获取sqlSource
    private SqlSource createSqlSource(Element selectElement) throws Exception {
        XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder();
        SqlSource sqlSource = xmlScriptBuilder.parseScriptNode(selectElement);
        return sqlSource;
    }

}

XMLScriptBuilder解析sql脚本,这一部分较为复杂。首先我们先明确,我们需要解析的内容是sql标签中的标签体,也就是sql脚本,需要将sql脚本组成一个SqlSource。

<select>
  select * from manage_user whereid = #{id}
  <if test="username!=null and username!=‘‘">
    and username = ${username}
  </if>
</select>

这样的sql脚本包含两个节点,一个是只包含普通的文本的sql节点,另一个则是if标签的sql节点。当然,真实的mybatis还包含<where>等节点。封装sql节点引入一个接口,SqlNode。每种节点最终都需要放到SqlSource中,我们可以在SqlSource中使用一个集合来存储,但是我们还有一个更好的选择,使用MixedSqlNode。现在MixedSqlNode中存储多个SqlNode根据不同的节点不同,我们将是文本节点和包含${}的节点封装成TextSqlNode,将只包含文本且只包含#{}的节点封装成StaticTextSqlNode。较为麻烦的是if标签,因为一个if标签里可能会包含带有#{}的文本内容,或者带有${}的文本内容,或许,if标签里还有if标签。这里我们必须要用到递归解析了。我们假设if标签中包含TextSqlNode和另一个if标签,此时我们就需要把两个标签放到if标签中,辛好我们有MixedSqlNode,于是if标签中的test表达式和MixedSqlNode组成了IfSqlNode,现在我们解析了全部的sql脚本将所有的SqlNode封装到MixedSqlNode,然后组装一个SqlSource。前边我们SqlSource是一个接口,现在我们将sql脚本中只包含StaticTextSqlNode节点的SqlSource封装成RawSqlSource,而包含TextSqlNode和IfSqlNode节点SqlSource封装成DynamicSqlSource

package builder;/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-08 11:30
 * @notify
 * @version 1.0
 */

import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.Text;
import sqlnode.SqlNode;
import sqlnode.impl.IfSqlNode;
import sqlnode.impl.MixedSqlNode;
import sqlnode.impl.StaticTextSqlNode;
import sqlnode.impl.TextSqlNode;
import sqlsource.SqlSource;
import sqlsource.impl.DynamicSqlSource;
import sqlsource.impl.RawSqlSource;

import java.util.ArrayList;
import java.util.List;

public class XMLScriptBuilder {

    private boolean isDynamic = false;

    //解析标签体
    public SqlSource parseScriptNode(Element selectElement) throws Exception {
        MixedSqlNode mixedSqlNode = parseDynamicTags(selectElement);
        SqlSource sqlSource;
        if (isDynamic) {//如果包含${}或者其他的子标签则为动态的
            sqlSource = new DynamicSqlSource(mixedSqlNode);
        } else {//全部的sqlNode都是文本,并且只包含#{}
            sqlSource = new RawSqlSource(mixedSqlNode);
        }
        return sqlSource;
    }

    private MixedSqlNode parseDynamicTags(Element selectElement) {
        //存储一个 <select> 中的所有sqlNode
        List<SqlNode> sqlNodes = new ArrayList<>();
        //查询总结点数量
        int nodeCount = selectElement.nodeCount();
        //遍历全部的sql节点
        for (int i = 0; i < nodeCount; i++) {
            //获取当前节点
            Node node = selectElement.node(i);
            //如果是纯文本的
            if (node instanceof Text) {
                //拿到文本节点
                String sqlText = node.getText().trim();
                if (!sqlText.equals("")) {
                    //如果包含 ${} 则创建 TextSqlNode 该节点只包含文本和${}
                    if (sqlText.indexOf("${") > -1) {
                        //如果包含${}或者其他的子标签则为动态的
                        isDynamic = true;
                        //将TextSqlNode添加到节点集合中
                        sqlNodes.add(new TextSqlNode(sqlText));
                    } else {
                        //将StaticTextSqlNode添加到节点集合中
                        sqlNodes.add(new StaticTextSqlNode(sqlText));
                    }
                }
            } else if (node instanceof Element) {
                //如果包含${}或者其他的子标签则为动态的
                isDynamic = true;
                //拿到节点名称
                String nodeName = node.getName();
                //如果是 if则表示是if标签
                if (nodeName.equals("if")) {
                    //将node转换成element
                    Element element = (Element) node;
                    //拿到if的条件
                    String test = element.attributeValue("test");
                    /*
                    此处递归调用,因为if标签中还有子节点
                    设sql为
                    select * from user
                    <if test=‘name!=null and name!=‘‘‘>
                        and name = #{name}
                    </if>
                    此时 select * from user 已经转换成了sqlNode
                    接下来的if标签,里边也包含子节点,所以递归,
                    第二次进入 (parseDynamicTags)
                    会创建一个StaticTextSqlNode,将该SqlNode添加到
                     List<SqlNode> sqlNodes = new ArrayList<>();
                     而这个集合最终会被封装成一个MixedSqlNode返回到第一次
                     调用(parseDynamicTags),所以此处使用
                     MixedSqlNode接收,并将该MixedSqlNode传递给IfSqlNode,然后
                     添加到sqlNodes中
                    */
                    MixedSqlNode mixedSqlNode = parseDynamicTags(element);
                    sqlNodes.add(new IfSqlNode(test, mixedSqlNode));
                }
            }
        }
        //返回节点集合包装类
        return new MixedSqlNode(sqlNodes);
    }
}

以上过程只是对sql解析的大概描述。现在我们缕清思路,Configuration包含的是mybatis的全局配置,其中包含DataSource和所有的mapper配置文件信息。形成了一个MappedStatement,MappedStatement包含的是sql标签的属性和内容,属性包含id,入参,出参,statement类型等,内容则是多个SqlNode形成的SqlSource。我们没有使用集合来存储SqlNode,而是在SqlSource内部持有一个MixedSqlNode此类是SqlNode接口的实现类,MixedSqlNode里有一个list集合存放各种类型的SqlNode,例如TextSqlNode,IfSqlNode等。

SqlSource接口,该接口中只有一个方法。getBoundSql(Object param);返回一个BoundSql对象。我们先不管BoundSql是干嘛的。

package sqlsource;
/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-04 14:41
 * @notify 获取sql语句
 * @version 1.0
 */

import mapping.BoundSql;

public interface SqlSource {
    /*
    传入参数,这个param就是sql的入参
    boundSQL 信息返回拼接的sql可能是${} 或者 #{} 如果是${}则可以直接获取sql
    如果是 #{} 还需要有方法获取 ?代表的属性。
     */
    BoundSql getBoundSql(Object param)throws Exception;
}

接着看SqlSource的实现类。

package sqlsource.impl;

import mapping.BoundSql;
import mapping.DynamicContext;
import sqlnode.SqlNode;
import sqlsource.SqlSource;
import sqlsource.SqlSourceParser;
import tokenparser.GenericTokenParser;
import tokenparser.ParameterMappingTokenHandler;

/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-04 14:52
 * @notify #{} 中的内容进行处理 RawSqlSource只包含 StaticTextSqlNode
 * @version 1.0
 */
public class RawSqlSource implements SqlSource {

    private SqlNode mixedSqlNode;

    public RawSqlSource(SqlNode mixedSqlNode) throws Exception {
        this.mixedSqlNode = mixedSqlNode;
    }

    @Override
    public BoundSql getBoundSql(Object param) throws Exception {
        //执行该sqlSource中的所有sqlNode
        DynamicContext dynamicContext = new DynamicContext(null);
        mixedSqlNode.apply(dynamicContext);
        //将 #{ } 替换成 ? 然后将内容存储到 ParameterMapping 中
        SqlSourceParser parser = new SqlSourceParser(dynamicContext);
        SqlSource sqlSource = parser.parse();
        return sqlSource.getBoundSql(param);
    }

}

调用RawSqlSource的getBoundSql(Object param);方法,创建了一个DynamicContext对象。这个对象有一个StringBuilder对象,和Object类型参数。

package mapping;

/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-04 18:02
 * @notify 每个SqlSource有多个SqlNode,而DynamicContext负责将多个sqlNode解析出来的sql信息拼接到一块
 * @version 1.0
 */
public class DynamicContext {
    //多个sqlNode调用自己的apply()都把解析好的sql放到这里。
    private StringBuilder sb = new StringBuilder();
    //sql入参
    private Object param;

    public String getSql() {
        return sb.toString();
    }

    public DynamicContext(Object param) {
        this.param = param;
    }

    public void appendSql(String sqlText) {
        sb.append(sqlText);
        sb.append(" ");
    }

    public Object getParam() {
        return param;
    }

}

接着我们调用SqlNode的apply(DynamicContext context)。其实当前的SqlNode其实就是MixedSqlNode

package sqlnode;

import mapping.DynamicContext;

/*
* @auther 顶风少年
* @mail [email protected]
* @date 2020-01-04 18:00
* @notify
* @version 1.0
*/
public interface SqlNode {
    void apply(DynamicContext context)throws Exception;
}

package sqlnode.impl;

/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-04 18:10
 * @notify 因为一个sqlSource有多个sqlNode 所以将所有的sqlNode封装成一个对象。
 * 集中的管理所有的sqlNode
 * @version 1.0
 */

import mapping.DynamicContext;
import sqlnode.SqlNode;

import java.util.List;

public class MixedSqlNode implements SqlNode {

    //封装SqlNode集合信息
    private List<SqlNode> sqlNodes;

    public MixedSqlNode(List<SqlNode> sqlNodes) {
        this.sqlNodes = sqlNodes;
    }

    /*
     * 对外提供对数据封装的操作
     * */
    @Override
    public void apply(DynamicContext context) throws Exception {
        //执行sqlSource中所有sqlNode的apply()不同的sqlNode有不同的解析方式。最终都会将自己解析的sql放到DynamicContext中
        for (SqlNode sqlNode : sqlNodes) {
            sqlNode.apply(context);
        }
    }
}

但我们调用MixedSqlNode的apply(DynamicContext context);其实就是循环调用每一个SqlNode自己的apply(DynamicContext context);前边我们说过,只包含StaticTextSqlNode节点的才能被封装成RawSqlSource而StaticTextSqlNode节点只包含文本内容且文本内容中只有#{}。所以apply方法

原文地址:https://www.cnblogs.com/zumengjie/p/12180702.html

时间: 2024-11-05 20:48:16

30分钟教你写一个mybatis框架的相关文章

从 0 开始手写一个 Mybatis 框架,三步搞定!

阅读本文大概需要 3 分钟. MyBatis框架的核心功能其实不难,无非就是动态代理和jdbc的操作,难的是写出来可扩展,高内聚,低耦合的规范的代码. 本文完成的Mybatis功能比较简单,代码还有许多需要改进的地方,大家可以结合Mybatis源码去动手完善. 1. Mybatis框架流程简介 在手写自己的Mybatis框架之前,我们先来了解一下Mybatis,它的源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,才能够更深入的理解源码(ref:Mybatis源码解读-设计模式总结

30分钟教你写出10分的淘宝标题

对于很多淘宝中小卖家来讲,对标题优化都存在着两个误区: 1.只要照抄人气宝贝(或者)销量最高的宝贝就可以了 这时候你往往会发现,别人的宝贝有展现,你的宝贝却没有,因为店铺基础不同,人气分不同,别人有排名,你却没有.并且这样做,还有一个非常不好的地方:你的标题都去抄别人的了,怎么能够知道你的产品是新的呢?这样在新品流量获得扶植方面,会存在一定的缺陷. 2.写标题没有方法,就把淘宝推荐的词组合到一起就可以了 不管是大类目还是小类目,不管是基础好的店铺还是基础差的店铺,都可以通过关键词的全店布局和关键

【干货】零基础30分钟让你拥有一个完整属于自己的短视频APP系统

目录 一.附言: 1 二.购买域名和购买服务器: 2 三.搭建服务器环境: 5 四.配置APP前端部分: 8 1.工具以及文件准备: 9 2.配置后端接口地址 11 3.配置APP启动图和启动图标 12 五.搭建APP后端部分 13 步骤,一步步输入并且回车. 13 1. 登录方才已经安装好的宝塔软件以及创建通信网站. 15 2. 导入数据库测试是否连通. 20 六.搭建APP后台管理端部分 24 七.打包APP下载并测试 31 一.附言: [干货]30分钟让你拥有一个完整属于自己的短视频APP

教你写Android ImageLoader框架之图片加载与加载策略

在教你写Android ImageLoader框架之初始配置与请求调度中,我们已经讲述了ImageLoader的请求配置与调度相关的设计与实现.今天我们就来深入了解图片的具体加载过程以及加载的策略(包括按顺序加载和逆序加载) ,在这其中我会分享我的一些设计决策,也欢迎大家给我提建议. 图片的加载 Loader与LoaderManager的实现 在上一篇文章教你写Android ImageLoader框架之初始配置与请求调度中,我们聊到了Loader与LoaderManager. ImageLoa

教你写Android网络框架之Http请求的分发与执行

前言 在<教你写Android网络框架>专栏的前两篇博客中,我们已经介绍了SimpleNet框架的基本结构,以及Request.Response.请求队列的实现,以及为什么要这么设计,这么设计的考虑是什么.前两篇博客中已经介绍了各个角色,今天我们就来剖析另外几个特别重要的角色,即NetworkExecutor.HttpStack以及ResponseDelivery,它们分别对应的功能是网络请求线程.Http执行器.Response分发,这三者是执行http请求和处理Response的核心. 我

教你写Android网络框架之Request、Response类与请求队列

转载请注明出处,本文来自[ Mr.Simple的博客 ]. 我正在参加博客之星,点击这里投我一票吧,谢谢~ 前言 在教你写Android网络框架之基本架构一文中我们已经介绍了SimpleNet网络框架的基本结构,今天我们就开始从代码的角度来开始切入该网络框架的实现,在剖析的同时我们会分析设计思路,以及为什么要这样做,这样做的好处是什么.这样我们不仅学到了如何实现网络框架,也会学到设计一个通用的框架应该有哪些考虑,这就扩展到框架设计的范畴,通过这个简单的实例希望能给新人一些帮助.当然这只是一家之言

30分钟用 Laravel 实现一个博客

介绍 Laravel 是一款 MVC架构. 目前最流行的 PHP框架. Laravel的优点在于: 丰富的composer类库支持, 优雅的代码, 未来的主流框架(目前市场占有率最高的框架) Laravel的缺点在于: 过于优雅(我们只需要编写极少的代码即可实现功能,意味着底层极其复杂的封装)导致程序的执行效率略低, 和thinkphp等国内主流框架相比,上手难度略高(因为它为我们集成了很多其他的功能,甚至你还需要学习nodeJS相关的知识). 本项目,是完全使用 Laravel框架 内的所提供

教你写Android ImageLoader框架之初始配置与请求调度

## 前言 在教你写Android ImageLoader框架之基本架构中我们对SimpleImageLoader框架进行了基本的介绍,今天我们就从源码的角度来剖析ImageLoader的设计与实现.   在我们使用ImageLoader前都会通过一个配置类来设置一些基本的东西,比如加载中的图片.加载失败的图片.缓存策略等等,SimpleImageLoader的设计也是如此.配置类这个比较简单,我们直接看源码吧. ImageLoaderConfig配置 /** * ImageLoader配置类,

教你写Android ImageLoader框架之图片缓存 (完结篇)

在教你写Android ImageLoader框架系列博文中,我们从基本架构到具体实现已经更新了大部分的内容.今天,我们来讲最后一个关键点,即图片的缓存.为了用户体验,通常情况下我们都会将已经下载的图片缓存起来,一般来说内存和本地都会有图片缓存.那既然是框架,必然需要有很好的定制性,这让我们又自然而然的想到了抽象.下面我们就一起来看看缓存的实现吧. 缓存接口 在教你写Android ImageLoader框架之图片加载与加载策略我们聊到了Loader,然后阐述了AbsLoader的基本逻辑,其中