一個C#開發編寫Java框架的心路歷程

kiba518發表於2021-03-15

前言

這一篇絮絮叨叨,邏輯不太清晰的編寫Java框架的的一個過程,主要描述我作為一個java初學者,在編寫Java框架時的一些心得感悟。

因為我是C#的開發者,所以,在編寫Java框架時,或多或少會帶入一些C#的固有觀念,所以,這也是一個C#觀念與Java觀念碰撞的一個框架。

Java與C#的一些小區別

名稱空間:在C#中名稱空間+類名是類,在Java中名稱空間+類名是名稱空間,即,Java中會出現Import某一個類的完全限定名。

反射:在C#中反射可以只用類名反射,Java中必須是完全限定名;在C#中反射是在記憶體或DLL類庫中查詢檔案,一個方法就搞定了,在Java中則需要手寫掃描資料夾或掃描Jar包的檔案,然後找到名稱一樣的檔案再反射。

for迴圈:在C#中有for迴圈和foreach迴圈,在Java中for迴圈支援foreach模式,如:

for(Kiba_User u : ul)

Java之Spring脈絡簡介

對於C#開發而言,Java開發的脈絡實在是清奇的不得了,因為Java使用了大量的依賴注入和控制反轉,從而讓它的結構非常的反人類。但這也是有一定的歷史原因的,因為它的開源語言,所以,大家在擴充套件框架時,都等於在做二次開發,因為依賴注入和控制反轉是二次開發最好的模式,所以,它就越積累越多,最後它徹底的變成了控制反轉的完全體,也就說,它在反人類的路上一去不反覆了(注意,Java開發者通常認為他們才是正常開發,為了避免衝突,請不要當面說他們反人類)。

下面我使用C#的描述的方式來勾勒一下Java之Spring的脈絡,如下圖:

因為,java很多物件都是用註解標識,然後在解析時例項化的,為了統一程式碼,所以,java形成了一種新的標準,例項化物件都用註解。

準備工作

本框架因為是學習框架,所以有些設計會常規的java不同,框架中不會使用類似@Service這樣的註解,但會使用@Data,因為Java中寫屬性確實有點費勁。

下面我們進行準備工作。

開發工具:IDEA。

專案框架:Spring。

JDK:1.8。

ORM:Mybatis。

首先我們建立一個Spring的Web專案——k_framework,C#開發可以參考:一個C#開發者重溫Java的心路歷程。(這裡只做WebApi的介紹)

然後我們編輯Pom.xml引入所需的Jar包,依賴如下:

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>12.1.0.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency> 
  </dependencies>

PS:這裡使用的資料庫是Oracle,然後我們的包管理工具Maven居然不能下載oracle的jar包。。。所以我們只能去官網下載,然後在CMD裡,使用Maven提供的命令安裝這個jar包。

然後結合Java的Spring框架的特質,設計一個專案結構,並在包k_framework下面實現。

專案結構設計如下:

系統約定如下:

DTO類名字尾需為Command和Query,標記命令用於處理的業務為增刪改、或查詢。

DTO類必須在同一包下,且類名不得重複。

前臺頁面必須定義一個同名的,屬性一致的Javascript的DTO類。

業務域類名=DTO的類名+Handler。

業務域類使用Excute函式處理業務。

關於結構

關於配置類與工具類:設計時,我們儘量讓控制器使用配置類,讓業務域使用工具類。當然,特殊情況下也可以一起使用。

關於業務域:Java中通常使用Service來命名處理業務的包,但因為有時候我們會把部署的Web專案也稱為服務,比如微服務專案裡每個WebApi都是服務,所以,這裡為了避免歧義,使用域來命名處理業務的包。

關於資料庫對映:在C#專案裡,我們是先建立對映,然後用倉儲通過泛型來處理資料庫資料,但在Mybatis裡,需要使用對映的物件來處理資料庫資料,即,每處理一個表,就要建立一個這個表的對映物件例項。

關於資料庫實體和資料庫擴充套件實體:顧名思義,資料庫擴充套件實體是資料庫實體的擴充套件,可以的簡單把它理解為檢視實體。

注:在C#中,圖中的這些大類的結構,通常會搞一個類庫專案來單獨處理,因為在C#中共享使用一個啟動專案的配置檔案,並且C#的專案檔案在VS中管理起來非常簡單便捷,但Java的專案檔案pom.xml並不是特別靈活,所以,這裡我們就在一個專案裡做結構。

整體結構圖如下:

程式碼實現——邏輯

工具類

首先,我們先建立工具類。

因為是簡單實現,所以我們只建立三個最基礎的工具類,ReflexHelper、StringHelper、FileHelper。(在java中通常工具類命名會以util結尾,這裡我保持c#的命名風格)

控制器

定義CommandController類,Get和Post兩個函式,用於處理全部的Get和Post請求。函式接受兩個引數,命令型別和命令的Json內容,然後通過命令型別發射呼叫業務域。

程式碼如下:

@RestController
@RequestMapping("/Command")
public class CommandController
{ 
    @Autowired
    private SqlSession sqlSession; 
    @RequestMapping(value = "/Get", method = RequestMethod.GET)
    @ResponseBody
    public BaseResult Get(String commandName,String commandJson) throws Exception {  
        Set<Class<?>> classes = ReflexHelper.getClasses("com.kiba.k_framework.dto");
        String newName ="";
        for(Class c:classes){
            String fullName = c.getName();
            System.out.println(fullName);
            String className = fullName.substring(fullName.lastIndexOf(".")+1,fullName.length());
            System.out.println(className);
            if(className.equals(commandName)) {
                newName = fullName.replace(".dto.", ".domain.") + "Handler";
                System.out.println(newName);
                break;
            }
        }
​
        Class<?> clazz = Class.forName(newName);
        Method method = clazz.getMethod("Excute", String.class,SqlSession.class);
        BaseResult ret = (BaseResult)method.invoke(clazz.newInstance(), commandJson,sqlSession);
        return ret;
    }
    @PostMapping(value = "/Post")
    @ResponseBody
    public BaseResult Post(String commandName,String commandJson) throws Exception {
        System.out.println(commandName);
        Set<Class<?>> classes = ReflexHelper.getClasses("com.kiba.k_framework.dto");
        String newName ="";
        for(Class c:classes){
            String fullName = c.getName();
            System.out.println(fullName);
            String className = fullName.substring(fullName.lastIndexOf(".")+1,fullName.length());
            System.out.println(className);
            if(className.equals(commandName)) {
                newName = fullName.replace(".dto.", ".domain.") + "Handler";
                System.out.println(newName);
                break;
            }
        } 
        Class<?> clazz = Class.forName(newName);
        Method method = clazz.getMethod("Excute", String.class,SqlSession.class);
        BaseResult ret = (BaseResult) method.invoke(clazz.newInstance(), commandJson,sqlSession);
        return ret;
    }  
}

如上程式碼所示,Controller接受到的請求,會被直接傳送到業務域處理,也就是說,理論上,這裡寫完了,就再也不用關注了。

注1:程式碼一開始使用註解@Autowired例項化了sqlSession,這個物件是mybatis的內部物件,後面會把它傳送到業務域,業務域裡通過它獲取mapper物件,這是因為,我們的業務域是反射呼叫的,所以在業務裡@Autowired註解將失效,它將無法對繼承BaseMapper的介面進行例項化。

注2:使用這種結構,我們的AOP除了可以使用@Aspect註解,還可以直接寫在Controller裡了。

注3:並不是所有專案和團隊組成都適用這個的框架。

程式碼實現——資料庫

在本框架中,資料庫連結使用Mybatis開源包。

Mybatis學習

在使用mybatis之前需要先學習一些知識,搞懂mybatis的一些類庫的關係,不然用起來會很迷茫。

mybatis:一個java的orm包。

mybatis-spring-boot-starter:一個mybatis工作組為了spring單獨開發的包,他讓spring框架使用mybatis更簡單,springBoot,springCloud等框架都可以用(對映使用註解@mapper,最新版 2.1.2)。

mybatis-plus:一個基於mybatis的擴充套件包,擁有一些在mapper建立後,會自帶一些基礎的增刪改查的方法。

mybatis-plus-boot-starter:mybatis-plus工作組為了spring單獨開發的包,,他讓spring框架使用mybatis-plus更簡單,springBoot,springCloud等框架都可以用(對映使用繼承BaseMapper,最新版3.42,mybatisplus-springboot-starter是mybatis-plus-boot-starter的增強包)。

瞭解了以上概念後,我們可得知,在springboot專案中使用mybatis,我們有兩個選擇,即使用mybatis-spring-boot-starter或mybatis-plus-boot-starter。

因為我是C#出身,所以,對映我更傾向於繼承,所以下面程式碼使用的是mybatis-plus-boot-starter。

Mybatis配置

在resources/application.yml下輸入配置程式碼如下:

server:
  port: 8088
spring:
  servlet:
    multipart:
      max-file-size: 5000MB
      max-request-size: 5000MB
  datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@192.168.1.1:1521/orcl
    username: abc
    password: 123
# mybatis
mybatis:
  mapper-locations: classpath:mapper/**/*.xml

程式碼中配置了Spring節點下的資料來源,配置為Oracle並設定連結賬戶密碼;還配置了mybatis節點下的對映路徑。該對映路徑下面會用到。

然後配置啟動類,增加註解@MapperScan("com.kiba.k_framework.mapper"),如下圖:

資料庫實體

接著我們建立資料庫實體,屬性跟資料庫表欄位一樣即可。但Java裡寫屬性太麻煩,所以這裡使用了@Data註解,被註解的類下,只要寫私有欄位即可,編譯時會為我們生成首字母大寫的屬性,並且編寫程式碼時,還可以點出【getName()/setName()】這樣的方法來獲取或設定屬性的值。程式碼如下:

package com.kiba.k_framework.entity;
import lombok.Data;
@Data
public class Kiba_User {
    private int id;
    private String name;
}

如果是第一次使用Idea,我們編寫程式碼時,在物件的後面是點不出【getName()/setName()】這樣的方法的,這是因為,我們沒有安裝lombok外掛,安裝外掛在File—Setting中,如下圖所示。

對映類

對映類,顧名思義,就是建立實體與資料庫關係的類,在這裡類中會指定實體類與資料庫表的關係,和實體欄位和表欄位的關係(通常情況是同名對映)。不過在Java裡,對映類除了要處理對映關係,還要擔任資料庫訪問的角色,而C#的對映類就是處理對映關係,訪問資料庫則有資料庫上下文實體負責,說實話,Java這種模式是有點奇怪,不過用久了也就無所謂了。

對映類程式碼如下:

public interface Kiba_UserMapper extends BaseMapper<Kiba_User> {
    @Select("select * from Kiba_User")
    List<Kiba_User> test();
    @Select("select * from Kiba_User where id=#{value}")
    List<Kiba_User> test2(Integer id);
​
    List<Kiba_User> test3(Integer id);
}

如上程式碼所示,對映類通過繼承泛型BaseMapper<Kiba_User> 實現了資料庫實體和表的對映。

然後程式碼裡定義了三個方法,都是查詢資料庫資料。

第一個方法—test:在方法上加了@Select註解,並且在註解裡編寫sql語句,這樣呼叫這個方法時,就會執行註解裡的語句。

第二個方法—test2:方法2多了一個入參,註解裡多了一個查詢條件, 註解裡通過#{value}的方式使用了入參的值。看到這,我們可以發現,註解裡有自己的方言,即註解裡還有一套自己的語法,這顯然明目張膽的增加了開發者的學習內容,我表示反對,但無效。

第二個方法—test3:這個方法沒有註解,但有對應的XML配置檔案,什麼是XML配置檔案?

如下圖所示,裡面有兩個同名,但字尾名不同的檔案,下方的Kiba_UserMapper.xml檔案就是,Kiba_UserMapper.java的xml配置檔案,這兩個檔案編譯的時候會被捏成一個類。系統根據什麼把他們捏一起的呢?還記得我們上面的配置嗎?我們配置了一個對映掃描包和一個對映配置路徑,系統就是根據它倆的掃描檔案結果,然後把同名的捏到一起的。

現在我們看一下Kiba_UserMapper.xml的內容。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
​
<mapper namespace="com.kiba.k_framework.mapper.Kiba_UserMapper"> 
    <select id="test3" resultType="com.kiba.k_framework.entity.Kiba_User" parameterType="Integer">
        select * from Kiba_User where id=#{value}
    </select>  
</mapper>

如程式碼所示,在mapper標籤裡配置了名稱空間——com.kiba.k_framework.mapper.Kiba_UserMapper,即它明確指定了這個XML要和誰捏在一起。(java裡名稱空間+類名還是名稱空間)

然後在mapper標籤裡配置了一個select標籤,【id="test3"】標記了它對應的函式名,resultType和parameterType標籤標識這他們對應的這個函式傳入傳出型別,然後內容是一個帶方言的sql語句。

看到這裡,我們可以得出,這個xml的select標籤是等於@select註解的,即為函式設定sql語句有兩種方式,一種是註解一種是xml檔案配置,因為上面的對映類中的前兩個方法已經有註解了,所以,xml配置檔案中並沒有重複配置。

這個模式嘛,是有點,呃。。。是比較特別。

業務域

現在我們在業務域裡使用一下對映類來獲取資料。

程式碼如下:

public class GetUserQueryHandler implements IHandler
{
    @Override
    public GetUserQueryReuslt Excute(String commandJson, SqlSession sqlSession)     {
        Kiba_UserMapper mapper = sqlSession.getMapper(Kiba_UserMapper.class);
        List<Kiba_User> users = mapper.test();
        GetUserQueryReuslt ret=new GetUserQueryReuslt();
        ret.setUsers(users);
        ret.setSuccess(true);
        return ret;
    }
}

這裡使用sqlSession.getMapper(Kiba_UserMapper.class)來獲取我們的maper例項,然後下面就可以正常呼叫他下面的方法了。

測試

現在我們啟動專案,用postman測試一下。

輸入http://localhost:8088/Command/Get?commandName=GetUserQuery&commandJson={}進行測試,得到結構如下圖所示:

測試成功,我們成功的通過傳送DTO實體實現了業務查詢。

結語

在使用Java的時候,我總感覺像回到了舊社會,錯誤提示、開發工具的使用、工程檔案的管理等等都很不友好。Spring框架看上去很簡潔,但因為這些不友好的朋友在中間阻礙著,整體的開發進度,並沒有想象中那麼快速。也許,我們都被微軟寵壞了吧。

----------------------------------------------------------------------------------------------------

到此,到此Java框架的開發就已經介紹完了。

程式碼已經傳到Github上了,歡迎大家下載。

Github地址: https://github.com/kiba518/Kiba_Java_Framework

----------------------------------------------------------------------------------------------------

注:此文章為原創,任何形式的轉載都請聯絡作者獲得授權並註明出處!
若您覺得這篇文章還不錯,請點選下方的推薦】,非常感謝!

https://www.cnblogs.com/kiba/p/14518906.html

 

 

相關文章