Day4 AOP与动态代理

AOP

AOP(Aspect Oriented Programming, 面向切面编程),是Spring的两大核心之一。

AOP概述

问题引入

  1. 程序员小强维护着一千个方法,一天老板要求小强把这一千个方法都要加上事务代码(统一代码);
  2. 小强咬咬牙,添加了一个新的方法,然后让这一千个方法去调用这个事务的方法,解决了当前问题。
  3. 但是过了一段时间,老板又跑过来和小强说,这一千个方法又要调用日志打印的方法功能,同时又要添加用户合法性验证。

这个时候小强有两种选择:

  1. 对老板说:滚,然后闪酷跑路。
  2. 使用AOP技术来实现这个功能,以后老板随便加都可以轻松搞定。

什么是AOP?

######## 简单理解:方法增强。

AOP可以增强方法的功能,而不需要修改原业务代码。

######## 深入理解

  1. 从编程语言的角度:处理粒度不同
    AOP是对OOP的扩展,OOP(即面向对象编程)能处理的最大粒度是对象,对OOP而言,当需要增强方法的功能时,必须修改类的定义。而AOP能处理的粒度可以深入到对象内部,可以是方法或者字段(Spring暂不支持字段增强),所以AOP可以使我们不用重新定义类,而增强原方法。
  2. 从设计模式的角度:AOP进一步降低了模块间的耦合度
    使得业务代码和功能性代码分离,降低它们之间的耦合度。

功能性代码,如:事务处理、参数校验、日志、监控、负载均衡

  1. 从解放程序员生产力的角度:更少的代码
    AOP只需要少量的配置或者注解就可以完成
  2. 从代码可读性的角度:业务逻辑更清晰
    程序员可以更聚焦于业务逻辑。

AOP实现原理:动态代理

代理模式
代理模式是常用的java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。我们在访问实际对象时,是通过代理对象来访问的。

何为动态代理?
普通的代理模式(属于静态代理)需要我们定义相关的代理类,而动态代理则使用一些工具类帮我们在程序运行时,自动生成代理类。

AOP与动态代理
动态代理是AOP的一种实现方式,AOP也可以使用静态织入的方式实现。
Spring使用动态代理来实现AOP的。

回顾 IOC与 DI :DI是IOC的一种实现方式,IOC也可以使用DL的方式实现。
Spring使用DI来说实现IOC。IOC和AOP是Spring两个最核心的特性。

下面用一个简单的例子,分别使用非代理模式、普通代理模式和动态代理模式来实现。

有一个UserService接口和其实现类UserServiceImpl,现需要给该接口内的每一个方法添加一个功能(打印运行时间)。

非代理

非代理模式,则需要修改原实现类,或者重新定义一个实现类,来添加新的功能要求。
原接口和实现类:

public interface UserService {

    void getUser();
    void createUser();
    void updateUser();
    void deleteUser();
}

public class UserServiceImpl implements UserService {

    @Override
    public void getUser() {
        System.out.println("getUser");
    }

    @Override
    public void createUser() {
        System.out.println("createUser");
    }

    @Override
    public void updateUser() {
        System.out.println("updateUser");
    }

    @Override
    public void deleteUser() {
        System.out.println("deleteUser");
    }
}

定义一个新的实现类,打印时间和方法名:

public class UserServiceImpl2 implements UserService {

    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void getUser() {
        System.out.println("getUser");
        System.out.println(dateFormat.format(new Date()).toString() +  " Log: method getUser invoked.");
    }

    @Override
    public void createUser() {
        System.out.println("createUser");
        System.out.println(dateFormat.format(new Date()).toString() +  " Log: method createUser invoked.");
    }

    @Override
    public void updateUser() {
        System.out.println("updateUser");
        System.out.println(dateFormat.format(new Date()).toString() +  " Log: method updateUser invoked.");
    }

    @Override
    public void deleteUser() {
        System.out.println("deleteUser");
        System.out.println(dateFormat.format(new Date()).toString() +  " Log: method deleteUser invoked.");
    }
}

测试:

    public static void main(String[] args) {
        UserService userDao = new UserServiceImpl2();
        userDao.getUser();
        userDao.createUser();
        userDao.updateUser();
        userDao.deleteUser();
    }

结果:

getUser
2019-12-22 10:29:26: method getUser invoked
createUser
2019-12-22 10:29:26: method createUser invoked
updateUser
2019-12-22 10:29:26: method updateUser invoked
deleteUser
2019-12-22 10:29:26: method deleteUser invoked

代理模式

代理模式,也需要定义一个实现代理接口的类,但不需要重写原方法,而是组合一个被代理对象。
代理类:

public class UserServiceProxy implements UserService {

    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void getUser() {
        userService.getUser();
        System.out.println(dateFormat.format(new Date()).toString() +  " Log: method getUser invoked.");
    }

    @Override
    public void createUser() {
        userService.createUser();
        System.out.println(dateFormat.format(new Date()).toString() +  " Log: method createUser invoked.");
    }

    @Override
    public void updateUser() {
        userService.updateUser();
        System.out.println(dateFormat.format(new Date()).toString() +  " Log: method updateUser invoked.");
    }

    @Override
    public void deleteUser() {
        userService.deleteUser();
        System.out.println(dateFormat.format(new Date()).toString() +  " Log: method deleteUser invoked.");
    }
}

测试:

    public static void main(String[] args) {
        UserService userServiceOriginal = new UserServiceImpl();
        UserService userServiceProxy = new UserServiceProxy(userServiceOriginal);
        userServiceProxy.getUser();
        userServiceProxy.createUser();
        userServiceProxy.updateUser();
        userServiceProxy.deleteUser();
    }

结果:

getUser
2019-12-22 11:34:49 Log: method getUser invoked.
createUser
2019-12-22 11:34:49 Log: method createUser invoked.
updateUser
2019-12-22 11:34:49 Log: method updateUser invoked.
deleteUser
2019-12-22 11:34:49 Log: method deleteUser invoked.

JDK动态代理

JDK动态代理不需要重写实现类,也不需要定义一个实现UserService接口的代理类。而是使用Proxy类帮我们动态生成代理类。

public class UserServiceProxy {
        private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 返回动态代理对象
     * @param userService 被代理对象
     * @return 代理对象
     */
    public static UserService newProxyInstance(UserService userService) {
            /*
             * Proxy.newProxyInstance方法返回动态生成的代理类
             * 参数:
             *  userService.getClass().getClassLoader():被代理类的类加载器;
             *  userService.getClass().getInterfaces():被代理类的接口,重要!说明JDK动态代理是基于代理接口的。
             *  第三个参数定义了一个内部类,实现了代理逻辑,内部参数:
             *      o:代理类对象;
             *      method:被代理方法;
             *      objects:被代理方法的参数;
             */
            return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                    userService.getClass().getInterfaces(), (o, method, objects) -> {
                        // 调用被代理方法
                        Object ret = method.invoke(userService, objects);
                        // 编写增强功能
                        System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked");
                        return ret;
                    });
        }
}

测试:

    public static void main(String[] args) {
        // 保存动态生成的代理类的字节码
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        UserService userService = UserServiceProxy.newProxyInstance(new UserServiceImpl());
        userService.getUser();
        userService.createUser();
        userService.updateUser();
        userService.deleteUser();
    }

结果:

getUser
2019-12-22 11:48:44: method getUser invoked
createUser
2019-12-22 11:48:44: method createUser invoked
updateUser
2019-12-22 11:48:44: method updateUser invoked
deleteUser
2019-12-22 11:48:44: method deleteUser invoked

JDK 动态代理解析

JDK 动态代理类是在运行时生成的,默认不会保存到本地文件,除非设置了如下属性:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
动态生成的代理类路径为:"工程目录/aop/dynamic_proxy/cglibproxy/$Proxy0.class",$Proxy0就是代理类的类名。

JDK动态代理使用了反射,以getUser()方法为例,我们可以查看保存的代理类中,该方法定义为:

    public final void getUser() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

关键就一句:super.h.invoke(this, m3, (Object[])null);

  1. invoke就是我们定义的:
(o, method, objects) -> {
    // 调用被代理方法
    Object ret = method.invoke(userService, objects);
    // 编写增强功能
    System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked");
    return ret;
}
  1. m3在动态生成的代理类中定义为:
m3 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("getUser");

Cglib动态代理

public class UserServiceCglibProxy {

    private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 返回动态代理对象
     * @param userService 被代理对象
     * @return 代理对象
     */
    public static UserService newProxyInstance(UserService userService) {

        /*
         * Enhancer.create方法返回动态生成的代理类
         * 参数:
         *  userService.getClass():被代理类的类;
         *  第二个参数定义了一个内部类,实现了代理逻辑,内部参数:
         *      o:代理类对象;
         *      method:被代理方法;
         *      objects:被代理方法的参数;
         *      methodProxy:方法代理;
         */
        return (UserService) Enhancer.create(userService.getClass(), (MethodInterceptor) (o, method, objects, methodProxy) -> {
            // 调用被代理方法
            Object ret = methodProxy.invokeSuper(o, objects);
            // 编写增强功能
            System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked");
            return ret;
        });
    }
}

测试:

    public static void main(String[] args) {
        // 保存动态生成的代理类的字节码
//        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\code");
        UserService userService = UserServiceCglibProxy.newProxyInstance(new UserServiceImpl());
        userService.getUser();
        userService.createUser();
        userService.updateUser();
        userService.deleteUser();
    }

结果:

getUser
2019-12-22 12:10:01: method getUser invoked
createUser
2019-12-22 12:10:01: method createUser invoked
updateUser
2019-12-22 12:10:01: method updateUser invoked
deleteUser
2019-12-22 12:10:01: method deleteUser invoked

Cglib动态代理解析

Cglib生成的代理类也需要设置才能保存的本地:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\code");
Cglib动态生成的代码更多,更复杂。

以getUser()方法为例,我们可以查看保存的代理类中,该方法定义为:

    public final void getUser() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$getUser$0$Method, CGLIB$emptyArgs, CGLIB$getUser$0$Proxy);
        } else {
            super.getUser();
        }
    }

关键代码:var10000.intercept(this, CGLIB$getUser$0$Method, CGLIB$emptyArgs, CGLIB$getUser$0$Proxy);
其中:

  1. intercept就是我们定义的:
(o, method, objects, methodProxy) -> {
            // 调用被代理方法
            Object ret = methodProxy.invokeSuper(o, objects);
            // 编写增强功能
            System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked");
            return ret;
        }
  1. this:代理对象;
  2. CGLIB$getUser$0$Method
    代理方法,可以理解为就是UserServiceImpl中的getUser方法;
  3. CGLIB$emptyArgs:方法参数;
  4. CGLIB$getUser$0$Proxy
    一个MethodProxy类型的对象,可以避免使用方法反射。有没有使用方法反射是Cglib和JDK动态代理的区别之一。

二者区别

######## 简单理解

  1. 使用场景
  • JDK动态代理用于接口
    例如,我们要代理UserServiceImpl,则UserServiceImpl必须实现一个接口,例如UserService接口;
  • Cglib用于类或者接口
    例如,我们要代理UserServiceImpl,不需要关注该类是否实现接口。
  1. 效率
  • Java8之前,Cglib效率高;
  • Java8开始,JDK动态代理效率高;

######## 深入理解

  1. 为什么JDK动态代理必须要有接口,而Cglib则不需要
    这是由他们的实现方式决定的。
  • JDK动态代理生成类继承关系
    JDK动态代理生成的代码:
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m6;
    private static Method m0;
    private static Method m4;
    private static Method m5;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void getUser() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void createUser() throws  {
        try {
            super.h.invoke(this, m6, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void updateUser() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void deleteUser() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("getUser");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m6 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("createUser");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("updateUser");
            m5 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("deleteUser");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从代理类继承关系看:public final class $Proxy0 extends Proxy implements UserService
可以看到,$Proxy0继承了Proxy类,而Java只有单继承,为了使使用被代理类的地方,都可以透明地使用代理类,只能使得它们拥有共同的接口。所以JDK动态代理只支持接口。

  • Cglib代理类继承关系
public class UserDaoImpl$$EnhancerByCGLIB$$4a28906a extends UserDaoImpl implements Factory

可以看到代理类并没有继承其他类,而是继承了被代理的类。所以Cglib的方式不关注被代理类是否有接口。

  1. 效率问题
  • 实现方式不同带来的效率不同
    • JDK代理使用了反射,而在之前反射是很“重”的操作;
    • Cglib使用了映射,通过方法名查找到对应方法,然后调用,没有使用到反射;
  • 官方与非官方带来的优化差异
    • 虽然JDK代理使用了反射这个“重操作”,但它会随着官方对Java语言,对JVM,对JIT即时编译器的不断优化,而带来效率的巨大改进;
    • Cglib是非官方的实现,优化力度不够;
posted @ 2019-12-22 21:37  cheng_18  阅读(424)  评论(0编辑  收藏  举报