Java基礎 :反射、注解、代理、線程池、依賴的學習和理解

高新技術的重要性

這里的高新技術指的是Java基礎中的知識,比如:反射、注解、代理、線程池、依賴注入等等。
市面上的開源框架大多都是使用了這些Java基礎的知識去實現的,掌握了這些Java基礎的知識,能幫助我們更好的理解一些好的開源框架的實現原理。

反射

JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
說白了就是在Android中給我們提供了一個android.jar可以調用android的api,但是還有一部分api的方法是沒有暴露出來的,那么如果我們想要調用這些方法,就需要通過反射來調用。

用處:
l 在運行時判斷任意一個對象所屬的類;
? obj instanceof Object => obj.getClass().equals(Object.class)
l 在運行時構造任意一個類的對象;
l 在運行時判斷任意一個類所具有的成員變量和方法;
l 在運行時調用任意一個對象的方法;
l 生成動態代理。
注意:
一般如果不是必要情況下,盡量不要使用反射,反射會影響app的性能。
獲取Class對象
萬事萬物皆對象,每個類中都具有成員變量,構造方法,成員方法,所以可以使用Class類來表示每個類,每個類是Class類的實例對象。
Class的實例對象是各個類在內存中的那份字節碼文件。
基本數據類型,String,void,數組,引用類型都存在類類型Class。
Class常見方法
l newInstance(); 創建實例對象
l getName(); 獲取類的名字,帶包名的
l getSimpleName(); 獲取不帶包名的類名
l getMethod(方法名,方法參數的類類型); 獲取指定公有的方法
l getDeclaredMethod(方法名,方法參數的類類型); 獲取所有的指定的方法
l getMethods(); 獲取所有公有地方法
l getDelcaredMethods(); 獲取類上面所有的方法
l getFields(); 獲取所有公有的成員變量
l getField("成員變量的名稱"); 通過成員變量的名字獲取公有的成員變量
l getDeclaredField("成員變量的名稱"); 通過成員變量的名字獲取成員變量
l getDeclaredFields(); 獲取所有的成員變量
l getConstructor("","","");
l getConstructors();
l getDeclaredConstructor(parameterTypes);
獲取Class文件的三種方式
獲取Class文件的三種方式:

  1. 類名.class;
  2. 對象名.getClass();
  3. Class.forName(“類的包名+類的名字”);
/**
* 獲取Class的三種方式:
*       類名.class
*       對象.getClass()
*       Class.forName("類名")
*/
public class GetClass {
   public static void main(String[] args) throws Exception {
       //方式1:Class.forName("類名")
       Class clazz1= Class.forName("com.fuyuan.example.bean.People");
       System.out.println("Class.forName()方式 :" + clazz1);

       //第二種:對象.getClass()方法
       People people=new People();
       Class clazz2 = people.getClass();
       System.out.println("對象.getClass()方式 :" + clazz2 + ", "  + (clazz1 == clazz2));

       //第三種:類名.class方法
       Class clazz3 = People.class;
       System.out.println("類名.class方式 :" + clazz3 + ", "  + (clazz1 == clazz3));
   }
}

運行結果:

6.png

.]

動態加載類和靜態加載類

靜態加載:在編譯時期加載的類,叫靜態加載。
動態加載:在運行時期加載的類,叫動態加載。

簡單來說:
在代碼中寫死的,直接new出來的類,就屬于是靜態加載;
運行期間通過配置信息來動態獲取相關類的實例,就屬于動態加載。

動態加載的優點:提高程序的可擴展性

動態加載范例
現有Excel、Word、PPT 3個類,他們都實現了Office接口,重寫了Office接口的startWork方法。
現在通過動態加載的方式,使用相應的類去執行相關的操作。

接口Office,包含一個startWork方法:Office.java


public interface Office {
    public void startWork();
  }

Office的實現類Excel:


public class Excel implements Office{
    @Override
    public void startWork() {
        System.out.println("Excel start work......");
    }
}

Office的實現類Word:


public class Word implements Office{
    @Override
    public void startWork() {
        System.out.println("Word start work......");
    }
}

寫一個測試類,演示動態加載的使用場景:DynamicLoad.java

/**
 * 動態加載
 */
  public class DynamicLoad {
  
    public static void main(String[] args) {
        String worker = "com.fuyuan.example.bean." + "Excel";
        startWord(worker);
    }
  
    public static void startWord(String worker) {
        try {
            // 1. 獲取字節碼文件
            Class clazz = Class.forName(worker);
            // 2. 獲取類的對象
            Office office = (Office) clazz.newInstance();
            // 3. 調用相關方法
            office.startWork();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  }

打印類中方法的信息
Class中用于獲取方法的API為:
l getMethod(方法名,方法參數的類類型); 獲取指定公有的方法
l getDeclaredMethod(方法名,方法參數的類類型); 獲取所有的指定的方法
l getMethods(); 獲取所有公有地方法
l getDelcaredMethods(); 獲取類上面所有的方法

Method的常見API:
l method.getReturnType(); 獲取返回值的類類型
l method.getName(); 獲取方法的名字
l method.getParameterTypes(); 獲取所有參數的類類型

需求:給定一個對象,打印出這個對象身上所有的方法的返回值、方法名和參數

/**
    * 打印方法的信息,包括:  返回值  方法名  參數
    */
   public static void printMethodMessage(Object object){
      String temp="";

      //1.獲取字節碼文件
      Class c=object.getClass();
      //2.獲取類上面的方法
      /**
       * 獲取一個方法
       * 參數1:方法的名字
       * 參數2:方法參數的類類型 
       */
//    c.getMethod(name, parameterTypes);

      //獲取所有的公有地方法
      Method[] methods = c.getMethods();

      //獲取類上面的所有方法
//    c.getDeclaredMethod(name, parameterTypes)
//    c.getDeclaredMethods();

      for (Method method : methods) {
         //返回值的類類型
         Class<?> returnType = method.getReturnType();
         temp+=returnType.getName()+"  ";

         //獲取方法的名字
         String name=method.getName();
         temp+=name+"(";

         //獲取參數
         Class<?>[] parameterTypes = method.getParameterTypes();
         for (Class<?> class1 : parameterTypes) {
            String parameterName=class1.getName();
            temp+=parameterName+",";
         }
         temp+=")";

            // 打印方法信息
         System.out.println(temp);
         temp="";
      }
   }

調用此方法打印String身上的所有方法信息,運行結果:

string1.png

打印類中成員變量的信息
Class中用于獲取成員變量的API為:
l getFields(); 獲取所有公有的成員變量
l getField("成員變量的名稱"); 通過成員變量的名字獲取公有的成員變量
l getDeclaredFields (); 獲取所有的成員變量(包括private的)
l getDeclaredFields("成員變量的名稱"); 通過成員變量的名字獲取成員變量

Field的常見API:
l getName(); 獲取成員變量的名稱
l getType(); 返回成員變量的類類型

需求:給定一個對象,打印出這個對象身上所有的成員變量名稱和類類型

/**
    * 打印類中成員變量的信息
    */
   public static void printFieldMessage(Object obj){
      //1.獲取字節碼文件
      Class c1=obj.getClass();

      //2.拿到字節碼文件中所有的變量
      // 獲取到所有的公有的成員變量
      Field[] fields = c1.getFields();

        // 獲取到指定公有的成員變量
//    c1.getField("成員變量的名稱");

        // 獲取到指定的成員變量
//    c1.getDeclaredField("成員變量的名稱");

        //可以獲取到所有的成員變量
//    c1.getDeclaredFields();

      for (Field field : fields) {
         //3.獲取成員變量的名稱
         String name=field.getName();
         //4.獲取成員變量的類型
         Class typeClass=field.getType();
         System.out.println(typeClass+"  "+name+";");
      }
   }

調用此方法打印int身上所有的公有的成員變量的信息,運行結果:

5.png

打印類中的構造方法的信息

可變參數

l 可變參數的出現解決了一個方法接受的參數個數不固定的問題;
l 可變參數只能出現在參數列表的最后;
l ...位于變量類型和變量名之間,前后有無空格都可以;
l 調用可變參數的方法時,編譯器為該可變參數隱含創建了一個數組;
l 在方法體中,可以以數組的形式訪問可變參數;

可變參數使用范例:

/**
 * 可變參數
 */
public class ChangeArgs {
    public static void main(String[] args) {
        System.out.println(add(2,5));
        System.out.println(add(2,5,9,7));
    }

    /**
     * 可變參數的使用(可變參數實質就是數組的形式)
     */
    public static int add(int... args){
        int sum=0;
        for (int i=0;i<args.length;i++) {
            sum+=args[i];
        }
        return sum;
    }
}

打印構造方法的信息
Class中用于獲取構造方法的API為:
l getConstructor("","",""); 獲取指定公有的構造方法,參數是可變參數
l getConstructors(); 獲取所有的公有構造方法
l getDeclaredConstructor(parameterTypes); 獲取指定的構造方法
l getDeclaredConstructors(); 獲取所有的構造方法

Constructor的常見API:
l getName(); 獲取構造方法的名字
l getParameterTypes(); 獲取所有參數的類類型

需求:給定一個對象,打印出這個對象身上所有的構造方法的名稱和參數類型

/**
    * 打印構造方法信息
    */
   public static void printConstructorMessage(Object obj){
      String temp="";
  
      //1.獲取字節碼文件
      Class c1=obj.getClass();
  
      //2.獲取構造方法
  
      //參數是可變參數,可以理解為數組,代表的是構造方法的參數的類類型
      //獲取指定的構造方法
//    c1.getConstructor("","","");
  
      //獲取所有公有的構造方法
//    c1.getConstructors();
  
      //獲取指定的構造方法
//    c1.getDeclaredConstructor(parameterTypes);
  
      //獲取所有構造方法
      Constructor[] constructors = c1.getDeclaredConstructors();
  
      for (Constructor constructor : constructors) {
         //獲取構造方法的名稱
         String name=constructor.getName();
         temp+=name+"(";
            
         //獲取構造的參數的類類型
         Class[] parameterTypes = constructor.getParameterTypes();
         for (Class class1 : parameterTypes) {
            //獲取構造方法的參數的類類型的名字
            String paramName=class1.getName();
            temp+=paramName+",";
         }
         temp+=")";
         System.out.println(temp);
         temp="";
      }
   }

調用此方法打印String身上所有的公有的成員變量的信息,運行結果:

4.png

方法和成員變量的反射
測試用Number類:Number.java

public class Number {
  
   private static String num="number start...";
  
   public void add(int a,int b){
      System.out.println(a+b);
   }
  
   public static void printNum(){
      System.out.println(num);
   }
}

通過反射調用某個類的方法
通過反射調用某個類的方法的步驟:
獲取Class字節碼
獲取構造方法
通過構造方法創建對象
獲取方法(可以是私有的)
方法設置Accessible為true(暴力反射)
調用invoke方法

/**
 *  通過反射調用方法
 */
  public static void reflectionMethod() throws Exception {
    // 1. 獲取字節碼文件
    Class clazz = Number.class;
  
    // 2. 獲取類的對象
    Number number = (Number) clazz.newInstance();
  
    // 3. 獲取方法
    Method method = clazz.getDeclaredMethod("add", int.class, int.class);
  
    // 4. 設置方法訪問權限(暴力反射)
    method.setAccessible(true);
  
    // 5. 執行方法
    method.invoke(number, 2, 6);
  }

通過反射給某個類的成員變量賦值
通過反射給某個類的成員變量賦值的步驟:
獲取Class字節碼
獲取構造方法
通過構造方法創建對象
獲取成員變量(可以是私有的)
方法設置Accessible為true(暴力反射)
調用Field.set(對象,參數值) 方法賦值

/**
 *  通過反射為成員變量賦值
 */
  public static void reflectionField() throws Exception {
    // 1. 獲取字節碼文件
    Class clazz = Number.class;
  
    // 2. 獲取類的對象
    Number number = (Number) clazz.newInstance();
  
    // 調用方法打印賦值前的成員變量的值
    System.out.print("修改前:");
    number.printNum();
  
    // 3. 獲取成員變量
    Field field = clazz.getDeclaredField("num");
  
    // 4. 設置成員變量的訪問權限(暴力反射)
    field.setAccessible(true);
  
    // 5. 為成員變量賦值
    field.set(number, "我是被修改后的Number值");
  
    // 調用方法打印賦值后的成員變量的值,看是否修改成功
    System.out.print("修改后:");
    number.printNum();
  }

方法執行結果:

3.png

注解

Annotion(注解)是一個接口,程序可以通過反射來獲取指定程序元素的Annotion對象,然后通過Annotion對象來獲取注解里面的元數據。
Annotation(注解)是JDK5.0及以后版本引入的。它可以用于創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。從某些方面看,Annotation就像修飾符一樣被使用,并應用于包、類型、構造方法、方法、成員變量、參數、本地變量的聲明中。
Annotation的行為十分類似public、final這樣的修飾符。每個Annotation具有一個名字和成員(成員的個數>=0)。每個Annotation的成員具有被稱為name=value對的名字和值(就像javabean一樣),name=value裝載了Annotation的信息。也就是說注解中可以不存在成員。

使用注解的基本規則:
Annotation不能影響程序代碼的執行,無論增加、刪除 Annotation,代碼都始終如一的執行。
  
Annotation類型:
Annotation類型定義了Annotation的名字、類型、成員默認值。一個Annotation類型可以說是一個特殊的java接口,它的成員變量是受限制的,而聲明Annotation類型時需要使用新語法。當我們通過java反射api訪問Annotation時,返回值將是一個實現了該 annotation類型接口的對象,通過訪問這個對象我們能方便的訪問到其Annotation成員。

簡而言之:
l 一個注解就是一個類,使用注解,就相當于創建了一個類的實例對象。
l 注解相當于一種標記,在程序中加了注解就等于為程序打上了某種標記。
l Java編譯器、開發工具或者其他程序可以用反射來了解你的類及各種元素上有無何種標記,通過不同的標記,就去干相應的事。
l 標記可以加在包,類,字段,方法,方法的參數以及局部變量上。

注解是一個特殊的類,他的格式同接口,只是在接口前加了”@“

7.png

注解的分類
根據注解參數的個數,我們可以將注解分為三類:
1.標記注解:一個沒有成員定義的Annotation類型被稱為標記注解,@Override;
2.單值注解
3.完整注解

根據注解使用方法和用途,我們可以將Annotation分為三類:
1.系統注解
2.元注解
3.自定義注解

系統注解

@Override
@Override 用來表示實現或者重寫接口或父類中的方法。
@Override 是一個標記注解類型,它被用作標注方法。它說明了被標注的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種Annotation在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示。這個annotaton常常在我們試圖覆蓋父類方法而確又寫錯了方法名時發揮威力。使用方法極其簡單:在使用此annotation時只要在被修飾的方法前面加上@Override即可。
@Deprecated
@Deprecated用來標記已過時的類型或者類型成員。
@Deprecated也是一個標記注解。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標注的程序元素。而且這種修飾具有一定的“延續性”:如果我們在代碼中通過繼承或者覆蓋的方式使用了這個過時的類型或者成員,雖然繼承或者覆蓋后的類型或者成員并不是被聲明為@Deprecated,但編譯器仍然要報警。
@SuppressWarnings
@SuppressWarnings是用來警告用戶的,它用于通知java編譯器禁止特定的編譯警告。
@SuppressWarnings被用于有選擇的關閉編譯器對類、方法、成員變量、變量初始化的警告。

元注解

元注解就是注解的注解,它的作用就是負責注解其他注解。
Java5.0定義了4個標準的元注解類型,它們被用來提供對其它注解類型作說明。

@Target
作用:
用于描述注解的使用范圍,即被描述的注解可以用在什么地方

取值(ElementType):
1.CONSTRUCTOR:用于描述構造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部變量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述參數
7.TYPE:用于描述類、接口(包括注解類型) 或enum聲明

使用范例:

/**
 * Created by Fuyuan on 2016/6/15.
 *
 * @Target用于表示注解應用的范圍
 * ElementType中包含方法、接口、包、注解、類、構造方法等等
 *
 * 這里指定自定義注解TestAnnotation只能被聲明在方法上和成員變量上
 */
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface TestTargetAnnotation {

}

@Retention
作用:
@Retention 用于說明注解的保留期,表示需要在什么級別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內有效)

取值(RetentionPoicy):
l SOURCE:在源文件中有效(即源文件保留)
l CLASS:在class文件中有效(即class保留,在加載到JVM虛擬機時丟棄)
l RUNTIME:在運行時有效(即運行時保留,此時可以通過反射獲得定義在某個類上的所有注解)

默認值在Class階段
@Override在SOURCE階段(給編譯器看的)
@SuppressWarning在SOURCE階段(給編譯器看)
@Deprecated在RUNTIME階段

使用范例:

/**
 * Created by Fuyuan on 2016/6/15.
 *
 *  注解的生命周期分為三個階段:
 *        java源文件、class文件、內存中的字節碼
 * 對應的@Retention元注解:
 *        RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME
 *
 * 默認值在Class階段
 * @Override在SOURCE階段(給編譯器看的)
 * @SuppressWarning在SOURCE階段(給編譯器看)
 * @Deprecated在RUNTIME階段(調進內存后掃描二進制碼來查看方法,所以是RUNTIME)
 *
 * 這里指定自定義注解的聲明生命為運行時保留
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface TestRetentionAnnotation {

}

@Documented

8.png

@Documented 注解用于生成文檔的時候,帶有@Documented的注解會被顯示在文檔中。

@Inherited
@Inherited 表示父類的注解可以被子類繼承, 前提是Retention必須是RUNTIME的。

@Inherited 元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。
如果一個使用了@Inherited修飾的注解類型被用于一個class,則這個注解將被用于該class的子類。

注意:@Inherited annotation類型是被標注過的class的子類所繼承。類并不從它所實現的接口繼承annotation,方法并不從它所重載的方法繼承annotation。

當@Inherited annotation類型標注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。

簡而言之:
@Inherited表示父類的注解可以被子類繼承,但是這個可被繼承的前提是注解的生命周期是運行時注解,且@Inherited代表的是子類可以繼承父類類級別的注解,父類的方法如果被子類重寫,子類不繼承父類方法上的注解。

范例:
定義一個注解,指明直接是@Inherited的:TestInheritedAnnotation.java

/**
 * Created by Fuyuan on 2016/6/15.
 *
 * @Inherited 表示父類的注解可以被子類繼承,
 */
@Target({ElementType.METHOD, ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestInheritedAnnotation {
    String value();
}

創建一個父類,在父類的方法、抽象方法、類上都使用了自定義的@Inherited的注解:Parent.java

@TestInheritedAnnotation("I'm parent")
public abstract class Parent {

    @TestInheritedAnnotation("I'm parent method1")
    public void method1() {
        System.out.println(" Parent method1......");
    }

    @TestInheritedAnnotation("I'm parent method2")
    public void method2() {
        System.out.println("Parent method2......");
    }

    @TestInheritedAnnotation("I'm parent absMethod")
    public abstract void absMethod();
}

定義一個子類,繼承Parent,實現其抽象方法absMethod,重寫其普通方法method1:Child.java

public class Child extends Parent{
    @Override
    public void method1() {
        super.method1();
    }

    @Override
    public void absMethod() {
        System.out.println("子類實現抽象父類的抽象方法absMethod");
    }
}

寫一個測試方法,判斷Child上面的各個方法和類上是否有我們的自定義注解@ TestInheritedAnnotation:

/**
 *  測試@Inherited注解
 */
private static void testInheritedAnnotation() throws NoSuchMethodException {
    // 1. 獲取子類的class
    Class clazz=Child.class;

    //2. 獲取子類重寫的父類抽象方法
    Method method = clazz.getMethod("absMethod");
    // 3. 判斷子類實現的父類的抽象方法是否繼承了注解
    if(method.isAnnotationPresent(TestInheritedAnnotation.class)){
        TestInheritedAnnotation inheritedAnnotation = method.getAnnotation(TestInheritedAnnotation.class);
        System.out.println("子類實現的抽象方法繼承到父類抽象方法中的Annotation,其信息如下:");
        System.out.println(inheritedAnnotation.value());
    }else{
        System.out.println("子類實現的抽象方法沒有繼承到父類抽象方法中的Annotation");
    }

    // 4. 判斷子類重寫父類的方法
    Method methodOverride = clazz.getMethod("method1");
    if(methodOverride.isAnnotationPresent(TestInheritedAnnotation.class)){
        TestInheritedAnnotation inheritedAnnotation = methodOverride.getAnnotation(TestInheritedAnnotation.class);
        System.out.println("子類method1方法繼承到父類method1方法中的Annotation,其信息如下:");
        System.out.println(inheritedAnnotation.value());
    }else{
        System.out.println("子類method1方法沒有繼承到父類method1方法中的Annotation");
    }

    // 5. 判斷子類不重寫父類的方法
    Method methodParent = clazz.getMethod("method2");
    if(methodParent.isAnnotationPresent(TestInheritedAnnotation.class)){
        TestInheritedAnnotation inheritedAnnotation = methodParent.getAnnotation(TestInheritedAnnotation.class);
        System.out.println("子類method2方法繼承到父類method2方法中的Annotation,其信息如下:");
        System.out.println(inheritedAnnotation.value());
    }else{
        System.out.println("子類method2方法沒有繼承到父類method2方法中的Annotation");
    }

    // 6. 判斷子類繼承自父類的類上的注解
    if(Child.class.isAnnotationPresent(TestInheritedAnnotation.class)){
        TestInheritedAnnotation annotation = (TestInheritedAnnotation) clazz.getAnnotation(TestInheritedAnnotation.class);
        System.out.println("子類繼承到父類類上Annotation,其信息如下:");
        System.out.println(annotation.value());
    }else{
        System.out.println("子類沒有繼承到父類類上Annotation");
    }
}

打印日志:

1.png

去掉@ TestInheritedAnnotation中的@Inherited標記,打印日志:

2.png

結論:
對于方法上的注解,加不加@Inherited沒有影響;但是對于類上的注解,加了@Inherited的,父類的注解會被子類繼承。
自定義注解
注解是一個特殊的類,他的格式同接口,只是在接口前加了”@“

9.png
定義注解格式:

public @interface 注解名 {定義體}

注解參數的可支持數據類型:
l 所有基本數據類型(int,float,boolean,byte,double,char,long,short)
l String類型
l Class類型
l enum類型
l Annotation類型
l 以上所有類型的數組

注意:
l 只能用public或默認(default)這兩個訪問權修飾。
例如:String value();這里把方法設為defaul默認類型;
l 參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組。
例如:String value();這里的參數成員就為String;  
l 如果只有一個參數成員, 方法名需要定義成value,后加小括號。

簡而言之:
l 當只有一個變量的時候,方法名最好定義成value。
原因:
假如方法名定義成value,那么用的時候可以直接@注解(“value值”);
假如方法名定義的其他的,比如name(),那么用的時候必須寫成key-value形式:@注解(name = “value值”)
l 當多個的時候可以隨便定義。
l 注解可以有默認值。

自定義注解范例:@CustomAnnotation

**
 * 自定義注解
 *
 * 假如方法名定義成value,那么用的時候可以直接@注解(“value值”);
 * 假如方法名定義的其他的,比如name(),那么用的時候必須寫成key -value形式:@注解(name = “value值”)
 */
public @interface CustomAnnotation {
    /** 假如注解中只有一個屬性,建議名字定義成value */
//    String value();

    /** 為注解的屬性定義一個默認值 */
    String name() default "zhangsan";

    int age();
}

使用范例:

@CustomAnnotation(age = 25)
private String custom = "hello";

注解的處理器

l <T extends Annotation> T getAnnotation(Class<T>annotationClass): 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
l Annotation[] getAnnotations():返回該程序元素上存在的所有注解。
l boolean is AnnotationPresent(Class<?extends Annotation>annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.
l Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組)。該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。

使用注解定義網絡框架
我們在發送網絡請求的時候,需要知道請求是GET請求還是POST請求,請求的URL是什么。這里我們通過一個簡單的自定義網絡框架來看一下注解是如何被應用的。

定義注解,用于標識網絡訪問的請求方式:RequestMethod.java

/**
 * 網絡請求方法: GET or POST
 */
@Target(ElementType.METHOD) // 限制使用位置:方法體
@Retention(RetentionPolicy.RUNTIME) // 生命周期:運行時
@Documented // 顯示在文檔中
public @interface RequestMethod {

    /** 定義枚舉類,限制網絡請求的方法僅POST和GET這2種 */
    enum Method{GET, POST}

    /** 默認請求方式為GET請求 */
    Method value() default Method.GET;
}

定義注解,用于標識網絡訪問的URL地址:RequestURL.java

/**
 * 網絡請求要訪問的URL地址
 */
@Target(ElementType.METHOD) // 限制使用位置:方法體
@Retention(RetentionPolicy.RUNTIME) // 生命周期:運行時
@Documented // 顯示在文檔中
public @interface RequestURL {

    /** 默認的訪問網絡的URL地址為"" */
    String value() default "";
}

寫一個簡單的網絡框架,用于解析注解,并發送網絡請求,并返回請求的結果:HttpUtil.java

/**
 * 網絡請求的工具類
 */
public class HttpUtil {
    /**
     * 解析并執行網絡請求
     * @param object 發起網絡請求的類
     */
    public static String parseRequest(Object object) throws IOException {
        // 網絡請求返回的結果
        String result = "";

        // 1. 獲取Class
        Class clazz = object.getClass();

        // 2. 獲取全部的公有方法
        Method[] methods = clazz.getMethods();

        // 3. 遍歷所有的方法,尋找哪個方法身上有@RequestURL和@RequestMethod
        for (Method method : methods) {
            // 4. 先判斷是否有@RequestURL,獲取要訪問的URL地址;
            // 若獲取不到URL地址,不需要再往后解析了
            if (method.isAnnotationPresent(RequestURL.class)) {
                // 獲取URL地址
                RequestURL annotationUrl = method.getAnnotation(RequestURL.class);
                String url = annotationUrl.value();
                // 如果URL地址為空,那么也不需要再往后解析了
                if (!TextUtils.isEmpty(url)) {
                   // 5. 判斷是否有@RequestMethod,獲取請求方法
                    if (method.isAnnotationPresent(RequestMethod.class)) {
                        // 獲取訪問的方法
                        RequestMethod annotationMethod = method.getAnnotation(RequestMethod.class);
                        RequestMethod.Method requestMethod = annotationMethod.value();
                        if (requestMethod.equals(RequestMethod.Method.GET)) {
                            // 發送GET請求
                            result = get(url);
                        } else {
                            // 發送POST請求
                            result = post(url);
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     *  發送GET請求
     * @param url 要請求的URL地址
     */
    public static String get(String url) throws IOException {
        // 借助okHttp發送網絡請求
        Request request = new Request.Builder().url(url).build();
        Call call = new OkHttpClient().newCall(request);
        // 發送同步請求
        Response response = call.execute();
        return  response.body().string();
    }

    /**
     *  發送POST請求
     * @param url 要請求的URL地址
     */
    public static String post(String url) throws IOException {
        RequestBody requestBody = new FormEncodingBuilder().add("key", "value").build();
        // 借助okHttp發送網絡請求
        Request request = new Request.Builder().post(requestBody).url(url).build();
        Call call = new OkHttpClient().newCall(request);
        // 發送同步請求
        Response response = call.execute();
        return  response.body().string();
    }
}

寫一個測試Activity測試我們的框架是否好用:MainActivity.java

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        loadNet();
    }

    @RequestMethod(RequestMethod.Method.GET)
    @RequestURL("http://192.168.191.1:8080/testjson.json")
    public void loadNet() {
        // 開啟線程訪問網絡
        new Thread(){
            @Override
            public void run() {
                try {
                    String request = HttpUtil.parseRequest(MainActivity.this);
                    Log.e("MainActivity", "========網絡請求返回結果========:" + request);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

執行結果:

10.png

Xutils injectView實現原理
使用場景:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @ViewInject(id = R.id.button1, clickable = true)
    private Button button1;
    @ViewInject(id = R.id.button2)
    private Button button2;
    ……
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AnnotateUtils.injectView(this);
    }
……
}

public static void injectViews(Object object, View sourceView){
    Field[] fields = object.getClass().getDeclaredFields();
    for (Field field : fields){
        ViewInject viewInject = field.getAnnotation(ViewInject.class);
        if(viewInject != null){
            int viewId = viewInject.id();
            boolean clickable = viewInject.clickable();
            if(viewId != -1){
                try {
                    field.setAccessible(true);
                    field.set(object, sourceView.findViewById(viewId));
                    if(clickable == true){
                        sourceView.findViewById(viewId).setOnClickListener((View.OnClickListener) (object));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

動態代理

代理根據運行和編譯時期,分為靜態代理和動態代理。

如果編譯時存在的話是靜態代理,所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委托類的關系在運行前就確定了。

如果運行時存在的則是動態代理,動態代理之所以稱為動態,是因為代理類是在運行時由Proxy類產生的,這就大大減少了需要我們手工設計代理類的數量。
動態代理的應用場景
AOP即Aspect orientedprogram,面向切面的編程

系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面
例如:
StudentService類用于處理學生信息
CourseService類用于處理課程信息
MiscService類用于處理教室信息
這三個類都有安全、事務、日志的功能,這三個功能貫穿到好多個模塊中,所以,它們就是交叉業務。

交叉業務的編程問題即為面向方面的編程,AOP的目標就是要使交叉業務模塊化。

所謂模塊化,就是將這些交叉業務只寫一份,應用到所有需要的地方,而不是每個需要的地方都寫一份。這就需要使用代理技術,代理技術是實現AOP功能的核心和關鍵。

11.png

要為系統中的各種接口的類增加代理功能,如果全部采用靜態代理方式,寫成百上千的代理類,不符合實際。
JVM可以在運行期動態的生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類。
JVM生成的動態類必須實現了一個或者多個接口,以便讓JVM知道他都實現了什么方法,需要為這些方法來生成代理。所以,JVM生成的動態類只能用于具有相同接口的目標類的代理。

12.png

如何實現動態代理

步驟:

  1.  定義接口
    
  2.  定義委托類,委托類需要實現接口
    
  3.  實現InvocationHandler接口,定義委托類和代理類的橋梁
    
  4.  生成代理類Proxy.newProxyInstance();
    
  5.  調用代理類的方法
    

定義接口StudentInterface.java

public interface StudentInterface {
    public void study();
    public void play();
    public void sleep();
}

定義委托類Student,委托類需要實現接口StudentInterface:

/**
 * 委托類
 */
public class Student implements StudentInterface {

    @Override
    public void study() {
        System.out.println("==== Student ==== study ====");
    }

    @Override
    public void play() {
        System.out.println("==== Student ==== play ====");
    }

    @Override
    public void sleep() {
        System.out.println("==== Student ==== sleep ====");
    }
}

實現InvocationHandler接口,定義委托類和代理類的橋梁:StudentProxyHandler.java

/**
 * 委托類Student和代理類的橋梁
 */
public class StudentProxyHandler implements InvocationHandler {

    /** 委托類對象 */
    private Object targetObj;

    public StudentProxyHandler(Object targetObj) {
        this.targetObj = targetObj;
    }

    /**
     * @param proxy  代理類的對象
     * @param method 要執行的方法
     * @param args 要執行的方法的參數
     * @return 方法執行后的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 代理類可以過濾方法,必須我們控制play方法不被執行
        if (method.getName().equals("play")) {
            System.out.println("====StudentProxy 攔截了 play方法 ==========");
            return null;
        }

        // 代理類可以增強方法,必須在執行方法前打印Log信息
        System.out.println("====打印了一條log,代理類有方法被執行了 ==========");

        // 執行委托類的對應方法
        Object result = method.invoke(targetObj, args);
        return result;
    }
}

通過Proxy.newProxyInstance();生成代理類對象:

// 創建委托類對象
Student student = new Student();
// 生成代理類對象
StudentInterface proxyInstance = (StudentInterface) Proxy.newProxyInstance(StudentInterface.class.getClassLoader(),
        new Class[]{StudentInterface.class}, new StudentProxyHandler(student));

調用代理類的方法:

// 調用代理類的方法
proxyInstance.study();
proxyInstance.sleep();
proxyInstance.play();


執行結果:

13.png

線程池

線程池的優點
l 避免線程的創建和銷毀帶來的性能開銷
l 避免大量的線程間因互相搶占系統資源導致的阻塞現象
l 能夠對線程進行簡單的管理并提供定時執行、間隔執行等功能

注意:
線程池在使用的時候最好搞成靜態的,因為線程池比較消耗資源。

線程池的概念

Java里面線程池的頂級接口是Executor,不過真正的線程池接口是 ExecutorService, ExecutorService 的默認實現是 ThreadPoolExecutor;普通類 Executors 里面調用的就是 ThreadPoolExecutor。

Executors提供四種線程池:
l newCachedThreadPool
newCachedThreadPool 得到的是一個可根據需要創建新線程的線程池。
特點:
? 如果有緩存的線程可用,優先用緩存的;如果沒現有線程可用,則創建一個新的線程加入到池中。
? 終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。因此,長時間保持空閑的線程池不會使用任何資源。

l newSingleThreadExecutor
newSingleThreadExecutor 創建是一個單線程池,也就是該線程池只有一個線程在工作,所有的任務是串行執行的,如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它,此線程池保證所有任務的執行順序按照任務的提交順序執行。

l newFixedThreadPool
newFixedThreadPool創建固定大小的線程池,每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小,線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。

l newScheduledThreadPool
newScheduledThreadPool 創建一個大小無限的線程池,此線程池支持定時以及周期性執行任務的需求。

線程池相關構造參數含義
/**
 * corePoolSize:
 *      線程池的核心線程數,一般情況下不管有沒有任務都會一直在線程池中一直存活,
 *      只有在 ThreadPoolExecutor 中的方法 allowCoreThreadTimeOut(boolean value) 設置為 true 時,
 *      閑置的核心線程會存在超時機制,如果在指定時間沒有新任務來時,核心線程也會被終止,
 *      而這個時間間隔由 keepAliveTime 屬性指定。
 *
 * maximumPoolSize:
 *      線程池所能容納的最大線程數,當活動的線程數達到這個值后,后續的新任務將會被阻塞。
 *
 * keepAliveTime:
 *      控制線程閑置時的超時時長,超過則終止該線程。
 *
 * unit:
 *      用于指定 keepAliveTime 參數的時間單位。
 *
 * workQueue:
 *      線程池的任務隊列,通過線程池的 execute(Runnable command) 方法會將任務 Runnable 存儲在隊列中。
 *
 * threadFactory:
 *      線程工廠,它是一個接口,用來為線程池創建新線程的
 */
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,//核心線程數
        5, //最大線程數
        5, //線程空閑時間存活時間
        TimeUnit.SECONDS, //存活時間的單位
        new LinkedBlockingQueue<Runnable>(),   //任務隊列
        Executors.defaultThreadFactory());//線程產生的工廠

線程池關閉的API

ThreadPoolExecutor 提供了兩個方法,用于線程池的關閉:
l shutdown()
不會立即的終止線程池,而是要等所有任務緩存隊列中的任務都執行完后才終止,但再也不會接受新的任務。
l shutdownNow()
立即終止線程池,并嘗試打斷正在執行的任務,并且清空任務緩存隊列,返回尚未執行的任務。

線程池的工作原理

當任務來了之后,如果核心線程數沒有滿,那么就使用核心線程數,如果核心線程數滿了,那么就將任務放入任務隊列中,如果任務隊列也滿了,就開始使用最大線程數,如果最大線程數也使用滿了,會拋出異常,拒絕任務。

依賴注入

什么是依賴
如果在 Class A 中,有Class B 的實例,則稱 Class A 對 ClassB 有一個依賴。

存在依賴的例子:

/**
 * Human對Father有一個依賴
 */
public class Human {
    // Human中使用了Father的實例,產生了對Father依賴
    private Father father;
    /**
     *  主動初始化依賴,耦合嚴重
     */
    public Human() {
        father = new Father();
    }
}

存在的問題:
l 如果現在要改變 father 生成方式,如需要用new Father(String name)初始化 father,需要修改 Human 代碼;
l 如果想測試不同 Father 對象對 Human 的影響會變得很困難,因為 father 的初始化被寫死在了 Human 的構造函數中;
l 如果new Father()過程非常緩慢,單測時我們希望用已經初始化好的 father 對象也很困難。

問題產生原因:
兩個類不夠獨立,耦合嚴重。

解決方案:由外界來提供依賴的實例

/**
 * Human對Father有一個依賴
 */
public class Human {

    private Father father;

    /**
     * 由外界來注入來傳入依賴,解耦
     */
    public Human(Father father) {
        this.father = father;
    }
}

類似這種非自己主動初始化依賴,而通過外部來傳入依賴的方式,我們就稱為依賴注入。

什么是依賴注入

依賴注入的目的是為了使類與類之間解耦合,提高系統的可擴展性和可維護性。
Java中一般都是通過注解 + 反射的方式來實現依賴注入的。

依賴注入示例:
通過依賴注入,為類的成員變量賦值。

定義注解類,用于生命成員變量的值:StringAnnotation.java

/**
 * 自定義注解,用于為成員變量賦值
 */
@Target(ElementType.FIELD) // 作用范圍:成員變量
@Retention(RetentionPolicy.RUNTIME) // 生命周期:運行時
@Documented // 在文檔中顯示
public @interface StringAnnotation {
    String value();

定義解析注解并為成員變量賦值的注入工具類:ParseAnnocation.java

/**
 *  注入工具類
 */
public class ParseAnnocation {
   public static void parseAnnocation(Object object) throws Exception{
      //1.獲取字節碼文件
      Class c=object.getClass();
      //2.獲取成員變量
      Field[] fields = c.getFields();
      //3.遍歷成員變量
      for (Field field : fields) {
            // 4. 找到帶有StringAnnotation注解的成員變量
         if(field.isAnnotationPresent(StringAnnotation.class)){
                StringAnnotation annotation = field.getAnnotation(StringAnnotation.class);
            //5. 獲取注解中的值
            String value=annotation.value();
                // 6. 為成員變量賦值
            field.set(object, value);
         }
      }
   }
}

使用注解 + 注入工具類,測試注入結果:

/**
 * 測試依賴注入
 */
public class Dependency {

    @StringAnnotation("張三")
    public String name = "猜我是誰";

    public static void main(String[] args) throws Exception {
        Dependency dependency = new Dependency();
        // 解析注解,為成員變量賦值
        ParseAnnocation.parseAnnocation(dependency);
        // 打印注入后的值
        System.out.println("======Dependency name =======" + dependency.name);
    }
}

運行結果:


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內容