【Spring教程】详解AOP的实现原理(动态代理)

2022-05-12 08:49:56 浏览数 (1)

文章目录
  • 一、简介
  • 二、名称解释
    • 1、切面(Aspect)
    • 2、连接点(Joinpoint)
    • 3、切点(Pointcut)
    • 4、通知(Advice)
    • 5、通知器(Advisor)
    • 6、代理(Proxy)
  • 三、静态代理和动态代理的区别
  • 四、静态代理实例
  • 五、aop的实现原理(动态代理)
    • 1、JDK动态代理
    • 控制台输出:
    • 2、Cglib动态代理
    • 控制台输出:
  • 六、两种动态代理方式区别

一、简介

  AOP是Aspect-Oriented Programming,即面向切面编程。   它是一种新的模块化机制,用来描述分散在对象/类或函数中的横切关注点。分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过切面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好地管理起来。

二、名称解释

1、切面(Aspect)

  切面由切点和通知组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

2、连接点(Joinpoint)

  连接点是在应用执行过程中能够插入切面(Aspect)的一个点。程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。

3、切点(Pointcut)

  切点是指通知(Advice)所要织入(Weaving)的具体位置。每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点。所以切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。AOP通过“切点”定位特定的连接点。   具体举个例子:比如开车经过一条高速公路,这条高速公路上有很多个出口(连接点),但是我们不会每个出口都会出去,只会选择我们需要的那个出口(切点)开出去。   简单可以理解为,每个出口都是连接点,但是我们使用的那个出口才是切点。每个应用有多个位置适合织入通知,这些位置都是连接点。但是只有我们选择的那个具体的位置才是切点。

4、通知(Advice)

  它定义在连接点做什么,为切面增强提供织入接口。例如,日志记录、权限验证、事务控制、性能检测、错误信息检测等。   Spring切面可以应用5种类型的通知:   前置通知(Before):在目标方法被调用之前调用通知功能;   后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;   返回通知(After-returning):在目标方法成功执行之后调用通知;   异常通知(After-throwing):在目标方法抛出异常后调用通知;   环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

5、通知器(Advisor)

  完成对目标方法的切面增强设计和关注点的设计以后,需要一个对象把它们结合起来,完成这个作用的就是Advisor。

6、代理(Proxy)

  它为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

三、静态代理和动态代理的区别

1、静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。 2、静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。 3、静态代理,在程序运行前,代理类的.class文件就已经存在了;动态代理,在程序运行时,运用反射机制动态创建而成。

四、静态代理实例

1、举一个手机缴话费的例子,TelecomOperator 类是服务类。

代码语言:javascript复制
package com.service;

/**
 * 定义一个电信运营商接口
 */
public interface TelecomOperator {
    //查询话费余额
    public void queryPhoneBal();

    //缴话费
    public void payPhoneBal();
}

2、TelecomOperatorImpl是实现类

代码语言:javascript复制
package com.controller;

import com.service.TelecomOperator;

public class TelecomOperatorImpl implements TelecomOperator {
    //查询话费余额
    @Override
    public void queryPhoneBal(){
        System.out.println("查话费方法...");
    }

    //缴话费
    @Override
    public void payPhoneBal(){
        System.out.println("缴话费方法...");
    }
}

3、TelecomOperatorProxy是服务代理类

代码语言:javascript复制
package com.controller;

import com.service.TelecomOperator;

/**
 * 第三方代理商
 *
 */
public class TelecomOperatorProxy implements TelecomOperator {
    private TelecomOperatorImpl telecomOperator;

    public TelecomOperatorProxy(TelecomOperatorImpl telecomOperator) {
        this.telecomOperator = telecomOperator;
    }

    //查询话费余额
    @Override
    public void queryPhoneBal(){
        System.out.println("切点:事务控制/日志输出");
        telecomOperator.queryPhoneBal();
        System.out.println("切点:事务控制/日志输出");
    }

    //缴话费
    @Override
    public void payPhoneBal(){
        System.out.println("切点:事务控制/日志输出");
        telecomOperator.payPhoneBal();
        System.out.println("切点:事务控制/日志输出");
    }
}

4、TelecomOperatorTest是测试类

代码语言:javascript复制
package com.controller;

public class TelecomOperatorTest {
    public static void main(String[] args) {
        TelecomOperatorImpl telecomOperator = new TelecomOperatorImpl();
        TelecomOperatorProxy proxy = new TelecomOperatorProxy(telecomOperator);
        proxy.queryPhoneBal();
      	proxy.payPhoneBal();
    }
}

5、控制台输出

代码语言:javascript复制
切点:事务控制/日志输出
查话费方法...
切点:事务控制/日志输出
切点:事务控制/日志输出
缴话费方法...
切点:事务控制/日志输出

五、aop的实现原理(动态代理)

1、JDK动态代理

代码语言:javascript复制
package com.controller;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK动态代理类
 */
public class TelecomOperatorJDKProxy implements InvocationHandler {
    private Object target;

    //返回代理对象
    public Object newProxy(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    /**
     * @param obj 目标对象代理类的实例
     * @param method 代理实例上调用父类方法的Method实例
     * @param args 代入到代理实例上方法参数值的数组
     */
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable{
        Object result = null;
        System.out.println("切点:事务控制/日志输出");
        result=method.invoke(target,args);
        System.out.println("切点:事务控制/日志输出");
        return result;
    }
}
代码语言:javascript复制
package com.controller;

import com.service.TelecomOperator;

public class TelecomOperatorJDKTest {
    public static void main(String[] args) {
        TelecomOperatorJDKProxy proxy = new TelecomOperatorJDKProxy();
        TelecomOperator telecomOperator = (TelecomOperator)proxy.newProxy(new TelecomOperatorImpl());
        telecomOperator.queryPhoneBal();
    }
}

控制台输出:

代码语言:javascript复制
切点:事务控制/日志输出
查话费方法...
切点:事务控制/日志输出

2、Cglib动态代理

代码语言:javascript复制
package com.controller;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Cglib动态代理类
 */
public class TelecomOperatorCglibProxy implements MethodInterceptor {
    private Object target;//代理的目标对象

    //创建目标对象的代理对象
    public Object newProxy(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();//该类用于生成代理对象
        enhancer.setSuperclass(this.target.getClass());//设置父类
        enhancer.setCallback(this);//回调方法,设置回调对象为本身
        return enhancer.create();//创建代理对象
    }

    /**
     * @param obj 目标对象代理类的实例(增强过)
     * @param method 代理实例上调用父类方法的Method实例
     * @param args 代入到代理实例上方法参数值的数组
     * @param proxy 使用它调用父类的方法
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("切点:事务控制/日志输出");
        Object object = proxy.invokeSuper(obj, args);
        //Object object = proxy.invoke(target,args);
        System.out.println("切点:事务控制/日志输出");
        return object;
    }
}
代码语言:javascript复制
package com.controller;

public class TelecomOperatorCglibTest {
    public static void main(String[] args) {
        TelecomOperatorCglibProxy proxy = new TelecomOperatorCglibProxy();
        TelecomOperatorImpl telecomOperatorimpl = (TelecomOperatorImpl)proxy.newProxy(new TelecomOperatorImpl());
        telecomOperatorimpl.queryPhoneBal();
    }
}

控制台输出:

代码语言:javascript复制
切点:事务控制/日志输出
查话费方法...
切点:事务控制/日志输出

  invoke方法调用的对象(target)没有增强过,invokeSuper方法调用的对象(obj)已经是增强了的,所以会再走一遍 MyMethodInterceptor的interceptor方法,如果是个拦截器链条,就会重新在走一次拦截器链。如果使用invoke(obj,args)就会循环调用,造成死循环,并抛异常java.lang.StackOverflowError。

六、两种动态代理方式区别

  1、java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而Cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。   2、JDK动态代理只能对实现了接口的类生成代理,而不能针对类;Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final 。   3、Cglib一个目标类方法会生成两个代理方法,一个重写目标方法,并实现代理逻辑,还有一个直接调用目标类方法。

0 人点赞