Java 注解和反射

1. 注解

1.1 注解的定義

注解就是源代碼的元數(shù)據(jù)抵恋,通熟的講就是代碼中的標簽纲菌。注解就有如下的特點:

  1. 注解是一個附屬品挠日,依賴于其他元素(包、類翰舌、方法嚣潜、屬性等等)存在。
  2. 注解本身沒有作用椅贱,在恰當?shù)臅r候由外部程序進行解析才會發(fā)生作用懂算。

1.2 注解的分類

  1. 按來源分
    • JDK 自帶注解,例如:@Override, @Deprecated, @SuppressWornings 庇麦。
    • 第三方注解计技。
    • 自定義注解。
  2. 按生命周期劃分
    • SOURCE:只存在于源代碼中山橄,編譯成 class 文件就不存在了垮媒。
    • Class:存在于源代碼中和 class 文件中。
    • RUNTIME:注解保留到運行時航棱。

1.3 元注解

元注解指的是用于修飾注解的注解睡雇,包括如下幾個:

  1. @Retention:指明 Annotation 的生命周期,傳入的值是一個枚舉類型饮醇,可選值為:
    • RetentionPolicy.SOURCE
    • RetentionPolicy.CLASS
    • RetentionPolicy.RUNTIME
  2. @Target:指明 Annotation 可以修飾程序哪些元素它抱,傳入的值為ElemetType[] 類型,值可為:
    • ElementType.CONSTRUCTOR :構造器
    • ElementType.FIELD:屬性
    • ElementType.LOCAL_VARIABLE:局部變量
    • ElementType.METHOD:方法
    • ElementType.PACKAGE:包
    • ElementType.PARAMETER:參數(shù)
    • ElementType.TYPE:類朴艰、接口(包括注解類型和 enum 聲明)
  3. @Documented:使用此修飾的注解將會被 javadoc 工具提取成文檔观蓄,使用此注解,其 @Retention 必須被設置為 RetentionPolicy.RUNTIME 呵晚。
  4. @Inherited:具有繼承性蜘腌。

1.4 自定義注解

自定義注解需要注意的問題:

  1. 使用 @interface 關鍵字定義。

  2. 自動繼承 java.lang.annotation.Annotation 接口饵隙。

  3. 配置參數(shù)的類型只能是八大基本類型撮珠、String、Class、enum芯急、Annotation 和對應的數(shù)組類型勺届。

  4. 配置參數(shù)聲明的語法格式如下([] 表示可省略):

    類型 變量名() [default 默認值];
    
  5. 如果只有一個配置參數(shù),其參數(shù)名必須為 value娶耍。

  6. 如果定義的注解含有配置參數(shù)免姿,那在使用注解時,必須指定參數(shù)值榕酒,指定形式為:“參數(shù)名=參數(shù)值”胚膊。如果只有一個參數(shù),直接寫參數(shù)值即可想鹰,定義中指定了默認值的參數(shù)可以不指定值紊婉,但沒有的一定要指定值

  7. 沒有成員的注解為標記辑舷,包含成員的稱為元數(shù)據(jù)喻犁。

1.5 注解的解析

Info.java:

package com.hkl;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Info {
    String info();
    String birthday();
    int age() default 0;
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Desc{
    String value();
}

App.java:

package com.hkl;

import java.lang.reflect.Method;

/**
 * Hello world!
 *
 */
@Info(info = "hkl", birthday = "2019/7/20")
@Desc("這是一個類")
public class App 
{
    @Info(info = "hkl", birthday = "2019/7/20", age = 22)
    @Desc("這是一個方法")
    public static void main( String[] args )
    {
        // 解析注解
        try {
            Class clazz = Class.forName("com.hkl.App");

            // 獲取類修飾的注解
            System.out.println("---------類中的注解---------");
            if(clazz.isAnnotationPresent(Info.class)){
                Info classInfo = (Info) clazz.getAnnotation(Info.class);
                System.out.println(classInfo.info());
                System.out.println(classInfo.birthday());
                System.out.println(classInfo.age());
            }

            if(clazz.isAnnotationPresent(Desc.class)){
                Desc classDesc = (Desc)clazz.getAnnotation(Desc.class);
                System.out.println(classDesc.value());
            }

            // 獲取方法修飾的注解
            Method[] methods = clazz.getMethods();

            System.out.println("---------方法中的注解解析---------");
            for(Method method : methods){
                if(method.isAnnotationPresent(Desc.class)){
                    Desc methodDesc = (Desc)method.getAnnotation(Desc.class);
                    System.out.println(methodDesc.value());
                }

                if(method.isAnnotationPresent(Info.class)){
                    Info methodInfo = (Info)method.getAnnotation(Info.class);
                    System.out.println(methodInfo.info());
                    System.out.println(methodInfo.birthday());
                    System.out.println(methodInfo.age());
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2. 反射

2.1 什么反射

反射指的是程序在運行期間借助反射 API 取得任何類的內(nèi)部信息,并通過這些內(nèi)部信息去操作對應對象的內(nèi)部屬性和方法何缓。

任何一個類肢础,在第一次使用時,就會被 JVM 加載到堆內(nèi)存的方法區(qū)中碌廓。JVM 加載類成功后传轰,就會在方法區(qū)中產(chǎn)生一個對應的 Class 對象(一個類只要一個 Class 對象),這個 Class 對象包含被加載類的全部結(jié)構信息氓皱。

2.2 獲取 Class 對象的常用方式

(1)類的 class 屬性

每一個類路召,都有一個 class 靜態(tài)屬性,這個靜態(tài)屬性就是類對應的 Class 對象波材。

Class<Person> cl1 = Person.class;

(2)Object 對象 的 getClass() 方法

Person p1 = new Person();
Class<Person> cl2 = (Class<Person>) p1.getClass();

(3)通過 Class 類的 forName() 方法(最常用)

try {
    Class cl3 = Class.forName("com.llm.hkl.Person");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

(4)通過 ClassLoader 類(不常用)

ClassLoader cl = Person.class.getClassLoader();
try {
    Class cl4 = cl.loadClass("com.llm.hkl.Person");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

2.3 反射的基本使用

反射的基本使用包括創(chuàng)建對象股淡,設置屬性和調(diào)用方法。Class 對象中大多數(shù) get 方法有 Declared 和無 Declared廷区,他們的區(qū)別是:

  1. 無 Declared:只能獲取到 public 修飾的唯灵,包括當前類和所有父類。
  2. 有 Declared:獲取到當前類所有的(含有 private)隙轻,但不包括其父類埠帕。

Person 類:

public class Person {
    private String name;
    private int age;
    public String habbit;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private Person(String name, int age, String habbit) {
        this.name = name;
        this.age = age;
        this.habbit = habbit;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getHabbit() {
        return habbit;
    }

    public void setHabbit(String habbit) {
        this.habbit = habbit;
    }

    private String say(String str){
        String str1 = name+"說:"+str;
        System.out.println(str1);
        return str1;
    }

    public void eat(String food){
        System.out.println(name+"吃"+food);
    }

    @Override
    public String toString() {
        return "["+name+","+age+","+habbit+"]";
    }
}

測試方法:

public class PersonTest {
    @Test
    public void ReflexTest() throws Exception{
        System.out.println("---------- new 方式創(chuàng)建對象 ----------");
        // 普通方式創(chuàng)建 Person 對象
        Person p1 = new Person("hkl", 22);

        // 直接設置屬性
        p1.habbit = "編程";
        // 調(diào)用方法
        System.out.println(p1);

        //無法直接設置私有屬性和調(diào)用私有方法
        //p1.name = "hkl";
        //p1.say(""Hello);

        //反射方式創(chuàng)建對象
        System.out.println("---------- 反射方式創(chuàng)建對象 ----------");
        Class<Person> clazz = Person.class;

        // 調(diào)用無參構造器參加對象
        Constructor<Person> constructor1 = clazz.getConstructor();
        Person p2 = constructor1.newInstance();
        System.out.println(p2);

        // 通過有參構造器
        Constructor<Person> constructor2 = clazz.getConstructor(String.class, int.class);
        Person p3 = constructor2.newInstance("hkl", 22);
        System.out.println(p3);

        // 通過私有的構造器
        Constructor<Person> constructor3 = clazz.getDeclaredConstructor(String.class, int.class, String.class);
        constructor3.setAccessible(true);
        Person p4 = constructor3.newInstance("hkl", 22, "編程");
        System.out.println(p4);

        //通過反射設置公有屬性
        Field personFeildHabbit = clazz.getDeclaredField("habbit");
        personFeildHabbit.set(p2, "編程");

        //通過反射設置私有屬性
        Field personFeildAge = clazz.getDeclaredField("age");
        personFeildAge.setAccessible(true);
        personFeildAge.set(p2, 18);

        //通過反射調(diào)用方法
        Method personMethodToString = clazz.getDeclaredMethod("toString");
        // 方法的返回值為調(diào)用方法的返回值
        String str = (String)personMethodToString.invoke(p2);
        System.out.println(str);

        // 通過反射調(diào)用私有方法
        Method personMethodSay = clazz.getDeclaredMethod("say", String.class);
        personMethodSay.setAccessible(true);
        String str2 = (String)personMethodSay.invoke(p2, "Hello");
        System.out.println(str2);
    }
}

2.5 設計模式:代理模式

使用一個代理對象將原始對象包裝起來, 然后用該代理對象取代原始對象。任何對原始對象的調(diào)用都要通過代理對象玖绿,代理對象決定是否以及何時將方法調(diào)用轉(zhuǎn)到原始對象上敛瓷。

2.5.1 靜態(tài)代理

代理類和原始對象在編譯期間就確定下來了,不利于程序的擴展斑匪,且每一個代理只能為一個接口服務呐籽,這就會在開發(fā)的過程中產(chǎn)生多大的代理類。

靜態(tài)代理的實現(xiàn):

  1. 代理類和原始對象實現(xiàn)相同的接口。
  2. 代理類保持原始對象的引用狡蝶。
  3. 代理類里調(diào)用原始對象的方法庶橱。

ClothFactory 接口:

package com.hkl.proxy;


public interface ClothFactory {
    String producer();
}

ClothFactoryProxy 類:

package com.hkl.proxy;

public class ClothFactoryProxy implements ClothFactory{
    private ClothFactory clothFactory;

    public ClothFactoryProxy(ClothFactory clothFactory) {
        this.clothFactory = clothFactory;
    }

    @Override
    public String producer() {
        System.out.println("代理對象做一些準備");
        clothFactory.producer();
        System.out.println("代理對象做一些收尾工作");

        return null;
    }
}

NikeFactory 類:

package com.hkl.proxy;

public class NikeFactory implements ClothFactory{

    public NikeFactory() {
    }

    @Override
    public String producer() {
        System.out.println("Nike 正在生產(chǎn)衣服");
        return null;
    }
}

測試方法:

@Test
public void staticProxyTest(){
    // 創(chuàng)建被代理對象
    NikeFactory nikeFactory = new NikeFactory();

    // 創(chuàng)建代理對象
    ClothFactoryProxy clothFactoryProxy = new ClothFactoryProxy(nikeFactory);

    // 通過代理對象調(diào)用被代理對象的方法
    clothFactoryProxy.producer();
}

2.5.2 動態(tài)代理模式

動態(tài)代理是通過 Java 的反射機制實現(xiàn)的。通過動態(tài)代理贪惹,只需一個代理對象就可以代理所有的對象苏章。

Humen :接口

package com.llm.proxy;
public interface Human {
    String belief();
    void eat(String food);
}

SuperMan:類

package com.hkl.proxy;

public class SuperMan implements Human {
    @Override
    public String belief() {
        return "我相信,我能行!";
    }

    @Override
    public void eat(String food) {
        System.out.println("正在吃"+food);
    }
}

動態(tài)代理類:

package com.hkl.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class DynamicProxy {
    // 獲取代理對象
    public static Object getInstance(Object obj){
        MyInvocation h = new MyInvocation();
        h.bind(obj);
        // 動態(tài)的創(chuàng)建對象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), h);
    }
}

class MyInvocation implements InvocationHandler{
    private Object obj;

    /**
     * 綁定被代理對象
     * @param obj
     */
    public void bind(Object obj){
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 代理對象調(diào)用被代理對象的方法
        Object ret = method.invoke(obj, args);
        return ret;
    }
}

測試方法:

@Test
public void dynamicProxyTest(){
    SuperMan sm = new SuperMan();
    Human h = (Human)DynamicProxy.getInstance(sm);
    System.out.println(h.belief());;
    h.eat("麻辣燙");

    NikeFactory nikeFactory = new NikeFactory();
    ClothFactory clothFactory = (ClothFactory) DynamicProxy.getInstance(nikeFactory);
    clothFactory.producer();
}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奏瞬,一起剝皮案震驚了整個濱河市枫绅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硼端,老刑警劉巖撑瞧,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異显蝌,居然都是意外死亡,警方通過查閱死者的電腦和手機订咸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門曼尊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脏嚷,你說我怎么就攤上這事骆撇。” “怎么了父叙?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵神郊,是天一觀的道長。 經(jīng)常有香客問我趾唱,道長涌乳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任甜癞,我火速辦了婚禮夕晓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悠咱。我一直安慰自己蒸辆,他們只是感情好,可當我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布析既。 她就那樣靜靜地躺著躬贡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪眼坏。 梳的紋絲不亂的頭發(fā)上拂玻,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天,我揣著相機與錄音,去河邊找鬼纺讲。 笑死擂仍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的熬甚。 我是一名探鬼主播逢渔,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乡括!你這毒婦竟也來了肃廓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤诲泌,失蹤者是張志新(化名)和其女友劉穎盲赊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敷扫,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡哀蘑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了葵第。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绘迁。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卒密,靈堂內(nèi)的尸體忽然破棺而出缀台,到底是詐尸還是另有隱情,我是刑警寧澤哮奇,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布膛腐,位于F島的核電站,受9級特大地震影響鼎俘,放射性物質(zhì)發(fā)生泄漏哲身。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一而芥、第九天 我趴在偏房一處隱蔽的房頂上張望律罢。 院中可真熱鬧,春花似錦棍丐、人聲如沸误辑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巾钉。三九已至,卻和暖如春秘案,著一層夾襖步出監(jiān)牢的瞬間砰苍,已是汗流浹背潦匈。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赚导,地道東北人茬缩。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像吼旧,于是被迫代替她去往敵國和親凰锡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,922評論 2 361