java高薪训练营

01 第一阶段 开源框架源码剖析(完结)

01 持久层框架设计实现及 MyBatis 源码分析

03 任务一:自定义持久层框架

01 JDBC 回顾及问题分析

sql 语句经常改变,而数据库配置相对固定,所以将数据库配置和 sql 配置文件分离

02 自定义持久层框架思路分析


03 IPersistence_Test 测试类编写

<!-- sqlMapConfig.xml -->
<configuration>
    <!--数据库配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3307/mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>
    <!--Mapper文件的全路径-->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>
<!-- UserMapper.xml -->
<!--如果都有同样id的select, 就需要通过mapper的namespace区分-->
<mapper namespace="user">
    <!--不能通过标签名区分,所以增加id属性来区分-->
    <!--sql唯一标识: namespace + id => statementId-->
    <!--通过resultType来指定结果类型-->
    <select id="selectList" resultType="org.malred.pojo.User">
        select * from tb_user
    </select>

    <!--将多个参数封装到对象传入-->
    <!--通过#{参数名}来明确要填充类的哪个参数-->
    <select id="selectOne" resultType="org.malred.pojo.User" paramterType="org.malred.pojo.User">
        select * from tb_user where id = #{id} and username = #{username}
    </select>
</mapper>
package org.malred.pojo;

public class User {


    private Long id;
    private String username;
    private String password;
    private String gender;
    private String addr;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }


    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }


    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }


    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }
}

04 Resources 类定义

package org.malred.io;

import java.io.InputStream;

public class Resources {
    // 根据配置文件的路径,将配置文件加载成字节流,存入内存
    public static InputStream getResourceAsStream(String path) {
        InputStream resourceAsStream =
                Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

打包 IPersistence 工程 (mvn install)

引入

<!-- IPersistence_test>pom.xml -->
<dependencies>
    <!--该工程需要先install才能用-->
    <dependency>
        <groupId>org.malred.iorm</groupId>
        <artifactId>IPersistence</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
package org.malred.test;

import org.malred.io.Resources;

import java.io.InputStream;

public class IPersistenceTest {
    public void test() {
        InputStream resourceAsStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
    }
}

05 容器对象定义

package org.malred.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

public class Configuration {
    // key: statementId, value: 封装好的MappedStatement
    Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
    private DataSource dataSource;

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}
package org.malred.pojo;

public class MappedStatement {
    // id标识
    private String id;
    // 返回值类型
    private String resultType;
    // 参数值类型
    private String parameterType;
    // sql语句
    private String sql;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getParameterType() {
        return parameterType;
    }

    public void setParameterType(String parameterType) {
        this.parameterType = parameterType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

06 解析核心配置文件 sqlMapConfig.xml


<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>
    <!--jdbc连接池-->
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <!--解析xml-->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <!--转换xml,查询dom树-->
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
</dependencies>
package org.malred.sqlSession;

import org.malred.config.XMLConfigBuilder;
import org.malred.pojo.Configuration;

import java.io.InputStream;

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) {
        // 1. 使用dom4j解析配置文件,将解析的内容封装到Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);
        // 2. 创建sqlSessionFactory
    }
}
package org.malred.sqlSession;

public interface SqlSessionFactory {
}
package org.malred.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.malred.pojo.Configuration;

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

public class XMLConfigBuilder {
    private Configuration configuration;

    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 解析配置文件,封装Configuration
     *
     * @param inputStream
     * @return
     */
    public Configuration parseConfig(InputStream inputStream) {
        try {
            // sqlMapConfig.xml
            Document document = new SAXReader().read(inputStream);
            // <configuration/>
            Element rootElement = document.getRootElement();
            // XPath表达式 查询所有 <property/>
            List<Element> property_doms = rootElement.selectNodes("//property");
            // 存放kv结构(name:value)
            Properties properties = new Properties();
            for (Element property : property_doms) {
                // 拿到name和value属性对应的值
                // <property name="xxx" value="xxx" />
                String name = property.attributeValue("name");
                String value = property.attributeValue("value");
                // 保存
                properties.setProperty(name, value);
            }
            // 创建连接池
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            try {
                comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
                comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
                comboPooledDataSource.setUser(properties.getProperty("username"));
                comboPooledDataSource.setPassword(properties.getProperty("password"));
            } catch (PropertyVetoException e) {
                throw new RuntimeException(e);
            }
            // 保存datasource信息
            configuration.setDataSource(comboPooledDataSource);
            // mapper.xml解析

        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}
public class IPersistenceTest {
    public void test() {
        InputStream resourceAsStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactory().build(resourceAsStream);
    }
}

07 解析映射配置文件 mapper.xml

package org.malred.config;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;

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

public class XMLMapperBuilder {
    private Configuration configuration;

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

    public void parse(InputStream inputStream) {
        try {
            // xxxMapper.xml
            Document document = new SAXReader().read(inputStream);
            // <mapper/>
            Element rootElement = document.getRootElement();
            // namespace
            String namespace = rootElement.attributeValue("namespace");
            // <select/> ...
            List<Element> select_doms = rootElement.selectNodes("//select");
            for (Element element : select_doms) {
                String id = element.attributeValue("id");
                String resultType = element.attributeValue("resultType");
                String parameterType = element.attributeValue("parameterType");
                String sqlTxt = element.getTextTrim();
                // sql信息封装
                MappedStatement mappedStatement = new MappedStatement();
                mappedStatement.setId(id);
                mappedStatement.setResultType(resultType);
                mappedStatement.setParamterType(parameterType);
                mappedStatement.setSql(sqlTxt);
                // 存入configuration
                configuration.getMappedStatementMap().put(namespace + "." + id, mappedStatement);
            }
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }

}
package org.malred.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.malred.io.Resources;
import org.malred.pojo.Configuration;

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

public class XMLConfigBuilder {
    private Configuration configuration;

    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 解析配置文件,封装Configuration
     *
     * @param inputStream
     * @return
     */
    public Configuration parseConfig(InputStream inputStream) {
        try {
            // ...
            // 保存datasource信息
            configuration.setDataSource(comboPooledDataSource);
            // mapper.xml解析: 拿到路径--字节输入流--dom4j解析
            List<Element> mapper_doms = rootElement.selectNodes("//mapper");
            for (Element mapper : mapper_doms) {
                String path = mapper.attributeValue("resource");
                InputStream resourceAsStream = Resources.getResourceAsStream(path);
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
                xmlMapperBuilder.parse(resourceAsStream);
            }
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
        return configuration;
    }
}
package org.malred.test;

import org.malred.io.Resources;
import org.malred.sqlSession.SqlSession;
import org.malred.sqlSession.SqlSessionFactory;
import org.malred.sqlSession.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class IPersistenceTest {
    public void test() {
        InputStream resourceAsStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
    }
}

08 会话对象 sqlSession 类定义

package org.malred.sqlSession;

import org.malred.config.XMLConfigBuilder;
import org.malred.pojo.Configuration;

import java.io.InputStream;

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) {
        // 1. 使用dom4j解析配置文件,将解析的内容封装到Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);
        // 2. 创建sqlSessionFactory,生产sqlSession会话对象
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;
    }
}
package org.malred.sqlSession;

public interface SqlSessionFactory {
    // 打开连接,创建会话对象
    public SqlSession openSession();
}
package org.malred.sqlSession;

import org.malred.pojo.Configuration;

public class DefaultSqlSessionFactory implements SqlSessionFactory{
    private Configuration configuration;

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession();
    }

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
}
package org.malred.sqlSession;

public interface SqlSession {
}
package org.malred.sqlSession;

public class DefaultSqlSession implements SqlSession {
}
    public void test() {
        InputStream resourceAsStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession=sqlSessionFactory.openSession();
    }

09 会话对象 sqlSession 方法定义

package org.malred.sqlSession;

import org.malred.pojo.Configuration;

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configuration configuration;

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

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}
package org.malred.sqlSession;

import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;

import java.util.List;

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;

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

    @Override
    public <E> List<E> selectList(String statementId, Object... params) {
        // 调用simpleExecutor里的query方法
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        // 根据statementId从configuration封装好的map里拿到statement
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) {
        List<Object> objects = selectList(statementId, params);
        if (objects.size() == 1) {
            return (T) objects.get(0);
        } else {
            throw new RuntimeException("查询结果为空或查询结果过多");
        }
    }
}
package org.malred.sqlSession;

import java.util.List;

public interface SqlSession {
    // 查询所有
    public <E> List<E> selectList(String statementId, Object... params);

    // 根据条件查询单个
    public <T> T selectOne(String statementId, Object... params);
}
package org.malred.sqlSession;

import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;

import java.util.List;

public interface Executor {
    public <E> List<E> query(
            Configuration configuration, MappedStatement mappedStatement, Object... params);
}
package org.malred.sqlSession;

import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;

import java.util.List;

public class SimpleExecutor implements Executor{
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) {
        return null;
    }
}
package org.malred.test;

import org.malred.io.Resources;
import org.malred.pojo.User;
import org.malred.sqlSession.SqlSession;
import org.malred.sqlSession.SqlSessionFactory;
import org.malred.sqlSession.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class IPersistenceTest {
    public void test() {
        InputStream resourceAsStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        User user = new User();
        user.setId(1L);
        user.setUsername("zhangsan");
        User one = sqlSession.selectOne("user.selectOne", user);
        System.out.println(one);
    }
}

10 查询对象 Query 定义

从 mybatis 中复制来的工具类

package org.malred.utils;

/**
 * Copyright 2009-2015 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author Clinton Begin
 */
public interface TokenHandler {
    String handleToken(String content);
}
package org.malred.utils;


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

public class ParameterMappingTokenHandler implements TokenHandler {
    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

    // context是参数名称 #{id} #{username}

    public String handleToken(String content) {
        parameterMappings.add(buildParameterMapping(content));
        return "?";
    }

    private ParameterMapping buildParameterMapping(String content) {
        ParameterMapping parameterMapping = new ParameterMapping(content);
        return parameterMapping;
    }

    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
        this.parameterMappings = parameterMappings;
    }

}
package org.malred.utils;


public class ParameterMapping {

    private String content;

    public ParameterMapping(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
package org.malred.utils;

/**
 * Copyright 2009-2017 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author Clinton Begin
 */
public class GenericTokenParser {

    private final String openToken; //开始标记
    private final String closeToken; //结束标记
    private final TokenHandler handler; //标记处理器

    public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
        this.openToken = openToken;
        this.closeToken = closeToken;
        this.handler = handler;
    }

    /**
     * 解析${}和#{}
     * @param text
     * @return
     * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
     * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
     */
    public String parse(String text) {
        // 验证参数问题,如果是null,就返回空字符串。
        if (text == null || text.isEmpty()) {
            return "";
        }

        // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
        int start = text.indexOf(openToken, 0);
        if (start == -1) {
            return text;
        }

        // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
        // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
        char[] src = text.toCharArray();
        int offset = 0;
        final StringBuilder builder = new StringBuilder();
        StringBuilder expression = null;
        while (start > -1) {
            // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
            if (start > 0 && src[start - 1] == '\\') {
                builder.append(src, offset, start - offset - 1).append(openToken);
                offset = start + openToken.length();
            } else {
                //重置expression变量,避免空指针或者老数据干扰。
                if (expression == null) {
                    expression = new StringBuilder();
                } else {
                    expression.setLength(0);
                }
                builder.append(src, offset, start - offset);
                offset = start + openToken.length();
                int end = text.indexOf(closeToken, offset);
                while (end > -1) {////存在结束标记时
                    if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时
                        // this close token is escaped. remove the backslash and continue.
                        expression.append(src, offset, end - offset - 1).append(closeToken);
                        offset = end + closeToken.length();
                        end = text.indexOf(closeToken, offset);
                    } else {//不存在转义字符,即需要作为参数进行处理
                        expression.append(src, offset, end - offset);
                        offset = end + closeToken.length();
                        break;
                    }
                }
                if (end == -1) {
                    // close token was not found.
                    builder.append(src, start, src.length - start);
                    offset = src.length;
                } else {
                    //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
                    builder.append(handler.handleToken(expression.toString()));
                    offset = end + closeToken.length();
                }
            }
            start = text.indexOf(openToken, offset);
        }
        if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
    }
}
package org.malred.sqlSession;

import org.malred.config.BoundSql;
import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;
import org.malred.utils.GenericTokenParser;
import org.malred.utils.ParameterMapping;
import org.malred.utils.ParameterMappingTokenHandler;
import org.malred.utils.TokenHandler;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) {
        // 1.注册驱动,获取连接
        try {
            Connection connection = configuration.getDataSource().getConnection();
            // 2.获取sql语句
            String sql = mappedStatement.getSql();
            // 转换sql语句 #{xxx} -> ?
            BoundSql boundSql = getBoundSql(sql);
            // 3.获取预处理对象
            PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlTxt());
            // 4.填充参数

            // 5.执行sql

            // 6.封装返回结果集

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    /**
     * 对#{}解析:1.将#{}替换为? 2.解析出#{}里面的值进行存储
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser parser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // 解析出来的sql
        String parseSql = parser.parse(sql);
        // #{}里解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }
}
package org.malred.config;

import org.malred.utils.ParameterMapping;

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

public class BoundSql {
    private String sqlTxt;// 解析后的sql
    private List<ParameterMapping> parameterMappingList=new ArrayList<>();

    public BoundSql(String sqlTxt, List<ParameterMapping> parameterMappingList) {
        this.sqlTxt = sqlTxt;
        this.parameterMappingList = parameterMappingList;
    }

    public String getSqlTxt() {
        return sqlTxt;
    }

    public void setSqlTxt(String sqlTxt) {
        this.sqlTxt = sqlTxt;
    }

    public List<ParameterMapping> getParameterMappingList() {
        return parameterMappingList;
    }

    public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
        this.parameterMappingList = parameterMappingList;
    }
}

11 参数设置实现

package org.malred.sqlSession;

import org.malred.config.BoundSql;
import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;
import org.malred.utils.GenericTokenParser;
import org.malred.utils.ParameterMapping;
import org.malred.utils.ParameterMappingTokenHandler;
import org.malred.utils.TokenHandler;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) {
        try {
            // 1.注册驱动,获取连接
            Connection connection = configuration.getDataSource().getConnection();
            // 2.获取sql语句
            String sql = mappedStatement.getSql();
            // 转换sql语句 #{xxx} -> ?
            BoundSql boundSql = getBoundSql(sql);
            // 3.获取预处理对象
            PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlTxt());
            // 4.填充参数
            String parameterType = mappedStatement.getParameterType();
            Class<?> parameterTypeClass = getClassType(parameterType);
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                // #{content}
                String content = parameterMapping.getContent();
                try {
                    // 反射获取属性值
                    Field declaredField = parameterTypeClass.getDeclaredField(content);
                    // 暴力访问
                    declaredField.setAccessible(true);
                    Object o = declaredField.get(params[0]);

                    preparedStatement.setObject(i + 1, o);
                } catch (NoSuchFieldException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
            // 5.执行sql

            // 6.封装返回结果集

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    /**
     * 根据标签指定的参数类型加载class
     *
     * @param parameterType
     * @return
     * @throws ClassNotFoundException
     */
    private Class<?> getClassType(String parameterType)  {
        if (parameterType != null) {
            Class<?> aClass = null;
            try {
                aClass = Class.forName(parameterType);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            return aClass;
        }
        return null;
    }

    /**
     * 对#{}解析:1.将#{}替换为? 2.解析出#{}里面的值进行存储
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        // ...
    }
}

12 封装返回结果集实现

package org.malred.sqlSession;

import org.malred.config.BoundSql;
import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;
import org.malred.utils.GenericTokenParser;
import org.malred.utils.ParameterMapping;
import org.malred.utils.ParameterMappingTokenHandler;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) {
        try {
            // 1.注册驱动,获取连接
            Connection connection = configuration.getDataSource().getConnection();
            // 2.获取sql语句
            String sql = mappedStatement.getSql();
            // 转换sql语句 #{xxx} -> ?
            BoundSql boundSql = getBoundSql(sql);
            // 3.获取预处理对象
            PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlTxt());
            // 4.填充参数
            String parameterType = mappedStatement.getParameterType();
            Class<?> parameterTypeClass = getClassType(parameterType);
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                // #{content}
                String content = parameterMapping.getContent();
                try {
                    // 反射获取属性值
                    Field declaredField = parameterTypeClass.getDeclaredField(content);
                    // 暴力访问
                    declaredField.setAccessible(true);
                    // params[0] -> object(eg. User)
                    Object o = declaredField.get(params[0]);

                    preparedStatement.setObject(i + 1, o);
                } catch (NoSuchFieldException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
            // 5.执行sql
            ResultSet resultSet = preparedStatement.executeQuery();
            String resultType = mappedStatement.getResultType();
            Class<?> resultTypeClass = getClassType(resultType);
            ArrayList<Object> objects = new ArrayList<>();

            // 6.封装返回结果集
            while (resultSet.next()) {
                // 元数据
                ResultSetMetaData metaData = resultSet.getMetaData();
                // 创建实例
                Object o = resultTypeClass.newInstance();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    // 字段名
                    String columnName = metaData.getColumnName(i);// 下标从1开始
                    // 字段值
                    Object value = resultSet.getObject(columnName);
                    // 使用反射,根据数据库表和实体的对应关系,完成封装
                    PropertyDescriptor propertyDescriptor =
                            new PropertyDescriptor(columnName, resultTypeClass);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    // 将值设置到对象的参数里
                    writeMethod.invoke(o, value);
                }
                objects.add(o);
            }
            return (List<E>) objects;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (IntrospectionException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据标签指定的参数类型加载class
     *
     * @param parameterType
     * @return
     * @throws ClassNotFoundException
     */
    private Class<?> getClassType(String parameterType) {
        if (parameterType != null) {
            Class<?> aClass = null;
            try {
                aClass = Class.forName(parameterType);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            return aClass;
        }
        return null;
    }

    /**
     * 对#{}解析:1.将#{}替换为? 2.解析出#{}里面的值进行存储
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser parser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // 解析出来的sql
        String parseSql = parser.parse(sql);
        // #{}里解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }
}

13 Client 端运行测试

package org.malred.pojo;

public class User {


    // 使用Long会在writhMethod的invoke处报错(Argument type dismatch)
    private int id;
    private String username;
    private String password;
    private String gender;
    private String addr;


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }


    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }


    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }


    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", gender='" + gender + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }
}
package org.malred.test;

import org.junit.After;
import org.junit.Before;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.malred.io.Resources;
import org.malred.pojo.User;
import org.malred.sqlSession.SqlSession;
import org.malred.sqlSession.SqlSessionFactory;
import org.malred.sqlSession.SqlSessionFactoryBuilder;

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

public class IPersistenceTest {
    SqlSession sqlSession;
    InputStream resourceAsStream;

    @BeforeEach
    public void before() {
        System.out.println("before");
        resourceAsStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = sqlSessionFactory.openSession();
    }


    @Test
    public void selectOne() {
        User user = new User();
        user.setId(1);
        user.setUsername("zhangsan");
        User one = sqlSession.selectOne("user.selectOne", user);
        System.out.println(one);
    }

    @Test
    public void selectList() {
        List<User> users = sqlSession.selectList("user.selectList");
        System.out.println(users);
    }

    @AfterEach
    public void after() {
        System.out.println("after");
        try {
            resourceAsStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

malred: 扩展, 添加其余三种操作

<!--如果都有同样id的select, 就需要通过mapper的namespace区分-->
<mapper namespace="user">
    <!--不能通过标签名区分,所以增加id属性来区分-->
    <!--sql唯一标识: namespace + id => statementId-->
    <!--通过resultType来指定结果类型-->
    <select id="selectList" resultType="org.malred.pojo.User">
        select * from tb_user
    </select>

    <!--将多个参数封装到对象传入-->
    <!--通过#{参数名}来明确要填充类的哪个参数-->
    <select id="selectOne" resultType="org.malred.pojo.User" parameterType="org.malred.pojo.User">
        select * from tb_user where id = #{id} and username = #{username}
    </select>

    <update id="update" parameterType="org.malred.pojo.User">
        update tb_user
        set
        username = #{username},
        password = #{password}
        where id = #{id}
    </update>

    <delete id="delete" parameterType="org.malred.pojo.User">
        delete from tb_user
        where id = #{id}
    </delete>

    <insert id="insert" parameterType="org.malred.pojo.User">
        insert into tb_user(gender,addr,username,password)
        values (#{gender},#{addr},#{username},#{password})
    </insert>
</mapper>
package org.malred.sqlSession;

import java.util.List;

public interface SqlSession {
    // 查询所有
    public <E> List<E> selectList(String statementId, Object... params);

    // 根据条件查询单个
    public <T> T selectOne(String statementId, Object... params);

    // 修改
    public int update(String statementId, Object... params);

    // 删除
    public int delete(String statementId, Object... params);

    // 插入
    public int insert(String statementId, Object... params);

    // 提交
    public void commit();
}
package org.malred.sqlSession;

import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;

import java.util.List;

public interface Executor {
    // 查找
    public <E> List<E> query(
            Configuration configuration, MappedStatement mappedStatement, Object... params);

    // 修改 插入 删除
    public int update(Configuration configuration, MappedStatement mappedStatement, Object... params);

    // 提交
    public  void commit();
}
package org.malred.sqlSession;

import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;

import java.util.List;

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;
    private Executor executor;

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

    // ...

    @Override
    public int update(String statementId, Object... params) {
        executor = new TransactionExecutor();
        // 根据statementId从configuration封装好的map里拿到statement
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        int count = executor.update(configuration, mappedStatement, params);
        return count;
    }

    @Override
    public int delete(String statementId, Object... params) {
        executor = new TransactionExecutor();
        // 根据statementId从configuration封装好的map里拿到statement
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        int count = executor.update(configuration, mappedStatement, params);
        return count;
    }

    @Override
    public int insert(String statementId, Object... params) {
        executor = new TransactionExecutor();
        // 根据statementId从configuration封装好的map里拿到statement
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        int count = executor.update(configuration, mappedStatement, params);
        return count;
    }

    @Override
    public void commit() {
        // Can't call rollback when autocommit=true
        if (!configuration.isAutoCommit()) {
            executor.commit();
        }
    }

}
package org.malred.sqlSession;

import org.malred.config.BoundSql;
import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;
import org.malred.utils.GenericTokenParser;
import org.malred.utils.ParameterMapping;
import org.malred.utils.ParameterMappingTokenHandler;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class TransactionExecutor implements Executor {

    private Connection connection;
    private PreparedStatement preparedStatement;
    private Configuration configuration;

    // ...

    @Override
    public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) {
        // 1.注册驱动,获取连接
        connection = null;
        this.configuration = configuration;
        try {
            connection = configuration.getDataSource().getConnection();
        } catch (SQLException e) {
            rollback(connection, configuration.isAutoCommit());
            throw new RuntimeException(e);
        }
        // 开启事务
        try {
            connection.setAutoCommit(configuration.isAutoCommit());
        } catch (SQLException e) {
            rollback(connection, configuration.isAutoCommit());
            throw new RuntimeException(e);
        }
        // 2.获取sql语句
        String sql = mappedStatement.getSql();
        // 转换sql语句 #{xxx} -> ?
        BoundSql boundSql = getBoundSql(sql);
        // 3.获取预处理对象
        preparedStatement = null;
        try {
            preparedStatement = connection.prepareStatement(boundSql.getSqlTxt());
        } catch (SQLException e) {
            rollback(connection, configuration.isAutoCommit());
            throw new RuntimeException(e);
        }
        // 4.填充参数
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = getClassType(parameterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            // #{content}
            String content = parameterMapping.getContent();
            try {
                // 反射获取属性值
                Field declaredField = parameterTypeClass.getDeclaredField(content);
                // 暴力访问
                declaredField.setAccessible(true);
                // params[0] -> object(eg. User)
                Object o = declaredField.get(params[0]);

                preparedStatement.setObject(i + 1, o);
            } catch (NoSuchFieldException e) {
                rollback(connection, configuration.isAutoCommit());
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                rollback(connection, configuration.isAutoCommit());
                throw new RuntimeException(e);
            } catch (SQLException e) {
                rollback(connection, configuration.isAutoCommit());
                throw new RuntimeException(e);
            }
        }
        // 5.执行sql
        int count = 0;
        try {
            count = preparedStatement.executeUpdate();
        } catch (SQLException e) {
            rollback(connection, configuration.isAutoCommit());
            throw new RuntimeException(e);
        }
        return count;
    }

    public void commit() {
        try {
            connection.commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private void rollback(Connection connection, boolean autoCommit) {
        System.out.println("something wrong! now rollback!");
        try {
            if (!autoCommit) {
                connection.rollback();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    // ...
}

14 功能扩展-getMapper 方法实现

当前问题: 每次都要创建 sqlsession,有很多重复代码

package org.malred.dao;

import org.malred.io.Resources;
import org.malred.pojo.User;
import org.malred.sqlSession.SqlSession;
import org.malred.sqlSession.SqlSessionFactory;
import org.malred.sqlSession.SqlSessionFactoryBuilder;

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

public class UserDaoImpl implements IUserDao {
    @Override
    public List<User> findAll() {
        InputStream resourceAsStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        List<User> users = sqlSession.selectList("user.selectList");
        return users;
    }

    @Override
    public User findByCondition(User u) {
        System.out.println("before");
        InputStream resourceAsStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        User user = new User();
        user.setId(1);
        user.setUsername("张三");
        User one = sqlSession.selectOne("user.selectOne", user);

        return one ;
    }
}


15 功能扩展-动态代理 invoke 方法实现

package org.malred.dao;

import org.malred.pojo.User;

import java.util.List;

public interface IUserDao {
    // 查询所有用户
    public List<User> findAll();

    // 根据条件查询
    public User findByCondition(User u);

    // 修改
    public int update(User u);

    // 删除
    public int delete(User u);

    // 插入
    public void insert(User u);
}
    @Test
    public void proxyFind() {
        IUserDao mapper = sqlSession.getMapper(IUserDao.class);
        List<User> users = mapper.findAll();
        System.out.println(users);

        User u = new User();
        u.setId(1);
        User user = mapper.findByCondition(u);
        System.out.println(user);
    }

    @Test
    public void proxyUpt() {
        IUserDao mapper = sqlSession.getMapper(IUserDao.class);
        User u = new User();
        u.setAddr("贝克街");
        u.setUsername("福尔摩斯");
        u.setPassword("shaloke");
        u.setGender("男");
        u.setId(200);
        mapper.insert(u);

        System.out.println(mapper.findByCondition(u));

        u.setUsername("艾克斯");
        int count = mapper.update(u);
        System.out.println("影响了" + count + "条数据");

        System.out.println(mapper.findByCondition(u));

        count = mapper.delete(u);
        System.out.println("影响了" + count + "条数据");

//        System.out.println(mapper.findByCondition(u));
    }
<!-- UserProxyMapper.xml -->
<!--走代理: statementid = 接口全限定名+方法名-->
<mapper namespace="org.malred.dao.IUserDao">
    <select id="findAll" resultType="org.malred.pojo.User">
        select * from tb_user
    </select>
    <select id="findByCondition" resultType="org.malred.pojo.User" parameterType="org.malred.pojo.User">
        select * from tb_user where id = #{id}
    </select>
    <update id="update" parameterType="org.malred.pojo.User">
        update tb_user
        set
        username = #{username},
        password = #{password}
        where id = #{id}
    </update>

    <delete id="delete" parameterType="org.malred.pojo.User">
        delete from tb_user
        where id = #{id}
    </delete>

    <insert id="insert" parameterType="org.malred.pojo.User">
        insert into tb_user(id,gender,addr,username,password)
        values (#{id},#{gender},#{addr},#{username},#{password})
    </insert>
</mapper>
package org.malred.sqlSession;

import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;

import java.lang.reflect.*;
import java.util.List;
import java.util.Objects;

public class DefaultSqlSession implements SqlSession {
    // ...

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理为Dao接口生成代理对象,并返回
        Object proxyInstance = Proxy.newProxyInstance(
                DefaultSqlSession.class.getClassLoader(),
                new Class[]{mapperClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 底层还是执行JDBC
                        // 准备参数 1: statementId: sql语句唯一标识 -> namespace.id
                        // 因为走mapper代理(invoke中)无法获取namespace和id,
                        // 所以xml里的namespace.id = 接口全限定类名.方法名

                        // 方法名
                        String methodName = method.getName();
                        // 方法所在类的名称
                        String className = method.getDeclaringClass().getName();

                        // xml里声明namespace+id就是statementid,在创建session时解析并存储
                        // 这里通过类名+方法名,然后去configuration里找
                        // xml里也要用类名+方法名来声明(如果是用代理)
                        String statementId = className + "." + methodName;

                        // 准备参数 2: params = args

                        // 获取被调用方法的返回值类型
                        Type genericReturnType = method.getGenericReturnType();

                        // 这里只是简单判断一下
                        // 判断是否进行了泛型类型参数化(是否有泛型)
                        if (genericReturnType instanceof ParameterizedType) {
//                            if (x instanceof Collection< ? >){
//                            }
//                            if (x instanceof Map<?,?>){
//                            }
                            List<Object> objects = selectList(statementId, args);
                            // 认为是集合
                            return objects;
                        }
                        // 返回值是基本类型 (int void)
                        if (genericReturnType.getTypeName().equals("int") ||
                                genericReturnType.getTypeName().equals("void")
                        ) {
                            // 修改 删除 插入 (int void)
                            return update(statementId, args);
                        }
                        return selectOne(statementId, args);
                    }
                });
        return (T) proxyInstance;
    }

}
<!-- sqlMapConfig.xml -->
<configuration>
    <!--数据库配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3307/mybatis?useSSL=false"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>
    <!--Mapper文件的全路径-->
    <mapper resource="UserMapper.xml"></mapper>
    <mapper resource="UserProxyMapper.xml"></mapper>
</configuration>

malred: 扩展,注解操作

    @Test
    public void annotationUptProduct() throws ParseException {
        IProductDao mapper = sqlSession.getMapper(IProductDao.class);
        TbProduct product = new TbProduct();
        product.setProduct_name("痛包");
        product.setProduct_time(ft.parse("2019-12-25"));
        product.setId(1);
        mapper.insert(product);

        System.out.println(mapper.findById(product));

        product.setProduct_name("艾克斯");
        int count = mapper.update(product);
        System.out.println("影响了" + count + "条数据");

        System.out.println(mapper.findById(product));

        count = mapper.delete(product);
        System.out.println("影响了" + count + "条数据");
    }
package org.malred.pojo;

import java.util.Date;

public class TbProduct {

    private int id;
    private String product_name;
    private Date product_time;

    @Override
    public String toString() {
        return "TbProduct{" +
                "id=" + id +
                ", product_name='" + product_name + '\'' +
                ", product_time=" + product_time +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProduct_name() {
        return product_name;
    }

    public void setProduct_name(String product_name) {
        this.product_name = product_name;
    }

    public Date getProduct_time() {
        return product_time;
    }

    public void setProduct_time(Date product_time) {
        this.product_time = product_time;
    }
}
package org.malred.dao;

import org.malred.Annotations.*;
import org.malred.pojo.TbProduct;

import java.util.List;

public interface IProductDao {
    @Select("select * from tb_product")
    public List<TbProduct> findAll();

    @Select("select * from tb_product where id = #{id}")
    public TbProduct findById(TbProduct product);

    @Update("update tb_product set " +
            "product_name = #{product_name}," +
            "product_time = #{product_time} " +
            "where id = #{id}")
    public int update(TbProduct product);

    @Insert("insert into tb_product(id,product_name,product_time) " +
            "values(#{id},#{product_name},#{product_time})")
    public void insert(TbProduct product);

    @Delete("delete from tb_product where id = #{id}")
    public int delete(TbProduct product);

}
package org.malred.Annotations;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 其他3个注解差不多
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Delete {
    // 保存sql语句
    String value();
}
package org.malred.sqlSession;

import org.malred.Annotations.Delete;
import org.malred.Annotations.Insert;
import org.malred.Annotations.Select;
import org.malred.Annotations.Update;
import org.malred.config.XMLMapperBuilder;
import org.malred.pojo.Configuration;
import org.malred.pojo.MappedStatement;

import java.lang.reflect.*;
import java.util.List;
import java.util.Objects;

public class DefaultSqlSession implements SqlSession {
    // ...

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理为Dao接口生成代理对象,并返回
        Object proxyInstance = Proxy.newProxyInstance(
                DefaultSqlSession.class.getClassLoader(),
                new Class[]{mapperClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 底层还是执行JDBC
                        // 准备参数 1: statementId: sql语句唯一标识 -> namespace.id
                        // 因为走mapper代理(invoke中)无法获取namespace和id,
                        // 所以xml里的namespace.id = 接口全限定类名.方法名

                        // 方法名
                        String methodName = method.getName();
                        // 方法所在类的名称
                        String className = method.getDeclaringClass().getName();

                        // xml里声明namespace+id就是statementid,在创建session时解析并存储
                        // 这里通过类名+方法名,然后去configuration里找
                        // xml里也要用类名+方法名来声明(如果是用代理)
                        String statementId = className + "." + methodName;

                        // 准备参数 2: params = args

                        // 获取被调用方法的返回值类型
                        Type genericReturnType = method.getGenericReturnType();

                        // 如果有注解
                        if (method.isAnnotationPresent(Select.class)) {
                            Select annotation = method.getAnnotation(Select.class);
                            ParseAnnotation(method, statementId, genericReturnType, annotation.value());
                        }
                        if (method.isAnnotationPresent(Update.class)) {
                            Update annotation = method.getAnnotation(Update.class);
                            ParseAnnotation(method, statementId, genericReturnType, annotation.value());
                        }
                        if (method.isAnnotationPresent(Delete.class)) {
                            Delete annotation = method.getAnnotation(Delete.class);
                            ParseAnnotation(method, statementId, genericReturnType, annotation.value());
                        }
                        if (method.isAnnotationPresent(Insert.class)) {
                            Insert annotation = method.getAnnotation(Insert.class);
                            ParseAnnotation(method, statementId, genericReturnType, annotation.value());
                        }

                        // 这里只是简单判断一下
                        // 判断是否进行了泛型类型参数化(是否有泛型)
                        if (genericReturnType instanceof ParameterizedType) {
//                            if (x instanceof Collection< ? >){
//                            }
//                            if (x instanceof Map<?,?>){
//                            }
                            List<Object> objects = selectList(statementId, args);
                            // 认为是集合
                            return objects;
                        }
                        // 返回值是基本类型 (int void)
                        if (genericReturnType.getTypeName().equals("int") ||
                                genericReturnType.getTypeName().equals("void")
                        ) {
                            // 修改 删除 插入 (int void)
                            return update(statementId, args);
                        }
                        return selectOne(statementId, args);
                    }
                });
        return (T) proxyInstance;
    }

    private void ParseAnnotation(Method method, String statementId, Type genericReturnType, String sqlTxt) {
        // sql信息封装
        MappedStatement mappedStatement = new MappedStatement();
        mappedStatement.setId(statementId);
        mappedStatement.setResultType(genericReturnType.getTypeName());
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        // 暂时只支持一个接口方法参数(并且不能是基本类型)
        mappedStatement.setParamterType(genericParameterTypes[0].getTypeName());
        // 注解保存的sql
        mappedStatement.setSql(sqlTxt);
        // 存入configuration
        configuration.getMappedStatementMap().put(statementId, mappedStatement);
    }
}

04 任务二:MyBatis基础回顾及高级应用

01 MyBatis相关概念回顾


我觉得问题是, 半自动虽然可以优化sql, 但是却失去了ORM框架的初衷(减少开发人员编写sql的负担, 降低出错概率, 专注业务逻辑)

02 MyBatis环境搭建回顾

package org.malred.pojo;

public class User {

    private Integer id;
    private String username;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }


    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mybatis_quickStarter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <!--引入依赖-->
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>
package org.malred.test;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.malred.pojo.User;

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

public class MybatisTest {
    InputStream resourceAsStream;
    SqlSession sqlSession;

    @Before
    public void before() throws Exception {
        resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = sqlSessionFactory.openSession();
    }

    @Test
    public void t1() throws Exception {
        List<User> users = sqlSession.selectList("user.findAll");
        System.out.println(users);
    }

    @After
    public void after() throws Exception {
        sqlSession.close();
        resourceAsStream.close();
    }
}

sqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--运行环境-->
    <environments default="development">
        <environment id="development">
            <!--事务管理-->
            <transactionManager type="JDBC"/>
            <!--使用mybatis使用的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3307/test"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--映射文件-->
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace+id = sql的唯一标识-->
<mapper namespace="user">
    <select id="findAll" resultType="org.malred.pojo.User">
        select * from lagou_mybatis_user
    </select>
</mapper>

03 MyBatis的CRUD回顾

05 任务三:MyBatis源码剖析

01 MyBatis架构原理-架构设计_构件关系_总体流

02 MyBatis初始化过程

02 IoC 容器设计实现及 Spring 源码分析

01 任务一:自定义IoC&AOP框架

02 Spring框架课程内容介绍

03 Spring框架整体回顾


04 IoC编程思想巩固

05 IoC与DI区别说明

06 AOP编程思想巩固



07 手写IoC和AOP之问题分析

03 MVC 框架设计实现及 SpringMVC 源码分析、通用数据操作接口设计及 SpringData 接口规范

02 任务二:自定义MVC框架

01 手写MVC框架之SpringMVC原理回顾

02 MVC框架之注解开发


web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <display-name>web app</display-name>
    <servlet>
        <servlet-name>malredmvc</servlet-name>
        <servlet-class>org.malred.mvcframework.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>malredmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>
package org.malred.mvcframework.servlet;

import javax.servlet.http.HttpServlet;

public class DispatcherServlet extends HttpServlet {
}
package org.malred.mvcframework.annotations;

import java.lang.annotation.*;

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWired {
    String value() default "";
}
package org.malred.mvcframework.annotations;

import java.lang.annotation.*;

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    String value() default "";
}
package org.malred.mvcframework.annotations;

import java.lang.annotation.*;

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value() default "";
}
package org.malred.mvcframework.annotations;

import java.lang.annotation.*;

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
    String value() default "";
}

03 MVC框架之流程结构开发

01 任务一: SpringMVC基础回顾及其高级深入

03 SpringMVC课程介绍

04 任务四:SpringData 高级 应用及其源码剖析

01 SpringDataJpa 内容介绍

02.第二阶段 Web服务器深度应用及调优(完结)

01.模块一 Tomcat深度剖析及性能调优、Nginx深度剖析及性能调优

高级使用与源码刨析

源码下载

浏览器请求服务器流程

Tomcat处理请求

servlet容器处理流程

总体架构

Coyote

Catalina

核心配置


appBase-->指定资源目录复制并简单修改出两个host
复制黏贴webapp到webapp2修改webapp和webapp2下的index.jsp
配置域名解析
复制webapp下的root文件夹到某个地方
修改root的index.jsp文件

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<!-- Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 -->
<!-- server根元素,创建server实例 -->
<!-- 
    port="8005" -> 关闭服务器的监听端口
    shutdown="SHUTDOWN" -> 关闭服务器的指令字符串
 -->
<Server port="8005" shutdown="SHUTDOWN">
    <!-- 五个监听器,初始化,无需更改 -->
    <!-- 以日志形式输出服务器,操作系统,JVM的版本信息 -->
    <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
    <!-- Security listener. Documentation at /docs/config/listeners.html
    <Listener className="org.apache.catalina.security.SecurityListener" />
    -->
    <!--APR library loader. Documentation at /docs/apr.html -->
    <!-- 加载(服务器启动)和销毁(服务器停止) APR,如果找不到APR库,则输出日志,并不影响tomcat启动 -->
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
    <!-- Prevent memory leaks due to use of particular java/javax APIs-->
    <!-- 避免jre内存泄漏 -->
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
    <!-- 加载(服务器启动)和销毁(服务器停止)全局命名服务 -->
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
    <!-- 在Context停止时重建Executor池中的线程,以避免ThreadLocal相关的内存泄露 -->
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>

    <!-- Global JNDI resources
         Documentation at /docs/jndi-resources-howto.html
         GlobalNamingResources中定义了全局命名服务
    -->
    <GlobalNamingResources>
        <!-- Editable user database that can also be used by
             UserDatabaseRealm to authenticate users
        -->
        <Resource name="UserDatabase" auth="Container"
                  type="org.apache.catalina.UserDatabase"
                  description="User database that can be updated and saved"
                  factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
                  pathname="conf/tomcat-users.xml"/>
    </GlobalNamingResources>

    <!-- A "Service" is a collection of one or more "Connectors" that share
         a single "Container" Note:  A "Service" is not itself a "Container",
         so you may not define subcomponents such as "Valves" at this level.
         Documentation at /docs/config/service.html
     -->
    <!-- service服务 -->
    <Service name="Catalina">

        <!--The connectors can use a shared executor, you can define one or more named thread pools-->
        <!-- 这个配置可以定义共享线程池,给connector使用,让多个connector使用同一个线程池 -->
        <!--
        <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
            maxThreads="150" minSpareThreads="4"/>
        -->
        <!-- 
              Connector 标签⽤于创建链接器实例
              默认情况下,server.xml 配置了两个链接器,⼀个⽀持HTTP协议,⼀个⽀持AJP协议
              ⼤多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进⾏优化
              默认情况下,Service 并未添加共享线程池配置。 如果我们想添加⼀个线程池, 可以在
              <Service> 下添加如下配置:
              name:线程池名称,⽤于 Connector中指定
              namePrefix:所创建的每个线程的名称前缀,⼀个单独的线程名称为
              namePrefix+threadNumber
              maxThreads:池中最⼤线程数
              minSpareThreads:活跃线程数,也就是核⼼池线程数,这些线程不会被销毁,会⼀直存在
              maxIdleTime:线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位
              毫秒
              maxQueueSize:在被执⾏前最⼤线程排队数⽬,默认为Int的最⼤值,也就是⼴义的⽆限。除⾮特
              殊情况,这个值 不需要更改,否则会有请求不会被处理的情况发⽣
              prestartminSpareThreads:启动线程池时是否启动 minSpareThreads部分线程。默认值为
              false,即不启动
              threadPriority:线程池中线程优先级,默认值为5,值从1到10
              className:线程池实现类,未指定情况下,默认实现类为
              org.apache.catalina.core.StandardThreadExecutor。如果想使⽤⾃定义线程池⾸先需要实现
              org.apache.catalina.Executor接⼝
         -->
        <Executor name="commonThreadPool"
                  namePrefix="thread-exec-"
                  maxThreads="200"
                  minSpareThreads="100"
                  maxIdleTime="60000"
                  maxQueueSize="Integer.MAX_VALUE"
                  prestartminSpareThreads="false"
                  threadPriority="5"
                  className="org.apache.catalina.core.StandardThreadExecutor"/>
        <!-- A "Connector" represents an endpoint by which requests are received
             and responses are returned. Documentation at :
             Java HTTP Connector: /docs/config/http.html
             Java AJP  Connector: /docs/config/ajp.html
             APR (HTTP/AJP) Connector: /docs/apr.html
             Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
        -->
        <!-- 
              connector可以配置多个,每个connector占一个线程
              redirectPort="8443" -> 把https的请求转发到8443端口
         -->
        <!--
             port:
             端⼝号,Connector ⽤于创建服务端Socket 并进⾏监听, 以等待客户端请求链接。如果该属性设置
             为0, Tomcat将会随机选择⼀个可⽤的端⼝号给当前Connector 使⽤
             protocol:
             当前Connector ⽀持的访问协议。 默认为 HTTP/1.1 , 并采⽤⾃动切换机制选择⼀个基于 JAVA
             NIO 的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)
             connectionTimeOut:
             Connector 接收链接后的等待超时时间, 单位为 毫秒。 -1 表示不超时。
             redirectPort:
             当前Connector 不⽀持SSL请求, 接收到了⼀个请求, 并且也符合security-constraint 约束,
             需要SSL传输,Catalina⾃动将请求重定向到指定的端⼝。
             executor:
             指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads 等属性配置内部线程池。
             可以使⽤共享线程池
             Engine 标签
             Engine 表示 Servlet 引擎
             Host 标签
             Host 标签⽤于配置⼀个虚拟主机
             URIEncoding:
             ⽤于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO-
             8859-1
        -->
        <!--org.apache.coyote.http11.Http11NioProtocol , ⾮阻塞式 Java NIO 链接器-->
        <Connector port="8080" protocol="HTTP/1.1" executor="commonThreadPool"
                   connectionTimeout="20000" redirectPort="8443"/>
        <!-- A "Connector" using the shared thread pool-->
        <!--
        <Connector executor="tomcatThreadPool"
                   port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        -->
        <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443
             This connector uses the NIO implementation. The default
             SSLImplementation will depend on the presence of the APR/native
             library and the useOpenSSL attribute of the
             AprLifecycleListener.
             Either JSSE or OpenSSL style configuration may be used regardless of
             the SSLImplementation selected. JSSE style configuration is used below.
        -->
        <!--
        <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
                   maxThreads="150" SSLEnabled="true">
            <SSLHostConfig>
                <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                             type="RSA" />
            </SSLHostConfig>
        </Connector>
        -->
        <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
             This connector uses the APR/native implementation which always uses
             OpenSSL for TLS.
             Either JSSE or OpenSSL style configuration may be used. OpenSSL style
             configuration is used below.
        -->
        <!--
        <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
                   maxThreads="150" SSLEnabled="true" >
            <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
            <SSLHostConfig>
                <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                             certificateFile="conf/localhost-rsa-cert.pem"
                             certificateChainFile="conf/localhost-rsa-chain.pem"
                             type="RSA" />
            </SSLHostConfig>
        </Connector>
        -->

        <!-- Define an AJP 1.3 Connector on port 8009 -->
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>


        <!-- An Engine represents the entry point (within Catalina) that processes
             every request.  The Engine implementation for Tomcat stand alone
             analyzes the HTTP headers included with the request, and passes them
             on to the appropriate Host (virtual host).
             Documentation at /docs/config/engine.html -->

        <!-- You should set jvmRoute to support load-balancing via AJP ie :
        <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
        -->
        <!-- defaultHost="localhost" => 没有指定主机时,默认使用名为localhost主机 -->
        <Engine name="Catalina" defaultHost="localhost">

            <!--For clustering, please take a look at documentation at:
                /docs/cluster-howto.html  (simple how to)
                /docs/config/cluster.html (reference documentation) -->
            <!--
            <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
            -->

            <!-- Use the LockOutRealm to prevent attempts to guess user passwords
                 via a brute-force attack -->
            <Realm className="org.apache.catalina.realm.LockOutRealm">
                <!-- This Realm uses the UserDatabase configured in the global JNDI
                     resources under the key "UserDatabase".  Any edits
                     that are performed against this UserDatabase are immediately
                     available for use by the Realm.  -->
                <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
                       resourceName="UserDatabase"/>
            </Realm>
            <!-- 
                appBase="webapps" => 资源目录,webapp默认访问Root文件夹
                unpackWARs="true" => 打包为war  
            -->
            <Host name="localhost" appBase="webapps"
                  unpackWARs="true" autoDeploy="true">

                <!-- SingleSignOn valve, share authentication between web applications
                     Documentation at: /docs/config/valve.html -->
                <!--
                <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
                -->

                <!-- Access log processes all example.
                     Documentation at: /docs/config/valve.html
                     Note: The pattern used is equivalent to using pattern="common" -->
                <!-- 记录访问的日志信息 -->
                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                       prefix="localhost_access_log" suffix=".txt"
                       pattern="%h %l %u %t &quot;%r&quot; %s %b"/>

            </Host>
            <Host name="www.abc.com" appBase="webapps"
                  unpackWARs="true" autoDeploy="true">
                <!-- http://www.abc.com/root -->
                <Context docBase="D:\webStorm_code\ROOT" path="/root"/>

                <!-- SingleSignOn valve, share authentication between web applications
                     Documentation at: /docs/config/valve.html -->
                <!--
                <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
                -->

                <!-- Access log processes all example.
                     Documentation at: /docs/config/valve.html
                     Note: The pattern used is equivalent to using pattern="common" -->
                <!-- 记录访问的日志信息 -->
                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                       prefix="localhost_access_log" suffix=".txt"
                       pattern="%h %l %u %t &quot;%r&quot; %s %b"/>
            </Host>
            <Host name="www.def.com" appBase="webapps2"
                  unpackWARs="true" autoDeploy="true">

                <!-- SingleSignOn valve, share authentication between web applications
                     Documentation at: /docs/config/valve.html -->
                <!--
                <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
                -->

                <!-- Access log processes all example.
                     Documentation at: /docs/config/valve.html
                     Note: The pattern used is equivalent to using pattern="common" -->
                <!-- 记录访问的日志信息 -->
                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                       prefix="localhost_access_log" suffix=".txt"
                       pattern="%h %l %u %t &quot;%r&quot; %s %b"/>

            </Host>
        </Engine>
    </Service>
</Server>

手写迷你tomcat

思路分析

1.0

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.malguy</groupId>
    <artifactId>minicat</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
package server;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * minicat主类
 */
public class Bootstrap {
    /**
     * socket监听的端口号
     */
    private int port=8080;
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * minicat启动需要初始化展开的一些操作
     */
    public void start() throws IOException {
        //todo minicat v1.0,http://localhost:8080,返回固定字符串到页面
        ServerSocket serverSocket=new ServerSocket(port);
        System.out.println("minicat start on port: "+port);
        while (true){
            //阻塞监听,直到连接
            Socket socket=serverSocket.accept();
            //有了socket,接收到请求
            OutputStream outputStream=socket.getOutputStream();
            outputStream.write("Hello Mincat".getBytes());
            socket.close();
        }
    }
    /**
     * mincat 程序启动入口
     * @param args
     */
    public static void main(String[] args) {
        Bootstrap bootstrap=new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

bug: 我们的响应没有遵循http的标准(响应体,响应头…)

package server; 
/**
 * http协议工具类,主要是提供响应头信息
 */
public class HttpProtocolUtil {
    /**
     * 为响应码200提供响应头信息
     * 响应体内容的长度
     * @return
     */
    public static String getHttpHeader200(long contentLength){
        return "HTTP/1.1 200 OK \n"+
                "Content-Type: text/html \n"+
                "Content-Length: "+ contentLength+"\n"+
                "\r\n"; //需要回车换行来间隔 头,体
    }
    /**
     * 为响应码404提供响应头信息
     * @return
     */
    public static String getHttpHeader404(){
        String str404="<h1>404 not found</h1>";
        return "HTTP/1.1 404 Not Found \n"+
                "Content-Type: text/html \n"+
                "Content-Length: "+str404.getBytes().length+"\n"+
                "\r\n"+str404; //需要回车换行来间隔 头,体
    }
}

修改Bootstarp

package server;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * minicat主类
 */
public class Bootstrap {
    /**
     * socket监听的端口号
     */
    private int port=8080;
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * minicat启动需要初始化展开的一些操作
     */
    public void start() throws IOException {
        //todo minicat v1.0,http://localhost:8080,返回固定字符串到页面
        ServerSocket serverSocket=new ServerSocket(port);
        System.out.println("minicat start on port: "+port);
        while (true){
            //阻塞监听,直到连接
            Socket socket=serverSocket.accept();
            //有了socket,接收到请求
            OutputStream outputStream=socket.getOutputStream();
            //响应信息
            String data="Hello Mincat";
            String responseText=
                    HttpProtocolUtil.getHttpHeader200(data.getBytes().length)+data;
            outputStream.write(responseText.getBytes());
            socket.close();
        }
    }
    /**
     * mincat 程序启动入口
     * @param args
     */
    public static void main(String[] args) {
        Bootstrap bootstrap=new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


v2.0

获取请求信息

/**
 * minicat主类
 */
public class Bootstrap {
    /**
     * socket监听的端口号
     */
    private int port=8080;
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * minicat启动需要初始化展开的一些操作
     */
    public void start() throws IOException {
        /*
         * minicat v1.0,http://localhost:8080,返回固定字符串到页面
        */
        ServerSocket serverSocket=new ServerSocket(port);
        System.out.println("minicat start on port: "+port); 
        /*
         * 完成Minicat 2.0版本
         * 需求: 封装Request和Response对象,返回html静态资源文件
         */
        while (true){
            Socket socket=serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            //从输入流中获取请求信息
            //使用bio模式
            //获取输入流的数据长度
            int count=0;
            while (count==0){
                count=inputStream.available();
            }
            byte[] bytes=new byte[count];
            inputStream.read(bytes);
            System.out.println("请求信息: "+new String(bytes));
            socket.close();
        }
    }
    /**
     * mincat 程序启动入口
     * @param args
     */
    public static void main(String[] args) {
        Bootstrap bootstrap=new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

封装request

/**
 * 把请求信息封装为Request对象(根据InputStream输入流封装)
 */
public class Request {
    private String method;//请求方式,GET/POST...
    private String url;//比如,/ /user
    private InputStream inputStream;//输入流,其他属性从输入流解析出来

    public Request() {
    }
    //传入输入流,构造
    public Request(InputStream inputStream) throws IOException {
        this.inputStream = inputStream;
        //从输入流中获取请求信息
        //使用bio模式
        //获取输入流的数据长度
        int count=0;
        while (count==0){
            count=inputStream.available();
        }
        byte[] bytes=new byte[count];
        inputStream.read(bytes);
        String inputStr=new String(bytes);
        //获取第一行请求头信息
        String firstLine=inputStr.split("\\n")[0];//GET / HTTP/1.1
        String[] s = firstLine.split(" ");
        this.method=s[0];
        this.url=s[1];
        System.out.println("请求方式: "+method);
        System.out.println("请求地址: "+url);
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }
}

封装response

/**
 * 封装Response对象,需要依赖于outputStream
 *
 * 改对象提供核心方法,输出(静态资源: html)
 */
public class Response {
    private OutputStream outputStream;

    public Response(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public Response() {
    }

    public void output(String content)throws Exception{
        outputStream.write(content.getBytes());
    }

    /**
     * 根据请求路径获取静态资源绝对路径,然后返回静态资源
     * @param path 请求路径  / 指向 classes目录(编译后生成)
     * @throws Exception
     */
    public void outputHtml(String path)throws Exception{
        //获取静态资源文件的绝对路径
        String absoluteResourcePath=StaticResourceUtil.getAbsolutePath(path);
        //输出静态资源文件
        File file=new File(absoluteResourcePath);
        //是否存在
        if(file.exists()&&file.isFile()){
            //读取静态资源,返回静态资源
            StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
        }else{
            //输出404
            output(HttpProtocolUtil.getHttpHeader404());
        }
    }
}

StaticResourceUtil

public class StaticResourceUtil {
    /**
     * 获取静态资源文件绝对路径
     * @param path
     * @return
     */
    public static String getAbsolutePath(String path){
        //resources
        String absolutPath=StaticResourceUtil.class.getResource("/").getPath();
        return absolutPath.replace("\\\\","/")+path;
    }

    /**
     * 读取静态文件输入流,通过输出流输出
     * @param fileInputStream 文件输入流
     * @param outputStream 输出流
     */
    public static void outputStaticResource(FileInputStream fileInputStream,
                                            OutputStream outputStream) throws Exception {
        int count=0;
        while (count==0){
            count= fileInputStream.available();;
        }
        //静态资源输入流长度
        int resourceSize=count;
        //输出http请求头,然后再输出具体内容
        outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
        //读取内容输出
        long written=0;//已经读取的内容长度
        int byteSize=1024;//计划缓冲的长度
        byte[] bytes=new byte[byteSize];
        while (written<resourceSize){
            //判断是否可以一次性写完(剩余文件大小<1024),则按剩余文件大小来处理
            if(written+byteSize>resourceSize){
                byteSize= (int) (resourceSize-written);//剩余的文件长度
                bytes=new byte[byteSize];
            }
            fileInputStream.read(bytes);
            outputStream.write(bytes);
            outputStream.flush();//刷新
            written+=byteSize;//更新已读取长度
        }
    }
}

bootstrap

/**
 * minicat主类
 */
public class Bootstrap {
    /**
     * socket监听的端口号
     */
    private int port=8080;
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * minicat启动需要初始化展开的一些操作
     */
    public void start() throws Exception {
        /*
         * minicat v1.0,http://localhost:8080,返回固定字符串到页面
        */
        ServerSocket serverSocket=new ServerSocket(port);
        System.out.println("minicat start on port: "+port); 
        /*
         * 完成Minicat 2.0版本
         * 需求: 封装Request和Response对象,返回html静态资源文件
         */
        while (true){
            Socket socket=serverSocket.accept();
            InputStream inputStream=socket.getInputStream();
            //封装request和response对象
            Request request=new Request(inputStream);
            Response response=new Response(socket.getOutputStream());
            response.outputHtml(request.getUrl());
            socket.close();
        }
    }
    /**
     * mincat 程序启动入口
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Bootstrap bootstrap=new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

v3.0

servlet

public interface Servlet {
    void init() throws Exception;
    void destory() throws Exception;
    void service(Request request,Response response) throws Exception;
}

HttpServlet

/**
 * 定义servlet规范
 */
public abstract class HttpServlet implements Servlet{
    public abstract void doGet(Request request,Response response);
    public abstract void doPost(Request request,Response response);
    @Override
    public void service(Request request, Response response) throws Exception {
        if("GET".equalsIgnoreCase(request.getMethod())){
            doGet(request,response);
        }
        if("POST".equalsIgnoreCase(request.getMethod())){
            doPost(request,response);
        }
    }
}

MiniServlet

public class MiniServlet extends HttpServlet{

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destory() throws Exception {

    }

    @Override
    public void doPost(Request request, Response response) {
        String content="<h1>servlet post</h1>";
        try {
            response.output(
                    HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doGet(Request request, Response response) {
        String content="<h1>servlet get</h1>";
        try {
            response.output(
                    HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>malguy</servlet-name>
        <!--        <servlet-class>server.MiniServlet</servlet-class>-->
        <servlet-class>org.malred.v3.server.MiniServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>malguy</servlet-name>
        <url-pattern>/malguy</url-pattern>
    </servlet-mapping>
</web-app>

<dependencies>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
</dependencies>

修改bootstrap

/**
 * minicat主类
 */
public class Bootstrap {
    /**
     * socket监听的端口号
     */
    private int port=8080;
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * minicat启动需要初始化展开的一些操作
     */
    public void start() throws Exception {
        //加载解析配置文件,web.xml
        loadServlet();
        /*
         * minicat v1.0,http://localhost:8080,返回固定字符串到页面
        */
        ServerSocket serverSocket=new ServerSocket(port);
        System.out.println("minicat start on port: "+port); 
        /**
         * Minicat3.0版本
         * 需求: 可以请求动态资源(servlet)
         */
        while (true){
            Socket socket=serverSocket.accept();
            InputStream inputStream=socket.getInputStream();
            //封装request和response对象
            Request request=new Request(inputStream);
            Response response=new Response(socket.getOutputStream());
            //根据request的url到map里找是否有对应的静态网页
            if(servletMap.get(request.getUrl())==null){
                //没在map里,表示是静态资源请求
                response.outputHtml(request.getUrl());
            }else {
                //在map里,动态资源servlet请求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request,response);
            }
            response.outputHtml(request.getUrl());
            socket.close();
        }
    }

    private Map<String,HttpServlet> servletMap=new HashMap<>();
    /**
     * 加载解析web.xml,初始化servlet
     */
    private void loadServlet() throws Exception{
        //加载配置文件
        InputStream resourceAsStream =
                this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader=new SAXReader();
        try {
            Document document=saxReader.read(resourceAsStream);
            //获取根元素
            Element rootElement = document.getRootElement();
            //获取servlet标签list
            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element=selectNodes.get(i);
                //<servlet-name>malguy</servlet-name>
                Element servletNameEle = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletNameEle.getStringValue();
                //<servlet-class>server.MiniServlet</servlet-class>
                Element servletClassEle = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletClassEle.getStringValue();
                //根据servlet-name找到对应的mapping,找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode
                        ("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                //url-pattern
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * mincat 程序启动入口
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Bootstrap bootstrap=new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

通过web.xml指定url对应的servlet,然后路由到该servlet处理请求

测试的时候不要开代理

多线程改造(无线程池)

RequestProcessor

public class RequestProcessor extends Thread{
    private Socket socket;
    private Map<String,HttpServlet> servletMap;

    public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
        this.socket=socket;
        this.servletMap=servletMap;
    }

    @Override
    public void run() {
        try {
            InputStream inputStream=socket.getInputStream();
            //封装request和response对象
            Request request=new Request(inputStream);
            Response response=new Response(socket.getOutputStream());
            //根据request的url到map里找是否有对应的静态网页
            if(servletMap.get(request.getUrl())==null){
                //没在map里,表示是静态资源请求
                response.outputHtml(request.getUrl());
            }else {
                //在map里,动态资源servlet请求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request,response);
            }
            response.outputHtml(request.getUrl());
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

bootstrap

/**
 * minicat主类
 */
public class Bootstrap {
    /**
     * socket监听的端口号
     */
    private int port=8080;
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * minicat启动需要初始化展开的一些操作
     */
    public void start() throws Exception {
        //加载解析配置文件,web.xml
        loadServlet();
        /*
         * minicat v1.0,http://localhost:8080,返回固定字符串到页面
        */
        ServerSocket serverSocket=new ServerSocket(port);
        System.out.println("minicat start on port: "+port);
        /**
         * Minicat4.0版本
         * 需求: 多线程改造
         */
        while (true){
            Socket socket=serverSocket.accept();
            RequestProcessor requestProcessor =new RequestProcessor(socket,servletMap);
            requestProcessor.start();
        }
    }

    private Map<String,HttpServlet> servletMap=new HashMap<>();
    /**
     * 加载解析web.xml,初始化servlet
     */
    private void loadServlet() throws Exception{
        //加载配置文件
        InputStream resourceAsStream =
                this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader=new SAXReader();
        try {
            Document document=saxReader.read(resourceAsStream);
            //获取根元素
            Element rootElement = document.getRootElement();
            //获取servlet标签list
            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element=selectNodes.get(i);
                //<servlet-name>malguy</servlet-name>
                Element servletNameEle = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletNameEle.getStringValue();
                //<servlet-class>server.MiniServlet</servlet-class>
                Element servletClassEle = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletClassEle.getStringValue();
                //根据servlet-name找到对应的mapping,找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode
                        ("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                //url-pattern
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * mincat 程序启动入口
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Bootstrap bootstrap=new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

多线程改造(线程池)

/**
 * minicat主类
 */
public class Bootstrap {
    /**
     * socket监听的端口号
     */
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    /**
     * minicat启动需要初始化展开的一些操作
     */
    public void start() throws Exception {
        //加载解析配置文件,web.xml
        loadServlet();
        int corePoolSize = 10;//初始线程池大小
        int maximumPoolSize = 50;//最大线程数
        long keepAliveTime = 100L;//最大保留时间
        TimeUnit unit = TimeUnit.SECONDS;//时间单位
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(50);//请求队列
        ThreadFactory threadFactory = Executors.defaultThreadFactory();//线程工厂
        RejectedExecutionHandler handle = new ThreadPoolExecutor.AbortPolicy();//拒绝策略
        //定义线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handle
        );
        /*
         * minicat v1.0,http://localhost:8080,返回固定字符串到页面
         */
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("minicat start on port: " + port);
        /**
         * Minicat5.0版本
         * 需求: 线程池
         */
        while (true) {
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
//            requestProcessor.start();
            threadPoolExecutor.execute(requestProcessor);
        }
    }

    private Map<String, HttpServlet> servletMap = new HashMap<>();

    /**
     * 加载解析web.xml,初始化servlet
     */
    private void loadServlet() throws Exception {
        //加载配置文件
        InputStream resourceAsStream =
                this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            //获取根元素
            Element rootElement = document.getRootElement();
            //获取servlet标签list
            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element = selectNodes.get(i);
                //<servlet-name>malguy</servlet-name>
                Element servletNameEle = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletNameEle.getStringValue();
                //<servlet-class>server.MiniServlet</servlet-class>
                Element servletClassEle = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletClassEle.getStringValue();
                //根据servlet-name找到对应的mapping,找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode
                        ("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                //url-pattern
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * mincat 程序启动入口
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  转载请注明: malred-blog java高薪训练营

  目录