造輪子,實現一個spring類似框架

Yao框架

每次看spring的源碼都是難以看進去, 但是平時都會通過各種各樣的博客文章中大致理解了spring的基本原理。 同時我也萌生了自己實現一個類似框架的想法,於是便有了這個項目。雖然是重複造輪子,但是寫這個項目的過程中我對於spring的實現也有了更深的瞭解吧。

雖然我只是無人氣的小博主, 但是我還是在我的在這裏推下我的框架,因爲夢想還是要有的,萬一這個框架哪天火了呢!

博客地址

**本項目使用的是java11版本的基礎上開發,不能保證在小於java11的版本上能正常運行 **

想學習spring的,又難以看進去源碼的, 可以看看我的這個項目,
歡迎star和留言。有疑問請留言, 我很樂意與你們一起探討技術問題。 這個項目我會堅持更新的,希望大家能給與我支持。

項目地址

https://github.com/blanexie/Yao

使用示例

可以參照我的項目源碼中的example模塊來使用

說明除了下面示例的方式, 還可以使用FactoryBean接口和@Bean註解方法的方式將bean放入容器中

導入maven包

<!-- 依賴注入的核心包, 提供基本的依賴注入功能  -->
<dependency>
    <groupId>xyz.xiezc</groupId>
    <artifactId>xioc</artifactId>
    <version>2.2</version>
</dependency>
<!--  整合mybatis的包  -->
<dependency>
    <groupId>xyz.xiezc</groupId>
    <artifactId>xorm</artifactId>
    <version>2.2</version>
</dependency>
<!--  數據源鏈接池  -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
 </dependency>
 <dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
     <version>1.1.22</version>
</dependency>
<!--  整合netty的包, 提供了基本的http能力和websocket支持  -->
<dependency>
    <groupId>xyz.xiezc</groupId>
    <artifactId>xweb</artifactId>
    <version>2.2</version>
</dependency>

定義一個啓動類

package xyz.xiezc.example;


import xyz.xiezc.ioc.starter.Xioc;
import xyz.xiezc.ioc.starter.annotation.Configuration;
import xyz.xiezc.ioc.starter.annotation.Cron;
import xyz.xiezc.ioc.starter.annotation.EnableCron;
import xyz.xiezc.ioc.starter.orm.annotation.MapperScan;

import java.time.LocalTime;

@MapperScan({"xyz.xiezc.example.web.mapper"})  //掃描mapper包
@Configuration
@EnableCron  //啓用定時任務
public class Application {

    /**
     * 每3秒執行一次。<br>
     * 表達式每部分的意思如下:
     * 秒 分 時 日 月 星期
     */
    @Cron("*/3 * * * * *")
    public void cronTest() {
        System.out.println("定時任務執行: " + LocalTime.now());
    }

    public static void main(String[] args) {
        Xioc.run(Application.class);
    }
}

定義一個Controller放入容器中

package xyz.xiezc.example.web.controller;

import cn.hutool.json.JSONUtil;
import xyz.xiezc.example.web.common.TestAopspect;
import xyz.xiezc.example.web.entity.Album;
import xyz.xiezc.example.web.mapper.AlbumMapper;
import xyz.xiezc.example.web.service.TestService;
import xyz.xiezc.ioc.starter.annotation.Aop;
import xyz.xiezc.ioc.starter.annotation.Inject;
import xyz.xiezc.ioc.starter.orm.common.example.Example;
import xyz.xiezc.ioc.starter.starter.web.annotation.Controller;
import xyz.xiezc.ioc.starter.starter.web.annotation.GetMapping;
import xyz.xiezc.ioc.starter.starter.web.entity.WebContext;

import java.util.List;
import java.util.Map;

@Aop(TestAopspect.class)
@Controller("/")
public class TestController {

    @Inject
    AlbumMapper albumMapper;
  	/**
  	 *  支持list注入, 會將符合TestService類型的bean都注入到這個list中
  	*/
    @Inject
    TestService[] testServices;

    @GetMapping("/get.json")
    public String get() {
        return testServices.length + "";
    }

    @GetMapping("/test.json")
    public String get(String param) {
        WebContext webContext = WebContext.get();
        //
        Example build = Example.of(Album.class)
                .andEqualTo(Album::getId, 3537) //支持類似mybatis-plus的lambda的使用方式
                .build();
        List<Album> albums = albumMapper.selectByExample(build);
        //獲取session信息
        Map<String, Object> session = webContext.getSession();
        session.put("param", param);
        return JSONUtil.toJsonStr(albums);
    }
}

定義一個實體類

import lombok.Data;
import Column;
import Id;
import Table;

import java.io.Serializable;
import java.time.LocalDateTime;

@Data
@Table("t_album")
public class Album implements Serializable {
    @Id
    Integer id;
    @Column
    String title;
    @Column
    String publishTime;
    @Column
    String type;
    @Column
    LocalDateTime createTime;
    @Column
    Integer coverId;
    @Column
    Integer see;
}

定義一個Mapper接口

import BaseMapper;
/**
* BaseMapper 中內置了很多的方法
*/
public interface AlbumMapper extends BaseMapper<Album> {
  
}

定義一個websocket的controller

import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import WebSockerController;
import WebSocketFrameHandler;

@WebSockerController("/websocket")
public class WebSocketHandler implements WebSocketFrameHandler {

    @Override
    public WebSocketFrame handleTextWebSocketFrame(TextWebSocketFrame textWebSocketFrame) {
        String text = textWebSocketFrame.text();
        TextWebSocketFrame textWebSocketFrame1 = new TextWebSocketFrame("resp:" + text);
        return textWebSocketFrame1;
    }

    @Override
    public WebSocketFrame handleBinaryWebSocketFrame(BinaryWebSocketFrame binaryWebSocketFrame) {
        return null;
    }
}

配置文件

### web服務的端口
xweb.server.port=8443
### 靜態文件的目錄
xweb.static.path=/static

## 是否啓用ssl
xweb.server.ssl.enable = false
##  證書鏈文件, .crt後綴的文件。啓用ssl後這個文件必須存在
xweb.server.ssl.certChainFile.Path=
## 私鑰文件, .pem後綴的文件。啓用ssl後這個文件必須存在
xweb.server.ssl.privatekeyFile.Path=


#----------------------------------------------------------------------------------------------------------------
## 基本配置信息
# JDBC URL,根據不同的數據庫,使用相應的JDBC連接字符串
url =jdbc:mysql://localhost:8306/daily
# 用戶名,此處也可以使用 user 代替
username = root
# 密碼,此處也可以使用 pass 代替
password = 123456
# JDBC驅動名,可選(Hutool會自動識別)
# driver = com.mysql.jdbc.Driver
# 是否在日誌中顯示執行的SQL
showSql = true
# 是否格式化顯示的SQL
formatSql = true


#----------------------------------------------------------------------------------------------------------------
## 連接池配置項

## ---------------------------------------------------- Druid
# 初始化時建立物理連接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時
initialSize = 0
# 最大連接池數量
maxActive = 8
# 最小連接池數量
minIdle = 0
# 獲取連接時最大等待時間,單位毫秒。配置了maxWait之後, 缺省啓用公平鎖,併發效率會有所下降, 如果需要可以通過配置useUnfairLock屬性爲true使用非公平鎖。
maxWait = 0
# 是否緩存preparedStatement,也就是PSCache。 PSCache對支持遊標的數據庫性能提升巨大,比如說oracle。 在mysql5.5以下的版本中沒有PSCache功能,建議關閉掉。作者在5.5版本中使用PSCache,通過監控界面發現PSCache有緩存命中率記錄, 該應該是支持PSCache。
poolPreparedStatements = false
# 要啓用PSCache,必須配置大於0,當大於0時, poolPreparedStatements自動觸發修改爲true。 在Druid中,不會存在Oracle下PSCache佔用內存過多的問題, 可以把這個數值配置大一些,比如說100
maxOpenPreparedStatements = -1
# 用來檢測連接是否有效的sql,要求是一個查詢語句。 如果validationQuery爲null,testOnBorrow、testOnReturn、 testWhileIdle都不會其作用。
validationQuery = SELECT 1
# 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
testOnBorrow = true
# 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能
testOnReturn = false
# 建議配置爲true,不影響性能,並且保證安全性。 申請連接的時候檢測,如果空閒時間大於 timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
testWhileIdle = false
# 有兩個含義: 1) Destroy線程會檢測連接的間隔時間 2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
timeBetweenEvictionRunsMillis = 60000
# 物理連接初始化的時候執行的sql
connectionInitSqls = SELECT 1
# 屬性類型是字符串,通過別名的方式配置擴展插件, 常用的插件有: 監控統計用的filter:stat  日誌用的filter:log4j 防禦sql注入的filter:wall
filters = stat,wall
# 類型是List<com.alibaba.druid.filter.Filter>, 如果同時配置了filters和proxyFilters, 是組合關係,並非替換關係
proxyFilters =

在這裏插入圖片描述

Xioc 小型的依賴注入框架,

目前支持的註解詳解

  • @Component 被註解的類放入容器成爲bean,可以註解類和方法。
  • @Configuration 被註解的類放入容器成爲bean, 只能註解類。
  • @Inject 註解字段,是的字段從容器中注入bean。
  • @Bean 方法註解, 只能配合@Configuration註解通過方法來生產bean
  • @BeanScan 類註解, 只能配合@Configuration一起使用,用來標註框架掃描的包路徑
  • @Init 方法註解, 被註解的方法在所在bean初始化完成後調用, 方法必須無參
  • @Value 字段註解。 將配置中的內容注入到字段中, 類型轉換參照Hutool的類型轉換工具。
  • @EventListener 類註解, 被註解的方類必須有無參構造方法,且實現ApplicationListener接口, 註解中必須設置處理的時間名稱。
  • @Cron 方法註解, 被註解的方法會作爲定時任務執行
  • @EnableCron 在啓動類上的註解, 只有這個註解存在, @Cron 註解纔會起作用

目前支持功能列表如下

  • 所有的bean都是單例模式
  • 基本的掃描注入類,
  • 支持方法注入bean
  • 支持注入配置,配置文件使用Hutool的setting
  • 支持類似springboot的starter一樣的導入包引入對應功能的方法

需要導入的starter包必須在xyz.xiezc.ioc.starter包下放上一個@Configuration註解的類。 並且可以配合@BeanScan一起使用

  • 支持bean初始化後調用的init方法
  • 支持基本的事件處理器
  • 支持配置注入

缺陷待完善

  • AOP功能需要在被切的類上面增加註解, 但是AOP的意義就在於不用修改被切類從而達到切面的目的

xweb 整合netty支持web的框架

支持的註解

  • @Controller 參考SpringMvc
  • @GetMapping 參考SpringMvc
  • @PostMapping 參考SpringMvc
  • @RequestBody 參考SpringMvc
  • @WebSockerController 被註解的類必須實現WebSocketFrameHandler接口, 從而來實現websocket. 這個註解的value值就是websocket的連接的url

說明

  • 默認靜態文件目錄是 classPath:static/ ,在這個目錄下面的文件會作爲靜態文件。可以在配置文件中指定 xweb.static.path 作爲靜態文件。
  • 請求路徑只有在controller中找不到後纔會進入靜態文件目錄下查找。
  • GET和POST請求只返回application/json格式的內容,不返回其他類型。這樣已經夠用了, 現在幾乎都是前後端分離的項目。

待改進

  • 只支持GET和Post請求
  • Post請求只默認提供支持的ContentType類型只有 “application/json” ,“multipart/form-data” ,
    "application/x-www-form-urlencoded"和從url中獲取參數的Default幾種類型,但是提供了擴展接口。
    只要實現HttpMessageConverter接口,並將實現類放入容器中,就可以了。

配置文件

參考上面使用示例中的配置文件

XORM 整合mybatis方便查詢的項目

支持的註解

  • @Column 標註在實體類中, 可以通過value 指定數據庫字段的名稱。
  • @Id 標註在實體類中, 可以通過value 指定數據庫字段的名稱。
  • @MapperScan 一定要標註在啓動類中, value指定項目中的mybatis的mapper接口的地址
  • @Table 標註在實體類上, 標識這個類對應一個數據庫表

支持的配置文件的配置

  • mybatis.configLocation 指定自定義的mybatis的配置文件地址
  • mybatis.mapperLocations 指定自定義的mapper.xml文件的路徑
  • mybatis.typeAliasesPackage 參考mybatis-spring
  • mybatis.typeHandlersPackage 參考mybatis-spring

數據源的配置

本框架中數據源的使用是完全使用hutool中的數據源的方式

基本配置


## 基本配置信息
# JDBC URL,根據不同的數據庫,使用相應的JDBC連接字符串
url = jdbc:mysql://127.0.0.1:8306/daily
# 用戶名,此處也可以使用 user 代替
username = root
# 密碼,此處也可以使用 pass 代替
password = 123456
# JDBC驅動名,可選(Hutool會自動識別)
driver = com.mysql.cj.jdbc.Driver

支持如下的數據源

  1. HikariCP
  2. Druid
  3. Tomcat
  4. Dbcp
  5. C3p0

本框架句自動識別數據源以及自動注入配置文件中的連接池配置(包括數據庫連接配置),如果引入了多種數據源的jar包。 本框架會按照上面的順序來檢測引入數據源包,檢測到則會自動構建數據源。如果沒有檢測到數據源包, 就會使用內置的簡單數據源(性能問題, 不推薦線上使用)

以druid爲例, 我們看下如何使用

  1. 先引入druid的包
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>${druid.version}</version>
</dependency>
  1. 再在配置文件中增加如下

#----------------------------------------------------------------------------------------------------------------
## 基本配置信息
# JDBC URL,根據不同的數據庫,使用相應的JDBC連接字符串
url = jdbc:mysql://<host>:<port>/<database_name>
# 用戶名,此處也可以使用 user 代替
username = 用戶名
# 密碼,此處也可以使用 pass 代替
password = 密碼
# JDBC驅動名,可選(Hutool會自動識別)
driver = com.mysql.jdbc.Driver
# 是否在日誌中顯示執行的SQL
showSql = true
# 是否格式化顯示的SQL
formatSql = true


#----------------------------------------------------------------------------------------------------------------
## 連接池配置項

## ---------------------------------------------------- Druid
# 初始化時建立物理連接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時
initialSize = 0
# 最大連接池數量
maxActive = 8
# 最小連接池數量
minIdle = 0
# 獲取連接時最大等待時間,單位毫秒。配置了maxWait之後, 缺省啓用公平鎖,併發效率會有所下降, 如果需要可以通過配置useUnfairLock屬性爲true使用非公平鎖。
maxWait = 0
# 是否緩存preparedStatement,也就是PSCache。 PSCache對支持遊標的數據庫性能提升巨大,比如說oracle。 在mysql5.5以下的版本中沒有PSCache功能,建議關閉掉。作者在5.5版本中使用PSCache,通過監控界面發現PSCache有緩存命中率記錄, 該應該是支持PSCache。
poolPreparedStatements = false
# 要啓用PSCache,必須配置大於0,當大於0時, poolPreparedStatements自動觸發修改爲true。 在Druid中,不會存在Oracle下PSCache佔用內存過多的問題, 可以把這個數值配置大一些,比如說100
maxOpenPreparedStatements = -1
# 用來檢測連接是否有效的sql,要求是一個查詢語句。 如果validationQuery爲null,testOnBorrow、testOnReturn、 testWhileIdle都不會其作用。
validationQuery = SELECT 1
# 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
testOnBorrow = true
# 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能
testOnReturn = false
# 建議配置爲true,不影響性能,並且保證安全性。 申請連接的時候檢測,如果空閒時間大於 timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
testWhileIdle = false
# 有兩個含義: 1) Destroy線程會檢測連接的間隔時間 2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
timeBetweenEvictionRunsMillis = 60000
# 物理連接初始化的時候執行的sql
connectionInitSqls = SELECT 1
# 屬性類型是字符串,通過別名的方式配置擴展插件, 常用的插件有: 監控統計用的filter:stat  日誌用的filter:log4j 防禦sql注入的filter:wall
filters = stat
# 類型是List<com.alibaba.druid.filter.Filter>, 如果同時配置了filters和proxyFilters, 是組合關係,並非替換關係
proxyFilters = 
  1. 開始你的表演, 寫你最熟悉的mapper代碼

說明

  • 目前沒有實現事務的功能, 還不支持事務。
  • Mapper接口必須實現xyz.xiezc.ioc.starter.orm.common.BaseMapper接口,BaseMapper接口提供了基本的方便的查詢的方法,十分方便。
  • 可以不用謝mapper.xml文件。 只有到BaseMapper接口不滿足需求的時候可以加上xml文件,
    mapper.xml文件的默認路徑是classPath:mapper/ 。 也可以通過配置文件mybatis.mapperLocations來指定

本框架待改進的地方

  • 實現事務的管理功能, 可以參考spring的事務管理實現

參考項目

hutool

hutool 這真的是一個很好的工具包項目, 全面而有強大, 依賴少。 本框架大量依賴這個工具包

blade

blade是和本項目類似的框架。我的整合netty中的代碼參考這個項目很多

spring

netty

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章