天天看點

java類加載及動态代理之JDK,CGLIB什麼是CGLIB為什麼使用CGLIB

本文主要介紹一下java的動态代理技術,所謂動态代理,都是在運作時期發生的,為目标對象産生一個代理對象,進而可以執行更多的功能,其中AOP程式設計是最典型的一個可以用動态代理實作的機制,實作動态代理技術有很多種,比如JDK的動态代理,asm,cglib,javassit等都可以實作,這些架構後面都會陸續有講解到,今天隻涉及JDK和CGLIB,要注意我們通常所說的代理都是指動态代理為主,靜态代理的使用場景很有限制.

動态代理常用的場景一般有哪些?

& 比如對某個類的方法進行增強

& 比如通過代理技術實作接口的調用

& 比如對某各類的方法的通路進行權限控制

動态代理的代理類型有哪些?

& 代理的類型是接口

& 代理的類型是類

動态代理的基礎技術是什麼?

代理的使用必須要熟悉java的反射機制,因為很多知識都是離不開反射的,會大量使用反射的常用API

JDK的動态代理實作和CGLIB實作的差別是什麼?

& JDK1.7版本之前要代理的類型隻能是接口,否則無法使用代理技術,JDK1.8版本之後是否支援需要看一下源碼

& CGLIB不但可以代理接口類型,而且它也支援代理類的執行個體,它還支援其它很多的功能

動态代理技術實作的本質是什麼?

& 其本質還是最終要生成代理類的位元組碼

下面就JDK的動态代理和CGLIB的代理技術通過代碼執行個體進行講解,首先看JDK的實作機制:

基于JDK的機制在使用上來說很簡單,隻要實作InvocationHandler接口,實作裡面的invoke方法接口,常用的步驟如下:

1.通過實作InvocationHandler接口來自定義自己的InvocationHandler;

  2.通過Proxy.getProxyClass獲得動态代理類   3.通過反射機制獲得代理類的構造方法,方法簽名為getConstructor(InvocationHandler.class)   4.通過構造函數獲得代理對象并将自定義的InvocationHandler執行個體對象傳為參數傳入   5.通過代理對象調用目标方法

下面就舉一個很簡單的例子,比如我們現在有一個打遊俠的業務邏輯,希望在打遊戲之前,要檢查一下目前的使用者賬号是否滿足條件需求,滿足登入遊戲平台打遊戲,不滿足給出提示,對于本例子來說 如果使用者賬号是'jhp',就通過,其它都不給于通過:

首先定義一個打遊戲的業務接口和實作類,代碼很簡單

package com.suning.dynamic_proxy.jdk;

/**
 * Created by jack on 2018/6/19.
 * 打遊戲
 */
public interface IGameService {
    /**
     * 打遊戲
     * @param uname
     */
    void playGame(String uname);
}

      
package com.suning.dynamic_proxy.jdk;

/**
 * Created by jack on 2018/6/19.
 * 打遊戲的業務實作
 */
public class GameService implements IGameService {
    @Override
    public void playGame(String uname) {
        System.out.println("目前正在打遊戲的使用者是:"+uname);
    }
}      

定義遊戲代理的handler:

package com.suning.dynamic_proxy.jdk.invocation;

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

/**
 * Created by jack on 2018/6/19.
 * 代理實作的真正功能
 */
public class GameInvocationHandler implements InvocationHandler {
    private Object target;
    public GameInvocationHandler (Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String uname = args[0].toString();      
        //模拟使用者賬号檢查機制         
        if (uname.equals("jhp")){
            return method.invoke(target,args);
        }
        else {
            throw new Exception("對不起,使用者資訊不合法...");
        }

    }
}      

有了handler,下面就可以使用角JDK自帶的Proxy來實作代理對象的産生了

package com.suning.dynamic_proxy.jdk.proxy;

import com.suning.dynamic_proxy.jdk.invocation.GameInvocationHandler;

import java.lang.reflect.Proxy;

/**
 * Created by jack on 2018/6/19
 * 遊戲代理
 */
public class GameProxy {
    /**
     * 産生代理對象 一定要求是基于接口的
     * @param object
     * @param <T>
     * @return
     */
    public static <T> T getProxy(Object object){
        return (T)Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),
                new GameInvocationHandler(object));
    }
}      

接下來再寫一個測試類:

package com.suning.dynamic_proxy.jdk.test;

import com.suning.dynamic_proxy.jdk.GameService;
import com.suning.dynamic_proxy.jdk.IGameService;
import com.suning.dynamic_proxy.jdk.proxy.GameProxy;

/**
 * Created by jack on 2018/6/19.
 */
public class GameProxyTest {
    public static void main(String[] args) {
        IGameService gameService = new GameService();
        IGameService gameProxyService = GameProxy.getProxy(gameService);      
        //如果是JHP就能通過,其它則通不過
        gameProxyService.playGame("jhp");
    }
}      

根據使用者賬号的不同,傳回兩種結果:

正常結果

java類加載及動态代理之JDK,CGLIB什麼是CGLIB為什麼使用CGLIB

登入失敗結果:抛異常

java類加載及動态代理之JDK,CGLIB什麼是CGLIB為什麼使用CGLIB

這個就是JDK動态代理的一個簡單應用,下面再講解一下CGLIB的使用

基于CGLIB的代理機制:看一下基礎概念

什麼是CGLIB

CGLIB是一個強大的、高性能的代碼生成庫。其被廣泛應用于AOP架構(Spring、dynaop)中,用以提供方法攔截操作。Hibernate作為一個比較受歡迎的ORM架構,同樣使用CGLIB來代理單端(多對一和一對一)關聯(延遲提取集合使用的另一種機制)

為什麼使用CGLIB

CGLIB代理主要通過對位元組碼的操作,為對象引入間接級别,以控制對象的通路。我們知道Java中有一個動态代理也是做這個事情的,那我們為什麼不直接使用Java動态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK動态代理更加強大,JDK動态代理雖然簡單易用,但是其有一個緻命缺陷是,隻能對接口進行代理。如果要代理的類為一個普通類、沒有接口,那麼Java動态代理就沒法使用了

下面繼續以執行個體和代碼為主進行講解:還是以登入系統為例,使用者登入之前肯定需要對使用者的賬号資訊進行驗證,這個就可以很實用

方法攔截器實作.那麼具體如何實作呢?隻需要實作CGLIB的MethodInterceptor接口裡面的interceptor方法:

首先定義一個簡單的使用者登入實作業務類:

package com.suning.dynamic_proxy.cglib.login;

/**
 * @Author 18011618
 * @Description
 * @Date 9:36 2018/6/20
 * @Modify By
 */
public class LoginService {
    public void login(String uname){
        System.out.println("恭喜您,已經成功登陸到系統,"+uname);
    }
}
      

現在需要對使用者賬号進行驗證,比如是jhp就讓其通過,其它不讓通過:,定義個實作MethodInterceptor的實作類

package com.suning.dynamic_proxy.cglib.login;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author 18011618
 * @Description 模拟登陸系統對使用者做的登陸檢查
 * @Date 9:39 2018/6/20
 * @Modify By
 */
public class LoginMethodInterceptor implements MethodInterceptor {
    /**
     *
     * @param object 目标對象
     * @param method 執行的方法
     * @param objects 參數
     * @param proxy 方法代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        System.out.println("execute user login before check....");
        if (objects.length<=0){
            System.out.println("sorry,paramter cat not is null or empty");
        }
        else {
            String username = objects[0].toString();      
            //模拟使用者賬号的檢查
            if (username.equals("jhp")){
               return proxy.invokeSuper(object,objects);

            }
            else {
                System.out.println("sorry,input username is not exists or error please repeat check it...");
            }
        }
        return null;



    }
}      

再寫一個測試類:

package com.suning.dynamic_proxy.cglib.login;

import net.sf.cglib.proxy.Enhancer;

/**
 * @Author 18011618
 * @Description
 * @Date 9:49 2018/6/20
 * @Modify By
 */
public class LoginMethodPrxoyTest {
    public static void main(String[] args) {
        //定義代理實作
        Enhancer enhancer = new Enhancer();
        //設定要代理的父類
        enhancer.setSuperclass(LoginService.class);
        //設定代理類攔截的方法處理器
        enhancer.setCallback(new LoginMethodInterceptor());
        //構造器裡面調用的方法不進行攔截
        enhancer.setInterceptDuringConstruction(false);
        //通過代理擷取具體的代理對象
        LoginService loginService = (LoginService) enhancer.create();
        loginService.login("abc");

    }
}      

上面代碼運作的效果如下:

java類加載及動态代理之JDK,CGLIB什麼是CGLIB為什麼使用CGLIB

如果換成是'jhp':

java類加載及動态代理之JDK,CGLIB什麼是CGLIB為什麼使用CGLIB

通過上面的代碼可以總結使用核心步驟:

1 建立一個實作MethodInterceptor的接口

2 執行個體化Enchaner對象

3 設定代理的父類

4 設定代理的攔截方法處理器

5 擷取代理對象執行個體

OK 這個就是模拟簡單實作了登入權限的驗證,下面再看一種業務場景用法,就是有時候可能需要對一個業務類裡面的不同方法見不同的攔截政策處理,這個它也可以實作

基于CGLIB實作的方法攔截政策機制

比如擴充一下上面的業務,對于一個使用者經常有的功能就是注冊和登入,現在就模拟實作這一的一個功能,要求對登入功能進行使用者名的檢查,對注冊功能進行密碼的檢查,如何實作這一的功能呢:

首先模拟定義一個簡單的使用者服務類:

package com.suning.dynamic_proxy.cglib.strategy;

/**
 * @Author 18011618
 * @Description 模拟使用者的服務 登陸和注冊
 * @Date 10:18 2018/6/20
 * @Modify By
 */
public class UserService {

    /**
     * 使用者注冊
     * @param username
     * @param password
     */
    public void register(String username,String password){
        System.out.println("成功向資料庫插入一條使用者資訊,"+username+"," +password);
    }

    /**
     * 使用者登陸
     * @param username
     */
    public void login(String username){
        System.out.println("使用者"+username+",恭喜您成功登陸到系統..");
    }
}      

然後為不同的功能定義不同的攔截器處理:下面這個是處理登入的方法攔截器

package com.suning.dynamic_proxy.cglib.strategy;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author 18011618
 * @Description 使用者登陸權限攔截
 * @Date 10:22 2018/6/20
 * @Modify By
 */
public class UserLoginProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        System.out.println("執行登陸前的方法攔截檢查....");
        if (objects.length<=0){
            System.out.println("對不起方法的參數不能為空");
        }
        else {
            String username = objects[0].toString();
            if (username.equals("jhp")){
                return proxy.invokeSuper(object,objects);

            }
            else {
                System.out.println("對不起您輸入的使用者名不正确...");
            }
        }
        return null;
    }
}      

再看下面的處理注冊方法的攔截器:

package com.suning.dynamic_proxy.cglib.strategy;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author 18011618
 * @Description 使用者注冊權限攔截
 * @Date 10:22 2018/6/20
 * @Modify By
 */
public class UserRegisterProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        System.out.println("....execute registor before......");
        if (objects.length<=0){
            System.out.println("sorry objects is not null or empty");
        }
        else {
            String pwd = objects[1].toString();
            if (pwd.equals("111111")){
                return proxy.invokeSuper(object,objects);

            }
            else {
                System.out.println("sorry input password is error please repeat check it...s");
            }
        }
        return null;
    }
}      

根據不同的方法傳回不同的攔截政策,也就是說是有順序的,是以要再實作一個CallbackFilter,看代碼:

package com.suning.dynamic_proxy.cglib.strategy;

import net.sf.cglib.proxy.CallbackFilter;

import java.lang.reflect.Method;

/**
 * @Author 18011618
 * @Description 方法攔截 可以根據指定的方法名稱進行攔截
 * @Date 10:26 2018/6/20
 * @Modify By
 */
public class MethodFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        //如果是注冊功能 就調用第二個攔截器處理
        if ("register".equals(method.getName())){
            return 1;
        }
        //調用第一個攔截器處理
        return 0;
    }
}      

最後再寫一個測試類來進行綜合測試:

package com.suning.dynamic_proxy.cglib.strategy;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

/**
 * @Author 18011618
 * @Description 模拟不同的方法 采取不同的攔截政策
 * @Date 10:26 2018/6/20
 * @Modify By
 */
public class UserProxyTest {
    public static void main(String[] args) {
        UserLoginProxy loginProxy = new UserLoginProxy();
        UserRegisterProxy registerProxy = new UserRegisterProxy();

        //設定代理類
        Enhancer enhancer = new Enhancer();
        //設定攔截的類
        enhancer.setSuperclass(UserService.class);
        //設定處理方法的攔截器 callbackfilter會根據方法名稱選擇不同的攔截器處理
        enhancer.setCallbacks(new Callback[]{loginProxy,registerProxy, NoOp.INSTANCE});
        //設定回調過濾器
        enhancer.setCallbackFilter(new MethodFilter());
        //建立具體的方法
        UserService userService = (UserService) enhancer.create();
        userService.login("jhp");
        System.out.println("===============================================");
        userService.register("jhp","111111");
    }
}      

上面代碼的運作效果如下:

java類加載及動态代理之JDK,CGLIB什麼是CGLIB為什麼使用CGLIB

有了這個功能就可以實作N個方法不同的攔截處理了,當然CGLIB還有其他的功能,比如也可以動态建立一個bean,下面簡單的示範一下:下面的代碼就動态建立了一個javabean同時還有setUname和getUname兩個方法,并且對setUname進行指派,然後再調用.

package com.suning.dynamic_proxy.cglib.beangenerator;

import net.sf.cglib.beans.BeanGenerator;

import java.lang.reflect.Method;

/**
 * @Author 18011618
 * @Description 通過cglib動态建立一個javabean 一個屬性
 * @Date 10:48 2018/6/20
 * @Modify By
 */
public class UserServiceGenerator {
    /**
     * 建立一個javabean
     * @return
     * @throws Exception
     */
    public String createJavaBean() throws Exception{
        //執行個體化bean的建立器
        BeanGenerator beanGenerator = new BeanGenerator();
        //為bean添加屬性
        beanGenerator.addProperty("uname",String.class);
        //建立一個bean
        Object myBean = beanGenerator.create();
        //設定bean的方法
        Method setter = myBean.getClass().getMethod("setUname",String.class);
        //執行bean
        setter.invoke(myBean,"jhp");
        //執行bean的方法
        Method getter = myBean.getClass().getMethod("getUname");
        return getter.invoke(myBean).toString();
    }
}      

寫一個測試類測試一下:

package com.suning.dynamic_proxy.cglib.beangenerator;

/**
 * @Author 18011618
 * @Description
 * @Date 10:51 2018/6/20
 * @Modify By
 */
public class BeanGeneratorTest {
    public static void main(String[] args)throws Exception {
        UserServiceGenerator generator = new UserServiceGenerator();
        String result = generator.createJavaBean();
        System.out.println(result);
    }
}      

看一下效果:

java類加載及動态代理之JDK,CGLIB什麼是CGLIB為什麼使用CGLIB

其它的功能就不再闡述了,因為本文主要是講解動态代理的實作機制的.