12_Spring-AOP

动态代理原理

在这里插入图片描述


AOP

  • Aspect Oriented Programming
  • 面向切面编程,是指通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
    • 面向切面编程:通过切入点指定容器中的组件中的方法,按照通知的方式做增强
  • AOP是OOP(面向对象编程)的延续
  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

AOP和OOP

  • OOP 面向对象编程,是Object Oriented Programming的简称
  • OOP:通过继承来增强
  • AOP:通过切面来增强

AOP的特点

  • AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
  • Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类(委托类)织入增强代码
  • SpringAOP使用的是JDK动态代理和CGlib动态代理
    • 如果有实现接口,使用的就是JDK动态代理,如果没有实现接口使用的就是CGlib动态代理

AOP的应用场景

  • 这场景通常是多次反复出现而又相对来说比较繁琐的部分
    • 事务管理、性能监视、安全检查、缓存、日志等

AOP的编程术语

  • Target:目标类 (需要被代理的类,委托类)
  • Proxy:代理类 (动态代理生成的)
  • JoinPoint:连接点,指被代理对象里那些可能会被增强的点(方法)如所有方法(候选的可能被增强候选点)
    • 获取增强过程中的信息
    • 比如可以获取Proxytargetmethodargs等信息
  • PointCut:切入点,已经被增强的连接点。
    • 获取指定增强的方法
    • AOP的目标 → 容器中的组件能够增强
    • 谁做增强(能够细化到方法级别)→ Pointcut切入点圈定增强范围 做记号
    • 做什么样的增强 → Advice通知 → 指导切入点指定的方法做一个什么样的增强
  • Advice:通知(具体的增强的代码)。代理对象执行到Joinpoint所做的事情。
  • Aspect:切面,是切入点和通知的结合切面是一个特殊的面:一个切入点和一个通知组成一个特殊的面
  • weaver:织入(植入)是指把advice应用到目标对象来创建新的代理对象的过程

AOP的实现

  1. 动态代理
    • 在这里插入图片描述
  2. SpringAOP
    • 在这里插入图片描述
    • Spring官方文档提供的方式,但是它建议你使用另一种方式AspectJ
  3. AspectJ

SpringAOP

在容器中注册3个组件:委托类组件、通知组件、代理组件(ProxyFactoryBean)

eg:

  • 使用注解注册委托类组件和通知组件
@Service
public class UserServiceImpl implements UserService{
    @Override
    public void sayHello(String s) {
        System.out.println("hello" + s);
    }
}
// 计算方法的执行时间
@Component
// 组件id customAdvice
public class CustomAdvice implements MethodInterceptor {
    /**
     * 相当于之前的InvocationHandler的方法
     */

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        // 记录开始时间
        long start = System.currentTimeMillis();

        // 执行委托类方法
        // 类似于动态代理的method.invoke
        Object proceed = methodInvocation.proceed();

        // 记录结束时间
        long end = System.currentTimeMillis();

        // 计算消耗时间
        long cost = end - start;
        System.out.println(methodInvocation.getMethod().getName() + "执行时间为:" + cost);

        return proceed;
    }
}
  • 通过代理组件注册代理组件
@Configuration
@ComponentScan("com.coo1heisenberg.demo2")
public class AppConfiguration {

    @Bean
    public ProxyFactoryBean userServiceProxy(UserService userService) {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();

        // 告知其委托类组件有哪些
        proxyFactoryBean.setTarget(userService);

        // 告知其通知组件有哪些 -> 提供的是通知组件的名称、id
        proxyFactoryBean.setInterceptorNames("customAdvice");
        // beanFactory.getBean("customAdvice");

        // 它里面的getObject会根据提供的值生成代理对象
        return proxyFactoryBean;
    }
}
  • 单元测试要从容器中指定代理组件取出
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfiguration.class)
public class SpringAOPTest {
    @Autowired
    @Qualifier("userServiceProxy")
    UserService userService;

    @Test
    public void testProxy() {
        userService.sayHello("zs");
    }
}

AspectJ

切面组件
  • 增加AspectJ的注解开关 → 配置类上增加一个注解@EnableAspectJAutoProxy
  • 把组件标记为切面组件@Aspect

eg:

  • 增加AspectJ的注解开关
@Configuration
@ComponentScan("com.coo1heisenberg.demo3")
@EnableAspectJAutoProxy //AspectJ注解使用的开关
public class AppConfiguration {
}
  • 把组件标记为切面组件
@Component
@Aspect
public class CustomAspect {
}
切入点表达式
  • 引入Aspectjweaver的依赖
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>
  • 在切面组件中使用@Pointcut注解
    • value属性:切入点表达式
    • 方法名:作为切入点id
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Pointcut {
    String value() default "";

    String argNames() default "";
}
  1. execution
@Component
@Aspect
public class CustomAspect {


    /**
     * execution(修饰符 返回值 包名 + 类名 + 方法名(形参))
     */
   @Pointcut(value = "execution(public void com.coo1heisenberg.demo3.service.UserServiceImpl.sayHello(java.lang.String))")

    /**
     * 可以增强多个方法
     *
     * 越具体,匹配范围越小,越宽泛,匹配范围越广
     * 通配符:
     *  - 修饰符:
     *      - 可以省略不写,如果省略不写代表任意修饰符
     *
     *  - 返回值:
     *      - 不能省略,但是可以使用通配符 * ,* 代表任意值
     *      - 如果是引用类型,要写全限定类名;全限定类名也可以出现通配符 *
     *
     *  - 包名、类名、方法名:
     *      - 可以使用 * (任意单词的一部分)来通配,也可以使用 .. (代表一部分值省略)来通配
     *      - 头和尾的位置不能使用.. ,但是可以使用 *
     *
     *  - 形参:
     *      - 省略不写,代表无参方法
     *      - 可以使用 * (任意单个类型的参数)来通配
     *      - 可以使用 .. (代表任意参数,数量任意,类型也任意)来通配
     *      - 如果是引用类型,要写全限定类名,全限定类名中也可以使用 通配符*
     */
    public void pointcut1() {}
}

  1. @annotation
    • @annotation(自定义注解的全限定类名)
    • 该注解写在哪个方法上,哪个方法就被划入到切入点范围内
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation {
}
@Component
@Aspect
public class CustomAspect {
    /**
     * 在容器中的组件中找包含特定注解的方法
     */
    @Pointcut("@annotation(com.coo1heisenberg.demo3.anno.MethodAnnotation)")
    public void pointcut() {

    }
}
  1. @target
    • @annotation(自定义注解的全限定类名)
    • 该注解写在类上,该类下所有的方法都划入到切入点的范围内
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeAnnotation {
}
@Component
@Aspect
public class CustomAspect {
    /**
     * 在容器中的组件中找包含特定注解的方法
     */
    @Pointcut("@annotation(com.coo1heisenberg.demo3.anno.MethodAnnotation)")
    public void pointcut1() {

    }

    /**
     * 在容器中的组件中找到包含特定注解的组件,特定的注解是自己定义的
     */
    @Pointcut("@target(com.coo1heisenberg.demo3.anno.TypeAnnotation)")
    public void pointcut2() {
        
    }
}

总结所有的切入点都是为了找特定的方法的


AspectJ切面
  • 在切面类中配置切面组件和通知方法
  • 切入点和通知要成对出现
@Component
@Aspect
public class CustomAspect {
    @Pointcut("execution(public void com.coo1heisenberg.demo3.service.UserServiceImpl.sayGoodBye(java.lang.String))")
    public void pointcut1() {

    }


    /**
     * before通知
     * 返回值:void
     * 方法名:任意去写
     * 形参:joinPoint连接点 (可写可不写)
     *
     */
    /**
     * 切入点和通知要成对出现
     */
    @Before("pointcut1()")
    public void before() {
        System.out.println("在委托类方法执行之前");
    }

    /**
     * after通知
     * 返回值:void
     * 方法名:任意去写
     * 形参:joinPoint连接点 (可写可不写)
     *
     */
    /**
     * 相当于try-catch中的finally,它是一定会执行到的
     */
    @After("pointcut1()")
    public void after() {
        System.out.println("在委托类方法执行之后");
    }

    /**
     * afterReturning通知
     * 返回值:void
     * 方法名:任意去写
     * 形参:Object(委托类方法的执行结果)
     *
     * 获得委托类方法返回的结果
     * 在return之后执行的
     */
    @AfterReturning(value = "pointcut1()", returning = "result")
    public void afterReturning(Object result) {
        System.out.println("after returning : " + result);
    }

    /**
     * afterThrowing通知
     * 返回值:void
     * 方法名:任意去写
     * 形参:Exception/Throwable(委托类方法执行过程中抛出异常)
     *
     * 能够获得委托类方法抛出的异常
     */

    @AfterThrowing("pointcut1()")
    public void afterThrowing() {
        System.out.println("after throwing : ");
    }

    /**
     * around通知
     * 返回值:Object,就是代理对象的返回结果
     * 方法名:任意去写
     * 形参:ProceedingJoinPoint连接点 (必须写) → 提供了proceed方法,执行的是委托类的代码
     *
     * around通知可以包含before、after、afterReturning、afterThrowing部分
     */
    /**
     * 类似于前面InvocationHandler的invoke,类似于MethodInterceptor的invoke
     */

    @Around("pointcut1()")
    public Object around(ProceedingJoinPoint joinPoint) {
        // before的部分
        System.out.println("before部分");
        Object proceed = null; 
        try {
            proceed = joinPoint.proceed(); // 执行委托类方法,委托类方法的返回值是proceed
            
            // 这里执行afterReturning
            System.out.println("afterReturning部分");
        } catch (Throwable e) {
            // afterThrowing
            System.out.println("afterThrowing部分");
            e.printStackTrace();
        } finally {
            // after部分
            System.out.println("after部分");
        }
        
        return proceed;
    }
}


JoinPoint连接点
  • 获取增强过程中的一些值
    • Signature 方法
    • Arguments 参数
    • This 代理对象
    • Target 委托类对象

eg:

@Before("mypointcut()")
public void before(JoinPoint joinPoint){
    //Signature 方法的描述
    //This 代理对象
    //Target 委托类对象
    //Arguments 参数
    Signature signature = joinPoint.getSignature();
    Object proxy = joinPoint.getThis();
    Object target = joinPoint.getTarget();
    Object[] args = joinPoint.getArgs();

    System.out.println("signature:" + signature.getName());
    System.out.println(proxy.getClass().getName());
    System.out.println(target.getClass().getName());
    System.out.println(Arrays.asList(args));
}

机制

在这里插入图片描述

相关推荐

  1. spring aop

    2024-04-09 11:54:06       38 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-09 11:54:06       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-09 11:54:06       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-09 11:54:06       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-09 11:54:06       18 阅读

热门阅读

  1. 什么是物理服务器?

    2024-04-09 11:54:06       13 阅读
  2. 链表的头插,尾插,逆序 and 合并两个有序表

    2024-04-09 11:54:06       13 阅读
  3. [C语言]二分查找

    2024-04-09 11:54:06       17 阅读
  4. restic备份

    2024-04-09 11:54:06       14 阅读
  5. [23年蓝桥杯] 买二赠一

    2024-04-09 11:54:06       12 阅读
  6. git使用

    git使用

    2024-04-09 11:54:06      12 阅读
  7. git 的使用,及其基本指令。

    2024-04-09 11:54:06       12 阅读
  8. go interface{} 作为函数参数

    2024-04-09 11:54:06       12 阅读
  9. 1006 换个格式输出整数

    2024-04-09 11:54:06       13 阅读