主頁 > 後端開發 > 架構中的設計模式

架構中的設計模式

2022-10-20 06:13:38 後端開發

1 設計模式概述

? 軟體設計模式(Software Design Pattern),俗稱設計模式,設計模式是一套被反復使用的、多數人知曉的、經過分類編目的、代碼設計經驗的總結,它描述了在軟體設計程序中的一些不斷重復發生的問題,以及該問題的解決方案,也就是說,它是解決特定問題的一系列套路,是前輩們的代碼設計經驗的總結,具有一定的普遍性,可以反復使用,使用設計模式的目的是為了代碼重用、讓代碼更容易被他人理解、保證代碼可靠性,

設計模式:

設計模式是一套被反復使用的、多數人知曉的、經過分類編目的、代碼設計經驗的總結,它描述了在軟體設計程序中一些不斷重復發生的問題,以及該問題的解決方案,

設計模式使用場景:

1、在程式設計上會使用到設計模式(宏觀)
2、在軟體架構設計上會使用到設計模式(程式中的體現)

設計模式的目的:

1、提高代碼的可重用性
2、提高代碼的可讀性
3、保障代碼的可靠性

GOF

? 《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《設計模式》一書),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995),這幾位作者常被稱為"四人組(Gang of Four)",而這本書也就被稱為"四人組(或 GOF)"書,

? 在《設計模式》這本書的最大部分是一個目錄,該目錄列舉并描述了 23 種設計模式,

file

? GOF中共提到了23種設計模式不是孤立存在的,很多模式之間存在一定的關聯關系,在大的系統開發中常常同時使用多種設計模式,這23種設計模式根據功能作用來劃分,可以劃分為3類:

? (1)創建型模式:用于描述“怎樣創建物件”,它的主要特點是“將物件的創建與使用分離”,單例、原型、工廠方法、抽象工廠、建造者5種設計模式屬于創建型模式,

? (2)結構型模式:用于描述如何將類或物件按某種布局組成更大的結構,代理、配接器、橋接、裝飾、外觀、享元、組合7種設計模式屬于結構型模式,

? (3)行為型模式:用于描述類或物件之間怎樣相互協作共同完成單個物件都無法單獨完成的任務,以及怎樣分配職責,模板方法、策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄、解釋器11種設計模式屬于行為型模式,

GOF的23種設計模式:

1、單例(Singleton)模式:某個類只能生成一個實體,該類提供了一個全域訪問點供外部獲取該實體,其拓展是有限多例模式,
2、原型(Prototype)模式:將一個物件作為原型,通過對其進行復制而克隆出多個和原型類似的新實體,
3、工廠方法(Factory Method)模式:定義一個用于創建產品的介面,由子類決定生產什么產品,
4、抽象工廠(AbstractFactory)模式:提供一個創建產品族的介面,其每個子類可以生產一系列相關的產品,
5、建造者(Builder)模式:將一個復雜物件分解成多個相對簡單的部分,然后根據不同需要分別創建它們,最后構建成該復雜物件,

6、代理(Proxy)模式:為某物件提供一種代理以控制對該物件的訪問,即客戶端通過代理間接地訪問該物件,從而限制、增強或修改該物件的一些特性,
7、配接器(Adapter)模式:將一個類的介面轉換成客戶希望的另外一個介面,使得原本由于介面不兼容而不能一起作業的那些類能一起作業,
8、橋接(Bridge)模式:將抽象與實作分離,使它們可以獨立變化,它是用組合關系代替繼承關系來實作,從而降低了抽象和實作這兩個可變維度的耦合度,
9、裝飾(Decorator)模式:動態的給物件增加一些職責,即增加其額外的功能,
10、外觀(Facade)模式:為多個復雜的子系統提供一個一致的介面,使這些子系統更加容易被訪問,
11、享元(Flyweight)模式:運用共享技術來有效地支持大量細粒度物件的復用,
12、組合(Composite)模式:將物件組合成樹狀層次結構,使用戶對單個物件和組合物件具有一致的訪問性,

13、模板方法(TemplateMethod)模式:定義一個操作中的演算法骨架,而將演算法的一些步驟延遲到子類中,使得子類可以不改變該演算法結構的情況下重定義該演算法的某些特定步驟,
14、策略(Strategy)模式:定義了一系列演算法,并將每個演算法封裝起來,使它們可以相互替換,且演算法的改變不會影響使用演算法的客戶,
15、命令(Command)模式:將一個請求封裝為一個物件,使發出請求的責任和執行請求的責任分割開,
16、職責鏈(Chain of Responsibility)模式:把請求從鏈中的一個物件傳到下一個物件,直到請求被回應為止,通過這種方式去除物件之間的耦合,
17、狀態(State)模式:允許一個物件在其內部狀態發生改變時改變其行為能力,
18、觀察者(Observer)模式:多個物件間存在一對多關系,當一個物件發生改變時,把這種改變通知給其他多個物件,從而影響其他物件的行為,
19、中介者(Mediator)模式:定義一個中介物件來簡化原有物件之間的互動關系,降低系統中物件間的耦合度,使原有物件之間不必相互了解,
20、迭代器(Iterator)模式:提供一種方法來順序訪問聚合物件中的一系列資料,而不暴露聚合物件的內部表示,
21、訪問者(Visitor)模式:在不改變集合元素的前提下,為一個集合中的每個元素提供多種訪問方式,即每個元素有多個訪問者物件訪問,
22、備忘錄(Memento)模式:在不破壞封裝性的前提下,獲取并保存一個物件的內部狀態,以便以后恢復它,
23、解釋器(Interpreter)模式:提供如何定義語言的放法,以及對語言句子的解釋方法,即解釋器,

2 單例模式

? 單例模式(Singleton Pattern)是 Java 中最常見的設計模式之一,這種型別的設計模式屬于創建型模式,它提供了一種創建物件的最佳方式,

單例模式涉及到一個單一的類,該類負責創建自己的物件,同時確保只有單個物件被創建,該類還提供了一種訪問它唯一物件的方式,其他類可以直接訪問該方法獲取該物件實體,而不需要實體化該類的物件,

單例模式特點:

1、單例類只能有一個實體,                  A a = new A()
2、單例類必須自己創建自己的唯一實體,        
3、單例類必須給所有其他物件提供這一實體,

單例模式優點:

1、在記憶體里只有一個實體,減少了記憶體的開銷,尤其是頻繁的創建和銷毀實體,
2、避免對資源的多重占用(比如寫檔案操作),

單例模式真實應用場景:

1、網站的計數器
2、應用程式的日志應用
3、資料庫連接池設計
4、多執行緒的執行緒池設計

2.1 單例模式-餓漢式

創建一個單例物件SingleModel,SingleModel 類有它的私有建構式和本身的一個靜態實體,

SingleModel類提供了一個靜態方法,供外界獲取它的靜態實體,DesignTest我們的演示類使用SingleModel類來獲取 SingleModel 物件,

file
創建SingleModel:

public class SingleModel {

    //創建 SingleModel 的一個物件
    private static SingleModel instance = new SingleModel();

    //讓建構式為 private,這樣該類就不會被實體化
    private SingleModel(){}

    //獲取唯一可用的物件
    public static SingleModel getInstance(){
        return instance;
    }

    public void useMessage(){
        System.out.println("Single Model!");
    }
}

單例測驗:

public class DemoTest {

    /****
     * 單例模式測驗
     */
    @Test
    public void testSingleModel(){
        //不合法的建構式
        //編譯時錯誤:建構式 SingleModel() 是不可見的
        //SingleModel singleModel = new SingleModel();

        //獲取唯一可用的物件
        SingleModel singleModel1 = SingleModel.getInstance();
        SingleModel singleModel2 = SingleModel.getInstance();

        //顯示訊息
        singleModel1.useMessage();

        //創建的2個物件是同一個物件
        System.out.println(singleModel1 == singleModel2);
    }
}

輸入結果如下:

Single Model!
true

我們測驗創建10萬個物件,用單例模式創建,僅占記憶體:104位元組,而如果用傳統方式創建10萬個物件,占記憶體大小為2826904位元組,

2.2 多種單例模式講解

? 單例模式有多種創建方式,剛才創建方式沒有特別的問題,但是程式啟動就需要創建物件,不管你用不用到物件,都會創建物件,都會消耗一定記憶體,因此在單例的創建上出現了多種方式,

懶漢式:

懶漢式有這些特點:

1、延遲加載創建,也就是用到物件的時候,才會創建
2、執行緒安全問題需要手動處理(不添加同步方法,執行緒不安全,添加了同步方法,效率低)
3、實作容易

案例如下:SingleModel1

file

如果在創建物件實體的方法上添加同步synchronized,但是這種方案效率低,代碼如下:

file

雙重校驗鎖:SingleModel2

? 這種方式采用雙鎖機制,安全且在多執行緒情況下能保持高性能,

public class SingleModel2 {

    //不實體化
    private static SingleModel2 instance;

    //讓建構式為 private,這樣該類就不會被實體化
    private SingleModel2(){}

    //獲取唯一可用的物件
    public static SingleModel2 getInstance(){
        //instance為空的時候才創建物件
        if(instance==null){
            //同步鎖,效率比懶漢式高
            synchronized (SingleModel2.class){
                //這里需要判斷第2次為空
                if(instance==null){
                    instance = new SingleModel2();
                }
            }
        }
        return instance;
    }

    public void useMessage(){
        System.out.println("Single Model!");
    }
}

指令重排問題解決

物件創建,一般正確流程如下:

1:申請記憶體空間
2:創建物件
3:將創建的物件指向申請的記憶體空間地址

但其實在物件創建的時候,也有可能發生 指令重排問題,也就是上面流程會被打亂:

1:申請記憶體空間
2:將創建的物件指向申請的記憶體空間地址
3:創建物件

如果是這樣的話,雙檢鎖在多執行緒情況下也會出現問題,需要添加volatile屬性,該屬性能防止指令重排,代碼如下:

public class SingleModel2 {

    //不實體化
    private static volatile SingleModel2 instance;

    //讓建構式為 private,這樣該類就不會被實體化
    private SingleModel2(){}

    //獲取唯一可用的物件
    public static SingleModel2 getInstance(){
        //instance為空的時候才創建物件
        if(instance==null){
            //同步鎖,效率比懶漢式高
            synchronized (SingleModel2.class){
                //這里需要判斷第2次為空
                if(instance==null){
                    instance = new SingleModel2();
                }
            }
        }
        return instance;
    }

    public void useMessage(){
        System.out.println("Single Model!");
    }
}

3 SpringAOP代理模式

? Spring是一個分層的JavaSE/EE full-stack(一站式) 輕量級開源框架,非常受企業歡迎,他解決了業務邏輯層和其他各層的松耦合問題,它將面向介面的編程思想貫穿整個系統應用,在Spring原始碼中擁有多個優秀的設計模式使用場景,有非常高的學習價值,

3.1 代理模式

定義:

給某物件提供一個代理物件,通過代理物件可以訪問該物件的功能,主要解決通過代理去訪問[不能直接訪問的物件],例如租房中介,你可以直接通過中介去了解房東的房源資訊,此時中介就可以稱為代理,

優點:

1、職責清晰, 
2、高擴展性, 
3、智能化,

缺點:

 1、由于在客戶端和真實主題之間增加了代理物件,因此有些型別的代理模式可能會造成請求的處理速度變慢, 
 2、實作代理模式需要額外的作業,有些代理模式的實作非常復雜,

代理實作方式:(代理實作技術方案)

基于介面的動態代理
    提供者:JDK官方的Proxy類,
    要求:被代理類最少實作一個介面,
基于子類的動態代理
    提供者:第三方的CGLib,如果報asmxxxx例外,需要匯入asm.jar,
    要求:被代理類不能用final修飾的類(最終類),

3.2 JDK動態代理

JDK動態代理要點:

1、被代理的類必須實作一個介面
2、用JDK代理,被代理的程序需要實作InvocationHandler
3、代理程序在invoke中實作
4、創建代理物件Proxy.newProxyInstance實作

? 我們以王五租房為例,王五通過中介直接租用戶主房屋,中介在這里充當代理角色,戶主充當被代理角色,

創建房東介面物件:LandlordService

public interface LandlordService {
    void rentingPay(String name);
}

創建房東物件:Landlord

public class Landlord implements LandlordService{

    /****
     * @param name
     */
    @Override
    public void rentingPay(String name){
        System.out.println(name+" 來交租!");
    }
}

創建代理處理程序物件:QFangProxy

public class QFangProxy implements InvocationHandler{

    private Object instance;

    public QFangProxy(Object instance) {
        this.instance = instance;
    }

    /****
     * 代理程序
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        args[0] = "中介QFang帶領租戶"+args[0];
        Object result = method.invoke(instance, args);
        return result;
    }
}

創建代理,并通過代理呼叫房東方法:JdkProxyTest

public class JdkProxyTest {
    public static void main(String[] args) {
        //給QFang產生代理
        LandlordService landlordService = new Landlord();
        QFangProxy proxy = new QFangProxy(landlordService);
        LandlordService landlordServiceProxy = (LandlordService) Proxy.newProxyInstance(LandlordService.class.getClassLoader(), new Class[]{LandlordService.class}, proxy);

        //通過代理物件呼叫Landlord物件的方法
        landlordServiceProxy.rentingPay("王五");
    }
}

運行結果如下:

中介QFang帶領客戶 來交租!

3.3 CGLib動態代理

CGLib動態代理要點:

1、代理程序可以實作MethodInterceptor(Callback)介面中的invoke來實作
2、通過Enhancer來創建代理物件

在上面的案例基礎上,把QFangProxy換成SFangProxy,代碼如下:

public class SFangProxy implements MethodInterceptor {

    private Object instance;

    public SFangProxy(Object instance) {
        this.instance = instance;
    }

    /***
     * 代理程序
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        args[0]="S房網帶租戶"+args[0];
        return method.invoke(instance,args);
    }
}

創建測驗類:CGLibProxyTest,代碼如下

public class CGLibProxyTest {
    public static void main(String[] args) {
        //給QFang產生代理
        LandlordService landlordService = new Landlord();
        SFangProxy proxy = new SFangProxy(landlordService);
        LandlordService landlordServiceProxy = (LandlordService) Enhancer.create(LandlordService.class,proxy);

        //通過代理物件呼叫Landlord物件的方法
        landlordServiceProxy.rentingPay("王五");
    }
}

3.4 Spring AOP-動態代理

? 基于SpringAOP可以實作非常強大的功能,例如宣告式事務、基于AOP的日志管理、基于AOP的權限管理等功能,利用AOP可以將重復的代碼抽取,重復利用,節省開發時間,提升開發效率,Spring的AOP其實底層就是基于動態代理而來,并且支持JDK動態代理和CGLib動態代理,動態代理的集中體現在DefaultAopProxyFactory類中,我們來決議下DefaultAopProxyFactory類,

file
? 如果我們在spring的組態檔中不配置<aop:config proxy-target->,此時默認使用的將是JDK動態代理,如果配置了,則會使用CGLib動態代理,

? JDK動態代理的創建JdkDynamicAopProxy如下:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

	//創建代理物件
	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isDebugEnabled()) {
			logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
		}
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
	}

	@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//JDK動態代理程序
		}
	}
}

? CGLib動態代理的創建ObjenesisCglibAopProxy如下:

class ObjenesisCglibAopProxy extends CglibAopProxy {

	//CGLib動態代理創建程序
	@Override
	@SuppressWarnings("unchecked")
	protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
		Class<?> proxyClass = enhancer.createClass();
		Object proxyInstance = null;

		if (objenesis.isWorthTrying()) {
			try {
				proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
			}
			catch (Throwable ex) {
				logger.debug("Unable to instantiate proxy using Objenesis, " +
						"falling back to regular proxy construction", ex);
			}
		}

		if (proxyInstance == null) {
			// Regular instantiation via default constructor...
			try {
				Constructor<?> ctor = (this.constructorArgs != null ?
						proxyClass.getDeclaredConstructor(this.constructorArgTypes) :
						proxyClass.getDeclaredConstructor());
				ReflectionUtils.makeAccessible(ctor);
				proxyInstance = (this.constructorArgs != null ?
						ctor.newInstance(this.constructorArgs) : ctor.newInstance());
			}
			catch (Throwable ex) {
				throw new AopConfigException("Unable to instantiate proxy using Objenesis, " +
						"and regular proxy instantiation via default constructor fails as well", ex);
			}
		}

		((Factory) proxyInstance).setCallbacks(callbacks);
		return proxyInstance;
	}
}

3.5 代理模式-檔案服務實戰

設計模式如果只是去學習他的模式,而不投入實際應用,其實無異于閉門造豬,因此我們要將設計模式投入實際開發使用才是對設計模式真正的領悟,

案例:根據檔案型別,將檔案存盤到不同服務

代理模式:

給一個物件創建一個代理物件,通過代理物件可以使用該物件的功能,

CGLib和JDK是代理模式實作的技術方案,

3.5.1 檔案服務應用

? 代理模式的應用場景除了代碼級別,還可以將代理模式遷移到應用以及架構級別,如下圖檔案上傳代理服務,針對一些圖片小檔案,我們可以直接把檔案存盤到FastDFS服務,針對大檔案,例如商品視頻介紹,我們可以把它存盤到第三方OSS

? 用戶通過檔案上傳代理服務可以間接訪問OSS和本地FastDFS,這種分布式海量檔案管理解決方案,這里不僅在代碼層面充分運用了代理模式,在架構層面也充分運用了代理模式,
file

3.5.2 分布式檔案代理服務器實作

1)實作分析

? 基于代理模式,我們實作檔案上傳分別路由到aliyunOSSFastDFS,用例圖如下:

file

講解:

1、FileUpload抽象介面,定義了檔案上傳方法,分別給它寫了2種實作,
2、AliyunOSSFileUpload是將檔案上傳到aliyunOSS,主要上傳mp4和avi的視頻大檔案,
3、FastdfsFileUpoad是將檔案上傳到FastDFS,主要上傳png/jpg等圖片小檔案,
4、FileUploadProxy是代理物件,供用戶訪問,呼叫了FileUpload的檔案上傳方法,為用戶提供不同檔案上傳呼叫,
5、FileController是控制器,用于接收用戶提交的檔案,并呼叫代理FileUploadProxy實作檔案上傳,

2)代碼實作

bootstrap.yml配置:

server:
  port: 18081
logging:
  level:
    #root: debug開啟dubug級別
    com.seckill.goods.dao: error
  pattern:
    console: "%msg%n"

#對應實體的id和需要處理的檔案型別的映射關系
upload:
  filemap:
    aliyunOSSFileUpload: avi,mp4
    fastdfsFileUpoad: png,jpg

#FastDFS配置
fastdfs:
  url: http://192.168.211.137:28181/
#aliyun
aliyun:
  oss:
    endpoint: oss-cn-beijing.aliyuncs.com
    accessKey: a7i6rVEjbtaJdYX2
    accessKeySecret: MeSZPybPHfJtsYCRlEaUbfRtdH8gl4
    bucketName: sklll
    key: video/
    backurl: https://sklll.oss-cn-beijing.aliyuncs.com/video/ #訪問地址配置

spring:
  application:
    name: seckill-goods
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  servlet:
    multipart:
      max-file-size: 100MB #上傳檔案大小配置

FileUpload介面定義:

public interface FileUpload {

    /***
     * 檔案上傳
     * @param buffers:檔案位元組陣列
     * @param extName:后綴名
     * @return
     */
    String upload(byte[] buffers,String extName);
}

AliyunOSSFileUpload實作:

@Component(value = "https://www.cnblogs.com/jiagooushi/p/aliyunOSSFileUpload")
public class AliyunOSSFileUpload implements FileUpload{

    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
    @Value("${aliyun.oss.accessKey}")
    private String accessKey;
    @Value("${aliyun.oss.accessKeySecret}")
    private String accessKeySecret;
    @Value("${aliyun.oss.key}")
    private String key;
    @Value("${aliyun.oss.bucketName}")
    private String bucketName;
    @Value("${aliyun.oss.backurl}")
    private String backurl;

    /****
     * 檔案上傳
     *  檔案型別如果是圖片,則上傳到本地FastDFS
     *  檔案型別如果是視頻,則上傳到aliyun OSS
     */
    @Override
    public String upload(byte[] buffers,String extName) {
        String realName = UUID.randomUUID().toString()+"."+extName;
        // 創建OSSClient實體,
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessKeySecret);
        // <yourObjectName>表示上傳檔案到OSS時需要指定包含檔案后綴在內的完整路徑,例如abc/efg/123.jpg,
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key+realName, new ByteArrayInputStream(buffers));
        // 上傳字串,
        ObjectMetadata objectMetadata = https://www.cnblogs.com/jiagooushi/p/new ObjectMetadata();
        objectMetadata.setContentType(FileUtil.getContentType("."+extName));
        putObjectRequest.setMetadata(objectMetadata);
        ossClient.putObject(putObjectRequest);

        // 關閉OSSClient,
        ossClient.shutdown();
        return backurl+realName;
    }
}

FastdfsFileUpoad實作:

@Component(value = "https://www.cnblogs.com/jiagooushi/p/fastdfsFileUpoad")
public class FastdfsFileUpoad implements FileUpload {

    @Value("${fastdfs.url}")
    private String url;

    /***
     * 檔案上傳
     * @param buffers:檔案位元組陣列
     * @param extName:后綴名
     * @return
     */
    @Override
    public String upload(byte[] buffers, String extName) {
        /***
         * 檔案上傳后的回傳值
         * uploadResults[0]:檔案上傳所存盤的組名,例如:group1
         * uploadResults[1]:檔案存盤路徑,例如:M00/00/00/wKjThF0DBzaAP23MAAXz2mMp9oM26.jpeg
         */
        String[] uploadResults = null;
        try {
            //獲取StorageClient物件
            StorageClient storageClient = getStorageClient();
            //執行檔案上傳
            uploadResults = storageClient.upload_file(buffers, extName, null);
            return url+uploadResults[0]+"/"+uploadResults[1];
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /***
     * 初始化tracker資訊
     */
    static {
        try {
            //獲取tracker的組態檔fdfs_client.conf的位置
            String filePath = new ClassPathResource("fdfs_client.conf").getPath();
            //加載tracker配置資訊
            ClientGlobal.init(filePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /***
     * 獲取StorageClient
     * @return
     * @throws Exception
     */
    public static StorageClient getStorageClient() throws Exception{
        //創建TrackerClient物件
        TrackerClient trackerClient = new TrackerClient();
        //通過TrackerClient獲取TrackerServer物件
        TrackerServer trackerServer = trackerClient.getConnection();
        //通過TrackerServer創建StorageClient
        StorageClient storageClient = new StorageClient(trackerServer,null);
        return storageClient;
    }
}

FileUploadProxy代理實作:

@Data
@Component
@ConfigurationProperties(prefix = "upload")
public class FileUploadProxy implements ApplicationContextAware{

    private ApplicationContext act;

    //aliyunOSSFileUpload     ->  mp4,avi
    private Map<String,List<String>> filemap;

    /***
     * 檔案上傳
     * @param file:上傳的檔案
     * @return
     */
    public String upload(MultipartFile file) throws Exception{
        //檔案名字  1.mp4
        String fileName = file.getOriginalFilename();
        //擴展名  mp4,jpg
        String extName = StringUtils.getFilenameExtension(fileName);

        //回圈filemap
        for (Map.Entry<String, List<String>> entry : filemap.entrySet()) {
            for (String suffix : entry.getValue()) {
                //匹配當前extName和當前map中對應的型別是否匹配
                if(extName.equalsIgnoreCase(suffix)){
                    //一旦匹配,則把key作為唯一值,從容器中獲取對應實體
                    return act.getBean(entry.getKey(), FileUpload.class).upload(file.getBytes(),extName);
                }
            }
        }
        return null;
    }

    //注入容器物件
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.act=applicationContext;
    }
}

FileController控制器實作:

@RestController
@RequestMapping(value = "https://www.cnblogs.com/file")
public class FileController {

    @Autowired
    private FileUploadProxy fileUploadProxy;

    /***
     * 檔案上傳
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping(value = "https://www.cnblogs.com/upload")
    public String upload(MultipartFile file) throws IOException {
        return fileUploadProxy.upload(file.getBytes(), StringUtils.getFilenameExtension(file.getOriginalFilename()));
    }
}

檔案上傳預覽效果:

<https://sklll.oss-cn-beijing.aliyuncs.com/video/77df7ada-4eea-4698-bfc5-bedd2c16f240.mp4>

file

FastDFS地址:

<http://192.168.211.137:28181/group1/M00/00/00/wKjTiV7kLtGASw5TAADJ9uXzZAQ622.png>
file

4 享元模式

定義:

運用共享技術來有効地支持大量細粒度物件的復用,它通過共享已經存在的物件來大幅度減少需要創建的物件數量、避免大量相似類的開銷,從而提高系統資源的利用率,

享元模式和單利的區別:

單利是物件只能自己創建自己,整個應用中只有1個物件
享元模式根據需要共享,不限制被誰創建(有可能有多個物件實體)

優點:

特定環境下,相同物件只要保存一份,這降低了系統中物件的數量,從而降低了系統中細粒度物件給記憶體帶來的壓力,

缺點:

為了使物件可以共享,需要將一些不能共享的狀態外部化,這將增加程式的復雜性,

4.1 享元模式實戰

案例:用戶下單,會話共享

4.2 會話跟蹤分析

? 會話跟蹤,如果是傳統專案用Session或者是Cookie,全專案通用,但在微服務專案中,不用Session也不用Cookie,所以想要在微服務專案中實作會話跟蹤,是有一定難度的,

? 當前微服務專案中,身份識別的主流方法是前端將用戶令牌存盤到請求頭中,每次請求將請求頭中的令牌攜帶到后臺,后臺每次從請求頭中獲取令牌來識別用戶身份,

? 我們在專案操作程序中,很多地方都會用到用戶身份資訊,比如下訂單的時候,要知道當前訂單屬于哪個用戶,記錄下單關鍵日志的時候,需要記錄用戶操作的資訊以及用戶資訊,關鍵日志記錄我們一般用AOP進行攔截操作,此時沒法直接把用戶身份資訊傳給AOP,這個時候我們可以利用享元模式實作用戶會話資訊共享操作,操作流程如下圖:

file

4.3 會話共享案例實作

? 基于上面的分析,我們采用享元模式實作用戶會話共享操作,要解決如下幾個問題:

1、用戶會話共享
2、會話多執行緒安全
3、訂單資料用戶資訊獲取
4、AOP日志記錄用戶資訊獲取

定義共享組件Session

? Session里面定義了每個執行緒中不變的用戶身份資訊usernamerolesex,其他的是可能存在變化的資料可以寫一個類繼承該類,

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public abstract class Session {
    //需要共享的用戶資訊
    private String username;
    private String name;
    private String sex;
    private String role;
    private Integer level;

    //擴展方法
    public abstract void handler();
}

享元組件邏輯操作物件SessionShar

? SessionShar該物件主要用于給當前執行緒填充共享資料,以及變更訪問方法和訪問資訊等資訊的邏輯操作,代碼如下:

public class SessionShar extends Session {
    //方便實體化
    public SessionShar(String username, String name, String sex, String role, Integer level) {
        super(username, name, sex, role, level);
    }

    /***
     * 擴展物件
     */
    @Override
    public void handler() {
        System.out.println("擴展功能!");
    }
}

多執行緒安全控制ThreadSession

? 每個執行緒請求的時候,我們需要保障會話安全,比如A執行緒訪問和B執行緒訪問,他們的用戶會話身份不能因為并發原因而發生混亂,這里我們可以采用ThreadLocal來實作,我們創建一個ThreadSession物件,并在該物件中創建ThreadLocal<Session>用戶存盤每個執行緒的會話資訊,并實作ThreadLocal<Session>的操作,代碼如下:

@Component
public class ThreadSession {

    //存盤需要共享的物件
    private static ThreadLocal<Session> sessions = new ThreadLocal<Session>();

    /****
     * 添加用戶資訊記錄
     */
    public void add(Session session){
        sessions.set(session);
    }

    /****
     * 獲取LogComponent
     */
    public Session get(){
        return sessions.get();
    }

    /****
     * 移除
     */
    public void remove(){
        sessions.remove();
    }
}

執行緒會話初始化AuthorizationInterceptor

? AuthorizationInterceptor攔截器的作用是用于初始化用戶訪問的時候用戶的身份資訊,并將身份資訊存盤到ThreadSessionThreadLocal中,在用戶訪問方法結束,銷毀ThreadSessionThreadLocal中會話,代碼如下:

@Component
public class AuthorizationInterceptor implements HandlerInterceptor {

    @Autowired
    private ThreadSession threadSession;
    /****
     * 將用戶會話存盤到ThreadLocal中
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            //獲取令牌
            String authorization = request.getHeader("token");
            //決議令牌
            if(!StringUtils.isEmpty(authorization)){
                Map<String, Object> tokenMap = JwtTokenUtil.parseToken(authorization);
                //封裝用戶身份資訊,存盤到ThreadLocal中,供當前執行緒共享使用
                //1.封裝需要共享的資訊
                //2.創建一個物件繼承封裝資訊,每次共享該物件 (不需要共享,則可以創建另外一個物件繼承它)
                //3.創建共享管理物件,實作共享資訊的增加、獲取、移除功能
                threadSession.add(new SessionShar(
                        tokenMap.get("username").toString(),
                        tokenMap.get("name").toString(),
                        tokenMap.get("sex").toString(),
                        tokenMap.get("role").toString(),
                        Integer.valueOf(tokenMap.get("level").toString())
                ));
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //輸出令牌校驗失敗
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print("身份校驗失敗!");
        response.getWriter().close();
        return false;
    }

    /**
     * 移除會話資訊
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        threadSession.remove();
    }
}

共享資訊使用:

①AOP記錄日志:創建AOP切面類LogAspect用于記錄日志,代碼如下:

@Component
@Aspect
@Slf4j
public class LogAspect {

    @Autowired
    private ThreadSession threadSession;

    /***
     * 記錄日志
     */
    @SneakyThrows
    @Before("execution(int com.itheima.shop.service.impl.*.*(..))")
    public void logRecode(JoinPoint joinPoint){
        //獲取方法名字和引數
        String methodName = joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName();
        //記錄日志
        log.info("用戶【"+threadSession.get().toString()+"】訪問:"+methodName);
    }

    /****
     * 引數獲取
     */
    public String args(Object[] args){
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i <args.length ; i++) {
            buffer.append("  args("+i+"):"+args[i].toString());
        }
        return buffer.toString();
    }
}

②添加訂單獲取用戶資訊:在添加訂單方法OrderServiceImpl.add(Order order)中,從ThreadSession中獲取用戶會話,并填充給Order,代碼如下:

file

添加訂單,日志輸出可以看到呼叫添加訂單和修改庫存時,都記錄了日志,并且獲取了用戶會話,效果如下:

LogComponent(username=zhaoliu, sex=男, role=ROLE_USER, methodName=com.itheima.shop.service.impl.OrderServiceImpl.add, message=  args(0):Order(itemId=1, id=1, money=9999, status=1, num=1, username=null))

LogComponent(username=zhaoliu, sex=男, role=ROLE_USER, methodName=com.itheima.shop.service.impl.ItemServiceImpl.modify, message=  args(0):1  args(1):1)

添加的訂單資料庫資料中也擁有用戶資訊,效果如下:

file

5 裝飾者模式

定義:

動態的向一個現有的物件添加新的功能,同時又不改變其結構,它屬于結構型模式,

擴展新功能,不需要修改現有物件就能實作--->裝飾者模式

優點:

裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實作類的功能,

缺點:

多層裝飾比較復雜,

5.1 裝飾者模式實戰

案例:結算價格計算,根據不同價格嵌套運算

5.2 訂單結算價格實戰

? 在訂單提交的時候,訂單價格和結算價格其實是兩碼事,訂單價格是當前商品成交價格,而結算價格是用戶最終需要支付的金額,最終支付的金額并不是一成不變,它也并不是商品成交價格,能改變結算價格的因素很多,比如滿100減10元,VIP用戶再減5塊,訂單結算金額計算我們就可以采用裝飾者模式,

file

5.3 裝飾者模式價格運算實作

實作思路分析

1、創建介面(MoneyOperation),定義訂單價格計算,因為所有價格波動,都是基于訂單價格來波動的,
2、創建訂單價格計算類(OrderPayMoneyOperation),實作MoneyOperation介面,實作訂單價格計算,
3、創建裝飾者物件(Decorator),以供功能擴展,
4、實作優惠券優惠金額計算功能擴展,創建Decorator的擴展類CouponsMoneyOperation,先計算訂單金額,再計算優惠券使用之后的優惠金額,
5、實作金幣抵現功能擴展,創建Decorator的擴展類GoldMoneyOperation,先計算訂單金額,再實作金幣優惠之后的金額,

基礎介面:創建介面MoneySum,該介面只用于定義計算訂單金額的方法,

public interface MoneySum {

    //訂單金額求和計算
    void sum(Order order);
}

訂單金額計算類:創建類OrderPayMoneyOperation實作訂單金額的計算,

@Component(value = "https://www.cnblogs.com/jiagooushi/p/orderMoneySum")
public class OrderMoneySum implements MoneySum {

    @Autowired
    private ItemDao itemDao;

    //總金額計算
    @Override
    public void sum(Order order) {
        //商品單價*總數量
        Item item = itemDao.findById(order.getItemId());
        order.setPaymoney(item.getPrice()*order.getNum());
        order.setMoney(item.getPrice()*order.getNum());
    }
}

裝飾者類:創建裝飾者類DecoratorMoneySum供其他類擴展,

public class DecoratorMoneySum implements MoneySum {

    private MoneySum moneySum;

    public void setMoneySum(MoneySum moneySum) {
        this.moneySum = moneySum;
    }

    //計算金額
    @Override
    public void sum(Order order) {
        moneySum.sum(order);
    }
}

滿100減10元價格計算:創建類FullMoneySum擴展裝飾者類,實作滿減價格計算,

@Component(value = "https://www.cnblogs.com/jiagooushi/p/fullMoneySum")
public class FullMoneySum extends DecoratorMoneySum{

    //原來的功能上進行增強
    @Override
    public void sum(Order order) {
        //原有功能
        super.sum(order);
        //增強
        moneySum(order);
    }

    //滿100減5塊
    public void moneySum(Order order){
        Integer paymoney = order.getPaymoney();
        if(paymoney>=100){
            order.setPaymoney(paymoney-10);
        }
    }
}

VIP優惠10元價格計算:創建類VipMoneySum,實作VIP優惠計算,

@Component(value = "https://www.cnblogs.com/jiagooushi/p/vipMoneySum")
public class VipMoneySum extends DecoratorMoneySum {

    //原有方法上增強
    @Override
    public void sum(Order order) {
        //原有功能
        super.sum(order);

        //增強
        vipMoneySum(order);
    }

    //Vip價格優惠-5
    public void vipMoneySum(Order order){
        order.setPaymoney(order.getPaymoney()-5);
    }
}

支付金額計算:修改OrderServiceImpladd()方法,添加訂單金額以及訂單支付金額的計算功能,代碼如下:

file

測驗效果

測驗資料中,我們選擇購買1件商品,當前登錄用戶為王五,擁有5個金幣,當前購買的商品id=1,商品單價是150元,滿減100,VIP優惠5元,最終支付135元,

{
    "itemId":"1",
    "id":"1",
    "status":1,
    "num":1,
    "couponsId":"1"
}

測驗生成的訂單如下:

file
不僅如此,我們可以隨時撤掉滿減和Vip優惠功能,

6 策略模式

定義:

策略模式是對演算法的包裝,把演算法的使用和演算法本身分隔開,委派給不同的物件管理,策略模式通常把一系列的演算法包裝到一系列的策略類里面,作為一個抽象策略類的子類或者介面的實作類,

? 簡單來說就是就定義一個策略介面,策略類去實作該介面去定義不同的策略,然后定義一個環境(Context,也就是需要用到策略的物件)類,以策略介面作為成員變數,根據環境來使用具體的策略,

優點:

1、演算法可以自由切換, 
2、避免使用多重條件判斷, 
3、擴展性良好,

缺點:

 1、策略類會增多, 
 2、所有策略類都需要對外暴露,

6.1 策略模式實戰

案例:結算價格計算,根據Vip不同等級進行運算

6.2 不同VIP優惠價格分析

file

? 用戶在購買商品的時候,很多時候會根據Vip等級打不同折扣,尤其是在線商城中體現的淋漓盡致,我們這里也基于真實電商案例來實作VIP等級價格制:

Vip0->普通價格
Vip1->減5元
Vip2->7折
Vip3->5折

6.3 代碼實作

定義策略介面Strategy

public interface Strategy {

    //價格計算
    Integer payMoney(Integer payMoney);
}

定義Vip0策略StrategyVipOne

@Component(value = "https://www.cnblogs.com/jiagooushi/p/strategyVipOne")
public class StrategyVipOne implements Strategy {

    //普通會員,沒有優惠
    @Override
    public Integer payMoney(Integer payMoney) {
        return payMoney;
    }
}

定義Vip1策略StrategyVipTwo

@Component(value = "https://www.cnblogs.com/jiagooushi/p/strategyVipTwo")
public class StrategyVipTwo implements  Strategy{

    //策略2
    @Override
    public Integer payMoney(Integer payMoney) {
        return payMoney-5;
    }
}

定義Vip2策略StrategyVipThree

@Component(value = "https://www.cnblogs.com/jiagooushi/p/strategyVipThree")
public class StrategyVipThree implements  Strategy{

    //策略3
    @Override
    public Integer payMoney(Integer payMoney) {
        return (int)(payMoney*0.7);
    }
}

定義Vip3策略StrategyVipFour

@Component(value = "https://www.cnblogs.com/jiagooushi/p/strategyVipFour")
public class StrategyVipFour implements  Strategy{

    //策略4
    @Override
    public Integer payMoney(Integer payMoney) {
        return (int)(payMoney*0.5);
    }
}

定義策略工廠StrategyFactory

@Data
@ConfigurationProperties(prefix = "strategy")
@Component
public class StrategyFactory implements ApplicationContextAware{

    //ApplicationContext
    //1、定義一個Map存盤所有策略【strategyVipOne=instanceOne】
    //                          【strategyVipTwo=instanceTwo】
    private ApplicationContext act;

    //定義一個Map,存盤等級和策略的關系,通過application.yml配置注入進來
    private Map<Integer,String> strategyMap;

    //3、根據會員等級獲取策略【1】【2】【3】
    public Strategy getStrategy(Integer level){
        //根據等級獲取策略ID
        String id = strategyMap.get(level);
        //根據ID獲取對應實體
        return act.getBean(id,Strategy.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        act=applicationContext;
    }
}

等級策略配置:修改application.yml,將如下策略配置進去

#策略配置
strategy:
  strategyMap:
    1: strategyVipOne
    2: strategyVipTwo
    3: strategyVipThree
    4: strategyVipFour

等級控制:修改UserHandler添加等級屬性

file

修改UserHandlerShare定義等級,代碼如下:

file
裝飾者模式中修改VipMoneySum的價格運算,代碼如下:

file

測驗:

file

本文由傳智教育博學谷教研團隊發布,

如果本文對您有幫助,歡迎關注點贊;如果您有任何建議也可留言評論私信,您的支持是我堅持創作的動力,

轉載請注明出處!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/518464.html

標籤:Java

上一篇:SpringBoot(八) - 統一資料回傳,統一分頁工具,統一例外處理

下一篇:常見的一些威脅情報分

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more