Java反射,註解,以及動態代理 基礎 最近在準備實習面試,被學長問到了Java反射,註解和動態代理的內容,發現有點自己有點懵,這幾天查了很多資料,就來說下自己的理解吧【如有錯誤,望指正】 Java反射 首先,我們得弄清一個,什麼是反射(Reflection)。簡單的來說,反射就是讓我們在程式運行的 ...
Java反射,註解,以及動態代理
基礎最近在準備實習面試,被學長問到了Java反射,註解和動態代理的內容,發現有點自己有點懵,這幾天查了很多資料,就來說下自己的理解吧【如有錯誤,望指正】
Java反射
首先,我們得弄清一個,什麼是反射(Reflection)。簡單的來說,反射就是讓我們在程式運行的時候能夠查看到類的信息,獲取並調用類的任意方法和屬性。
在Java運行時,系統會將所有的對象維護一個被稱為運行是的類型標識,然後這個信息跟蹤這每個對象所屬的類,我們可以和根據Java專門的類訪問這些信息,這個類就是Class
【實際上Class對象表示的是一個類型,它不一定是類,可能是基本數據類型,比如int】。
Class獲取方法
-
通過getClass()獲取
Student stu = new Student; Class c = stu.getClass(); //如果這個類在包裡面,則會將包名也列印出來 // getSimpleName只獲得類名 System.out.println(c.getName());
-
使用forName獲取
同樣,我們也可以使用靜態方法forName
獲得Class對象。例如:String className= "java.util.Random"; Class c2 = Class.forName(className);
當然,className必須為介面或者類名才能成功。
-
直接獲取
Class c3 = Student.class;
由Class得到對象
-
使用Class對象的newInstance()方法來創建Class對象
Class c3 = Test.class; Object o = c3.newInstance();
其中newInstance()會根據類的
預設構造器【無參構造器】
創建新的對象,如果沒有預設的構造器,就會報錯。假如我們的構造器裡面有參數怎麼辦,這時候我們就需要使用java.lang.reflect.Constructor
中的newInstance()
方法了。 -
使用Constructor類中的newInstance()
// getConstructor裡面是構造參數的形參內容 Constructor constructor = c3.getConstructor(String.class); Object o = constructor.newInstance("你好");
java反射中最重要的內容——檢查類的結構
在Java的java.lang.reflect中有三個類:Field、Method、Constructor分別來描述類的域【也就是變數】,方法和構造器。
-
Field的獲取以及方法
Class textClass = Test.class; // getDeclaredFields()獲得這個類的全部域 // getField()獲得公有域以及其父類的公有域 Field[] fields = textClass.getDeclaredFields();
簡單的來說,通過Field可以獲得:
變數的
許可權
——getModifiers(),返回int,然後通過Modifier.toString(int)獲得訪問許可權獲得變數的
類型
——getType()變數的
名字
——getName() -
Method的獲取以及方法
Class textClass = Test.class; // 同樣可以使用getMethods()和getDeclaredMethods()返回介面和類的方法 Method[] methods = textClass.getMethods();
通過Method可以獲取:
方法的
許可權
——getgetModifiers()方法的
返回值類型
——getReturnType(),方法返回類型為Class
,然後你懂得。方法的
所有參數
——Parameter[] parameters = method.getParameters();方法的
執行
——invoke()。在獲取一個方法後,我們可以使用invoke()
來調用這個方法。Object invoke(Object obj,Object...args),obj為實例化後的對象【對於靜態方法可以被設置null】,args為方法調用的參數
例如,
public class Test { public void say(String msg){ System.out.println(msg); } public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class c = Test.class; // 返回唯一的方法,第一個參數是方法的名字,第二個是方法參數的類型 Method method = c.getMethod("say", String.class); Object o = c.newInstance(); method.invoke(o,"你好"); } }
-
Constructor的獲取以及方法
Class textClass = Test.class; // 同樣getDeclaredConstructors()和getConstructors() Constructor[] constructors = aClass.getConstructors();
方法的的使用和Method差不多,但是它沒有getReturnType()方法。
這些方法我只是簡單的介紹了一下,詳細信息可以參考API。
神奇的Java註解
Java註解可以很簡單的說,就是為方法或者其他數據提供描述的東西。
它的本質就是一個介面,一個繼承了Annotation的介面。
-
基本java註解的類型
【元註解】:也就是在自定義一個註解時,可以註解在註解上面,有以下幾個元註解——>-
@Target:註解的作用目標,用來指明註解可以作用的目標是誰,例如類,方法或者欄位屬性,裡面的value【為一個ElementType數組】可以指明值如下:
ElementType.TYPE:允許被修飾的註解作用在類、介面和枚舉上
ElementType.FIELD:允許作用在屬性欄位上
ElementType.METHOD:允許作用在方法上
ElementType.PARAMETER:允許作用在方法參數上
ElementType.CONSTRUCTOR:允許作用在構造器上
ElementType.LOCAL_VARIABLE:允許作用在本地局部變數上
ElementType.ANNOTATION_TYPE:允許作用在註解上
ElementType.PACKAGE:允許作用在包上
-
@Retention:註解的生命周期,裡面的value【枚舉類型】可以指明值如下:
RetentionPolicy.SOURCE:當前註解編譯期可見,不會寫入 class 文件
RetentionPolicy.CLASS:類載入階段丟棄,會寫入 class 文件
RetentionPolicy.RUNTIME:永久保存,可以反射獲取
- @Documented:註解是否應當被包含在 JavaDoc 文檔中
- @Inherited:是否允許子類繼承該註解
- @Repeatable:重覆註解,允許這個註解在某個方法和其他數據上面重覆使用
【Java內置三大註解】:除了上述元註解,Java還內置了另外三種註解——>
- @Override:子類重寫父類的方法時,會使用該註解。用於檢查父類是否包含該註解
- @Deprecated:當某一方法和欄位不推薦使用時,使用該註解標註。
- @SuppressWarnings:壓制Java的警告
-
-
Java註解的自定義以及實現
Java註解的自定義如下
@Target(value = {ElementType.METHOD,ElementType.TYPE}) // 註解的作用地方 @Retention(value = RetentionPolicy.RUNTIME) // 註解的生命周期 public @interface TestAnnotation { String name() default "這是個類"; int time(); }
那麼我們該如果如何使用註解發揮作用呢?我們可以想想,如果我們能夠獲得註解的信息,那麼我們是不是就可以根據註解的信息來對方法做適當的調整。這時候,當然是大名鼎鼎的反射出馬了。
- java.lang.Package.getAnnotation(Class<A> annotationClass) 獲得這個指令類型的註解。
使用如下:
@TestAnnotation(time = 0) public class Test { @TestAnnotation(name = "這是個方法",time = 1) public void say(){ } public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { // 獲得類上面的註解 TestAnnotation classAnnotation = Test.class.getAnnotation(TestAnnotation.class); System.out.println("類的名字為:"+classAnnotation.name()+"------類的時間是"+classAnnotation.time()); Method method = Test.class.getMethod("say"); // 獲得方法上面的註解 TestAnnotation methodAnnotation = method.getAnnotation(TestAnnotation.class); System.out.println("方法的名字是:"+methodAnnotation.name()+"------方法的時間是"+methodAnnotation.time()); } } // 輸出: // 類的名字為:這是個類------類的時間是0 // 方法的名字是:這是個方法------方法的時間是1
現在我們知道如何進行自定義註解的使用了,那麼我們怎麼能夠根據註釋內容的不同去改變方法的執行呢?這時候,我們我們就可以使用
invoke()
方法了。舉個最簡單的慄子:
@TestAnnotation(name = "你好") public void say(String msg){ System.out.println("信息是:"+msg); } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { Method method = Test.class.getMethod("say",String.class); // 獲得方法上面的註解 TestAnnotation methodAnnotation = method.getAnnotation(TestAnnotation.class); // 執行方法 method.invoke(Test.class.newInstance(),methodAnnotation.name()); } // 輸出結果: // 信息是:你好
代理
代理就是給某個對象提供一個代理對象,並由代理對象控制對於原對象的訪問,即客戶不直接操控原對象,而是通過代理對象間接地操控原對象。
代理分為:
- 靜態代理:代理類是在編譯時就已經實現好了,成為了一個class文件
- 動態代理:是在程式運行時動態地生成類位元組碼,然後載入到JVM中
有幾個概念:
- 抽象角色:介面類
- 實現角色:實現類
- 代理角色:代理實現的類,最終使用的對象
靜態代理
在說動態代理之前,我們先說一下靜態代理,靜態代理很簡單,就是工廠模式。
那麼就讓我們來實現一下靜態代理吧
抽象角色:介面類
public interface TestService {
void say();
void play();
}
實現角色:實現類
public class TestServiceImpl implements TestService {
@Override
public void say() {
System.out.println("說話乎");
}
@Override
public void play() {
System.out.println("浪的飛起");
}
}
代理類
public class Test implements TestService{
private TestService testService;
public Test(TestService testService) {
this.testService = testService;
}
@Override
public void say() {
System.out.println("開始說話");
testService.say();
System.out.println("結束說話");
}
@Override
public void play() {
System.out.println("開始浪");
testService.play();
System.out.println("是個狼人");
}
public static void main(String[] args) {
TestServiceImpl testImpl = new TestServiceImpl();
Test test = new Test(testImpl);
test.play();
test.say();
}
}
在這裡面,我們可以看到,從外表看起來say()
和play()
方法都是由test
這個代理來完成的,但實際上,真正的執行者是TestServiceImpl
來完成的,test
只是在執行的時候加了一些事務邏輯。
既然有了靜態代理,為什麼我們還需要動態代理呢?從代碼中可以看出,代理類和實現類是一一對應的,如果我們有N個實現類,都要在方法執行前加一樣的邏輯,那麼我們不得不創建N個代理類。這時候,我們就需要使用動態代理了。
動態代理
本次動態代理是針對JDK動態代理進行探討。
正如前面所說,如果我們要在很多類使用同一種邏輯時,會心態爆炸,那麼我們怎麼去解決這個問題呢,這時候,我們可以想一想反射。
在使用的動態代理的過程中,有兩個關鍵的東東,一個是InvocationHandler
介面,一個是Proxy
類。
- InvocationHandler
每一個動態代理類都必須要實現InvocationHandler這個介面,並且每個代理類的實例都關聯到了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行調用。
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我們所代理的那個真實對象,也就是
實現類
method: 指代的是我們所要調用真實對象的某個方法的Method對象
args: 指代的是調用真實對象某個方法時接受的參數
- Proxy
Proxy這個類的作用就是用來動態創建一個代理對象的類
其中我們使用最多是newProxyInstance()
去創建代理類
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
loader:一個ClassLoader對象,定義了由哪個ClassLoader對象來對生成的代理對象進行載入
interfaces:一個Interface對象的數組,表示的是我將要給我需要代理的對象提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理對象就宣稱實現了該介面(多態),這樣我就能調用這組介面中的方法了
h:一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上
創建一個代理類,實現方法調用前或後的邏輯
public class TestHandler implements InvocationHandler{
// object為實現類的對象
private Object object;
public TestHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("開始方法執行");
Object o = method.invoke(object,args);
System.out.println("方法結束");
return o;
}
}
實例化代理類,並
public static void main(String[] args) {
// 實現類
TestService testService = new TestServiceImpl();
// 裡面傳入要代理的實現類對象
TestHandler testHandler = new TestHandler(testService);
/**
* testService.getClass().getClassLoader() 代表我們使用這個類來載入我們代理對象
* testService.getClass().getInterfaces() 代表我們調用這些介面中的方法
* testHandler 將代理對象與testHandler關聯
*/
TestService service = (TestService) Proxy.newProxyInstance(testService.getClass().getClassLoader(),
testService.getClass().getInterfaces(),testHandler);
service.play();
service.say();
反射,註解,以及動態代理就簡單地介紹完了,可以這樣說反射是註解以及動態代理的基礎,註解的實現和動態代理都要靠反射發揮作用。
還是多讀下書吧,面試實習是把殺豬刀