第三章 Java基礎 (二)

(八)Java的反射機制

1、什麼是反射?——框架設計的靈魂

  • 框架:半成品軟件。可以在框架的基礎上進行軟件開發,簡化代碼。
  • java代碼在計算機中經歷的階段(三階段)
    在這裏插入圖片描述
  • 反射:就是把Java類中的各種成分映射成相應的Java類,然後對這些類進行操作。(如:類的成員屬性->Field、類的成員方法->Method、類的構造方法->Constructor等等)
    反射的好處包括:
    (1)可以在程序運行過程中,操作這些對象。
    (2)可以解耦,提高程序可擴展性。

Java反射說的是在運行狀態中,對於任何一個類,我們都能夠知道這個類有哪些方法和屬性。對於任何一個對象,我們都能夠對它的方法和屬性進行調用。
我們把這種動態獲取對象信息和調用對象方法的功能稱之爲反射機制。

2、字節碼Class對象

(1)Class類對象——描述.class字節碼文件
將(.java類文件)經過編譯後的.class字節碼文件(位於硬盤上)通過類加載器(ClassLoader)加載進內存,通過java.lang.Class類對象對字節碼文件進行描述。每一個類都是一個Class類的實例對象。
Class類對象是用來對.class文件進行描述。主要包括三個成員變量:

  • 類成員變量 Field[] fields
  • 類構造方法 Constructor[] constructors
  • 類成員方法 Method[] methods

(2)獲取Class對象的方式
1. Class.forName(“全類名”):將字節碼文件加載進內存,返回class對象
在Source源代碼階段,此時java類仍位於硬盤上。多用於配置文件,將類名定義在配置文件中。讀取文件,並觸發類構造器加載類。
Class.forName() 方法如果寫錯類的路徑會報 ClassNotFoundException 的異常。
2. 類名.class:通過類名的屬性class獲取
在Class類對象階段,此時java類位於內存中,但沒有實際對象。多用於參數的傳遞。
通過這種方式時,只會加載Dog類,並不會觸發其類構造器的初始化。
3. 對象.getClass():getClass()方法在Object類中定義
在運行階段,此時已經獲取類的實例對象,多用於對象的獲取字節碼的方式。

		// 方法1:Class.forName("全類名")
        try {
            Class cls1 = Class.forName("com.test.demo.Dog");
        } catch (ClassNotFoundException e) {}
        // 方法2:類名.class
        Class cls2 = Dog.class;
        // 方法3:對象.getClass()
        Dog dog = new Dog();
    	Class cls3 = dog.getClass();
		// 用 == 比較3個對象是否爲同一個對象(指向同一物理地址)
		System.out.print(cls1 == cls2);	//	true
		System.out.print(cls1 == cls3);	//	true

結論:同一個字節碼文件(*.class)在一次程序運行過程中,只會被加載一次,不論通過哪一種方式獲取的class對象都是同一個。

3、反射機制

反射機制reflect可以在運行期間獲取類的字段、方法、父類和接口等信息。

(3.1)類成員變量的反射

  1. 獲取類成員變量:
  • Field[] getFields():獲取所有public 修飾的成員變量
  • Field getField(String name):獲取指定名稱的public修飾的成員變量
  • Field[] getDeclaredFields():獲取所有的成員變量,不考慮修飾符
  • Filed getDeclaredField(String name):獲取指定名稱的成員變量,不考慮修飾符
  1. Filed:成員變量
  • get(Object object) :獲取值
  • void set(Object obj, Object value):設置值
  • setAccessible(true) :忽略訪問權限修飾符的安全檢查,用於暴力反射,修改私有成員變量的值
 /**
     * 獲取成員變量的信息
     * java.lang.reflect.Field
     * Field類封裝了關於成員變量的操作
     * getFields()方法獲取的是所有的public的成員變量的信息
     * getDeclaredFields獲取的是該類自己聲明的成員變量的信息
     * @param obj
     */
	public static void printFieldMessage(Object obj) {
		Class c = obj.getClass();	// 獲取obj的class對象
		//Field[] fs = c.getFields();
		Field[] fs = c.getDeclaredFields();
		for (Field field : fs) {
			// 獲取成員變量的類型的類類型
			Class fieldType = field.getType();
			String typeName = fieldType.getName();
			// 獲取成員變量的名稱
			String fieldName = field.getName();
			// 獲取成員變量的值(默認初始化 = null)
			Object fieldValue = field.get(obj);
			System.out.println(typeName+" "+fieldName+"="+fieldValue);
			// 設置成員變量的值
			// 注意這裏的應用:暴力反射——可以通過反射對私有變量修改值
			// 前提:忽略訪問權限修飾符的安全檢查
			// field.setAccessible(true);
			// field.set(obj,"value");
		}
	}

(3.2)類成員方法的反射

  1. 獲取類成員方法:
  • Method[] getMethods():獲取所有public修飾的成員方法
  • Method getMethod(String name,類<?>… parameterTypes):獲取指定的public修飾的成員方法,name 爲方法名,parameterTypes爲參數列表(重載)
  • Method[] getDeclaredMethods():獲取所有成員方法
  • Method getDeclaredMethod(String name,類<?>… parameterTypes):獲取指定的成員方法,name 爲方法名,parameterTypes爲參數列表(重載)
  1. Method
  • invoke(obj … args):執行方法
  • setAccessible(true) :忽略訪問權限修飾符的安全檢查,用於暴力反射,修改私有成員方法的值

(2.1)獲取方法

	/**
	 * 打印類的信息,包括類的成員函數、成員變量(只獲取成員函數)
	 * Method類,方法對象
	 * 一個成員方法就是一個Method對象
	 * getMethods()方法獲取的是所有的public的函數,包括父類繼承而來的
	 * getDeclaredMethods()獲取的是所有該類自己聲明的方法,不問訪問權限
	 * @param obj 該對象所屬類的信息
	 */
	public static void printClassMethodMessage(Object obj){
		//要獲取類的信息  首先要獲取類的類類型
		Class c = obj.getClass();
		//獲取類的名稱
		System.out.println("類的名稱是:"+c.getName());

		Method[] ms = c.getMethods();//c.getDeclaredMethods()
		for(int i = 0; i < ms.length;i++){
			Class returnType = ms[i].getReturnType();//得到方法的返回值類型的類類型
			String methodName = ms[i].getName();//得到方法的名稱
			Class[] paramTypes = ms[i].getParameterTypes();	//獲取參數類型--->得到的是參數列表的類型的類類型
			System.out.print(returnType.getName()+" ");
			System.out.print(ms[i].getName()+"(");
			for (Class class1 : paramTypes) {
				System.out.print(class1.getName()+",");
			}
			System.out.println(")");
		}
	}

(2.2)方法反射的操作
通過method.invoke(obj, …args)可以調用obj實例的method方法。

class A{
	public void print(){
		System.out.println("helloworld");
	}
	public void print(int a,int b){
		System.out.println(a+b);
	}
	public void print(String a,String b){
		System.out.println(a.toUpperCase()+","+b.toLowerCase());
	}
}

public class MethodDemo1 {
	public static void main(String[] args) {
  	   //1.首先獲取類
	   //2.獲取方法,方法由名稱和參數列表決定
	   //3.方法的反射操作,用m對象來進行方法調用 和直接用對象a1.print調用的效果完全相同.方法如果沒有返回值返回null,有返回值返回具體的返回值
		A a1 = new A();
		Class c = a1.getClass();

	    try {
	    	Method m = c.getMethod("print", int.class,int.class);//獲取方法print(int,int)
	    	Object o = m.invoke(a1, 10,20);//進行反射操作
             Method m1 = c.getMethod("print",String.class,String.class);//獲取方法print(String,String)
             //a1.print("hello", "WORLD");
             o = m1.invoke(a1, "hello","WORLD");
             Method m2 = c.getMethod("print");//獲取方法print()
             m2.invoke(a1);//進行反射操作
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

(3.3)類構造方法的反射

  1. 獲取類構造方法:
  • Constructor[] getConstructors():獲取public修飾的構造方法
  • Constructor getConstructor(類<?>… parameterTypes):獲取指定的public修飾的構造方法(構造方法的方法名 = 類名),parameterTypes爲參數列表
  • Constructor[] getDeclaredConstructors():獲取所有構造方法
  • Constructor getDeclaredConstructor(類<?>… parameterTypes):獲取指定的構造方法,name爲方法名(構造方法的方法名 = 類名),parameterTypes爲參數列表
  1. Constructor:構造方法
  • T.newInstance(Object… init args):創建對象
  • Class.newInstance():如果使用空參數構造方法創建對象,操作可以簡化爲:Class對象的newInstance方法
  • setAccessible(true) :忽略訪問權限修飾符的安全檢查,用於暴力反射,修改私有構造方法的值

(3.1)獲取構造函數

	/**
	 * 打印對象的構造函數的信息
	 * 構造函數也是對象
	 * java.lang. Constructor中封裝了構造函數的信息
	 * getConstructors獲取所有的public的構造函數
	 * getDeclaredConstructors得到所有的構造函數
	 * @param obj
	 */
	public static void printConMessage(Object obj){
		Class c = obj.getClass();

		//Constructor[] cs = c.getConstructors();
		Constructor[] cs = c.getDeclaredConstructors();
		for (Constructor constructor : cs) {
			System.out.print(constructor.getName()+"(");
			//獲取構造函數的參數列表--->得到的是參數列表的類類型
			Class[] paramTypes = constructor.getParameterTypes();
			for (Class class1 : paramTypes) {
				System.out.print(class1.getName()+",");
			}
			System.out.println(")");
		}
	}

(3.2)通過構造函數生成類實例

public class ClassTest {
    public static void main(String[] args) throws NoSuchMethodException {
        Class class_dog = Dog.class;
		// 1. 獲取實例構造器(含參)
        Constructor constructor = class_dog.getConstructor(String.class, int.class);
		Object dog1 = constructor.newInstance("Tom", 10); // 通過newInstance生成類實例,如果沒有顯示的聲明默認構造器,class_dog.getConstructor()會拋出NoSuchMethodException異常。
		// 2. 獲取構造方法(不含參)
		Constructor constructor1 = class_dog.getConstructor();
		Object dog2 = class_dog.newInstance(); // 不含參的構造方法可以通過Class.newInstance簡化生成對象
    }
}
class Dog {
    private String name;
    private int age;
    
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

(3.4)類名的獲取

String getName()

4、反射案例

(4.1)Class類的動態加載

編譯時刻加載的類是靜態加載類,運行時刻加載的類是動態加載類。

  • 需求
    寫一個"框架",在不能改變該類任何代碼的前提下,可以幫我們創建任意類的對象,並且執行其中任意方法。
  • 實現
  1. 配置文件
  2. 反射
  • 步驟
  1. 將需要創建的對象的全類名和需要執行的方法定義在配置文件中
  2. 在程序中加載讀取配置文件
  3. 使用反射奇數來加載類文件進內存
  4. 創建對象,執行方法

Person.java

public class Person{
	String name;
	int age;
	public void sleep(){System.out.println("sleep...");}
}

Student.java

public class Student{
	String stuName;
	String stuNum;
	public void study(){System.out.println("study...");}
}

pro.properties文件

// 動態加載Person類
// 輸出 sleep...
className = cn.itcast.domain.Person
methodName = sleep
// 動態加載Student類
// 輸出study...
className = cn.itcast.domain.Student
methodName = study

ReflectTest.java

public class ReflectTest{
	public static void main(String[] args){
		// 可以創建任意類對象,可以執行任意方法,且不用改變類的任何代碼
		// 1. 加載配置文件
		// 1.1 創建Properties對象
		Properties pro = new Properties();
		// 1.2 加載配置文件,轉換爲一個集合
		// 1.2.1 獲取class目錄下配置文件
		ClassLoader classLoader = ReflectTest.class.getClassLoader();
		InputStream is = classLoader.getResourceAsStream("pro.properties");
		pro.load(is)l
		// 2. 獲取配置文件中定義的數據
		String className = pro.getProperty("className");
		String methodName = pro.getProperty("methodName");
		// 3. 加載該類進內存
		Class cls = Class.forName(className);
		// 4. 創建對象
		Object obj = cls.newInstance();
		// 5. 獲取方法對象
		Method method = cls.getMethod(methodName);
		// 6. 執行方法
		method.invoke(obj);
	}
}

(4.2)JDBC數據庫連接

在JDBC 的操作中,如果要想進行數據庫的連接,則必須按照以上的幾步完成

  1. 通過Class.forName()加載數據庫的驅動程序 (通過反射加載,前提是引入相關了Jar包)
  2. 通過 DriverManager 類進行數據庫的連接,連接的時候要輸入數據庫的連接地址、用戶名、密碼
  3. 通過Connection 接口接收連接
public class ConnectionJDBC {  
  
    /** 
     * @param args 
     */  
    //驅動程序就是之前在classpath中配置的JDBC的驅動程序的JAR 包中  
    public static final String DBDRIVER = "com.mysql.jdbc.Driver";  
    //連接地址是由各個數據庫生產商單獨提供的,所以需要單獨記住  
    public static final String DBURL = "jdbc:mysql://localhost:3306/test";  
    //連接數據庫的用戶名  
    public static final String DBUSER = "root";  
    //連接數據庫的密碼  
    public static final String DBPASS = "";  
      
      
    public static void main(String[] args) throws Exception {  
        Connection con = null; //表示數據庫的連接對象  
        Class.forName(DBDRIVER); //1、使用CLASS 類加載驅動程序 ,反射機制的體現 
        con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、連接數據庫  
        System.out.println(con);  
        con.close(); // 3、關閉數據庫  
    }  

(4.3)Spring框架使用——反射

在 Java的反射機制在做基礎框架的時候非常有用,行內有一句這樣的老話:反射機制是Java框架的基石。一般應用層面很少用,不過這種東西,現在很多開源框架基本都已經封裝好了,自己基本用不着寫。典型的除了hibernate之外,還有spring也用到很多反射機制。最經典的就是xml的配置模式。
Spring通過XML配置模式裝載Bean的過程:

  1. 將程序內所有XML或Properties配置文件加載入內存中
  2. Java類裏面解析xml或properties裏面的內容,得到對應實體類的字節碼字符串以及相關屬性信息
  3. 使用反射機制,根據這個字符串獲得某個類的Class實例
  4. 動態配置實例的屬性

通過反射,Spring框架可以在不改變代碼的前提下,直接修改配置文件。模擬Spring加載XML配置文件:

public class BeanFactory {
       private Map<String, Object> beanMap = new HashMap<String, Object>();
       /**
       * bean工廠的初始化.
       * @param xml xml配置文件
       */
       public void init(String xml) {
              try {
                     //讀取指定的配置文件
                     SAXReader reader = new SAXReader();
                     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                     //從class目錄下獲取指定的xml文件
                     InputStream ins = classLoader.getResourceAsStream(xml);
                     Document doc = reader.read(ins);
                     Element root = doc.getRootElement();  
                     Element foo;
                    
                     //遍歷bean
                     for (Iterator i = root.elementIterator("bean"); i.hasNext();) {  
                            foo = (Element) i.next();
                            //獲取bean的屬性id和class
                            Attribute id = foo.attribute("id");  
                            Attribute cls = foo.attribute("class");
                           
                            //利用Java反射機制,通過class的名稱獲取Class對象
                            Class bean = Class.forName(cls.getText());
                           
                            //獲取對應class的信息
                            java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
                            //獲取其屬性描述
                            java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
                            //設置值的方法
                            Method mSet = null;
                            //創建一個對象
                            Object obj = bean.newInstance();
                           
                            //遍歷該bean的property屬性
                            for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {  
                                   Element foo2 = (Element) ite.next();
                                   //獲取該property的name屬性
                                   Attribute name = foo2.attribute("name");
                                   String value = null;
                                  
                                   //獲取該property的子元素value的值
                                   for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {
                                          Element node = (Element) ite1.next();
                                          value = node.getText();
                                          break;
                                   }
                                  
                                   for (int k = 0; k < pd.length; k++) {
                                          if (pd[k].getName().equalsIgnoreCase(name.getText())) {
                                                 mSet = pd[k].getWriteMethod();
                                                 //利用Java的反射極致調用對象的某個set方法,並將值設置進去
                                                 mSet.invoke(obj, value);
                                          }
                                   }
                            }
                           
                            //將對象放入beanMap中,其中key爲id值,value爲對象
                            beanMap.put(id.getText(), obj);
                     }
              } catch (Exception e) {
                     System.out.println(e.toString());
              }
       }
      
       //other codes
}

(九)Java註解

1、什麼是Java註解

註解與註釋:
註解:說明程序的。給計算機看得。
註釋:用文字描述程序,給程序員看的。方便程序員理解。
註解(Annotation)是插入代碼中的元數據,一種代碼級別的說明。它是在JDK5.0及以後版本引入的一個特性,與類、接口、枚舉是在同一個層次。它可以聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,註釋。
(1)JDK 1.5 之後的新特性
(2)用來說明程序
(3)使用註解:@註解名稱
Annotation的作用大致可分爲三類:

  • 編寫文檔:通過代碼裏標識的註解生成文檔(生成java doc文檔(api));
/**
* 註解javadoc演示
* @author itcast
* @version 1.0
* @since 1.5
* /
public class AnnoDemo1 {
	/**
	* 計算兩數的和
	* @param a 整數
	* @param b 整數
	* @return 兩數的和
	* /
	public int add(int a,int b){
		return a+b;
	}
}

生成命令

javadoc AnnoDemo1.java
  • 代碼分析:通過代碼裏標識的註解對代碼進行分析(使用反射);
  • 編譯檢查:通過代碼裏標識的註解讓編譯器能實現基本的編譯檢查;

Java提供了一種源程序中的元素關聯任何信息和任何元數據的途徑和方法。
它可以在編譯期使用預編譯工具進行處理, 也可以在運行期使用 Java 反射機制進行處理,用於創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。
本質上,Annotion是一種特殊的接口,程序可以通過反射來獲取指定程序元素的Annotion對象,然後通過Annotion對象來獲取註解裏面的元數據。(元數據metadata:關於數據的數據)

2、Java中的常見註解

(2.1)JDK中內置註解

  1. @Override
    用於檢測被該註解標註的方法是否時繼承自父類(接口)的。
    @Override 是一個標記註解類型,它被用作標註方法。它說明了被標註的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種Annotation在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示:Method does not override method from its superclass
public class Fruit{
    public void displayName(){
        System.out.println("水果的名字是:*****");
    }
}

class Orange extends Fruit{
    @Override
    public void displayName(){
        System.out.println("水果的名字是:桔子");
    }
}
  1. @Deprecated
    用於標註已經過時的方法;
    Deprecated也是一個標記註解。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標註的程序元素。而且這種修飾具有一定的 “延續性”:如果我們在代碼中通過繼承或者覆蓋的方式使用了這個過時的類型或者成員,雖然繼承或者覆蓋後的類型或者成員並不是被聲明爲 @Deprecated,但編譯器仍然要報警。
  2. @SuppressWarnnings
    用於通知java編譯器禁止特定的編譯警告;
    @SuppressWarnings 其註解目標爲類、字段、函數、函數入參、構造函數和函數的局部變量。在java5.0,sun提供的javac編譯器爲我們提供了-Xlint選項來使編譯器對合法的程序代碼提出警告,此種警告從某種程度上代表了程序錯誤。有時我們無法避免這種警告,在調用的方法前增加@SuppressWarnings修飾,告訴編譯器停止對此方法的警告。
    一般傳遞參數all @SuppressWarnnings(“all”)
public class AppleService {
    public void displayName(){
        System.out.println("水果的名字是:蘋果");
    }

    /**
     * @deprecated 該方法已經過期,不推薦使用
     */
    @Deprecated
    public void showTaste(){
        System.out.println("水果的蘋果的口感是:脆甜");
    }

    public void showTaste(int typeId){
        if(typeId==1){
            System.out.println("水果的蘋果的口感是:酸澀");
        }
        else if(typeId==2){
            System.out.println("水果的蘋果的口感是:綿甜");
        }
        else{
            System.out.println("水果的蘋果的口感是:脆甜");
        }
    }
}

public class AppleConsumer {
    //@SuppressWarnings({"deprecation"})	壓制過時警告(對於過時的方法不顯示警告)
    public static void main(String[] args) {
        AppleService appleService=new AppleService();
        appleService.showTaste();
        appleService.showTaste(2);
    }
}

ppleService類的showTaste() 方法被@Deprecated標註爲過時方法,在AppleConsumer類中使用的時候,編譯器會給出該方法已過期,不推薦使用的提示。
但如果標註@SuppressWarnings({“deprecation”}),則抑制了過時警告,編譯器不會報警告。
(2.2)Java第三方註解
Spring:@Autowired、@Service、@Repository
Mybatis:@InsertProvider、@UpdateProvider、@Options

3、註解的分類

(3.1)按照運行機制分類

  • 源碼註解:註解只在源碼中存在,編譯成.class文件就不存在了
  • 編譯時註解:註解只在源碼中與.class文件都存在了,包括@Override、@Deprecated、@SuppressWarnnings
  • 運行時註解:在運行階段仍起作用,甚至影響運行邏輯的註解,如@Autowired

(3.2)根據註解功能與用途

  • 系統內置註解:系統自帶的註解類型,如@Override
  • 元註解:註解的註解,負責註解其他註解,如@Target
  • 自定義註解:用戶根據自己的需求自定義的註解類型

(3.3)根據成員個數分類

  • 標記註解:沒有定義成員的Annotation類型,自身代表某類信息,如:@Override
  • 單成員註解:只定義了一個成員,比如@SuppressWarnings 定義了一個成員String[] value,使用value={…}大括號來聲明數組值,一般也可以省略“value=”
  • 多成員註解:定義了多個成員,使用時以name=value對分別提供數據

4、自定義註解

(4.1)格式

元註解
public @interface 註解名稱{
	... 屬性列表
}

(4.2)本質
註解本質上就是一個接口,該接口默認繼承java.lang.annotation.Annotation接口。

public interface MyAnno extends java.lang.annotation.Annotation{}

(4.3)屬性
接口中的抽象方法被稱爲註解的屬性。每一個抽象方法實際上是聲明瞭一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數類型。

  1. 屬性的返回值類型有下列取值:byte,short,char,int,long,float,double,boolean八種基本數據類型、String、enum枚舉類型、Annotation註解類型及以上類型的數組
  2. 定義了屬性,在使用時需要給屬性賦值
  • 如果定義屬性時,使用default關鍵字給屬性默認初始化值,則使用註解時,可以不進行屬性的賦值
  • 如果只有一個屬性需要賦值,且屬性的名稱是value,則value可以省略,直接定義值即可(例如@SuppressWarnings(“all”))
  • 數組賦值時,值使用{}包裹。如果數組中只有一個值,則{}省略
public @interface MyAnnotation{
	int age();									
	String name() default "張三";	
	String[] strs();
}

public @interface SuppressWarnings{
	String[] value();
}

@MyAnno(age = 10,name = "李四",strs={"aaa","bbb","ccc"} )
public class Worker{}

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

  • @Target
    作用:
    描述註解能夠作用的位置
    取值(ElementType):
    1.CONSTRUCTOR:用於描述構造器
    2.FIELD:用於描述域
    3.LOCAL_VARIABLE:用於描述局部變量
    4.METHOD:用於描述方法
    5.PACKAGE:用於描述包
    6.PARAMETER:用於描述參數
    7.TYPE:用於描述類、接口(包括註解類型) 或enum聲明
  • @Retention
    作用:
    描述該註解的生命週期,表示在什麼編譯級別上保存該註解的信息。Annotation被保留的時間有長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因爲Annotation與class在使用上是被分離的)。
    取值(RetentionPoicy):
    1.SOURCE:當前被描述的註解只在源文件中有效,不會保留到class字節碼文件中,也不會被JVM讀取到。
    2.CLASS:當前被描述的註解,會保留到class字節碼文件中,不會被JVM讀取到。
    3.RUNTIME:當前被描述的註解,會保留到class字節碼文件中,並被JVM讀取到。
  • @Documented
    @Documented 用來描述註解是否被抽取到api文檔中。在生成javadoc文檔的時候將該Annotation也寫入到文檔中。
  • @Inherited
    @Inherited 元註解是一個標記註解,@Inherited用來描述註解是否被子類繼承。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno{}

(4.5)在程序使用(解析)註解:獲取註解中定義的屬性值

  1. 解析註解信息的方法
    Java使用Annotation接口來代表程序元素前面的註解,該接口是所有Annotation類型的父接口。相應地,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受註解的程序元素。
    實際上,java.lang.reflect 包所有提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義爲運行時的Annotation後,該註解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation纔會被虛擬機讀取。
    AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之後,程序就可以調用該對象的如下七個方法來訪問Annotation信息:
  • T getAnnotation(Class annotationClass) :返回該程序元素上存在的、指定類型的註解,如果該類型註解不存在,則返回null;
  • Annotation[] getDeclaredAnnotation(Class):返回該程序元素上存在的、指定類型的註解,如果該類型註解不存在,則返回null;與此接口中的其他方法不同,該方法將忽略繼承的註解;
  • Annotation[] getAnnotations():返回該程序元素上存在的所有註解;
  • Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註釋。與此接口中的其他方法不同,該方法將忽略繼承的註解;
  • Annotation[] getAnnotationsByType(Class):返回直接存在於此元素上指定註解類型的所有註解;
  • Annotation[] getDeclaredAnnotationsByType(Class):返回直接存在於此元素上指定註解類型的所有註解。與此接口中的其他方法不同,該方法將忽略繼承的註解;
  • boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,否則返回false;
  1. 解析註解信息實例
/***********註解聲明***************/
/**
 * 水果名稱註解
 */
@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{BLUE, RED, GREEN};

    /**
     * 顏色屬性
     * @return
     */
    Color fruitColor() default Color.GREEN;
}


/**
 * 水果供應商註解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供應商編號
     * @return
     */
    public int id() default -1;

    /**
     * 供應商名稱
     * @return
     */
    public String name() default " ";

    /**
     * 供應商地址
     * @return
     */
    public String address() default " ";
}
/***********註解使用***************/
public class Apple {
    @FruitName("Apple")
    private String appleName;
    @FruitColor(fruitColor = FruitColor.Color.RED)
    private String appleColor;
    @FruitProvider(id = 1, name = "陝西紅富士集團", address = "陝西紅富士大廈")
    private String appleProvider;

    public String getAppleProvider() {
        return appleProvider;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }

    public String getAppleName() {
        return appleName;
    }

    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }

    public String getAppleColor() {
        return appleColor;
    }

    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }

    public void displayName(){
        System.out.println(getAppleName());
    }
}
/***********註解信息獲取***************/
public class AnnotationParser {
    public static void main(String[] args) {
        Field[] fields = Apple.class.getDeclaredFields();
        for (Field field : fields) {
            //System.out.println(field.getName().toString());
            if (field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = field.getAnnotation(FruitName.class);
                System.out.println("水果的名稱:" + fruitName.value());
            }else if (field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor = field.getAnnotation(FruitColor.class);
                System.out.println("水果的顏色:"+fruitColor.fruitColor());
            }else if (field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);
                System.out.println("水果供應商編號:" + fruitProvider.id() + " 名稱:" + fruitProvider.name() + " 地址:" + fruitProvider.address());
            }
        }
    }
}
/***********輸出結果***************/
水果的名稱:Apple
水果的顏色:RED
水果供應商編號:1 名稱:陝西紅富士集團 地址:陝西紅富士大廈

(4.6)小結

  1. 註解很常用
  2. 註解一般給編譯器、解析程序使用
  3. 註解不是程序的一部分,可理解爲一個標籤

5、項目實戰1——框架

(5.1)反射框架

  1. 利用註解代替配置文件
/**
* 描述需要執行的類名和方法名
* /
@Target ({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro{
	String className();
	String methodName();
}
}
  1. 註解解析案例:實現反射動態加載類
    (1)通過反射獲取註解定義位置的對象(Class,Method,Field)
    (2)獲取指定的註解:getAnnotation(Class)其實就是在內存中生成了一個該註解接口的子類實現對象
    (3)調用註解中的抽象方法獲取配置的屬性值
public class ReflectTest{
		public static void main(String[] args) throws Exception{
		// 前提:不能改變該類的任何代碼,可以創建任意類的對象,可以執行任意方法
		// 1. 解析註解
		// 1.1 獲取該類的字節碼文件對象
		Class<ReflectTest> reflectTestClass = ReflectTest.class;
		// 2.獲取上邊的註解對象,起始就是在內存中生成了一個註解接口的子類實現對象
		// public class ProImpl implements Pro{
		//	public String className(){
		//		return "cn.itcast.annotation.Demo1";
		// 	}
		// 	public String methodName(){
		//		return "show";
		//	}
		// }
		Pro an = reflectTestClass.getAnnotation(Pro.class);
		// 3.調用註解對象中定義的抽象方法,獲取返回值
		String className = an.className();
		String methodName = an.methodName();
		// 4. 加載該類進內存
		Class cls = Class.forName(className);
		// 5. 創建對象
		Object obj = cls.newInstance();
		// 6. 獲取方法對象
		Method method = cls.getMethod(methodName);
		// 7. 執行方法
		method.invoke(obj);
}

(5.2) 測試框架

  1. 簡單的測試框架
// 簡單的測試框架
// 當主方法柱形後,會自動執行被檢測的所有方法(加Check註解的方法),判斷方法是否有異常,記錄到文件中
public class TestCheck{
	public static void main(String[] args){
		// 1.創建計算器對象
		Calculator c = new Calculator();
		// 2. 獲取字節碼文件對象
		Class cls = c.getClass();
		// 3. 獲取所有方法
		Method[] methods = cls.getMethods();
		int number = 0;//出現異常的次數
		BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

		for(Method method: methods){
		// 4. 判斷方法上是否有Check註釋
		if(method.isAnnotationPresent(Check.class)){
		// 5.如果有Check註釋,則執行
		try{
			method.invoke(c);
		}catch(Exception e){
		// 6. 捕獲異常,並記錄到文件中
			number ++;
			bw.write(method.getName() + "方法出現異常");
			bw.write("異常的名稱:"+e.getCause().getClass().getSimpleName());
			bw.write("異常的原因:"+e.getCause().getMessage());
		}
	}
}
bw.write("本次測試一共出現"+number+"次異常");
bw.flush();
bw.close();
}
}
  1. 定義的計算器類
public class Calculator{
	// 加法
	@Check
	public void add(){System.out.println("1+0="+(1+0));}
	// 減法
	@Check
	public void sub(){System.out.println("1-0="+(1-0));}
	// 乘法
	@Check
	public void mul(){System.out.println("1*0="+(1*0));}
	// 除法
	@Check
	public void div(){System.out.println("1/0="+(1/0));}
}
  1. Check註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check{}

6、項目實戰2——Hibernate

(6.1)項目需求
項目取自一個公司持久層架構,用來代替Hibernate解決方案,核心代碼通過註解實現。
1、有一張用戶表,字段包括用戶ID,用戶名、手機號;
2、有一個用戶類,方便的對每個字段或字段組合條件進行檢索,並打印出SQL。
(6.2)註解實戰
註解聲明
Table註解

@Target({ElementType.TYPE})//作用域爲類或接口
@Retention(RetentionPolicy.RUNTIME)//生命週期爲運行時
public @interface Table{
	String value();//一個值,表名
}

Column註解

@Target({ElementType.Field})//作用域爲字段
@Retention(RetentionPolicy.RUNTIME)//生命週期爲運行時
public @interface Column{
	String value();//一個值,字段名
}

註解使用

@Table("db_user")
public class User{
	@Column("_id")
	private int id;
	@Column("user_name");
	private String userName;
	@Column("mobile_phone");
	private String mobilePhone;
}

註解信息獲取
通過註解進行對數據庫信息的查找

    //根據userName在數據庫中進行查詢
    public static String FindUserByUserName(String userName) {
    		StringBuilder sb = new StringBuilder();
    		//1.獲取class
    		Class clazz = User.class;
    		//2.獲取table的名字
    		boolean exists = clazz.isAnnotationPresent(Table.class);
    		if(!exists)return null;
    		Table t = (Table)clazz.getAnnotation(Table.class);
    		Stirng tableName = t.value;
    		sb.append("select * from ").append(tableName).append("where 1=1");
    		//3.獲取userName字段
    		Field field = clazz.getDeclaredField("userName");
    		//4.處理每個字段對應的sql
			//4.1 拿到數據庫字段名
			boolean fExists = field.isAnnotationPresent(Column.class);
			String columnName = Column.value();
			//4.2 拿到字段的值
			String fieldValue = userName;
			//4.3 拼裝sql
			sb.append(" and "+columnName+" = '"+fieldValue + "'");
			
			return sb.toString();
    }

輸出結果

select * from db_user where user_name = 'chy'

(十)依賴倒置(DIP)、依賴注入(DI)和控制反轉(IOC)

1、依賴(Dependency)

定義
依賴是類與類之間的連接,依賴關係表示一個類依賴於另一個類的定義,通俗來講就是需要
實例
一個人(Person)可以買車(Car)和房子(House),Person類依賴於Car類和House類

public static void main(String ... args){
        //TODO:

    Person person = new Person();
    person.buy(new House());
    person.buy(new Car());

}

static class Person{

    //表示依賴House
    public void buy(House house){}
    //表示依賴Car
    public void buy(Car car){}
}

static class House{
}

static class Car{
}

2、依賴倒置(Dependence Inversion Principle)

定義
高層模塊不應該依賴低層模塊,二者都該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象;通俗來講,依賴倒置原則的本質就是通過抽象(接口或抽象類)使個各類或模塊的實現彼此獨立,互不影響,實現模塊間的松耦合。
類A直接依賴類B,假如要將類A改爲依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責複雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操作;假如修改類A,會給程序帶來不必要的風險。此時將類A修改爲依賴接口interface,類B和類C各自實現接口interface,類A通過接口interface間接與類B或者類C發生聯繫,則會大大降低修改類A的機率。
實例
(1)不使用依賴倒置,每次出行都需要修改Person類代碼

public class Person {

    private Bike mBike;
    private Car mCar;
    private Train mTrain;

    public Person(){
        mBike = new Bike();
        //mCar = new Car();
//        mTrain = new Train();
    }

    public void goOut(){
        System.out.println("出門啦");
        mBike.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person person = new Person();
        person.goOut();
    }
}

(2)使用依賴倒置,上層模塊不應該依賴底層模塊,它們都應該依賴於抽象。抽象不應該依賴於細節,細節應該依賴於抽象。

public class Person {

//    private Bike mBike;
    private Car mCar;
    private Train mTrain;
    private Driveable mDriveable;

    public Person(){
//        mBike = new Bike();
        //mCar = new Car();
       mDriveable = new Train();
    }

    public void goOut(){
        System.out.println("出門啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person person = new Person();
        person.goOut();
    }
}

3、控制反轉(Inversion of Control)

定義
IoC 是一種新的設計模式,它對上層模塊與底層模塊進行了更進一步的解耦。控制反轉的意思是反轉了上層模塊對於底層模塊的依賴控制。
實例

public class Person2 {

    private Driveable mDriveable;

    public Person2(Driveable driveable){
        this.mDriveable = driveable;
    }

    public void goOut(){
        System.out.println("出門啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:將 mDriveable 的實例化移到 Person 外面
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}

就這樣無論出行方式怎麼變化,Person 這個類都不需要更改代碼了。
在上面代碼中,Person 把內部依賴的創建權力移交給了 Person2。也就是說 Person 只關心依賴提供的功能,但並不關心依賴的創建。
其中Person2稱爲IoC容器(依賴注入的地方)

4、依賴注入(Dependency injection)

爲了不因爲依賴實現的變動而去修改 Person,也就是說以可能在 Driveable 實現類的改變下不改動 Person 這個類的代碼,儘可能減少兩者之間的耦合需要採用IoC 模式來進行改寫代碼。
這個需要我們移交出對於依賴實例化的控制權,Person 無法實例化依賴了,它就需要在外部(IoC 容器)賦值給它,這個賦值的動作有個專門的術語叫做注入(injection),需要注意的是在 IoC 概念中,這個注入依賴的地方被稱爲 IoC 容器,但在依賴注入概念中,一般被稱爲注射器 (injector)。
表達通俗一點就是:我不想自己實例化依賴,你(injector)創建它們,然後在合適的時候注入給我

實現依賴注入有 3 種方式:

  • 構造函數中注入
  • setter 方式注入
  • 接口注入
/**
 * 接口方式注入
 * 接口的存在,表明了一種依賴配置的能力。
 */
public interface DepedencySetter {
    void set(Driveable driveable);
}
public class Person2  implements DepedencySetter {

    //接口方式注入
    @Override
    public void set(Driveable driveable) {
        this.mDriveable = mDriveable;
    }

    private Driveable mDriveable;

    //構造函數注入
    public Person2(Driveable driveable){
        this.mDriveable = driveable;
    }

    //setter 方式注入
    public void setDriveable(Driveable mDriveable) {
        this.mDriveable = mDriveable;
    }

    public void goOut(){
        System.out.println("出門啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}

(十一)Java代理模式

1、代理模式基本概念及分類

(1.1)基本概念
在這裏插入圖片描述
爲其他對象提供一種代理以控制對這個對象的訪問。代理類和委託類有共同的父類或父接口,這樣在任何使用委託類對象的地方都可以用代理對象替代。代理對象起到中介的作用,可去掉功能服務或增加額外的服務。負責爲委託類預處理消息,過濾消息並將請求分派給委託類處理,以及進行消息被委託類執行後的後續操作。
例如火車票代售處是火車站的代理,相對於火車站,可以提供額外的服務,如電話預約,提供額外服務的同時,會收取一定金額的手續費。也可以將原有的功能去掉,如代售處不能提供退票服務。
(1.2)代理模式模型
代理模式一般設計到角色有4 種:
1、抽象角色:對應代理接口(<< interface >>Subject),用來定義代理類和委託類的公共對外方法/接口;
2、真實角色:對應委託類(接口實現類RealSubject),真正實現業務邏輯的類,是代理角色所代表的真實對象,是最終要引用的對象;
3、代理角色:對應代理類(Proxy),用來代理和封裝真實角色。代理角色內部含有對真實對象的引用,從而可以操作真實對象。同時,代理對象可以在執行真是對象操作時,添加或去除其他操作,相當於對真實對象進行封裝;
4、客戶角色:對應客戶端,使用代理類和主題接口完成一些工作。
在這裏插入圖片描述
在代理模式中真實角色對於客戶端角色來說的透明的,也就是客戶端不知道也無需知道真實角色的存在。 爲了保持行爲的一致性,代理角色和真實角色通常會實現相同的接口,所以在訪問者看來兩者沒有絲毫的區別。
通過代理角色這中間一層,能有效控制對真實角色(委託類對象)的直接訪問,也可以很好地隱藏和保護委託類對象,同時也爲實施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。
(1.3)代理模式特點
(1.3.1)代理模式優點

  • 隱藏委託類的實現,調用者只需要和代理類進行交互即可。
  • 解耦,在不改變委託類代碼情況下做一些額外處理,比如添加初始判斷及其他公共操作
    (1.3.2)代理模式應用場景
    代理的使用場景很多,struts2中的 action 調用, hibernate的懶加載, spring的 AOP無一不用到代理。總結起來可分爲以下幾類:
    1、在原方法執行之前和之後做一些操作,可以用代理來實現(比如記錄Log,做事務控制等)。
    2、封裝真實的主題類,將真實的業務邏輯隱藏,只暴露給調用者公共的主題接口。
    3、在延遲加載上的應用。
    (1.4)常見代理模式
    (1)遠程代理:爲不同地理的對象,提供局域網代表對象。類似客戶端-服務器代理模式。
    (2)虛擬代理:根據需要將資源消耗很大的對象進行延遲,真正需要的時候進行創建。如網絡圖片緩存。
    (3)保護代理:控制用戶的訪問權限,如網頁需要註冊才能瀏覽發帖。
    (4)智能引用代理:提供對目標對象額外的服務。如日誌處理、權限管理、事務處理……

2、代理模式實現方式

根據代理類的生成時間不同可以將代理分爲靜態代理和動態代理。

(2.1)靜態代理

所謂靜態代理也就是在程序運行前就已經存在代理類的.class文件,代理類和委託類的關係在運行前就確定了。
接口

public interface Moveable {
	void move();
}

被代理對象Car

public class Car implements Moveable {

	@Override
	public void move() {
		//實現開車
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽車行駛中....");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

通過代理記錄Car行駛時間
(2.1.1)繼承方式

public class Car2 extends Car {

	@Override
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("汽車開始行駛....");
		super.move();
		long endtime = System.currentTimeMillis();
		System.out.println("汽車結束行駛....  汽車行駛時間:" 
				+ (endtime - starttime) + "毫秒!");
	}
	
}

(2.1.2)聚合方式
聚合:在代理中引用被代理對象

public class Car3 implements Moveable {

	public Car3(Car car) {
		super();
		this.car = car;
	}

	private Car car;
	
	@Override
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("汽車開始行駛....");
		car.move();
		long endtime = System.currentTimeMillis();
		System.out.println("汽車結束行駛....  汽車行駛時間:" 
				+ (endtime - starttime) + "毫秒!");
	}

}

(2.1.3)繼承方式與聚合方式對比
聚合方式比繼承方式更適合代理模式:適合功能的疊加(可靈活傳遞,組合)
記錄日誌代理

public class CarLogProxy implements Moveable {

	public CarLogProxy(Moveable m) {
		super();
		this.m = m;
	}

	private Moveable m;
	
	@Override
	public void move() {
		System.out.println("日誌開始....");
		m.move();
		System.out.println("日誌結束....");
	}

}

記錄時間代理

public class CarTimeProxy implements Moveable {

	public CarTimeProxy(Moveable m) {
		super();
		this.m = m;
	}

	private Moveable m;
	
	@Override
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("汽車開始行駛....");
		m.move();
		long endtime = System.currentTimeMillis();
		System.out.println("汽車結束行駛....  汽車行駛時間:" 
				+ (endtime - starttime) + "毫秒!");
	}

}

測試

	public static void main(String[] args) {
		Car car = new Car();
		CarLogProxy clp = new CarLogProxy(car);
		CarTimeProxy ctp = new CarTimeProxy(clp);
		ctp.move();//先記錄日誌後記錄時間
	}

如果要按照上述的方法使用代理模式,那麼真實角色(委託類)必須是事先已經存在的,並將其作爲代理對象的內部屬性。但是實際使用時,一個真實角色必須對應一個代理角色,如果大量使用會導致類的急劇膨脹;此外,如果事先並不知道真實角色(委託類),該如何使用代理呢?這個問題可以通過Java的動態代理類來解決。

(2.2)動態代理

通過動態代碼可實現對不同類、不同方法的代理。動態代理的源碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件(.class)。代理類和委託類的關係在程序運行時確定。
(2.2.1)JDK動態代理
1.實現模式
在這裏插入圖片描述
Java動態代理類位於java.lang.reflect包下,一般主要涉及到以下兩個類:
(1)Interface InvocationHandler
InvocationHandler是負責連接代理類和委託類的中間類必須實現的接口,它自定義了一個 invoke 方法,用於集中處理在動態代理類對象上的方法調用,每次生成動態代理對象都邀制定一個對應的調用處理器對象,通常在該方法中實現對委託類的代理訪問。

public object invoke(Object obj,Method method,Object[] args)

在實際使用時,obj指代理類的實例,method指被代理的方法,args是該方法的參數數組。這個抽象方法在代理類中動態實現。
該方法也是InvocationHandler接口所定義的唯一的一個方法,該方法負責集中處理動態代理類上的所有方法的調用。調用處理器根據這三個參數進行預處理或分派到委託類實例上執行。
(2)Proxy class動態代理類
Proxy是 Java 動態代理機制的主類,它提供了一組靜態方法來爲一組接口動態地生成代理類及其對象。

static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)

返回代理類的一個實例,返回後的代理類可以當做被代理類使用(可使用被代理類在接口中聲明過的方法)
該方法用於爲指定類裝載器、一組接口及調用處理器生成動態代理類實例
2.實例
步驟1:創建一個實現接口InvocationHandler的調用處理器,它必須實現invoke方法

public class TimeHandler implements InvocationHandler {
//動態代理類對應的調用處理程序類(時間處理器)
	public TimeHandler(Object target) {
		super();
		this.target = target;
	}
//代理類持有一個委託類的對象引用
	private Object target;
	
	/*
	 * 參數:
	 * proxy  被代理對象
	 * method  被代理對象的方法
	 * args 方法的參數
	 * 
	 * 返回值:
	 * Object  方法的返回值
	 * */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		long starttime = System.currentTimeMillis();
		System.out.println("汽車開始行駛....");
		method.invoke(target);//調用被代理對象的方法(Car的move方法)
		long endtime = System.currentTimeMillis();
		System.out.println("汽車結束行駛....  汽車行駛時間:" 
				+ (endtime - starttime) + "毫秒!");
		return null;
	}
}

TimeHandler實現了InvocationHandler的invoke方法,當代理對象的方法被調用時,invoke方法會被回調。其中proxy表示實現了公共代理方法的動態代理對象。
步驟2:創建被代理的類以及接口
接口

public interface Moveable {
	void move();
}

代理類

public class Car implements Moveable {

	@Override
	public void move() {
		//實現開車
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽車行駛中....");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

步驟3:調用Proxy的靜態方法newProxyInstance,提供ClassLoader和代理接口類型數組動態創建一個代理類,並通過代理調用方法

//客戶端,使用代理類和主題接口完成功能
public class Test {
	/**
	 * JDK動態代理測試類
	 */
	public static void main(String[] args) {
		Car car = new Car();
		InvocationHandler h = new TimeHandler(car);
		Class<?> cls = car.getClass();
		/**
		 * loader  類加載器
		 * interfaces  實現接口
		 * h InvocationHandler
		 */
		Moveable m = (Moveable)Proxy.newProxyInstance(cls.getClassLoader(),
												cls.getInterfaces(), h);//獲得動態代理對象,動態代理對象與代理對象實現同一接口
		m.move();//調用動態代理的move方法
	}
}

上面代碼通過InvocationHandler handler=new TimeHandler(target);將委託對象作爲構造方法的參數傳遞給了TimeHandler來作爲代理方法調用的對象。當我們調用代理對象的move()方法時,該調用將會被轉發到TimeHandler對象的invoke上,從而達到動態代理的效果。
3.總結
所謂動態代理是這樣一種class:它是運行時生成的class,該class需要實現一組interface,使用動態代理類時,必須實現InvocationHandler接口
(2.2.2)cglib動態代理
實例
步驟1:引入cglib-nodep.jar
步驟2:代理類,不用實現接口

public class Train {

	public void move(){
		System.out.println("火車行駛中...");
	}
}

步驟3:cglib代理類,實現MethodInterceptor接口

public class CglibProxy implements MethodInterceptor {

	private Enhancer enhancer = new Enhancer();//創建代理類的屬性
	
	public Object getProxy(Class clazz){
		//設置創建子類(需要產生代理)的類
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		
		return enhancer.create();//返回代理類的實例
	}
	
	/**
	 * 攔截所有目標類方法的調用
	 * obj  目標類的實例
	 * m   目標方法的反射對象
	 * args  方法的參數
	 * proxy代理類的實例
	 */
	@Override
	public Object intercept(Object obj, Method m, Object[] args,
			MethodProxy proxy) throws Throwable {
		System.out.println("日誌開始...");
		//代理類調用父類的方法(cglib採用繼承的方式,故代理類是目標類的子類)
		proxy.invokeSuper(obj, args);
		System.out.println("日誌結束...");
		return null;
	}

}

步驟4:獲取代理類,並調用代理類的方法

	public static void main(String[] args) {

		CglibProxy proxy = new CglibProxy();
		Train t = (Train)proxy.getProxy(Train.class);
		t.move();
	}

(2.2.3)JDK動態代理與cglib動態代理的區別
JDK動態代理:只能代理實現了接口的類,沒有實現接口的類不能實現JDK的動態代理
CGLIB動態代理:針對類來實現代理,對指定目標類產生一個子類,通過方法攔截技術攔截所有父類方法的調用。

(2.3)靜態代理與動態代理對比

1、靜態代理
優點:
業務類只需要關注業務邏輯本身,保證了業務類的重用性。這是代理的共有優點。
缺點:
(1)代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要爲每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。
(2)如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的複雜度。
(3)採用靜態代理模式,那麼真實角色(委託類)必須事先已經存在的,並將其作爲代理對象代理對象內部屬性。但是實際使用時,一個真實角色必須對應一個代理角色,如果大量使用會導致類的急劇膨脹。
2、動態代理
優點
1、動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。
2、動態代理類不僅簡化了編程工作,而且提高了軟件系統的可擴展性,因爲Java 反射機制可以生成任意類型的動態代理類。
缺點
JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因爲採用的是繼承,所以不能對final修飾的類進行代理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章