只有掌握了这三种代理模式,才能进军Spring AOP哦!

栏目: 编程工具 · 发布时间: 5年前

内容简介:首先我们来看看代理模式:所谓代理模式,是指客户端(Client)并不直接调用实际的对象(下图右下角的RealSubject),而是通过调用代理(ProxySubject),来间接的调用实际的对象。代理模式的使用场合,一般是由于客户端不想直接访问实际对象,或者访问实际的对象存在技术上的障碍,因而通过代理对象作为桥梁,来完成间接访问。

首先我们来看看代理模式:

只有掌握了这三种代理模式,才能进军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诊断工具,不懂的可以看看这篇文章http://www.dblearn.cn/article/5,用它就可以线上反编译代码。

这里我们添加一个断点:

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,那么就不会被拦截,即不会执行目标对象额外的业务方法.

以上所述就是小编给大家介绍的《只有掌握了这三种代理模式,才能进军Spring AOP哦!》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

About Face 2.0

About Face 2.0

Alan Cooper、Robert M. Reimann / Wiley / March 17, 2003 / USD 35.00

First published seven years ago-just before the World Wide Web exploded into dominance in the software world-About Face rapidly became a bestseller. While the ideas and principles in the original book......一起来看看 《About Face 2.0》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具