面試官問我:什麼是靜態代理?什麼是動態代理?註解、反射你會嗎?

據說微信搜索《Java魚仔》會變動強哦!  java

本文收錄於JavaStarter,裏面有我完整的Java系列文章,學習或面試均可以看看哦git

開場github

一位穿着藍色襯衫,牛仔褲,拿着一個白色保溫杯的中年男子急匆匆地坐在你對面,看樣子是項目上的東西很急,估摸面試時間不會太長,這樣一想心情放鬆了許多......(後來我就被打臉了)面試

 

面試開始微信

面試官:小夥子,我看你的簡歷上說精通java基礎對吧,那我先簡單來問幾個java基礎。框架

好的好的,面試官你問。(一聽到簡單兩個字就心裏竊喜......)函數

面試官:你知道Java中有個東西叫代理嗎?工具

知道知道,代理就是經過代理對象去訪問實際的目標對象,好比咱們在生活中租房,能夠直接找房東,也能夠經過某些租房平臺去租房,經過租房平臺的這種方式就是代理。在java中這種租房平臺就被叫作代理類,代理類不只能實現目標對象,還能增長一些額外的功能。據我所知java中的代理方式有靜態代理和動態代理。(這個時候面試官很大機率會問你這兩種代理模式)。學習

面試官:沒想到你還能經過生活中的現象去理解代碼,不錯不錯,我看你提到了靜態代理和動態代理,那你給我說說什麼是靜態代理吧測試

(果真問了,還好我作了準備)靜態代理就是在代碼運行以前,這個代理類就已經存在了。仍是以上面的租房爲例,在代碼中會首先建立一個通用的租房接口:

public interface Room {
    void rent();
}

而後須要有一個被代理的類(或者稱爲真實的類)和一個代理類:

public class RealRoom implements Room {
    private String roomname;
    public RealRoom(String roomname) {
        this.roomname = roomname;
    }
    public void rent() {
        System.out.println("租了"+roomname);
    }
}

代理類以下:

public class ProxyClass implements Room {
    RealRoom realRoom;
    public ProxyClass(RealRoom realRoom) {
        this.realRoom = realRoom;
    }
    public void rent() {
        System.out.println("租房前收取中介費");
        realRoom.rent();
        System.out.println("租房後收取服務費");
    }
}

代理類能夠在不改變被代理對象的狀況下增長功能,最後咱們測試一下這個靜態代理:

public class Main {
    public static void main(String[] args) {
        RealRoom realRoom =new RealRoom("碧桂園");
        ProxyClass proxyClass=new ProxyClass(realRoom);
        proxyClass.rent();
    }
}

而後觀察結果:

租房前收取中介費
租了碧桂園
租房後收取服務費

 

面試官:既然靜態代理那麼強大,那他有什麼缺點嗎?

因爲靜態代理在代碼運行以前就已經存在代理類,所以對於每個代理對象都須要建一個代理類去代理,當須要代理的對象不少時就須要建立不少的代理類,嚴重下降程序的可維護性。用動態代理就能夠解決這個問題。

 

面試官:那你給我講一講動態代理吧

動態代理是指代理類不是寫在代碼中,而是在運行過程當中產生的,java提供了兩種實現動態代理的方式,分別是基於Jdk的動態代理基於Cglib的動態代理

 

面試官:基於JDK的動態代理我忘了,你給我複習複習。

(我???算了算了) 實現Jdk的動態代理須要實現InvocationHandler接口,而後實現其中的invoke方法。若是代理的方法被調用,那麼代理便會通知和轉發給內部的 InvocationHandler 實現類invoke,由它實現處理內容。

public class ProxyHandler implements InvocationHandler {
    Object object;
    public ProxyHandler(Object object) {
        this.object = object;
    }
    //proxy 代理對象
    //method 要實現的方法
    //args 方法的參數    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執行以前:"+method.getName());
        Object invoke = method.invoke(object, args);
        System.out.println("代理執行以後:"+method.getName());
        return invoke;
    }
}

接下來在main方法中執行動態代理

public static void main(String[] args) {
    Room room=new RealRoom("碧桂園");
    //obj.getClass().getClassLoader()類加載器
    //obj.getClass().getInterfaces() 目標類實現的接口
    //InvocationHandler對象
    InvocationHandler invocationHandler=new ProxyHandler(room);
    Room proxyRoom = (Room) Proxy.newProxyInstance(room.getClass().getClassLoader(), room.getClass().getInterfaces(), invocationHandler);
    proxyRoom.rent();
}

這段代碼的核心是Proxy.newProxyInstance,目的是運行期間生成代理類,最後經過代理類執行被代理的方法。最後結果以下:

代理執行以前:rent
租了碧桂園
代理執行以後:rent

 

面試官:被你這麼一說我想起來動態代理了,那他的優點呢?

以前我講靜態代理的時候說靜態代理的缺點在於對於每個被代理的對象,都須要建一個代理類。由於靜態代理是在項目運行前就寫好的。可是動態代理就不是這樣,因爲動態代理在運行時才建立代理類,所以只須要寫一個動態代理類就好。好比我再建立一個被代理的對象賣房:

寫一個通用接口Sell

public interface Sell {
    void sellRoom();
}

接着仍是寫一個被代理對象的類:

public class RealSell implements Sell {
    public void sellRoom() {
        System.out.println("賣房了");
    }
}

接下來在main方法中執行動態代理

public static void main(String[] args) {
        Sell sell=new RealSell();
        InvocationHandler invocationHandler=new ProxyHandler(sell);
        Sell proxysell= (Sell) Proxy.newProxyInstance(sell.getClass().getClassLoader(),sell.getClass().getInterfaces(),invocationHandler);
        proxysell.sellRoom();
    }

最終實現結果以下:

代理執行以前:sellRoom
賣房了
代理執行以後:sellRoom

經過動態代理,我能夠經過一個動態代理類,去代理多個對象。

 

面試官:若是我記的沒錯,經過這種方式只能代理接口吧,我看你上面的例子也都是代理接口,那我若是想代理類該怎麼辦呢?

jdk動態代理確實只能代理接口,JDK動態代理是基於接口的方式,換句話來講就是代理類和目標類都實現同一個接口。若是想要代理類的話可使用CGLib,CGLib動態代理是代理類去繼承目標類,而後實現目標類的方法。

建立一個目標類CGRoom

public class CGRoom {
    public void rent(String roomName){
        System.out.println("租了"+roomName);
    }
}

建立cglib的動態代理類,繼承MethodInterceptor ,實現其中的intercept方法

public class MyMethodInterceptor implements MethodInterceptor {

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理執行以前:"+method.getName());
        Object object=methodProxy.invokeSuper(o,objects);
        System.out.println("代理執行以後:"+method.getName());
        return object;
    }
}

最後經過enhance對象來建立代理類

public static void main(String[] args) {
    //建立Enhancer對象,相似於JDK動態代理的Proxy類,下一步就是設置幾個參數
    Enhancer enhancer=new Enhancer();
    //設置目標類的字節碼文件
    enhancer.setSuperclass(CGRoom.class);
    //設置回調函數
    enhancer.setCallback(new MyMethodInterceptor());
    //建立代理對象
    CGRoom proxy= (CGRoom) enhancer.create();
    proxy.rent("碧桂園");
}

最終實現如下結果:

代理執行以前:rent
租了碧桂園
代理執行以後:rent

 

面試官:既然動態代理被你說的這麼牛,那你日常工做中有使用到嗎?

日常個人業務代碼中雖然幾乎沒有使用過動態代理,可是我工做中使用的Spring系列框架中的AOP,以及RPC框架中都用到了動態代理,以AOP爲例,AOP經過動態代理對目標對象進行了加強,好比咱們最經常使用的前置通知、後置通知等。

 

面試官:不錯!下面再考你幾個基礎,說說你對註解的理解,註解又解決了哪些問題?

Java語言中的類、方法、變量、參數和包均可以用註解標記,程序運行過程當中咱們能夠獲取到相應的註解以及註解中定義的內容,好比說 Spring 中若是檢測到說你的類被 @Component註解標記的話,Spring 容器在啓動的時候就會把這個類歸爲本身管理,這樣你就能夠經過 @Autowired註解注入這個對象了。

 

面試官:那你知道如何本身去定義註解嗎?

知道知道,自定義註解主要有如下四步:

第一步經過@interface聲明註解:

public @interface Myannotation {
    String key() default "";
}

第二步經過四種元註解修飾註解:(面試的時候說出這四種註解就能夠了)

元註解的做用就是負責其餘註解,java中一共有四個元註解,分別是@Target,@Retention,@Documented,@Inherited,下面先介紹如下四種註解的做用:

@Target:Target說明了註解所修飾的對象範圍,取值(ElementType)有:

  1. 用於描述構造器
  1. 用於描述屬性
  2. 用於描述局部變量
  1. 用於描述方法
  2. 用於描述包
  3.  用於描述參數
  4.  用於描述類、接口(包括註解類型)或者enum聲明

@Retention:Retention定義了註解的保留範圍,取值(RetentionPoicy)有:

  1. 在源文件中有效(即源文件保留)
  1. 在class文件中有效(即class保留)
  2. 在運行時有效(即運行時保留)

@Documented:Documented用於描述其它類型的annotation應該被做爲被標註的程序成員的公共API,所以能夠被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。

@Inherited:Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的。若是一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。 

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Myannotation {
    String key() default "";
}

第三步使用註解,由於定義Target時定義了MEHTOD和FIELD,所以能夠在屬性和方法中使用這個註解:

public class MyannotationTest {
    @Myannotation(key = "javayz")
    private String username;
}

第四步利用反射解析註解

public static void main(String[] args) {
    Class myclass=MyannotationTest.class;
    Field[] fields = myclass.getDeclaredFields();
    for (Field field :fields){
        if (field.isAnnotationPresent(Myannotation.class)){
            System.out.println("配置了自定義註解");
            Myannotation annotation = field.getAnnotation(Myannotation.class);
            System.out.println("屬性:"+field.getName()+"上的註解key爲"+annotation.key());
        }
    }
}

輸出結果:

配置了自定義註解
屬性:username上的註解key爲javayz

 

面試官:我看你上面第四步提到了反射是吧?那你給我講講什麼是反射,它有啥特色:

(我暈,我就說了反射兩個字啊,還好有準備)JAVA 反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲 java 語言的反射機制。

在上面第四步利用反射解析註解中,我經過MyannotationTest.class獲取到了MyannotationTest的類對象,又用myclass.getDeclaredFields();獲取到了全部的屬性。這就是反射。

結束

面試官:不錯,這幾塊的基礎算你過關了,下面我要開始真正的技術面試了!

天吶!居然這纔算開始,還好我關注了公衆號《Java魚仔》,天天在地鐵公交上都能學到知識點!

相關文章
相關標籤/搜索