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

前言

這一篇絮絮叨叨,邏輯不太清晰的編寫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" "//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測試一下。

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

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

結語

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

—————————————————————————————————-

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

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

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

—————————————————————————————————-

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

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

 

 

Tags: