3.4-AOP

优质
小牛编辑
125浏览
2023-12-01

《手写简易的 Tomcat 服务器》的基础上,介绍 Spring 的实现原理。

1. Spring AOP 的使用方法

1.1 PointCut Expression

切面应用于哪些方法。

1.1.1 各种 designators(指示器) 的区别

通过什么方法去匹配。

需求指示器
匹配方法@execution
匹配注解/TYPE@target
@args
匹配注解/TYPE@within
匹配注解/METHOD@annotation
匹配包/类型within
匹配对象this
bean
target
匹配参数args

1.1.2 通配符

通配符功能
*匹配任意数量的字符。
+匹配指定类及其子类。
..匹配任意数量的子包或参数。

1.1.3 Operators(运算符)

运算符功能
&&与运算符。
\\或运算符。
!非运算符。

1.1.4 匹配包/类型(within)

@Pointcut("within(com.iecas.soundsystem.aop.service.ProductService)")
public void matchType(){}

@Pointcut("within(com.iecas.soundsystem.aop.service..*)")
public void matchPackage(){}

1.1.5 匹配注解

@Pointcut("@target(com.iecas.soundsystem.aop.anno.NeedSecured)")
public void matchAnnotationType(){}

@Pointcut("@annotation(com.iecas.soundsystem.aop.anno.NeedSecured)")
public void matchAnnotationMethod(){}

@Pointcut("@args(com.iecas.soundsystem.aop.anno.NeedSecured)")
public void matchAnnotationArgs(){}

1.1.6 匹配方法

@Pointcut("execution(* com.iecas.soundsystem.aop.service.ProductService.exDemo())")
public void matchMethod(){}

1.1.7 匹配参数

@Pointcut("args(Long))")
public void matchArgs(){}

1.1.8 匹配对象

@Pointcut("bean(*Service))")
public void matchBean(){}

@Pointcut("this(com.iecas.soundsystem.aop.service.ProductService))")
public void matchThis(){}

@Pointcut("target(com.iecas.soundsystem.aop.service.IService))")
public void matchTarget(){}

1.2 5种 Advice 以及参数和结果的绑定

注解功能
Before前置通知。
After(Finally)后置通知,方法执行完之后。
AfterReturning返回通知,方法执行之后。
AfterThrowing异常通知,抛出异常之后。
Around环绕通知
    @Before("matchTarget()")
    public void before(){
        System.out.println("Before:匹配 ProductService 类里面的所有方法");
    }

    @After("matchTarget()")
    public void after(){
        System.out.println("After:匹配 ProductService 类里面的所有方法");
    }


    @AfterReturning(value = "matchTarget()",returning = "returnValue")
    public void afterReturning(String returnValue){
        System.out.println("AfterReturning:匹配 ProductService 类里面的所有方法");
        System.out.println("返回值:"+returnValue);
    }

    @AfterThrowing(value = "matchTarget()")
    public void afterThrowing(){
        System.out.println("AfterThrowing:匹配 ProductService 类里面的所有方法");
    }

2. Spring AOP 的实现原理解析

2.1 概述

大纲

2.1.2 织入的时机

  • 编译时织入(AspectJ)
  • 类加载时织入(AspectJ 5+)
  • 运行时织入(Spirng AOP)

    2.2 设计模式

    2.2.1 代理模式

  • 静态代理模式

  • 动态代理模式

2.2.2 责任链模式

2.3 实现

2.3.1 基于 JDK 的实现

2.3.2 基于 Cglib 的实现

3. Spring AOP经典代码解读

3.2 安全校验 @PreAuthorize

url 被指定的用户访问。

1. 反射

1.1 原理

思考题1:有哪些方法可以在运行时生成一个 Java 类。 答案:

  • 方法1 利用 Java 程序生成一段代码,用 ProcessBuilder 之类启动 javac 进程,指定上面的生成文件作为输入,进行编译。最后,利用类加载器,在运行时进行加载即可。
  • 方法2 利用字节码操纵工具和类库生成字节码。

    1.2 实现方式

    1.2.1 获取 Class 对象的三种实现方式

  • 调用对象的 getClass 方法
  • 调用类的 class 属性
  • 调用 forName(全限定类名)

    1.3 应用

    1.3.1 同名属性批量复制

    copyProperties(Object source,Class<T> clazz){
       //获取源对象的所有属性名称。
       Field[] sourceFileds=source.getClass.getField();
       for(f:sourceFields){
            map.put(f.getName(),f.get(source));
       }
    }
    

    2. 动态代理

    2.1 实现方式

    2.1.1 jdk

  • 定义接口。
    public interface Hello {
      void sayHello();
    }
    
  • 被代理类实现接口,重写接口方法。
    public class HelloImpl implements Hello {
      @Override
      public void sayHello() {
          System.out.println("Hello World");
      }
    }
    
  • 实现 InvocationHandler 接口,重写 invoke 方法。

    public class MyInvocationHandler implements InvocationHandler {
      //传入被代理的目标对象。
      private Object target;
      public MyInvocationHandler(Object target) {
          this.target = target;
      }
    
      // target 为被代理对象,args 包含类加载器信息和接口信息。
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          System.out.println("Before:"+method.getName());
          Object result=method.invoke(target,args);
          System.out.println("After:"+method.getName());
          return result;
      }
    }
    
  • 在应用中使用 Proxy 静态类的 newProxyInstance 方法创建增强类,调用增强类的方法。
    public class MyDynamicProxyTest {
      public static void main(String[] args) {
          HelloImpl hello=new HelloImpl();
          //传入 handler 对象
          MyInvocationHandler handler=new MyInvocationHandler(hello);
          Hello proxyHello=(Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(),
                  HelloImpl.class.getInterfaces(),handler);
          proxyHello.sayHello();
      }
    }
    
    思考2:如果 interface 中定义了两个方法呢,如何对针对方法使用不同切面。 答案:被代理类的需要实现两个方法,hadler 中调用 Method 参数对 getName 方法区分不同方法。 思考3:使用 jdk 提供的代理机制,被代理类需要继承接口,对源代码侵入,有没有更加优雅的方法。答案:cglib。
    public class MyInterceptor implements MethodInterceptor {
      public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
          // object 表示要拦截的对象。
          // method 表示要增强的方法。
          // args 表示参数列表。
          // methodProxy 表示代理的方法。
          //invokeSuper方法表示对被代理对象方法的调用。
          System.out.println("Before:"+method.getName());
          Object result=methodProxy.invokeSuper(object,args);
          System.out.println("After:"+method.getName());
          return result;
      }
    }
    
    public class MyInterceptorTest {
      public static void main(String[] args) {
          MyInterceptor interceptor=new MyInterceptor();
          Enhancer enhancer=new Enhancer();
          enhancer.setSuperclass(HelloImpl.class);
          enhancer.setCallback(interceptor);
          HelloImpl hello=(HelloImpl) enhancer.create();
          hello.sayHello();
      }
    }
    

参考资料