什么是動態代理?
動態代理是在程序運行期,動態的創建目標對象的代理對象,并對目標對象中的方法進行功能性增強的一種技術。
在生成代理對象的過程中,目標對象不變,代理對象中的方法是目標對象方法的增強方法。可以理解為運行期間,對象中方法的動態攔截,在攔截方法的前后執行功能操作。
動態代理的常見使用場景有:
- 統計每個 api 的請求耗時;
- 統一的日志輸出;
- 校驗被調用的 api 是否已經登錄和權限鑒定;
- Spring AOP。
動態代理和靜態代理有什么區別?
靜態代理其實就是事先寫好代理類,可以手工編寫也可以使用工具生成,但它的缺點是每個業務類都要對應一個代理類,特別不靈活也不方便。
動態代理是在程序運行期,動態的創建目標對象的代理對象,并對目標對象中的方法進行功能性增強的一種技術。
所以總結來說,動態代理和靜態代理的效果都是一樣的,但靜態代理使用麻煩,而動態代理使用簡單,后者也是現在編程中實現代理的主流方式。
如何實現動態代理?
在 Java 中,實現動態代理的常用方式是 JDK Proxy 和 CGLib。
JDK Proxy 和 CGLib 有什么區別?
JDK Proxy 和 CGLib 的區別主要體現在以下幾個方面:
- JDK Proxy 是 Java 語言自帶的功能,無需通過加載第三方類實現;
- Java 對 JDK Proxy 提供了穩定的支持,并且會持續的升級和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
- JDK Proxy 是通過攔截器加反射的方式實現的;
- JDK Proxy 只能代理繼承接口的類;
- JDK Proxy 實現和調用起來比較簡單;
- CGLib 是第三方提供的工具,基于 ASM 實現的,性能比較高;
- CGLib 無需通過接口來實現,它是通過實現子類的方式來完成調用的。
動態代理的底層是如何實現的?
不同的動態代理的底層實現是不同的,比如 JDK Proxy 底層是通過反射技術實現的,而 CGLib 是基于 ASM,一個 Java 字節碼操作框架實現的。
說一下反射機制?
Java 反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱為 Java 語言的反射機制。簡單來說,反射機制指的是程序在運行時能夠獲取自身的信息。在 Java 中,只要給定類的名字,就可以通過反射機制來獲得類的所有信息。
Java 反射機制主要提供了以下功能,這些功能都位于 java.lang.reflect 包:
- 在運行時判斷任意一個對象所屬的類;
- 在運行時構造任意一個類的對象;
- 在運行時判斷任意一個類所具有的成員變量和方法;
- 在運行時調用任意一個對象的方法;
- 生成動態代理。
在程序開發中反射的使用場景有很多,比如以下幾個。
使用場景一:編程工具 IDEA 或 Eclipse 等,在寫代碼時會有代碼(屬性或方法名)提示,就是因為使用了反射;
使用場景二:很多知名的框架,如 Spring、MyBatis 等,為了讓程序更優雅更簡潔,也會使用到反射。
例如,Spring 可以通過配置來加載不同的類,調用不同的方法,代碼如下所示:
<bean id="person" class="com.spring.beans.Person" init-method="initPerson">
</bean>
例如,MyBatis 在 Mapper 使用外部類的 Sql 構建查詢時,代碼如下所示:
@SelectProvider(type = PersonSql.class, method = "getListSql")
List<Person> getList();
class PersonSql {public String getListSql() {String sql = new SQL() {{SELECT("*");FROM("person");}}.toString();return sql;}
}
使用場景三:數據庫連接池,也會使用反射調用不同類型的數據庫驅動,代碼如下所示:
String url = "jdbc:mysql://127.0.0.1:3306/mydb";
String username = "root";
String password = "root";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);
如何使用反射?
在 Java 中,反射獲取調用類可以通過 Class.forName(),反射獲取類實例要通過 newInstance(),相當于 new 一個新對象,反射獲取方法要通過 getMethod(),獲取到類方法之后使用 invoke() 對類方法進行調用。如果是類方法為私有方法的話,則需要通過 setAccessible(true) 來修改方法的訪問限制,以上的這些操作就是反射的基本使用,具體調用如下。
- 反射調用靜態方法
Class myClass = Class.forName("類名");
Method method = myClass.getMethod("調用方法名");
method.invoke(myClass);
- 反射調用公共方法
Class myClass = Class.forName("類名");
// 創建實例對象(相當于 new )
Object instance = myClass.newInstance();
Method method2 = myClass.getMethod("調用方法名");
method2.invoke(instance);
- 反射調用私有方法
Class myClass = Class.forName("類名");
// 創建實例對象(相當于 new )
Object object = myClass.newInstance();
Method method3 = myClass.getDeclaredMethod("調用方法名");
method3.setAccessible(true);
method3.invoke(object);
反射有什么優缺點?
優點:
- 能夠運行時動態獲取類的實例,大大提高系統的靈活性和擴展性;
- 與 Java 動態編譯相結合,可以實現無比強大的功能;
- 對于 Java 這種先編譯再運行的語言,能夠讓我們很方便的創建靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代碼的鏈接,更加容易實現面向對象。
缺點:
- 反射會消耗一定的系統資源,因此,如果不需要動態地創建一個對象,那么就不需要用反射;
- 反射調用方法時可以忽略權限檢查,獲取這個類的私有方法和屬性,因此可能會破壞類的封裝性而導致安全問題。