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 種設計模式,

? 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 物件,

創建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

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

雙重校驗鎖: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類,

? 如果我們在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,這種分布式海量檔案管理解決方案,這里不僅在代碼層面充分運用了代理模式,在架構層面也充分運用了代理模式,

3.5.2 分布式檔案代理服務器實作
1)實作分析
? 基于代理模式,我們實作檔案上傳分別路由到aliyunOSS和FastDFS,用例圖如下:

講解:
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/archive/2022/10/19/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/archive/2022/10/19/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/archive/2022/10/19/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>

FastDFS地址:
<http://192.168.211.137:28181/group1/M00/00/00/wKjTiV7kLtGASw5TAADJ9uXzZAQ622.png>

4 享元模式
定義:
運用共享技術來有効地支持大量細粒度物件的復用,它通過共享已經存在的物件來大幅度減少需要創建的物件數量、避免大量相似類的開銷,從而提高系統資源的利用率,
享元模式和單利的區別:
單利是物件只能自己創建自己,整個應用中只有1個物件
享元模式根據需要共享,不限制被誰創建(有可能有多個物件實體)
優點:
特定環境下,相同物件只要保存一份,這降低了系統中物件的數量,從而降低了系統中細粒度物件給記憶體帶來的壓力,
缺點:
為了使物件可以共享,需要將一些不能共享的狀態外部化,這將增加程式的復雜性,
4.1 享元模式實戰
案例:用戶下單,會話共享
4.2 會話跟蹤分析
? 會話跟蹤,如果是傳統專案用Session或者是Cookie,全專案通用,但在微服務專案中,不用Session也不用Cookie,所以想要在微服務專案中實作會話跟蹤,是有一定難度的,
? 當前微服務專案中,身份識別的主流方法是前端將用戶令牌存盤到請求頭中,每次請求將請求頭中的令牌攜帶到后臺,后臺每次從請求頭中獲取令牌來識別用戶身份,
? 我們在專案操作程序中,很多地方都會用到用戶身份資訊,比如下訂單的時候,要知道當前訂單屬于哪個用戶,記錄下單關鍵日志的時候,需要記錄用戶操作的資訊以及用戶資訊,關鍵日志記錄我們一般用AOP進行攔截操作,此時沒法直接把用戶身份資訊傳給AOP,這個時候我們可以利用享元模式實作用戶會話資訊共享操作,操作流程如下圖:

4.3 會話共享案例實作
? 基于上面的分析,我們采用享元模式實作用戶會話共享操作,要解決如下幾個問題:
1、用戶會話共享
2、會話多執行緒安全
3、訂單資料用戶資訊獲取
4、AOP日志記錄用戶資訊獲取
定義共享組件:Session
? Session里面定義了每個執行緒中不變的用戶身份資訊username、role、sex,其他的是可能存在變化的資料可以寫一個類繼承該類,
@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攔截器的作用是用于初始化用戶訪問的時候用戶的身份資訊,并將身份資訊存盤到ThreadSession的ThreadLocal中,在用戶訪問方法結束,銷毀ThreadSession的ThreadLocal中會話,代碼如下:
@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,代碼如下:

添加訂單,日志輸出可以看到呼叫添加訂單和修改庫存時,都記錄了日志,并且獲取了用戶會話,效果如下:
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)
添加的訂單資料庫資料中也擁有用戶資訊,效果如下:

5 裝飾者模式
定義:
動態的向一個現有的物件添加新的功能,同時又不改變其結構,它屬于結構型模式,
擴展新功能,不需要修改現有物件就能實作--->裝飾者模式
優點:
裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實作類的功能,
缺點:
多層裝飾比較復雜,
5.1 裝飾者模式實戰
案例:結算價格計算,根據不同價格嵌套運算
5.2 訂單結算價格實戰
? 在訂單提交的時候,訂單價格和結算價格其實是兩碼事,訂單價格是當前商品成交價格,而結算價格是用戶最終需要支付的金額,最終支付的金額并不是一成不變,它也并不是商品成交價格,能改變結算價格的因素很多,比如滿100減10元,VIP用戶再減5塊,訂單結算金額計算我們就可以采用裝飾者模式,

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/archive/2022/10/19/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/archive/2022/10/19/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/archive/2022/10/19/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);
}
}
支付金額計算:修改OrderServiceImpl的add()方法,添加訂單金額以及訂單支付金額的計算功能,代碼如下:

測驗效果:
測驗資料中,我們選擇購買1件商品,當前登錄用戶為王五,擁有5個金幣,當前購買的商品id=1,商品單價是150元,滿減100,VIP優惠5元,最終支付135元,
{
"itemId":"1",
"id":"1",
"status":1,
"num":1,
"couponsId":"1"
}
測驗生成的訂單如下:

不僅如此,我們可以隨時撤掉滿減和Vip優惠功能,
6 策略模式
定義:
策略模式是對演算法的包裝,把演算法的使用和演算法本身分隔開,委派給不同的物件管理,策略模式通常把一系列的演算法包裝到一系列的策略類里面,作為一個抽象策略類的子類或者介面的實作類,
? 簡單來說就是就定義一個策略介面,策略類去實作該介面去定義不同的策略,然后定義一個環境(Context,也就是需要用到策略的物件)類,以策略介面作為成員變數,根據環境來使用具體的策略,
優點:
1、演算法可以自由切換,
2、避免使用多重條件判斷,
3、擴展性良好,
缺點:
1、策略類會增多,
2、所有策略類都需要對外暴露,
6.1 策略模式實戰
案例:結算價格計算,根據Vip不同等級進行運算
6.2 不同VIP優惠價格分析

? 用戶在購買商品的時候,很多時候會根據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/archive/2022/10/19/strategyVipOne")
public class StrategyVipOne implements Strategy {
//普通會員,沒有優惠
@Override
public Integer payMoney(Integer payMoney) {
return payMoney;
}
}
定義Vip1策略:StrategyVipTwo
@Component(value = "https://www.cnblogs.com/jiagooushi/archive/2022/10/19/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/archive/2022/10/19/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/archive/2022/10/19/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添加等級屬性

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

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

測驗:

本文由
傳智教育博學谷教研團隊發布,如果本文對您有幫助,歡迎
關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/518484.html
標籤:其他
上一篇:SpringBoot(八) - 統一資料回傳,統一分頁工具,統一例外處理
下一篇:常見的一些威脅情報分
