美文网首页Java架构技术栈程序员IT@程序员猿媛
只有掌握了这三种代理模式,才能进军Spring AOP!

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

作者: 若丨寒 | 来源:发表于2019-04-03 00:18 被阅读3次

    代理模式定义

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

    所谓代理模式,是指客户端(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();
        }
    }
    

    运行结果:


    如此,一个静态代理类就创建好了,我们可以专注在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();
        }
    

    运行结果如下:

    如图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


    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();
        }
    

    运行结果:


    我们看到已经成功代理了。但是结果有乱码出现,此处设置一个// 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 UserServiceImplEnhancerByCGLIB3ca8cfc3 extends UserServiceImpl

    可以看到生成的代理类继承了目标对象,因此有两个注意点:

    1. 目标对象不能处理被final关键字修饰,因为被final修饰的对象是不可继承的。
    2. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

    最后

    大家觉得不错可以点个赞在关注下,以后还会分享更多文章!同时我的的专栏:Java架构技术栈,以后还会分享更多文章!

    相关文章

      网友评论

        本文标题:只有掌握了这三种代理模式,才能进军Spring AOP!

        本文链接:https://www.haomeiwen.com/subject/ijypbqtx.html