掌握Spring AOP(一)核心概念及动态代理

2022-01-08  本文已影响0人  云芈山人

一、什么是Aop?

  1. Aop与Oop一样,都是一种编程思想。
  2. Aop面向切换编程, 其实就是无侵入的进行功能增强,使用Aop可以实现业务代码和系统代码分离(如日志记录、权限控制、性能统计等通用功能)。
  3. Aop的实现方式主要有几种:AspectJ、Spring Aop、Spring整合AspectJ。
  4. Aop的核心思想,就是通过织入去增强代码,织入又分为静态织入和动态织入。

二、Aop的基本概念

Aop核心功能图解
Aop核心功能图解.png
  1. 目标对象
    被一个或多个切面增强的对象。
  2. 连接点(joinpoint)
    程序执行中的某个具体的执行点。
  3. 切入点(pointcut)
    对连接点的特征进行描述,可以用正则表达式。增强处理和一个切入点表达式相关联,并在与这个切入点匹配的某个连接点上运行。
  4. 代理对象
    由Aop框架所创建的对象,实现执行增强处理的方法。
  5. 织入(weave)
    将增强处理连接到应用程序中的类型或对象上的过程。
  6. 通知(advice)(增强功能)
    拦截到连接点之后要执行的代码,分为前置通知、后置通知、异常通知、最终通知、环绕通知五类。
  7. 切面(aspect)
    一个模块化的横切逻辑,可能会横切多个对象。
  8. 通知器
    是一个特殊的切面。
  9. 引入(introduction)
    在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

三、动态代理

为什么使用代理?

简单来说,代理就是帮【目标对象】去完成它应该做,但不想或不擅长做的事情。(如社保代缴,黄牛挂号等)

代理技术分为静态代理和动态代理。

为什么使用动态代理?

增强对象的功能。这种增强,符合开闭原则,不会对目标对象就行修改,只需要扩展就可以实现增强。

动态代理技术常用的两种(两个方式生成的代理类都是继承了Proxy):JDK动态代理技术、CGLib动态代理技术。

Spring默认使用的是JDK,可以人为指定使用CGLib.

其它区别:

  1. JDK1.7之前,CGLib运行比JDK要快,之后效率差不多,但是JDK产生代理对象的效率要高。
  2. CGLib底层是通过ASM字节码工具包去实现的字节码重写。而JDK只是相当于帮程序员在后台写了java文件,并编译、加载 。
JDK和CGLib产生代理对象的方式和处理步骤是怎么样的?
  1. 产生代理对象
    Proxy.newProxyInstance(classloader,interfaces,【InvocationHandler实现类】)
/**
 * 主要作用就是生成代理类 使用JDK的动态代理实现 它是基于接口实现的
 * 
 * @author think
 *
 */
public class JDKProxyFactory {

    /**
     * @return
     */
    public Object getProxy(Object target) {

        Class<?> clazz = UserService.class;
        Class<?> clazz2 = target.getClass();
        System.out.println(clazz);
        System.out.println(clazz2);
        System.out.println(clazz2.getInterfaces());
        System.out.println(target.getClass().getInterfaces());
        // 如何生成一个代理类呢?
        // 1、编写源文件(java文件)----目录类接口interface实现类(调用了目标对象的方法)
        // class Proxy4{

        // InvocationHandler
        // 目标对象
        // 目标对象的方法
        // void saveUer(){
        // 动态生成的
        // 需要自定义编写
        // InvocationHandler.invoke(){
        // 编写其他逻辑
        // 调用目标对象的方法
        // }

        // }
        // }
        // 2、编译源文件为class文件
        // 3、将class文件加载到JVM中(ClassLoader)
        // 4、将class文件对应的对象进行实例化(反射)

        // Proxy是JDK中的API类
        // 第一个参数:目标对象的类加载器
        // 第二个参数:目标对象的接口
        // 第三个参数:代理对象的执行处理器
//       Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[] { clazz2 },new MyInvocationHandler(target));
        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
                new MyInvocationHandler(target));
        return proxy;
    }

2.调用InvocationHandler去完成增强。

/**
 * JDK动态代理使用的动态增强的类
 * 
 * @author think
 *
 */
public class MyInvocationHandler implements InvocationHandler {

    // 目标对象的引用
    private Object target;

    // 通过构造方法将目标对象注入到代理对象中
    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    /**
     * 代理对象会执行的方法
     * 第一个参数:代理对象本身
     * 第二个参数:目标对象的方法对象(Method对象),一个方法针对一个Method对象
     * 第三个参数:目标对象的方法参数
     * 
     * 代理对象执行的逻辑:
     *      需要执行目标对象的原方法?
     *          如何执行目标对象的原方法呢?
     *          该处使用的是反射
     *          【要调用方法的Method对象.invoke(要调用方法的对象,要调用方法的参数)】
     *      只是在调用目标对象的原方法前边和后边可能要加上一些增强功能的代码
     * 
     * 增强代码比如:
     *      在原方法调用之前,开启事务,源方法结束之后,提交和回滚事务
     * 
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("这是jdk的代理方法");
        // 下面的代码,是反射中的API用法
        // 该行代码,实际调用的是[目标对象]的方法
        // 利用反射,调用[目标对象]的方法
        Object returnValue = method.invoke(target, args);
        // AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        
        // ReflectiveMethodInvocation.proceed()进行递归增强
        
        // 增强的部分
        return returnValue;
    }
}

3.生成的代理对象

public final class $Proxy4 extends Proxy implements Proxy4 {
    private static Method m1;
    private static Method m6;
    private static Method m2;
    private static Method m7;
    private static Method m11;
    private static Method m13;
    private static Method m0;
    private static Method m8;
    private static Method m12;
    private static Method m3;
    private static Method m5;
    private static Method m10;
    private static Method m4;
    private static Method m9;

    public $Proxy4(InvocationHandler var1) throws  {
        super(var1);
    }
   
    .......

     public final void saveUser() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m6 = Class.forName("com.sun.proxy.$Proxy4").getMethod("getInvocationHandler", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m7 = Class.forName("com.sun.proxy.$Proxy4").getMethod("getProxyClass", Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"));
            m11 = Class.forName("com.sun.proxy.$Proxy4").getMethod("getClass");
            m13 = Class.forName("com.sun.proxy.$Proxy4").getMethod("notifyAll");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m8 = Class.forName("com.sun.proxy.$Proxy4").getMethod("wait");
            m12 = Class.forName("com.sun.proxy.$Proxy4").getMethod("notify");
            m3 = Class.forName("com.sun.proxy.$Proxy4").getMethod("saveUser");
            m5 = Class.forName("com.sun.proxy.$Proxy4").getMethod("newProxyInstance", Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"), Class.forName("java.lang.reflect.InvocationHandler"));
            m10 = Class.forName("com.sun.proxy.$Proxy4").getMethod("wait", Long.TYPE);
            m4 = Class.forName("com.sun.proxy.$Proxy4").getMethod("isProxyClass", Class.forName("java.lang.Class"));
            m9 = Class.forName("com.sun.proxy.$Proxy4").getMethod("wait", Long.TYPE, Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4.测试

    @Test
    public void testJDKProxy() {

        // 1、创建目标对象
        UserService service = new UserServiceImpl();
        // 2、生成代理对象
        JDKProxyFactory proxyFactory = new JDKProxyFactory();
        // 得到代理对象
        UserService proxy = (UserService) proxyFactory.getProxy(service);

        // 生成class文件
        generatorClass(proxy);

        // 3、调用目标对象的方法
        service.saveUser();
        System.out.println("===============");
        // 4、调用代理对象的方法
        proxy.saveUser();
    }

enhancer.create

/**
 * 主要作用就是生成代理类 使用CGLib动态代理技术实现 它是基于继承的方式实现的
 * cglib底层是通过asm包去实现的字节码的编写
 * @author think
 *
 */
public class CgLibProxyFactory {

    /**
     * @param clazz
     * @return
     */
    public Object getProxyByCgLib(Class<?> clazz) {
        // 创建增强器
        Enhancer enhancer = new Enhancer();
        // 设置需要增强的类的类对象
        enhancer.setSuperclass(clazz);
        // 设置回调函数
        enhancer.setCallback(new MyMethodInterceptor());
        // 获取增强之后的代理对象
        Object object = enhancer.create();

        // generatorClass(enhancer);

        return object;
    }

    /*
     * private void generatorClass(Enhancer enhancer) { FileOutputStream out = null;
     * try { byte[] bs = DefaultGeneratorStrategy.INSTANCE.generate(enhancer);
     * FileOutputStream fileOutputStream = new FileOutputStream("Enhancer_cglib" +
     * ".class"); fileOutputStream.write(bs); fileOutputStream.flush();
     * fileOutputStream.close(); } catch (Exception e) { e.printStackTrace(); }
     * finally { if (out != null) { try { out.close(); } catch (IOException e) { //
     * TODO Auto-generated catch block } } }
     * 
     * }
     */
}

2.调用MethodInterceptor去完成增强。

/**
 * cglib动态代理使用的动态增强的类
 * 
 * @author think
 *
 */
public class MyMethodInterceptor implements MethodInterceptor {

    /***
     * Object proxy:这是代理对象,也就是[目标对象]的子类
     * Method method:[目标对象]的方法 
     * Object[] arg:参数
     * MethodProxy methodProxy:代理对象的方法
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
        // 因为代理对象是目标对象的子类
        // 该行代码,实际调用的是父类目标对象的方法
        System.out.println("这是cglib的代理方法");

        // 通过调用子类[代理类]的invokeSuper方法,去实际调用[目标对象]的方法
        // Object returnValue = method.invoke(target, arg);
        Object returnValue = methodProxy.invokeSuper(proxy, arg);
        
        //  method.invoke(target, arg);
        
        //目标对象的saveUser(){}
        //代理对象saveUser(){
        //super();
        //}
        
        // 代理对象调用代理对象的invokeSuper方法,而invokeSuper方法会去调用目标类的invoke方法完成目标对象的调用

        return returnValue;
    }

}

3.测试

    @Test
    public void testCgLibProxy() {
        // 创建目标对象
        UserService service = new UserServiceImpl();

        // 生成代理对象
        CgLibProxyFactory proxyFactory = new CgLibProxyFactory();
        UserService proxy = (UserService) proxyFactory.getProxyByCgLib(service.getClass());

        // 调用目标对象的方法
        service.saveUser();
        System.out.println("===============");
        // 调用代理对象的方法
        proxy.saveUser();
    }
使用代理对象增强功能的原理?

代理对象执行时,会调用InvocationHandler或者MethodInterceptor去完成增强。

代理对象执行

1.JDK

  InvocationHandler{
    target
    invoke(proxy,method,args){
        
        //调用目标对象(反射的API)
        method.invoke(target,args)
    }
  }

2.CGLib

  MethodInterceptor{
     
      intercept(proxy,method,args,proxyMethod){
        
        //调用目标对象(代理对象的API)
        //代理对象又会调用目标对象
        proxyMethod.invokeSuper(proxy,args)
        //method.invoke(target,args)
      }

  }
什么时候会调用以上的方法?

当代理对象被创建的时候,是不会调用以上的方法的。
当代理对象被访问其中方法的时候,才会调用以上两种(互斥)方法。

上一篇下一篇

猜你喜欢

热点阅读