只有掌握了這三種代理模式,才能進軍Spring AOP!

代理模式定義

首先我們來看看代理模式:

只有掌握了這三種代理模式,才能進軍Spring AOP!

所謂代理模式,是指客戶端(Client)並不直接調用實際的對象(下圖右下角的RealSubject),而是通過調用代理(ProxySubject),來間接的調用實際的對象。代理模式的使用場合,一般是由於客戶端不想直接訪問實際對象,或者訪問實際的對象存在技術上的障礙,因而通過代理對象作為橋樑,來完成間接訪問。

業務場景

首先有個UserService接口,接口裡有一個添加用戶的方法

public interface UserService {
void addUser();
}

這是它的實現類

public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加一個用戶");
}
}

現在需要在添加用戶的時候記錄一下日誌。當然,你可以直接在addUser裡面直接寫添加日誌的代碼,

 public void addUser() {
System.out.println("添加一個用戶");
System.out.println("拿個小本本記一下");
}

但是Java推崇單一職責原則,如果這樣寫就違背了這個原則,我們需要將添加日誌的代碼解耦出來,讓addUser()方法專注寫自己的業務邏輯。

靜態代理

根據類圖,創建一個靜態代理類

public class UserStaticProxy implements UserService{
private UserService userService;
public UserStaticProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser() {
userService.addUser();
System.out.println("拿個小本本記錄一下");
}
}

我們建立一個測試類來測試靜態代理:

public class Test {
public static void main(String[] args) {
UserStaticProxy userStaticProxy = new UserStaticProxy(new UserServiceImpl());
userStaticProxy.addUser();
}
}

運行結果:

只有掌握了這三種代理模式,才能進軍Spring AOP!

如此,一個靜態代理類就創建好了,我們可以專注在Service寫業務邏輯,添加日誌等非業務邏輯交給這個靜態代理類來完成。

靜態代理的缺點

缺點一:接口增加方法,代理類需要同步維護

隨著業務擴大,UserService類裡不知有addUser方法,還有updateUser、deleteUser、batchUpdateUser、batchDeleteUser等方法,這些方法都需要記錄日誌。

UserServiceImpl類如下:

public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加一個用戶");
}
@Override
public void updateUser() {
System.out.println("更新一個用戶");
}
@Override
public void deleteUser() {
System.out.println("刪除一個用戶");
}
@Override
public void batchUpdateUser() {
System.out.println("批量更新用戶");
}
@Override
public void batchDeleteUser() {
System.out.println("批量刪除用戶");
}
}

那麼對應的靜態代理類如下:

public class UserStaticProxy implements UserService{
private UserService userService;
public UserStaticProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser() {
userService.addUser();
System.out.println("拿個小本本記錄一下");
}
@Override
public void updateUser() {
userService.updateUser();
System.out.println("拿個小本本記錄一下");
}
@Override
public void deleteUser() {
userService.deleteUser();
System.out.println("拿個小本本記錄一下");
}
@Override
public void batchUpdateUser() {
userService.batchUpdateUser();
System.out.println("拿個小本本記錄一下");
}
@Override
public void batchDeleteUser() {
userService.batchDeleteUser();
System.out.println("拿個小本本記錄一下");
}
}

從上面我們可以看到,代理類裡有很多重複的日誌代碼。因為代理類和目標對象實現同一個接口,一旦接口增加方法,代理類也得同步增加方法並且得同步增加重複的額外功能代碼,增大了代碼量

缺點二:接口越多,導致代理類繁多

如果需要增加業務類,如StudentService,TeacherService等等,這些類裡的方法也都需要實現增加日誌的方法,那麼就需要同步創建對應的代理類。此外靜態代理類不是自動生成的,需要在編譯之前就編寫好的,如果業務越來越龐大,那麼創建的代理類越來越多,這樣又增大了代碼量

如何解決這些缺點呢?這時候就需要動態代理方法了

JDK動態代理

其實動態代理和靜態代理的本質是一樣的,最終程序運行時都需要生成一個代理對象實例,通過它來完成相關增強以及業務邏輯,只不過靜態代理需要硬編碼的方式指定,而動態代理支持運行時動態生成這種實現方式。

JDK本身幫我們實現了動態代理,只需要使用newProxyInstance方法:

 public static Object newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)

注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次為:

  • ClassLoader loader,:指定當前目標對象使用類加載器
  • Class>[] interfaces,:代理類需要實現的接口列表
  • InvocationHandler h:調用處理程序,將目標對象的方法分派到該調用處理程序

代碼示例:

public class DynamicProxy implements InvocationHandler {
private Object target; // 目標對象
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
System.out.println("拿個小本本記錄一下");
return result;
}
}

上文的invoke方法,負責增強目標對象的方法,接口類的所有方法都會走這個invoke方法。另外bind方法簡單封裝了JDK的代理方法newProxyInstance,負責返回接口類。

測試類:

 public static void main(String[] args) {
DynamicProxy dynamicProxy = new DynamicProxy();
UserService userService = (UserService)dynamicProxy.bind(new UserServiceImpl());
userService.addUser();
userService.updateUser();
}

運行結果如下:

只有掌握了這三種代理模式,才能進軍Spring AOP!

如圖UserService接口裡的所有方法都已經加上了日誌邏輯了,此外,我們看一下UserDynamicProxy這個類裡的target屬性是Object類型的。所以,這個動態代理的方法同樣可以給其他Service複用。可以這樣調用:

DynamicProxy dynamicProxy = new DynamicProxy();
TeacherService teacherService = (TeacherService)dynamicProxy.bind(new TeacherServiceImpl());

綜上,動態代理解決了靜態代理的缺點

用arthas查看JDK動態代理生成的類

動態代理是運行時候動態生成代理類的,這個類放在內存中,我們要怎麼才能看到這個類呢?

artias是阿里開源的一個牛逼閃閃的Java診斷工具。

這裡我們添加一個斷點:

public static void main(String[] args) throws IOException {
DynamicProxy dynamicProxy = new DynamicProxy();
UserService userService = (UserService)dynamicProxy.bind(new UserServiceImpl());
userService.addUser();
userService.updateUser();
System.in.read();
}

運行 arthas

只有掌握了這三種代理模式,才能進軍Spring AOP!

jad命令反編譯,java生成的代理類都在com.sun.proxy目錄下。因此反編譯命令如下

jad com.sun.proxy.$Proxy0

package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.UserService;
public final class $Proxy0
extends Proxy
implements UserService {
private static Method m1;
private static Method m6;
private static Method m2;
private static Method m7;
private static Method m0;
private static Method m3;
private static Method m4;
private static Method m5;
public final void addUser() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void updateUser() {
try {
this.h.invoke(this, m4, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}

}
public final void deleteUser() {
try {
this.h.invoke(this, m5, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void batchUpdateUser() {
try {
this.h.invoke(this, m6, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void batchDeleteUser() {
try {
this.h.invoke(this, m7, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m6 = Class.forName("proxy.UserService").getMethod("batchUpdateUser", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m7 = Class.forName("proxy.UserService").getMethod("batchDeleteUser", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("proxy.UserService").getMethod("addUser", new Class[0]);
m4 = Class.forName("proxy.UserService").getMethod("updateUser", new Class[0]);
m5 = Class.forName("proxy.UserService").getMethod("deleteUser", new Class[0]);

return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}

由上面的代碼可以看到我們的代理類已經生成好了,沒當我們調用方法如 addUser(),實際分派到h變量的invoke方法上執行:

this.h.invoke(this, m3, null);

h變量是什麼呢?其實就是我們實現了InvocationHandler的DynamicProxy類。

cglib動態代理

通過觀察上面的靜態代理和JDK動態代理模式,發現要求目標對象實現一個接口,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口。這時候要怎麼處理呢?下面引出大名鼎鼎的CGlib動態代理

cglib代理,也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展。

要用cglib需要引入它的jar包,因為spring已經集成了它,因此引入spring包即可

編寫代理類:

public class CGLibProxy implements MethodInterceptor {
private Object target; // 目標對象
public Object bind(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
//設置父類
enhancer.setSuperclass(this.target.getClass());
//設置回調函數
enhancer.setCallback(this);
//創建子類(代理對象)
return enhancer.create();
}
@Override

public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("拿個小本本記錄一下");
return result;
}
}

其中,Enhancer需要設置目標對象為父類(因為生成的代理類需要繼承目標對象)

測試類:

 public static void main(String[] args) throws IOException {
CGLibProxy cgLibProxy = new CGLibProxy();
UserServiceImpl userService = (UserServiceImpl)cgLibProxy.bind(new UserServiceImpl());
userService.addUser();
userService.updateUser();
System.in.read();
}

運行結果:

只有掌握了這三種代理模式,才能進軍Spring AOP!

我們看到已經成功代理了。但是結果有亂碼出現,此處設置一個// TODO,我猜測是Spring對CGlib再封裝導致的,也請知道的大大回答一下。

用arthas查看cglib動態代理生成的類

步驟和JDK代理類雷同,只不過cglib的代理類生成在和測試類同一個包下,由於代碼太多,只上部分代碼

package com.example.demo.proxy;
import com.example.demo.proxy.UserServiceImpl;
import java.lang.reflect.Method;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3
extends UserServiceImpl
implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$deleteUser$0$Method;
private static final MethodProxy CGLIB$deleteUser$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$addUser$1$Method;
private static final MethodProxy CGLIB$addUser$1$Proxy;
private static final Method CGLIB$updateUser$2$Method;
private static final MethodProxy CGLIB$updateUser$2$Proxy;
private static final Method CGLIB$batchUpdateUser$3$Method;
private static final MethodProxy CGLIB$batchUpdateUser$3$Proxy;
private static final Method CGLIB$batchDeleteUser$4$Method;
private static final MethodProxy CGLIB$batchDeleteUser$4$Proxy;
private static final Method CGLIB$equals$5$Method;
private static final MethodProxy CGLIB$equals$5$Proxy;
private static final Method CGLIB$toString$6$Method;

private static final MethodProxy CGLIB$toString$6$Proxy;
private static final Method CGLIB$hashCode$7$Method;
private static final MethodProxy CGLIB$hashCode$7$Proxy;
private static final Method CGLIB$clone$8$Method;
private static final MethodProxy CGLIB$clone$8$Proxy;
public final void deleteUser() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$deleteUser$0$Method, CGLIB$emptyArgs, CGLIB$deleteUser$0$Proxy);
return;
}
super.deleteUser();
}
public final void addUser() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$addUser$1$Method, CGLIB$emptyArgs, CGLIB$addUser$1$Proxy);
return;
}
super.addUser();
}
public final void updateUser() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$updateUser$2$Method, CGLIB$emptyArgs, CGLIB$updateUser$2$Proxy);
return;
}
super.updateUser();
}

其中

public class UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3 extends UserServiceImpl

可以看到生成的代理類繼承了目標對象,因此有兩個注意點:

  1. 目標對象不能處理被final關鍵字修飾,因為被final修飾的對象是不可繼承的。
  2. 目標對象的方法如果為final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法.


分享到:


相關文章: