1. 程式人生 > >JAVA類載入器、註解和動態代理

JAVA類載入器、註解和動態代理

一.類載入器

1.什麼是類載入器,作用是什麼

類載入器就載入位元組碼檔案(.class)

這裡寫圖片描述

2.類載入器的種類

類載入器有三種,不同載入器載入不同

這裡寫圖片描述

  • BootStrap:引導類載入器:載入都是最基礎的檔案
  • ExtClassLoader:擴充套件類載入器:載入都是基礎的檔案
  • AppClassLoader:應用類載入器:三方jar包和自己編寫java檔案
  • 載入順序:BootStrap -> ExtClassLoader -> AppClassLoader

怎麼獲得類載入器(重點)

  • 獲得位元組碼物件的三種方式
    • Class.forName(“…”);
    • 物件.getClass()
    • 類名.class
  • 位元組碼物件.getClassLoad()獲得ClassLoader型別(即類載入器)的物件
  • 由於src下的檔案都會被編譯,故通過類載入器的物件可以獲得classes(即編譯後的src)下的任何資源[注意是由於在classes內,故都是對應java檔案的位元組碼檔案]
  • 例項程式碼:
package demo;
import java.net.URL;
public class Demo {
    public static void main(String[]args) {
        //獲得Demo位元組碼檔案的類載入器
        Class clazz = Demo.class;//獲得Demo的位元組碼物件
ClassLoader classLoader = clazz.getClassLoader();//獲得Demo的類載入器 //getResource的引數路徑相對classes(src) URL jdbc_properties_url = classLoader.getResource("demo/jdbc.properties");//獲得classes(src)下的任何資源地址 String path = jdbc_properties_url.getPath(); System.out.println(path); } } //列印結果/Users/wangzhe/Documents/workspace/WEB25/build/classes/demo/jdbc.properties

二.註解 @xxx

1.什麼是註解,註解的作用

  • 註解就是符合一定格式的語法 @xxxx

  • 註解作用:

    • 註釋:在閱讀程式時清楚(給程式設計師看的)
    • 註解:給jvm看的(給機器看的)
  • 註解在目前而言最主流的應用:代替配置檔案

    如在建立web專案時選項Dynamic web module version3.0以上沒有web.xml檔案,是因為被註解取代,如下:

    package demo;
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    @WebServlet("/demo")
    public class DemoServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }
    }

    此時@WebServlet就代替在web.xml中配置url-pattern等相關配置。

  • 關於配置檔案與註解開發的優缺點:

    • 註解優點:開發效率高,成本低
    • 註解缺點:耦合性大,並且不利於後期維護(如果是在web.xml中配置則直接修改配置資訊即可)
    • 故企業開發中,我們通常把不變的內容定義成註解,變化的內容在xml中配置

2.jdk5提供的註解

  • @Override:告知編譯器此方法是覆蓋父類的(可以在方法上用),幫助檢查覆蓋父類的方法是否正確

  • @Deprecated:標註過時

  • @SuppressWarnings:壓制警告(可以在方法上用,也可以在類上用,還可以在屬性上用)

  • 發現的問題:

    不同的註解只能在不同的位置使用(方法上、欄位上、類上)

  • 例項程式碼:

    package annotation;
    import java.util.ArrayList;
    import java.util.List;
    public class AnnoDemo {
    public static void main(String[] args) {
        @SuppressWarnings(value = { "rawtypes", "unused" })//rawtypes壓制泛型的警告、unused壓制未使用的警告,all壓制所有警告
        List list = new ArrayList();
        show();
    }
    
    @Deprecated
    public static void show() {}
    
    public static void show(String xx) {}
    
    @Override
    public String toString() {
        return "AnnoDemo []";
    }
    }

3.自定義註解(瞭解)

  • 思路

    • 怎樣去編寫一個自定義的註解
    • 怎樣去使用註解
    • 怎樣去解析註解—–使用反射知識
  • 編寫一個註解

    • 定義註解關鍵字:@interface

    • 註解的屬性

      • 語法:返回值 名稱();

      • 注意:如果屬性的名字是value,並且註解的屬性值有一個那麼在使用註解時可以省略value

      • 如定義名為value的字串型別屬性,使用時如下:

      //定義如下
      public @interface MyAnno {
      String value();
      }
      //使用如下
      public class MyAnnoTest {
      @MyAnno("xxx")
      public void show(){}
      }
      • 如定義名為value的字串陣列型別屬性,使用時如下:
      //定義如下
      public @interface MyAnno {
      String[] value();
      }
      //使用如下
      public class MyAnnoTest {
      @MyAnno({"xxx","bbb"})
      public void show(){}
      }
      • 如定義其他名稱的對應型別屬性,使用時如下:
      //定義如下
      public @interface MyAnno {
      //註解的屬性
      String name();
      int age() default 28;
      }
      //使用如下
      public class MyAnnoTest {
      @MyAnno(name="aaa",age=10)
      public void show(){}
      }
      • 註解屬性型別只是以下幾種

      • 基本型別

        • String
        • 列舉型別
        • 註解型別
        • Class型別
        • 以上型別的陣列型別
  • 使用註解

    在類、方法、欄位上面使用@XXX

  • 解析使用了註解的類

    • 介入一個概念:元註解:代表修飾註解的註解,作用:限制定義的註解的特性
    • @Target代表註解修飾的範圍:類上使用,方法上使用,欄位上使用
      • ElementType.FIELD:欄位上可用此註解
      • ElementType.METHOD:方法上可以用此註解
      • ElementType.TYPE:類/介面上可以使用此註解
    • @Retention
      • RetentionPolicy.SOURCE: 註解在原始碼級別可見(預設)
      • RetentionPolicy.CLASS:註解在位元組碼檔案級別可見
      • RetentionPolicy.RUNTIME:註解在整個執行階段都可見

這裡寫圖片描述

  • 注意:

    • 要想解析使用了註解的類,那麼該註解的Retention必須設定成Runtime
    • 關於註解解析的實質:從註解中解析出屬性值
    • 位元組碼物件存在於獲得註解相關的方法
      • isAnnotationParsent<Class <?extends Annotation> annotationClass>:判斷該位元組碼物件身上是否使用該註解了
      • getAnnotation(Class <A> annotationClass):獲得該位元組碼物件身上的註解物件
  • 例項程式碼:

    • 定義註解:可以修飾類和方法;註解在整個執行階段都可見(才能保證通過位元組碼物件可以取出對應註解);定義註解的屬性
    package annotation;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    @Target({ElementType.METHOD,ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnno {
        //註解的屬性
        String name();
        int age() default 28;
    }
    • 定義使用了註解的類
    package annotation;
    public class MyAnnoTest {
        @MyAnno(name="aaa",age=10)
        public void show(String str){
            System.out.println("show running......"+str);
        }
    }
    • 解析使用了註解的類並輸出註解的屬性內容
    package annotation;
    import java.lang.reflect.Method;
    public class MyAnnoParser {
        public static void main(String[] args) throws NoSuchMethodException, SecurityException {
            //解析show方法上面的@MyAnno
            //直接目的是:獲得show方法上MyAnno中的引數
            //獲得show方法的位元組碼物件
            Class clazz = MyAnnoTest.class;
            Method method = clazz.getMethod("show",String.class);
            //獲得show方法上的@MyAnno
            MyAnno annotation = method.getAnnotation(MyAnno.class);
            //獲得@MyAnno上的屬性值
            System.out.println(annotation.name());
            System.out.println(annotation.age());
        }
    }

4.註解案例:模擬單元測試

  • 編寫註解MyTest用於修飾方法,且註解在整個階段都可見。此註解的作用僅做修飾,用於被註解修飾的方法需要執行某些內容。

    package case1;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyTest {
    //不需要屬性
    }
  • 編寫使用該註解的類。

    package case1;
    import org.junit.Test;
    public class TestDemo {
    //程式設計師開發中測試用的程式碼
    @Test
    public void test1() {
        System.out.println("test1 running...");
    }
    @MyTest
    public void test2() {
        System.out.println("test2 running...");
    }
    @MyTest
    public void test3() {
        System.out.println("test3 running...");
    }
    }
  • 編寫執行對應解析內容的類,先通過位元組碼物件找到對應的類,獲取所有的方法,再通過方法篩選被@MyTest註解修飾的方法,最後執行對應內容(本demo執行的是對應方法體)

    package case1;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    public class MyTestParster {
    public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
        //獲得TestDemo
        Class clazz = TestDemo.class;
        //獲得所有的方法
        Method[] methods = clazz.getMethods();
        if(methods!=null) {
            //獲得註解使用了@MyTest的方法
            for(Method method : methods) {
                //判斷該方法是否使用了@MyTest註解
                boolean annotationPresent = method.isAnnotationPresent(MyTest.class);
                if(annotationPresent) {
                    //該方法使用MyTest註解
                    method.invoke(clazz.newInstance(), null);
                }
            }
        }   
    }
    }
  • 輸出結果

    test3 running...
    test2 running...

三.動態代理

1.什麼是代理(中介)

  • 目標物件/被代理物件(房主:真正的租房的方法)
  • 代理物件(黑中介:有租房子的方法(呼叫房主的租房的方法))
  • 執行代理物件方法的物件(租房的人 )
  • 流程:我們要租房——>中介(租房的方法)——>房主(租房的方法)
  • 抽象:呼叫物件——>代理物件——>目標物件

2.動態代理

動態代理:不用手動編寫一個代理物件,不需要一一編寫與目標物件相同的方法,這個過程,在執行時 的記憶體中動態生成代理物件。(位元組碼物件級別的代理物件 )

這裡寫圖片描述

  • 動態代理的API

    在jdk的API中存在一個Proxy中存在一個生成動態代理的的方法newProxyInstance

返回值型別 方法
static Object newProxyInstance(ClassLoader loader,Class[]interfaces,InvoationHandlerh)

* 返回值:Object就是代理物件
* 引數1:loader代表與目標物件相同的類載入器(目標物件.getClass().getClassLoader())
* 引數2:interfaces代表與目標物件實現的所有的介面位元組碼物件陣列
* 引數3:h具體的代理的操作,InvacationHandler介面

  • 注意:JDK的Proxy方式實現的動態代理,目標物件必須有介面,沒有介面不能實現jdk版動態代理

  • 代理物件與介面的關係

    這裡寫圖片描述

  • 例項程式碼(利用動態代理的內容為目標物件進行動態代理)

    • 編寫介面內容
    package proxy;
    public interface TargetInterface {
        public void method1();
        public String method2();
        public int method3(int x);
    }
    • 編寫目標實現類
    package proxy;
    public class Target implements TargetInterface{
        @Override
        public void method1() {
            System.out.println("method1 running...");
        }
        @Override
        public String method2() {
            System.out.println("method2 running...");
            return "method2";
        }
        @Override
        public int method3(int x) {
            return x;
        }
    }
    • 編寫第一版代理內容,建立代理物件,利用匿名內部類重寫invoke方法實現對目標物件的目標方法進行相關處理,並利用代理物件呼叫對應方法進行結果檢視。(請參見注釋
    package proxy;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import org.junit.Test;
    public class ProxyTest {
        @Test
        public void test1() {
            //獲得動態的代理物件(在執行時,內容中動態的為Target建立一個虛擬的代理物件)
            //objProxy是代理物件 根據引數確定到底是誰的代理物件
            /*
             * 引數一ClassLoader loader:與目標物件相同的類載入器
             * 引數二Class<?>[] interfaces:介面的位元組碼物件陣列
             * 引數三InvocationHandler h:具體的代理的操作,InvocationHandler介面
             */
            TargetInterface objProxy = (TargetInterface)Proxy.newProxyInstance(Target.class.getClassLoader(), new Class[]{TargetInterface.class}, new InvocationHandler() {
                //invoke()代表的是執行代理物件的方法
                @Override
                /*
                 * 引數二Method method:代表目標物件的方法位元組碼物件
                 * 引數三Object[] args:代表目標物件相應方法的引數
                 */
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("目標方法前的邏輯");
                    //執行目標物件的方法
                    Object invoke = method.invoke(new Target(), args);
                    System.out.println("目標方法後的邏輯");
                    return invoke;
                }
            });
            objProxy.method1();
            String method2 = objProxy.method2();
            System.out.println(method2);
        }
    }

    執行結果:

    目標方法前的邏輯
    method1 running...
    目標方法後的邏輯
    目標方法前的邏輯
    method2 running...
    目標方法後的邏輯
    method2
    • 編寫第二版內容,對相關方法內容進行詳細闡述。(請參見注釋
    package proxy;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    public class ProxyTest2 {
        public static void main(String[] args) {
            final Target target = new Target();
            //動態建立代理物件
            TargetInterface proxy = (TargetInterface)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , new InvocationHandler() {
                @Override
                //invoke方法被執行幾次?看代理物件呼叫方法幾次
                //代理物件呼叫介面相應的方法,都是呼叫invoke()
                /*
                 * 引數一Object proxy:指的就是代理物件
                 * 引數二Method method:指的是目標方法的位元組碼物件
                 * 引數三Object[] args:指的是呼叫目標方法時的引數
                 */
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //執行相關處理操作...
                    //利用反射的知識點
                    Object invoke = method.invoke(target, args);//目標物件的相應方法
                    //執行相關處理操作...
                    return invoke;//return返回的值給代理物件
                }
            });
            proxy.method1();//呼叫invoke,對應引數二method就是目標物件的method1,args是null,返回值是null
            String method2 = proxy.method2();//呼叫invoke,對應引數二method就是目標物件的method2,args是null,返回值是”method2“
            int method3 = proxy.method3(100);//呼叫invoke,對應引數二method就是目標物件的method3,args是Object[]{100},返回值是100
            System.out.println(method2);
            System.out.println(method3);
        }
    }

    執行結果:

    method1 running...
    method2 running...
    method2
    100
  • 案例DEMO:使用動態代理完成全域性編碼

    • 例項程式碼
    package filter;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    public class EncodingFilter2 implements Filter{
        @Override
        public void destroy() {}
        @Override
        public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
                throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest)arg0;
            //使用動態代理完成全域性編碼
            HttpServletRequest enhanceRequest = (HttpServletRequest)Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //對getParameter方法進行增強
                    String name = method.getName();//這是目標物件的方法名稱
                    if("getParameter".equals(name)) {
                        String invoke = (String)method.invoke(request,args);
                        //轉碼
                        invoke = new String(invoke.getBytes("ISO8859-1"),"UTF-8");
                        return invoke;
                    }
                    return method.invoke(request, args);
                }
            });
            arg2.doFilter(enhanceRequest, arg1);
        }
        @Override
        public void init(FilterConfig arg0) throws ServletException {}
    
    }
    • 總結

      • 具體應用時,裝飾者模式一般用於內容增強
      • 動態代理一般用於攔截

相關推薦

JAVA載入註解動態代理

一.類載入器 1.什麼是類載入器,作用是什麼 類載入器就載入位元組碼檔案(.class) 2.類載入器的種類 類載入器有三種,不同載入器載入不同 BootStrap:引導類載入器:載入都是最基礎的檔案 ExtClassLoader:擴充套

java基礎第十八篇之單元測試註解動態代理

1:單元測試 1)JUnit是一個Java語言的單元測試框架,這裡的單元指的就是方法 2)單元測試用來替換以前的main方法 1.1 Junit測試的步驟 1:在方法的上面加上 @Test 2:將junit庫新增到工程的構建路徑 3:選中方法--->右鍵--->JunitTest

反射註解動態代理

反射是指計算機程式在執行時訪問、檢測和修改它本身狀態或行為的一種能力,是一種超程式設計語言特性,有很多語言都提供了對反射機制的支援,它使程式能夠編寫程式。Java的反射機制使得Java能夠動態的獲取類的資訊和呼叫物件的方法。 一、Java反射機制及基本用法 在Java中,Class(類型別)是反射程式設計的起

java載入動態代理

import java.lang.reflect.*; import java.util.ArrayList; import java.util.Collection; public class ProxyTest { public static void main(String[] args) throw

Java載入( CLassLoader ) 死磕9: 上下文載入原理案例

【正文】Java類載入器(  CLassLoader ) 死磕9:  上下文載入器原理和案例 本小節目錄 9.1. 父載入器不能訪問子載入器的類 9.2. 一個寵物工廠介面 9.3. 一個寵物工廠管理類 9.4 APPClassLoader不能訪問子載入器中的類 9.5. 執行緒上下文

Java載入( CLassLoader ) 死磕8: 使用ASM,載入實現AOP

【正文】Java類載入器(  CLassLoader ) 死磕8:  使用ASM,和類載入器實現AOP 本小節目錄 8.1. ASM位元組碼操作框架簡介 8.2. ASM和訪問者模式 8.3. 用於增強位元組碼的事務類 8.4 通過ASM訪問註解 8.5. 通過ASM注入AOP事務程式

Java載入( CLassLoader ) 死磕 12: 匯入 & 分類

JAVA類載入器 死磕系列 目錄 by   瘋狂創客圈 1.匯入1.1. 從class檔案的載入開始1.2. 什麼是類載入器2. JAVA類載入器分類2.1. 作業系統的環境變數2.2. Bootstrap ClassLoader(啟動類載入器)2.3. Extention ClassL

Java原始碼分析——ClassClassLoader解析(三) 載入實現自定義載入

    在這個系列的第一篇章就講解了Class類的獲取以及載入過程,但是並沒有提及具體的載入過程,在java中,載入一個類是通過ClassLoader類來執行的,也就是類載入器完成。java中所有的類,都必須載入進jvm中才能執行,這個載入的意思是

Java載入雙親委派模型.md

0.類載入過程 一般來說,類載入分為3個過程,載入,連結和初始化。 1.載入階段,是Java將位元組碼資料從不同資料來源讀取到JVM中,並對映為JVM認可的Class物件,這裡的資料來源可能有Jar包,class檔案,甚至網路資料來源等。如果輸入資料不是ClassFile結構,則會丟

載入反射,反射的應用例項(泛型擦除配置檔案)

類載入器 1.1類的載入 當程式要使用某個類時,如果該類還未被載入到記憶體中,則系統會通過載入,連線,初始化三步來實現對這個類進行初始化。   1.1.1載入 指將class檔案讀入記憶體,併為之建立一個Class物件。 任何類被使用時系統都會建立一個Class物件(位元組碼檔案物件,建

JAVA多執行緒:JVM載入(自動載入雙親委託機制載入名稱空間執行時包的解除安裝等)

  Jvm提供了三大內建的類載入器,不同的類載入器負責將不同的類載入到記憶體之中 根載入器(Bootstrap ClassLoader) 是最頂層的載入器,是由C++編寫的,主要負責虛擬機器核心類庫的載入,如整個java.lang包,根載入器是獲取不到引用的,因此

java載入雙親委派載入機制

java類載入器分類詳解  1、Bootstrap ClassLoader:啟動類載入器,也叫根類載入器,負責載入java的核心類庫,例如(%JAVA_HOME%/lib)目錄下的rt.jar(包含Sy

Java載入雙親委派機制

前言 之前詳細介紹了Java類的整個載入過程(類載入機制詳解)。雖然,篇幅較長,但是也不要被內容嚇到了,其實每個階段都可以用一句話來概括。 1)載入:查詢並載入類的二進位制位元組流資料。 2)驗證:保證被載入的類的正確性。 3)準備:為類的靜態變數分配記憶體,並設定預設初始值。 4)解析:把類中的符號引用轉換

java載入——ClassLoader

web rac rgb 好的 全盤負責機制 安全 trac 字節 如何 Java的設計初衷是主要面向嵌入式領域,對於自己定義的一些類,考慮使用依需求載入原則。即在程序使用到時才載入類,節省內存消耗,這時就可以通過類載入器來動態載入。 假設你平時僅僅是做web開發,那應該

Java載入 ClassLoader的解析

index html dir obj ble body 6.4 odin 普通 //參考 : http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 類載入器基本概念 類載

JAVA載入詳解

Java類載入器的作用就是在執行時載入類。Java類載入器基於三個機制:委託、可見性和單一性。委託機制是指將載入一個類的請求交給父類載入器,如果這個父類載入器不能夠找到或者載入這個類,那麼再載入它。可見性的原理是子類的載入器可以看見所有的父類載入器載入的類,而父類載入器看不到子類載入器載入的

1.java載入

Java類載入器ClassLoader總結 JAVA類裝載方式,有兩種: 1.隱式裝載, 程式在執行過程中當碰到通過new 等方式生成物件時,隱式呼叫類裝載器載入對應的類到jvm中。 2.顯式裝載, 通過class.forname()等方法,顯式載入需要的類 類載

Java載入(死磕5)

Java類載入器(  CLassLoader )  死磕5:  自定義一個檔案系統classLoader 本小節目錄 5.1. 自定義類載入器的基本流程 5.2. 入門案例:自定義檔案系統類載入器 5.3. 案例的環境配置 5.4 FileClassLoader

Java載入( 死磕9)

【正文】Java類載入器(  CLassLoader ) 死磕9:  上下文載入器原理和案例 本小節目錄 9.1. 父載入器不能訪問子載入器的類 9.2. 一個寵物工廠介面 9.3. 一個寵物工廠管理類 9.4 APPClassLoader不能訪問子載入器中的類 9.5. 執行緒上下文

Java載入( 死磕7)

【正文】Java類載入器(  CLassLoader )死磕7:  基於加密的自定義網路載入器 本小節目錄 7.1. 加密傳輸Server端的原始碼 7.2. 加密傳輸Client端的原始碼 7.3. 使用亦或實現簡單加密和解密演算法 7. 網路加密SafeClassLoader的原始