一、概述
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...
實現原理:
-
獲取代理對象的引用,獲取被代理對象的接口
-
JDK代理重新生成一個類,同時實現被代理對象所實現的接口
-
獲取被代理對象的引用
-
動態生成Class字節碼
- 編譯後加載到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則無需特定的編譯器處理。