5年經驗的Java工程師面試答不出反射和動態代理!怕是隻會CRUD哦

 

分享阿里 P8 高級架構師吐血總結的 《BATJ大廠高級Java必問面試學習視頻》,附送 100G 面試學習視頻文檔java


阿里 P8 級高級架構師吐血總結的面試學習視頻, 內容覆蓋很廣,分佈式緩存、RPC 調用、Zookeeper、消息隊列、分佈式搜索引擎、分佈式 session、分庫分表等。面試


另外,附送 100G 學習、面試視頻文檔喲~編程


獲取方式:【關注 + 轉發】後,私信我,回覆關鍵字【面試】,便可免費無套路獲取哦~
如下是資源的部分目錄以及內容截圖:數組

 

 


重要的事再說一遍,獲取方式:【關注 + 轉發】後,私信我,回覆關鍵字【面試】,便可免費無套路獲取哦~緩存


正文開始session


1、反射概述


反射機制指的是Java在運行時候有一種自觀的能力,可以瞭解自身的狀況爲下一步作準備,其想表達的意思就是:在運行狀態中,對於任意一個類,都可以獲取到這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性(包括私有的方法和屬性),這種動態獲取的信息以及動態調用對象的方法的功能就稱爲java語言的反射機制。通俗點講,經過反射,該類對咱們來講是徹底透明的,想要獲取任何東西均可以,這是一種動態獲取類的信息以及動態調用對象方法的能力。架構


想要使用反射機制,就必需要先獲取到該類的字節碼文件對象(.class),經過該類的字節碼對象,就可以經過該類中的方法獲取到咱們想要的全部信息(方法,屬性,類名,父類名,實現的全部接口等等),每個類對應着一個字節碼文件也就對應着一個Class類型的對象,也就是字節碼文件對象分佈式


Java提供的反射機制,依賴於咱們下面要講到的Class類和java.lang.reflect類庫。咱們下面要學習使用的主要類有:①Class表示類或者接口;②java.lang.reflect.Field表示類中的成員變量;③java.lang.reflect.Method表示類中的方法;④java.lang.reflect.Constructor表示類的構造方法;⑤Array提供動態數組的建立和訪問數組的靜態方法。ide


回到頂部函數


2、反射之Class類


(1)初識Class類
在類Object下面提供了一個方法:


,此方法將會被全部的子類繼承,該方法的返回值爲一個Class,這個Class類就是反射的源頭。那麼Class類是什麼呢?Class類是Java中用來表達運行時類型信息的對應類,咱們剛剛也說過全部類都會繼承Object類的getClass()方法,那麼也體現了着Java中的每一個類都有一個Class對象,當咱們編寫並編譯一個建立的類就會產生對應的class文件並將類的信息寫到該class文件中,當咱們使用正常方式new一個對象或者使用類的靜態字段時候,JVM的累加器子系統就會將對應的Class對象加載到JVM中,而後JVM根據這個類型信息相關的Class對象建立咱們須要的實例對象或者根據提供靜態變量的引用值。將Class類稱爲類的類型,一個Class對象稱爲類的類型對象。

 


(2)Class有下面的幾個特色


①Class也是類的一種(不一樣於class,class是一個關鍵字);
②Class類只有一個私有的構造函數


,只有JVM可以建立Class類的實例;
③對於同一類的對象,在JVM中只存在惟一一個對應的Class類實例來描述其信息;
④每一個類的實例都會記得本身是由哪一個Class實例所生成;
⑤經過Class能夠完整的獲得一個類中的完整結構;


(3)獲取Class類實例


剛剛說到過Class只有一個私有的構造函數,因此咱們不能經過new建立Class實例 ,有下面這幾種獲取Class實例的方法:


①Class.forName("類的全限定名"),該方法只能獲取引用類型的類類型對象。該方法會拋出異常(a.l類加載器在類路徑中沒有找到該類 b.該類被某個類加載器加載到JVM內存中,另一個類加載器有嘗試從同一個包中加載)

//Class<T> clazz = Class.forName("類的全限定名");這是經過Class類中的靜態方法forName直接獲取一個Class的對象
Class<?> clazz1 = null;
try {
clazz1 = Class.forName("reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(clazz1); //class reflect.Person



②若是咱們有一個類的對象實例,那麼經過這個對象的getClass()方法能夠得到他的Class對象,以下所示

//Class<T> clazz = xxx.getClass(); //經過類的實例獲取類的Class對象
Class<?> clazz3 = new Person().getClass();
System.out.println(clazz3); //class reflect.Person
 
Class<?> stringClass = "string".getClass();
System.out.println(stringClass); //class java.lang.String
 
/**
* [表明數組,
* B表明byte;
* I表明int;
* Z表明boolean;
* L表明引用類型
* 組合起來就是指定類型的一維數組,若是是[[就是二維數組
*/
Class<?> arrClass = new byte[20].getClass();
System.out.println(arrClass); //class [B
 
Class<?> arrClass1 = new int[20].getClass();
System.out.println(arrClass1); //class [I
 
Class<?> arrClass2 = new boolean[20].getClass();
System.out.println(arrClass2); //class [Z

Class<?> arrClass3 = new Person[20].getClass();
System.out.println(arrClass3); //class [Lreflect.Person;

Class<?> arrClass4 = new String[20].getClass();
System.out.println(arrClass4); //class [Ljava.lang.String;

③經過類的class字節碼文件獲取,經過類名.class獲取該類的Class對象

//Class<T> clazz = XXXClass.class; 當類已經被加載爲.class文件時候,
Class<Person> clazz2 = Person.class;
System.out.println(clazz2);
System.out.println(int [][].class);//class [[I
System.out.println(Integer.class);//class java.lang.Integer

(4)關於包裝類的靜態屬性


咱們知道,在Java中對於基本類型和void都有對應的包裝類。在包裝類中有一個靜態屬性TYPE保存了該類的類類型。以下所示

/**
* The {@code Class} instance representing the primitive type
* {@code int}.
*
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");



咱們使用這個靜態屬性來得到Class實例,以下所示

Class c0 = Byte.TYPE; //byte
Class c1 = Integer.TYPE; //int
Class c2 = Character.TYPE; //char
Class c3 = Boolean.TYPE; //boolean
Class c4 = Short.TYPE; //short
Class c5 = Long.TYPE; //long
Class c6 = Float.TYPE; //float
Class c7 = Double.TYPE; //double
Class c8 = Void.TYPE; //void



(5)經過Class類的其餘方法獲取


①public native Class<? super T> getSuperclass():獲取該類的父類

Class c1 = Integer.class;
Class par = c1.getSuperclass();
System.out.println(par); //class java.lang.Number


②public Class<?>[] getClasses():獲取該類的全部公共類、接口、枚舉組成的Class數組,包括繼承的;


③public Class<?>[] getDeclaredClasses():獲取該類的顯式聲明的全部類、接口、枚舉組成的Class數組;


④(Class/Field/Method/Constructor).getDeclaringClass():獲取該類/屬性/方法/構造器所在的類
回到頂部


3、Class類的API


這是下面測試用例中使用的Person類和實現的接口
Person


一、建立實例對象

public void test4() throws Exception{
Class clazz =Class.forName("reflect.Person");
Person person = (Person)clazz.newInstance();
System.out.println(person);
}



建立運行時類的對象,使用newInstance(),實際上就是調用運行時指定類的無參構造方法。這裏也說明要想建立成功,須要對應的類有無參構造器,而且構造器的權限要足夠,不然會拋出下面的異常。


①咱們顯示聲明Person類一個帶參構造,並無無參構造,這種狀況會拋出InstantiationException


②更改無參構造器訪問權限爲private

 

二、獲取構造器


(1)獲取指定可訪問的構造器建立對象實例


上面咱們所說的使用newInstance方法建立對象,若是不指定任何參數的話默認是調用指定類的無參構造器的。那麼若是沒有無參構造器,又想建立對象實例怎麼辦呢,就使用 Class類提供的獲取構造器的方法,顯示指定咱們須要調用哪個無參構造器。

@Test
public void test5() throws Exception {
Class clazz = Class.forName("reflect.Person");
//獲取帶參構造器
Constructor constructor = clazz.getConstructor(String.class, String .class);
//經過構造器來實例化對象
Person person = (Person) constructor.newInstance("p1", "person");
System.out.println(person);
}



當咱們指定的構造器所有不夠(好比設置爲private),咱們在調用的時候就會拋出下面的異常


(2)得到所有構造器

@Test
public void test6() throws Exception {
Class clazz1 = Class.forName("reflect.Person");
Constructor[] constructors = clazz1.getConstructors();
for (Constructor constructor : constructors) {
Class[] parameters = constructor.getParameterTypes();
System.out.println("構造函數名:" + constructor + " " + "參數");
for (Class c: parameters) {
System.out.print(c.getName() + " ");
}
System.out.println();
}
}



運行結果以下


三、獲取成員變量並使用Field對象的方法


(1)Class.getField(String)方法能夠獲取類中的指定字段(可見的), 若是是私有的能夠用getDeclaedField("name")方法獲取,經過set(對象引用,屬性值)方法能夠設置指定對象上該字段的值, 若是是私有的須要先調用setAccessible(true)設置訪問權限,用獲取的指定的字段調用get(對象引用)能夠獲取指定對象中該字段的值。

@Test
public void test7() throws Exception {
Class clazz1 = Class.forName("reflect.Person");
//得到實例對象
Person person = (Person) clazz1.newInstance();
/**
* 得到類的屬性信息
* 使用getField(name),經過指定的屬性name得到
* 若是屬性不是public的,使用getDeclaredField(name)得到
*/
Field field = clazz1.getDeclaredField("id");
//若是是private的,須要設置權限爲可訪問
field.setAccessible(true);
//設置成員變量的屬性值
field.set(person, "person1");
//獲取成員變量的屬性值,使用get方法,其中第一個參數表示得到字段的所屬對象,第二個參數表示設置的值
System.out.println(field.get(person)); //這裏的field就是id屬性,打印person對象的id屬性的值
}



(2)得到所有成員變量

@Test
public void test8() throws Exception{
Class clazz1 = Class.forName("reflect.Person");
//得到實例對象
Person person = (Person) clazz1.newInstance();
person.setId("person1");
person.setName("person1_name");
Field[] fields = clazz1.getDeclaredFields();
for (Field f : fields) {
//打開private成員變量的可訪問權限
f.setAccessible(true);
System.out.println(f+ ":" + f.get(person));
}
}


 


四、獲取方法並使用method


(1)使用Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String, Class...)方法能夠獲取類中的指定方法,若是爲私有方法,則須要打開一個權限。setAccessible(true);用invoke(Object, Object...)能夠調用該方法。若是是私有方法而使用的是getMethod方法來得到會拋出NoSuchMethodException

@Test
public void test9() throws Exception{
Class clazz1 = Class.forName("reflect.Person");
//得到實例對象
Person person = (Person) clazz1.newInstance();
person.setName("Person");
//①不帶參數的public方法
Method playBalls = clazz1.getMethod("playBalls");
//調用得到的方法,須要指定是哪個對象的
playBalls.invoke(person);

//②帶參的public方法:第一個參數是方法名,後面的可變參數列表是參數類型的Class類型
Method sing = clazz1.getMethod("sing",String.class);
//調用得到的方法,調用時候傳遞參數
sing.invoke(person,"HaHaHa...");

//③帶參的private方法:使用getDeclaredMethod方法
Method dance = clazz1.getDeclaredMethod("dance", String.class);
//調用得到的方法,須要先設置權限爲可訪問
dance.setAccessible(true);
dance.invoke(person,"HaHaHa...");
}



(2)得到全部方法(不包括構造方法)

@Test
public void test10() throws Exception{
Class clazz1 = Class.forName("reflect.Person");
//得到實例對象
Person person = (Person) clazz1.newInstance();
person.setName("Person");
Method[] methods = clazz1.getDeclaredMethods();
for (Method method: methods) {
System.out.print("方法名" + method.getName() + "的參數是:");
//得到方法參數
Class[] params = method.getParameterTypes();
for (Class c : params) {
System.out.print(c.getName() + " ");
}
System.out.println();
}
}



五、得到該類的全部接口


Class[] getInterfaces():肯定此對象所表示的類或接口實現的接口,返回值:接口的字節碼文件對象的數組

@Test
public void test11() throws Exception{
Class clazz1 = Class.forName("reflect.Person");
Class[] interfaces = clazz1.getInterfaces();
for (Class inter : interfaces) {
System.out.println(inter);
}
}



六、獲取指定資源的輸入流


InputStream getResourceAsStream(String name),返回值:一個 InputStream 對象;若是找不到帶有該名稱的資源,則返回 null;參數:所需資源的名稱,若是以"/"開始,則絕對資源名爲"/"後面的一部分。

@Test
public void test12() throws Exception {
ClassLoader loader = this.getClass().getClassLoader();
System.out.println(loader);//sun.misc.Launcher$AppClassLoader@18b4aac2 ,應用程序類加載器
System.out.println(loader.getParent());//sun.misc.Launcher$ExtClassLoader@31befd9f ,擴展類加載器
System.out.println(loader.getParent().getParent());//null ,不能得到啓動類加載器

Class clazz = Person.class;//自定義的類
ClassLoader loader2 = clazz.getClassLoader();
System.out.println(loader2);//sun.misc.Launcher$AppClassLoader@18b4aac2

//下面是得到InputStream的例子
ClassLoader inputStreamLoader = this.getClass().getClassLoader();
 InputStream inputStream = inputStreamLoader.getResourceAsStream("person.properties");
Properties properties = new Properties();
properties.load(inputStream);
System.out.println("id:" + properties.get("id"));
System.out.println("name:" + properties.get("name"));
}



其中properties文件內容


5、反射的應用之動態代理


代理模式在Java中應用十分普遍,它說的是使用一個代理將對象包裝起來而後用該代理對象取代原始對象,任何原始對象的調用都須要經過代理對象,代理對象決定是否以及什麼時候將方法調用轉到原始對象上。這種模式能夠這樣簡單理解:你本身想要作某件事情(被代理類),可是以爲本身作很是麻煩或者不方便,因此就叫一個另外一我的(代理類)來幫你作這個事情,而你本身只須要告訴要作啥事就行了。上面咱們講到了反射,在下面咱們會說一說java中的代理


一、靜態代理


靜態代理其實就是程序運行以前,提早寫好被代理類的代理類,編譯以後直接運行便可起到代理的效果,下面會用簡單的例子來講明。在例子中,首先咱們有一個頂級接口(ProductFactory),這個接口須要代理類(ProxyTeaProduct)和被代理類(TeaProduct)都去實現它,在被代理類中咱們重寫須要實現的方法(action),該方法會交由代理類去選擇是否執行和在何處執行;被代理類中主要是提供頂級接口的的一個引用可是引用實際指向的對象則是實現了該接口的代理類(使用多態的特色,在代理類中提供構造器傳遞實際的對象引用)。分析以後,咱們經過下面這個圖理解一下這個過程。

package proxy;

/**
* 靜態代理
*/
//產品接口
interface ProductFactory {
void action();
}
 
//一個具體產品的實現類,做爲一個被代理類
class TeaProduct implements ProductFactory{
@Override
public void action() {
System.out.println("我是生產茶葉的......");
}
}

//TeaProduct的代理類
class ProxyTeaProduct implements ProductFactory {
//咱們須要ProductFactory的一個實現類,去代理這個實現類中的方法(多態)
ProductFactory productFactory;
 
//經過構造器傳入實際被代理類的對象,這時候代理類調用action的時候就能夠在其中執行被代理代理類的方法了
public ProxyTeaProduct(ProductFactory productFactory) {
this.productFactory = productFactory;
}
 
@Override
public void action() {
System.out.println("我是代理類,我開始代理執行方法了......");
productFactory.action();
}
}
public class TestProduct {
 
public static void main(String[] args) {
//建立代理類的對象
ProxyTeaProduct proxyTeaProduct = new ProxyTeaProduct(new TeaProduct());
//執行代理類代理的方法
proxyTeaProduct.action();
}
}



那麼程序測試的輸出結果也很顯然了,代理類執行本身實現的方法,而在其中有調用了被代理類的方法


那麼咱們想一下,上面這種稱爲靜態代理的方式有什麼缺點呢?由於每個代理類只能爲一個藉口服務(由於這個代理類須要實現這個接口,而後去代理接口實現類的方法),這樣一來程序中就會產生過多的代理類。好比說咱們如今又來一個接口,那麼是否是也須要提供去被代理類去實現它而後交給代理類去代理執行呢,那這樣程序就不靈活了。那麼若是有一種方式,就能夠處理新添加接口的以及實現那不就更加靈活了嗎,在java中反射機制的存在爲動態代理創造了機會


二、JDK中的動態代理


動態代理是指經過代理類來調用它對象的方法,而且是在程序運行使其根據須要建立目標類型的代理對象。它只提供一個代理類,咱們只須要在運行時候動態傳遞給須要他代理的對象就能夠完成對不一樣接口的服務了。看下面的例子。(JDK提供的代理正能針對接口作代理,也就是下面的newProxyInstance返回的必需要是一個接口)

package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* JDK中的動態代理
*/
//第一個接口
interface TargetOne {
void action();
}
//第一個接口的被代理類
class TargetOneImpl implements TargetOne{
@Override
public void action() {
System.out.println("我會實現父接口的方法...action");
}
}


//動態代理類
class DynamicProxyHandler implements InvocationHandler {
//接口的一個引用,多態的特性會使得在程序運行的時候,它實際指向的是實現它的子類對象
private TargetOne targetOne;
//咱們使用Proxy類的靜態方法newProxyInstance方法,將代理對象假裝成那個被代理的對象
/**
* ①這個方法會將targetOne指向實際實現接口的子類對象
* ②根據被代理類的信息返回一個代理類對象
*/
public Object setObj(TargetOne targetOne) {
this.targetOne = targetOne;
// public static Object newProxyInstance(ClassLoader loader, //被代理類的類加載器
// Class<?>[] interfaces, //被代理類實現的接口
// InvocationHandler h) //實現InvocationHandler的代理類對象
return Proxy.newProxyInstance(targetOne.getClass().getClassLoader(),targetOne.getClass().getInterfaces(),this);
}
//當經過代理類的對象發起對接口被重寫的方法的調用的時候,都會轉換爲對invoke方法的調用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("這是我代理以前要準備的事情......");
/**
* 這裏回想一下在靜態代理的時候,咱們顯示指定代理類須要執行的是被代理類的哪些方法;
* 而在這裏的動態代理實現中,咱們並不知道代理類會實現什麼方法,他是根據運行時經過反射來
* 知道本身要去指定被代理類的什麼方法的
*/
Object returnVal = method.invoke(this.targetOne,args);//這裏的返回值,就至關於真正調用的被代理類方法的返回值
System.out.println("這是我代理以後要處理的事情......");
return returnVal;
}
}
public class TestProxy {
public static void main(String[] args) {
//建立被代理類的對象
TargetOneImpl targetOneImpl = new TargetOneImpl();
//建立實現了InvocationHandler的代理類對象,而後調用其中的setObj方法完成兩項操做
//①將被代理類對象傳入,運行時候調用的是被代理類重寫的方法
//②返回一個類對象,經過代理類對象執行接口中的方法
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
TargetOne targetOne = (TargetOne) dynamicProxyHandler.setObj(targetOneImpl);
targetOne.action(); //調用該方法運行時都會轉爲對DynamicProxyHandler中的invoke方法的調用
}
}



運行結果以下。如今咱們對比jdk提供的動態代理和咱們剛剛實現的靜態代理,剛剛說到靜態代理對於新添加的接口須要定義對應的代理類去代理接口的實現類。而上面的測試程序所使用的動態代理規避了這個問題,即咱們不須要顯示的指定每一個接口對應的代理類,有新的接口添加沒有關係,只須要在使用的時候傳入接口對應的實現類而後返回代理類對象(接口實現類型),而後調用被代理類的方法便可。


6、動態代理與AOP簡單實現


一、AOP是什麼


AOP(Aspect Orient Programming)咱們通常稱之爲面向切面編程,做爲一種面向對象的補充,用於處理系統中分佈於各個模塊的橫切關注點,好比事務管理、日誌記錄等。AOP實現的關鍵在於AOP的代理(實際實現上有靜態代理和動態代理),咱們下面使用JDK的動態代理的方式模擬實現下面的場景。


二、模擬實現AOP


咱們先考慮下面圖中的狀況和說明。而後咱們使用動態代理的思想模擬簡單實現一下這個場景

package aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
//基於jdk的針對接口實現動態代理,要求的接口
interface Target {
void login();

void logout();
}
 
//被代理類
class TargetImpl implements Target {
@Override
public void login() {
System.out.println("log......");
}

@Override
public void logout() {
System.out.println("logout......");
}
}

class Util {
public void printLog() {
System.out.println("我是記錄打印日誌功能的方法......");
}
 
public void getProperties() {
System.out.println("我是獲取配置文件信息功能的方法......");
}
}

//實現了InvocationHandler的統一代理類
class DynamicProxyHandler implements InvocationHandler {
private Object object;

/**
* 參數爲obj,是應對對不一樣的被代理類,都能綁定與該代理類的代理關係
* 這個方法會將targetOne指向實際實現接口的子類對象,即當前代理類實際要去代理的那個類
*/
public void setObj(Object obj) {
this.object = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Util util = new Util();
util.getProperties();
Object object = method.invoke(this.object, args); //這個方法是個動態的方法,能夠是login,能夠是logout,具體在測試調用中調用不一樣方法
util.printLog();
return object;
}
}
 
//該類的主要做用就是動態的建立一個代理類的對象,同時須要執行被代理類
class MyDynamicProxyUtil {
//參數obj表示動態的傳遞進來被代理類的對象
public static Object getProxyInstance(Object object) {
//獲取代理類對象
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
dynamicProxyHandler.setObj(object);
//設置好代理類與被代理類之間的關係後,返回一個代理類的對象
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), dynamicProxyHandler);
}
}

public class TestAop {
public static void main(String[] args) {
//得到被代理類
Target target = new TargetImpl();
//經過代理類工具類,設置實際與代理類綁定的被代理類,並返回一個代理類對象執行實際的方法
Target execute = (Target) MyDynamicProxyUtil.getProxyInstance(target);
execute.login();
execute.logout();
}
}


 


如今來分析一下上面的代碼,首先咱們看一下下面的這個圖。在圖中動態代理增長的通用日誌方法配置文件方法就是增長的方法,他在執行用戶實際本身開發的方法以前、以後調用。對應於上面的程序就是Target接口的實現類實現的login、logout方法被代理類動態的調用,在他們執行以前會調用日誌模塊和配置文件模塊的功能。

相關文章
相關標籤/搜索