【基础原理】java代理模式和Spring AOP的基本逻辑

先通过了解代理模式的原理 ,希望知道动态代理是怎么实现的动态增强;
然后再具体看下spring AOP是如何使用动态代理实现的切面、织入等逻辑。

一. 代理模式

1. 代理的概念

代理模式是指client调用一个对象时,通过代理实例调用(并增强)此对象,而不是直接调用它。

 

2. 静态代理

看下静态代理的实现思路,为理解动态代理提供一些基础

 
实现的基本思路:

通过实现同一个接口(用于实现方法增强)+ 接口聚合到代理类中。如下代码:

//代理和被代理对象都要实现的接口
public interface ITeacherDao {
   void teach(); // 授课的方法
}

//被代理对象
public class TeacherDao implements ITeacherDao {
   @Override
   public void teach() {
      System.out.println(" 老师授课中  。。。。。");
   }
}

//静态代理对象
public class TeacherDaoProxy implements ITeacherDao {
    private ITeacherDao target; //聚合:被代理对象

    public TeacherDaoProxy(ITeacherDao target) {//面向接口编程:
        this.target = target;
    }

    @Override
    public void teach() {
        System.out.println("开始代理  完成某些操作。。。。。 ");//增强
        target.teach(); //被代理对象的方法
        System.out.println("提交。。。。。");//增强
    }
}


//client:将被代理类放入到代理类,然后通过创建代理类,来调用被代理对象的方法。
TeacherDao teacherDao = new TeacherDao();
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
teacherDaoProxy.teach();

小结:
实现逻辑其实不难,就是代理class、被代理class都实现一个接口,且代理class的方法实现了被代理对象的增强。

这里的增强需要注意:

  1. 使用了面向接口编程,即可以根据被代理class的不同,调用不同的被代理instance的方法;
  2. 增强:在执行时,会在被执行方法前后进行添加一些执行逻辑。

上面的逻辑其实解释了代理的基本要素:

  1. 被代理对象的方法是代理对象去调用,而不是自己调用
  2. 因为调用被代理,所以可以在被调用前后进行增强。

但静态代理的缺点也比较明显:每个被代理类都需要手动去实现代理类,如果需要代理的类比较多,那代码是比较臃肿的。

 

3. 动态代理

3.1. 概述

动态代理的代理类并不是在Java代码中定义的,而是在JVM运行时根据我们在Java代码中的“指示”动态生成的。
所以相比于静态代理,动态代理可以统一处理被代理类的所有方法,而不用修改每个代理类的方法。

 

3.2. 代码实现
//接口
public interface ITeacherDao {
   void teach(); 
   void sayHello(String name);
}

//目标类(被代理类)
public class TeacherDao implements ITeacherDao {
   @Override
   public void teach() {
      System.out.println(" 老师授课中.... ");
   }
   @Override
   public void sayHello(String name) {
      System.out.println("hello " + name);
   }
}

//代理工厂
public class ProxyFactory {
   private Object target;
   public ProxyFactory(Object target) {
      this.target = target;
   } 
   
   //给目标对象 生成一个代理对象
   public Object getProxyInstance() {
      return Proxy.newProxyInstance(
 target.getClass().getClassLoader(), //1. 类加载器:创建代理类时检查
 target.getClass().getInterfaces(),  //2. 接口:创建代理类要素1
            new InvocationHandler() {    //3. 代理逻辑:创建代理类要素2
               @Override
               public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                  System.out.println("JDK代理开始~~");
                  Object returnVal = method.invoke(target, args);
                  System.out.println("JDK代理提交");
                  return returnVal;                }
            });
   }
}
ITeacherDao target = new TeacherDao();
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();

proxyInstance.teach();
proxyInstance.sayHello(" tom ");

这里我们可以将invocationHandler看作是一个代理类,携带着被代理对象,在invoke方法中调用了被代理对象的方法,并可以在方法前后进行增强。

 

3.3. 源码理解

动态构建动态代理类的逻辑

我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,看下源码具体是怎么实现的:

//java.lang.reflect.Proxy
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
       ... 一些检查和校验

        final Class<?>[] intfs = interfaces.clone();


        //生成代理类:通过类加载器 + 代理接口
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
             ... 一些检查和校验

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //通过反射构造出来的构造器实现了动态代理对象
            return cons.newInstance(new Object[]{h});
        } catch 。。。
    }

 
源码的基本逻辑:

先通过 Class<?> cl = getProxyClass0(loader, intfs)获取到了代理class实例,然后通过类实例创建了构建器,进而实现了代理对象。
 
执行getProxyClass0方法时会动态的生成一个代理类文件,下面我们看一下类文件的具体内容:

//动态生成的代理类文件
//0表示生成的第一个代理类,如果有多个序号会依次递增
public final class $Proxy0 extends Proxy implements Person{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *这里是代理类的构造器,注意看入参数类型是InvocationHandler
  *
  *super(paramInvocationHandler),是调用父类Proxy的构造方法。
  *父类持有:protected InvocationHandler h;
  *Proxy构造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  */
  public $Proxy0(InvocationHandler paramInvocationHandler) throws {
    super(paramInvocationHandler);
  }
  

   static{
    try{
      //注意看:giveMoney通过反射得到方法,名字是m3
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch ...
  }
 
 /**
  *直接就调用了InvocationHandler中的invoke方法,并把m3传了进去,这里的m3其实就是被代理方法
  *看到这里大概知道了动态代理的本质是:通过InvocationHandler去调用被代理类的方法
  */
  public final void giveMoney() throws {
    try{
      this.h.invoke(this, m3, null);
      return;
    }catch ...
  }

  ... toString,hashCode、equals方法的内容。

}

 
小结一下上面反编译的class:

代理类的生成:

  • 首先$Proxy0是动态生成的代理类,0代表第一个代理类,如果有多个(被代理)实例,序号会依次递增;
  • 代理类中会生成相同签名的方法

代理调用:

  • 当执行方法时,会找到代理类中相同签名的方法,进行执行;
  • invoke执行:每个代理类中都有InvocationHandler,其实代理工作是他做的,即他持有被代理对象,通过invoke方法(如上例实践),增强并执行(被代理)实例方法。

 

二. Spring AOP中的代理原理

Spring AOP是基于代理实现的,默认为标准的 JDK 动态代理。参考上述动态代理的讲解(接口+对象的加载器),我们可以知道,spring可以使得任何接口(参考3.2 代理工厂)(或者接口的集合)可以被代理。

值得关注的是:

将被代理对象A交给代理对象B执行,B执行A方法时,可以在A方法执行前后进行增强,但并不能改造A对象方法内部

 

1. 一个简单的场景

有10个方法在执行前想进行一些校验(校验的逻辑都一致),方法执行完之后统一进行返回值的处理,统一返回给某处。

2. AOP概念分析

先看下对于上面的场景我们需要关注那些点,spring是怎么做到的:

  • 指定对哪些方法进行增强:切(入)点,程序中通过切入点表达式来表示切点;
  • 对这些方法怎么(前、后、围绕还是异常时)进行增强:Advice(通知)
  • 怎么组织切点和通知:通过切面

其此在程序运行过程中,对于目标方法spring这边通过JointPoint(连接点)去表示。

简单来说我们构建一个AOP逻辑:先要确定切点和通知,知道哪些方法要被拦截进行增强,当连接点要执行时,进行拦截增强,而这些逻辑都被切面进行组织。

 

3. 实操

@Aspect
@Component
@Slf4j
public class SchedulerAspect {

    @Resource
    private NacosService nacosService;

    @Pointcut("execution(* xxx.statictask.FlinkRefreshTaskDataScheduler.*(..) )&&" +
            " @annotation(org.springframework.scheduling.annotation.Scheduled)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object auditLogCut(ProceedingJoinPoint joinPoint) {
        Object result = null;
        if (!nacosService.isApiOldestInstance()) {
            return result;
        }
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            log.error(throwable.getMessage());
        }
        return result;
    }
}

简单描述下AOP的逻辑:

@Aspect 声明了一个切面逻辑,其中我们要进行增强的方法有:FlinkRefreshTaskDataScheduler类下所有添加了@Scheduled
注解的方法,通过@AroundauditLogCut
方法规定了增强的逻辑:当方法执行前判断此进程是否时nacos注册最早的节点,如果是我就允许你执行定时任务,如果不是则不能执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roman_日积跬步-终至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值