Spring之IOC,DI,動態代理,反射
Spring框架是J2EE開發中一個使用廣泛的框架,它使得dao和service層的維護更加便利。Spring框架有兩個重要的特徵,一個是IOC,另一個是AOP。我們在這裡主要介紹IOC,以及IOC中涉及的主要技術。
IOC(Inversion Of Control),控制反轉是將建立物件的控制權從程式設計師手中轉向Spring框架。Spring框架在建立物件時使用了DI技術。
DI(Dependency Injection),依賴注入主要是通過setter注入和構造器注入,產生一個類Class的bean(例項)。DI主要是通過動態代理和反射技術來實現的。
我們先來了解代理的概念。何為代理呢?顧名思義,代理可以幫助我們去完成某項事務,而且可以在完成事務中增強、完善相應的功能。舉例來說,我國多個地區已經實施適齡公民可以直接報考駕照,但在實際中,我們在考取駕照時,往往會委託一家駕校幫助我們報考駕照,在這之中,駕校就充當了我們報考駕照的代理角色。顯然,在很多時候,通過代理可以幫助我們節省時間,獲得更加便捷的服務。
其實,我們在Java開發中運用代理,與生活中的代理概念相似。
, 接下來,我們在介紹動態代理之前,先了解靜態代理。我們接下來以生活中報考駕照為例寫一個簡單的案例,來了解靜態代理。
package bkjz; /** * 考駕照的人 */ public class Examiner { //身份證號,姓名 private String id,name; public Examiner(String id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "考生(姓名:" +name+ ",身份證號:" + id + ")"; } } ****************** package bkjz; /** * 考駕照,核心業務功能介面 * 傳遞入一個具體報考駕照的人 */ public interface GetDriverLicense { public void getDriverLicense(Examiner examiner); } ****************** package bkjz.impl; import bkjz.Examiner; import bkjz.GetDriverLicense; /** * 實現報考駕照的核心功能介面的類 */ public class GetDriverLicenseImpl implements GetDriverLicense { @Override public void getDriverLicense(Examiner examiner) { System.out.println("報考駕照核心業務:您的各項報名指標合格,可以報考駕照"); } } ****************** package bkjz.impl; import bkjz.Examiner; import bkjz.GetDriverLicense; /** * 駕校代理,幫助報考者報考駕照 */ public class OrgProxy implements GetDriverLicense { //呼叫核心功能的介面實現類 private GetDriverLicense getDriverLicense; //構造器傳參 public OrgProxy(GetDriverLicenseImpl getDriverLicense){ this.getDriverLicense=getDriverLicense; } @Override public void getDriverLicense(Examiner examiner) { System.out.println("增強功能1:你好,"+examiner+",我來幫你收集報名需要提交哪些資料"); System.out.println("增強功能2:你好,"+examiner+",我來幫你推薦體檢醫院,幫你節省時間"); System.out.println("增強功能3:你好,"+examiner+",我來幫你到車管所遞交報名資料,幫你節省時間"); System.out.println("==========================="); getDriverLicense.getDriverLicense(examiner); System.out.println("==========================="); System.out.println("增強功能4:你好,"+examiner+",報名成功後,我幫你把報考所需身份資料的原件取回,幫你節省時間"); System.out.println("增強功能5:你好,"+examiner+",我來幫你安排培訓學習時間,幫你通過考試"); System.out.println("增強功能6:你好,"+examiner+",你通過駕照考試後,我幫你郵寄駕照,幫你節省時間"); } } ************************* package bkjz; import bkjz.impl.GetDriverLicenseImpl; import bkjz.impl.OrgProxy; /** * 測試類 */ public class Test { public static void main(String[] args) { //一個準備報考駕照的考生 Examiner examiner=new Examiner("320923198901142757","張三"); //找個駕校來報考駕照,以及安排後續的學習取照 OrgProxy orgProxy=new OrgProxy(new GetDriverLicenseImpl()); orgProxy.getDriverLicense(examiner); } }
執行測試類後,執行結果如下:
增強功能1:你好,考生(姓名:張三,身份證號:320923198901142757),我來幫你收集報名需要提交哪些資料 增強功能2:你好,考生(姓名:張三,身份證號:320923198901142757),我來幫你推薦體檢醫院,幫你節省時間 增強功能3:你好,考生(姓名:張三,身份證號:320923198901142757),我來幫你到車管所遞交報名資料,幫你節省時間 =========================== 報考駕照核心業務:您的各項報名指標合格,可以報考駕照 =========================== 增強功能4:你好,考生(姓名:張三,身份證號:320923198901142757),報名成功後,我幫你把報考所需身份資料的原件取回,幫你節省時間 增強功能5:你好,考生(姓名:張三,身份證號:320923198901142757),我來幫你安排培訓學習時間,幫你通過考試 增強功能6:你好,考生(姓名:張三,身份證號:320923198901142757),你通過駕照考試後,我幫你郵寄駕照,幫你節省時間
可見,通過代理的確能夠為我們帶來很多便利,還可以增強很多功能。實際上,我們平時遇到的過濾器、攔截器從本質上來說,也是通過代理的方式實現的,通過代理增強一些功能,從而控制對被代理的業務的訪問。當然,過濾器、攔截器運用的代理技術要比靜態代理更靈活。
靜態代理有個弊端,那就是如果具體業務改變時,我們需要改寫其他的代理類,在需要代理的功能很多時,就會顯著增加我們的工作量,不利於效率的提升。為此,我們引入動態代理的概念。
所謂動態代理,顯然,我們需要增加一些增強型的功能時,不必再多次建立代理類,而是動態地進行相應的擴充套件即可。我們通過案例來看基於業務介面的動態代理。
package test2_proxy.proxy;
import test2_proxy.service.OrderServiceImpl;
import test2_proxy.service.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 基於介面的動態代理
* 對於一些需要重複執行的驗證功能或下一步操作,
* 在核心方法執行前後都會執行增強的功能
*/
public class ServiceProxy {
public static Object newProxyInstance(Object target){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("動態增強功能1:登入許可權認證");
System.out.println("動態增強功能2:黑名單驗證");
System.out.println("=====================");
Object invoke = method.invoke(target, args);//執行被代理的核心業務功能
System.out.println("=====================");
System.out.println("動態增強功能3:進入訂單中心");
System.out.println("此處省略一萬行(●—●)...");
return invoke;
}
});
}
}
***********************
package test2_proxy.service;
/**
* 使用者登入、註冊、找回密碼等一系列服務的介面
*/
public interface UserService {
public void testUserService();
}
************************
package test2_proxy.service;
import org.springframework.stereotype.Service;
/**
* 實現使用者服務介面的類
*/
@Service
public class UserServiceImpl implements UserService {
@Override
public void testUserService() {
System.out.println("UserService核心功能");
}
}
***************************
package test2_proxy.service;
/**
* 訂單服務介面,如下單、取消訂單、放入購物車等
*/
public interface OrderService {
public void testOrderService();
}
***************************
package test2_proxy.service;
/**
* 實現訂單核心功能介面的類
*/
public class OrderServiceImpl implements OrderService {
@Override
public void testOrderService() {
System.out.println("OrderService核心功能");
}
}
***************************
package test2_proxy;
import test2_proxy.proxy.ServiceProxy;
import test2_proxy.service.OrderService;
import test2_proxy.service.OrderServiceImpl;
import test2_proxy.service.UserService;
import test2_proxy.service.UserServiceImpl;
/**
* 測試類
*/
public class Test {
public static void main(String[] args) {
UserService userService=new UserServiceImpl();
userService = (UserService)ServiceProxy.newProxyInstance(userService);
userService.testUserService();
System.out.println("***************************");
OrderService orderService=new OrderServiceImpl();
orderService = (OrderService)ServiceProxy.newProxyInstance(orderService);
orderService.testOrderService();
}
}
上述程式碼執行完後,執行結果如下:
動態增強功能1:登入許可權認證
動態增強功能2:黑名單驗證
=====================
UserService核心功能
=====================
動態增強功能3:進入訂單中心
此處省略一萬行(●—●)...
***************************
動態增強功能1:登入許可權認證
動態增強功能2:黑名單驗證
=====================
OrderService核心功能
=====================
動態增強功能3:進入訂單中心
此處省略一萬行(●—●)...
可見,動態代理比靜態代理更加靈活,它的核心是 java.lang.reflect.Proxy代理類,該類位於reflect反射包下,可知動態代理當中是用了反射的技術來實現的。
接下來,我們再看基於類實現的動態代理。此處需要在專案中新增 cglib-2.2.2.jar包的依賴,運用cglib的方法,使程式碼更簡潔。我們通過案例來實現。
package test3_proxy_cglib.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 動態代理類
*/
public class ServiceProxy {
public static Object newProxyInstance(Object target) {
//1.工具類
Enhancer enhancer = new Enhancer();
//2.設定父類,傳遞類物件
enhancer.setSuperclass(target.getClass());
//3.設定回撥函式
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib增強功能1");
System.out.println("cglib增強功能2");
System.out.println("此處省略一萬行(●—●)...");
System.out.println("==================");
return method.invoke(target, objects);
}
});
//4.功能增強後,返回代理物件
return enhancer.create();
}
}
****************************
package test3_proxy_cglib;
/**
* 使用者服務功能類
*/
public class UserService {
public void testUserService(){
System.out.println("UserService核心功能");
}
}
*****************************
package test3_proxy_cglib;
/**
* 訂單服務功能類
*/
public class OrderService {
public void testOrderService(){
System.out.println("OrderService核心功能");
}
}
**********************************
package test3_proxy_cglib;
import test3_proxy_cglib.proxy.ServiceProxy;
/**
* 測試類
*/
public class Test {
public static void main(String[] args) {
UserService userService=new UserService();
userService= (UserService)ServiceProxy.newProxyInstance(userService);
userService.testUserService();
System.out.println("***************************");
OrderService orderService=new OrderService();
orderService=(OrderService)ServiceProxy.newProxyInstance(orderService);
orderService.testOrderService();
}
}
上述程式碼的執行結果如下:
cglib增強功能1
cglib增強功能2
此處省略一萬行(●—●)...
==================
UserService核心功能
***************************
cglib增強功能1
cglib增強功能2
此處省略一萬行(●—●)...
==================
OrderService核心功能
接下來,我們看下反射。
Java反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。Java關於反射的定義主要在 java.lang.reflect包中。Java反射需要用到Class物件,Class物件的由來是將class檔案讀入記憶體,併為之建立一個Class物件。
Class 類的例項表示正在執行的 Java 應用程式中的類和介面,也就是jvm中有N多的例項,每個類都有對應的Class物件(包括基本資料型別)。Class 沒有公共構造方法,Class 物件是在載入類時由 Java 虛擬機器以及通過呼叫類載入器中的defineClass 方法自動構造的。也就是說,Class 物件不需要我們自己去建立,JVM已經幫我們建立好了。
在使用反射時,我們需要先獲取Class物件。獲取Class物件有三種方式:
第一,Object物件.getClass()
第二,任何資料型別(包括基本資料型別)都有一個靜態static的class屬性
第三,通過Class類的靜態方法forName(String className)來獲取 Class.forName(String className) ------>[這是我們常用的獲取類物件的方法]
需要注意的是,在程式執行期間,一個類只有一個Class物件產生,該Class物件由JVM管理。獲得了類物件,我們就隨之可以使用這個類物件的所有屬性和方法。
通過反射,我們可以獲取一個類的任意方法(包括構造方法)和屬性,從而充分利用一個類所提供的一切資源。
另外,我們在使用各類集合(如List,Set,Map)時,為了指明集合中元素的型別,通常會用到泛型,泛型是作用於程式編譯期,編譯過後,泛型的約束就會失效;反射則作用於程式編譯期之後的執行期,因此,我們可以通過反射來越過泛型的檢查。我們通過下面的案例來看:
package test9;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 泛型作用於編譯期,會在編譯之前對程式進行檢查
* 編譯期過後,泛型約束失效
* 反射作用於執行期
* 演示通過反射越過泛型的檢查
*/
public class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//1.先宣告一個List集合,元素泛型約束為String型別
List<String> strList=new ArrayList<>();
//2.向集合中新增String型的元素
strList.add("aa");
strList.add("bb");
//注:在編譯期之前,向集合中新增非泛型元素,程式會在編譯期就報錯
//strList.add(123);
//這時,我們通過反射來越過泛型檢查
Class strListClass = strList.getClass();//先獲取Class物件
//獲取add方法,為了保障程式清晰,在main方法上丟擲異常
Method method = strListClass.getMethod("add", Object.class);
//通過反射呼叫add方法,新增一個int型資料
method.invoke(strList,123);
System.out.println("strList="+strList);
}
}
上述程式碼列印結果如下:
strList=[aa, bb, 123]
可見,定義元素泛型為String的List集合成功越過了泛型檢查,添加了一個int型資料。