1. 程式人生 > >Spring之IOC,DI,動態代理,反射

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型資料。