一、注解(Annotation)
1.什么是注解:
??注解可以說是注釋的更高級的一種,相當(dāng)于標(biāo)記,注解同樣不影響代碼的執(zhí)行,但是注解能夠用來創(chuàng)建文檔、跟蹤代碼中的依耐性、執(zhí)行基本編譯時(shí)的檢查。
??同時(shí)注解是一個(gè)接口,程序可以通過反射來獲取指定程序中元素的Annotation對象,然后通過該對象獲取注解中元數(shù)據(jù)信息信息。
即:==Annotation(注解)是Java提供的一種對元程序中元素關(guān)聯(lián)信息和元數(shù)據(jù)(metadata)的一種途徑和方法。==
[引入時(shí)間:jdk1.5之后]
標(biāo)記可以用在包、類、方法、字段、方法參數(shù)及局部變量上來關(guān)聯(lián)信息。這些信息被儲存在Annotation的“name = value”結(jié)構(gòu)對中。
注意:這些標(biāo)記和關(guān)聯(lián)的信息并不會(huì)影響程序中代碼的任何執(zhí)行,而至于可以用在反射的時(shí)候在運(yùn)行期間被訪問,那是只有通過特定的工具才能對Anntation類型中的數(shù)據(jù)訪問和處理。
2.注解分類
按照注解參數(shù)個(gè)數(shù)分類:
- 標(biāo)記注解:一個(gè)沒有成員定義的Annotation類型被稱為標(biāo)記注解。
- 單值注解。
- 完整注解
根據(jù)注解的使用方法和用途分類:
- JDK內(nèi)置系統(tǒng)注解。
- 元注解。
- 自定義注解。
系統(tǒng)內(nèi)置標(biāo)準(zhǔn)注解:
3個(gè)標(biāo)準(zhǔn)內(nèi)置注解,定義在java.lang中:
- @Override:用于修飾此方法重寫父類的方法。
- @Deprecated:用于修飾已經(jīng)過時(shí)的方法。
- @SuppressWarning:用于禁止特定的編譯警告。
==@Override:限定重寫父類方法==
使用這個(gè)注解來標(biāo)記我們重寫的方法,能夠幫我們判斷是否是重寫,即方法名寫錯(cuò)的時(shí)候,編譯報(bào)錯(cuò)。
==@Deprecated:標(biāo)記已過時(shí)==
使用這個(gè)注解標(biāo)記某個(gè)方法,讓這個(gè)方法稱為過時(shí)的方法,當(dāng)使用時(shí)候,能夠提示使用者這個(gè)方法已經(jīng)過時(shí)了,不推薦使用。
==@SuppressWarning:抑制編譯器警告==
使用這個(gè)注解的時(shí)候,可以去除我們不想看到或者出現(xiàn)的警告。(目前不知道這個(gè)有什么用。)
抑制編譯器警告參數(shù):
- deprecation:使用了不贊成的類或方法時(shí)的警告。
- unchecked: 使用了未檢查的轉(zhuǎn)換時(shí)的警告,例如使用集合的時(shí)候沒有用泛型(Generics)來指定集合保存的類型。
- fallthrough:當(dāng)Switch程序塊直接通往下一種情況而沒有使用break時(shí)的警告。
- path:當(dāng)類路徑、源文件中有不存在的路徑時(shí)的警告。
- serial:在可序列化的類上缺少serialVersionUID定義時(shí)的警告。
- finally:任何finally句子不能正常完成時(shí)的警告。
- all:以上所有的警告。
3.自定義注解
注解的深入學(xué)習(xí),自定義注解。想要自定義注解,首先我們要了解元注解及相關(guān)自定義注解的語法。
①元注解(meta-annotation):
元注解的作用是注解其他注解(也就是我們自定義的注解包括系統(tǒng)內(nèi)置標(biāo)準(zhǔn)注解)。
- @Target
- @Retention
- @Documented
- @Inherited
==@Target==
這個(gè)元注解說明了Annotation所修飾的范圍:即注解可被用于package、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)、本地變量(循環(huán)變量、catch參數(shù)等)。
用于描述注解的使用范圍,這個(gè)范圍的取值(ElementType)有:
- CONSTRUCTOR:用于描述構(gòu)造器
- FIELD:用于描述屬性
- LOCAL_VARIABLE:用于描述局部變量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述參數(shù)
- TYPE:用于描述類、接口(包括注解類型)和enum申明、
使用實(shí)例:
/**
* 注解Table可用于注解類、接口(包括Annotation類型)或enum申明
*/
Target(ElementType.TYPE)
public @interface Table {
/**
* 數(shù)據(jù)表名稱注解,默認(rèn)值為類名稱
*/
public abstract String tableName() default "className";
}
/**
* 注解NoDBColumn僅可用于注解類的成員變量
*/
Target(ElementType.FIELD)
public @interface NoDBColumn {
}
==@Retention==
作用:表示在什么級別保存注解信息,用于描述注解的生命周期(即被描述的注解在什么范圍內(nèi)有效),取值(RetentionPoicy):
- SOURCE:在源文件中有效
- CLASS:在class文件中有效
- RUNTIME:在運(yùn)行時(shí)有效
具體實(shí)例:
/**
* Column注解的RetentionPolicy屬性值是RUNTIME,這樣注解處理器就可以通過
* 反射,獲取到該注解的屬性值,從而做一些運(yùn)行時(shí)的邏輯處理
*/
@(Java高級)[感謝網(wǎng)上各個(gè)大神文章, 讓我們明悟?。?!]
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public abstract String name() default "fileName";
public abstract String setFuncName() default "setField";
public abstract String getFuncName() default "getField";
public abstract boolean defaultDBValue() default false;
}
==@Documented==
是一個(gè)標(biāo)記注解,沒有成員。用于描述其他類型的annotation應(yīng)該作為被標(biāo)注的程序成員的公共API,即可以被javadoc這樣的工具文檔化。
具體實(shí)例:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public abstract String name() default "fileName";
public abstract String setFuncName() default "setField";
public abstract String getFuncName() default "getField";
public abstract boolean defaultDBValue() default false;
}
==@Inherited==
??這也是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。
??注意@Inherited annotation類型是被標(biāo)注過的class的子類所繼承。類并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
??當(dāng)@Inherited annotation類型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢一個(gè)@Inherited annotation類型的annotation時(shí),反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達(dá)類繼承結(jié)構(gòu)的頂層。
具體實(shí)例:
@Inherited
public @interface Greeting {
public enum FontColor {
BULE, RED, GREEN
};
String name();
FontColor fontColor() default FontColor.GREEN;
}
②自定義注解
自定義注解格式:
public @interface 注解名{定義體}
注意事項(xiàng):自定義注解時(shí),自動(dòng)繼承java.lang.Annotation接口 ,由編譯程序自動(dòng)完成其他細(xì)節(jié)。同時(shí),不能繼承其他接口或注解。
定義體中每一個(gè)方法實(shí)際上都是一個(gè)配置參數(shù),方法名=參數(shù)名,返回值類型=參數(shù)的類型(返回值類型只能是基本類型、Class、Annotation、String、enum和以上所有類型的數(shù)組),可以通過default申明參數(shù)的默認(rèn)值。
參數(shù)設(shè)定:
- 只能用
public和默認(rèn)(default)這兩個(gè)訪問權(quán)限修飾符修飾。 - 只有一個(gè)參數(shù)成員時(shí),最好將參數(shù)名稱設(shè)置為"value"。
具體實(shí)例:
/**
* 水果名稱注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 顏色屬性
* @return
*/
Color fruitColor() default Color.BULE;
}
//被注解的類
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor = FruitColor.Color.RED)
private String appleColor;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void displayName() {
System.out.println("水果的名字是:蘋果");
}
}
==注意:==
注解元素必須要有確定的值,即要么在定義注解中設(shè)置默認(rèn)值,要么在使用注解時(shí)給定值,一般使用空字符串或0作為默認(rèn)值,但是很難看出這個(gè)元素是存在或缺失的的特征,所以我們在注解定義的時(shí)候,給元素賦上特殊的值,空字符串或-1,這個(gè)是一種習(xí)慣。
4.注解處理器
對于我們寫的注解,如果不能去讀取它的話,那還不如寫注釋呢!
所以,接下來我們要做的就是創(chuàng)建使用注解處理器。
①注解處理器類庫(Java.lang.reflect.AnnotatedElement)
我們知道Annotation接口是所有Annotation類型的父接口,在java.lang.reflect包下有一個(gè)AnnotatedElement接口(它是所有程序元素的父接口),它有一些實(shí)現(xiàn)類使我們需要的:
- Class :類的一些定義
- Constructor :類的構(gòu)造器定義
- Field :類的成員變量定義
- Method :類的方法定義
- Packet :包的一些定義
在java.lang.reflect包下有很多工具,是在我們反射的時(shí)候需要用的。
程序通過反射獲取了某個(gè)類的AnnotatedElement對象之后,程序就可以調(diào)用該對象的4個(gè)方法來訪問Annotation信息:
- T getAnnotation(Class annotationClass):返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
- Annotation[] getAnnotations():返回該程序元素上所有存在的注解。
- boolean is AnnotationPresent(Class< ?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在返回true,不存在返回false。
- **Annotation[] getDeclaredAnnotaions(): **返回直接存在于此元素上的所有注解。(忽略繼承過來的注解)如果沒有直接存在于次元素上的注解,則返回一個(gè)長度為0的數(shù)組。
下面是一個(gè)簡單的例子:
==自定義注解==
/**
* 水果名稱注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果顏色注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 顏色枚舉
*/
public enum Color{
BULE , RED , GREEN
};
/**
* 顏色屬性
* @return
*/
Color fruitColor() default Color.BULE;
}
/**
* 水果供應(yīng)者注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供應(yīng)商編號
*/
public int id() default -1;
/**
*
* 供應(yīng)商名稱
*/
public String name() default "";
/**
*
* 供應(yīng)商地址
*/
public String address() default "";
}
==自定義注解處理器==
/**
* 注解處理器
*/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){
String strFruitName = "水果名稱:";
String strFruitColor = "水果顏色:";
String strFruitProvicer = "供應(yīng)商信息:";
//返回直接存在于clazz上的所有注釋
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.
getAnnotation(FruitName.class);
strFruitName = strFruitName + fruitName.value();
System.out.println(strFruitName);
}else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor = (FruitColor) field.
getAnnotation(FruitColor.class);
strFruitColor = strFruitColor + fruitColor.fruitColor();
System.out.println(strFruitColor);
}else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider = (FruitProvider) field.
getAnnotation(FruitProvider.class);
strFruitProvicer = "供應(yīng)商編號:" + fruitProvider.id()
+ " 供應(yīng)商名稱:" + fruitProvider.name()
+ " 供應(yīng)商地址:" + fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
==測試類==
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
運(yùn)行結(jié)果
水果名稱:Apple
水果顏色:RED
供應(yīng)商編號:9527 供應(yīng)商名稱:陜西紅富士集團(tuán) 供應(yīng)商地址:陜西省西安市延安路89號紅富士大廈
到此,我們注解就講完了,下面附上一張圖,便于我們整理知識點(diǎn)和鞏固知識點(diǎn)。
[圖片上傳失敗...(image-e731e1-1540044443552)]
二、反射
反射是Java動(dòng)態(tài)性之一。
何為動(dòng)態(tài)性,即動(dòng)態(tài)性語言:
??程序在運(yùn)行時(shí)可以改變其結(jié)構(gòu),新的函數(shù)可以引進(jìn),已有的函數(shù)可以被刪除等結(jié)構(gòu)上的變化。(如JavaScript、Ruby、Python等屬于動(dòng)態(tài)語言)
本質(zhì)上Java和C/C++一樣不屬于動(dòng)態(tài)語言的,但是因?yàn)榉瓷錂C(jī)制,可以在程序運(yùn)行期間加載、探知和使用編譯期間完全未知的類,并可以生成相關(guān)類對象的實(shí)例,從而調(diào)用某個(gè)方法和改變某個(gè)屬性值。因此,Java屬于半個(gè)動(dòng)態(tài)性語言。
1.反射機(jī)制
概念
Java中的反射機(jī)制是指在運(yùn)行狀態(tài)中,對于任何一個(gè)類都能知道這個(gè)類的所有屬性和方法;并且對于任意一個(gè)對象,都能調(diào)用它的方法;這種動(dòng)態(tài)獲取信息和調(diào)用對象方法的功能即為Java語言的反射機(jī)制。
使用場合(為什么要使用反射)
我們學(xué)習(xí)過多態(tài),知道編譯類型和運(yùn)行類型不一致的時(shí)候,當(dāng)我們想調(diào)用運(yùn)行類型特中的特有方法的時(shí)候需要強(qiáng)制轉(zhuǎn)換,因?yàn)槲覀兛赡苤肋@個(gè)對象屬于那些類。
但是當(dāng)我們在實(shí)際開發(fā)過程中,可能會(huì)有某個(gè)外部對象傳入過來,我們需要使用這個(gè)對象獨(dú)有的方法,那在編譯的過程中而我們根本無法知道這個(gè)對象屬于哪些類,只有運(yùn)行的時(shí)候我們才能知道傳入的對象和類的信息,那么這個(gè)時(shí)候就要用到反射了!
2.反射使用
①反射API
- Class類(java.lang.Class):反射的核心類,可以獲取類的屬性、方法等信息。
- Field類(java.lang.reflec):表示類中的成員變量,可以獲取和設(shè)置類中的屬性值。
- Method類(Java.lang.reflec):表示類的方法,可以獲取方法中的信息或執(zhí)行方法。
- Constructor類(Java.lang.reflec):表示類的構(gòu)造方法。
②獲取方法和屬性等信息
大致步驟:獲取想要的類的Class對象—>調(diào)用Class類中的方法—>
使用反射API來操作這些信息。
獲取Class對象的幾個(gè)方法
- Class clazz = 對象名(想要的類的對象).getClass();
- Class clazz = 類名.getClass();
- 使用Class類中的forName()靜態(tài)方法(最安全、性能最好)
==Class clazz = Class.forName("類的安全路徑");==(通常使用這個(gè))
簡單案例:
package com.xsl.reflectTest;
/**
* 測試類Person,通過反射獲取這個(gè)類的方法和屬性
*/
public class Person {
private String name;
private String gender;
private int age;
public Person() {}
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "姓名:"+name+" 性別:"+gender+" 年齡:"+age;
}
}
接下來我們通過反射來獲取Person類中的方法、屬性等信息
package com.xsl.reflectTest;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) {
try {
//獲取Person類的Class對象
Class clazz = Class.forName("com.xsl.reflectTest.Person");
//獲取Person類的所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);
}
//獲取Person類的所有屬性
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
}
//獲取Person類所有的構(gòu)造器
Constructor[] constructors = clazz.getDeclaredConstructors();
for(Constructor cons : constructors){
System.out.println(cons);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
輸出結(jié)果:
所有方法
public java.lang.String com.xsl.reflectTest.Person.getGender()
public void com.xsl.reflectTest.Person.setGender(java.lang.String)
public int com.xsl.reflectTest.Person.getAge()
public void com.xsl.reflectTest.Person.setAge(int)
public java.lang.String com.xsl.reflectTest.Person.toString()
public java.lang.String com.xsl.reflectTest.Person.getName()
public void com.xsl.reflectTest.Person.setName(java.lang.String)
所有屬性
private java.lang.String com.xsl.reflectTest.Person.name
private java.lang.String com.xsl.reflectTest.Person.gender
private int com.xsl.reflectTest.Person.age
所有構(gòu)造方法
public com.xsl.reflectTest.Person()
public com.xsl.reflectTest.Person(java.lang.String,java.lang.String,int)
③來創(chuàng)建對象為我們使用
創(chuàng)建對象的兩種方法:
- 使用Class對象的==newInstance()==方法創(chuàng)建該Class對象對應(yīng)類的實(shí)例。(要求對應(yīng)的類要有空參構(gòu)造器)目前提示不推薦使用,即方法過時(shí)。【2018年】
- 使用Class對象獲取指定的Constructor對象,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建對應(yīng)類的實(shí)例。(這個(gè)方法可以指定構(gòu)造方法創(chuàng)建實(shí)例)
簡單例子(延續(xù)上面的例子):
public class demo01 {
public static void main(String[] args) throws Exception{
//首先獲取Class對象
Class clazz = Class.forName("com.xsl.reflectTest.Person");
//采用第一種方法創(chuàng)建對象
Person person = (Person) clazz.newInstance();//提示了不推薦使用,我們知道就好
//設(shè)置屬性
person.setName("劉備");
person.setAge(20);
person.setGender("男");
System.out.println(person);//會(huì)默認(rèn)調(diào)用toString()方法,我們重寫了
System.out.println("====================");
Constructor constructor = clazz.getDeclaredConstructor(
String.class,String.class,int.class);
Person person1 = (Person) constructor.newInstance("孫尚香","女",16);
System.out.println(person1);
}
}
運(yùn)行結(jié)果:
姓名:劉備 性別:男 年齡:20
姓名:孫尚香 性別:女 年齡:16
3.通過反射操作泛型、注解
①反射操作泛型
Java采用泛型擦除機(jī)制來引入泛型。Java中的泛型僅僅是給編譯器javac使用的,為確保數(shù)據(jù)的安全性和免去強(qiáng)制類型轉(zhuǎn)換的麻煩。
??為了通過反射操作這些類型以迎合開發(fā)的需要,Java新增了ParameterizedType、GenericArrayType、TypeVariable和WildcardType這幾個(gè)類型代表不能被歸一到Class類中的類型但是又和原始類型齊名的類型。
- ParameterizedType:表示一種參數(shù)化的類型,比如Collection< String >
- GenericArrayType:表示一種元素類型是參數(shù)化類型或者類型變量的數(shù)組類型。
- TypeVariable:是各種類型變量的公共父接口。
- WildcardType:代表一種通配符類型表達(dá)式,比如?、? extends Number、? super Integet。
簡單實(shí)例:
/*
通過反射獲取泛型信息
*/
public class Demo {
/*
定義兩個(gè)帶泛型的方法
*/
public static void test01(Map<String,Person> map, List<Person> list){
System.out.println("Demo01.test01()");
}
public Map<Integer,Person> test02(){
System.out.println("Demo01.test02()");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
//獲取指定方法參數(shù)泛型信息
Method m = Demo.class.getMethod("test01", Map.class, List.class);
Type[] t = m.getGenericParameterTypes();
for(Type paramType : t){
System.out.println("#"+paramType);
if(paramType instanceof ParameterizedType){
//獲取泛型中的具體信息
Type[] genericTypes = ((ParameterizedType) paramType).
getActualTypeArguments();
for (Type genericType : genericTypes){
System.out.println("泛型信息:"+genericType);
}
}
}
System.out.println();
//獲得指定方法返回值泛型信息
Method m2 = Demo.class.getMethod("test02",null);
Type returnType = m2.getGenericReturnType();
System.out.println("#"+returnType);
if(returnType instanceof ParameterizedType){
Type[] genericTypes = ((ParameterizedType) returnType).
getActualTypeArguments();
for(Type genericType : genericTypes){
System.out.println("返回值,泛型類型:"+genericType);
}
}
}
}
②反射操作注解
反射操作注解,即在上面注解中的注解器便是通過反射操作的!
4.反射性能
Method/Constructor/Field/Element都繼承了AccessibleObject,其中有一個(gè)方法setAccessible(boolean flag)方法,這個(gè)方法有兩個(gè)作用:
- 啟動(dòng)/禁止訪問安全檢查開關(guān):參數(shù)flag為true時(shí),反射的對象在使用時(shí),取消Java語言訪問檢查。默認(rèn)有false,實(shí)施Java語言訪問檢查。
- 禁止安全檢查,提高反射效率。
簡單實(shí)例:
public class TestReflect {
public static void testNoReflect(){
Person person = new Person();
long startTimes = System.currentTimeMillis();
for(int i = 0;i < Integer.MAX_VALUE; i++){
person.getName();
}
long times = System.currentTimeMillis() - startTimes;
System.out.println("沒有通過反射,調(diào)用方法消耗時(shí)間:"+times+"毫秒");
}
public static void testNoAccess() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Person person = new Person();
Method method = Class.forName("com.xsl.reflectTest.Person").
getMethod("getName");
long startTimes = System.currentTimeMillis();
for(int i =0;i < Integer.MAX_VALUE;i ++){
method.invoke(person,null);
}
long times = System.currentTimeMillis() - startTimes;
System.out.println("通過反射,沒有訪問權(quán)限,調(diào)用方法共消耗時(shí)間:"+
times+"毫秒");
}
public static void testUserAccess() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Person person = new Person();
Method method = Class.forName("com.xsl.reflectTest.Person").
getMethod("getName");
method.setAccessible(true);
long startTimes = System.currentTimeMillis();
for(int i =0;i < Integer.MAX_VALUE;i ++){
method.invoke(person,null);
}
long times = System.currentTimeMillis() - startTimes;
System.out.println("通過反射,有訪問權(quán)限,調(diào)用方法共消耗時(shí)間:"+
times+"毫秒");
}
public static void main(String[] args) throws Exception {
testNoReflect();
testNoAccess();
testUserAccess();
}
}
運(yùn)行結(jié)果:
沒有通過反射,調(diào)用方法消耗時(shí)間:4毫秒
通過反射,沒有訪問權(quán)限,調(diào)用方法共消耗時(shí)間:5077毫秒
通過反射,有訪問權(quán)限,調(diào)用方法共消耗時(shí)間:2611毫秒
雖然直接調(diào)用和反射調(diào)用消耗時(shí)間相差很大,但是這是我們將調(diào)用次數(shù)放大到了Integer.MAX_VALUE的結(jié)果,問題不大。但是禁止訪問檢查后,反射的效率確實(shí)提升了不少。
三、動(dòng)態(tài)代理
在了解動(dòng)態(tài)代理之前,我們先了解一下一種設(shè)計(jì)模式--代理模式--。
同時(shí)對于代理,根據(jù)創(chuàng)建代理的時(shí)間點(diǎn),分為靜態(tài)代理和動(dòng)態(tài)代理。
1.代理模式
代理模式是Java常用的設(shè)計(jì)模式,特征是代理類和委托類有==同樣的接口==,代理類的工作主要是負(fù)責(zé)為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類以及事后處理消息等。
代理類和委托類之間通常會(huì)存在關(guān)聯(lián)關(guān)系,一個(gè)代理類的對象與一個(gè)委托類的對象關(guān)聯(lián),代理類對象本身并不真正實(shí)現(xiàn)服務(wù),而是通過調(diào)用委托類的對象的相關(guān)方法,來提供特定服務(wù)。即我們訪問實(shí)際對象時(shí),是通過代理對象來訪問的。代理模式就是在訪問實(shí)際對象時(shí)引入一定程度的間接性,通過這種間接性,可以附加多種用途。
2.靜態(tài)代理
何為靜態(tài)代理?
由程序員創(chuàng)建或特定工具自動(dòng)生成源代碼,也就是在編譯時(shí)就已經(jīng)將接口、被代理類、代理類等確定下來。在運(yùn)行之前代理類的.class文件就已經(jīng)生成。
靜態(tài)代理的簡單實(shí)現(xiàn):
首先我們定義好==公共接口==:
/*
學(xué)生(接口)-->就是 被代理類(學(xué)生) 和代理類(班長)的公共接口
*/
public interface Person {
//教班費(fèi)的行為
void giveMoney();
}
定義好==被代理類==:
/*
Student類實(shí)現(xiàn)Person接口,并實(shí)現(xiàn)交班費(fèi)的功能
*/
public class Student implements Person{
private String name;
public Student(String name){
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name+"交了班費(fèi)100塊");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后定義==代理類==:
/*
學(xué)生代理類,也實(shí)現(xiàn)了Person接口,保存一個(gè)學(xué)生實(shí)體,如此就可以代理學(xué)生產(chǎn)生行為
*/
public class StudentProxy implements Person{
//被代理的學(xué)生
Student stu;
public StudentProxy(Student stu){
//只代理學(xué)生
if(stu.getClass() == Student.class){
this.stu = stu;
}
}
//代理交班費(fèi),調(diào)用被代理學(xué)生上交班費(fèi)行為
@Override
public void giveMoney() {
stu.giveMoney();
}
}
最后我們來測試一下:
public class Test {
public static void main(String[] args) {
//被代理的學(xué)生曹操,他的班費(fèi)上交由代理對象monitor班長完成
Student caocao = new Student("曹操");
//生成代理對象,并將曹操傳給代理對象
Person monitor = new StudentProxy(caocao);
//班長代理上交班費(fèi)
monitor.giveMoney();
}
}
運(yùn)行結(jié)果:
曹操交了班費(fèi)100塊
從測試及結(jié)果上看,我們并沒有直接通過Student類對象caocao來執(zhí)行交班費(fèi)的行為,而是通過StudentProxy類對象monitor來代理執(zhí)行的,這種模式就是代理模式。
代理模式:公共接口(Person),被代理類(Student),代理類(StudentProxy),而且代理類要有被代理類的具體實(shí)例,才能通過這個(gè)間接的調(diào)用方法
那么就有疑問了?這樣做有什么意義呢?
這個(gè)意義就在于代理模式的間接性,通過間接性,我們可以在代理過程中加入其它的用途或者是處理。
簡單的說,在交班費(fèi)之前,先來評價(jià)一下Student對象caocao,修改一下代碼:
/*
學(xué)生代理類,也實(shí)現(xiàn)了Person接口,保存一個(gè)學(xué)生實(shí)體,如此就可以代理學(xué)生產(chǎn)生行為
*/
public class StudentProxy implements Person{
//被代理的學(xué)生
Student stu;
public StudentProxy(Student stu){
//只代理學(xué)生
if(stu.getClass() == Student.class){
this.stu = stu;
}
}
//代理交班費(fèi),調(diào)用被代理學(xué)生上交班費(fèi)行為
@Override
public void giveMoney() {
//在這里添加就可以了
System.out.println(stu.getName()+"學(xué)習(xí)很好,就是疑心太大,老說有人偷他作業(yè)");
stu.giveMoney();
}
}
重新執(zhí)行:
曹操學(xué)習(xí)很好,就是疑心太大,老說有人偷他作業(yè)
曹操交了班費(fèi)100塊
我們可以代理過程中切入一些其他的操作。
這個(gè)可以看Spring的面向切面編程(AOP),在一個(gè)切點(diǎn)前執(zhí)行操作,在一個(gè)切點(diǎn)后執(zhí)行操作。
3.動(dòng)態(tài)代理
動(dòng)態(tài)代理就是代理類在程序運(yùn)行時(shí)創(chuàng)建的方式。
??對于靜態(tài)代理,代理類是我們已經(jīng)定義好的,在程序運(yùn)行之前就編譯完成的。而動(dòng)態(tài)代理中,代理類不是在Java代碼中定義的,而是在運(yùn)行期間根據(jù)我們在Java代碼中的“提示”動(dòng)態(tài)生成的。
那么相比于靜態(tài)代理,動(dòng)態(tài)代理有什么好處呢?
動(dòng)態(tài)代理的優(yōu)勢可以很方便的對代理類的函數(shù)進(jìn)行統(tǒng)一處理,而不用修改每個(gè)代理類中的方法。
用上面靜態(tài)代理中代理類StudentProxy中的giveMoney()方法舉例子:
@Override
public void giveMoney() {
//調(diào)用被代理方法前我們加入個(gè)處理方法
beforeMethod();
stu.giveMoney();//被代理方法
}
那好,這里我們只有一個(gè)giveMoney()方法,我們也只需要寫一次beforeMethod()處理方法,但是如果多了很多其他方法呢,我們是不是也要在每個(gè)方法中,都寫一次處理方法呢?
所以我們通過動(dòng)態(tài)代理可以解決這個(gè)問題:
動(dòng)態(tài)代理的簡單實(shí)現(xiàn):
首先,我們需要java.lang.reflect包下的==Proxy類==和一個(gè)==InvocationHandler==接口,通過這2個(gè)工具我們可以生成JDK動(dòng)態(tài)代理類和動(dòng)態(tài)代理對象。
基本步驟
- 創(chuàng)建一個(gè)==InvocationHandler對象==
//MyInvocationHandler<Person>(stu)這個(gè)是我們實(shí)現(xiàn)
//InvocationHandler接口的類
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
- 使用Proxy類的==getProxyClass靜態(tài)方法==生成一個(gè)動(dòng)態(tài)代理類對象StuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(),new Class<?>[]{Person.class});
- 獲得stuProxyClass中一個(gè)帶InvocationHandler參數(shù)的constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.calss);
- 通過構(gòu)造器constructor來創(chuàng)建動(dòng)態(tài)實(shí)例stuProxy
Person stuProxy = (Person)constructor.newInstance(stuHandler);
OK,動(dòng)態(tài)代理對象創(chuàng)建完畢。
上面4個(gè)步驟也可以分成2個(gè)步驟完成:
//創(chuàng)建一個(gè)與動(dòng)態(tài)代理對象相關(guān)聯(lián)的InvocationHandler
InvocationHandle stuHandler = new MyInvocationHandler<Person>(stu);
//創(chuàng)建一個(gè)代理對象stuProxy,代理對象的每個(gè)方法執(zhí)行
//都會(huì)替換執(zhí)行Invocation中的invoke方法
Person stuProxy = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(),new Class<?>[]{Person.class},stuHandler);
我們基本上以上面的例子來展示:
首先我們創(chuàng)建一個(gè)==接口==:
/*
創(chuàng)建接口
*/
public interface Person {
//交班費(fèi)
void giveMoney();
//學(xué)習(xí)
void study();
}
然后是需要==被代理的類==:
/*
創(chuàng)建需要被代理的類
*/
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
try {
//假設(shè)數(shù)錢用時(shí)1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"交了班費(fèi)50元");
}
@Override
public void study() {
System.out.println(name+"正在學(xué)習(xí)");
}
}
然后我們創(chuàng)建一個(gè)工具類,用來打印方法執(zhí)行的時(shí)間:
/**
* 檢測方法執(zhí)行時(shí)間的工具類:
* 即在方法執(zhí)行前調(diào)用start方法,方法結(jié)束后調(diào)用finsh方法,即可
* 計(jì)算出方法執(zhí)行的時(shí)間
*/
public class MonitorUti {
private static ThreadLocal<Long> t = new ThreadLocal<>();
public static void start(){
t.set(System.currentTimeMillis());
}
public static void finsh(String methodName){
long endTimes = System.currentTimeMillis();
System.out.println(methodName+"方法一共耗時(shí)"+(endTimes - t.get())+"毫秒");
}
}
再實(shí)現(xiàn)InvocationHandler接口,并持有代理對象實(shí)例
public class stuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理對象
T target;
public stuInvocationHandler(T target){
this.target =target;
}
/**
*
* @param proxy 代表動(dòng)態(tài)代理對象
* @param method 正在執(zhí)行的方法
* @param args 調(diào)用目標(biāo)方法時(shí)傳入的實(shí)參
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執(zhí)行"+method.getName()+"方法");
//代理過程中插入檢測方法,計(jì)算方法耗時(shí)
MonitorUti.start();
Object result = method.invoke(target,args);
MonitorUti.finsh(method.getName());
return result;
}
}
最后我們直接測試:
public class ProxyTest {
public static void main(String[] args) throws InterruptedException {
//創(chuàng)建一個(gè)實(shí)例對象,這個(gè)對象是被代理的對象
Person caocao = new Student("曹操");
//創(chuàng)建一個(gè)與代理對象相關(guān)聯(lián)的InvocationHandler
InvocationHandler stuHandler = new stuInvocationHandler<Person>(caocao);
/*
創(chuàng)建一個(gè)代理對象來代理caocao,
代理對象的每個(gè)執(zhí)行方法都會(huì)替換執(zhí)行Invocation中的invoke方法
*/
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
new Class[]{Person.class},stuHandler);
//執(zhí)行交班費(fèi)的方法
stuProxy.giveMoney();
stuProxy.study();
}
}
運(yùn)行結(jié)果:
代理執(zhí)行g(shù)iveMoney方法
曹操交了班費(fèi)50元
giveMoney方法一共耗時(shí)1012毫秒
代理執(zhí)行study方法
曹操正在學(xué)習(xí)
study方法一共耗時(shí)0毫秒
結(jié)論:
我們發(fā)現(xiàn),我們調(diào)用被代理類方法的時(shí)候,我們并沒有修改其任何代碼,但是執(zhí)行結(jié)果卻改變了了,我們只是在StuInvocationHadnler中修改了幾行代碼而已,我們調(diào)用被代理類的任何方法,運(yùn)行時(shí)都被添加了我們自定義的工具類的方法。
這就是動(dòng)態(tài)代理帶來的改變,但是具體是為什么了?
4.動(dòng)態(tài)代理原理
我們先來慢慢理清楚:
- 我們有==被代理類==
- 我們有==公共接口(所有的抽象方法被代理類都要實(shí)現(xiàn)的)==
- 我們有實(shí)現(xiàn)了InvocationHandler接口的類,我們叫它==調(diào)用處理器==(這里構(gòu)造參數(shù)T,表示泛型,這樣無論被代理類是什么類型,都能傳入了)
- 最后我們通過Proxy類的靜態(tài)方法創(chuàng)建了代理類對象
大致圖我們看下:
[圖片上傳失敗...(image-fb521e-1540044443552)]
所以,終歸那么多,最主要的核心還是
Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class},stuHandler);
這段代碼,這段代碼究竟做了什么呢?
經(jīng)過我們分析源碼,一層一層解刨,在Java虛擬機(jī)緩存中,找到了生成的代理類:
public final class $Proxy0 extends Proxy implements Person
這個(gè)代理類的構(gòu)造器:
public $Proxy0(InvocationHandler paramInvocationHandler){
super(paramInvocationHandler);
}
這個(gè)父類構(gòu)造器有一個(gè)參數(shù)InvocationHandler paramInvocationHandler
這不就是我們傳入的==調(diào)用處理器對象==嗎?
再來看父類Proxy源碼:
//這里正好有個(gè)this.h就是InvocationHandler類型?。。?protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
因此,子類$Proxy0中不正好也有了這個(gè)對象嗎?
繼續(xù)分析代理類源碼:
//在靜態(tài)代碼塊中有這樣的一行代碼
m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
這不正是我們代理類的嗎,通過反射獲取方法,然后繼續(xù)找:
//這里重寫了接口Person的方法
public final void giveMoney()
throws
{
try
{
//上面我們知道h是我們傳入的參數(shù)“調(diào)用處理器”對象
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
終于撥開云霧了:
this.h.invoke(this, m3, null);這行代碼不就是直接調(diào)用了我們“調(diào)用處理器”中實(shí)現(xiàn)的invoke方法嗎?
到這,終于知道動(dòng)態(tài)反射的一系列過程了?。。?!
5.總結(jié)
從源碼可以看出,生成的代理類是繼承了Proxy類,所以,動(dòng)態(tài)代理只能對接口來代理。