1. 程式人生 > >Java進階--08--註解、反射、位元組碼、類載入

Java進階--08--註解、反射、位元組碼、類載入

一、註解(Annotation)

1、內建註解

  • 註解與註釋:註解除了可以看,還能被其他的程式所讀取;
  • 常用註解:@override、@deprecated--不建議使用的、@suppressWarining("all")--警告抑制

2、自定義註解

(1)註解關鍵字:@interface;

(2)四個元註解

  • 元註解的作用:就是用來說明解釋其他註解的註解;
  • @Target:定義註解的作用目標;
@Target(ElementType.TYPE)   //介面、類、列舉、註解
@Target(ElementType.FIELD) //欄位、列舉的常量
@Target(ElementType.METHOD) //方法
  • @Retention: 定義註解的保留策略(生命週期);
@Retention(RetentionPolicy.SOURCE)   //註解僅存在於原始碼中,在class位元組碼檔案中不包含
@Retention(RetentionPolicy.CLASS)     // 預設的保留策略,註解會在class位元組碼檔案中存在,但執行時無法獲得,
@Retention(RetentionPolicy.RUNTIME)  // 註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
  • @Document:說明該註解將被包含在javadoc中;
  • @Inherited:說明子類可以繼承父類中的該註解;

(3)註解的引數

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
	int age() default 0;
	String[] strs();
}
  • 單個引數建議引數名寫為 value;
  • 引數沒有賦預設值,用註解時必須賦值不然會報錯; 

3、註解讀取

(1)應用示例:用註解完成 orm 對映

  • 定義註解、在類中使用註解、用反射讀取註解

 

二、反射(reflection)

1、概述

(1)概念:

  • JAVA反射機制是在執行狀態中,對於任意一個實體類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意方法和屬性;

(2)獲取class 檔案物件的方法

  • class.forName()
  • obj.getClass()
  • class.class

(3)說明:

  • 對於一個類來說,類載入到記憶體中後,class 檔案物件有且只有一個;

2、動態獲取資訊和動態操作

(1)動態獲取

   1、取屬性

  • 獲取公開屬性
Field[] fields = clazz.getFields();
Field age = clazz.getField("age");
  • 獲取所有屬性
Field[] dfs = clazz.getDeclaredFields();
Field dage = clazz.getDeclaredField("age");

   2、取方法

Method getage = clazz.getDeclaredMethod("setAge", int.class);
Method[] ms = clazz.getDeclaredMethods();
  • 獲取公開方法的方法參考屬性; 

   3、取構造器

  • 具體方法參考屬性的;

(2)動態操作

  1、api 動態呼叫構造器新建物件

  • 空參:clazz.newInstance():本質就呼叫空參構造器;
  • 有參:
Constructor<?> ins = clazz.getDeclaredConstructor(String.class,int.class);
Student obj = (Student) ins.newInstance("allen",12);

  2、動態執行方法

Method getage = clazz.getDeclaredMethod("getAge", null);
int age = (int) getage.invoke(obj, null);

  3、動態設定和獲取屬性(暴力反射)

Field dage = clazz.getDeclaredField("age");
dage.setAccessible(true);
int age = (int) dage.get(obj);

3、反射效能

(1)提高反射效能

  • 反射提高了程式的靈活性的同時降低了效能;
  • 通過關閉安全檢查可以提高反射的效能(setAccessible(true));

(2)反射操作泛型

  • type  是一個反射包下的介面;
  • Class
  • ParameterizedType
  • GenericArrayType
  • WildcardType
  • TypeVariable

(3)反射讀取註解

  • 讀取註解先要獲得相應的class、field 和 method 物件;
  • 然後就是呼叫方法讀取註解

4、動態編譯和指令碼引擎

(1)動態編譯

   1、動態編譯

  • 第一種是利用 Runtime 類用exec 呼叫 javac 命令進行動態編譯;
  • 第二種用 JavaCompiler 類的 run 方法編譯;

   2、動態執行

  • 第一種還是用 runtime 類來呼叫 java 命令;
  • 第二種是反射來呼叫,要用到類載入器;
/*Runtime rt = Runtime.getRuntime();
rt.exec("javac d:/Hello.java");
Process pro = rt.exec("java -cp d: Hello");
InputStream is = pro.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = "";
while((str = br.readLine()) != null) {
	System.out.println(str);
}*/

JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
jc.run(null, null, null, "d:/Hello.java");
URL[] urls = {new URL("file:/d:/")};
URLClassLoader loader = new URLClassLoader(urls);
Class<?> clazz = loader.loadClass("Hello");
Method m = clazz.getDeclaredMethod("main", String[].class);
Object ins = clazz.newInstance();
m.invoke(ins, (Object)new String[] {});

(2)指令碼引擎

  • 概念:就是一套讓java 程式能夠操作js等指令碼的API;
  • 作用:通過 java 程式碼執行 js 等指令碼;通過引擎的上下文,讓java 和指令碼之間進行資料交換;
  • 基本使用:
//get engine
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine jsEngine = sem.getEngineByName("js");
//write js
String str = "msg = 'haha';print(msg)";
//run js by java
jsEngine.eval(str);
Object msg = jsEngine.get("msg");
System.out.println(msg);
  • 還可以將 engine 強轉為 invocable 執行 js 函式 ;
String str = "function getData() {print('ahah')}";
jsEngine.eval(str);
Invocable invo = (Invocable)jsEngine;
invo.invokeFunction("getData", null);
  • 在js程式碼中匯入java包,使用包中的java類
String jscode = "var list=java.util.Arrays.asList(['allen','mike','ai']);";
engine.eval(jscode);
List<String> list2 = (List<String>) engine.get("list");
for (String t : list2) {
	System.out.println(t);
}
  • 執行單獨的js 檔案
URL url=Demo01.class.getClassLoader().getResource("a.js");
FileReader reader=new FileReader(url.getPath());
engine.eval(reader);
reader.close();

 

三、位元組碼操作

1、概述

(1)java 動態性的兩種實現方法:反射和位元組碼操作;

(2)作用:通過位元組碼操作可以動態新建和修改一個類;位元組碼操作的效能是高於反射的;

(3)常用的位元組碼操作類庫:CGLIB和 javassist;

2、javassist入門

(1)最外層 api 包含 CtClass、Ctfield 和 CtMethod 三個類,這三個類的作用和反射中的calss、field 和 method 相類似;

(2)基本使用

 

ClassPool pool = ClassPool.getDefault();
CtClass stu = pool.getCtClass("com.stan.test.Stu");
//do with field
CtField f = CtField.make("public int age = 10;", stu);
stu.addField(f);
//do with method
CtMethod m  = CtMethod.make("public int getOne(){return 2;}", stu);
stu.addMethod(m);
//do with constructor
CtConstructor cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, stu);
cons.setBody("this.name = name;");
stu.addConstructor(cons);

3、詳細api

(1)獲取類相關的基本信心,比如類名等;

(2)新增或者修改方法,兩種方案;

(3)新增欄位;

(4)構造器的獲取;

(5)註解的讀取;

 

四、JVM核心機制

1、類載入過程

(0)JVM 的記憶體結構

  • 棧(方法):方法壓棧執行,結束彈棧釋放;
  • 堆(物件):存物件的,class 物件也是存放在這個區;
  • 方法區(實質也是堆):包含常量池、類的靜態成員、類的程式碼;

(1)類載入:就是 class 檔案轉變為 JVM 可用的類的過程;

(2)過程概述:

   1、載入

  • class檔案位元組碼載入到記憶體,方法區生成靜態變數的資料結構,堆中生成一個 class 物件;

   2、連結:類二進位制程式碼合併到JVM 的執行狀態之中

  • 驗證:安全檢查;
  • 準備:為靜態變數分配記憶體並賦初始化值,此過程在方法區中;
  • 解析:常量池內的符號引用替換為直接引用的過程;

   3、初始化

  • 就是把靜態變數的賦值動作和靜態程式碼塊收到類構造器中,然後類構造器進行類的初始化操作;

(3)初始化時機

1、主動引用

  • 主動引用類的時候,比如 new物件的時候,類會進行初始化;
  • 類初始化的時候,如果父類沒有被初始化,會先初始化父類;

2、被動引用

  • 引用類中常量,不會初始化;
  • 子類引用父類的靜態域,子類不會初始化,父類會;
  • 定義陣列引用類不會初始化;

2、類載入器的層次結構

(1)引導類載入器

  • 作用是載入核心類庫;用C語言寫的;不是繼承lang 包裡的 classloader

(2)擴充套件類載入器

  • 載入jre中的擴充套件包(ext);繼承lang 包裡的 classloader;用java 寫的;

(3)應用類載入器

  • 我們在程式中自己寫的類就是他載入的;預設記載 classpath 路徑下的類;

(4)自定義類載入器

  • 可以自己繼承 classloader 實現自己的類載入器;

(5)代理模式--雙親委託機制

  • java 的類載入採用的是代理模式:就是讓別的類載入器來載入類;
  • 具體採用的是雙親委託機制:就是要載入類的時候把這個事一層一層往上拋,到最頂層的引導類載入器,然後往下走,那一層能處理就載入;
  • 作用:就是能保證 java 核心類庫的安全;

3、自定義類載入器

(1)說明:

  • 不一定非要用雙親委託;
  • 同一個類被不同的載入器載入,也被 jvm 認為是不同的類;

(2)系統檔案類載入器(示例)

(3)網路類載入器(示例)

(4)加密解密載入器(示例):取反

(5)執行緒上下文載入器(示例)

(6)tomcat 類載入和 OSGI(java 動態模組系統)