Spring AOP详解

概念

OOP 和 AOP区别

  • oop是一种面向对象的程序设计,aop是一种面向切面的编程技术,是对oop的补充和完善。

AOP适用场景

  • 保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。
  • 组件代码与业务代码解耦。例如日志功能、事务功能、异常处理、统一拦截、数据提取等。很多开源组件都是利用AOP的面向切面编程特性实现零侵入的。
  • 代码高度复用、功能可配置。

AOP分类

  • 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

143

AOP概念

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象
  • 连接点(Join point):程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候
  • 通知(Advice):在切面(Aspect)的某个特定连接点上(Join point)执行的动作。通知的类型包括"around","before","after"等等。
  • 切入点(Pointcut):匹配连接点(Join point)的断言。通知(Advice)跟切入点表达式关联,并在与切入点匹配的任何连接点上面运行。
  • 引入(Introduction):声明额外的方法或字段。Spring AOP允许你向任何被通知(Advice)对象引入一个新的接口(及其实现类)。
  • 目标对象(Target object):被一个或多个切面(Aspect)所通知(Advice)的对象,也称作被通知对象。
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)
  • 织入(Weaving):把切面(aspect)连接到其他的应用程序类型或者对象上,并创建一个被通知对象。

使用

Pointcut(切入点)

使用execution表达式

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)  

除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

实例:

execution(* com.sample.service.impl..*.*(..)

* execution : 				表达式主体
* 第一个* : 				  返回值的类型任意
* com.sample.service.impl:  AOP所切服务的包名,即,我们的业务部分
* 包名后面的.. :              表示当前包及子包
* 第二个* :				  表示类名,*即所有类
* .*(..) : 					表示任何方法名,括号表示参数,两个点表示任何参数类型

使用within匹配包类型

//匹配ProductServiceImpl类里面的所有方法
@Pointcut("within(com.aop.service.impl.ProductServiceImpl)")
public void matchType() {}

//匹配com.aop.service包及其子包下所有类的方法
@Pointcut("within(com.aop.service..*)")
public void matchPackage() {}

使用this target bean表达式匹配对象类型

//匹配AOP对象的目标对象为指定类型的方法,即ProductServiceImpl的aop代理对象的方法
@Pointcut("this(com.aop.service.impl.ProductServiceImpl)")
public void matchThis() {}

//匹配实现ProductService接口的目标对象
@Pointcut("target(com.aop.service.ProductService)")
public void matchTarget() {}

//匹配所有以Service结尾的bean里面的方法
@Pointcut("bean(*Service)")
public void matchBean() {}

使用args匹配参数

//匹配第一个参数为Long类型的方法
@Pointcut("args(Long, ..) ")
public void matchArgs() {}

使用@annotation、@within、@target、@args匹配注解

//匹配标注有AdminOnly注解的方法
@Pointcut("@annotation(com.aop.annotation.AdminOnly)")
public void matchAnno() {}

//匹配标注有Beta的类底下的方法,要求annotation的Retention级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void matchWithin() {}

//匹配标注有Repository的类底下的方法,要求annotation的Retention级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void matchTarget() {}

//匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void matchArgs() {}

Advice(增强/通知)

@Before前置通知

前置通知在切入点运行前执行,不会影响切入点的逻辑

@After后置通知

后置通知在切入点正常运行结束后执行,如果切入点抛出异常,则在抛出异常前执行

@AfterThrowing异常通知

异常通知在切入点抛出异常前执行,如果切入点正常运行(未抛出异常),则不执行

@AfterReturning返回通知

返回通知在切入点正常运行结束后执行,如果切入点抛出异常,则不执行

@Around环绕通知

环绕通知是功能最强大的通知,可以在切入点执行前后自定义一些操作。环绕通知需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行

实例

@Aspect
@Component
public class LogAdvice {
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    /**
     * 横切所有controller下的public方法
     */
    @Pointcut("execution(public * com.ctc.controller.*.*(..))")
    public void controllerPointCut(){}
 
    /**
     * 横切所有service下的public方法
     */
    @Pointcut("execution(public * com.ctc.service.*.*(..))")
    public void servicePointCut(){}
 
    /**
     * 环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("controllerPointCut()")
    public BaseResponse aroundController(ProceedingJoinPoint  joinPoint) throws Throwable{
        String uuid = UUID.randomUUID().toString().replace("-", "");
        // 添加日志链
        MDC.put("mdcId", uuid);
        // 获取Controller入参
        Object[] objects = joinPoint.getArgs();
        String request = "";
        for (Object o : objects) {
            if (o instanceof BaseRequest) {
                request = o.toString();
                break;
            }
        }
        String methodName = joinPoint.getSignature().getName();
        logger.info("请求开始: methodName = {}, request = {}", methodName, request);
        long startTime = System.currentTimeMillis();
        BaseResponse response = (BaseResponse) joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;
        logger.info("请求结束: methodName = {}, result = {}, 执行时间: time = {}ms", methodName, response, executeTime);
        if (MDC.get("reqId") != null) {
            // 请求结束后移除日志链
            MDC.remove("reqId");
        }
        return response;
    }
 
    /**
     * 前置通知业务层
     */
    @Before("servicePointCut()" )
    public void aroundService(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        // 获取Service入参
        Object[] objects = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < objects.length; i++) {
            sb.append(names[i] + " = " + objects[i]);
            if (i != objects.length) {
                sb.append(", ");
            }
        }
        String methodName = methodSignature.getName();
        logger.info("执行方法:{}; {}", methodName, sb.toString());
    }
}
@RestController
@RequestMapping("/test")
public class TestController {
 
    @Autowired
    private TestService testService;
 
    @GetMapping("/hello")
    public BaseResponse getInfo() {
        return ResponseUtil.getSuccessResponse();
    }
 
    @PostMapping("/post")
    public BaseResponse getInfo2(@RequestBody ProductInfoRequest request) {
        testService.test1();
        testService.test2(1, "adff", request);
        return ResponseUtil.getSuccessResponse(request);
    }
}
 
@Service
public class TestServiceImpl implements TestService {
 
    @Override
    public void test1() {
        test3();
    }
 
    @Override
    public void test2(int i, String s, BaseRequest request) {
    }
    
    // 该示例中test3不会被横切到,因为其方法修饰符为private
    private void test3() {
    }
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×