手寫開源ORM框架介紹

超人小冰發表於2020-12-17

手寫開源ORM框架介紹

簡介

前段時間利用空閒時間,參照mybatis的基本思路手寫了一個ORM框架。一直沒有時間去補充相應的文件,現在正好抽時間去整理下。通過思路歷程和程式碼註釋,一方面重溫下知識,另一方面準備後期去完善這個框架。

傳統JDBC連線

參照傳統的JDBC連線資料庫過程如下,框架所做的事情就是把這些步驟進行封裝。

// 1. 註冊 JDBC 驅動
Class.forName(JDBC_DRIVER);
// 2. 開啟連結
conn = DriverManager.getConnection(DB_URL,USER,PASS);
// 3. 例項化statement
stmt = conn.prepareStatement(sql);
// 4. 填充資料
stmt.setString(1,id);
// 5. 執行Sql連線
ResultSet rs = stmt.executeQuery();
// 6. 結果查詢
while(rs.next()){
    // 7. 通過欄位檢索
    int id  = rs.getInt("id");
    String name = rs.getString("name");
    String url = rs.getString("url");
}

思路歷程

上述是執行Java連線資料庫通用語句,ORM框架就是物件關係對映框架。我們需要做的重點是步驟4和7,將物件欄位填充至資料庫,將資料庫的內容取出作為物件返回。瞭解了基本的方法後,開始動手寫一個框架了。

資料庫驅動

開始,需要構建一個驅動註冊類,這個類主要用來對資料庫驅動進行構造。詳見程式碼塊1

程式碼塊1

資料庫驅動類(com.simple.ibatis.dirver)

/**
 * @Author  xiabing
 * @Desc    驅動註冊中心,對資料庫驅動進行註冊的地方
 **/
public class DriverRegister {

    /*
     * mysql的driver類
     * */
    private static final String MYSQLDRIVER = "com.mysql.jdbc.Driver";

    /*
     * 構建driver快取,儲存已經註冊了的driver的型別
     * */
    private static final Map<String,Driver> registerDrivers = new ConcurrentHashMap<>();

    /*
    *   初始化,此處是將DriverManager中已經註冊了驅動放入自己快取中。當然,你也可以在這個方法內註冊
        常見的資料庫驅動,這樣後續就可以直接使用,不用自己手動註冊了。
    * */
    static {
        Enumeration<Driver> driverEnumeration = DriverManager.getDrivers();
        while (driverEnumeration.hasMoreElements()){
            Driver driver = driverEnumeration.nextElement();
            registerDrivers.put(driver.getClass().getName(),driver);
        }
    }

    /*
     *  載入mysql驅動,此個方法可以寫在靜態程式碼塊內,代表專案啟動時即註冊mysql驅動
     * */
    public void loadMySql(){
       if(! registerDrivers.containsKey(MYSQLDRIVER)){
           loadDriver(MYSQLDRIVER);
       }
    }

    /*
     *  載入資料庫驅動通用方法,並註冊到registerDrivers快取中,此處是註冊資料庫驅動的方法
     * */
    public void loadDriver(String driverName){

        Class<?> driverType;
        try {
            // 註冊驅動,返回驅動類
            driverType = Class.forName(driverName);
            // 將驅動例項放入驅動快取中
            registerDrivers.put(driverType.getName(),(Driver)driverType.newInstance());
        }catch (ClassNotFoundException e){
            throw new RuntimeException(e);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

驅動註冊後,需要建立資料庫連線。但是框架如果一個請求建一個連線,那木勢必耗費大量的資源。所以該框架引入池化的概念,對連線進行池化管理。詳情見程式碼2

程式碼塊2

資料來源類(com.simple.ibatis.datasource)

PoolConnection

/**
 * @Author  xiabing
 * @Desc    連線代理類,是對Connection的一個封裝。除了提供基本的連線外,還想記錄
            這個連線的連線時間,因為有的連線如果一直連線不釋放,那木我可以通過檢視
            這個連線已連線的時間,如果超時了,我可以主動釋放。
 **/
public class PoolConnection {
    
    // 真實的資料庫連線
    public Connection connection;
    
    // 資料開始連線時間
    private Long CheckOutTime;
    
    // 連線的hashCode
    private int hashCode = 0;

    public PoolConnection(Connection connection) {
        this.connection = connection;
        this.hashCode = connection.hashCode();
    }

    public Long getCheckOutTime() {
        return CheckOutTime;
    }

    public void setCheckOutTime(Long checkOutTime) {
        CheckOutTime = checkOutTime;
    }
    
    // 判斷兩個PoolConnection物件是否相等,其實是判斷其中真實連線的hashCode是否相等
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof  PoolConnection){
            return connection.hashCode() ==
                    ((PoolConnection) obj).connection.hashCode();
        }else if(obj instanceof Connection){
            return obj.hashCode() == hashCode;
        }else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return hashCode;
    }
}

NormalDataSource

/**
 * @Author  xiabing
 * @Desc    普通資料來源,這個資料來源是用來產生資料庫連線的。來一個請求就會建立一個資料庫連線,沒有池化的概念
 **/
public class NormalDataSource implements DataSource{
    
    // 驅動名稱
    private String driverClassName;
    
    // 資料庫連線URL
    private String url;
    
    // 資料庫使用者名稱
    private String userName;
    
    // 資料庫密碼
    private String passWord;

    // 驅動註冊中心
    private DriverRegister driverRegister = new DriverRegister();

    public NormalDataSource(String driverClassName, String url, String userName, String passWord) {
        // 初始化時將驅動進行註冊
        this.driverRegister.loadDriver(driverClassName);
        this.driverClassName = driverClassName;
        this.url = url;
        this.userName = userName;
        this.passWord = passWord;
    }
    
    // 獲取資料庫連線
    @Override
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url,userName,passWord);

    }
    
    // 移除資料庫連線,此方法沒有真正移除,只是將連線中未提交的事務進行回滾操作。
    public void removeConnection(Connection connection) throws SQLException{
        if(!connection.getAutoCommit()){
            connection.rollback();
        }
    }
    
    // 獲取資料庫連線
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return DriverManager.getConnection(url,username,password);
    }
    
    // 後續方法因為沒有用到,所有沒有進行重寫了
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

}

PoolDataSource

/**
 * @Author  xiabing
 * @Desc    池化執行緒池,對連線進行管理
 **/
public class PoolDataSource implements DataSource{

    private Integer maxActiveConnectCount = 10; // 最大活躍執行緒數,此處可根據實際情況自行設定

    private Integer maxIdleConnectCount = 10; // 最大空閒執行緒數,此處可根據實際情況自行設定

    private Long maxConnectTime = 30*1000L; // 連線最長使用時間,在自定義連線中配置了連線開始時間,在這裡來判斷該連線是否超時

    private Integer waitTime = 2000; // 執行緒wait等待時間

    private NormalDataSource normalDataSource; // 使用normalDataSource來產生連線

    private Queue<PoolConnection> activeConList = new LinkedList<>(); // 存放活躍連線的佇列

    private Queue<PoolConnection> idleConList = new LinkedList<>(); // 存放空閒連線的佇列

    public PoolDataSource(String driverClassName, String url, String userName, String passWord) {

        this(driverClassName,url,userName,passWord,10,10);

    }

    public PoolDataSource(String driverClassName, String url, String userName, String passWord,Integer maxActiveConnectCount,Integer maxIdleConnectCount) {
        // 初始化normalDataSource,因為normalDataSource已經封裝了新建連線的方法
        this.normalDataSource = new NormalDataSource(driverClassName,url,userName,passWord);
        this.maxActiveConnectCount = maxActiveConnectCount;
        this.maxIdleConnectCount = maxIdleConnectCount;

    }
    /**
     * @Desc 獲取連線時先從空閒連線列表中獲取。若沒有,則判斷現在活躍連線是否已超過設定的最大活躍連線數,沒超過,new一個
     *        若超過,則判斷第一個連線是否已超時,若超時,則移除掉在新建。若未超時,則wait()等待。
     **/
    @Override
    public Connection getConnection(){
        Connection connection = null;
        try {
            connection =  doGetPoolConnection().connection;
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
        return connection;
    }

    public void removeConnection(Connection connection){
        PoolConnection poolConnection = new PoolConnection(connection);
        doRemovePoolConnection(poolConnection);
    }
    
    private PoolConnection doGetPoolConnection() throws SQLException{
        PoolConnection connection = null;
        while (connection == null){
            // 加鎖
            synchronized (this){
                // 判斷是否有空閒連線
                if(idleConList.size() < 1){ 
                    // 判斷活躍連線數是否已經超過預設的最大活躍連線數
                    if(activeConList.size() < maxActiveConnectCount){
                        // 如果還可以新建連線,就新建一個連線
                        connection = new PoolConnection(normalDataSource.getConnection());
                    }else {
                        // 走到這一步,說明沒有空閒連線,並且活躍連線已經滿了,則只能看哪些連線超時,判斷第一個連線是否超時
                        PoolConnection poolConnection = activeConList.peek();
                        // 這就是我為啥要給資料庫連線加連線時間了
                        if(System.currentTimeMillis() - poolConnection.getCheckOutTime() > maxConnectTime){
                            // 若第一個連線已經超時了,移除第一個活躍連線
                            PoolConnection timeOutConnect = activeConList.poll();
                            if(!timeOutConnect.connection.getAutoCommit()){
                                // 如果該連線設的是非自動提交,則對事物進行回滾操作
                                timeOutConnect.connection.rollback();
                            }
                            // 置為空,讓垃圾收集器去收集
                            timeOutConnect.connection.close();
                            timeOutConnect = null;
                            // 新建一個連線
                            connection = new PoolConnection(normalDataSource.getConnection());
                        }else {
                            // 走到這一步,代表所有連線都沒有超時,只能等了
                            try{
                                // 於是等一會
                                this.wait(waitTime);
                            }catch (InterruptedException e){
                                // ignore錯誤,並退出
                                break;  
                            }
                        }
                    }
                }else {
                    // 這一步代表空閒連線佇列裡面有連線,則直接取出
                    connection = idleConList.poll();
                }
                if(connection != null){
                    // 設定這個連線的連線時間
                    connection.setCheckOutTime(System.currentTimeMillis());
                    // 然後放入活躍連線佇列中
                    activeConList.add(connection);
                }
            }
        }

        return connection;
    }
    
    // 移除連線
    private void doRemovePoolConnection(PoolConnection connection){
        // 加鎖
        synchronized (this){
            // 移除連線,這裡需要重寫PoolConnection的equal方法,因為只要真實連線相同,就可以代表這個封裝的連線相等了
            activeConList.remove(connection);
            if(idleConList.size() < maxIdleConnectCount){
                // 加入空閒連線佇列中
                idleConList.add(connection);
            }
            // 通知其他等待執行緒,讓他們繼續獲取連線
            this.notifyAll();
        }
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return getConnection();
    }
    
    // 後面的方法沒有用到就沒有重寫了
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

使用資料庫連線池獲取連線後,接下來就是關鍵了,生成SQL預處理語句,並進行資料填充。這讓我想到了mybatis使用方法。

@Select("select * from users")
List<User> getUsers();

上面是mybatis基於註解的配置方式。除了註解,還支援xml。於是框架準備先使用基於註解的方式進行程式碼配置。首先,需要知道這些介面類的類名。於是提供一個配置,根據指定包名獲取包下所有類的類名詳情見程式碼3

程式碼塊3

方法類(com.simple.ibatis.util)

PackageUtil

/**
 * @Author  xiabing
 * @Desc    獲取指定包下所有類的名稱
 **/
public class PackageUtil {

    private static final String CLASS_SUFFIX = ".class";
    private static final String CLASS_FILE_PREFIX = "classes"  + File.separator;
    private static final String PACKAGE_SEPARATOR = ".";

    /**
     * 查詢包下的所有類的名字
     * @param packageName
     * @param showChildPackageFlag 是否需要顯示子包內容
     * @return List集合,內容為類的全名
     */
    public static List<String> getClazzName(String packageName, boolean showChildPackageFlag){
        List<String> classNames = new ArrayList<>();
        String suffixPath = packageName.replaceAll("\\.", "/");
        // 獲取類載入器
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        try{
            Enumeration<URL> urls = loader.getResources(suffixPath);
            while(urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if(url != null){
                    if ("file".equals(url.getProtocol())) {
                        String path = url.getPath();
                        classNames.addAll(getAllClassName(new File(path),showChildPackageFlag));
                    }
                }
            }
        }catch (IOException e){
            throw new RuntimeException("load resource is error , resource is "+packageName);
        }
        return classNames;
    }

    
    // 獲取所有類的名稱
    private static List<String> getAllClassName(File file,boolean flag){

        List<String> classNames = new ArrayList<>();

        if(!file.exists()){
            return classNames;
        }
        if(file.isFile()){
            String path = file.getPath();
            if(path.endsWith(CLASS_SUFFIX)){
                path = path.replace(CLASS_SUFFIX,"");
                String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length()).replace(File.separator, PACKAGE_SEPARATOR);
                classNames.add(clazzName);
            }
        }else {
            File[] listFiles = file.listFiles();
            if(listFiles != null && listFiles.length > 0){
                for (File f : listFiles){
                    if(flag) {
                        classNames.addAll(getAllClassName(f, flag));
                    }else {
                        if(f.isFile()){
                            String path = f.getPath();
                            if(path.endsWith(CLASS_SUFFIX)) {
                                path = path.replace(CLASS_SUFFIX, "");
                                String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length()).replace(File.separator,PACKAGE_SEPARATOR);
                                classNames.add(clazzName);
                            }
                        }
                    }
                }
            }
        }
        return classNames;
    }
}

在知道介面類存放在哪些類後,我需要解析這些類中的各個方法,包括每個方法的註解,入參,和返回值。同時,我需要使用一個全域性配置類,用於存放我解析後生成的物件,並且一些資料來源配置資訊也可以放在這個全域性類中。詳情見程式碼4

程式碼塊4

核心類(com.simple.ibatis.core)

Config

/**
 * @Author  xiabing
 * @Desc    框架的核心配置類
 **/
public class Config {

    // 資料來源
    private PoolDataSource dataSource;

    // mapper包地址(即待解析的包名),後續改為List<String>,能同時載入多個mapper包 todo
    private String daoSource;

    // mapper核心檔案(存放類解析後的物件)
    private MapperCore mapperCore;

    // 是否啟用事務
    private boolean openTransaction;

    // 是否開啟快取
    private boolean openCache;

    public Config(String mapperSource,PoolDataSource dataSource){
        this.dataSource = dataSource;
        this.daoSource = mapperSource;
        this.mapperCore = new MapperCore(this);
    }
    
    // 生成SQL語句執行器
    public SimpleExecutor getExecutor(){
        return new SimpleExecutor(this,this.getDataSource(),openTransaction,openCache);
    }
    
    // 後文都是基本的get,set方法
    public PoolDataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(PoolDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public String getDaoSource() {
        return daoSource;
    }

    public void setDaoSource(String daoSource) {
        this.daoSource = daoSource;
    }

    public MapperCore getMapperCore() {
        return mapperCore;
    }

    public void setMapperCore(MapperCore mapperCore) {
        this.mapperCore = mapperCore;
    }

    public boolean isOpenTransaction() {
        return openTransaction;
    }

    public void setOpenTransaction(boolean openTransaction) {
        this.openTransaction = openTransaction;
    }

    public boolean isOpenCache() {
        return openCache;
    }

    public void setOpenCache(boolean openCache) {
        this.openCache = openCache;
    }

}

SqlSource

/**
 * @Author  xiabing5
 * @Desc    將sql語句拆分為sql部分和引數部分,因為我們需要使用java物件對資料庫預處理物件進行注入。具體看下面例子
 
 * @example select * from users where id = {user.id} and name = {user.name}
 *            -> sql = select * from users where id = ? and name = ?
 *            -> paramResult = {user.id,user.name}
 **/
public class SqlSource {
    /**sql語句,待輸入欄位替換成?*/
    private String sql;
    /**待輸入欄位*/
    private List<String> param;
    /**select update insert delete*/
    private Integer executeType;

    public SqlSource(String sql){
        this.param = new ArrayList<>();
        this.sql = sqlInject(this.param,sql);
    }
    
    /**sql注入,對要注入的屬性,必須使用{} 包裹,並替換為? 見例子 */
    private String sqlInject(List<String> paramResult, String sql){

        String labelPrefix = "{";

        String labelSuffix = "}";
        // 將語句的所有的{}全部都解析成?,並將{xxx}中的xxx提取出來。
        while (sql.indexOf(labelPrefix) > 0 && sql.indexOf(labelSuffix) > 0){
            String sqlParamName = sql.substring(sql.indexOf(labelPrefix),sql.indexOf(labelSuffix)+1);
            sql = sql.replace(sqlParamName,"?");
            paramResult.add(sqlParamName.replace("{","").replace("}",""));
        }
        return sql;
    }

    public String getSql() {
        return sql;
    }

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

    public List<String> getParam() {
        return param;
    }

    public void setParam(List<String> param) {
        this.param = param;
    }

    public Integer getExecuteType() {
        return executeType;
    }

    public void setExecuteType(Integer executeType) {
        this.executeType = executeType;
    }
}

MapperCore

/**
 * @Author  xiabing5
 * @Desc    mapper方法解析核心類,此處是將介面中的方法進行解析的關鍵類
 **/
public class MapperCore {
    
    // select方法
    private static final Integer SELECT_TYPE = 1;
    // update方法
    private static final Integer UPDATE_TYPE = 2;
    // delete方法
    private static final Integer DELETE_TYPE = 3;
    // insert方法
    private static final Integer INSERT_TYPE = 4;

    /**mapper檔案解析類快取,解析後都放在裡面,避免重複對一個mapper檔案進行解析*/
    private static Map<String,MethodDetails> cacheMethodDetails = new ConcurrentHashMap<>();

    /**
     * 全域性配置
     * */
    private Config config;


    public MapperCore(Config config){
        this.config = config;
        load(config.getDaoSource());
    }

    /*
     * 載入,解析指定包名下的類
     * */
    private void load(String source){
        /**載入mapper包下的檔案*/
        List<String> clazzNames = PackageUtil.getClazzName(source,true);
        try{
            for(String clazz: clazzNames){
                Class<?> nowClazz = java.lang.Class.forName(clazz);
                // 不是介面跳過,只能解析介面
                if(!nowClazz.isInterface()){
                    continue;
                }

                /**介面上沒有@Dao跳過。我們對介面類上加了@Dao後,就代表要解析*/
                boolean skip = false;
                Annotation[] annotations = nowClazz.getDeclaredAnnotations();
                for(Annotation annotation:annotations){
                    // 該介面中的註釋中是否有帶@Dao
                    if(annotation instanceof Dao) {
                        skip = true;
                        break;
                    }
                }
                if(!skip){
                    continue;
                }
                // 呼叫反射介面,獲取所有介面中的方法
                Method[] methods = nowClazz.getDeclaredMethods();
                for( Method method : methods){
                    // 解析方法詳情
                    MethodDetails methodDetails = handleParameter(method);
                    // 解析@SELECT()等註解中的SQL內容
                    methodDetails.setSqlSource(handleAnnotation(method));
                    // 解析完成後放入快取
                    cacheMethodDetails.put(generateStatementId(method),methodDetails);
                }
            }
        }catch (ClassNotFoundException e){
            throw new RuntimeException(" class load error,class is not exist");
        }
    }

    /**
     * 獲得方法詳情,從方法快取池中獲取,因為每個方法事先已經完成解析
     * */
    public MethodDetails getMethodDetails(Method method){
        String statementId = generateStatementId(method);
        if(cacheMethodDetails.containsKey(statementId)){
            return cacheMethodDetails.get(statementId);
        }
        return new MethodDetails();
    }

    /**
     * 獲得方法對應的sql語句
     * */
    public SqlSource getStatement(Method method){
        String statementId = generateStatementId(method);
        if(cacheMethodDetails.containsKey(statementId)){
            return cacheMethodDetails.get(statementId).getSqlSource();
        }
        throw new RuntimeException(method + " is not sql");
    }

    /**
     * 獲得方法對應的引數名
     * */
    public List<String> getParameterName(Method method){
        String statementId = generateStatementId(method);
        if(cacheMethodDetails.containsKey(statementId)){
            return cacheMethodDetails.get(statementId).getParameterNames();
        }
        return new ArrayList<>();
    }

    /**
     *  獲取方法返回型別
     * */
    public Class getReturnType(Method method){
        String statementId = generateStatementId(method);
        if(cacheMethodDetails.containsKey(statementId)){
            return cacheMethodDetails.get(statementId).getReturnType();
        }
        return null;
    }

    /**
     * 獲得方法對應的引數型別
     * */
    public Class<?>[] getParameterType(Method method) {
        String statementId = generateStatementId(method);
        if(cacheMethodDetails.containsKey(statementId)){
            return cacheMethodDetails.get(statementId).getParameterTypes();
        }
        return new Class<?>[]{};
    }

    /**
     * 獲得方法是SELECT UPDATE DELETE INSERT
     * */
    public Integer getMethodType(Method method){
       String statementId = generateStatementId(method);
        if(cacheMethodDetails.containsKey(statementId)){
            return cacheMethodDetails.get(statementId).getSqlSource().getExecuteType();
        }
        return null;
    }


    /**
     * 獲得方法是否返回集合型別list
     * */
    public boolean getHasSet(Method method){
        String statementId = generateStatementId(method);
        if(cacheMethodDetails.containsKey(statementId)){
            return cacheMethodDetails.get(statementId).isHasSet();
        }
        return false;
    }

    /**
     * 解析方法內的註解
     * */
    private MethodDetails handleParameter(Method method){
        
        MethodDetails methodDetails = new MethodDetails();
        
        // 獲取方法輸入引數數量
        int parameterCount = method.getParameterCount();
        
        // 獲取方法輸入各引數型別
        Class<?>[] parameterTypes = method.getParameterTypes();
        
        // 獲取方法輸入各引數名稱
        List<String> parameterNames = new ArrayList<>();

        Parameter[] params = method.getParameters();
        for(Parameter parameter:params){
            parameterNames.add(parameter.getName());
        }

        /*
         * 獲得方法引數的註解值替代預設值,如果使用了@Param註解,則預設使用註解中的值作為引數名
         * */
        for(int i = 0; i < parameterCount; i++){
            parameterNames.set(i,getParamNameFromAnnotation(method,i,parameterNames.get(i)));
        }

        methodDetails.setParameterTypes(parameterTypes);
        methodDetails.setParameterNames(parameterNames);

        /** 獲取方法返回型別*/
        Type methodReturnType = method.getGenericReturnType();
        Class<?> methodReturnClass = method.getReturnType();
        if(methodReturnType instanceof ParameterizedType){
            /** 返回是集合類 目前僅支援List  todo*/
            if(!List.class.equals(methodReturnClass)){
                throw new RuntimeException("now ibatis only support list");
            }
            Type type = ((ParameterizedType) methodReturnType).getActualTypeArguments()[0];
            /** 設定 返回集合中的物件型別 */
            methodDetails.setReturnType((Class<?>) type);   
            /** 標註該方法返回型別是集合型別 */
            methodDetails.setHasSet(true);
        }else {
            /** 不是集合型別,就直接設定返回型別 */
            methodDetails.setReturnType(methodReturnClass);
            /** 標註該方法返回型別不是集合型別 */
            methodDetails.setHasSet(false);
        }

        return methodDetails;
    }

    /**
     * 解析@select,@update等註解,獲取SQL語句,並封裝成物件
     * */
    private SqlSource handleAnnotation(Method method){
        SqlSource sqlSource = null;
        String sql = null;
        /** 獲取方法上所有註解 */
        Annotation[]  annotations = method.getDeclaredAnnotations();
        for(Annotation annotation : annotations){
            /** 如果有@select註解 */
            if(Select.class.isInstance(annotation)){
                Select selectAnnotation = (Select)annotation;
                sql = selectAnnotation.value();
                /** 語句封裝成sqlSource物件 */
                sqlSource = new SqlSource(sql);
                /** 設定執行語句型別 */
                sqlSource.setExecuteType(SELECT_TYPE);
                break;
            }else if(Update.class.isInstance(annotation)){
                Update updateAnnotation = (Update)annotation;
                sql = updateAnnotation.value();
                sqlSource = new SqlSource(sql);
                sqlSource.setExecuteType(UPDATE_TYPE);
                break;
            }else if(Delete.class.isInstance(annotation)){
                Delete deleteAnnotation = (Delete) annotation;
                sql = deleteAnnotation.value();
                sqlSource = new SqlSource(sql);
                sqlSource.setExecuteType(DELETE_TYPE);
                break;
            }else if(Insert.class.isInstance(annotation)){
                Insert insertAnnotation = (Insert) annotation;
                sql = insertAnnotation.value();
                sqlSource = new SqlSource(sql);
                sqlSource.setExecuteType(INSERT_TYPE);
                break;
            }
        }
        if(sqlSource == null){
            throw  new RuntimeException("method annotation not null");
        }
        return sqlSource;
    }

    /**
     * 獲取@Param註解內容
     * */
    private String getParamNameFromAnnotation(Method method, int i, String paramName) {
        final Object[] paramAnnos = method.getParameterAnnotations()[i];
        for (Object paramAnno : paramAnnos) {
            if (paramAnno instanceof Param) {
                paramName = ((Param) paramAnno).value();
            }
        }
        return paramName;
    }

    /**
     * 生成唯一的statementId
     * */
    private static String generateStatementId(Method method){
        return method.getDeclaringClass().getName() + "." + method.getName();
    }
    
    /**
     * 每個mapper方法的封裝類
     * */
    public static class MethodDetails{
        /**方法返回型別,若是集合,則代表集合的物件類,目前集合類僅支援返回List  */
        private Class<?> returnType;

        /**方法返回型別是否是集合*/
        private boolean HasSet;

        /**執行型別,SELECT,UPDATE,DELETE,INSERT*/
        private Integer executeType;

        /**方法輸入引數型別集合*/
        private Class<?>[] parameterTypes;

        /**方法輸入引數名集合*/
        private List<String> parameterNames;

        /**sql語句集合*/
        private SqlSource sqlSource;

        public Class<?> getReturnType() {
            return returnType;
        }

        public void setReturnType(Class<?> returnType) {
            this.returnType = returnType;
        }

        public boolean isHasSet() {
            return HasSet;
        }

        public void setHasSet(boolean hasSet) {
            HasSet = hasSet;
        }

        public Integer getExecuteType() {
            return executeType;
        }

        public void setExecuteType(Integer executeType) {
            this.executeType = executeType;
        }

        public Class<?>[] getParameterTypes() {
            return parameterTypes;
        }

        public void setParameterTypes(Class<?>[] parameterTypes) {
            this.parameterTypes = parameterTypes;
        }

        public List<String> getParameterNames() {
            return parameterNames;
        }

        public void setParameterNames(List<String> parameterNames) {
            this.parameterNames = parameterNames;
        }

        public SqlSource getSqlSource() {
            return sqlSource;
        }

        public void setSqlSource(SqlSource sqlSource) {
            this.sqlSource = sqlSource;
        }
    }
}

SQL語句已經解析完畢,後面開始執行SQL語句。在之前,我想給執行SQL語句時加點功能,如提供select快取,事務功能,詳情見程式碼塊5,程式碼塊6

程式碼塊5

快取類(com.simple.ibatis.cache)

Cache

/**
 * @author xiabing
 * @description: 自定義快取介面,主要提供增刪改查功能
 */
public interface Cache {

    /**放入快取*/
    void putCache(String key,Object val);

    /**獲取快取*/
    Object getCache(String key);

    /**清空快取*/
    void cleanCache();

    /**獲取快取健數量*/
    int getSize();

    /**移除key的快取*/
    void removeCache(String key);
}

SimpleCache

/**
 * @author xiabing
 * @description: 快取的簡單實現
 */
public class SimpleCache implements Cache{
    
    /**用HashMap來實現快取的增刪改查*/
    private static Map<String,Object> map = new HashMap<>();


    @Override
    public void putCache(String key, Object val) {
        map.put(key,val);
    }

    @Override
    public Object getCache(String key) {
        return map.get(key);
    }

    @Override
    public void cleanCache() {
        map.clear();
    }

    @Override
    public int getSize() {
        return map.size();
    }

    @Override
    public void removeCache(String key) {
        map.remove(key);
    }
}

LruCache

/**
 * @author xiabing
 * @description: 快取包裝類沒,使快取具有LRU功能
 */
public class LruCache implements Cache{
    
    /**快取大小*/
    private static Integer cacheSize = 2000;
    
    /**HashMap負載因子,可以自己指定*/
    private static Float loadFactory = 0.75F;
    
    /**真實快取*/
    private Cache trueCache;
    
    /**使用LinkedHashMap來實現Lru功能*/
    private Map<String,Object> linkedCache;
    
    /**待移除的元素*/
    private static Map.Entry removeEntry;

    public LruCache(Cache trueCache){
        this(cacheSize,loadFactory,trueCache);
    }

    public LruCache(Integer cacheSize, Float loadFactory, Cache trueCache) {
        this.trueCache = trueCache;
        /**初始化LinkedHashMap,並重寫removeEldestEntry() 方法,不瞭解的可以看我之前的部落格*/
        this.linkedCache = new LinkedHashMap<String, Object>(cacheSize,loadFactory,true){
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                // 當快取容量超過設定的容量時,返回true,並記錄待刪除的物件
                if(getSize() >  cacheSize){
                    removeEntry = eldest;
                    return true;
                }
                return false;
            }
        };
    }

    // 放入快取,當待刪除的元素不為空時,就在真實快取中刪除該元素
    @Override
    public void putCache(String key, Object val) {
        // 真實快取中放入
        this.trueCache.putCache(key,val);
        // linkedHashMap中放入,此處放入會呼叫重寫的removeEldestEntry()方法
        this.linkedCache.put(key,val);
        // 當removeEldestEntry()執行後,如果有待刪除元素,就開始執行刪除操作
        if(removeEntry != null){
            removeCache((String)removeEntry.getKey());
            removeEntry = null;
        }
    }

    @Override
    public Object getCache(String key) {
        // 因為呼叫linkedHashMap中的get()方法會對連結串列進行再一次排序。見之前關於快取的部落格
        linkedCache.get(key);
        return trueCache.getCache(key);
    }

    @Override
    public void cleanCache() {
        trueCache.cleanCache();
        linkedCache.clear();
    }

    @Override
    public int getSize() {
        return trueCache.getSize();
    }

    @Override
    public void removeCache(String key) {
        trueCache.removeCache(key);
    }
}

程式碼塊6

事務類(com.simple.ibatis.transaction)

Transaction

/**
 * @Author  xiabing
 * @Desc    封裝事務功能
 **/
public interface Transaction {

    /**獲取資料庫連線*/
    Connection getConnection() throws SQLException;
    
    /**事務提交*/
    void commit() throws SQLException;
    
    /**事務回滾*/
    void rollback() throws SQLException;
    
    /**關閉連線*/
    void close() throws SQLException;
}

SimpleTransaction

/**
 * @Author  xiabing
 * @Desc    事務的簡單實現,封裝了資料庫的事務功能
 **/
public class SimpleTransaction implements Transaction{

    private Connection connection; // 資料庫連線
    private PoolDataSource dataSource; // 資料來源
    private Integer level = Connection.TRANSACTION_REPEATABLE_READ; // 事務隔離級別
    private Boolean autoCommmit = true; // 是否自動提交

    public SimpleTransaction(PoolDataSource dataSource){
        this(dataSource,null,null);
    }

    public SimpleTransaction(PoolDataSource dataSource, Integer level, Boolean autoCommmit) {
        this.dataSource = dataSource;
        if(level != null){
            this.level = level;
        }
        if(autoCommmit != null){
            this.autoCommmit = autoCommmit;
        }
    }
    
    /**獲取資料庫連線,並設定該連線的事務隔離級別,是否自動提交*/
    @Override
    public Connection getConnection() throws SQLException{
        if(connection != null){
            return connection;
        }
        this.connection = dataSource.getConnection();

        this.connection.setAutoCommit(autoCommmit);

        this.connection.setTransactionIsolation(level);

        return this.connection;
    }

    @Override
    public void commit() throws SQLException{
        if(this.connection != null){
            this.connection.commit();
        }
    }

    @Override
    public void rollback() throws SQLException{
        if(this.connection != null){
            this.connection.rollback();
        }
    }
    
    /**關閉連線*/
    @Override
    public void close() throws SQLException{
        /**如果該連線設定自動連線為false,則在關閉前進行事務回滾一下*/
        if(autoCommmit != null && !autoCommmit){
            connection.rollback();
        }

        if(connection != null){
            dataSource.removeConnection(connection);
        }

        this.connection = null;
    }
}

TransactionFactory

/**
 * @Author  xiabing
 * @Desc    事務工廠,封裝獲取事務的功能
 **/
public class TransactionFactory {

    public static SimpleTransaction newTransaction(PoolDataSource poolDataSource, Integer level, Boolean autoCommmit){
        return new SimpleTransaction(poolDataSource,level,autoCommmit);
    }
    
}

程式碼真正執行SQL的地方,見程式碼塊7

程式碼塊7

語句執行類(com.simple.ibatis.execute)

Executor

/**
 * @Author  xiabing
 * @Desc    執行介面類,後續執行器需要實現此介面
 **/
public interface Executor {

    /**獲取Mapper介面代理類,使用了動態代理技術,見實現類分析*/
    <T> T getMapper(Class<T> type);
    
    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;
}

SimpleExecutor

/**
 * @Author  xiabing
 * @Desc    簡單執行器類,對於Sql的執行和處理都依賴此類
 **/
public class SimpleExecutor implements Executor{

    /**全域性配置類*/
    public Config config;
    
    /**mapper核心類,解析mapper介面類*/
    public MapperCore mapperCore;
    
    /**資料庫源*/
    public PoolDataSource poolDataSource;
    
    /**事務*/
    public Transaction transaction;
    
    /**快取*/
    public Cache cache;

    public SimpleExecutor(Config config,PoolDataSource poolDataSource){
        this(config,poolDataSource,false,false);
    }

    public SimpleExecutor(Config config,PoolDataSource poolDataSource,boolean openTransaction,boolean openCache){
        this.config = config;
        this.mapperCore = config.getMapperCore();
        this.poolDataSource = poolDataSource;
        if(openCache){
            // 設定快取策略為LRU
            this.cache = new LruCache(new SimpleCache());
        }
        if(openTransaction){
            // 關閉自動提交
            this.transaction = TransactionFactory.newTransaction(poolDataSource,Connection.TRANSACTION_REPEATABLE_READ,false);
        }else {
            // 開啟自動提交
            this.transaction = TransactionFactory.newTransaction(poolDataSource,null,null);
        }
    }
    
    /**
    * @Desc   獲取mapper介面的代理類
              為什麼要使用代理類,因為我們寫mapper介面類只寫了介面,卻沒有提供它的實現。於是
              使用動態代理機制對這些介面進行代理,在代理類中實現sql執行的方法。此處是參照
              mybatis的設計。
    **/
    @Override
    public <T> T getMapper(Class<T> type){
        /**MapperProxy代理類分析見詳情8.1*/
        MapperProxy mapperProxy = new MapperProxy(type,this);
        return (T)mapperProxy.initialization();
    }
    
    /*select 語句執行流程分析**/
    public <E> List<E> select(Method method,Object[] args) throws Exception{
    
        /**PreparedStatementHandle 生成器見9.1 */
        PreparedStatementHandle preparedStatementHandle = new PreparedStatementHandle(mapperCore,transaction,method,args);
        PreparedStatement preparedStatement = preparedStatementHandle.generateStatement();
        
        /**執行查詢介面,此處是真實呼叫sql語句的地方 */
        preparedStatement.executeQuery();
        ResultSet resultSet = preparedStatement.getResultSet();
        
        /**查詢方法的返回引數值,若是void型別,就不用返回任務東西 */
        Class returnClass = mapperCore.getReturnType(method);
        if(returnClass == null || void.class.equals(returnClass)){
            return null;
        }else {
            /**ResultSetHandle 結果處理器見9.2 */
            ResultSetHandle resultSetHandle = new ResultSetHandle(returnClass,resultSet);
            return resultSetHandle.handle();
        }
    }
    
    /*update 語句執行流程分析,update,delete,insert都是呼叫的這個方法**/
    public int update(Method method,Object[] args)throws SQLException{
        PreparedStatementHandle preparedStatementHandle = null;
        PreparedStatement preparedStatement = null;
        Integer count = null;
        try{
            /**PreparedStatementHandle 生成器見9.1 */
            preparedStatementHandle = new PreparedStatementHandle(mapperCore,transaction,method,args);
            preparedStatement = preparedStatementHandle.generateStatement();
            
            /**返回受影響的行數 */
            count =  preparedStatement.executeUpdate();
        }finally {
            if(preparedStatement != null){
                preparedStatement.close();
            }
        }
        return count;
    }
    
    /**後續方法直接呼叫transaction相關方法*/
    @Override
    public void commit() throws SQLException{
        transaction.commit();
    }

    @Override
    public void rollback() throws SQLException{
        transaction.rollback();
    }

    @Override
    public void close() throws SQLException{
        transaction.close();
    }
}

因為我們mapper介面類只有介面,沒有實現。但為什麼可以呼叫這些介面方法?此處參照mybatis原始碼,自己通過動態代理技術實現介面的動態代理,在代理方法裡寫我們自己執行Sql的邏輯。代理類見程式碼塊8

程式碼塊8

mapper介面代理類(com.simple.ibatis.execute)

MapperProxy

/**
 * @Author  xiabing
 * @Desc    mapper介面代理類
 **/
public class MapperProxy<T> implements InvocationHandler{
    
    /**要代理的mapper介面類*/
    private Class<T> interfaces;
    
    /**具體的執行器,執行mapper中的一個方法相當於執行一條sql語句*/
    private SimpleExecutor executor;

    public MapperProxy(Class<T> interfaces,SimpleExecutor executor) {
        this.interfaces = interfaces;
        this.executor = executor;
    }
    
    /**反射方法,該方法自定義需要代理的邏輯。不瞭解可以去看下JAVA動態代理技術*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        /**object方法直接代理*/
        if(Object.class.equals(method.getDeclaringClass())){
            result = method.invoke(this,args);
        }else {
            // 獲取方法是select還是非select方法
            Integer methodType = executor.mapperCore.getMethodType(method);

            if(methodType == null){
                throw new RuntimeException("method is normal sql method");
            }
            if(methodType == 1){
                /**呼叫執行器的select方法,返回集合類*/
                List<Object> list =  executor.select(method,args);
                result = list;
                /**檢視介面的返回是List,還是單個物件*/
                if(!executor.mapperCore.getHasSet(method)){
                    if(list.size() == 0){
                        result = null;
                    }else {
                        /**單個物件就直接取第一個*/
                        result = list.get(0);
                    }
                }

            }else{
                /**返回受影響的行數*/
                Integer count = executor.update(method,args);
                result = count;
            }
        }
        return result;
    }
    
    /**構建代理類*/
    public T initialization(){
        return (T)Proxy.newProxyInstance(interfaces.getClassLoader(),new Class[] { interfaces },this);
    }
}

程式碼塊7中執行SQL其實包含3個過程,生成SQL預處理語句,執行SQL,將SQL結果轉為JAVA物件。詳情見程式碼塊9

程式碼塊9

各生成器類(com.simple.ibatis.statement)

PreparedStatementHandle

/**
 * @Author  xiabing
 * @Desc    PreparedStatement生成器
 **/
public class PreparedStatementHandle {
    /**
     * 全域性核心mapper解析類
     */
    private MapperCore mapperCore;

    /**
     * 待執行的方法
     */
    private Method method;

    private Transaction transaction;

    private Connection connection;

    /**
     * 方法輸入引數
     */
    private Object[] args;

    public PreparedStatementHandle(MapperCore mapperCore, Transaction transaction,Method method, Object[] args)throws SQLException {
        this.mapperCore = mapperCore;
        this.method = method;
        this.transaction = transaction;
        this.args = args;
        connection = transaction.getConnection();
    }

    /**
     * @Author  xiabing5
     * @Desc    引數處理核心方法  todo
     **/
    public PreparedStatement generateStatement() throws SQLException{
        // 獲取已經解析方法的sqlSource類,已經將待注入欄位變為?,後續直接填充就好
        SqlSource sqlSource = mapperCore.getStatement(method);
        // 呼叫connection方法生成預處理語句
        PreparedStatement preparedStatement = connection.prepareStatement(sqlSource.getSql());
        
        // 獲取方法輸入引數的型別
        Class<?>[] clazzes = mapperCore.getParameterType(method);
        List<String> paramNames = mapperCore.getParameterName(method);
        List<String> params = sqlSource.getParam();
        // 詳見typeInject方法,注入SQL引數
        preparedStatement = typeInject(preparedStatement,clazzes,paramNames,params,args);
        return preparedStatement;
    }

    /**
     * @Author  xiabing
     * @Desc    preparedStatement構建,將引數注入到SQL語句中
     * @Param  preparedStatement 待構建的preparedStatement
     * @Param  clazzes 該方法中引數型別陣列
     * @Param  paramNames 該方法中引數名稱列表,若有@Param註解,則為此註解的值,預設為類名首字母小寫
     * @Param  params 待注入的引數名,如user.name或普通型別如name
     * @Param  args 真實引數值
     **/
    private PreparedStatement typeInject(PreparedStatement preparedStatement,Class<?>[] clazzes,List<String> paramNames,List<String> params,Object[] args)throws SQLException{

        for(int i = 0; i < paramNames.size(); i++){
            // 第i個引數名稱
            String paramName = paramNames.get(i);
            // 第i個引數型別
            Class type = clazzes[i];
            if(String.class.equals(type)){
                // 原始SQL中需要注入的引數名中是否有此引數名稱
                // example: select * from users where id = {id} and name = {name}  則{id}中的id和name就是如下的params裡面內容
                int injectIndex = params.indexOf(paramName);
                /**此處是判斷sql中是否有待注入的名稱({name})和方法內輸入物件名(name)相同,若相同,則直接注入*/
                if(injectIndex >= 0){
                    preparedStatement.setString(injectIndex+1,(String)args[i]);
                }
            }else if(Integer.class.equals(type) || int.class.equals(type)){
                int injectIndex = params.indexOf(paramName);
                if(injectIndex >= 0){
                    preparedStatement.setInt(injectIndex+1,(Integer)args[i]);
                }
            }else if(Float.class.equals(type) || float.class.equals(type)){
                int injectIndex = params.indexOf(paramName);
                if(injectIndex >= 0){
                    preparedStatement.setFloat(injectIndex+1,(Float)args[i]);
                }
            }else {
                /** 若待注入的是物件。example:
                 @SELECT(select * from users where id = {user.id} and name = {user.name})
                 List<User> getUser(User user)
                */
                // 物件工廠,獲取物件包裝例項,見程式碼塊10
                ObjectWrapper objectWrapper = ObjectWrapperFactory.getInstance(args[i]);
                for(int j = 0; j < params.size(); j++){
                    /**此處是判斷物件的屬性 如user.name,需要先獲取user物件,在呼叫getName方法獲取值*/
                    if((params.get(j).indexOf(paramName)) >= 0 ){
                        try{
                            String paramProperties = params.get(j).substring(params.get(j).indexOf(".")+1);
                            Object object = objectWrapper.getVal(paramProperties);
                            Class childClazz = object.getClass();
                            if(String.class.equals(childClazz)){
                                preparedStatement.setString(j+1,(String)object);
                            }else if(Integer.class.equals(childClazz) || int.class.equals(childClazz)){
                                preparedStatement.setInt(j+1,(Integer)object);
                            }else if(Float.class.equals(childClazz) || float.class.equals(childClazz)){
                                preparedStatement.setFloat(j+1,(Float)object);
                            }else {
                                /**目前不支援物件中包含物件,如dept.user.name  todo*/
                                throw new RuntimeException("now not support object contain object");
                            }
                        }catch (Exception e){
                            throw new RuntimeException(e.getMessage());
                        }
                    }
                }
            }
        }
        return preparedStatement;
    }

    public void closeConnection() throws SQLException{
        transaction.close();
    }
}

ResultSetHandle

/**
 * @Author  xiabing
 * @Desc    ResultSet結果處理器,執行之後返回的json物件,轉為java物件的過程
 **/
public class ResultSetHandle {

    /**轉換的目標型別*/
    Class<?> typeReturn;

    /**待轉換的ResultSet*/
    ResultSet resultSet;

    Boolean hasSet;

    public ResultSetHandle(Class<?> typeReturn,ResultSet resultSet){
        this.resultSet = resultSet;
        this.typeReturn = typeReturn;
    }

    /**
     * ResultSet處理方法,目前僅支援String,int,Float,不支援屬性是集合類 todo
     * */
    public <T> List<T> handle() throws Exception{

        List<T> res = new ArrayList<>(resultSet.getRow());
        Object object = null;
        ObjectWrapper objectWrapper = null;
        Set<ClazzWrapper.FiledExpand> filedExpands = null;
        // 返回型別若不是基本資料型別
        if(!TypeUtil.isBaseType(typeReturn)){
            // 生成物件
            object = generateObj(typeReturn);
            // 將物件封裝成包裝類
            objectWrapper = ObjectWrapperFactory.getInstance(object);

            /** 獲取物件屬性 */
            filedExpands = objectWrapper.getMapperFiledExpands();
        }

        while (resultSet.next()){
            /** 若返回是基礎資料型別,則直接將結果放入List中並返回 */
            if(String.class.equals(typeReturn)){
                String val = resultSet.getString(1);
                if(val != null){
                    res.add((T)val);
                }
            }else if(Integer.class.equals(typeReturn) || int.class.equals(typeReturn)){
                Integer val = resultSet.getInt(1);
                if(val != null){
                    res.add((T)val);
                }
            }else if(Float.class.equals(typeReturn) || float.class.equals(typeReturn)){
                Float val = resultSet.getFloat(1);
                if(val != null){
                    res.add((T)val);
                }
            }else { // 若返回的是物件(如User這種)
                // 查詢物件屬性,一個個注入
                for(ClazzWrapper.FiledExpand filedExpand:filedExpands){
                    // 如果物件屬性是String型別,例如User.name是String型別
                    if(String.class.equals(filedExpand.getType())){
                        // resultSet中獲取該屬性
                        String val = resultSet.getString(filedExpand.getPropertiesName());
                        if(val != null){
                            // 填充到物件包裝類中
                            objectWrapper.setVal(filedExpand.getPropertiesName(),val);
                        }
                    }else if(Integer.class.equals(filedExpand.getType()) || int.class.equals(filedExpand.getType())){
                        Integer val = resultSet.getInt(filedExpand.getPropertiesName());
                        if(val != null){
                            objectWrapper.setVal(filedExpand.getPropertiesName(),val);
                        }
                    }else if(Float.class.equals(filedExpand.getType()) || float.class.equals(filedExpand.getType())){
                        Float val = resultSet.getFloat(filedExpand.getPropertiesName());
                        if(val != null){
                            objectWrapper.setVal(filedExpand.getPropertiesName(),val);
                        }
                    }else {
                        continue;
                    }
                }
                // 後續將物件包裝類轉為真實物件,放入List中並返回。物件包裝類見程式碼10
                res.add((T)objectWrapper.getRealObject());
            }
        }
        return res;
    }

    // 根據型別,根據反射,例項化物件
    private Object generateObj(Class<?> clazz) throws Exception{
        Constructor[] constructors  = clazz.getConstructors();
        Constructor usedConstructor = null;
        // 獲取無參構造器,若物件沒有無參構造方法,則失敗
        for(Constructor constructor:constructors){
            if(constructor.getParameterCount() == 0){
                usedConstructor = constructor;
                break;
            }
        }
        if(constructors == null) {
            throw new RuntimeException(typeReturn + " is not empty constructor");
        }
        // 利用反射生成例項
        return usedConstructor.newInstance();
    }
}

程式碼塊9中大量用到了物件增強,類增強。使用這些原因是 在編寫框架程式碼時,因為並不知道每個屬性名,不可能把每個get或者set方法寫死,只能通過將物件封裝成包裝類。然後利用反射,要填充某個物件,就呼叫setVal(屬性名),獲取物件類似。詳情見程式碼塊10

程式碼塊10

類增強(com.simple.ibatis.reflect)

ClazzWrapper

/**
 * @Author  xiabing
 * @Desc    clazz解析類
 **/
public class ClazzWrapper {
    /**
     * 待解析類
     * */
    private Class<?> clazz;
    /**
     * 該類儲存的屬性名
     * */
    private Set<String> propertiesSet = new HashSet<>();

    /**
     * 該類儲存的屬性名及屬性類
     * */
    private Set<FiledExpand> filedExpandSet = new HashSet<>();

    /**
     * 該類儲存的get方法。key為屬性名,value為getxxx方法
     * */
    private Map<String,Method> getterMethodMap = new HashMap<>();
    /**
     * 該類儲存的set方法。key為屬性名,value為setxxx方法
     * */
    private Map<String,Method> setterMethodMap = new HashMap<>();
    /**
     * 快取,避免對同一個類多次解析
     * */
    private static Map<String,ClazzWrapper> clazzWrapperMap = new ConcurrentHashMap<>();

    public ClazzWrapper(Class clazz){
        this.clazz = clazz;
        // 對類進行解析,如果已經解析了,則不用二次解析
        if(!clazzWrapperMap.containsKey(clazz.getName())){
            // 獲取該類的所有屬性
            Field[] fields = clazz.getDeclaredFields();
            for(Field field : fields){
                // 獲取屬性的名稱,屬性型別
                FiledExpand filedExpand = new FiledExpand(field.getName(),field.getType());
                filedExpandSet.add(filedExpand);
                propertiesSet.add(field.getName());
            }
            // 獲取該類的方法
            Method[] methods = clazz.getMethods();
            for(Method method:methods){
                String name = method.getName();
                // 如果是get方法
                if(name.startsWith("get")){
                    name = name.substring(3);
                    if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
                        name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
                    }
                    // 放入get方法快取中
                    if(propertiesSet.contains(name)){
                        getterMethodMap.put(name,method);
                    }
                }else if(name.startsWith("set")){
                    name = name.substring(3);
                    if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
                        name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
                    }
                    if(propertiesSet.contains(name)){
                        setterMethodMap.put(name,method);
                    }
                }
                else {
                    continue;
                }
            }
            // 放入快取,代表該類已經完成解析
            clazzWrapperMap.put(clazz.getName(),this);
        }
    }
    
    // 查詢該類是否包含指定屬性的get方法
    public boolean hasGetter(String properties){
        ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName());

        return clazzWrapper.getterMethodMap.containsKey(properties);
    }

    // 查詢該類是否包含指定屬性的set方法
    public boolean hasSetter(String properties){
        ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName());

        return clazzWrapper.setterMethodMap.containsKey(properties);
    }

    // 獲取該類指定屬性的set方法
    public Method getSetterMethod(String properties){
        if(!hasSetter(properties)){
           throw new RuntimeException("properties " + properties + " is not set method") ;
        }
        ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName());
        return clazzWrapper.setterMethodMap.get(properties);
    }

    // 獲取該類指定屬性的get方法
    public Method getGetterMethod(String properties){
        if(!hasGetter(properties)){
            throw new RuntimeException("properties " + properties + " is not get method") ;
        }
        ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName());
        return clazzWrapper.getterMethodMap.get(properties);
    }

    // 獲取該類所有屬性
    public Set<String> getProperties(){

        ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName());

        return clazzWrapper.propertiesSet;
    }

    // 獲取該類所有屬性增強
    public Set<FiledExpand> getFiledExpandSet(){
        ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName());

        return clazzWrapper.filedExpandSet;
    }

    /**
     *  屬性增強類
     * */
    public static class FiledExpand{

        // 屬性名稱
        String propertiesName;

        // 屬性型別
        Class type;

        public FiledExpand() {
        }

        public FiledExpand(String propertiesName, Class type) {
            this.propertiesName = propertiesName;
            this.type = type;
        }

        public String getPropertiesName() {
            return propertiesName;
        }

        public void setPropertiesName(String propertiesName) {
            this.propertiesName = propertiesName;
        }

        public Class getType() {
            return type;
        }

        public void setType(Class type) {
            this.type = type;
        }

        @Override
        public int hashCode() {
            return propertiesName.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof FiledExpand){
                return ((FiledExpand) obj).propertiesName.equals(propertiesName);
            }
            return false;
        }
    }
}

ObjectWrapper

/**
 * @Author  xiabing
 * @Desc    物件增強,封裝了get,set方法
 **/
public class ObjectWrapper {
    // 真實物件
    private Object realObject;
    // 該物件的類的增強
    private ClazzWrapper clazzWrapper;

    public ObjectWrapper(Object realObject){
        this.realObject = realObject;
        this.clazzWrapper = new ClazzWrapper(realObject.getClass());
    }

    // 呼叫物件指定屬性的get方法
    public Object getVal(String property) throws Exception{

        return clazzWrapper.getGetterMethod(property).invoke(realObject,null);
    }

    // 呼叫物件指定屬性的set方法
    public void setVal(String property,Object value) throws Exception{

        clazzWrapper.getSetterMethod(property).invoke(realObject,value);
    }

    public Set<String> getProperties(){

        return clazzWrapper.getProperties();
    }

    public Set<ClazzWrapper.FiledExpand> getMapperFiledExpands(){

        return clazzWrapper.getFiledExpandSet();
    }

    public Object getRealObject(){

        return realObject;

    }
}

ObjectWrapperFactory

/**
 * @Author  xiabing5
 * @Desc    物件增強類。使用物件增強是因為我們不知道每個物件的getXXX方法,只能利用反射獲取物件的屬性和方法,然後
            統一提供getVal或者setVal來獲取或者設定指定屬性,這就是物件的增強的重要性
 **/
public class ObjectWrapperFactory {

    public static ObjectWrapper getInstance(Object o){
        return new ObjectWrapper(o);
    }

}

總結感悟

上述是我在寫這個框架時的一些思路歷程,程式碼演示請見我github。通過自己手寫原始碼,對mybatis也有了個深刻的見解。當然有很多問題,後面也會進一步去完善,如果你對這個開源專案感興趣,不妨star一下呀!

相關文章