1. Java反射機制是什么?
Java 反射機制是指在運行時動態(tài)地獲取和操作類的信息、調(diào)用對象的方法和訪問對象的屬性的能力
。通過反射,可以在程序運行時分析和修改類的結(jié)構(gòu)、行為和狀態(tài)。
Java 反射機制提供了以下功能:
- 獲取類的信息:可以獲取類的名稱、修飾符、父類、實現(xiàn)的接口等。
- 創(chuàng)建對象:可以通過反射實例化對象,即使不知道具體類名。
- 調(diào)用方法:可以通過反射獲取類的方法,并調(diào)用這些方法。
- 訪問和修改字段:可以通過反射獲取和設(shè)置類的字段值。
- 動態(tài)代理:可以使用反射動態(tài)生成代理對象,實現(xiàn) AOP(面向切面編程)等功能。
- 操作數(shù)組:可以通過反射創(chuàng)建、訪問和修改數(shù)組對象。
- 注解處理:可以通過反射獲取類、方法、字段上的注解,并進行相應(yīng)的處理。
通過反射機制,可以在運行時動態(tài)地操作類和對象,解耦了編譯時的依賴關(guān)系,提供了更大的靈活性和擴展性。
但是需要注意,由于反射涉及到動態(tài)生成和修改類的結(jié)構(gòu),可能會影響性能,并且需要額外的權(quán)限。
1.2 Java反射例子
正射:
一般情況下,我們使用某個類,都會知道這個類,以及要用它來做什么,可以直接通過new實例化創(chuàng)建對象
,然后使用這個對象對類進行操作,這個就屬于正射~
Student類
public class Student {
private int id;
public void setId(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Student student = new Student();
student.setId(1);
System.out.println("正射獲取ID :"+student.getId());
//輸出:正射獲取ID :1
正射很好理解,就不多說了,現(xiàn)在來講講反射,同樣可以創(chuàng)建對象,調(diào)用對象的方法,等等正射能做的事,通過反射都能做
反射:
反射則是一開始并不知道要初始化的是什么類
,無法使用new來實例化創(chuàng)建對象
,主要是通過JDK提供的反射API
來實現(xiàn),在運行時才知道要操作的是什么類,并且可以獲取到類的完整構(gòu)造以及調(diào)用對應(yīng)的方法
,這就是反射~
//1、通過 Class.forName 方法獲取 Srudent類的 Class 對象
Class<?> clz = Class.forName("Reflect.Student");
//2、通過 getConstructor 方法獲取類的無參構(gòu)造方法
// Constructor<?> constructor = clz.getConstructor();//可省略
//3、通過 newInstance 方法實例化對象
// Object stu = constructor.newInstance();//可省略
Student stu = (Student) clz.newInstance();//(代替2-3)直接通過字節(jié)碼對象Class.newInstance()實例化對象
//4、通過 getMethod 方法獲取類的方法信息("setId", int.class) setId為方法名 int.class為方法參數(shù)類型
Method setId = clz.getMethod("setId", int.class);
//5、通過 invoke 方法調(diào)用該方法
setId.invoke(stu,3);
//也可以直接通過對象stu 調(diào)用Student類的方法
Method getId = clz.getMethod("getId");
System.out.println("反射獲取ID :"+getId.invoke(stu));
//輸出:反射獲取ID :3
總結(jié)就是:
上述例子反射的調(diào)用過程,可以看到獲取一個類的反射對象
,主要過程為:
- 獲取類的Class實例對象
- 根據(jù)Class實例對象獲取Constructor對象
- 再根據(jù)Constructor對象的newInstance方法獲取到類的反射對象
獲取到類的反射對象后,就可以對類進行操作了
~ 例如,上述示例中對類的方法進行調(diào)用過程為:
-
根據(jù)Class實例對象獲取到類的Method對象
-
再根據(jù)Method對象的invoke方法調(diào)用到具體類的方法
上面示例反向調(diào)用過程中我們是通過Class.forName("類的全局定名")
這種方式來獲取到類的Class實例對象,除了這種,常用的還有其他兩種
2. Java反射機制中獲取Class的三種方式及區(qū)別?
獲取類的java.lang.Class實例對象,常見的三種方式分別為:
- 通過
MyClass.class
獲取,這里的MyClass指具體類~~ - 通過
Class.forName("類的全局定名")
獲取,全局定名為包名+類名 - 通過
new MyClass().getClass()
獲取,這里的MyClass指具體類~
Class<?> clz = Class.forName("Reflect.Student"); //全類名獲取
Class<Student> clz = Student.class; //類.class獲取
Class<? extends Student> clz = new Student().getClass();//newClass().getClass()獲取
區(qū)別在于:
- 通過MyClass.class獲取,JVM會使用ClassLoader類加載器將類加載到內(nèi)存中,但并不會做任何類的初始化工作,返回java.lang.Class對象
- 通過Class.forName(“類的全局定名”)獲取,同樣,類會被JVM加載到內(nèi)存中,并且會進行類的靜態(tài)初始化工作,返回java.lang.Class對象
- 通過newMyClass().getClass()獲取,這種方式使用了new進行實例化操作,因此靜態(tài)初始化和非靜態(tài)初始化工作都會進行,getClass方法屬于頂級Object類中的方法,任何子類對象都可以調(diào)用,哪個子類調(diào)用,就返回那個子類的java.lang.Class對象
具體體現(xiàn)可參考鏈接:吾日三省賈斯汀----->Java–反射機制原理、幾種Class獲取方式及應(yīng)用場景
總結(jié):
- MyClass.class
不會做任何類的初始化工作
- Class.forName會進行類的
靜態(tài)初始化工作
- new MyClass().getClass
靜態(tài)初始化
和非靜態(tài)初始化
工作都會進行 - 使用這三種方式任意一種最終在JVM加載到內(nèi)存中都會是
內(nèi)存地址相同
的(jvm類加載的雙親委派機制,類只會被加載一次)
3. Java反射機制的應(yīng)用場景有哪些?
3.1. 優(yōu)化靜態(tài)工廠模式(解耦)
3.1.1 優(yōu)化前(工廠類和產(chǎn)品類耦合)
簡單工廠示例:
步驟1:創(chuàng)建抽象產(chǎn)品類
public interface Product {
void show();
}
步驟2:創(chuàng)建具體產(chǎn)品類:
public class ProductA implements Product {
@Override
public void show() {
System.out.println("生產(chǎn)了產(chǎn)品A");
}
}
public class ProductB implements Product {
@Override
public void show() {
System.out.println("生產(chǎn)了產(chǎn)品B");
}
}
public class ProductC implements Product {
@Override
public void show() {
System.out.println("生產(chǎn)了產(chǎn)品C");
}
}
步驟3:創(chuàng)建簡單工廠類
/**
* 靜態(tài)工廠
*/
public class Product_factory {
/**
* todo 常規(guī)工廠 (工廠和產(chǎn)品耦合)
*/
public static Product createProduct(String productName) throws Exception {
Product product = null;
if ("A".equals(productName)) {
product = new ProductA();
}else if("B".equals(productName)){
product = new ProductB();
}else if("C".equals(productName)){
product = new ProductC();
}else{
throw new Exception("沒有該產(chǎn)品");
}
return product;
}
}
步驟4:調(diào)用簡單工廠類
public static void main(String[] args) throws Exception {
//通過工廠生產(chǎn)對象A
Product A = Product_factory.createProduct("A");
//調(diào)用A對象的方法
A.show();
Product B = Product_factory.createProduct("B");
B.show();
Product C = Product_factory.createProduct("C");
C.show();
}
輸出:
A產(chǎn)品被生產(chǎn)了
B產(chǎn)品被生產(chǎn)了
B產(chǎn)品被生產(chǎn)了
優(yōu)化前的弊端
每增加一個接口的子類,必須修改工廠類的邏輯
例如我需要加一個產(chǎn)品C,就必須要修改工廠,追加產(chǎn)品C的生產(chǎn)過程
public class ProductD implements Product{
@Override
public void show() {
System.out.println("D產(chǎn)品被生產(chǎn)了");
}
}
這樣就違背了開閉原則(在追加新產(chǎn)品時,應(yīng)該不要修改原來已有的代碼,而是在原來的基礎(chǔ)上擴展)
這個時候反射就可以克服這個弊端(將產(chǎn)品和工廠解耦)
3.1.2 反射優(yōu)化后(工廠類和產(chǎn)品類解耦合)
優(yōu)化后
優(yōu)化工廠類
/**
* todo 反射工廠(通過產(chǎn)品全類名來創(chuàng)建產(chǎn)品) (工廠和產(chǎn)品解耦合)
*/
public static Product createProductReflect(String Full_product_name ) {
Product product = null;
try {
//根據(jù)產(chǎn)品類的全類名反射生成產(chǎn)品類的class字節(jié)對象
Class<?> aClass = Class.forName(Full_product_name);
//通過產(chǎn)品類的字節(jié)碼對象 創(chuàng)建真實對象
product = (Product) aClass.newInstance();
}catch (Exception e){
e.printStackTrace();
}
return product;
}
測試類:
//全類名反射通過工廠生產(chǎn)產(chǎn)品
Product A = Product_factory.createProductReflect("factory.Simple_factory.ProductA");
A.show();
Product B = Product_factory.createProductReflect("factory.Simple_factory.ProductB");
B.show();
Product C = Product_factory.createProductReflect("factory.Simple_factory.ProductC");
B.show();
輸出:
A產(chǎn)品被生產(chǎn)了
B產(chǎn)品被生產(chǎn)了
B產(chǎn)品被生產(chǎn)了
這樣如果要追加產(chǎn)品D,只需要新增產(chǎn)品D,無需修改工廠,只需要在需要D產(chǎn)品時,通過對工廠引入產(chǎn)品的全類名就可以生產(chǎn)產(chǎn)品類對象
新增產(chǎn)品D
public class ProductD implements Product{
@Override
public void show() {
System.out.println("D產(chǎn)品被生產(chǎn)了");
}
}
通過對工廠引入產(chǎn)品的全類名生產(chǎn)產(chǎn)品類對象
Product D = Product_factory.createProductReflect("factory.Simple_factory.ProductD");
D.show();
輸出:
D產(chǎn)品被生產(chǎn)了
使用Java反射機制優(yōu)化簡單工廠模式后,可以看到,
不論具體產(chǎn)品類更新多頻繁,都不需要再修改工廠類
,從而解決了普通簡單工廠模式操作成本高和系統(tǒng)復(fù)雜性高的問題~
3.1.3 利用反射再優(yōu)化(配置文件配置全類名映射)
簡單工廠模式的工廠類采用Java反射機制進行優(yōu)化后,此時的仍然存在這樣一個問題,
子類的全局定名(包名+類名)是寫死的
,但是實際上開發(fā)者在寫代碼時是很難提前預(yù)知所有的子類的全局定名(包名+類名)的,因此需要進行二次優(yōu)化~
優(yōu)化思路:
通過配置文件方式,
統(tǒng)一定義類名對應(yīng)全局定名(包名+類名),將配置文件存放到資源目錄下
,程序運行時通過ClassLoader類加載器動態(tài)獲取到配置文件中定義的子類的全局定名
~
再次優(yōu)化步驟2:配置類名對應(yīng)全局定名(包名+類名)
創(chuàng)建屬性配置文件Product.properties
//產(chǎn)品抽象類Product相關(guān)子類的全局定名(包名+類名)定義
//key value
ProductA = com.justin.java.lang.ProductA
ProductB = com.justin.java.lang.ProductB
ProductC = com.justin.java.lang.ProductC
再次優(yōu)化步驟3:修改調(diào)用工廠類
public class FactoryTest {
@Test
public void test() throws IOException {
ClassLoader classLoader = this.getClass().getClassLoader();
Properties prop = new Properties();
prop.load(classLoader.getResourceAsStream("Product.properties"));
String className = "";
try {
className = prop.getProperty("ProductA");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("沒有A這款產(chǎn)品,無法生產(chǎn)~");
}
try {
className = prop.getProperty("ProductB");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("沒有B這款產(chǎn)品,無法生產(chǎn)~");
}
try {
className = prop.getProperty("ProductC");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("沒有C這款產(chǎn)品,無法生產(chǎn)~");
}
}
}
輸出:
生產(chǎn)了產(chǎn)品A
生產(chǎn)了產(chǎn)品B
生產(chǎn)了產(chǎn)品C
相比較優(yōu)化前,將產(chǎn)品類對應(yīng)的全類名,放在了配置文件里面,在生產(chǎn)產(chǎn)品時,根據(jù)以下配置將產(chǎn)品類對應(yīng)的全類名從配置文件里面取到,然后再根據(jù)全類名反射構(gòu)建產(chǎn)品對象:
ClassLoader classLoader = this.getClass().getClassLoader();
Properties prop = new Properties();
prop.load(classLoader.getResourceAsStream("Product.properties"));
classLoader.getResourceAsStream("Product.properties")
是通過類加載器來獲取資源文件 “Product.properties” 的輸入流。getResourceAsStream() 方法是 java.lang.ClassLoader 類的一個方法,它可以根據(jù)給定的路徑從類路徑中查找并返回對應(yīng)的資源文件的輸入流。
prop.load()
是 java.util.Properties 類的一個方法,用于將輸入流中的數(shù)據(jù)加載到屬性對象中。
在這行代碼中,prop 是一個屬性對象,通過調(diào)用 prop.load() 方法,并將類加載器獲取到的資源文件輸入流作為參數(shù),實現(xiàn)將資源文件的內(nèi)容加載到屬性對象中。
綜上所述,這行代碼的作用是使用類加載器加載名為 "Product.properties" 的資源文件,并將其讀取為屬性對象。這樣可以方便地獲取和操作資源文件中定義的屬性值。
3.2 代理模式中的動態(tài)代理實現(xiàn)
代理模式是什么?
代理(Proxy)模式是一種設(shè)計模式,通過代理對象來訪問目標(biāo)對象,還可以在不修改目標(biāo)對象的情況下,對代理對象進行拓展,增強目標(biāo)對象的功能~
3.2.1 靜態(tài)代理
靜態(tài)代理是在編譯時就已經(jīng)確定代理類和被代理類的關(guān)系
,代理類和被代理類實現(xiàn)同一個接口或繼承同一個父類
。代理類持有對被代理對象的引用(代理對象),在調(diào)用目標(biāo)方法之前或之后執(zhí)行一些額外的邏輯
。靜態(tài)代理的代碼在編譯時就已經(jīng)確定,因此代理類需要為每一個被代理類編寫一個對應(yīng)的代理類。這種方式的好處是簡單直觀,容易理解和掌握,但是當(dāng)被代理的類較多時,會產(chǎn)生大量的重復(fù)代碼。
也就是一個被代理類對應(yīng)著一個代理類,但是當(dāng)被代理的類較多時,會產(chǎn)生大量的重復(fù)代碼。
其實靜態(tài)代理通俗的來說,就是
被代理類和代理類共同實現(xiàn)一個接口
,并且實現(xiàn)接口的方法,代理類通過聲明被代理類的實例化對象(代理對象)
(也就是編譯時就已經(jīng)確定代理類和被代理類的關(guān)系),通過調(diào)用和被代理類一樣的方法
(這就是為什么要共同實現(xiàn)一個接口的方法),并且在代理類方法中通過代理對象調(diào)用被代理類的方法
(可以在被代理類的方法做出增代理類設(shè)置的增強),從而達到代理或代理增強的效果
3.2.2 動態(tài)代理
動態(tài)代理是在運行時生成代理類,不需要對每個被代理類都編寫一個對應(yīng)的代理類
。它通過使用 Java 的反射機制,在運行時動態(tài)地創(chuàng)建代理類和代理對象
。代理類實現(xiàn)一個統(tǒng)一的接口或繼承一個父類
,并持有一個 InvocationHandler 對象作為其調(diào)用處理器。在調(diào)用目標(biāo)方法時,代理類會將方法調(diào)用轉(zhuǎn)發(fā)給 InvocationHandler 處理器,并可以在調(diào)用之前或之后添加額外的邏輯
,動態(tài)代理的優(yōu)勢在于可以更加靈活地動態(tài)創(chuàng)建代理對象,減少了重復(fù)的代理類編寫,適用于代理類較多或需要動態(tài)管理代理對象的場景。
3.2.2.1 JDK動態(tài)代理(反射構(gòu)造代理對象)
JDK 原生動態(tài)代理,主要利用了JDK API的java.lang.reflect.Proxy
和java.lang.relfect.InnvocationHandler
這兩個類來實現(xiàn)~
通過java.lang.reflect.Proxy代理類的newProxyInstance方法
,傳遞3個參數(shù),分別是:
- 目標(biāo)對象的加載器 通過Object.getClass().getClassLoader方式獲取
- 目標(biāo)對象的實現(xiàn)接口類型 通過Object.getClass().getInterfaces()方式獲取
- InnvocationHandler事件處理器 通過new實例化對象并重寫invoke方法方式獲取
步驟:
- 創(chuàng)建被代理類的接口,讓被代理類實現(xiàn)接口方法
- 創(chuàng)建代理類,通過構(gòu)造方法將被代理類對象注入到代理類
- 通過設(shè)置3個參數(shù)(1、目標(biāo)對象的加載器,2、目標(biāo)對象的實現(xiàn)接口類型,3、InnvocationHandler事件處理器(增強目標(biāo)對象的方法))
- 編寫一個返回代理對象的方法:通過Proxy代理類的newProxyInstance方法將三個參數(shù)傳入,返回生成的代理對象。
- 在測試類中將被代理類對象通過代理類有參構(gòu)造引入,然后生成代理對象,執(zhí)行增強的方法
舉例:
產(chǎn)品類接口Product_interface
/**
* 被代理類接口
*/
public interface Product_interface {
void sell();
}
產(chǎn)品類Product
/**
* @Description TODO 被代理類
**/
public class Product implements Product_interface{
@Override
public void sell() {
System.out.println("生產(chǎn)了一臺iphone 15 pro max");
}
}
代理類ProductProxy
/**
* @Description TODO 動態(tài)代理類
**/
public class ProductProxy {
private Object target;//被代理的對象
public ProductProxy (Object target){//通過構(gòu)造方法引入被代理對象
this.target = target;
}
/**
* 利用JDK API獲取到代理對象
* @return
*/
public Object getProxyInstance(){
//目標(biāo)對象的加載器
ClassLoader classLoader = target.getClass().getClassLoader();//反射
//目標(biāo)對象的實現(xiàn)接口類型
Class<?>[] interfaces = target.getClass().getInterfaces();//反射
//InvocationHandler事件處理器實例對象
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增強
System.out.println("提前接到消息的黃牛正在蹲搶中.............");
// 執(zhí)行目標(biāo)對象方法
Object value = method.invoke(target, args);
//后置增強
System.out.println("無貨......................");
return null;//若無返回值 就直接返回 若需要返回一個返回值 就如實返回
}
};
//傳入3個參數(shù),創(chuàng)建代理類的實例對象,并返回
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
}
}
測試類ProductBuyTest
public static void main(String[] args) {
Product_interface product = new Product();//創(chuàng)建被代理類對象
ProductProxy productProxy = new ProductProxy(product);//將被代理類的對象交給代理類
Product_interface proxy = (Product_interface) productProxy.getProxyInstance();//由代理類生成代理對象
proxy.sell();//通過代理對象執(zhí)行被代理類的增強方法
}
輸出:
提前接到消息的黃牛正在蹲搶中.............
富士康生產(chǎn)了一臺iphone 15 pro max
無貨......................
JDK原生動態(tài)代理中,獲取代理示例對象過程中,獲取目標(biāo)對象的類加載器,通過
target.getClass().getClassLoader獲取到目標(biāo)對象的類加載器
,target.getClass()方式獲取目標(biāo)對象的Class實例對象
使用的就是Java反射機制來實現(xiàn)的~
3.2.2.2 cglib動態(tài)代理(沒有用反射)
CGLIB(Code Generation Library)是一個基于ASM(一個Java字節(jié)碼操作框架)的代碼生成庫
,它可以在運行時動態(tài)地生成目標(biāo)類的子類
,從而實現(xiàn)對目標(biāo)類的代理
。
使用時需引入cglib依賴
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
產(chǎn)品類Product
/**
* 被代理類
*/
public class Product {
public void sell() {
System.out.println("富士康生產(chǎn)了一臺iphone 15 pro max");
}
}
代理類CglibProxy
/**
* @Description: 代理類 用來獲取代理對象
*/
public class CglibProxy implements MethodInterceptor {
private Object target;//被代理的對象
public CglibProxy (Object target){//通過構(gòu)造方法引入被代理對象
this.target = target;
}
/**
* 用于構(gòu)造代理對象
* @return
*/
public Object getProxyObject() {
//創(chuàng)建Enhancer對象,類似于JDK代理中的Proxy類
Enhancer enhancer = new Enhancer();
//設(shè)置父類的字節(jié)碼對象。指定父類
enhancer.setSuperclass(target.getClass());
//設(shè)置回調(diào)函數(shù)
enhancer.setCallback(this);
//創(chuàng)建代理對象
Object proxyObject = enhancer.create();
return proxyObject;
}
/*
* 攔截器
* 1.目標(biāo)對象的方法調(diào)用
* 2.行為增強
* 參數(shù) o: cglib 動態(tài)生成的代理類的實例
* method:實體類所調(diào)用的都被代理的方法的引用
* objects 參數(shù)列表
* methodProxy:生成的代理類對方法的代理引用
* */
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//前置增強
System.out.println("提前接到消息的黃牛正在蹲搶中.............");
//要調(diào)用目標(biāo)對象的方法
Object obj = method.invoke(target, objects);
//后置增強
System.out.println("無貨......................");
return null;
}
}
輸出:
提前接到消息的黃牛正在蹲搶中.............
富士康生產(chǎn)了一臺iphone 15 pro max
無貨......................
實現(xiàn)步驟:文章來源:http://www.zghlxwxcb.cn/news/detail-697917.html
- 引入cglib依賴
- 創(chuàng)建被代理類
- 創(chuàng)建cglib代理類并且實現(xiàn)MethodInterceptor 接口,重寫intercept方法
- 通過構(gòu)造方法注入被代理類對象給代理對象賦值
- 編寫一個返回代理對象的方法:
1、創(chuàng)建Enhancer對象,
2、給Enhancer對象設(shè)置父類(被代理類)的字節(jié)碼對象,
3、給Enhancer對象設(shè)置回調(diào)函數(shù),
4、創(chuàng)建代理對象 返回代理對象 - 在intercept方法里面調(diào)用目標(biāo)對象的方法(增強)
- 在測試類中將被代理類對象通過代理類有參構(gòu)造引入,然后生成代理對象,執(zhí)行增強的方法
參考來自:
Java–反射機制原理、幾種Class獲取方式及應(yīng)用場景—作者:吾日三省賈斯汀文章來源地址http://www.zghlxwxcb.cn/news/detail-697917.html
到了這里,關(guān)于【Java基礎(chǔ)】深入理解反射、反射的應(yīng)用(工廠模式、代理模式)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!