Java代理簡述 Spring(11) - Introductions進行類擴展方法 Spring筆記(3) - SpringAOP基礎詳解和源碼探究

  1.什麼是代理?

  對類或對象(目標對象)進行增強功能,最終形成一個新的代理對象,(Spring Framework中)當應用調用該對象(目標對象)的方法時,實際調用的是代理對象增強後的方法,比如對功能方法login實現日誌記錄,可以通過代理實現;

  PS:目標對象--被增強的對象;代理對象--增強後的對象;

  2.爲什麼需要代理?

  一些類裏面的方法有相同的代碼或類中有相同的功能,可以將這些相同抽取出來形成一個公共的方法或功能,但Java有兩個重要的原則:單一職責(對類來說的,即一個類應該只負責一項職責)和開閉原則(開放擴展,修改關閉),如果每個類的每個功能都調用了公共功能,就破壞了單一職責,如下圖;如果這個類是別人已經寫好的,你動了這個代碼,同時也破壞了開閉原則(同時改動代碼很麻煩,裏面可能涉及其他很多的調用,可能帶出無數的bug,改代碼比新開發功能還難/(ㄒoㄒ)/~~);

  由於有上面的問題存在,使用代理來實現是最好的解決辦法;

  3.Java實現代理有哪些?

  (1)靜態代理:通過對目標方法進行繼承或聚合(接口)實現;(會產生類爆炸,因此在不確定的情況下,儘量不要使用靜態代理,避免產生類爆炸)

    1)繼承:代理對象繼承目標對象,重寫需要增強的方法;

//業務類(目標對象)
public class UserServiceImpl {
    public void query(){
        System.out.println("業務操作查詢數據庫....");
    }
}
//日誌功能類
public class Log {
    public static void info(){
        System.out.println("日誌功能");
    }
}
//繼承實現代理(代理對象)
public class UserServiceLogImpl extends UserServiceImpl {
    public void query(){
        Log.info();
        super.query();
    }
}

    從上面代碼可以看出,每種增強方法會產生一個代理類,如果現在增強方法有日誌和權限,單個方法增強那需要兩個代理類(日誌代理類和權限代理類),如果代理類要同時擁有日誌和權限功能,那又會產生一個代理類,同時由於順序的不同,可能會產生多個類,比如先日誌後權限是一個代理類,先權限後日志又是另外一個代理類。

    由此可以看出代理使用繼承的缺陷:產生的代理類過多(產生類爆炸),非常複雜,維護難;

    2)聚合:代理對象和目標對象都實現同一接口,使用裝飾者模式,提供一個代理類構造方法(代理對象當中要包含目標對象),參數是接口,重寫目標方法;

//接口
public interface UserDao {
    void query();
}
//接口實現類:目標對象
public class UserDaoImpl implements  UserDao {
    @Override
    public void query() {
        System.out.println("query......");
    }
}
//日誌功能類
public class Log {
    public static void info(){
        System.out.println("日誌功能");
    }
}
//聚合實現代理:同樣實現接口,使用裝飾者模式;(代理對象)
public class UserDaoLogProxy implements UserDao {
    UserDao userDao;
    public UserDaoLogProxy(UserDao userDao){//代理對象包含目標對象
        this.userDao = userDao;
    }
    @Override
    public void query() {
        Log.info();
        userDao.query();
    }
}
//測試
public static void main(String[] args) {
    UserDaoImpl target = new UserDaoImpl();
    UserDaoLogProxy proxy = new UserDaoLogProxy(target);
    proxy.query();
}

    聚合由於利用了面向接口編程的特性,產生的代理類相對繼承要少一點(雖然也是會產生類爆炸,假設有多個Dao,每個Dao產生一個代理類,所以還是會產生類爆炸),如下案例:

//時間記錄功能
public class Timer {
    public static void timer(){
        System.out.println("時間記錄功能");
    }
}
//代理類:時間功能+業務
public class UserDaoTimerProxy implements UserDao {

    UserDao userDao;
    public UserDaoTimerProxy(UserDao userDao){
        this.userDao = userDao;
    }
    @Override
    public void query() {
        Timer.timer();
        userDao.query();
    }
}
//測試
public static void main(String[] args) {
    //timer+query
    UserDao target = new UserDaoTimerProxy(new UserDaoImpl());
    //log+timer+query
    UserDao proxy = new UserDaoLogProxy(target);
    proxy.query();
}
public static void main(String[] args) {
    //log+query
    UserDao target = new UserDaoLogProxy(new UserDaoImpl());
    //timer+log+query
    UserDao proxy = new UserDaoTimerProxy(target);
    proxy.query();
}

  PS:

    裝飾和代理的區別:代理不需要指定目標對象,可以對任何對象進行代理;裝飾需要指定目標對象,所以需要構造方法參數或set方法指定目標對象;

    幾個io的Buffer流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)就是使用了裝飾模式的靜態代理;

public class BufferedReader extends Reader {
    private Reader in;
    ........
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }

    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }
}

  (2)動態代理:Java有JDK動態代理和CGLIB代理;(Spring Framework通過Spring AOP實現代理,底層還是使用JDK代理和CGLIB代理來實現)

    模擬動態代理:不需要手動創建類文件(因爲一旦手動創建類文件,會產生類爆炸),通過接口反射生成一個類文件,然後調用第三方的編譯技術,動態編譯這個產生的類文件成class文件,然後利用URLclassLoader把這個動態編譯的類加載到jvm中,然後通過反射把這個類實例化。(通過字符串產生一個對象實現代理);

    PS:Java文件 -> class文件 -> byte字節(JVM)-> class對象(類對象)-> new(對象);

    所以步驟如下:

      1)代碼實現一個內容(完整的Java文件內容,包含包名、變量、構造方法、增強後的方法等),使其通過IO產生一個Java文件;
      2)通過第三方編譯技術產生一個class文件;
      3)加載class文件利用反射實例化一個代理對象出來;
public class ProxyUtil {
    /**
     *  content --->string
     *     |
     *     |生成
     *     v
     *  .java   <-----通過io產生
     *  .class  <-----Java文件編程產生
     *
     *  .new    <-----class文件反射產生實例對象
     * @return
     */
    public static Object newInstance(Object target){
        Object proxy=null;
        //根據對象獲取接口
        Class targetInf =target.getClass().getInterfaces()[0];
        //獲取接口的所有方法
        //getMethods(),該方法是獲取本類以及父類或者父接口中所有的公共方法(public修飾符修飾的)
        //getDeclaredMethods(),該方法是獲取本類中的所有方法,包括私有的(private、protected、默認以及public)的方法
        Method[] declaredMethods = targetInf.getDeclaredMethods();
        String line="\n";
        String tab ="\t";
        //接口名稱
        String targetInfName = targetInf.getSimpleName();
        String content ="";
        //包位置
        String packageContent = "package com;"+line;
        //接口
        String importContent = "import "+targetInf.getName()+";"+line;
        String clazzFirstLineContent = "public class $Proxy implements "+targetInfName+"{"+line;
        //屬性
        String filedContent  =tab+"private "+targetInfName+" target;"+line;
        //構造方法
        String constructorContent =tab+"public $Proxy ("+targetInfName+" target){" +line
                +tab+tab+"this.target =target;"
                +line+tab+"}"+line;
        //方法
        String methodContent = "";
        for(Method method:declaredMethods){
            //返回值
            String returnTypeName = method.getReturnType().getSimpleName();
            //方法名
            String methodName = method.getName();
            // Sting.class String.class 參數類型
            Class<?>[] parameterTypes = method.getParameterTypes();
            String argsContent = "";
            String paramsContent="";
            int flag = 0;
            for(Class args :parameterTypes){
                //String,參數類型
                String simpleName = args.getSimpleName();
                //String p0,Sting p1,
                argsContent+=simpleName+" p"+flag+",";
                paramsContent+="p"+flag+",";
                flag++;
            }
            if (argsContent.length()>0){
                argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);
                paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);
            }
            methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") {"+line
                    //增強方法先寫死
                    +tab+tab+"System.out.println(\"log\");"+line;
            if(returnTypeName.equals("void")){
                methodContent+=tab+tab+"target."+methodName+"("+paramsContent+");"+line
                        +tab+"}"+line;
            }else{
                methodContent+=tab+tab+"return target."+methodName+"("+paramsContent+");"+line
                        +tab+"}"+line;
            }
        }
        content+=packageContent+importContent+clazzFirstLineContent+filedContent
                +constructorContent+methodContent+"}";
        //生成Java文件
        File file = new File("D:\\com\\$Proxy.java");
        try{
            if(!file.exists()){
                file.createNewFile();
            }
            //創建
            FileWriter wr = new FileWriter(file);
            wr.write(content);
            wr.flush();
            wr.close();
            //編譯Java文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

            StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
            Iterable units = fileMgr.getJavaFileObjects(file);

            JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
            t.call();
            fileMgr.close();
            //new --> 反射
            URL[] urls = new URL[]{new URL("file:D:\\\\")};
            //加載外部文件
            URLClassLoader classLoader = new URLClassLoader(urls);
            Class<?> proxyClass = classLoader.loadClass("com.$Proxy");
            Constructor constructor = proxyClass.getConstructor(targetInf);
            //構造方法創建實例
            proxy = constructor.newInstance(target);
            //clazz.newInstance();//根據默認構造方法創建對象
            //Class.forName()
        }catch (Exception e){
            e.printStackTrace();
        }
        return proxy;
    }
}

  自定義代理的缺點:生成Java文件;動態編譯文件;需要一個URLClassLoader;涉及到了IO操作,軟件的最終性能體現到了IO操作,即IO操作影響到軟件的性能;

案例:

  1)接口:

public interface UserDao {
     void query();
     void query(String name);
     String getName(String id);
}

  2)實現類:

public class UserService implements UserDao {
    @Override
    public void query() {
        System.out.println("query");
    }

    @Override
    public void query(String name) {
        System.out.println(name);
    }

    @Override
    public String getName(String id) {
        System.out.println("id:"+id);
        return "李四";
    }
}

  3)測試:

public static void main(String[] args) {
    UserDao dao = (UserDao) ProxyUtil.newInstance(new UserService());
    dao.query();
    dao.query("張三");
}
--------結果--------
log
query
log
張三

          

package com;
import com.hrh.dynamicProxy.dao.UserDao;
public class $Proxy implements UserDao{//自定義動態代理生成的文件
    private UserDao target;
    public $Proxy (UserDao target){
        this.target =target;
    }
    public String getName(String p) {
        System.out.println("log");
        return target.getName(p);
    }
    public void query(String p) {
        System.out.println("log");
        target.query(p);
    }
    public void query() {
        System.out.println("log");
        target.query();
    }
}

  JDK動態代理:基於反射實現,通過接口反射得到字節碼,然後將字節碼轉成class,通過一個native(JVM實現)方法來執行;

  案例實現:實現 InvocationHandler 接口重寫 invoke 方法,其中包含一個對象變量和提供一個包含對象的構造方法;

public class MyInvocationHandler implements InvocationHandler {
    Object target;//目標對象
    public MyInvocationHandler(Object target){
        this.target=target;
    }
    /**
     * @param proxy 代理對象
     * @param method 目標對象的目標方法
     * @param args    目標方法的參數
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("log");
        return method.invoke(target,args);
    }
}
public class MyInvocationHandlerTest {
    public static void main(String[] args) {
        //參數: 當前類的classLoader(保證MyInvocationHandlerTest當前類可用)
        //         接口數組:通過接口反射得到接口裏面的方法,對接口裏面的所有方法都進行代理
        //         實現的InvocationHandler:參數是目標對象
        UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(MyInvocationHandlerTest.class.getClassLoader(),
                new Class[]{UserDao.class},new MyInvocationHandler(new UserService()));
        jdkproxy.query("query");
        //-----結果------
        //log
        //query
    }
}

  下面查看底層JDK生成的代理類class:

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class});

        try {
            FileOutputStream fileOutputStream = new FileOutputStream("xxx本地路徑\\$Proxy18.class");
            fileOutputStream.write(bytes);
            fileOutputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

  或在代碼的最前面添加下面代碼:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "xxx本地路徑");

  代理類class反編譯後的內容:下面的內容驗證了JDK動態代理爲什麼基於聚合(接口)來的,而不能基於繼承來的?因爲JDK動態代理底層已經繼承了Proxy,而Java是單繼承,不支持多繼承,所以以接口來實現;

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.hrh.dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy18 extends Proxy implements UserDao {
    private static Method m1;
    private static Method m4;
    private static Method m5;
    private static Method m2;
    private static Method m0;
    private static Method m3;

    public $Proxy18(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void query(String var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void query() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String getName(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("com.hrh.dao.UserDao").getMethod("query", Class.forName("java.lang.String"));
            m5 = Class.forName("com.hrh.dao.UserDao").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.hrh.dao.UserDao").getMethod("getName", Class.forName("java.lang.String"));
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

  CGLIB代理:藉助asm(一個操作字節碼的框架)實現代理操作;CGLIB基於繼承來的(前文有CGLIB代理類class的反編譯可以看出);

public class UserService {
    public void query(){
        System.out.println("query");
    }

    public static void main(String[] args) {
        // 通過CGLIB動態代理獲取代理對象的過程
        Enhancer enhancer = new Enhancer();
        // 設置enhancer對象的父類
        enhancer.setSuperclass(UserService.class);
        // 設置enhancer的回調對象
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("before method run...");
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("after method run...");
                return result;
            }
        });
        //創建代理對象
        UserService bean = (UserService) enhancer.create();
        bean.query();
    }
}
//-----------結果------
before method run...
query
after method run...

  PS:如果只是對項目中一個類進行代理,可以使用靜態代理,如果是多個則使用動態代理;

  關於代理的其他相關知識介紹可參考前文:Spring(11) - Introductions進行類擴展方法Spring筆記(3) - SpringAOP基礎詳解和源碼探究

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章