AOP(Aspect Oriented Programming)面向切面编程与OOP (Object-Oriented Programming)面向对象编程在java中都占有非常重要的地位,Java是一个面向对象编程的语言,面向切面编程通过提供对程序结构不同的思维方式对OOP进行补充。对于OOP来说,最主要的模块单元是类,对于AOP来说是切面(aspect).这些切面使关注点模块化,例如跨多个类或对象、方法的事务管理。此类关注点通常被称为横切关注点

Spring的关键组件之一是AOP框架。 尽管Spring IOC容器不依赖于AOP,这意味着在不需要时就不需要使用AOP,但AOP是对Spring IOC的补充,可以提供功能强大的中间件解决方案。

如果该类中其他的方法也要输出方法输入参数呢?更进一步,其他类中的其他方法也要输出方法输入参数呢?如果不介意成本或者代码的优雅,当然可以一个方法一个方法像上面那样的添加代码。

但是AOP给我们另外一个更优雅的解决方案,在每个方法执行之前切入相同的逻辑。而这个切面所执行相同的逻辑,以下都会用增强这个词来替代,英文advice。

进行增强的目标有类、对象或者方法,其实最终还是执行方法之上(Spring AOP并不支持属性的增强),所以这些类、对象或者方法都可以称之为连接点(joint point),对应Java类org.aspectj.lang.JoinPoint。另外,究竟是哪些类、对象或者方法需要增强呢?这就需要通过切入点来匹配了。切入点(point cut)简称切点,对应类org.aspectj.lang.reflect.Pointcut。切点用于匹配连接点(二者不是一个概念)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-36RzQABj-1647596207548)(SpringBoot/img/webp.webp)]

1、AOP的操作步骤

1、开启注解

首先开启Aspect(通过加入注解@EnableAspectJAutoProxy开启)

2、定义自己的切面类

Aspect只能标识这个类作为一个切面,而没有作为Spring容器扫描的标识。

代码如下:

@Component
@Aspect // 切面
public class aop6 {


    /*要代理的目标类是否实现了指定的接口*/  // 切入点
    @Pointcut("@target(com.chengshiyu.springboot9_day.annotation.AnCustomAnnotation)")
    public void pointcut(){}

    @Before("pointcut()") // 增强方法
    public void before(JoinPoint joinPoint){  // 连接点 JoinPoint
        System.out.println("before-------------"); // weave  直入
        Object[] args = joinPoint.getArgs();
        if (args.length < 1){
            System.out.println("no args");
            return;
        }
        if (args.length == 1){
            System.out.println(args[0]);
            return;
        }
        StringBuilder builder = new StringBuilder();
        for (Object arg : args) {
            builder.append(arg).append(",");
        }
        builder.delete(builder.length()-1,builder.length());
        System.out.println(builder.toString());
    }


    @After("pointcut()")
    public void after(JoinPoint joinPoint){
        System.out.println("after----------------");
        Object[] args = joinPoint.getArgs();   // 获取的永远是连接点方法里面的值
        if (args.length < 1){
            System.out.println("no args");
            return;
        }
        if (args.length == 1){
            System.out.println(args[0]);
            return;
        }
        StringBuilder builder = new StringBuilder();
        for (Object arg : args) {
            builder.append(arg).append(",");
        }
        builder.delete(builder.length()-1,builder.length());
        System.out.println(builder.toString());
    }
}

一定要有@Component @Aspect

首先类com.example.aop.anno.AnCustomAspect上面必须添加org.aspectj.lang.annotation.Aspect注解,标识当前类作为一个切面。同时要注意加上org.springframework.stereotype.Component注解保证能被Spring容器扫描并注册、管理,因为Spring中切面编程是针对于容器中的bean的。

因为只有给IOC容器管理,我们的spring的一些注解才会有用!!!

3、编写切入点

关于切入点,连接点,增强方法以及通知下面会有讲解,

4、编写增强方法

5、联系连接点

6、进行增强

注意事项:

  • 如果要使⽤Spring aop⾯向切⾯编程,调⽤切⼊点⽅法的对象必须通过Spring容器获取
  • 如果⼀个类中的⽅法被声明为切⼊点并且织⼊了切点之后,通过Spring容器获取该类对象,实则获取到的是⼀个代理对象
  • 如果⼀个类中的⽅法没有被声明为切⼊点,通过Spring容器获取的就是这个类真实创建的对象

2、专业名字

  1. 切面 Advior
  2. 切点 poincut
  3. 连接点 JoinPoint
  4. 增强方法 Active
  5. 织入 waver
  6. 目标 target
  7. 代理 proxy
  1. 前置增强 @before
  2. 后置增强 @after
  3. 环绕增强 @around
  4. 抛出增强 @throws
  5. 引入增强 @Introduction

3、切入点详解

切入点:就是我们的增强方法具体要执行到谁头上

比如切西瓜,如果西瓜是连接点,那么,有很多西瓜,你具体切开哪个西瓜,你才能吃哪个西瓜,同样的道理,很多连接点,你要增强的那个方法就是切入点,其余没有增强的方法,仍然是连接点

比如UserDaoImpl中有add,delete,update等方法,那么如果你要增强add方法,那么,就是这个add就是你的切入点

具体使用:如下

一个通过@Pointcut标识的方法,也就是切点,通过切点表达式匹配需要增强的那些类、对象或方法。切点表达式是在org.aspectj.lang.annotation.Pointcut注解属性中来定义的。

3.1、aop中切入点路径详解

示例:

    /*public com.chengshiyu.springboot9.Service.impl.OrderServiceImpl.add(User user,String a ,String b)*/

@Pointcut("execution(* com.chengshiyu.springboot9_day.Service..*.*(..))")
public void pointcut(){}

源码:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
            throws-pattern?)

modifiers-pattern:匹配方法修饰符 publicor protected
ret-type-pattern:匹配方法返回类型 *代表所有类型
declaring-type-pattern:匹配类名称 可以包含.或...前者代表单层目录,后者代表任意层级目录。
name-pattern:匹配方法名称 *匹配任意方法名称
param-pattern:匹配参数名称 如果是()代表没有方法参数,(..)匹配任意个数或类型参数,(*)匹配一个任意类型的参数,(*,String)匹配两个参数,第一个任意类型,第二个参数必须为String类型。
throws-pattern:匹配方法异常类型
其中returning type pattern、name pattern, param-pattern是必须的,其他的可以不需要。比如execution(* set*(..))就是一个最简单的,ret-type-pattern为*(匹配任意返回类型的方法),name-pattern为set*(匹配方法名称以set开头的方法),param-pattern为…(匹配任意类型或者数量的方法参数),其他的没有定义,完整意思就是:匹配所有方法名以set开头的方法

3.2、execution

官方解释:

for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP

  • image-20220318004820776

具体代码实现

service层代码

package com.chengshiyu.springboot9_day.Service;

import com.chengshiyu.springboot9_day.Entity.User;

/**
 * @author 程世玉
 * @create 2022/3/17 17:25
 * @PROJECT_NAME Second-SpringBootTest
 * @Description
 */
public interface OrderService {

    public User add(User user);

    public int register(User user);

}
package com.chengshiyu.springboot9_day.Service.impl;

import com.chengshiyu.springboot9_day.Entity.User;
import com.chengshiyu.springboot9_day.Service.OrderService;
import org.springframework.stereotype.Service;

/**
 * @author 程世玉
 * @create 2022/3/17 17:27
 * @PROJECT_NAME Second-SpringBootTest
 * @Description
 */
@Service
public class OrderServiceImpl implements OrderService {


    @Override
    public User add(User user) {
        System.out.println("add方法体,执行了具体的代码逻辑");
        user.setUsername("add修改了!!");
        return user;
    }

    @Override
    public int register(User user) {
        System.out.println("register方法体,执行了具体的代码逻辑");
        return 0;
    }
}

AOP层次

package com.chengshiyu.springboot9_day.Config;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author 程世玉
 * @create 2022/3/17 17:30
 * @PROJECT_NAME Second-SpringBootTest
 * @Description
 */
@Component
@Aspect
public class aop1 {
    /*public com.chengshiyu.springboot9.Service.impl.OrderServiceImpl.add(User user,String a ,String b)*/
    /* * com.chengshiyu.springboot9_day..*.*(..)*/
    @Pointcut("execution(* com.chengshiyu.springboot9_day.Service..*.*(..))")
    public void pointcut(){}


    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("before-------------");
        Object[] args = joinPoint.getArgs();
        if (args.length < 1){
            System.out.println("no args");
            return;
        }
        if (args.length == 1){
            System.out.println(args[0]);
            return;
        }
        StringBuilder builder = new StringBuilder();
        for (Object arg : args) {
            builder.append(arg).append(",");
        }
        builder.delete(builder.length()-1,builder.length());
        System.out.println(builder.toString());
    }


    @After("pointcut()")
    public void after(JoinPoint joinPoint){
        System.out.println("after----------------");
        Object[] args = joinPoint.getArgs();   // 获取的永远是连接点方法里面的值
        if (args.length < 1){
            System.out.println("no args");
            return;
        }
        if (args.length == 1){
            System.out.println(args[0]);
            return;
        }
        StringBuilder builder = new StringBuilder();
        for (Object arg : args) {
            builder.append(arg).append(",");
        }
        builder.delete(builder.length()-1,builder.length());
        System.out.println(builder.toString());
    }
}

提前说明一下:

Object[] args = joinPoint.getArgs();

这一句,getArgs,只能获取到参数里面的东西,不是获取我们返回值,也就是说,即使你用的After后置增强,也获得到的也仅仅是他参数变化之后的值,并不能得到他的返回值

结论:

  1. ```@Pointcut(“execution(* com.chengshiyu.springboot9_day.Service….(…))”)`注解扫描的就是service下所有的包
  2. 无论是@After 还是@Before注解,方法参数都是JoinPoint,获取到的args都是参数里面的值,获取到的都是连接点方法里面的参数的值,不会获取到方法return返回的值

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐