手寫Spring AOP實現

一、概述

Spring的最根本的使命就是簡化開發。體現在:基於POJO的輕量級和最小侵入性編程,通過DI和麪向接口實現鬆耦合,基於切面和慣性聲明式編程,通過切面和模板減少樣板代碼。

Spring是面向Bean進行編程的,Spring提供了IOC容器通過配置文件或者註解的方式來管理對象之間關係。

Spring的注入方式:Setter、構造方法、強制賦值。

控制反轉的兩種方式是依賴注入和依賴查找,最早Spring是兩種都包含的,但是使用依賴查詢的頻率過低,就被Spring團隊移除了。依賴注入的基本思想就是不創建對象,而是描述創建對象的方式,在代碼中不直接與對象和服務連接,但在配置文件中描述何種組件需要哪些服務。IOC容器將會負責聯繫這一切。

AOP的核心思想就是解耦,重點就是制定出切面(關注點、方面)之間組合的規則。只要制定出組合的規則,那麼各個模塊就可以組合,因此AOP就是面向規則編程。

在Spring中AOP思想的體現:權限認證、日誌、事務、懶加載、上下文處理、錯誤跟蹤、緩存處理。

二、Spring AOP

(1)Java JDK Proxy

首先創建一個接口:

public interface MyInterface {
    void doSomething();
}

然後爲這個接口創建實現類,這個類就是被代理的目標:

public class MyInterfaceImpl implements MyInterface {
    @Override
    public void doSomething() {
       System.out.println("Method: doSomething is running...");
    }
}

然後實現代理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxyObject implements InvocationHandler {
    private MyInterface target;
    public Object getInstance(MyInterface target) throws Exception {
       this.target = target;
       Class clazz = target.getClass();
       return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    @Override
    public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
       System.out.println("before...");
       this.target.doSomething();
       System.out.println("after...");
       return null;
    }
}

在Main方法中調用:

public class Main {
    public static void main(String[] args) throws Exception {
       MyInterface i = (MyInterface)new MyProxyObject().getInstance(new MyInterfaceImpl());
       i.doSomething();
    }
}

結果:

before... 
Method: doSomething is running...
after...

實現原理:

  1. 獲取代理對象的引用,獲取被代理對象的接口

  2. JDK代理重新生成一個類,同時實現被代理對象所實現的接口

  3. 獲取被代理對象的引用

  4. 動態生成Class字節碼

  5. 編譯後加載到JVM

(2)自定義動態代理實現

使用JDK的動態代理,無非就是定義接口,再使用類實現接口,這個類就是被代理對象。然後創建代理對象,代理對象類實現InvocationHandler接口,重寫invoke方法。

在使用動態的代理方法的時候通過代理對象獲取一個新的對象,這個對象可以強轉爲被代理對象類型,因此可以調用原先的方法。不過這個方法已經在invoke中做了手腳。

實現自己的InvocationHandler:

import java.lang.reflect.Method;

public interface MyInvocationHandler {
   public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable;
}

實現自己的代理工具類,主要功能就是生成源代碼並且加載到Java虛擬機:

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 生成代理對象的源代碼
public class MyProxy {
    public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) throws Exception {

        // 1. 生成源代碼
        String sourceCode = genrateSourceCode(interfaces);

        // 2. 將源代碼保存到磁盤
        String path = MyProxy.class.getResource("").getPath();
        FileWriter writer = new FileWriter(new File(path + "$Proxy0.java"));
        writer.write(sourceCode);
        writer.flush();
        writer.close();

        // 3. 編譯成爲字節碼文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> javaFileObjects = manager.getJavaFileObjects(new File(path + "$Proxy0.java"));
        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, javaFileObjects);
        task.call();
        manager.close();

        // 4. 加載字節碼到Java虛擬機
        Class<?> proxyClass = loader.findClass("$Proxy0");

        // 5. 返回被代理後的對象
        Constructor<?> constructor = proxyClass.getConstructor(MyInvocationHandler.class);
        return constructor.newInstance(h);
    }

    private static String genrateSourceCode(Class<?>[] interfaces) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("package com.bmm.custom;\n");
        buffer.append("\n");
        buffer.append("import java.lang.reflect.Method;\n");
        buffer.append("\n");
        buffer.append("\n");
        StringBuffer interfaceNames = new StringBuffer();
        int classNumber = interfaces.length;
        for (int i = 0; i < classNumber - 1; i++) {
            interfaceNames.append(interfaces[i].getName());
            interfaceNames.append(", ");
        }
        interfaceNames.append(interfaces[classNumber - 1].getName());
        interfaceNames.append(" ");
        interfaceNames.append("{\n");
        buffer.append("public final class $Proxy0 extends MyProxy implements ");
        buffer.append(interfaceNames);
        buffer.append("\n");
        buffer.append("\tMyInvocationHandler handler;\n");
        buffer.append("\n");
        buffer.append("\tpublic $Proxy0(MyInvocationHandler handler) {\n");
        buffer.append("\t\tthis.handler = handler;\n");
        buffer.append("\t}\n\n");

        for (Class clazz : interfaces) {
            for (Method method : clazz.getMethods()) {
                buffer.append("\tpublic " + method.getReturnType().getName() + " " + method.getName() + "() {\n");
                buffer.append("\t\ttry{\n");
                buffer.append("\t\t\tMethod method = " + clazz.getName() + ".class.getMethod(\"" + method.getName() + "\", new Class[]{});\n");
                buffer.append("\t\t\tthis.handler.invoke(this, method, null);\n");
                buffer.append("\t\t} catch(Throwable t) {\n");
                buffer.append("\t\t\tt.printStackTrace();\n");
                buffer.append("\t\t}\n");
                if (!"void".equals(method.getReturnType().getName())) {
                    buffer.append("\t\treturn null;\n");
                }
                buffer.append("\t}\n\n");
            }
        }

        buffer.append("}\n");
        return buffer.toString();
    }
}

實現自己的類加載器:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {
    private File file;

    public MyClassLoader() {
        String path = MyClassLoader.class.getResource("").getPath();
        this.file = new File(path);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if (this.file != null) {
            File classFile = new File(this.file, name.replaceAll("\\.", "/") + ".class");
            if (classFile.exists()) {
                FileInputStream in = null;
                try {
                    in = new FileInputStream(classFile);
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = in.read(buffer)) != -1) {
                        out.write(buffer, 0, len);
                    }
                    out.flush();
                    return defineClass(className, out.toByteArray(), 0, out.size());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (in != null) {
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return null;
    }
}

使用相同的方式使用自定義的動態代理:

/* TestProxyObject.java */
import java.lang.reflect.Method;

import com.bmm.custom.MyClassLoader;
import com.bmm.custom.MyInvocationHandler;
import com.bmm.custom.MyProxy;

public class TestProxyObject implements MyInvocationHandler {
   private TestInterface target;

   public Object getInstance(TestInterface target) throws Exception {
      this.target = target;
      Class clazz = target.getClass();
      return MyProxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this);
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
      System.out.println("before...");
      this.target.test();
      System.out.println("after...");
      return null;
   }
}

/* TestInterface.java */
public interface TestInterface {
   void test();
   String echo();
}

/* TestInterfaceImpl.java */
public class TestInterfaceImpl implements TestInterface {

   @Override
   public void test() {
      System.out.println("Method: test is running...");
   }

   @Override
   public String echo() {
      return "Method: echo is running...";
   }
}

/* Main.java */
public class Main {
    public static void main(String[] args) throws Exception {
        TestInterface i = (TestInterface) new TestProxyObject().getInstance(new TestInterfaceImpl());
        i.test();
    }
}

所以代理模式歸根到底就是字節碼重組。

(3)CGLib動態代理

工程添加cglib-nodep-3.3.0.jar

創建代理對象:

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

import java.lang.reflect.Method;

public class MyProxyObject implements MethodInterceptor{

    public Object getInstance(Class clazz) throws Exception {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before...");
        methodProxy.invokeSuper(o, objects);
        System.out.println("after...");
        return null;
    }
}

創建被代理對象:

public class MyObject {
    public void test() {
        System.out.println("Method: test is running...");
    }
}

使用CGLib代理:

public class Main {
    public static void main(String[] args) throws Exception {
        MyObject myObject = (MyObject) new MyProxyObject().getInstance(MyObject.class);
        myObject.test();
    }
}

JDK的動態代理通過接口來進行強轉,生成的代理對象可以強轉爲接口。CGLib的動態代理通過生成一個被代理對象的子類,然後重寫父類方法實現,生成的對象可以強轉爲被代理對象。動態代理關注過程。CGLib基於ASM和字節碼編輯技術。

Spring AOP的動態代理實現主要是JDK動態代理和CGLib動態代理。

  • JDK動態代理只提供接口的代理,不支持類的代理。核心InvocationHandler接口和Proxy類,InvocationHandler 通過invoke()方法反射來調用目標類中的代碼,動態地將橫切邏輯和業務編織在一起;接着,Proxy利用 InvocationHandler動態創建一個符合某一接口的的實例, 生成目標類的代理對象。
  • 如果代理類沒有實現 InvocationHandler 接口,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成指定類的一個子類對象,並覆蓋其中特定方法並添加增強代碼,從而實現AOP。CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記爲final,那麼它是無法使用CGLIB做動態代理的。
  • 靜態代理與動態代理區別在於生成AOP代理對象的時機不同,相對來說AspectJ的靜態代理方式具有更好的性能,但是AspectJ需要特定的編譯器進行處理,而Spring AOP則無需特定的編譯器處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章