• AOP随笔


    AOP: Aspect-Oriented Programming 面向切面编程。

    首先明确一个点:AOP是一个概念。那么对于一个概念,其实现方式多种多样,分为静态AOP、动态AOP,而对于动态AOP的实现又分为动态代理和动态字节码增强实现。

    这里分享的AOP主要是基于Spring的AOP实现。

    AOP的一些基础概念

    • 连接点(Joinpoint):程序执行的某个特定位置,比如方法调用前、方法调用后、方法抛出异常后等,这些点都可以是连接点。

    • 切点(PointCut):一个程序有若干个连接点,并不是所有的连接点都是需要关注的,需要我们关注的那部分连接点就是切点。

    • 通知(增强)(Advice):就是在切点处要插入的逻辑;分为前置、后置、异常、返回、环绕通知;

    • 切面(Advice):切点和通知组合在一起形成切面,对应的切面横切整个应用程序。切面实现了横切关注点的模块化。

    • 目标对象(Target):需要被增强的业务对象。

    • 织入(Weaving):将通知增强添加到目标对象的具体连接点的过程。具体来说,就是生成代理对象并将切面融入到业务流程的过程。

    • 代理类(Proxy):一个类在被织入器织入增强逻辑后产生的一个类就是代理类。

    上面这些点在Spring中的体现就是一个一个的注解。

    Spring AOP的使用方式

    比如我们现在有一个除法计算类,这个方法计算可能会抛错,在抛错的时候我们需要记录一下日志,那么使用方式如下:

    (说明:很多地方在介绍AOP的时候,都喜欢以日志来当做案例,但是日志记录并不是一个好的AOP使用场景,因为系统中各个日志记录点和记录格式不具有通用性。)

    • 首先定义实现方法:

      public class MathCalculator {
      
          public int div(int i, int j) {
              return i / j;
          }
      }
      
    • 然后定义切面:

      @Aspect
      public class LogAspects {
      
          @Pointcut("execution(public int MathCalculator.*(..))")
          public void poinCut() {
          }
      
          @Around("poinCut()")
          public Object logArround(ProceedingJoinPoint joinPoint) throws Throwable {
              System.out.println("环绕开始!");
              try {
                  return joinPoint.proceed();
              } catch (Exception e) {
                  System.out.println("环绕异常!");
                  throw e;
              } finally {
                  System.out.println("环绕结束!");
              }
          }
      }
      

    我们在切面中定义了一个环绕通知,这样就对MathCalculator的div方法调用进行了环绕处理。

    Spring AOP的实现方式

    上面提到动态AOP有两种实现方式:动态代理和动态字节码增强。

    在Spring中主要是通过动态代理实现的:基于JDK的动态代理和基于CGLIB的动态代理。

    要了解Spring AOP的实现原理,只要把上面两个技术了解后就能明白相应的原理了。

    JDK动态代理

    • 1、JDK动态代理是基于接口的,所以我们首先来创建一个接口

      /**
       * 抽象主题接口
       **/
      public interface Subject {
      
          void doSomething();
      }
      
    • 2、创建对应接口的实现类

      /**
       * 真实主题类
       **/
      public class RealSubject implements Subject {
          @Override
          public void doSomething() {
              System.out.println("doSomething....");
          }
      }
      
    • 3、创建代理类,实现 java.lang.reflect.InvocationHandler 接口

      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      
      /**
       * jdkd动态代理类
       **/
      public class JDKDynamicProxy implements InvocationHandler {
      
          private Object target;
      
          public JDKDynamicProxy(Object target) {
              this.target = target;
          }
      
          /**
           * 获取被代理接口实例对象
           */
          public <T> T getProxy() {
              return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
          }
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              System.out.println("Do something before");
              Object result = method.invoke(target, args);
              System.out.println("Do something after");
              return result;
          }
      }
      
    • 4、创建测试类

      import com.lnjecit.proxy.dynamic.jdk.JDKDynamicProxy;
      
      /**
       * 测试类
       **/
      public class Ceshi {
          public static void main(String[] args) {
              Subject subject = new JDKDynamicProxy(new RealSubject()).getProxy();
              subject.doSomething();
          }
      }
      

    像这样运行Ceshi类的main方法就能发现对应的doSomething()方法被我们的逻辑进行了拦截。

    • 5、动态代理原理

      我们打开生成的代理类,可以看到内容如下:

      import com.lnjecit.proxy.Subject;
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      import java.lang.reflect.UndeclaredThrowableException;
      
      public final class $Proxy0 extends Proxy implements Subject {
          private static Method m1;
          private static Method m3;
          private static Method m2;
          private static Method m0;
      
          public $Proxy0(InvocationHandler var1) throws  {
              super(var1);
          }
          
          public final void doSomething() throws  {
              try {
                  super.h.invoke(this, m3, (Object[])null);
              } catch (RuntimeException | Error var2) {
                  throw var2;
              } catch (Throwable var3) {
                  throw new UndeclaredThrowableException(var3);
              }
          }
          
          // 为了精简代码,下面省略了重写的 equals、hashCode、toString、toString 方法
      
          static {
              try {
                  m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                  m3 = Class.forName("com.lnjecit.proxy.Subject").getMethod("doSomething");
                  m2 = Class.forName("java.lang.Object").getMethod("toString");
                  m0 = Class.forName("java.lang.Object").getMethod("hashCode");
              } catch (NoSuchMethodException var2) {
                  throw new NoSuchMethodError(var2.getMessage());
              } catch (ClassNotFoundException var3) {
                  throw new NoClassDefFoundError(var3.getMessage());
              }
          }
      }
      

      可以看到,动态生成的代理类有如下特性:

      1、继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

      2、提供了一个使用InvocationHandler作为参数的构造方法。

      3、生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。

      4、重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。

      5、代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。

      上面的InvocationHandler就是我们实现横切逻辑的地方,它是横切逻辑的载体。

    CGLIB动态代理

    CGLIB使用如下:

    • 1、定义代理目标类

      public class UserService {
          public String getUserName(Long userId) {
              System.out.println("获取用户名..");
              return "user" + userId;
          }
      }
      
    • 2、实现MethodInterceptor,定义事务拦截器

      public class TransactionInterceptor implements MethodInterceptor {
          Object target;
      
          public TransactionInterceptor(Object target) {
              this.target = target;
          }
      
          /**
           * proxy:代理对象,CGLib动态生成的代理类实例
           * method:目标对象的方法,上文中实体类所调用的被代理的方法引用
           * args:目标对象方法的参数列表,参数值列表
           * methodProxy:代理对象的方法,生成的代理类对方法的代理引用
           */
          @Override
          public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
              System.out.println("开启事务..." + proxy.getClass().getSimpleName());
              Object objValue = null;
              try {
                  // 反射调用目标类方法
                  objValue = method.invoke(target, args);
                  System.out.println("返回值为:" + objValue);
              } catch (Exception e) {
                  System.out.println("调用异常!" + e.getMessage());
              } finally {
                  System.out.println("调用结束,关闭事务...");
              }
              return objValue;
          }
      
          /**
           * 获取代理实例
           */
          public Object getTargetProxy() {
              // Enhancer类是cglib中的一个字节码增强器,它可以方便的为你所要处理的类进行扩展
              Enhancer eh = new Enhancer();
              // 1.将目标对象所在的类作为Enhancer类的父类
              eh.setSuperclass(target.getClass());
              // 2.通过实现MethodInterceptor实现方法回调
              eh.setCallback(this);
              // 3. 创建代理实例
              return eh.create();
          }
      }
      
    • 3、使用

      public static void main(String[] args) {
          // 1. 创建目标实例
          UserService userService = new UserService();
          // 2. 创建事务拦截器
          TransactionInterceptor transactionInterceptor = new TransactionInterceptor(userService);
          // 3. 创建代理实例
          UserService userServiceProxy = (UserService) transactionInterceptor.getTargetProxy();
          // 4. 使用代理实例调用目标方法
          userServiceProxy.getUserName(6L);
      }
      

    说明:CGLIB是基于继承来实现的。它首先创建一个类,继承代理目标类,然后重写其中的方法,然后在重写的方法中将调用委托给目标对象,然后在委托调用前后实现横切逻辑。

    通过上面两个例子可以看出,JDK的动态代理不是通过继承实现的,那么就是通过实现接口实现的。而CGLIB则是通过动态生成代理类的子类来实现的。

  • 相关阅读:
    一张图,理解JAVA体系结构、运行机制、JVN运行机制、Java平台(初学)
    高效的CSS代码(2)
    高效的CSS代码(1)
    hibernate的报错信息a different object with the same identifier value was already associated with the session解决办法
    从tomcat下载文件的配置方法(很全呢)
    mysql中对于时间的处理,时间的滚动,求时间间隔,切换时区等等
    分享一个在js中判断数据是undefined,NaN,null,的技巧
    Java中Date类型如何向前向后滚动时间,( 附工具类)
    如何让tomcat服务器运行在80端口,并且无需输入项目名即可访问项目()
    前端的字符串时间如何自动转换为后端Java的Date属性,介绍springMVC中如何解决时间转换问题
  • 原文地址:https://www.cnblogs.com/mrxiaobai-wen/p/14805015.html
Copyright © 2020-2023  润新知