## 9.1、環境搭建 ### 9.1.1、創建module ![image](https://img2023.cnblogs.com/blog/2052479/202308/2052479-20230806234218377-617105837.png) ### 9.1.2、選擇maven ![i ...
9.1、環境搭建
9.1.1、創建module
9.1.2、選擇maven
9.1.3、設置module名稱和路徑
9.1.4、module初始狀態
9.1.5、配置打包方式和依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.rain</groupId>
<artifactId>spring_proxy</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- junit測試 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
9.2、場景模擬
9.2.1、創建Calculator介面及實現類
package org.rain.spring.proxy;
/**
* @author liaojy
* @date 2023/8/6 - 23:53
*/
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
package org.rain.spring.proxy;
/**
* @author liaojy
* @date 2023/8/6 - 23:55
*/
public class CalculatorImpl implements Calculator {
public int add(int i, int j) {
int result = i + j;
System.out.println("方法內部 result = " + result);
return result;
}
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法內部 result = " + result);
return result;
}
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法內部 result = " + result);
return result;
}
public int div(int i, int j) {
int result = i / j;
System.out.println("方法內部 result = " + result);
return result;
}
}
9.2.2、為Calculator實現類增加日誌功能
package org.rain.spring.proxy;
/**
* @author liaojy
* @date 2023/8/6 - 23:55
*/
public class CalculatorImpl implements Calculator {
public int add(int i, int j) {
System.out.println("[日誌] add 方法開始了,參數是:" + i + "," + j);
int result = i + j;
System.out.println("方法內部 result = " + result);
System.out.println("[日誌] add 方法結束了,結果是:" + result);
return result;
}
public int sub(int i, int j) {
System.out.println("[日誌] sub 方法開始了,參數是:" + i + "," + j);
int result = i - j;
System.out.println("方法內部 result = " + result);
System.out.println("[日誌] sub 方法結束了,結果是:" + result);
return result;
}
public int mul(int i, int j) {
System.out.println("[日誌] mul 方法開始了,參數是:" + i + "," + j);
int result = i * j;
System.out.println("方法內部 result = " + result);
System.out.println("[日誌] mul 方法結束了,結果是:" + result);
return result;
}
public int div(int i, int j) {
System.out.println("[日誌] div 方法開始了,參數是:" + i + "," + j);
int result = i / j;
System.out.println("方法內部 result = " + result);
System.out.println("[日誌] div 方法結束了,結果是:" + result);
return result;
}
}
9.3、場景分析
9.3.1、代碼缺陷
關於帶日誌功能的實現類,有如下缺陷:
-
附加功能對核心業務功能有干擾,降低了開發效率
-
附加功能分散在各個業務功能方法中,不利於統一維護
9.3.2、解決思路
解決這兩個問題,核心方式就是:解耦;把附加功能從業務功能代碼中抽取出來
9.3.3、技術難點
要抽取的代碼在方法內部,靠以前把子類中的重覆代碼抽取到父類的方式沒法解決,因此需要引入新的技術(代理模式)。
9.4、代理模式的概述
9.4.1、概念
-
代理模式是二十三種設計模式中的一種,屬於結構型模式
-
它的思想就是在不改動目標方法代碼的基礎上,增強目標方法的功能
-
它的實現就是通過提供一個代理類,讓我們在調用目標方法的時候,不再是直接對目標方法進行調用,而是通過代理類間接調用目標方法
-
它的作用就是把不屬於目標方法核心邏輯的代碼從目標方法中剝離出來,從而實現解耦和統一維護
9.4.2、術語
-
目標:封裝了核心功能代碼,的類、對象、方法
-
代理:封裝了增強功能代碼、且能調用目標,的類、對象、方法
9.4.3、生活中的目標和代理
-
廣告商找大明星(目標)拍廣告,需要經過經紀人(代理)
-
買房者找賣房者(目標)購房,需要經過房產中介(代理)
9.5、靜態代理
先將實現類CalculatorImpl還原為沒有增加日誌功能的狀態,即9.2.1小節的狀態
9.5.1、創建靜態代理類CalculatorStaticProxy
註意:代理類和目標類要實現相同的介面,這樣能保證它們有相同的方法列表
package org.rain.spring.proxy;
/**
* @author liaojy
* @date 2023/8/7 - 12:56
*/
public class CalculatorStaticProxy implements Calculator {
// 將被代理的目標對象聲明為成員變數
private Calculator target;
public CalculatorStaticProxy(Calculator target) {
this.target = target;
}
public int add(int i, int j) {
// 附加功能由代理類中的代理方法來實現
System.out.println("[日誌] add 方法開始了,參數是:" + i + "," + j);
// 通過目標對象來實現核心業務邏輯
int addResult = target.add(i, j);
System.out.println("[日誌] add 方法結束了,結果是:" + addResult);
return addResult;
}
public int sub(int i, int j) {
System.out.println("[日誌] sub 方法開始了,參數是:" + i + "," + j);
int subResult = target.sub(i, j);
System.out.println("[日誌] sub 方法結束了,結果是:" + subResult);
return subResult;
}
public int mul(int i, int j) {
System.out.println("[日誌] mul 方法開始了,參數是:" + i + "," + j);
int mulResult = target.mul(i, j);
System.out.println("[日誌] mul 方法結束了,結果是:" + mulResult);
return mulResult;
}
public int div(int i, int j) {
System.out.println("[日誌] div 方法開始了,參數是:" + i + "," + j);
int divResult = target.div(i, j);
System.out.println("[日誌] div 方法結束了,結果是:" + divResult);
return divResult;
}
}
9.5.2、測試
package org.rain.spring.test;
import org.junit.Test;
import org.rain.spring.proxy.CalculatorImpl;
import org.rain.spring.proxy.CalculatorStaticProxy;
/**
* @author liaojy
* @date 2023/8/7 - 14:12
*/
public class ProxyTest {
@Test
public void testStaticProxy(){
CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(new CalculatorImpl());
int addResult = calculatorStaticProxy.add(1, 2);
System.out.println(addResult);
}
}
9.5.3、靜態代理的缺點
-
靜態代理確實實現瞭解耦,但是由於代碼都寫死了,完全不具備任何的靈活性
-
當其他目標類也需要附加日誌,就得創建更多靜態代理類,還是產生了大量重覆的代碼;而且日誌功能還是分散的,沒有統一管理
9.6、動態代理
動態代理的意思是,在代碼運行的過程中動態地生成目標類的代理類
9.6.1、創建生成代理對象的工廠類ProxyFactory
比起實現固定介面方法的靜態代理,動態代理的關鍵是能動態獲取並實現目標的介面方法;
因此動態代理能對任意目標對象的核心業務方法(介面方法)進行增強
package org.rain.spring.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @author liaojy
* @date 2023/8/7 - 23:07
*/
//這個類不是一個代理類而是一個工具(工廠)類,用於動態生成目標對象的代理對象
public class ProxyFactory {
//因為被代理的目標對象是任意的,所以目標對象變數的類型設為Object
private Object target;
//通過工廠類的有參構造方法,對目標對象變數進行賦值
public ProxyFactory(Object target) {
this.target = target;
}
//生成任意目標對象所對應的代理對象;因為不確定動態生成的代理對象的類型,所以返回值設為Object
public Object getPoxy(){
//通過目標對象獲取應用類載入器
ClassLoader classLoader = target.getClass().getClassLoader();
//獲取目標對象實現的所有介面的class對象所組成的數組
Class<?>[] interfaces = target.getClass().getInterfaces();
//通過InvocationHandler的匿名內部類,來設置代理類中如何重寫介面中的抽象方法
InvocationHandler invocationHandler = new InvocationHandler() {
//通過invoke方法來統一管理代理類中的方法該如何執行,該方法有三個參數
/**
* @param proxy:表示代理對象
* @param method:表示要執行的方法
* @param args:表示要執行的方法的參數列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在調用目標對象執行功能之前,加入額外的操作(這裡是附加日誌功能)
System.out.println("[日誌] "+method.getName()+" 方法開始了,參數是:" + Arrays.toString(args));
//固定寫法:調用目標對象實現的核心邏輯(最重要的步驟)
Object result = method.invoke(target, args);
//在調用目標對象執行功能之後,加入額外的操作(這裡是附加日誌功能)
System.out.println("[日誌] "+method.getName()+" 方法結束了,結果是:" + result);
//固定寫法:保證代理對象和目標對象的返回值一致
return result;
}
};
//返回(java.lang.reflect包下的)Proxy類的newProxyInstance方法所生產的代理對象
/**
* newProxyInstance方法有三個參數:
*
* 1、ClassLoader classLoader:指定載入(動態生成的)代理類的類載入器
* 類只有被載入後才能使用,(動態生成的)代理類需要用應用類載入器來載入
* 類載入器有四種:
* 跟類載入器(用於載入核心類庫)
* 擴展類載入器(用於載入擴展類庫)
* 應用類載入器(用於載入自己寫的類或第三方jar包中的類)
* 自定義類載入器
*
* 2、Class<?>[] interfaces:指定代理對象要實現的介面
* 這個參數用於保證代理對象和目標對象有相同的方法列表
*
* 3、InvocationHandler invocationHandle:指定調用處理器
* 該處理器設置了代理對象實現的介面的方法被調用時,該如何執行
*/
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
}
}
9.6.2、測試
@Test
public void testDynamicProxy(){
//根據目標對象來創建(動態)代理對象的工廠
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
//通過(動態)代理對象的工廠,生成目標對象所對應的(動態)代理對象
//因為代理類是動態生成的,所以不確定代理類的類型,因此用其所實現的介面類型
Calculator poxy = (Calculator) proxyFactory.getPoxy();
//調用動態代理對象的方法,該方法是目標對象核心業務方法的增強方法
int addResult = poxy.add(1, 2);
System.out.println(addResult);
}
9.6.3、增強的位置
除了可以在調用目標對象執行功能之前或之後,加入額外的操作之外;
還可以在調用目標對象執行功能發生異常時(catch位置)或在調用目標對象執行功能完畢時(finally位置),加入額外的操作
也就是說,(靜態或動態)代理能增強的位置一共有四個
//通過invoke方法來統一管理代理類中的方法該如何執行,該方法有三個參數
/**
* @param proxy:表示代理對象
* @param method:表示要執行的方法
* @param args:表示要執行的方法的參數列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
//第1個增強位置:在調用目標對象執行功能之前,加入額外的操作(這裡是附加日誌功能)
System.out.println("[日誌] "+method.getName()+" 方法開始了,參數是:" + Arrays.toString(args));
//固定寫法:調用目標對象實現的核心邏輯(最重要的步驟)
result = method.invoke(target, args);
//第2個增強位置:在調用目標對象執行功能之後,加入額外的操作(這裡是附加日誌功能)
System.out.println("[日誌] "+method.getName()+" 方法結束了,結果是:" + result);
} catch (Exception e) {
//第3個增強位置:在調用目標對象執行功能發生異常時,加入額外的操作(這裡是附加日誌功能)
System.out.println("[日誌] "+method.getName()+",異常:"+e.getMessage());
} finally {
//第4個增強位置:在調用目標對象執行功能完畢時,加入額外的操作(這裡是附加日誌功能)
System.out.println("[日誌] "+method.getName()+",方法執行完畢");
}
//固定寫法:保證代理對象和目標對象的返回值一致
return result;
}
9.6.4、擴展知識
-
動態代理有兩種方式:jdk動態代理(本示例)和cglib動態代理
-
jdk動態代理,要求目標必須實現介面,而且只能對目標所實現的介面方法進行增強
-
jdk動態代理,生成的代理類在com.sun.proxy包下,類名為:$proxy+數字
-
cglib動態代理,不要求目標必須實現介面,生成的代理類會繼承目標類,並且和目標類在相同的包下
-
雖然在實際中很少寫動態代理的代碼,但瞭解動態代理的思想,對學習Spring的AOP知識很有幫助